summaryrefslogtreecommitdiff
path: root/packages/i18n/build.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/i18n/build.ts')
-rw-r--r--packages/i18n/build.ts163
1 files changed, 163 insertions, 0 deletions
diff --git a/packages/i18n/build.ts b/packages/i18n/build.ts
new file mode 100644
index 0000000000..a6bbf7dc63
--- /dev/null
+++ b/packages/i18n/build.ts
@@ -0,0 +1,163 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import fs from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { dirname, resolve } from 'node:path';
+import { watch as chokidarWatch } from 'chokidar';
+import * as esbuild from 'esbuild';
+import { build } from 'esbuild';
+import { execa } from 'execa';
+import { globSync } from 'glob';
+import { generateLocaleInterface } from './scripts/generateLocaleInterface.js';
+import type { BuildOptions, BuildResult, Plugin, PluginBuild } from 'esbuild';
+
+const _filename = fileURLToPath(import.meta.url);
+const _dirname = dirname(_filename);
+const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8'));
+const _rootPackageDir = resolve(_dirname, '../../');
+const _rootPackage = JSON.parse(fs.readFileSync(resolve(_rootPackageDir, 'package.json'), 'utf-8'));
+const _frontendLocalesDir = resolve(_dirname, '../../built/_frontend_dist_/locales');
+const _localesDir = resolve(_rootPackageDir, 'locales');
+
+const entryPoints = globSync('./src/**/**.{ts,tsx}');
+
+const options: BuildOptions = {
+ entryPoints,
+ minify: process.env.NODE_ENV === 'production',
+ sourceRoot: 'src',
+ outdir: './built',
+ target: 'es2022',
+ platform: 'node',
+ format: 'esm',
+ sourcemap: 'linked',
+};
+
+// コマンドライン引数を取得
+const args = process.argv.slice(2).map(arg => arg.toLowerCase());
+
+// built配下をすべて削除する
+if (!args.includes('--no-clean')) {
+ fs.rmSync('./built', { recursive: true, force: true });
+}
+
+if (args.includes('--watch')) {
+ await watchSrc();
+} else {
+ await buildSrc();
+}
+
+function copyLocales(): void {
+ const srcDir = _localesDir;
+ const destDir = resolve(_dirname, 'built/locales');
+
+ fs.mkdirSync(destDir, { recursive: true });
+
+ const files = fs.readdirSync(srcDir).filter(f => f.endsWith('.yml'));
+ for (const file of files) {
+ fs.copyFileSync(resolve(srcDir, file), resolve(destDir, file));
+ }
+ console.log(`[${_package.name}] locales copied (${files.length} files).`);
+}
+
+/**
+ * フロントエンド用の locale JSON を書き出す
+ * Service Worker が HTTP 経由で取得するために必要
+ */
+async function writeFrontendLocalesJson(): Promise<void> {
+ // 動的 import でビルド済みモジュールから読み込み(循環参照回避)
+ const { writeFrontendLocalesJson: write } = await import('./built/index.js');
+ await write(_frontendLocalesDir, _rootPackage.version);
+ console.log(`[${_package.name}] frontend locales JSON written to ${_frontendLocalesDir}`);
+}
+
+async function buildSrc(): Promise<void> {
+ console.log(`[${_package.name}] start building...`);
+
+ await generateLocaleInterface(_localesDir);
+
+ await build(options)
+ .then(() => {
+ console.log(`[${_package.name}] build succeeded.`);
+ })
+ .catch((err) => {
+ process.stderr.write(err.stderr);
+ process.exit(1);
+ });
+
+ copyLocales();
+ await writeFrontendLocalesJson();
+
+ if (process.env.NODE_ENV === 'production') {
+ console.log(`[${_package.name}] skip building d.ts because NODE_ENV is production.`);
+ } else {
+ await buildDts();
+ }
+
+ console.log(`[${_package.name}] finish building.`);
+}
+
+function buildDts(): Promise<unknown> {
+ return execa(
+ 'tsc',
+ [
+ '--project', 'tsconfig.json',
+ '--rootDir', 'src',
+ '--outDir', 'built',
+ '--declaration', 'true',
+ '--emitDeclarationOnly', 'true',
+ ],
+ {
+ stdout: process.stdout,
+ stderr: process.stderr,
+ },
+ );
+}
+
+async function watchSrc(): Promise<void> {
+ const localesWatcher = chokidarWatch(_localesDir, {
+ ignoreInitial: true,
+ });
+ localesWatcher.on('all', async (event, path) => {
+ if (!path.endsWith('.yml')) return;
+ console.log(`[${_package.name}] locales changed: ${event} ${path}`);
+ copyLocales();
+ await writeFrontendLocalesJson();
+ await generateLocaleInterface(_localesDir);
+ });
+
+ const plugins: Plugin[] = [{
+ name: 'gen-dts',
+ setup(build: PluginBuild) {
+ build.onStart(() => {
+ console.log(`[${_package.name}] detect changed...`);
+ });
+ build.onEnd(async (result: BuildResult) => {
+ if (result.errors.length > 0) {
+ console.error(`[${_package.name}] watch build failed:`, result);
+ return;
+ }
+ await buildDts();
+ });
+ },
+ }];
+
+ console.log(`[${_package.name}] start watching...`);
+
+ const context = await esbuild.context({ ...options, plugins });
+ await context.watch();
+
+ await new Promise((resolve, reject) => {
+ process.on('SIGHUP', resolve);
+ process.on('SIGINT', resolve);
+ process.on('SIGTERM', resolve);
+ process.on('uncaughtException', reject);
+ process.on('exit', resolve);
+ }).finally(async () => {
+ await context.dispose();
+ await localesWatcher.close();
+ console.log(`[${_package.name}] finish watching.`);
+ });
+}