modernize backend to target the same ES and TS standards as the rest of the app

This commit is contained in:
Hazelnoot 2025-10-06 00:01:10 -04:00
parent 22f49db21f
commit 9beeca5942
29 changed files with 460 additions and 232 deletions

View file

@ -17,7 +17,7 @@
"paths": {
"@/*": ["*"]
},
"target": "es2022"
"target": "ESNext"
},
"minify": false,
"sourceMaps": "inline"

View file

@ -7,7 +7,7 @@
/** @type {NodeListOf<HTMLIFrameElement>} */
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;

View file

@ -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,
},
},
},
];

View file

@ -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: "<rootDir>/built-test/entry.js",
setupFilesAfterEnv: ["<rootDir>/test/jest.setup.ts"],
testMatch: [
"<rootDir>/test/e2e/**/*.ts",
],
};

View file

@ -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: "<rootDir>/built-test/entry.js",
setupFilesAfterEnv: ["<rootDir>/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: [
"<rootDir>/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: [
"<rootDir>/test/e2e/**/*.ts",
],
};

View file

@ -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,

View file

@ -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,

View file

@ -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: [
"<rootDir>/test/unit/**/*.ts",

View file

@ -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);
}

View file

@ -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/**/*"
]
}

View file

@ -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"
]
}

View file

@ -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"
}
}

View file

@ -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を行う。

View file

@ -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,
});

View file

@ -6,11 +6,49 @@
import { appendContentWarning } from './append-content-warning.js';
import type { Packed } from './json-schema.js';
// export type PackedNoteForSummary = Omit<Partial<Packed<'Note'>>, 'user'> & {
// user: Omit<Partial<Packed<'Note'>['user']>, 'instance'> & {
// instance?: Partial<Packed<'Note'>['user']['instance']> | null;
// };
// };
// Workaround for weird typescript but
// type Pivot<N, U, I> = {
// [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<N extends { user: U }, U extends { instance?: I | null }, I extends object> = Split<N, 'user'> & {
// user: Split<U, 'instance'> & {
// instance: I | undefined;
// };
// };
//
// type Split<T, O extends keyof T, R extends keyof T = Exclude<keyof T, O>> = {
// [K in (keyof T) & R]: T[K];
// } & {
// [K in (keyof T) & O]?: T[K] | undefined;
// };
//
// export type PackedNoteForSummary = Pivot<Packed<'Note'>, Packed<'Note'>['user'], NonNullable<Packed<'Note'>['user']['instance']>>;
export type PackedNoteForSummary = DeepPartial<Packed<'Note'>>;
// Do we really not have a type for this yet??
type DeepPartial<T extends object> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : 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}"`);
}

View file

@ -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

View file

@ -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,

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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();
});

View file

@ -29,6 +29,7 @@
el.textContent = reload;
}
/** @type {NodeListOf<HTMLElement>} */
const i18nEls = document.querySelectorAll('[data-i18n]');
for (const el of i18nEls) {
const key = el.dataset.i18n;

View file

@ -0,0 +1,3 @@
declare const CLIENT_ENTRY: string;
declare const LANGS_VERSION: string;
declare const LANGS: string[];

View file

@ -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"

View file

@ -17,7 +17,7 @@
"paths": {
"@/*": ["*"]
},
"target": "es2022"
"target": "ESNext"
},
"minify": false
}

View file

@ -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"
]
}

View file

@ -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",

View file

@ -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/**/*"
]
}

View file

@ -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" }
]
}

View file

@ -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"
]
}