在 Makelore 构建并预检静态发布产物
This commit is contained in:
108
electron/services/publish-runtime.ts
Normal file
108
electron/services/publish-runtime.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user