chore: eslint with strict type imports

This commit is contained in:
Sylver 2024-02-12 19:04:42 +08:00
parent f849e80984
commit ee0c6ffa4a
74 changed files with 1065 additions and 4564 deletions

View File

@ -4,7 +4,7 @@
"npm.scriptExplorerExclude": ["^((?!watch|generate:watch).)*$"], "npm.scriptExplorerExclude": ["^((?!watch|generate:watch).)*$"],
"eslint.workingDirectories": [ "eslint.workingDirectories": [
{ {
"pattern": "./{packages,apps}/*" "pattern": "./packages/*"
} }
], ],
"files.associations": { "files.associations": {

View File

@ -5,6 +5,7 @@ RUN npm i -g pnpm
WORKDIR /usr/src/micro WORKDIR /usr/src/micro
COPY patches patches
COPY pnpm-lock.yaml pnpm-workspace.yaml ./ COPY pnpm-lock.yaml pnpm-workspace.yaml ./
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \ RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
pnpm fetch pnpm fetch

View File

@ -18,7 +18,7 @@
}, },
"devDependencies": { "devDependencies": {
"syncpack": "^12.3.0", "syncpack": "^12.3.0",
"turbo": "1.11.3", "turbo": "1.12.3",
"typescript": "^5.3.3" "typescript": "^5.3.3"
}, },
"packageManager": "pnpm@7.0.0" "packageManager": "pnpm@7.0.0"

View File

@ -5,5 +5,7 @@ module.exports = {
}, },
rules: { rules: {
'unicorn/no-abusive-eslint-disable': 'off', 'unicorn/no-abusive-eslint-disable': 'off',
'unicorn/filename-case': 'off',
'import/no-default-export': 'off',
}, },
}; };

View File

