mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-07-25 10:54:56 +02:00
chore: pnpm dev時のプロセス管理をより綿密に行い、Windows環境でのプロセス残留を抑止 (#17690)
This commit is contained in:
@@ -3,6 +3,7 @@ import { version as summalyVersion } from '@misskey-dev/summaly';
|
||||
import type { Plugin, ExternalOption } from 'rolldown';
|
||||
import { execa, execaNode } from 'execa';
|
||||
import type { ResultPromise } from 'execa';
|
||||
import fkill from 'fkill';
|
||||
import esmShim from '@rollup/plugin-esm-shim';
|
||||
|
||||
/**
|
||||
@@ -10,6 +11,7 @@ import esmShim from '@rollup/plugin-esm-shim';
|
||||
*/
|
||||
function backendDevServerPlugin(): Plugin {
|
||||
let backendProcess: ResultPromise | null = null;
|
||||
let backendShutdownPromise: Promise<void> | null = null;
|
||||
|
||||
async function runBuildAssets() {
|
||||
await execa('pnpm', ['run', 'build-assets'], {
|
||||
@@ -20,12 +22,31 @@ function backendDevServerPlugin(): Plugin {
|
||||
}
|
||||
|
||||
async function killBackendProcess() {
|
||||
if (backendProcess) {
|
||||
backendProcess.catch(() => {}); // backendProcess.kill()によって発生する例外を無視するためにcatch()を呼び出す
|
||||
backendProcess.kill();
|
||||
await new Promise(resolve => backendProcess!.on('exit', resolve));
|
||||
backendProcess = null;
|
||||
}
|
||||
if (backendShutdownPromise) return backendShutdownPromise;
|
||||
if (!backendProcess) return;
|
||||
|
||||
const processToKill = backendProcess;
|
||||
backendProcess = null;
|
||||
processToKill.catch(() => {}); // プロセスの終了によって発生する例外を無視するためにcatch()を呼び出す
|
||||
|
||||
backendShutdownPromise = (async () => {
|
||||
if (process.platform === 'win32' && processToKill.pid != null) {
|
||||
await fkill(processToKill.pid, {
|
||||
force: true,
|
||||
tree: true,
|
||||
silent: true,
|
||||
waitForExit: 5000,
|
||||
});
|
||||
} else {
|
||||
processToKill.kill();
|
||||
}
|
||||
|
||||
await processToKill.catch(() => {});
|
||||
})().finally(() => {
|
||||
backendShutdownPromise = null;
|
||||
});
|
||||
|
||||
return backendShutdownPromise;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -49,6 +70,9 @@ function backendDevServerPlugin(): Plugin {
|
||||
await runBuildAssets();
|
||||
}
|
||||
},
|
||||
async closeWatcher() {
|
||||
await killBackendProcess();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
308
scripts/dev.mjs
308
scripts/dev.mjs
@@ -10,104 +10,264 @@ import { execa } from 'execa';
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
|
||||
await execa('pnpm', ['clean'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
/** @type {Set<import('execa').ResultPromise>} */
|
||||
const childProcesses = new Set();
|
||||
/** @type {Set<import('execa').ResultPromise>} */
|
||||
const persistentChildProcesses = new Set();
|
||||
let shuttingDown = false;
|
||||
let persistentChildProcessesStarted = false;
|
||||
let persistentChildProcessFailed = false;
|
||||
/** @type {Promise<void> | null} */
|
||||
let shutdownPromise = null;
|
||||
|
||||
/**
|
||||
* 開発用コマンドを起動し、終了時にまとめて停止できるよう追跡する。
|
||||
* Windows では Ctrl+C の配信先を分離し、出力を親コンソールへ中継する。
|
||||
*
|
||||
* @param {string} command - 実行するコマンド。
|
||||
* @param {string[]} args - コマンドへ渡す引数。
|
||||
* @param {import('execa').Options} options - execa の起動オプション。
|
||||
* @returns {import('execa').ResultPromise} 起動した子プロセス。
|
||||
*/
|
||||
function spawnChildProcess(command, args, options) {
|
||||
const isWindows = process.platform === 'win32';
|
||||
const pnpmPath = _dirname + '/../node_modules/pnpm/bin/pnpm.mjs';
|
||||
const windowsCommand = [process.execPath, pnpmPath, ...args]
|
||||
.map(argument => `"${argument}"`)
|
||||
.join(' ');
|
||||
const executable = isWindows && command === 'pnpm' ? process.env.ComSpec ?? 'cmd.exe' : command;
|
||||
const executableArgs = isWindows && command === 'pnpm'
|
||||
? ['/d', '/s', '/c', `start "" /b /wait ${windowsCommand}`]
|
||||
: args;
|
||||
const childProcess = execa(executable, executableArgs, isWindows ? {
|
||||
...options,
|
||||
// `start /b` keeps the process in the current console without forwarding
|
||||
// Ctrl+C, allowing only this supervisor to coordinate the shutdown.
|
||||
windowsVerbatimArguments: true,
|
||||
windowsHide: false,
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
buffer: false,
|
||||
} : options);
|
||||
|
||||
if (isWindows) {
|
||||
if (options.stdout != null) childProcess.stdout?.pipe(options.stdout, { end: false });
|
||||
if (options.stderr != null) childProcess.stderr?.pipe(options.stderr, { end: false });
|
||||
}
|
||||
|
||||
childProcesses.add(childProcess);
|
||||
return childProcess;
|
||||
}
|
||||
|
||||
/**
|
||||
* 子プロセスの終了を待機し、追跡対象から取り除く。
|
||||
*
|
||||
* @param {string} command - 実行するコマンド。
|
||||
* @param {string[]} args - コマンドへ渡す引数。
|
||||
* @param {import('execa').Options} options - execa の起動オプション。
|
||||
* @returns {Promise<import('execa').Result>} 子プロセスの実行結果。
|
||||
*/
|
||||
async function runChildProcess(command, args, options) {
|
||||
const childProcess = spawnChildProcess(command, args, options);
|
||||
|
||||
try {
|
||||
return await childProcess;
|
||||
} finally {
|
||||
childProcesses.delete(childProcess);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 常駐する子プロセスがすべて終了していれば、終了結果を引き継いで親も終了する。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function shutdownIfAllPersistentChildProcessesStopped() {
|
||||
if (shuttingDown || !persistentChildProcessesStarted || persistentChildProcesses.size > 0) return;
|
||||
|
||||
void shutdown(persistentChildProcessFailed ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 常駐する子プロセスを起動し、終了時の追跡解除・エラー出力・親の終了判定を設定する。
|
||||
*
|
||||
* @param {string} command - 実行するコマンド。
|
||||
* @param {string[]} args - コマンドへ渡す引数。
|
||||
* @param {import('execa').Options} options - execa の起動オプション。
|
||||
* @returns {void}
|
||||
*/
|
||||
function startChildProcess(command, args, options) {
|
||||
const childProcess = spawnChildProcess(command, args, options);
|
||||
persistentChildProcesses.add(childProcess);
|
||||
|
||||
void childProcess.then(() => {
|
||||
childProcesses.delete(childProcess);
|
||||
persistentChildProcesses.delete(childProcess);
|
||||
shutdownIfAllPersistentChildProcessesStopped();
|
||||
}, error => {
|
||||
childProcesses.delete(childProcess);
|
||||
persistentChildProcesses.delete(childProcess);
|
||||
if (!shuttingDown) {
|
||||
persistentChildProcessFailed = true;
|
||||
console.error(error);
|
||||
shutdownIfAllPersistentChildProcessesStopped();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 子プロセスとその配下のプロセスを停止し、終了を待機する。
|
||||
*
|
||||
* @param {import('execa').ResultPromise} childProcess - 停止する子プロセス。
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function stopChildProcess(childProcess) {
|
||||
if (process.platform === 'win32' && childProcess.pid != null) {
|
||||
const result = await execa('taskkill', ['/pid', childProcess.pid.toString(), '/t', '/f'], {
|
||||
reject: false,
|
||||
});
|
||||
if (result.failed) childProcess.kill();
|
||||
} else {
|
||||
childProcess.kill();
|
||||
}
|
||||
|
||||
await childProcess.catch(() => {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 追跡中の子プロセスを一度だけ停止して、指定した終了コードで終了する。
|
||||
*
|
||||
* @param {number} exitCode - 親プロセスに返す終了コード。
|
||||
* @returns {Promise<void>} 実行中または完了した停止処理。
|
||||
*/
|
||||
function shutdown(exitCode) {
|
||||
if (shutdownPromise != null) return shutdownPromise;
|
||||
|
||||
shuttingDown = true;
|
||||
shutdownPromise = (async () => {
|
||||
await Promise.allSettled([...childProcesses].map(stopChildProcess));
|
||||
process.exit(exitCode);
|
||||
})();
|
||||
|
||||
return shutdownPromise;
|
||||
}
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
void shutdown(0);
|
||||
});
|
||||
|
||||
// アセットのビルドで依存しているので一番最初に必要
|
||||
await execa('pnpm', ['--filter', 'i18n', 'build'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
process.on('SIGTERM', () => {
|
||||
void shutdown(0);
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
execa('pnpm', ['build-pre'], {
|
||||
try {
|
||||
await runChildProcess('pnpm', ['clean'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
}),
|
||||
execa('pnpm', ['build-assets'], {
|
||||
});
|
||||
|
||||
// アセットのビルドで依存しているので一番最初に必要
|
||||
await runChildProcess('pnpm', ['--filter', 'i18n', 'build'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
}),
|
||||
execa('pnpm', ['--filter', 'backend...', '--filter=!backend', 'build'], {
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
runChildProcess('pnpm', ['build-pre'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
}),
|
||||
runChildProcess('pnpm', ['build-assets'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
}),
|
||||
runChildProcess('pnpm', ['--filter', 'backend...', '--filter=!backend', 'build'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
}),
|
||||
// icons-subsetterは開発段階では使用されないが、型エラーを抑制するためにはじめの一度だけビルドする
|
||||
runChildProcess('pnpm', ['--filter', 'icons-subsetter', 'build'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
}),
|
||||
runChildProcess('pnpm', ['--filter', 'misskey-js', 'build'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
}),
|
||||
]);
|
||||
|
||||
startChildProcess('pnpm', ['build-pre', '--watch'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
}),
|
||||
// icons-subsetterは開発段階では使用されないが、型エラーを抑制するためにはじめの一度だけビルドする
|
||||
execa('pnpm', ['--filter', 'icons-subsetter', 'build'], {
|
||||
});
|
||||
|
||||
startChildProcess('pnpm', ['build-assets', '--watch'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
}),
|
||||
execa('pnpm', ['--filter', 'misskey-js', 'build'], {
|
||||
});
|
||||
|
||||
startChildProcess('pnpm', ['--filter', 'backend', 'dev'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
execa('pnpm', ['build-pre', '--watch'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
startChildProcess('pnpm', ['--filter', 'frontend', 'watch'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
|
||||
execa('pnpm', ['build-assets', '--watch'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
startChildProcess('pnpm', ['--filter', 'frontend-embed', 'watch'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
|
||||
execa('pnpm', ['--filter', 'backend', 'dev'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
startChildProcess('pnpm', ['--filter', 'sw', 'watch'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
|
||||
execa('pnpm', ['--filter', 'frontend', 'watch'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
startChildProcess('pnpm', ['--filter', 'misskey-js', 'watch', '--no-clean'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
|
||||
execa('pnpm', ['--filter', 'frontend-embed', 'watch'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
startChildProcess('pnpm', ['--filter', 'i18n', 'watch', '--no-clean'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
|
||||
execa('pnpm', ['--filter', 'sw', 'watch'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
startChildProcess('pnpm', ['--filter', 'misskey-reversi', 'watch', '--no-clean'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
|
||||
execa('pnpm', ['--filter', 'misskey-js', 'watch', '--no-clean'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
startChildProcess('pnpm', ['--filter', 'misskey-bubble-game', 'watch', '--no-clean'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
|
||||
execa('pnpm', ['--filter', 'i18n', 'watch', '--no-clean'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
|
||||
execa('pnpm', ['--filter', 'misskey-reversi', 'watch', '--no-clean'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
|
||||
execa('pnpm', ['--filter', 'misskey-bubble-game', 'watch', '--no-clean'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
persistentChildProcessesStarted = true;
|
||||
shutdownIfAllPersistentChildProcessesStopped();
|
||||
} catch (error) {
|
||||
if (!shuttingDown) {
|
||||
console.error(error);
|
||||
await shutdown(1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user