From 9beeca594222f97c48a525379a240c22fc5888e8 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 6 Oct 2025 00:01:10 -0400 Subject: [PATCH] modernize backend to target the same ES and TS standards as the rest of the app --- packages/backend/.swcrc | 2 +- packages/backend/assets/embed.js | 2 +- packages/backend/eslint.config.js | 99 +++++++--- packages/backend/jest.config.e2e.cjs | 15 -- packages/backend/jest.config.e2e.ts | 173 ++++++++++++++++++ ...jest.config.fed.cjs => jest.config.fed.ts} | 2 +- .../{jest.config.cjs => jest.config.ts} | 2 +- ...st.config.unit.cjs => jest.config.unit.ts} | 4 +- packages/backend/jest.js | 6 +- packages/backend/jsconfig.frontend.json | 24 +++ packages/backend/jsconfig.json | 24 --- packages/backend/package.json | 25 ++- packages/backend/scripts/dev.mjs | 8 +- packages/backend/scripts/watch.mjs | 6 +- packages/backend/src/misc/get-note-summary.ts | 44 ++++- .../server/api/mastodon/MastodonConverters.ts | 4 +- .../server/api/mastodon/endpoints/instance.ts | 3 +- .../server/api/mastodon/endpoints/status.ts | 32 +++- packages/backend/src/server/web/boot.embed.js | 2 +- packages/backend/src/server/web/cli.js | 2 +- packages/backend/src/server/web/error.js | 1 + packages/backend/src/server/web/global.d.ts | 3 + .../backend/test-federation/tsconfig.json | 24 +-- packages/backend/test-server/.swcrc | 2 +- packages/backend/test-server/tsconfig.json | 41 +---- packages/backend/test/tsconfig.json | 34 +--- packages/backend/tsconfig.backend.json | 33 ++++ packages/backend/tsconfig.json | 57 +----- packages/backend/tsconfig.scripts.json | 18 ++ 29 files changed, 460 insertions(+), 232 deletions(-) delete mode 100644 packages/backend/jest.config.e2e.cjs create mode 100644 packages/backend/jest.config.e2e.ts rename packages/backend/{jest.config.fed.cjs => jest.config.fed.ts} (85%) rename packages/backend/{jest.config.cjs => jest.config.ts} (99%) rename packages/backend/{jest.config.unit.cjs => jest.config.unit.ts} (79%) create mode 100644 packages/backend/jsconfig.frontend.json delete mode 100644 packages/backend/jsconfig.json create mode 100644 packages/backend/src/server/web/global.d.ts create mode 100644 packages/backend/tsconfig.backend.json create mode 100644 packages/backend/tsconfig.scripts.json diff --git a/packages/backend/.swcrc b/packages/backend/.swcrc index f4bf7a4d2a..758045025f 100644 --- a/packages/backend/.swcrc +++ b/packages/backend/.swcrc @@ -17,7 +17,7 @@ "paths": { "@/*": ["*"] }, - "target": "es2022" + "target": "ESNext" }, "minify": false, "sourceMaps": "inline" diff --git a/packages/backend/assets/embed.js b/packages/backend/assets/embed.js index 24fccc1b6c..38b6133847 100644 --- a/packages/backend/assets/embed.js +++ b/packages/backend/assets/embed.js @@ -7,7 +7,7 @@ /** @type {NodeListOf} */ const els = document.querySelectorAll('iframe[data-misskey-embed-id]'); - window.addEventListener('message', function (event) { + window.addEventListener('message', (event) => { els.forEach((el) => { if (event.source !== el.contentWindow) { return; diff --git a/packages/backend/eslint.config.js b/packages/backend/eslint.config.js index 1c2c4d8a0a..f900352ff1 100644 --- a/packages/backend/eslint.config.js +++ b/packages/backend/eslint.config.js @@ -6,18 +6,18 @@ export default [ ...sharedConfig, { ignores: [ - "assets/", - "**/built/", - "migration/", - "**/node_modules/", - "test/", - "test-federation/", - "test-server/", - "**/temp/", - "**/@types/", - "**/coverage/", - "*.*", - "**/*.test.ts", + '**/built/', + 'migration/', + '**/node_modules/', + 'test/', + 'test-federation/', + 'test-server/', + '**/temp/', + '**/@types/', + '**/coverage/', + 'ormconfig.js', + 'scripts/check_connect.js', + 'scripts/generate_api_json.js', ], }, { @@ -29,10 +29,14 @@ export default [ }, { files: ['src/**/*.ts', 'src/**/*.tsx'], + ignores: [ + '*.*', + 'src/server/web/**/*.d.ts', + ], languageOptions: { parserOptions: { parser: tsParser, - project: ['./tsconfig.json'], + project: ['./tsconfig.backend.json'], sourceType: 'module', tsconfigRootDir: import.meta.dirname, }, @@ -94,42 +98,50 @@ export default [ 'no-restricted-syntax': [ 'error', { - "selector": "NewExpression[callee.name='Date'][arguments.length=0]", - "message": "new Date() is restricted. Use TimeService.date instead." + 'selector': 'NewExpression[callee.name=\'Date\'][arguments.length=0]', + 'message': 'new Date() is restricted. Use TimeService.date instead.', }, { - "selector": "NewExpression[callee.name='MemoryKVCache']", - "message": "Cache constructor will produce an unmanaged instance. Use CacheManagementService.createMemoryKVCache() instead." + 'selector': 'NewExpression[callee.name=\'MemoryKVCache\']', + 'message': 'Cache constructor will produce an unmanaged instance. Use CacheManagementService.createMemoryKVCache() instead.', }, { - "selector": "NewExpression[callee.name='MemorySingleCache']", - "message": "Cache constructor will produce an unmanaged instance. Use CacheManagementService.createMemorySingleCache() instead." + 'selector': 'NewExpression[callee.name=\'MemorySingleCache\']', + 'message': 'Cache constructor will produce an unmanaged instance. Use CacheManagementService.createMemorySingleCache() instead.', }, { - "selector": "NewExpression[callee.name='RedisKVCache']", - "message": "Cache constructor will produce an unmanaged instance. Use CacheManagementService.createRedisKVCache() instead." + 'selector': 'NewExpression[callee.name=\'RedisKVCache\']', + 'message': 'Cache constructor will produce an unmanaged instance. Use CacheManagementService.createRedisKVCache() instead.', }, { - "selector": "NewExpression[callee.name='RedisSingleCache']", - "message": "Cache constructor will produce an unmanaged instance. Use CacheManagementService.createRedisSingleCache() instead." + 'selector': 'NewExpression[callee.name=\'RedisSingleCache\']', + 'message': 'Cache constructor will produce an unmanaged instance. Use CacheManagementService.createRedisSingleCache() instead.', }, { - "selector": "NewExpression[callee.name='QuantumKVCache']", - "message": "Cache constructor will produce an unmanaged instance. Use CacheManagementService.createQuantumKVCache() instead." + 'selector': 'NewExpression[callee.name=\'QuantumKVCache\']', + 'message': 'Cache constructor will produce an unmanaged instance. Use CacheManagementService.createQuantumKVCache() instead.', }, { - "selector": "CallExpression[callee.property.name='delete'][arguments.length=1] > ObjectExpression[properties.length=0]", - "message": "repository.delete({}) will produce a runtime error. Use repository.deleteAll() instead." + 'selector': 'CallExpression[callee.property.name=\'delete\'][arguments.length=1] > ObjectExpression[properties.length=0]', + 'message': 'repository.delete({}) will produce a runtime error. Use repository.deleteAll() instead.', }, { - "selector": "CallExpression[callee.property.name='update'][arguments.length>=1] > ObjectExpression[properties.length=0]", - "message": "repository.update({}, {...}) will produce a runtime error. Use repository.updateAll({...}) instead." + 'selector': 'CallExpression[callee.property.name=\'update\'][arguments.length>=1] > ObjectExpression[properties.length=0]', + 'message': 'repository.update({}, {...}) will produce a runtime error. Use repository.updateAll({...}) instead.', }, ], }, }, { - files: ['src/server/web/**/*.js', 'src/server/web/**/*.ts'], + files: [ + './assets/**/*.js', + './assets/**/*.mjs', + './assets/**/*.cjs', + './src/server/web/**/*.js', + './src/server/web/**/*.mjs', + './src/server/web/**/*.cjs', + './src/server/web/**/*.d.ts', + ], languageOptions: { globals: { ...globals.browser, @@ -137,6 +149,12 @@ export default [ CLIENT_ENTRY: true, LANGS_VERSION: true, }, + parserOptions: { + parser: tsParser, + project: ['./jsconfig.frontend.json'], + sourceType: 'module', + tsconfigRootDir: import.meta.dirname, + }, }, rules: { 'no-restricted-globals': 'off', @@ -144,4 +162,25 @@ export default [ 'no-restricted-syntax': 'off', }, }, + { + files: [ + 'eslint.*', + 'jest.*', + 'scripts/dev.mjs', + 'scripts/watch.mjs', + ], + ignores: [ + 'ormconfig.js', + 'scripts/check_connect.js', + 'scripts/generate_api_json.js', + ], + languageOptions: { + parserOptions: { + parser: tsParser, + project: ['./tsconfig.scripts.json'], + sourceType: 'module', + tsconfigRootDir: import.meta.dirname, + }, + }, + }, ]; diff --git a/packages/backend/jest.config.e2e.cjs b/packages/backend/jest.config.e2e.cjs deleted file mode 100644 index 4502da47df..0000000000 --- a/packages/backend/jest.config.e2e.cjs +++ /dev/null @@ -1,15 +0,0 @@ -/* -* For a detailed explanation regarding each configuration property and type check, visit: -* https://jestjs.io/docs/en/configuration.html -*/ - -const base = require('./jest.config.cjs') - -module.exports = { - ...base, - globalSetup: "/built-test/entry.js", - setupFilesAfterEnv: ["/test/jest.setup.ts"], - testMatch: [ - "/test/e2e/**/*.ts", - ], -}; diff --git a/packages/backend/jest.config.e2e.ts b/packages/backend/jest.config.e2e.ts new file mode 100644 index 0000000000..004b7812e1 --- /dev/null +++ b/packages/backend/jest.config.e2e.ts @@ -0,0 +1,173 @@ +/* +* For a detailed explanation regarding each configuration property and type check, visit: +* https://jestjs.io/docs/en/configuration.html +*/ + +import base from './jest.config.ts'; + +export default { + ...base, + globalSetup: "/built-test/entry.js", + setupFilesAfterEnv: ["/test/jest.setup.ts"], + + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "C:\\Users\\ai\\AppData\\Local\\Temp\\jest", + + // Automatically clear mock calls and instances between every test + // clearMocks: false, + + // Indicates whether the coverage information should be collected while executing the test + // collectCoverage: false, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + collectCoverageFrom: ['src/**/*.ts', '!src/**/*.test.ts'], + + // The directory where Jest should output its coverage files + coverageDirectory: "coverage", + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "\\\\node_modules\\\\" + // ], + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: "v8", + + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // // A set of global variables that need to be available in all test environments + // globals: { + // }, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "json", + // "jsx", + // "ts", + // "tsx", + // "node" + // ], + moduleFileExtensions: [ + "ts", + "js" + ], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + moduleNameMapper: { + // Do not resolve .wasm.js to .wasm by the rule below + '^(.+)\\.wasm\\.js$': '$1.wasm.js', + // SWC converts @/foo/bar.js to `../../src/foo/bar.js`, and then this rule + // converts it again to `../../src/foo/bar` which then can be resolved to + // `.ts` files. + // See https://github.com/swc-project/jest/issues/64#issuecomment-1029753225 + // TODO: Use `--allowImportingTsExtensions` on TypeScript 5.0 so that we can + // directly import `.ts` files without this hack. + '^((?:\\.{1,2}|[A-Z:])*/.*)\\.js$': '$1', + }, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + //preset: "ts-jest/presets/js-with-ts-esm", + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state between every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: './jest-resolver.cjs', + + // Automatically restore mock state between every test + restoreMocks: true, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + roots: [ + "/test", + ], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + // setupFiles: [], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + testEnvironment: "node", + + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + testMatch: [ + "/test/e2e/**/*.ts", + ], +}; diff --git a/packages/backend/jest.config.fed.cjs b/packages/backend/jest.config.fed.ts similarity index 85% rename from packages/backend/jest.config.fed.cjs rename to packages/backend/jest.config.fed.ts index fae187bc23..f6251c2102 100644 --- a/packages/backend/jest.config.fed.cjs +++ b/packages/backend/jest.config.fed.ts @@ -3,7 +3,7 @@ * https://jestjs.io/docs/en/configuration.html */ -const base = require('./jest.config.cjs'); +import base from './jest.config.ts'; module.exports = { ...base, diff --git a/packages/backend/jest.config.cjs b/packages/backend/jest.config.ts similarity index 99% rename from packages/backend/jest.config.cjs rename to packages/backend/jest.config.ts index 5a4aa4e15a..b53836ca3a 100644 --- a/packages/backend/jest.config.cjs +++ b/packages/backend/jest.config.ts @@ -3,7 +3,7 @@ * https://jestjs.io/docs/en/configuration.html */ -module.exports = { +export default { // All imported modules in your tests should be mocked automatically // automock: false, diff --git a/packages/backend/jest.config.unit.cjs b/packages/backend/jest.config.unit.ts similarity index 79% rename from packages/backend/jest.config.unit.cjs rename to packages/backend/jest.config.unit.ts index aa5992936b..bbad35a806 100644 --- a/packages/backend/jest.config.unit.cjs +++ b/packages/backend/jest.config.unit.ts @@ -3,9 +3,9 @@ * https://jestjs.io/docs/en/configuration.html */ -const base = require('./jest.config.cjs') +import base from './jest.config.ts'; -module.exports = { +export default { ...base, testMatch: [ "/test/unit/**/*.ts", diff --git a/packages/backend/jest.js b/packages/backend/jest.js index 0e761d8c92..0f28dc2f80 100644 --- a/packages/backend/jest.js +++ b/packages/backend/jest.js @@ -24,7 +24,11 @@ child.on('error', (err) => { }); child.on('exit', (code, signal) => { if (code === null) { - process.exit(128 + signal); + if (signal != null) { + process.exit(128 + signal); + } else { + process.exit(128); + } } else { process.exit(code); } diff --git a/packages/backend/jsconfig.frontend.json b/packages/backend/jsconfig.frontend.json new file mode 100644 index 0000000000..3354f1e464 --- /dev/null +++ b/packages/backend/jsconfig.frontend.json @@ -0,0 +1,24 @@ +{ + "extends": "../shared/jsconfig.web.json", + "include": [ + "./assets/**/*.js", + "./assets/**/*.mjs", + "./assets/**/*.cjs", + "./assets/**/*.d.ts", + "./src/server/web/**/*.js", + "./src/server/web/**/*.mjs", + "./src/server/web/**/*.cjs", + "./src/server/web/**/*.d.ts" + ], + "exclude": [ + "**/node_modules/**/*", + "**/built/**/*", + "./jspm_packages", + "./tmp", + "./temp", + "./test", + "./test-federation", + "./test-server", + "**/coverage/**/*" + ] +} diff --git a/packages/backend/jsconfig.json b/packages/backend/jsconfig.json deleted file mode 100644 index 5c3f2689c6..0000000000 --- a/packages/backend/jsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "skipLibCheck": true, - "allowJs": true, - "checkJs": true - }, - "include": [ - "./**/*.js" - ], - "exclude": [ - "node_modules", - "./built", - "./jspm_packages", - "./tmp", - "./temp", - "./test", - "./test-federation", - "./test-server" - ] -} diff --git a/packages/backend/package.json b/packages/backend/package.json index 1edb516312..5fb88f5e72 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -18,11 +18,18 @@ "build": "swc src -d built -D --strip-leading-paths", "build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc --strip-leading-paths", "watch:swc": "swc src -d built -D -w --strip-leading-paths", - "build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json", + "build:tsc": "tsc -p tsconfig.backend.json && tsc-alias -p tsconfig.backend.json", "watch": "node ./scripts/watch.mjs", "restart": "pnpm build && pnpm start", "dev": "node ./scripts/dev.mjs", - "typecheck": "pnpm --filter megalodon build && tsc --noEmit && tsc -p test --noEmit && tsc -p test-federation --noEmit", + "typecheck-all": "pnpm --filter megalodon build && pnpm run --no-bail typecheck:scripts && pnpm run --no-bail typecheck:backend && pnpm run --no-bail typecheck:frontend && pnpm run --no-bail typecheck:backend:web && pnpm run --no-bail typecheck:test && pnpm run --no-bail typecheck:test-federation && pnpm run --no-bail typecheck:test-server", + "typecheck": "pnpm --filter megalodon build && pnpm run typecheck:scripts && pnpm run typecheck:backend && pnpm run typecheck:frontend && pnpm run typecheck:test && pnpm run typecheck:test-federation && pnpm run typecheck:test-server", + "typecheck:scripts": "tsc -p tsconfig.scripts.json --noEmit", + "typecheck:backend": "tsc -p tsconfig.backend.json --noEmit", + "typecheck:frontend": "tsc -p jsconfig.frontend.json --noEmit", + "typecheck:test": "tsc -p test/tsconfig.json --noEmit", + "typecheck:test-federation": "tsc -p test-federation/tsconfig.json --noEmit", + "typecheck:test-server": "tsc -p test-server/tsconfig.json --noEmit", "eslint-all": "pnpm run --no-bail eslint:backend && pnpm run --no-bail eslint:test && pnpm run --no-bail eslint:test-federation && pnpm run --no-bail eslint:test-server", "eslint": "pnpm run eslint:backend && pnpm run eslint:test && pnpm run eslint:test-federation && pnpm run eslint:test-server", "eslint:backend": "eslint --quiet --cache -c eslint.config.js .", @@ -30,11 +37,11 @@ "eslint:test-federation": "eslint --quiet --cache -c test-federation/eslint.config.js ./test-federation", "eslint:test-server": "eslint --quiet --cache -c test-server/eslint.config.js ./test-server", "lint": "pnpm typecheck && pnpm eslint", - "jest": "cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.unit.cjs", - "jest:e2e": "cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.e2e.cjs", - "jest:fed": "node ./jest.js --forceExit --config jest.config.fed.cjs", - "jest-and-coverage": "cross-env NODE_ENV=test node ./jest.js --coverage --forceExit --config jest.config.unit.cjs", - "jest-and-coverage:e2e": "cross-env NODE_ENV=test node ./jest.js --coverage --forceExit --config jest.config.e2e.cjs", + "jest": "cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.unit.ts", + "jest:e2e": "cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.e2e.ts", + "jest:fed": "node ./jest.js --forceExit --config jest.config.fed.ts", + "jest-and-coverage": "cross-env NODE_ENV=test node ./jest.js --coverage --forceExit --config jest.config.unit.ts", + "jest-and-coverage:e2e": "cross-env NODE_ENV=test node ./jest.js --coverage --forceExit --config jest.config.e2e.ts", "jest-clear": "cross-env NODE_ENV=test node ./jest.js --clearCache", "test": "pnpm jest", "test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e", @@ -176,7 +183,6 @@ "tsc-alias": "1.8.16", "tsconfig-paths": "4.2.0", "typeorm": "0.3.27", - "typescript": "5.9.2", "ulid": "3.0.1", "uuid": "13.0.0", "vary": "1.1.2", @@ -240,6 +246,9 @@ "nodemon": "3.1.10", "pid-port": "2.0.0", "simple-oauth2": "5.1.0", + "ts-jest": "29.4.4", + "ts-node": "10.9.2", + "typescript": "5.9.2", "supertest": "7.1.4" } } diff --git a/packages/backend/scripts/dev.mjs b/packages/backend/scripts/dev.mjs index b41c6749aa..05a8d01ffc 100644 --- a/packages/backend/scripts/dev.mjs +++ b/packages/backend/scripts/dev.mjs @@ -5,7 +5,7 @@ import { execa, execaNode } from 'execa'; -/** @type {import('execa').ExecaChildProcess | undefined} */ +/** @type {import('execa').ResultPromise | undefined} */ let backendProcess; async function execBuildAssets() { @@ -32,8 +32,8 @@ async function killProc() { if (backendProcess) { backendProcess.catch(() => {}); // backendProcess.kill()によって発生する例外を無視するためにcatch()を呼び出す backendProcess.kill(); - await new Promise(resolve => backendProcess.on('exit', resolve)); - backendProcess = undefined; + await new Promise(resolve => backendProcess?.on('exit', resolve)) + .finally(() => backendProcess = undefined); } } @@ -49,7 +49,7 @@ async function killProc() { stdio: [process.stdin, process.stdout, process.stderr, 'ipc'], serialization: "json", }) - .on('message', async (message) => { + .on('message', /** @param {{type: string}} message */ async (message) => { if (message.type === 'exit') { // かならずbuild->build-assetsの順番で呼び出したいので、 // 少々トリッキーだがnodemonからのexitイベントを利用してbuild-assets->startを行う。 diff --git a/packages/backend/scripts/watch.mjs b/packages/backend/scripts/watch.mjs index a0ccea3b16..cd92eb0701 100644 --- a/packages/backend/scripts/watch.mjs +++ b/packages/backend/scripts/watch.mjs @@ -8,20 +8,20 @@ import { execa } from 'execa'; (async () => { // なぜかchokidarが動かない影響で、watchされない /* - execa('tsc-alias', ['-w', '-p', 'tsconfig.json'], { + execa('tsc-alias', ['-w', '-p', 'tsconfig.backend.json'], { stdout: process.stdout, stderr: process.stderr, }); */ setInterval(() => { - execa('tsc-alias', ['-p', 'tsconfig.json'], { + execa('tsc-alias', ['-p', 'tsconfig.backend.json'], { stdout: process.stdout, stderr: process.stderr, }); }, 3000); - execa('tsc', ['-w', '-p', 'tsconfig.json'], { + execa('tsc', ['-w', '-p', 'tsconfig.backend.json'], { stdout: process.stdout, stderr: process.stderr, }); diff --git a/packages/backend/src/misc/get-note-summary.ts b/packages/backend/src/misc/get-note-summary.ts index be183b4979..c6a5493531 100644 --- a/packages/backend/src/misc/get-note-summary.ts +++ b/packages/backend/src/misc/get-note-summary.ts @@ -6,11 +6,49 @@ import { appendContentWarning } from './append-content-warning.js'; import type { Packed } from './json-schema.js'; +// export type PackedNoteForSummary = Omit>, 'user'> & { +// user: Omit['user']>, 'instance'> & { +// instance?: Partial['user']['instance']> | null; +// }; +// }; + +// Workaround for weird typescript but +// type Pivot = { +// [KNote in keyof N]?: KNote extends 'user' +// ? { +// [KUser in keyof U]?: KUser extends 'instance' +// ? { +// [KInst in keyof I]?: I[KInst] | undefined; +// } +// : U[KUser] +// } +// : N[KNote] +// }; +// type Pivot = Split & { +// user: Split & { +// instance: I | undefined; +// }; +// }; +// +// type Split> = { +// [K in (keyof T) & R]: T[K]; +// } & { +// [K in (keyof T) & O]?: T[K] | undefined; +// }; +// +// export type PackedNoteForSummary = Pivot, Packed<'Note'>['user'], NonNullable['user']['instance']>>; +export type PackedNoteForSummary = DeepPartial>; + +// Do we really not have a type for this yet?? +type DeepPartial = { + [K in keyof T]?: T[K] extends object ? DeepPartial : T[K]; +}; + /** * 投稿を表す文字列を取得します。 * @param {*} note (packされた)投稿 */ -export const getNoteSummary = (note: Packed<'Note'>): string => { +export const getNoteSummary = (note: PackedNoteForSummary): string => { if (note.deletedAt) { return '(❌⛔)'; } @@ -26,13 +64,13 @@ export const getNoteSummary = (note: Packed<'Note'>): string => { if (note.mandatoryCW) { cw = appendContentWarning(cw, `Note is flagged: "${note.mandatoryCW}"`); } - if (note.user.mandatoryCW) { + if (note.user?.mandatoryCW) { const username = note.user.host ? `@${note.user.username}@${note.user.host}` : `@${note.user.username}`; cw = appendContentWarning(cw, `${username} is flagged: "${note.user.mandatoryCW}"`); } - if (note.user.instance?.mandatoryCW) { + if (note.user?.instance?.mandatoryCW) { cw = appendContentWarning(cw, `${note.user.host} is flagged: "${note.user.instance.mandatoryCW}"`); } diff --git a/packages/backend/src/server/api/mastodon/MastodonConverters.ts b/packages/backend/src/server/api/mastodon/MastodonConverters.ts index 7307ec38e3..84cf07c2ed 100644 --- a/packages/backend/src/server/api/mastodon/MastodonConverters.ts +++ b/packages/backend/src/server/api/mastodon/MastodonConverters.ts @@ -287,7 +287,7 @@ export class MastodonConverters { this.getUser(p) .then(u => this.encode(u, mentionedRemoteUsers)) .catch(() => null))) - .then((p: Entity.Mention[]) => p.filter(m => m)); + .then((p: (Entity.Mention | null)[]) => p.filter(m => m != null)); const tags = note.tags.map(tag => { return { @@ -345,7 +345,7 @@ export class MastodonConverters { sensitive: status.sensitive || !!cw, spoiler_text: cw, visibility: status.visibility, - media_attachments: status.media_attachments.map((a: Entity.Account) => convertAttachment(a)), + media_attachments: status.media_attachments.map((a: Entity.Attachment) => convertAttachment(a)), mentions: mentions, tags: tags, card: null, //FIXME diff --git a/packages/backend/src/server/api/mastodon/endpoints/instance.ts b/packages/backend/src/server/api/mastodon/endpoints/instance.ts index f502b5e191..109b02f2d4 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/instance.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/instance.ts @@ -41,8 +41,7 @@ export class ApiInstanceMastodon { const response: MastodonEntity.Instance = { uri: this.config.host, title: this.meta.name || 'Sharkey', - shortDescription: this.meta.description || 'This is a vanilla Sharkey Instance. It doesn\'t seem to have a description.', - description: this.meta.about || 'This is a vanilla Sharkey Instance.', + description: this.meta.description || this.meta.about || 'This is a vanilla Sharkey Instance.', email: instance.email || '', version: `3.0.0 (compatible; Sharkey ${this.config.version}; like Akkoma)`, urls: instance.urls, diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index 7a058a0ed9..f5942a5267 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -10,7 +10,6 @@ import { parseTimelineArgs, TimelineArgs, toBoolean, toInt } from '@/server/api/ import { MastodonClientService } from '@/server/api/mastodon/MastodonClientService.js'; import { MastodonDataService } from '@/server/api/mastodon/MastodonDataService.js'; import { getNoteSummary } from '@/misc/get-note-summary.js'; -import type { Packed } from '@/misc/json-schema.js'; import { isPureRenote } from '@/misc/is-renote.js'; import { convertAttachment, convertPoll, MastodonConverters } from '../MastodonConverters.js'; import type { Entity } from 'megalodon'; @@ -46,7 +45,34 @@ export class ApiStatusMastodon { // Fixup - Discord ignores CWs and renders the entire post. if (response.sensitive && _request.headers['user-agent']?.match(/\bDiscordbot\//)) { - response.content = getNoteSummary(data.data satisfies Packed<'Note'>); + // TODO move this mastoConverters? + response.content = getNoteSummary({ + ...data.data, + user: { + ...data.data.account, + emojis: {}, + noindex: data.data.account.noindex ?? false, + }, + visibility: data.data.visibility === 'direct' + ? 'specified' + : data.data.visibility === 'private' + ? 'followers' + : data.data.visibility === 'unlisted' + ? 'home' + : data.data.visibility, + mentions: data.data.mentions.map(m => m.id), + tags: data.data.tags.map(t => t.name), + poll: data.data.poll && { + ...data.data.poll, + choices: data.data.poll.options.map(o => ({ + ...o, + text: o.title, + votes: o.votes_count ?? 0, + isVoted: o.votes_count != null, + })), + }, + emojis: {}, + }); response.media_attachments = []; response.in_reply_to_id = null; response.in_reply_to_account_id = null; @@ -182,7 +208,7 @@ export class ApiStatusMastodon { if (body.in_reply_to_id && removed === '/unreact') { const id = body.in_reply_to_id; const post = await client.getStatus(id); - const react = post.data.emoji_reactions.filter((e: Entity.Emoji) => e.me)[0].name; + const react = post.data.emoji_reactions.filter((e: Entity.Reaction) => e.me)[0].name; const data = await client.deleteEmojiReaction(id, react); return reply.send(data.data); } diff --git a/packages/backend/src/server/web/boot.embed.js b/packages/backend/src/server/web/boot.embed.js index 4f27e2fb30..97eb6ef458 100644 --- a/packages/backend/src/server/web/boot.embed.js +++ b/packages/backend/src/server/web/boot.embed.js @@ -18,7 +18,7 @@ let forceError = localStorage.getItem('forceError'); if (forceError != null) { - renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.'); + renderError('FORCED_ERROR'); return; } diff --git a/packages/backend/src/server/web/cli.js b/packages/backend/src/server/web/cli.js index 30ee77f4d9..03e0701b32 100644 --- a/packages/backend/src/server/web/cli.js +++ b/packages/backend/src/server/web/cli.js @@ -41,7 +41,7 @@ window.onload = async () => { document.getElementById('submit').addEventListener('click', () => { api('notes/create', { - text: document.getElementById('text').value + text: (/** @type {HTMLInputElement} */(document.getElementById('text'))).value }).then(() => { location.reload(); }); diff --git a/packages/backend/src/server/web/error.js b/packages/backend/src/server/web/error.js index 4c6ae730b3..5ea45b5aab 100644 --- a/packages/backend/src/server/web/error.js +++ b/packages/backend/src/server/web/error.js @@ -29,6 +29,7 @@ el.textContent = reload; } + /** @type {NodeListOf} */ const i18nEls = document.querySelectorAll('[data-i18n]'); for (const el of i18nEls) { const key = el.dataset.i18n; diff --git a/packages/backend/src/server/web/global.d.ts b/packages/backend/src/server/web/global.d.ts new file mode 100644 index 0000000000..db55d05b2d --- /dev/null +++ b/packages/backend/src/server/web/global.d.ts @@ -0,0 +1,3 @@ +declare const CLIENT_ENTRY: string; +declare const LANGS_VERSION: string; +declare const LANGS: string[]; diff --git a/packages/backend/test-federation/tsconfig.json b/packages/backend/test-federation/tsconfig.json index f9cd6e3850..c6b1ba2ae8 100644 --- a/packages/backend/test-federation/tsconfig.json +++ b/packages/backend/test-federation/tsconfig.json @@ -1,24 +1,16 @@ { + "extends": "../../shared/tsconfig.node.json", "compilerOptions": { - "target": "ES2022", - "module": "nodenext", - "moduleResolution": "nodenext", - "strict": true, - "skipLibCheck": true, - "resolveJsonModule": true, - "isolatedModules": true, - "incremental": true, + "types": ["jest", "node"], + "verbatimModuleSyntax": false, "outDir": "./built", - "baseUrl": "./", + "paths": { + "@/*": ["../src/*"] + }, "typeRoots": [ - "../node_modules/@types", - ], - "lib": [ - "esnext" - ], - "types": ["jest", "node"] + "../node_modules/@types" + ] }, - "compileOnSave": false, "include": [ "daemon.ts", "./test/**/*.ts" diff --git a/packages/backend/test-server/.swcrc b/packages/backend/test-server/.swcrc index eeac7eabc6..6cf275985e 100644 --- a/packages/backend/test-server/.swcrc +++ b/packages/backend/test-server/.swcrc @@ -17,7 +17,7 @@ "paths": { "@/*": ["*"] }, - "target": "es2022" + "target": "ESNext" }, "minify": false } diff --git a/packages/backend/test-server/tsconfig.json b/packages/backend/test-server/tsconfig.json index cb394ecccd..5472c60509 100644 --- a/packages/backend/test-server/tsconfig.json +++ b/packages/backend/test-server/tsconfig.json @@ -1,53 +1,28 @@ { + "extends": "../tsconfig.backend.json", "compilerOptions": { - "allowJs": true, - "noEmitOnError": true, - "noImplicitAny": true, - "noImplicitReturns": true, - "noUnusedParameters": false, - "noUnusedLocals": false, - "noFallthroughCasesInSwitch": true, - "declaration": false, - "sourceMap": true, - "target": "ES2022", - "module": "nodenext", - "moduleResolution": "nodenext", - "allowSyntheticDefaultImports": true, - "removeComments": false, - "noLib": false, - "strict": true, - "strictNullChecks": true, - "strictPropertyInitialization": false, - "skipLibCheck": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "resolveJsonModule": true, - "isolatedModules": true, - "incremental": true, - "rootDir": "../src", - "baseUrl": "./", + "rootDir": "../", "paths": { "@/*": ["../src/*"] }, "outDir": "../built-test", - "types": [ - "node" - ], "typeRoots": [ "../src/@types", "../node_modules/@types", "../node_modules" - ], - "lib": [ - "esnext" ] }, - "compileOnSave": false, "include": [ "./**/*.ts", "../src/**/*.ts" ], "exclude": [ + "**/node_modules", + "**/built/", + "**/*.test.ts", + "./test/**/*", + "./test-federation/**/*", + "./test-server/**/*", "../src/**/*.test.ts" ] } diff --git a/packages/backend/test/tsconfig.json b/packages/backend/test/tsconfig.json index f3b6a5108d..74376f3a78 100644 --- a/packages/backend/test/tsconfig.json +++ b/packages/backend/test/tsconfig.json @@ -1,43 +1,19 @@ { + "extends": "../../shared/tsconfig.node.json", "compilerOptions": { - "allowJs": true, - "noEmitOnError": false, - "noImplicitAny": true, - "noImplicitReturns": true, - "noUnusedParameters": false, - "noUnusedLocals": false, - "noFallthroughCasesInSwitch": true, - "declaration": false, + "types": ["jest", "node"], + "outDir": "./built", "sourceMap": true, - "target": "ES2022", - "module": "nodenext", - "moduleResolution": "nodenext", - "allowSyntheticDefaultImports": true, + "verbatimModuleSyntax": false, "removeComments": false, - "noLib": false, - "strict": true, - "strictNullChecks": true, - "strictPropertyInitialization": false, - "skipLibCheck": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "resolveJsonModule": true, - "isolatedModules": true, - "incremental": true, - "baseUrl": "./", "paths": { "@/*": ["../src/*"] }, "typeRoots": [ "../node_modules/@types", "../src/@types" - ], - "lib": [ - "esnext" - ], - "types": ["jest", "node"] + ] }, - "compileOnSave": false, "include": [ "./**/*.ts", "../src/**/*.test.ts", diff --git a/packages/backend/tsconfig.backend.json b/packages/backend/tsconfig.backend.json new file mode 100644 index 0000000000..53a0136cfd --- /dev/null +++ b/packages/backend/tsconfig.backend.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../shared/tsconfig.node.json", + "compilerOptions": { + "removeComments": false, + "verbatimModuleSyntax": false, + "rootDir": "./src", + "outDir": "./built", + "paths": { + "@/*": ["./src/*"] + }, + "typeRoots": [ + "./src/@types", + "./node_modules/@types", + "./node_modules" + ], + }, + "include": [ + "./src/**/*.ts" + ], + "exclude": [ + "node_modules", + "**/built/", + "**/*.test.ts", + "./src/server/web/**/*.js", + "./src/server/web/**/*.mjs", + "./src/server/web/**/*.cjs", + "./src/server/web/**/*.d.ts", + "test/**/*", + "test-federation/**/*", + "test-server/**/*" + ] +} diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index afed1f186c..8bb0f921ee 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -1,53 +1,10 @@ { - "compilerOptions": { - "allowJs": true, - "noEmitOnError": true, - "noImplicitAny": true, - "noImplicitReturns": true, - "noUnusedParameters": false, - "noUnusedLocals": false, - "noFallthroughCasesInSwitch": true, - "declaration": false, - "sourceMap": false, - "target": "ES2022", - "module": "nodenext", - "moduleResolution": "nodenext", - "allowSyntheticDefaultImports": true, - "removeComments": false, - "noLib": false, - "strict": true, - "strictNullChecks": true, - "strictPropertyInitialization": false, - "skipLibCheck": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "resolveJsonModule": true, - "isolatedModules": true, - "incremental": true, - "rootDir": "./src", - "baseUrl": "./", - "paths": { - "@/*": ["./src/*"] - }, - "outDir": "./built", - "types": [ - "node" - ], - "typeRoots": [ - "./src/@types", - "./node_modules/@types", - "./node_modules" - ], - "lib": [ - "esnext" - ] - }, - "compileOnSave": false, - "include": [ - "./src/**/*.ts" - ], - "exclude": [ - "node_modules", - "./src/**/*.test.ts" + "$schema": "https://json.schemastore.org/tsconfig", + "files": [], + // WebStorm only reads one tsconfig per directory, so this tricks it into loading both. + "references": [ + { "path": "./tsconfig.scripts.json" }, + { "path": "./tsconfig.backend.json" }, + { "path": "./jsconfig.frontend.json" } ] } diff --git a/packages/backend/tsconfig.scripts.json b/packages/backend/tsconfig.scripts.json new file mode 100644 index 0000000000..11ef48c44a --- /dev/null +++ b/packages/backend/tsconfig.scripts.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../shared/tsconfig.node.json", + "compilerOptions": { + "noImplicitAny": false, + "allowImportingTsExtensions": true, + }, + "include": [ + "jest.*", + "eslint.*", + "scripts/**/*" + ], + "exclude": [ + "ormconfig.js", + "scripts/check_connect.js", + "scripts/generate_api_json.js" + ] +}