1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
/*
* 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 { 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 = fs.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(
'tsgo',
[
'--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.`);
});
}
|