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).)*$"],
"eslint.workingDirectories": [
{
"pattern": "./{packages,apps}/*"
"pattern": "./packages/*"
}
],
"files.associations": {

View File

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

View File

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

View File

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

View File

@ -11,8 +11,8 @@ import { Transform } from 'stream';
export class ExifTransformer extends Transform {
private static readonly app1Marker = Buffer.from('ffe1', 'hex');
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 flirMarker = Buffer.from('FLIR', 'utf-8');
private static readonly xmpMarker = Buffer.from('http://ns.adobe.com/xap', 'utf8');
private static readonly flirMarker = Buffer.from('FLIR', 'utf8');
private static readonly maxMarkerLength = Math.max(
ExifTransformer.exifMarker.length,
ExifTransformer.xmpMarker.length,
@ -48,7 +48,7 @@ export class ExifTransformer extends Transform {
// no app1 in the current pendingChunk
if (app1Start === -1) {
// 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);
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 hosts = result.data.hosts.map(enhanceHost);
export const hosts = result.data.hosts.map((host) => enhanceHost(host));
export const rootHost = hosts[0];
if (rootHost.isWildcard) {
@ -92,9 +92,9 @@ if (disallowed.has(config.secret.toLowerCase())) {
const token = randomBytes(24).toString('hex');
throw new Error(
dedent`
${c.redBright.bold('Do not use the default secret.')}
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)}
${c.redBright.bold('Do not use the default secret.')}
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)}
`,
);
}

View File

@ -41,7 +41,7 @@ export const expandMime = (input: string | string[]) => {
if (!Array.isArray(input)) input = [input];
const output: string[] = [];
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) {
output.push(...alias);
continue;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -72,8 +72,13 @@ export class File extends Resource {
}
getDisplayName() {
if (this.name) return this.name;
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() {

View File

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

View File

@ -1,7 +1,7 @@
import { InjectRepository } from '@mikro-orm/nestjs';
import { EntityRepository } from '@mikro-orm/postgresql';
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 { LinkService } from './link.service.js';
@ -9,7 +9,7 @@ import { LinkService } from './link.service.js';
export class LinkController {
constructor(
@InjectRepository(Link) private readonly linkRepo: EntityRepository<Link>,
private readonly linkService: LinkService
private readonly linkService: LinkService,
) {}
@Get('link/:id')

View File

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

View File

@ -1,5 +1,5 @@
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';
@Controller()
@ -10,7 +10,7 @@ export class ThumbnailController {
async getThumbnailContent(
@Param('fileId') fileId: string,
@Req() request: FastifyRequest,
@Res() reply: FastifyReply
@Res() reply: FastifyReply,
) {
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 { InjectRepository } from '@mikro-orm/nestjs';
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
@ -23,7 +24,7 @@ export class ThumbnailService {
private static readonly IMAGE_TYPES = new Set(
Object.keys(sharp.format)
.map((key) => mime.lookup(key))
.filter((key) => key && key.startsWith('image'))
.filter((key) => key && key.startsWith('image')),
);
private static readonly VIDEO_TYPES = new Set([
@ -42,7 +43,7 @@ export class ThumbnailService {
@InjectRepository('Thumbnail') private readonly thumbnailRepo: EntityRepository<Thumbnail>,
@InjectRepository('File') private readonly fileRepo: EntityRepository<File>,
private readonly storageService: StorageService,
private readonly fileService: FileService
private readonly fileService: FileService,
) {}
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.
const positions = ['5%', '10%', '20%', '40%'];
const size = `${ThumbnailService.THUMBNAIL_SIZE}x?`;
for (let positionIndex = 0; positionIndex < positions.length; positionIndex++) {
const percent = positions[positionIndex];
for (const [positionIndex, percent] of positions.entries()) {
const stream = ffmpeg(filePath).screenshot({
count: 1,
timemarks: [percent],

View File

@ -1,5 +1,5 @@
import { Controller, Get, Param, Response } from '@nestjs/common';
import type { FastifyReply } from 'fastify';
import { type FastifyReply } from 'fastify';
import { UserService } from './user.service.js';
@Controller()
@ -10,7 +10,7 @@ export class UserController {
async verifyUser(
@Param('userId') userId: string,
@Param('verifyId') verifyId: string,
@Response() reply: FastifyReply
@Response() reply: FastifyReply,
) {
await this.userService.verifyUser(userId, verifyId);
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 type { MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs';
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 { Field, ObjectType } from '@nestjs/graphql';

View File

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

View File

@ -4,10 +4,10 @@
"compilerOptions": {
"outDir": "dist",
"noUncheckedIndexedAccess": false,
"strictPropertyInitialization": false,
"strictPropertyInitialization": false, // doesn't play nice with mikroorm/graphql decorators
"noImplicitOverride": false,
"emitDecoratorMetadata": 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 {
overwrite: true,
@ -17,9 +17,6 @@ export default {
fragmentMasking: false,
},
},
'src/@generated/introspection.json': {
plugins: ['introspection'],
},
},
hooks: {
afterAllFileWrite: ['prettier --write'],

View File

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

View File

@ -1,4 +1,3 @@
/* eslint-disable react/no-danger */
import clsx from 'clsx';
import * as avatar from 'generate-avatar';
import type { FC } from 'react';
@ -14,7 +13,7 @@ export const Avatar: FC<AvatarProps> = (props) => {
const containerRef = useRef<HTMLDivElement>(null);
const svg = useMemo(() => {
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]);
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 type { FC, HTMLAttributes } 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 { usePaths } from '../hooks/usePaths';
import { Container } from './container';

View File

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

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { ComponentProps } from 'react';
import React from 'react';
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 { InputContainer } from './container';

View File

@ -1,6 +1,7 @@
import { useFormikContext } from 'formik';
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.

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'> {
href: string;

View File

@ -1,5 +1,6 @@
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_INPUT_CLASSES, BASE_INPUT_MAX_HEIGHT } from './input/container';

View File

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

View File

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

View File

@ -1,7 +1,8 @@
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 { RegularUserFragment } from '../../@generated/graphql';
import type { RegularUserFragment } from '../../@generated/graphql';
import { Container } from '../../components/container';
import { Section } from '../../components/section';
import { Skeleton, SkeletonList, SkeletonWrap } from '../../components/skeleton';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,3 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { useConfig } from './useConfig';
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
const value = pageContext.urlParsed.search[key];
if (value) {
const result = parser ? parser(value) : (value as any);
return result;
return parser ? parser(value) : (value as any);
}
}
@ -18,8 +17,7 @@ export const useQueryState = <S>(key: string, initialState?: S, parser?: (input:
const search = new URLSearchParams(window.location.search);
const value = search.get(key);
if (value) {
const result = parser ? parser(value) : (value as any);
return result;
return parser ? parser(value) : (value as any);
}
}

View File

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

View File

@ -1,4 +1,4 @@
import { FC } from 'react';
import type { FC } from 'react';
import { Container } from '../components/container';
import { useConfig } from '../hooks/useConfig';
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 { Title } from '../../components/title';
import { FileList } from '../../containers/file-list/file-list';

View File

@ -1,9 +1,10 @@
import { useMutation, useQuery } from '@urql/preact';
import clsx from 'clsx';
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 { graphql } from '../../../@generated';
import { graphql } from '../../../@generated/gql';
import { Button, ButtonStyle } from '../../../components/button';
import { Container } from '../../../components/container';
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 { graphql } from '../../../@generated';
import { graphql } from '../../../@generated/gql';
import { Breadcrumbs } from '../../../components/breadcrumbs';
import { Button } from '../../../components/button';
import { Container } from '../../../components/container';
@ -42,7 +43,6 @@ export const Page: FC = () => {
const { logout } = useLogoutUser();
const [, refreshMutation] = useMutation(RefreshToken);
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
if (!confirmation) return;
await refreshMutation({});

View File

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

View File

@ -1,5 +1,6 @@
import { FC, useEffect } from 'react';
import { graphql } from '../../../@generated';
import type { FC } from 'react';
import { useEffect } from 'react';
import { graphql } from '../../../@generated/gql';
import { Container } from '../../../components/container';
import { Error } from '../../../components/error';
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 { useAsync } from '../../../hooks/useAsync';
import { useConfig } from '../../../hooks/useConfig';
import { PageProps } from '../../../renderer/types';
import type { PageProps } from '../../../renderer/types';
import { useQuery, useMutation } from '@urql/preact';
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 { LoginForm } from '../../containers/login-form';
import { Container } from '../../components/container';

View File

@ -1,7 +1,7 @@
import { Form, Formik } from 'formik';
import { FC } from 'react';
import type { FC } from 'react';
import * as Yup from 'yup';
import { graphql } from '../../@generated';
import { graphql } from '../../@generated/gql';
import type { CreatePasteDto } from '../../@generated/graphql';
import { Button } from '../../components/button';
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 { graphql } from '../../../@generated';
import { graphql } from '../../../@generated/gql';
import { Button } from '../../../components/button';
import { Container } from '../../../components/container';
import { Embed } from '../../../components/embed/embed';
@ -12,7 +13,7 @@ import { decryptContent } from '../../../helpers/encrypt.helper';
import { hashToObject } from '../../../helpers/hash-to-object';
import { navigate } from '../../../helpers/routing';
import { useUser } from '../../../hooks/useUser';
import { PageProps } from '../../../renderer/types';
import type { PageProps } from '../../../renderer/types';
import { useQuery } from '@urql/preact';
const PasteQuery = graphql(`

View File

@ -1,8 +1,9 @@
import { useMutation } from '@urql/preact';
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 { graphql } from '../../@generated';
import { graphql } from '../../@generated/gql';
import { Button } from '../../components/button';
import { Container } from '../../components/container';
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 {
passToClient: ['state', 'routeParams'],

View File

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

View File

@ -1,12 +1,13 @@
import { cacheExchange } from '@urql/exchange-graphcache';
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 type { OnRenderHtmlAsync } from 'vike/types';
import { App } from '../app';
import { cacheOptions } from './cache';
import { renderToStringWithData } from './prepass';
import { PageProps } from './types';
import type { PageProps } from './types';
import { PageContextProvider } from './usePageContext';
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 documentHtml = escapeInject`<!DOCTYPE html>
<html lang="en">
<Helmet>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
${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 schema from '../@generated/introspection.json';
export const cacheOptions: Partial<CacheExchangeOpts> = {
schema: schema,
resolvers: {
User: {
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 { Client } from '@urql/preact';
import type { Client } from '@urql/preact';
const MAX_DEPTH = 3;
const isPromiseLike = (value: unknown): value is Promise<unknown> => {

View File

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

View File

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

View File

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

View File

@ -1,21 +1,21 @@
{
"extends": "@atlasbot/configs/tsconfig/esm.json",
"compilerOptions": {
"strict": true,
"module": "ES2022",
"target": "ES2022",
"moduleResolution": "Bundler",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"types": ["vite/client"],
"skipLibCheck": true,
"esModuleInterop": true,
"noUnusedLocals": true,
"noUncheckedIndexedAccess": false,
"jsx": "react-jsx",
"jsxImportSource": "preact",
"baseUrl": "./",
"paths": {
"react": ["./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 ssr from 'vike/plugin';
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({
buildSteps: [
{ name: 'client' },
{
name: 'client',
},
{
name: 'server',
config: {
@ -21,11 +24,14 @@ export default defineConfig({
noExternal: ['react-helmet-async', 'prism-react-renderer', 'qrcode.react', 'formik'],
},
plugins: [
codegen(),
eslint({ cache: true }),
preact(),
ssr({ disableAutoFullBuild: true }),
vavite({
handlerEntry: '/src/server/index.ts',
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": {
"build": {
"dependsOn": [
"^build"
],
"outputs": [
"dist/**",
".next/{server,cache,src,static}"
]
},
"lint": {
"outputs": []
},
"test": {
"outputs": []
},
"clean": {
"outputs": []
},
"watch": {
"cache": false
}
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/{server,cache,src,static}"]
},
"lint": {
"outputs": []
},
"test": {
"outputs": []
},
"clean": {
"outputs": []
},
"watch": {
"cache": false
}
}
}
}