在 Makelore 构建并预检静态发布产物

This commit is contained in:
2026-08-12 21:19:39 +08:00
parent 3a80625fe2
commit 5b44864265
26 changed files with 1275 additions and 217 deletions

View File

@@ -0,0 +1,108 @@
import { spawn, type ChildProcess } from 'node:child_process';
import { existsSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname, join } from 'node:path';
import { app } from 'electron';
const require = createRequire(import.meta.url);
const OUTPUT_LIMIT = 64 * 1024;
export class PublishRuntimeError extends Error {
constructor(readonly code: 'LOCAL_BUILD_RUNTIME_UNAVAILABLE' | 'LOCAL_BUILD_FAILED' | 'LOCAL_BUILD_TIMEOUT') {
super(code);
this.name = 'PublishRuntimeError';
}
}
export type PublishRuntime = {
npmCli: string;
nodeVersion: string;
npmVersion: string;
};
function childEnvironment(): NodeJS.ProcessEnv {
const allowed = ['ALLUSERSPROFILE', 'APPDATA', 'COMMONPROGRAMFILES', 'COMMONPROGRAMFILES(X86)',
'COMMONPROGRAMW6432', 'COMSPEC', 'HOME', 'HOMEDRIVE', 'HOMEPATH', 'LOCALAPPDATA',
'HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY', 'NODE_EXTRA_CA_CERTS',
'http_proxy', 'https_proxy', 'no_proxy',
'NUMBER_OF_PROCESSORS', 'OS', 'PATH', 'PATHEXT', 'PROGRAMDATA', 'PROGRAMFILES',
'PROGRAMFILES(X86)', 'PROGRAMW6432', 'SYSTEMDRIVE', 'SYSTEMROOT', 'TEMP', 'TMP',
'USERPROFILE', 'WINDIR', 'XDG_CACHE_HOME', 'XDG_CONFIG_HOME'];
const env: NodeJS.ProcessEnv = { ELECTRON_RUN_AS_NODE: '1', CI: '1', npm_config_update_notifier: 'false' };
for (const key of allowed) {
const value = process.env[key];
if (value !== undefined) env[key] = value;
}
return env;
}
async function terminateTree(child: ChildProcess): Promise<void> {
if (!child.pid || child.exitCode !== null) return;
if (process.platform === 'win32') {
const taskkill = join(process.env.SYSTEMROOT ?? 'C:\\Windows', 'System32', 'taskkill.exe');
await new Promise<void>((resolve) => {
const killer = spawn(taskkill, ['/pid', String(child.pid), '/t', '/f'], { shell: false, windowsHide: true });
killer.once('close', () => resolve());
killer.once('error', () => resolve());
});
} else {
try { process.kill(-child.pid, 'SIGKILL'); } catch { child.kill('SIGKILL'); }
}
}
export async function runElectronNode(input: {
args: string[];
cwd?: string;
deadlineMs: number;
}): Promise<{ stdout: string; stderr: string }> {
return await new Promise((resolve, reject) => {
const child = spawn(process.execPath, input.args, {
cwd: input.cwd,
env: childEnvironment(),
shell: false,
windowsHide: true,
detached: process.platform !== 'win32',
stdio: ['ignore', 'pipe', 'pipe'],
});
let stdout = '';
let stderr = '';
const append = (current: string, chunk: Buffer) => (current + chunk.toString('utf8')).slice(-OUTPUT_LIMIT);
child.stdout?.on('data', (chunk: Buffer) => { stdout = append(stdout, chunk); });
child.stderr?.on('data', (chunk: Buffer) => { stderr = append(stderr, chunk); });
let timedOut = false;
const timer = setTimeout(() => {
timedOut = true;
void terminateTree(child);
}, input.deadlineMs);
child.once('error', () => {
clearTimeout(timer);
reject(new PublishRuntimeError('LOCAL_BUILD_RUNTIME_UNAVAILABLE'));
});
child.once('close', (code) => {
clearTimeout(timer);
if (timedOut) reject(new PublishRuntimeError('LOCAL_BUILD_TIMEOUT'));
else if (code !== 0) reject(new PublishRuntimeError('LOCAL_BUILD_FAILED'));
else resolve({ stdout, stderr });
});
});
}
export async function resolvePublishRuntime(): Promise<PublishRuntime> {
try {
const packagedNpmPackage = join(process.resourcesPath, 'publish-runtime', 'package.json');
const npmPackage = app.isPackaged && existsSync(packagedNpmPackage)
? packagedNpmPackage
: require.resolve('npm/package.json');
const npmCli = join(dirname(npmPackage), 'bin', 'npm-cli.js');
const npmResult = await runElectronNode({ args: [npmCli, '--version'], deadlineMs: 10_000 });
const nodeResult = await runElectronNode({ args: ['--version'], deadlineMs: 10_000 });
return {
npmCli,
nodeVersion: nodeResult.stdout.trim().replace(/^v/, ''),
npmVersion: npmResult.stdout.trim(),
};
} catch (error) {
if (error instanceof PublishRuntimeError) throw error;
throw new PublishRuntimeError('LOCAL_BUILD_RUNTIME_UNAVAILABLE');
}
}