@ -26,24 +26,24 @@
"@mikro-orm/migrations": "^5.9.7", "@mikro-orm/migrations": "^5.9.7",
"@mikro-orm/nestjs": "^5.2.3", "@mikro-orm/nestjs": "^5.2.3",
"@mikro-orm/postgresql": "^5.9.7", "@mikro-orm/postgresql": "^5.9.7",
"@nestjs/common": "^10.3.0", "@nestjs/common": "^10.3.2",
"@nestjs/core": "^10.3.0", "@nestjs/core": "^10.3.2",
"@nestjs/graphql": "^12.0.11", "@nestjs/graphql": "^12.1.1",
"@nestjs/jwt": "^10.2.0", "@nestjs/jwt": "^10.2.0",
"@nestjs/mercurius": "^12.0.11", "@nestjs/mercurius": "^12.1.1",
"@nestjs/passport": "^10.0.3", "@nestjs/passport": "^10.0.3",
"@nestjs/platform-fastify": "^10.3.0", "@nestjs/platform-fastify": "^10.3.2",
"@nestjs/schedule": "^4.0.0", "@nestjs/schedule": "^4.0.1",
"@ryanke/venera": "^1.0.5", "@ryanke/venera": "^1.0.5",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.0", "class-validator": "^0.14.0",
"fastify": "^4.25.2", "fastify": "^4.26.0",
"fluent-ffmpeg": "^2.1.2", "fluent-ffmpeg": "^2.1.2",
"graphql": "^16.8.1", "graphql": "^16.8.1",
"mercurius": "^13.3.3", "mercurius": "^13.3.3",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
"nodemailer": "^6.9.8", "nodemailer": "^6.9.9",
"otplib": "^12.0.1", "otplib": "^12.0.1",
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",
@ -55,7 +55,7 @@
"devDependencies": { "devDependencies": {
"@atlasbot/configs": "^10.5.15", "@atlasbot/configs": "^10.5.15",
"@mikro-orm/cli": "^5.9.7", "@mikro-orm/cli": "^5.9.7",
"@swc/core": "^1.3.102", "@swc/core": "^1.4.0",
"@types/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
"@types/bytes": "^3.1.4", "@types/bytes": "^3.1.4",
"@types/dedent": "^0.7.2", "@types/dedent": "^0.7.2",
@ -63,7 +63,7 @@
"@types/luxon": "^3.4.0", "@types/luxon": "^3.4.0",
"@types/mime-types": "^2.1.4", "@types/mime-types": "^2.1.4",
"@types/ms": "^0.7.34", "@types/ms": "^0.7.34",
"@types/node": "^20.10.6", "@types/node": "^20.11.17",
"@types/nodemailer": "^6.4.14", "@types/nodemailer": "^6.4.14",
"@types/passport-jwt": "^4.0.0", "@types/passport-jwt": "^4.0.0",
"@types/utf-8-validate": "^5.0.2", "@types/utf-8-validate": "^5.0.2",
@ -72,21 +72,21 @@
"content-range": "^2.0.2", "content-range": "^2.0.2",
"dedent": "^1.5.1", "dedent": "^1.5.1",
"escape-string-regexp": "^5.0.0", "escape-string-regexp": "^5.0.0",
"file-type": "^18.7.0", "file-type": "^19.0.0",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"istextorbinary": "^9.5.0", "istextorbinary": "^9.5.0",
"luxon": "^3.4.4", "luxon": "^3.4.4",
"ms": "^3.0.0-canary.1", "ms": "^3.0.0-canary.1",
"nanoid": "^5.0.4", "nanoid": "^5.0.5",
"normalize-url": "^8.0.0", "normalize-url": "^8.0.0",
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
"reflect-metadata": "^0.2.1", "reflect-metadata": "^0.2.1",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tsup": "^8.0.1", "tsup": "^8.0.2",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vitest": "^1.1.3", "vitest": "^1.2.2",
"zod": "^3.22.4", "zod": "^3.22.4",
"zod-validation-error": "^2.1.0" "zod-validation-error": "^3.0.0"
}, },
"mikro-orm": { "mikro-orm": {
"useTsNode": true, "useTsNode": true,

View File

@ -11,8 +11,8 @@ import { Transform } from 'stream';
export class ExifTransformer extends Transform { export class ExifTransformer extends Transform {
private static readonly app1Marker = Buffer.from('ffe1', 'hex'); private static readonly app1Marker = Buffer.from('ffe1', 'hex');
private static readonly exifMarker = Buffer.from('457869660000', 'hex'); // Exif\0\0 private static readonly exifMarker = Buffer.from('457869660000', 'hex'); // Exif\0\0
private static readonly xmpMarker = Buffer.from('http://ns.adobe.com/xap', 'utf-8'); private static readonly xmpMarker = Buffer.from('http://ns.adobe.com/xap', 'utf8');
private static readonly flirMarker = Buffer.from('FLIR', 'utf-8'); private static readonly flirMarker = Buffer.from('FLIR', 'utf8');
private static readonly maxMarkerLength = Math.max( private static readonly maxMarkerLength = Math.max(
ExifTransformer.exifMarker.length, ExifTransformer.exifMarker.length,
ExifTransformer.xmpMarker.length, ExifTransformer.xmpMarker.length,
@ -48,7 +48,7 @@ export class ExifTransformer extends Transform {
// no app1 in the current pendingChunk // no app1 in the current pendingChunk
if (app1Start === -1) { if (app1Start === -1) {
// if last byte is ff, wait for more // if last byte is ff, wait for more
if (!atEnd && pendingChunk[pendingChunk.length - 1] === ExifTransformer.app1Marker[0]) { if (!atEnd && pendingChunk.at(-1) === ExifTransformer.app1Marker[0]) {
if (chunk) this.pending.push(chunk); if (chunk) this.pending.push(chunk);
return; return;
} }

View File

@ -80,7 +80,7 @@ const enhanceHost = (host: z.infer<typeof schema>['hosts'][0]) => {
}; };
export const config = result.data as Omit<z.infer<typeof schema>, 'hosts'>; export const config = result.data as Omit<z.infer<typeof schema>, 'hosts'>;
export const hosts = result.data.hosts.map(enhanceHost); export const hosts = result.data.hosts.map((host) => enhanceHost(host));
export const rootHost = hosts[0]; export const rootHost = hosts[0];
if (rootHost.isWildcard) { if (rootHost.isWildcard) {
@ -92,9 +92,9 @@ if (disallowed.has(config.secret.toLowerCase())) {
const token = randomBytes(24).toString('hex'); const token = randomBytes(24).toString('hex');
throw new Error( throw new Error(
dedent` dedent`
${c.redBright.bold('Do not use the default secret.')} ${c.redBright.bold('Do not use the default secret.')}
Please generate a random, secure secret or you risk anyone being able to impersonate you. Please generate a random, secure secret or you risk anyone being able to impersonate you.
If you're lazy, here is a random secret: ${c.underline(token)} If you're lazy, here is a random secret: ${c.underline(token)}
`, `,
); );
} }

View File

@ -41,7 +41,7 @@ export const expandMime = (input: string | string[]) => {
if (!Array.isArray(input)) input = [input]; if (!Array.isArray(input)) input = [input];
const output: string[] = []; const output: string[] = [];
for (const mimeType of input) { for (const mimeType of input) {
const alias = MIME_MAP.get(mimeType.replace(WILDCARD_REGEX, '')); const alias = MIME_MAP.get(mimeType.replaceAll(WILDCARD_REGEX, ''));
if (alias) { if (alias) {
output.push(...alias); output.push(...alias);
continue; continue;

View File

@ -18,8 +18,7 @@ export function parseCursor(cursor: string) {
export function paginate<T>(items: T[], total: number, offset: number): Paginated<T> { export function paginate<T>(items: T[], total: number, offset: number): Paginated<T> {
const edges: Edge<T>[] = []; const edges: Edge<T>[] = [];
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { for (const [itemIndex, item] of items.entries()) {
const item = items[itemIndex];
const cursor = createCursor(offset + itemIndex); const cursor = createCursor(offset + itemIndex);
edges.push({ edges.push({
cursor: cursor, cursor: cursor,
@ -31,7 +30,7 @@ export function paginate<T>(items: T[], total: number, offset: number): Paginate
edges: edges, edges: edges,
totalCount: total, totalCount: total,
pageInfo: { pageInfo: {
endCursor: edges[0] ? edges[edges.length - 1].cursor : undefined, endCursor: edges[0] ? edges.at(-1)!.cursor : undefined,
startCursor: edges[0] ? edges[0].cursor : undefined, startCursor: edges[0] ? edges[0].cursor : undefined,
hasPreviousPage: offset > 0, hasPreviousPage: offset > 0,
hasNextPage: offset + items.length < total, hasNextPage: offset + items.length < total,

View File

@ -1,5 +1,4 @@
/* eslint-disable sonarjs/no-duplicate-string */ import type { Ref } from '@mikro-orm/core';
import type { IdentifiedReference } from '@mikro-orm/core';
import { BeforeCreate, Entity, Property, type EventArgs } from '@mikro-orm/core'; import { BeforeCreate, Entity, Property, type EventArgs } from '@mikro-orm/core';
import { ObjectType } from '@nestjs/graphql'; import { ObjectType } from '@nestjs/graphql';
import type { FastifyRequest } from 'fastify'; import type { FastifyRequest } from 'fastify';
@ -14,7 +13,7 @@ export abstract class Resource {
@Property({ nullable: true }) @Property({ nullable: true })
hostname?: string; hostname?: string;
abstract owner?: IdentifiedReference<User>; abstract owner?: Ref<User>;
abstract getPaths(): ResourceLocations; abstract getPaths(): ResourceLocations;
getUrls() { getUrls() {

View File

@ -11,64 +11,56 @@ import { migrate } from './migrate.js';
import { AppModule } from './modules/app.module.js'; import { AppModule } from './modules/app.module.js';
import { HostGuard } from './modules/host/host.guard.js'; import { HostGuard } from './modules/host/host.guard.js';
async function bootstrap() { await migrate();
await migrate();
const logger = new Logger('bootstrap'); const logger = new Logger('bootstrap');
const server = fastify({ const server = fastify({
trustProxy: process.env.TRUST_PROXY === 'true', trustProxy: process.env.TRUST_PROXY === 'true',
maxParamLength: 500, maxParamLength: 500,
bodyLimit: config.uploadLimit, bodyLimit: config.uploadLimit,
}); });
const adapter = new FastifyAdapter(server as any); const adapter = new FastifyAdapter(server as any);
const app = await NestFactory.create<NestFastifyApplication>(AppModule, adapter); const app = await NestFactory.create<NestFastifyApplication>(AppModule, adapter);
app.useGlobalGuards(new HostGuard()); app.useGlobalGuards(new HostGuard());
app.useGlobalPipes( app.useGlobalPipes(
new ValidationPipe({ new ValidationPipe({
whitelist: true, whitelist: true,
forbidNonWhitelisted: true, forbidNonWhitelisted: true,
forbidUnknownValues: true, forbidUnknownValues: true,
exceptionFactory(errors) { exceptionFactory(errors) {
// without this, nestjs won't include validation errors in the graphql response, // without this, nestjs won't include validation errors in the graphql response,
// just a blank bad request error, which is just a little confusing. thanks nestjs! // just a blank bad request error, which is just a little confusing. thanks nestjs!
const formattedErrors = errors.map((error) => { const formattedErrors = errors.map((error) => {
if (error.constraints) { if (error.constraints) {
const constraints = Object.values(error.constraints); const constraints = Object.values(error.constraints);
if (constraints[0]) return constraints.join(', '); if (constraints[0]) return constraints.join(', ');
} }
return error.toString(); return error.toString();
}); });
return new BadRequestException(formattedErrors.join('\n')); return new BadRequestException(formattedErrors.join('\n'));
}, },
transformOptions: { transformOptions: {
enableImplicitConversion: true, enableImplicitConversion: true,
}, },
}) }),
); );
await app.register(fastifyCookie as any); await app.register(fastifyCookie as any);
await app.register(fastifyHelmet.default as any); await app.register(fastifyHelmet.default as any);
await app.register(fastifyMultipart.default as any, { await app.register(fastifyMultipart.default as any, {
limits: { limits: {
fieldNameSize: 100, fieldNameSize: 100,
fieldSize: 100, fieldSize: 100,
fields: 0, fields: 0,
files: 1, files: 1,
headerPairs: 20, headerPairs: 20,
}, },
}); });
await app.listen(8080, '0.0.0.0', (error, address) => { await app.listen(8080, '0.0.0.0', (error, address) => {
if (error) throw error; if (error) throw error;
logger.log(`Listening at ${address}`); logger.log(`Listening at ${address}`);
});
}
// top-level await is not supported by ncc
bootstrap().catch((error) => {
console.error(error);
process.exit(1);
}); });

View File

@ -1,9 +1,9 @@
import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { Controller, Get, Req, UseGuards } from '@nestjs/common';
import type { FastifyRequest } from 'fastify';
import { config, hosts, rootHost } from '../config.js'; import { config, hosts, rootHost } from '../config.js';
import { UserId } from './auth/auth.decorators.js'; import { UserId } from './auth/auth.decorators.js';
import { OptionalJWTAuthGuard } from './auth/guards/optional-jwt.guard.js'; import { OptionalJWTAuthGuard } from './auth/guards/optional-jwt.guard.js';
import { UserService } from './user/user.service.js'; import { UserService } from './user/user.service.js';
import { type FastifyRequest } from 'fastify';
@Controller() @Controller()
export class AppController { export class AppController {
@ -23,7 +23,7 @@ export class AppController {
return { return {
inquiries: config.inquiries, inquiries: config.inquiries,
uploadLimit: config.uploadLimit, uploadLimit: config.uploadLimit,
allowTypes: config.allowTypes ? [...config.allowTypes?.values()] : undefined, allowTypes: config.allowTypes ? [...config.allowTypes.values()] : undefined,
email: !!config.email, email: !!config.email,
rootHost: { rootHost: {
url: rootHost.url, url: rootHost.url,

View File

@ -25,7 +25,7 @@ export class AppResolver {
return { return {
inquiriesEmail: config.inquiries, inquiriesEmail: config.inquiries,
uploadLimit: config.uploadLimit, uploadLimit: config.uploadLimit,
allowTypes: config.allowTypes ? [...config.allowTypes?.values()] : [], allowTypes: config.allowTypes ? [...config.allowTypes.values()] : [],
requireEmails: !!config.email, requireEmails: !!config.email,
rootHost: this.filterHost(rootHost), rootHost: this.filterHost(rootHost),
currentHost: this.filterHost(currentHost), currentHost: this.filterHost(currentHost),

View File

@ -14,7 +14,7 @@ import {
Res, Res,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import type { FastifyReply, FastifyRequest } from 'fastify'; import { type FastifyReply, type FastifyRequest } from 'fastify';
import { rootHost } from '../../config.js'; import { rootHost } from '../../config.js';
import { UserId } from '../auth/auth.decorators.js'; import { UserId } from '../auth/auth.decorators.js';
import { JWTAuthGuard } from '../auth/guards/jwt.guard.js'; import { JWTAuthGuard } from '../auth/guards/jwt.guard.js';

View File

@ -72,8 +72,13 @@ export class File extends Resource {
} }
getDisplayName() { getDisplayName() {
if (this.name) return this.name;
const extension = this.getExtension(); const extension = this.getExtension();
return this.name ? this.name : extension ? `${this.id}.${extension}` : this.id; if (extension) {
return `${this.id}.${extension}`;
}
return this.id;
} }
getPaths() { getPaths() {

View File

@ -118,8 +118,9 @@ export class FileService implements OnApplicationBootstrap {
uploadStream = uploadStream.pipe(transformer).pipe(new PassThrough()); uploadStream = uploadStream.pipe(transformer).pipe(new PassThrough());
break; break;
} }
default: default: {
throw new Error(`Unknown or unsupported conversion ${fromGroup} to ${toGroup}`); throw new Error(`Unknown or unsupported conversion ${fromGroup} to ${toGroup}`);
}
} }
} }

View File

@ -1,7 +1,7 @@
import { InjectRepository } from '@mikro-orm/nestjs'; import { InjectRepository } from '@mikro-orm/nestjs';
import { EntityRepository } from '@mikro-orm/postgresql'; import { EntityRepository } from '@mikro-orm/postgresql';
import { Controller, Get, Param, Request, Res } from '@nestjs/common'; import { Controller, Get, Param, Request, Res } from '@nestjs/common';
import type { FastifyReply, FastifyRequest } from 'fastify'; import { type FastifyReply, type FastifyRequest } from 'fastify';
import { Link } from './link.entity.js'; import { Link } from './link.entity.js';
import { LinkService } from './link.service.js'; import { LinkService } from './link.service.js';
@ -9,7 +9,7 @@ import { LinkService } from './link.service.js';
export class LinkController { export class LinkController {
constructor( constructor(
@InjectRepository(Link) private readonly linkRepo: EntityRepository<Link>, @InjectRepository(Link) private readonly linkRepo: EntityRepository<Link>,
private readonly linkService: LinkService private readonly linkService: LinkService,
) {} ) {}
@Get('link/:id') @Get('link/:id')

View File

@ -1,5 +1,5 @@
import { Controller, Get, Param, Req } from '@nestjs/common'; import { Controller, Get, Param, Req } from '@nestjs/common';
import type { FastifyRequest } from 'fastify'; import { type FastifyRequest } from 'fastify';
import { PasteService } from './paste.service.js'; import { PasteService } from './paste.service.js';
@Controller('paste') @Controller('paste')

View File

@ -1,5 +1,5 @@
import { Controller, Get, Param, Req, Res } from '@nestjs/common'; import { Controller, Get, Param, Req, Res } from '@nestjs/common';
import type { FastifyReply, FastifyRequest } from 'fastify'; import { type FastifyReply, type FastifyRequest } from 'fastify';
import { ThumbnailService } from './thumbnail.service.js'; import { ThumbnailService } from './thumbnail.service.js';
@Controller() @Controller()
@ -10,7 +10,7 @@ export class ThumbnailController {
async getThumbnailContent( async getThumbnailContent(
@Param('fileId') fileId: string, @Param('fileId') fileId: string,
@Req() request: FastifyRequest, @Req() request: FastifyRequest,
@Res() reply: FastifyReply @Res() reply: FastifyReply,
) { ) {
return this.thumbnailService.sendThumbnail(fileId, request, reply); return this.thumbnailService.sendThumbnail(fileId, request, reply);
} }

View File

@ -1,3 +1,4 @@
/* eslint-disable no-await-in-loop */
import { EntityRepository } from '@mikro-orm/core'; import { EntityRepository } from '@mikro-orm/core';
import { InjectRepository } from '@mikro-orm/nestjs'; import { InjectRepository } from '@mikro-orm/nestjs';
import { BadRequestException, Injectable, Logger } from '@nestjs/common'; import { BadRequestException, Injectable, Logger } from '@nestjs/common';
@ -23,7 +24,7 @@ export class ThumbnailService {
private static readonly IMAGE_TYPES = new Set( private static readonly IMAGE_TYPES = new Set(
Object.keys(sharp.format) Object.keys(sharp.format)
.map((key) => mime.lookup(key)) .map((key) => mime.lookup(key))
.filter((key) => key && key.startsWith('image')) .filter((key) => key && key.startsWith('image')),
); );
private static readonly VIDEO_TYPES = new Set([ private static readonly VIDEO_TYPES = new Set([
@ -42,7 +43,7 @@ export class ThumbnailService {
@InjectRepository('Thumbnail') private readonly thumbnailRepo: EntityRepository<Thumbnail>, @InjectRepository('Thumbnail') private readonly thumbnailRepo: EntityRepository<Thumbnail>,
@InjectRepository('File') private readonly fileRepo: EntityRepository<File>, @InjectRepository('File') private readonly fileRepo: EntityRepository<File>,
private readonly storageService: StorageService, private readonly storageService: StorageService,
private readonly fileService: FileService private readonly fileService: FileService,
) {} ) {}
async getThumbnail(fileId: string) { async getThumbnail(fileId: string) {
@ -111,8 +112,7 @@ export class ThumbnailService {
// and it is so whatever. maybe there is a way to do this faster, but this is already pretty fast. // and it is so whatever. maybe there is a way to do this faster, but this is already pretty fast.
const positions = ['5%', '10%', '20%', '40%']; const positions = ['5%', '10%', '20%', '40%'];
const size = `${ThumbnailService.THUMBNAIL_SIZE}x?`; const size = `${ThumbnailService.THUMBNAIL_SIZE}x?`;
for (let positionIndex = 0; positionIndex < positions.length; positionIndex++) { for (const [positionIndex, percent] of positions.entries()) {
const percent = positions[positionIndex];
const stream = ffmpeg(filePath).screenshot({ const stream = ffmpeg(filePath).screenshot({
count: 1, count: 1,
timemarks: [percent], timemarks: [percent],

View File

@ -1,5 +1,5 @@
import { Controller, Get, Param, Response } from '@nestjs/common'; import { Controller, Get, Param, Response } from '@nestjs/common';
import type { FastifyReply } from 'fastify'; import { type FastifyReply } from 'fastify';
import { UserService } from './user.service.js'; import { UserService } from './user.service.js';
@Controller() @Controller()
@ -10,7 +10,7 @@ export class UserController {
async verifyUser( async verifyUser(
@Param('userId') userId: string, @Param('userId') userId: string,
@Param('verifyId') verifyId: string, @Param('verifyId') verifyId: string,
@Response() reply: FastifyReply @Response() reply: FastifyReply,
) { ) {
await this.userService.verifyUser(userId, verifyId); await this.userService.verifyUser(userId, verifyId);
return reply.redirect(302, '/login?verified=true'); return reply.redirect(302, '/login?verified=true');

View File

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/consistent-type-assertions */
/* eslint-disable import/no-default-export */
import { FlushMode } from '@mikro-orm/core'; import { FlushMode } from '@mikro-orm/core';
import type { MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs'; import type { MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs';
import { Logger, NotFoundException } from '@nestjs/common'; import { Logger, NotFoundException } from '@nestjs/common';

View File

@ -1,4 +1,3 @@
/* eslint-disable unicorn/no-keyword-prefix */
import type { Type } from '@nestjs/common'; import type { Type } from '@nestjs/common';
import { Field, ObjectType } from '@nestjs/graphql'; import { Field, ObjectType } from '@nestjs/graphql';

View File

@ -1,4 +1,3 @@
/* eslint-disable unicorn/no-keyword-prefix */
import type { Type } from '@nestjs/common'; import type { Type } from '@nestjs/common';
import { Field, Int, ObjectType } from '@nestjs/graphql'; import { Field, Int, ObjectType } from '@nestjs/graphql';
import { Edge } from './edge.type.js'; import { Edge } from './edge.type.js';

View File

@ -4,10 +4,10 @@
"compilerOptions": { "compilerOptions": {
"outDir": "dist", "outDir": "dist",
"noUncheckedIndexedAccess": false, "noUncheckedIndexedAccess": false,
"strictPropertyInitialization": false, "strictPropertyInitialization": false, // doesn't play nice with mikroorm/graphql decorators
"noImplicitOverride": false, "noImplicitOverride": false,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"lib": ["es2021", "dom"] "lib": ["es2021", "dom"],
} },
} }

View File

@ -0,0 +1,23 @@
module.exports = {
extends: require.resolve('@atlasbot/configs/eslint/react'),
parserOptions: {
project: './tsconfig.json',
},
rules: {
'@typescript-eslint/no-floating-promises': 'off',
'jsx-a11y/no-autofocus': 'off',
'jsx-a11y/media-has-caption': 'off',
'unicorn/consistent-destructuring': 'off',
'react/react-in-jsx-scope': 'off',
'unicorn/filename-case': 'off',
'import/no-default-export': 'off',
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
disallowTypeAnnotations: false,
fixStyle: 'separate-type-imports',
},
],
},
};

View File

@ -1,11 +0,0 @@
module.exports = {
extends: require.resolve('@atlasbot/configs/eslint/next'),
parserOptions: {
project: './tsconfig.json',
},
rules: {
'@typescript-eslint/no-floating-promises': 'off',
'jsx-a11y/no-autofocus': 'off',
'jsx-a11y/media-has-caption': 'off',
},
};

View File

@ -1,4 +1,4 @@
import { CodegenConfig } from '@graphql-codegen/cli'; import type { CodegenConfig } from '@graphql-codegen/cli';
export default { export default {
overwrite: true, overwrite: true,
@ -17,9 +17,6 @@ export default {
fragmentMasking: false, fragmentMasking: false,
}, },
}, },
'src/@generated/introspection.json': {
plugins: ['introspection'],
},
}, },
hooks: { hooks: {
afterAllFileWrite: ['prettier --write'], afterAllFileWrite: ['prettier --write'],

View File

@ -11,17 +11,14 @@
}, },
"scripts": { "scripts": {
"build": "tsc --noEmit && rm -rf ./dist/* && vavite build && tsup && rm -rf ./dist/server", "build": "tsc --noEmit && rm -rf ./dist/* && vavite build && tsup && rm -rf ./dist/server",
"generate": "graphql-codegen --config codegen.ts",
"start": "node ./dist/index.js", "start": "node ./dist/index.js",
"watch": "concurrently \"vavite serve\" \"pnpm generate --watch\"" "watch": "vavite serve"
}, },
"devDependencies": { "devDependencies": {
"@atlasbot/configs": "^10.5.15", "@atlasbot/configs": "^10.5.15",
"@fastify/early-hints": "^1.0.1", "@fastify/early-hints": "^1.0.1",
"@fastify/http-proxy": "^9.3.0", "@fastify/http-proxy": "^9.4.0",
"@graphql-codegen/cli": "^5.0.0", "@graphql-codegen/client-preset": "^4.2.2",
"@graphql-codegen/client-preset": "^4.1.0",
"@graphql-codegen/introspection": "^4.0.0",
"@graphql-typed-document-node/core": "^3.2.0", "@graphql-typed-document-node/core": "^3.2.0",
"@parcel/watcher": "^2.3.0", "@parcel/watcher": "^2.3.0",
"@preact/preset-vite": "^2.8.1", "@preact/preset-vite": "^2.8.1",
@ -32,20 +29,19 @@
"@urql/preact": "^4.0.4", "@urql/preact": "^4.0.4",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"concurrently": "^8.2.2",
"copy-to-clipboard": "^3.3.3", "copy-to-clipboard": "^3.3.3",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"fastify": "^4.25.2", "fastify": "^4.26.0",
"formik": "^2.4.5", "formik": "^2.4.5",
"generate-avatar": "1.4.10", "generate-avatar": "1.4.10",
"graphql": "^16.8.1", "graphql": "^16.8.1",
"http-status-codes": "^2.3.0", "http-status-codes": "^2.3.0",
"nanoid": "^5.0.4", "nanoid": "^5.0.5",
"path-to-regexp": "^6.2.1", "path-to-regexp": "^6.2.1",
"postcss": "^8.4.33", "postcss": "^8.4.35",
"preact": "^10.19.3", "preact": "^10.19.4",
"preact-render-to-string": "^6.3.1", "preact-render-to-string": "^6.3.1",
"prettier": "^3.1.1", "prettier": "^3.2.5",
"prism-react-renderer": "^2.3.1", "prism-react-renderer": "^2.3.1",
"qrcode.react": "^3.1.0", "qrcode.react": "^3.1.0",
"react": "npm:@preact/compat@^17.1.2", "react": "npm:@preact/compat@^17.1.2",
@ -55,11 +51,13 @@
"react-markdown": "^9.0.1", "react-markdown": "^9.0.1",
"remark-gfm": "^4.0.0", "remark-gfm": "^4.0.0",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"tsup": "^8.0.1", "tsup": "^8.0.2",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vavite": "^4.0.1", "vavite": "^4.0.3",
"vike": "^0.4.156", "vike": "^0.4.161",
"vite": "^5.0.11", "vite": "^5.1.1",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-graphql-codegen": "^3.3.6",
"yup": "^1.3.3" "yup": "^1.3.3"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
import React, { FC, Fragment } from 'react'; import type { FC } from 'react';
import React, { Fragment } from 'react';
import { Header } from './components/header/header'; import { Header } from './components/header/header';
import { Title } from './components/title'; import { Title } from './components/title';
import './styles/globals.css'; import './styles/globals.css';

View File

@ -1,4 +1,3 @@
/* eslint-disable react/no-danger */
import clsx from 'clsx'; import clsx from 'clsx';
import * as avatar from 'generate-avatar'; import * as avatar from 'generate-avatar';
import type { FC } from 'react'; import type { FC } from 'react';
@ -14,7 +13,7 @@ export const Avatar: FC<AvatarProps> = (props) => {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const svg = useMemo(() => { const svg = useMemo(() => {
const result = avatar.generateFromString(props.userId); const result = avatar.generateFromString(props.userId);
return result.replace(/(width|height)="(\d+)"/g, '$1="100%"'); return result.replaceAll(/(width|height)="(\d+)"/g, '$1="100%"');
}, [props.userId]); }, [props.userId]);
return <div className={classes} dangerouslySetInnerHTML={{ __html: svg }} ref={containerRef} />; return <div className={classes} dangerouslySetInnerHTML={{ __html: svg }} ref={containerRef} />;

View File

@ -1,4 +1,3 @@
/* eslint-disable react/button-has-type */
import clsx from 'clsx'; import clsx from 'clsx';
import type { FC, HTMLAttributes } from 'react'; import type { FC, HTMLAttributes } from 'react';
import { forwardRef } from 'react'; import { forwardRef } from 'react';

View File

@ -1,4 +1,4 @@
import { FC } from 'react'; import type { FC } from 'react';
import { getErrorMessage } from '../helpers/get-error-message.helper'; import { getErrorMessage } from '../helpers/get-error-message.helper';
import { usePaths } from '../hooks/usePaths'; import { usePaths } from '../hooks/usePaths';
import { Container } from './container'; import { Container } from './container';

View File

@ -12,7 +12,7 @@ import { Input } from '../input/input';
import { Link } from '../link'; import { Link } from '../link';
import { useToasts } from '../toast'; import { useToasts } from '../toast';
import { HeaderUser } from './header-user'; import { HeaderUser } from './header-user';
import { graphql } from '../../@generated'; import { graphql } from '../../@generated/gql';
import { useMutation } from '@urql/preact'; import { useMutation } from '@urql/preact';
const ResendVerificationEmail = graphql(` const ResendVerificationEmail = graphql(`

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { ComponentProps } from 'react'; import type { ComponentProps } from 'react';
import React from 'react'; import React from 'react';
import type { InputChildProps } from './container'; import type { InputChildProps } from './container';

View File

@ -1,4 +1,5 @@
import React, { ComponentProps } from 'react'; import type { ComponentProps } from 'react';
import React from 'react';
import type { InputChildProps } from './container'; import type { InputChildProps } from './container';
import { InputContainer } from './container'; import { InputContainer } from './container';

View File

@ -1,6 +1,7 @@
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import type { FC } from 'react'; import type { FC } from 'react';
import { Button, ButtonProps } from '../button'; import type { ButtonProps } from '../button';
import { Button } from '../button';
/** /**
* Wraps a button and disables when the form is not ready to be submitted. * Wraps a button and disables when the form is not ready to be submitted.

View File

@ -1,4 +1,5 @@
import { ComponentProps, forwardRef } from 'react'; import type { ComponentProps} from 'react';
import { forwardRef } from 'react';
export interface LinkProps extends ComponentProps<'a'> { export interface LinkProps extends ComponentProps<'a'> {
href: string; href: string;

View File

@ -1,5 +1,6 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { FC, Fragment, ReactNode, memo } from 'react'; import type { FC, ReactNode } from 'react';
import { Fragment, memo } from 'react';
import { BASE_BUTTON_CLASSES } from './button'; import { BASE_BUTTON_CLASSES } from './button';
import { BASE_INPUT_CLASSES, BASE_INPUT_MAX_HEIGHT } from './input/container'; import { BASE_INPUT_CLASSES, BASE_INPUT_MAX_HEIGHT } from './input/container';

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { ToastProps } from './toast'; import type { ToastProps } from './toast';
export type ToastContextData = null | ((toast: ToastProps) => void); export type ToastContextData = null | ((toast: ToastProps) => void);
export const ToastContext = React.createContext<ToastContextData>(null); export const ToastContext = React.createContext<ToastContextData>(null);

View File

@ -1,5 +1,5 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { type FC, type ReactNode } from 'react'; import type { FC, ReactNode } from 'react';
import { FiInfo } from 'react-icons/fi'; import { FiInfo } from 'react-icons/fi';
export const Warning: FC<{ children: ReactNode; className?: string }> = ({ children, className }) => { export const Warning: FC<{ children: ReactNode; className?: string }> = ({ children, className }) => {

View File

@ -1,7 +1,8 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { FC, Fragment, useState } from 'react'; import type { FC } from 'react';
import { Fragment, useState } from 'react';
import { FiDownload } from 'react-icons/fi'; import { FiDownload } from 'react-icons/fi';
import { RegularUserFragment } from '../../@generated/graphql'; import type { RegularUserFragment } from '../../@generated/graphql';
import { Container } from '../../components/container'; import { Container } from '../../components/container';
import { Section } from '../../components/section'; import { Section } from '../../components/section';
import { Skeleton, SkeletonList, SkeletonWrap } from '../../components/skeleton'; import { Skeleton, SkeletonList, SkeletonWrap } from '../../components/skeleton';

View File

@ -1,7 +1,7 @@
import { memo, useEffect, useMemo, useState } from 'react'; import { memo, useEffect, useMemo, useState } from 'react';
import { FiFileMinus, FiTrash } from 'react-icons/fi'; import { FiFileMinus, FiTrash } from 'react-icons/fi';
import { graphql } from '../../../@generated'; import { graphql } from '../../../@generated/gql';
import { FileCardFragment } from '../../../@generated/graphql'; import type { FileCardFragment } from '../../../@generated/graphql';
import { Link } from '../../../components/link'; import { Link } from '../../../components/link';
import { Skeleton } from '../../../components/skeleton'; import { Skeleton } from '../../../components/skeleton';
import { useConfig } from '../../../hooks/useConfig'; import { useConfig } from '../../../hooks/useConfig';

View File

@ -1,7 +1,7 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { memo } from 'react'; import { memo } from 'react';
import { graphql } from '../../../@generated'; import { graphql } from '../../../@generated/gql';
import { PasteCardFragment } from '../../../@generated/graphql'; import type { PasteCardFragment } from '../../../@generated/graphql';
import { Link } from '../../../components/link'; import { Link } from '../../../components/link';
import { Time } from '../../../components/time'; import { Time } from '../../../components/time';
import { useUser } from '../../../hooks/useUser'; import { useUser } from '../../../hooks/useUser';

View File

@ -1,7 +1,7 @@
import { useQuery } from '@urql/preact'; import { useQuery } from '@urql/preact';
import type { FC } from 'react'; import type { FC } from 'react';
import { Fragment } from 'react'; import { Fragment } from 'react';
import { graphql } from '../../@generated'; import { graphql } from '../../@generated/gql';
import { Breadcrumbs } from '../../components/breadcrumbs'; import { Breadcrumbs } from '../../components/breadcrumbs';
import { Card } from '../../components/card'; import { Card } from '../../components/card';
import { Error } from '../../components/error'; import { Error } from '../../components/error';

View File

@ -1,5 +1,5 @@
import { memo } from 'react'; import { memo } from 'react';
import { IconType } from 'react-icons/lib'; import type { IconType } from 'react-icons/lib';
interface MissingPreviewProps { interface MissingPreviewProps {
icon: IconType; icon: IconType;

View File

@ -3,7 +3,7 @@ import { Form, Formik } from 'formik';
import type { FC } from 'react'; import type { FC } from 'react';
import { Fragment, useCallback, useEffect, useState } from 'react'; import { Fragment, useCallback, useEffect, useState } from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { LoginMutationVariables } from '../@generated/graphql'; import type { LoginMutationVariables } from '../@generated/graphql';
import { Input } from '../components/input/input'; import { Input } from '../components/input/input';
import { OtpInput } from '../components/input/otp'; import { OtpInput } from '../components/input/otp';
import { Submit } from '../components/input/submit'; import { Submit } from '../components/input/submit';

View File

@ -1,5 +1,6 @@
import { CombinedError, useQuery } from '@urql/preact'; import type { CombinedError } from '@urql/preact';
import { graphql } from '../@generated'; import { useQuery } from '@urql/preact';
import { graphql } from '../@generated/gql';
const ConfigQuery = graphql(` const ConfigQuery = graphql(`
query Config { query Config {

View File

@ -1,4 +1,3 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { useConfig } from './useConfig'; import { useConfig } from './useConfig';
export const usePaths = () => { export const usePaths = () => {

View File

@ -8,8 +8,7 @@ export const useQueryState = <S>(key: string, initialState?: S, parser?: (input:
// during SSR, we can grab query params from the page context // during SSR, we can grab query params from the page context
const value = pageContext.urlParsed.search[key]; const value = pageContext.urlParsed.search[key];
if (value) { if (value) {
const result = parser ? parser(value) : (value as any); return parser ? parser(value) : (value as any);
return result;
} }
} }
@ -18,8 +17,7 @@ export const useQueryState = <S>(key: string, initialState?: S, parser?: (input:
const search = new URLSearchParams(window.location.search); const search = new URLSearchParams(window.location.search);
const value = search.get(key); const value = search.get(key);
if (value) { if (value) {
const result = parser ? parser(value) : (value as any); return parser ? parser(value) : (value as any);
return result;
} }
} }

View File

@ -1,8 +1,10 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { CombinedError, TypedDocumentNode, useMutation, useQuery } from '@urql/preact'; import type { CombinedError, TypedDocumentNode } from '@urql/preact';
import { graphql } from '../@generated'; import { useMutation, useQuery } from '@urql/preact';
import type { GetUserQuery, LoginMutationVariables, RegularUserFragment } from '../@generated/graphql'; import { graphql } from '../@generated/gql';
import type { GetUserQuery, LoginMutationVariables } from '../@generated/graphql';
import { navigate, reload } from '../helpers/routing'; import { navigate, reload } from '../helpers/routing';
import { type RegularUserFragment } from '../@generated/graphql';
import { useAsync } from './useAsync'; import { useAsync } from './useAsync';
const RegularUserFragment = graphql(` const RegularUserFragment = graphql(`

View File

@ -1,4 +1,4 @@
import { FC } from 'react'; import type { FC } from 'react';
import { Container } from '../components/container'; import { Container } from '../components/container';
import { useConfig } from '../hooks/useConfig'; import { useConfig } from '../hooks/useConfig';
import { Spinner } from '../components/spinner'; import { Spinner } from '../components/spinner';

View File

@ -1,4 +1,4 @@
import { FC } from 'react'; import type { FC } from 'react';
import { Container } from '../../components/container'; import { Container } from '../../components/container';
import { Title } from '../../components/title'; import { Title } from '../../components/title';
import { FileList } from '../../containers/file-list/file-list'; import { FileList } from '../../containers/file-list/file-list';

View File

@ -1,9 +1,10 @@
import { useMutation, useQuery } from '@urql/preact'; import { useMutation, useQuery } from '@urql/preact';
import clsx from 'clsx'; import clsx from 'clsx';
import { QRCodeSVG } from 'qrcode.react'; import { QRCodeSVG } from 'qrcode.react';
import { FC, Fragment, useCallback, useMemo } from 'react'; import type { FC } from 'react';
import { Fragment, useCallback, useMemo } from 'react';
import { FiChevronLeft, FiChevronRight, FiCopy, FiDownload } from 'react-icons/fi'; import { FiChevronLeft, FiChevronRight, FiCopy, FiDownload } from 'react-icons/fi';
import { graphql } from '../../../@generated'; import { graphql } from '../../../@generated/gql';
import { Button, ButtonStyle } from '../../../components/button'; import { Button, ButtonStyle } from '../../../components/button';
import { Container } from '../../../components/container'; import { Container } from '../../../components/container';
import { Error } from '../../../components/error'; import { Error } from '../../../components/error';

View File

@ -1,6 +1,7 @@
import { FC, Fragment } from 'react'; import type { FC } from 'react';
import { Fragment } from 'react';
import { useMutation, useQuery } from '@urql/preact'; import { useMutation, useQuery } from '@urql/preact';
import { graphql } from '../../../@generated'; import { graphql } from '../../../@generated/gql';
import { Breadcrumbs } from '../../../components/breadcrumbs'; import { Breadcrumbs } from '../../../components/breadcrumbs';
import { Button } from '../../../components/button'; import { Button } from '../../../components/button';
import { Container } from '../../../components/container'; import { Container } from '../../../components/container';
@ -42,7 +43,6 @@ export const Page: FC = () => {
const { logout } = useLogoutUser(); const { logout } = useLogoutUser();
const [, refreshMutation] = useMutation(RefreshToken); const [, refreshMutation] = useMutation(RefreshToken);
const [refresh, refreshing] = useAsync(async () => { const [refresh, refreshing] = useAsync(async () => {
// eslint-disable-next-line no-alert
const confirmation = confirm('Are you sure? This will invalidate all existing configs and sessions and will sign you out of the dashboard.') // prettier-ignore const confirmation = confirm('Are you sure? This will invalidate all existing configs and sessions and will sign you out of the dashboard.') // prettier-ignore
if (!confirmation) return; if (!confirmation) return;
await refreshMutation({}); await refreshMutation({});

View File

@ -4,7 +4,7 @@ import type { FC, ReactNode } from 'react';
import { Fragment, useState } from 'react'; import { Fragment, useState } from 'react';
import { FiDownload, FiShare, FiTrash } from 'react-icons/fi'; import { FiDownload, FiShare, FiTrash } from 'react-icons/fi';
import { useMutation, useQuery } from '@urql/preact'; import { useMutation, useQuery } from '@urql/preact';
import { graphql } from '../../../@generated'; import { graphql } from '../../../@generated/gql';
import { Container } from '../../../components/container'; import { Container } from '../../../components/container';
import { Embed } from '../../../components/embed/embed'; import { Embed } from '../../../components/embed/embed';
import { Error } from '../../../components/error'; import { Error } from '../../../components/error';
@ -16,7 +16,7 @@ import { downloadUrl } from '../../../helpers/download.helper';
import { navigate } from '../../../helpers/routing'; import { navigate } from '../../../helpers/routing';
import { useAsync } from '../../../hooks/useAsync'; import { useAsync } from '../../../hooks/useAsync';
import { useQueryState } from '../../../hooks/useQueryState'; import { useQueryState } from '../../../hooks/useQueryState';
import { PageProps } from '../../../renderer/types'; import type { PageProps } from '../../../renderer/types';
const GetFile = graphql(` const GetFile = graphql(`
query GetFile($fileId: ID!) { query GetFile($fileId: ID!) {

View File

@ -1,5 +1,6 @@
import { FC, useEffect } from 'react'; import type { FC } from 'react';
import { graphql } from '../../../@generated'; import { useEffect } from 'react';
import { graphql } from '../../../@generated/gql';
import { Container } from '../../../components/container'; import { Container } from '../../../components/container';
import { Error } from '../../../components/error'; import { Error } from '../../../components/error';
import { PageLoader } from '../../../components/page-loader'; import { PageLoader } from '../../../components/page-loader';
@ -12,7 +13,7 @@ import { getErrorMessage } from '../../../helpers/get-error-message.helper';
import { navigate, prefetch } from '../../../helpers/routing'; import { navigate, prefetch } from '../../../helpers/routing';
import { useAsync } from '../../../hooks/useAsync'; import { useAsync } from '../../../hooks/useAsync';
import { useConfig } from '../../../hooks/useConfig'; import { useConfig } from '../../../hooks/useConfig';
import { PageProps } from '../../../renderer/types'; import type { PageProps } from '../../../renderer/types';
import { useQuery, useMutation } from '@urql/preact'; import { useQuery, useMutation } from '@urql/preact';
const GetInvite = graphql(` const GetInvite = graphql(`

View File

@ -1,4 +1,5 @@
import { FC, useEffect } from 'react'; import type { FC } from 'react';
import { useEffect } from 'react';
import { Title } from '../../components/title'; import { Title } from '../../components/title';
import { LoginForm } from '../../containers/login-form'; import { LoginForm } from '../../containers/login-form';
import { Container } from '../../components/container'; import { Container } from '../../components/container';

View File

@ -1,7 +1,7 @@
import { Form, Formik } from 'formik'; import { Form, Formik } from 'formik';
import { FC } from 'react'; import type { FC } from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { graphql } from '../../@generated'; import { graphql } from '../../@generated/gql';
import type { CreatePasteDto } from '../../@generated/graphql'; import type { CreatePasteDto } from '../../@generated/graphql';
import { Button } from '../../components/button'; import { Button } from '../../components/button';
import { Container } from '../../components/container'; import { Container } from '../../components/container';

View File

@ -1,6 +1,7 @@
import { FC, useEffect, useState } from 'react'; import type { FC } from 'react';
import { useEffect, useState } from 'react';
import { FiBookOpen, FiClock, FiTrash } from 'react-icons/fi'; import { FiBookOpen, FiClock, FiTrash } from 'react-icons/fi';
import { graphql } from '../../../@generated'; import { graphql } from '../../../@generated/gql';
import { Button } from '../../../components/button'; import { Button } from '../../../components/button';
import { Container } from '../../../components/container'; import { Container } from '../../../components/container';
import { Embed } from '../../../components/embed/embed'; import { Embed } from '../../../components/embed/embed';
@ -12,7 +13,7 @@ import { decryptContent } from '../../../helpers/encrypt.helper';
import { hashToObject } from '../../../helpers/hash-to-object'; import { hashToObject } from '../../../helpers/hash-to-object';
import { navigate } from '../../../helpers/routing'; import { navigate } from '../../../helpers/routing';
import { useUser } from '../../../hooks/useUser'; import { useUser } from '../../../hooks/useUser';
import { PageProps } from '../../../renderer/types'; import type { PageProps } from '../../../renderer/types';
import { useQuery } from '@urql/preact'; import { useQuery } from '@urql/preact';
const PasteQuery = graphql(` const PasteQuery = graphql(`

View File

@ -1,8 +1,9 @@
import { useMutation } from '@urql/preact'; import { useMutation } from '@urql/preact';
import { Form, Formik } from 'formik'; import { Form, Formik } from 'formik';
import { FC, useState } from 'react'; import type { FC } from 'react';
import { useState } from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { graphql } from '../../@generated'; import { graphql } from '../../@generated/gql';
import { Button } from '../../components/button'; import { Button } from '../../components/button';
import { Container } from '../../components/container'; import { Container } from '../../components/container';
import { Input } from '../../components/input/input'; import { Input } from '../../components/input/input';

View File

@ -1,4 +1,4 @@
import { Config } from 'vike/types'; import type { Config } from 'vike/types';
export default { export default {
passToClient: ['state', 'routeParams'], passToClient: ['state', 'routeParams'],

View File

@ -2,7 +2,7 @@ import { createClient, fetchExchange, ssrExchange } from '@urql/preact';
import { Provider as UrqlProvider } from '@urql/preact'; import { Provider as UrqlProvider } from '@urql/preact';
import { hydrate } from 'preact'; import { hydrate } from 'preact';
import { HelmetProvider } from 'react-helmet-async'; import { HelmetProvider } from 'react-helmet-async';
import { OnRenderClientAsync } from 'vike/types'; import type { OnRenderClientAsync } from 'vike/types';
import { App } from '../app'; import { App } from '../app';
import { PageContextProvider } from './usePageContext'; import { PageContextProvider } from './usePageContext';
import { cacheOptions } from './cache'; import { cacheOptions } from './cache';
@ -37,6 +37,6 @@ export const onRenderClient: OnRenderClientAsync = async (pageContext) => {
</HelmetProvider> </HelmetProvider>
</UrqlProvider> </UrqlProvider>
</PageContextProvider>, </PageContextProvider>,
document.getElementById('root')!, document.querySelector('#root')!,
); );
}; };

View File

@ -1,12 +1,13 @@
import { cacheExchange } from '@urql/exchange-graphcache'; import { cacheExchange } from '@urql/exchange-graphcache';
import { Provider as UrqlProvider, createClient, fetchExchange, ssrExchange } from '@urql/preact'; import { Provider as UrqlProvider, createClient, fetchExchange, ssrExchange } from '@urql/preact';
import { HelmetProvider, HelmetServerState } from 'react-helmet-async'; import type { HelmetServerState } from 'react-helmet-async';
import { HelmetProvider } from 'react-helmet-async';
import { dangerouslySkipEscape, escapeInject } from 'vike/server'; import { dangerouslySkipEscape, escapeInject } from 'vike/server';
import type { OnRenderHtmlAsync } from 'vike/types'; import type { OnRenderHtmlAsync } from 'vike/types';
import { App } from '../app'; import { App } from '../app';
import { cacheOptions } from './cache'; import { cacheOptions } from './cache';
import { renderToStringWithData } from './prepass'; import { renderToStringWithData } from './prepass';
import { PageProps } from './types'; import type { PageProps } from './types';
import { PageContextProvider } from './usePageContext'; import { PageContextProvider } from './usePageContext';
const GRAPHQL_URL = (import.meta.env.PUBLIC_ENV__FRONTEND_API_URL || import.meta.env.FRONTEND_API_URL) + '/graphql'; const GRAPHQL_URL = (import.meta.env.PUBLIC_ENV__FRONTEND_API_URL || import.meta.env.FRONTEND_API_URL) + '/graphql';
@ -43,7 +44,7 @@ export const onRenderHtml: OnRenderHtmlAsync = async (pageContext): ReturnType<O
const helmet = helmetContext.helmet!; const helmet = helmetContext.helmet!;
const documentHtml = escapeInject`<!DOCTYPE html> const documentHtml = escapeInject`<!DOCTYPE html>
<html lang="en"> <html lang="en">
<Helmet> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
${dangerouslySkipEscape(helmet.title.toString())} ${dangerouslySkipEscape(helmet.title.toString())}

View File

@ -1,10 +1,7 @@
import { CacheExchangeOpts } from '@urql/exchange-graphcache'; import type { CacheExchangeOpts } from '@urql/exchange-graphcache';
import { relayPagination } from '@urql/exchange-graphcache/extras'; import { relayPagination } from '@urql/exchange-graphcache/extras';
import schema from '../@generated/introspection.json';
export const cacheOptions: Partial<CacheExchangeOpts> = { export const cacheOptions: Partial<CacheExchangeOpts> = {
schema: schema,
resolvers: { resolvers: {
User: { User: {
files: relayPagination(), files: relayPagination(),

View File

@ -1,6 +1,6 @@
import { VNode } from 'preact'; import type { VNode } from 'preact';
import renderToString from 'preact-render-to-string'; import renderToString from 'preact-render-to-string';
import { Client } from '@urql/preact'; import type { Client } from '@urql/preact';
const MAX_DEPTH = 3; const MAX_DEPTH = 3;
const isPromiseLike = (value: unknown): value is Promise<unknown> => { const isPromiseLike = (value: unknown): value is Promise<unknown> => {

View File

@ -1,7 +1,8 @@
import { FC } from 'react'; import type { FC } from 'react';
import { SSRData } from '@urql/preact'; import type { SSRData } from '@urql/preact';
// https://vike.dev/pageContext#typescript // https://vike.dev/pageContext#typescript
/* eslint-disable @typescript-eslint/no-namespace */
declare global { declare global {
namespace Vike { namespace Vike {
interface PageContext { interface PageContext {

View File

@ -14,6 +14,5 @@ export function PageContextProvider({
} }
export function usePageContext() { export function usePageContext() {
const pageContext = useContext(Context); return useContext(Context);
return pageContext;
} }

View File

@ -1,11 +1,12 @@
import FastifyEarlyHints from '@fastify/early-hints'; import FastifyEarlyHints from '@fastify/early-hints';
import FastifyProxy from '@fastify/http-proxy'; import FastifyProxy from '@fastify/http-proxy';
import Fastify, { FastifyInstance } from 'fastify'; import type { FastifyInstance } from 'fastify';
import { IncomingMessage, ServerResponse } from 'http'; import Fastify from 'fastify';
import type { IncomingMessage, ServerResponse } from 'http';
import { compile, match } from 'path-to-regexp'; import { compile, match } from 'path-to-regexp';
import url from 'url'; import url from 'url';
import { renderPage } from 'vike/server'; import { renderPage } from 'vike/server';
import { PageContext } from 'vike/types'; import type { PageContext } from 'vike/types';
import { REWRITES } from './rewrites'; import { REWRITES } from './rewrites';
const rewrites = REWRITES.map(({ source, destination }) => ({ const rewrites = REWRITES.map(({ source, destination }) => ({
@ -58,8 +59,7 @@ async function startServer() {
for (const { match, toPath } of rewrites) { for (const { match, toPath } of rewrites) {
const result = match(pathname); const result = match(pathname);
if (result) { if (result) {
const replaced = toPath(result.params); return toPath(result.params);
return replaced;
} }
} }
@ -99,14 +99,14 @@ async function startServer() {
} }
const { httpResponse } = pageContext; const { httpResponse } = pageContext;
if (!httpResponse) { if (httpResponse) {
reply.status(500).send('Internal Server Error');
} else {
const { body, statusCode, headers, earlyHints } = httpResponse; const { body, statusCode, headers, earlyHints } = httpResponse;
reply.writeEarlyHints({ link: earlyHints.map((e) => e.earlyHintLink) }); reply.writeEarlyHints({ link: earlyHints.map((e) => e.earlyHintLink) });
headers.forEach(([name, value]) => reply.header(name, value)); for (const [name, value] of headers) reply.header(name, value);
reply.status(statusCode); reply.status(statusCode);
reply.send(body); reply.send(body);
} else {
reply.status(500).send('Internal Server Error');
} }
}); });
@ -115,6 +115,7 @@ async function startServer() {
} }
let fastify: FastifyInstance | undefined; let fastify: FastifyInstance | undefined;
// eslint-disable-next-line unicorn/prefer-top-level-await
const fastifyHandlerPromise = startServer().catch((error) => { const fastifyHandlerPromise = startServer().catch((error) => {
console.error(error); console.error(error);
process.exit(1); process.exit(1);

View File

@ -1,21 +1,21 @@
{ {
"extends": "@atlasbot/configs/tsconfig/esm.json",
"compilerOptions": { "compilerOptions": {
"strict": true,
"module": "ES2022", "module": "ES2022",
"target": "ES2022", "target": "ES2022",
"moduleResolution": "Bundler", "moduleResolution": "Bundler",
"lib": ["DOM", "DOM.Iterable", "ESNext"], "lib": ["DOM", "DOM.Iterable", "ESNext"],
"types": ["vite/client"], "types": ["vite/client"],
"skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": true,
"noUnusedLocals": true, "noUncheckedIndexedAccess": false,
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "preact", "jsxImportSource": "preact",
"baseUrl": "./", "baseUrl": "./",
"paths": { "paths": {
"react": ["./node_modules/preact/compat/"], "react": ["./node_modules/preact/compat/"],
"react-dom": ["./node_modules/preact/compat/"], "react-dom": ["./node_modules/preact/compat/"],
// todo: https://github.com/gxmari007/vite-plugin-eslint/issues/74
"vite-plugin-eslint": ["./node_modules/vite-plugin-eslint/dist/index.d.ts"],
}, },
}, },
} }

View File

@ -2,11 +2,14 @@ import { preact } from '@preact/preset-vite';
import { vavite } from 'vavite'; import { vavite } from 'vavite';
import ssr from 'vike/plugin'; import ssr from 'vike/plugin';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import eslint from 'vite-plugin-eslint';
import codegen from 'vite-plugin-graphql-codegen';
// todo: https://github.com/dotansimha/graphql-code-generator/issues/9774
export default defineConfig({ export default defineConfig({
buildSteps: [ buildSteps: [
{ name: 'client' }, {
name: 'client',
},
{ {
name: 'server', name: 'server',
config: { config: {
@ -21,11 +24,14 @@ export default defineConfig({
noExternal: ['react-helmet-async', 'prism-react-renderer', 'qrcode.react', 'formik'], noExternal: ['react-helmet-async', 'prism-react-renderer', 'qrcode.react', 'formik'],
}, },
plugins: [ plugins: [
codegen(),
eslint({ cache: true }),
preact(), preact(),
ssr({ disableAutoFullBuild: true }), ssr({ disableAutoFullBuild: true }),
vavite({ vavite({
handlerEntry: '/src/server/index.ts', handlerEntry: '/src/server/index.ts',
serveClientAssetsInDev: true, serveClientAssetsInDev: true,
clientAssetsDir: 'dist/client',
}), }),
], ],
}); });

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +1,20 @@
{ {
"baseBranch": "origin/main", "pipeline": {
"pipeline": { "build": {
"build": { "dependsOn": ["^build"],
"dependsOn": [ "outputs": ["dist/**", ".next/{server,cache,src,static}"]
"^build" },
], "lint": {
"outputs": [ "outputs": []
"dist/**", },
".next/{server,cache,src,static}" "test": {
] "outputs": []
}, },
"lint": { "clean": {
"outputs": [] "outputs": []
}, },
"test": { "watch": {
"outputs": [] "cache": false
},
"clean": {
"outputs": []
},
"watch": {
"cache": false
}
} }
} }
}