diff --git a/package.json b/package.json index 01d3037826..a9b6394115 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,9 @@ "build": "pnpm build-pre && pnpm -r build && pnpm build-assets", "build-storybook": "pnpm --filter frontend build-storybook", "build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api", - "start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js", - "start:inspect": "cd packages/backend && node --inspect ./built/boot/entry.js", - "start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js", + "start": "pnpm check:connect && cd packages/backend && pnpm start", + "start:inspect": "cd packages/backend && pnpm start:inspect", + "start:test": "ncp ./.github/misskey/test.yml ./.config/test.yml && cd packages/backend && pnpm start:test", "cli": "cd packages/backend && pnpm cli", "init": "pnpm migrate", "migrate": "cd packages/backend && pnpm migrate", diff --git a/packages/backend/migration/js/migration-config.js b/packages/backend/migration/js/migration-config.js index 853735661b..248acb76fc 100644 --- a/packages/backend/migration/js/migration-config.js +++ b/packages/backend/migration/js/migration-config.js @@ -3,8 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { path as configYamlPath } from '../../built/config.js'; -import * as yaml from 'js-yaml'; +import { path as configJsonPath } from '../../built/config.js'; import fs from "node:fs"; export function isConcurrentIndexMigrationEnabled() { @@ -14,7 +13,7 @@ export function isConcurrentIndexMigrationEnabled() { let loadedConfigCache = undefined; function loadConfigInternal() { - const config = yaml.load(fs.readFileSync(configYamlPath, 'utf-8')); + const config = JSON.parse(fs.readFileSync(configJsonPath, 'utf-8')); return { disallowExternalApRedirect: Boolean(config.disallowExternalApRedirect ?? false), diff --git a/packages/backend/package.json b/packages/backend/package.json index 545779fd7a..32bc519a63 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -7,13 +7,14 @@ "node": "^22.15.0 || ^24.10.0" }, "scripts": { - "start": "node ./built/boot/entry.js", - "start:inspect": "node --inspect ./built/boot/entry.js", - "start:test": "cross-env NODE_ENV=test node ./built/boot/entry.js", - "migrate": "pnpm typeorm migration:run -d ormconfig.js", - "revert": "pnpm typeorm migration:revert -d ormconfig.js", - "cli": "node ./built/boot/cli.js", - "check:connect": "node ./scripts/check_connect.js", + "compile-config": "node ./scripts/compile_config.js", + "start": "pnpm compile-config && node ./built/boot/entry.js", + "start:inspect": "pnpm compile-config && node --inspect ./built/boot/entry.js", + "start:test": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./built/boot/entry.js", + "migrate": "pnpm compile-config && pnpm typeorm migration:run -d ormconfig.js", + "revert": "pnpm compile-config && pnpm typeorm migration:revert -d ormconfig.js", + "cli": "pnpm compile-config && node ./built/boot/cli.js", + "check:connect": "pnpm compile-config && node ./scripts/check_connect.js", "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", @@ -24,11 +25,11 @@ "typecheck": "tsc --noEmit && tsc -p test --noEmit && tsc -p test-federation --noEmit", "eslint": "eslint --quiet \"{src,test-federation}/**/*.ts\"", "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 pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.unit.cjs", + "jest:e2e": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --forceExit --config jest.config.e2e.cjs", + "jest:fed": "pnpm compile-config && node ./jest.js --forceExit --config jest.config.fed.cjs", + "jest-and-coverage": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --coverage --forceExit --config jest.config.unit.cjs", + "jest-and-coverage:e2e": "cross-env NODE_ENV=test pnpm compile-config && cross-env NODE_ENV=test node ./jest.js --coverage --forceExit --config jest.config.e2e.cjs", "jest-clear": "cross-env NODE_ENV=test node ./jest.js --clearCache", "test": "pnpm jest", "test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e", @@ -127,7 +128,6 @@ "ip-cidr": "4.0.2", "ipaddr.js": "2.2.0", "is-svg": "6.1.0", - "js-yaml": "4.1.1", "json5": "2.2.3", "jsonld": "9.0.0", "jsrsasign": "11.1.0", @@ -233,6 +233,7 @@ "jest": "29.7.0", "jest-mock": "29.7.0", "jest-util": "29.7.0", + "js-yaml": "4.1.1", "nodemon": "3.1.11", "pid-port": "2.0.0", "simple-oauth2": "5.1.0", diff --git a/packages/backend/scripts/compile_config.js b/packages/backend/scripts/compile_config.js new file mode 100644 index 0000000000..0f79000883 --- /dev/null +++ b/packages/backend/scripts/compile_config.js @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve } from 'node:path'; +import * as yaml from 'js-yaml'; + +const _filename = fileURLToPath(import.meta.url); +const _dirname = dirname(_filename); + +const dir = `${_dirname}/../../../.config`; + +const configYmlPath = process.env.MISSKEY_CONFIG_YML + ? resolve(dir, process.env.MISSKEY_CONFIG_YML) + : process.env.NODE_ENV === 'test' + ? resolve(dir, 'test.yml') + : resolve(dir, 'default.yml'); + +// Change extension from .yml to .json +const configJsonPath = configYmlPath.replace(/\.yml$/, '.json'); + +if (!fs.existsSync(configYmlPath)) { + console.error(`Configuration file not found: ${configYmlPath}`); + process.exit(1); +} + +const yamlContent = fs.readFileSync(configYmlPath, 'utf-8'); +const config = yaml.load(yamlContent); +fs.writeFileSync(configJsonPath, JSON.stringify(config, null, '\t'), 'utf-8'); + +console.log(`Compiled config: ${configYmlPath} -> ${configJsonPath}`); diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index fdf6fe18e2..85e6302af3 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -6,7 +6,6 @@ import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; -import * as yaml from 'js-yaml'; import { type FastifyServerOptions } from 'fastify'; import type * as Sentry from '@sentry/node'; import type * as SentryVue from '@sentry/vue'; @@ -223,14 +222,19 @@ const _dirname = dirname(_filename); const dir = `${_dirname}/../../../.config`; /** - * Path of configuration file + * Path of configuration file (YAML) */ -export const path = process.env.MISSKEY_CONFIG_YML +const configYmlPath = process.env.MISSKEY_CONFIG_YML ? resolve(dir, process.env.MISSKEY_CONFIG_YML) : process.env.NODE_ENV === 'test' ? resolve(dir, 'test.yml') : resolve(dir, 'default.yml'); +/** + * Path of configuration file (JSON, pre-compiled from YAML) + */ +export const path = configYmlPath.replace(/\.yml$/, '.json'); + export function loadConfig(): Config { const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../built/meta.json`, 'utf-8')); @@ -243,7 +247,7 @@ export function loadConfig(): Config { JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_embed_vite_/manifest.json`, 'utf-8')) : { 'src/boot.ts': { file: null } }; - const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source; + const config = JSON.parse(fs.readFileSync(path, 'utf-8')) as Source; const url = tryCreateUrl(config.url ?? process.env.MISSKEY_URL ?? ''); const version = meta.version; diff --git a/packages/i18n/build.ts b/packages/i18n/build.ts index a6bbf7dc63..fde46f0c18 100644 --- a/packages/i18n/build.ts +++ b/packages/i18n/build.ts @@ -11,6 +11,7 @@ import * as esbuild from 'esbuild'; import { build } from 'esbuild'; import { execa } from 'execa'; import { globSync } from 'glob'; +import * as yaml from 'js-yaml'; import { generateLocaleInterface } from './scripts/generateLocaleInterface.js'; import type { BuildOptions, BuildResult, Plugin, PluginBuild } from 'esbuild'; @@ -49,7 +50,18 @@ if (args.includes('--watch')) { await buildSrc(); } -function copyLocales(): void { +/** + * 何故か文字列にバックスペース文字が混入することがあり、YAMLが壊れるので取り除く + */ +function clean(text: string) { + return text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), ''); +} + +/** + * Convert locale YAML files to JSON during build + * This allows runtime to avoid loading js-yaml + */ +function compileLocales(): void { const srcDir = _localesDir; const destDir = resolve(_dirname, 'built/locales'); @@ -57,9 +69,12 @@ function copyLocales(): void { const files = fs.readdirSync(srcDir).filter(f => f.endsWith('.yml')); for (const file of files) { - fs.copyFileSync(resolve(srcDir, file), resolve(destDir, file)); + const yamlContent = clean(fs.readFileSync(resolve(srcDir, file), 'utf-8')); + const jsonContent = yaml.load(yamlContent); + const jsonFile = file.replace(/\.yml$/, '.json'); + fs.writeFileSync(resolve(destDir, jsonFile), JSON.stringify(jsonContent), 'utf-8'); } - console.log(`[${_package.name}] locales copied (${files.length} files).`); + console.log(`[${_package.name}] locales compiled to JSON (${files.length} files).`); } /** @@ -87,7 +102,7 @@ async function buildSrc(): Promise { process.exit(1); }); - copyLocales(); + compileLocales(); await writeFrontendLocalesJson(); if (process.env.NODE_ENV === 'production') { @@ -123,7 +138,7 @@ async function watchSrc(): Promise { localesWatcher.on('all', async (event, path) => { if (!path.endsWith('.yml')) return; console.log(`[${_package.name}] locales changed: ${event} ${path}`); - copyLocales(); + compileLocales(); await writeFrontendLocalesJson(); await generateLocaleInterface(_localesDir); }); diff --git a/packages/i18n/package.json b/packages/i18n/package.json index d06e485da0..eb511faa2a 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -32,11 +32,9 @@ "esbuild": "0.27.0", "execa": "9.6.0", "glob": "11.1.0", + "js-yaml": "4.1.1", "nodemon": "3.1.11", "tsx": "4.20.6", "typescript": "5.9.3" - }, - "dependencies": { - "js-yaml": "4.1.1" } } diff --git a/packages/i18n/src/index.ts b/packages/i18n/src/index.ts index e428267748..336144fb33 100644 --- a/packages/i18n/src/index.ts +++ b/packages/i18n/src/index.ts @@ -8,7 +8,6 @@ */ import * as fs from 'node:fs'; -import * as yaml from 'js-yaml'; import type { Locale } from './autogen/locale.js'; import type { ILocale, ParameterizedString } from './types.js'; @@ -71,13 +70,6 @@ function merge(...args: (T | ILocale | undefined)[]): T { }), {} as ILocale) as T; } -/** - * 何故か文字列にバックスペース文字が混入することがあり、YAMLが壊れるので取り除く - */ -function clean (text: string) { - return text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), ''); -} - /** * 空文字列が入ることがあり、フォールバックが動作しなくなるのでプロパティごと消す */ @@ -98,7 +90,7 @@ function build(): Record { // https://github.com/misskey-dev/misskey/pull/14057#issuecomment-2192833785 const metaUrl = import.meta.url; const locales = languages.reduce((a, lang) => { - a[lang] = (yaml.load(clean(fs.readFileSync(new URL(`./locales/${lang}.yml`, metaUrl), 'utf-8'))) ?? {}) as ILocale; + a[lang] = (JSON.parse(fs.readFileSync(new URL(`./locales/${lang}.json`, metaUrl), 'utf-8')) ?? {}) as ILocale; return a; }, {} as Locales);