在 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

@@ -1,5 +1,5 @@
const { execFileSync } = require('child_process');
const { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } = require('fs');
const { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } = require('fs');
const { join } = require('path');
const MEOWA_RELEASE_CREDENTIAL_FILE_NAME = 'meowa-game-assets-credential.json';
@@ -9,6 +9,7 @@ exports.default = async function afterPack(context) {
console.log(`[after-pack] Target: ${platform}/${context.arch}`);
writeMeowaReleaseCredential(context);
copyPublishRuntime(context);
assertNoPersistedUserData(context.appOutDir);
if (platform !== 'win32') {
@@ -19,6 +20,18 @@ exports.default = async function afterPack(context) {
patchNsisExtractionMacro();
};
function copyPublishRuntime(context) {
const source = join(__dirname, '..', 'build', 'publish-runtime');
const resourcesDir = context.packager.getResourcesDir(context.appOutDir);
const destination = join(resourcesDir, 'publish-runtime');
if (!existsSync(join(source, 'node_modules', 'proc-log', 'package.json'))) {
throw new Error(`Prepared publish runtime is incomplete: ${source}`);
}
rmSync(destination, { recursive: true, force: true });
cpSync(source, destination, { recursive: true });
console.log('[after-pack] Copied self-contained npm publish runtime.');
}
function writeMeowaReleaseCredential(context) {
const apiKey = process.env.MEOWART_API_KEY?.trim();
if (!apiKey) {

9
scripts/before-pack.cjs Normal file
View File

@@ -0,0 +1,9 @@
const { execFileSync } = require('node:child_process');
const { join } = require('node:path');
exports.default = async function beforePack() {
execFileSync(process.execPath, [join(__dirname, 'prepare-publish-runtime.mjs')], {
cwd: join(__dirname, '..'),
stdio: 'inherit',
});
};

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env node
import { cpSync, existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
import { createRequire } from 'node:module';
import { tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
const require = createRequire(import.meta.url);
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const expectedVersion = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).dependencies?.npm;
const npmPackagePath = require.resolve('npm/package.json');
const npmPackage = JSON.parse(readFileSync(npmPackagePath, 'utf8'));
const npmCli = join(dirname(npmPackagePath), 'bin', 'npm-cli.js');
const destination = join(root, 'build', 'publish-runtime');
if (npmPackage.version !== expectedVersion) {
throw new Error(`Publish npm version mismatch: expected ${expectedVersion}, got ${npmPackage.version}`);
}
if (!npmPackage.bundleDependencies?.includes('proc-log')) {
throw new Error('Publish npm package does not declare its runtime dependencies as bundled dependencies');
}
const temporaryRoot = mkdtempSync(join(tmpdir(), 'makelore-publish-runtime-'));
try {
const packResult = spawnSync(process.execPath, [
npmCli,
'pack',
dirname(npmPackagePath),
'--ignore-scripts',
'--json',
'--pack-destination',
temporaryRoot,
], { cwd: root, encoding: 'utf8', shell: false });
if (packResult.status !== 0) {
throw new Error(`Unable to pack publish npm runtime: ${packResult.stderr || packResult.stdout}`);
}
const packMetadata = JSON.parse(packResult.stdout);
if (!Array.isArray(packMetadata) || packMetadata.length !== 1) {
throw new Error('npm pack returned unexpected metadata');
}
const filename = packMetadata[0]?.filename;
if (typeof filename !== 'string'
|| !/^[^/\\]+\.tgz$/.test(filename)
|| resolve(temporaryRoot, filename) !== join(temporaryRoot, filename)) {
throw new Error(`npm pack returned an unsafe filename: ${JSON.stringify(filename)}`);
}
mkdirSync(join(temporaryRoot, 'runtime'), { recursive: true });
await import('tar').then(({ x }) => x({
file: join(temporaryRoot, filename),
cwd: join(temporaryRoot, 'runtime'),
strip: 1,
}));
const stagedRuntime = join(temporaryRoot, 'runtime');
for (const dependency of npmPackage.bundleDependencies) {
if (!existsSync(join(stagedRuntime, 'node_modules', ...dependency.split('/'), 'package.json'))) {
throw new Error(`Packed npm runtime is missing bundled dependency ${dependency}`);
}
}
rmSync(destination, { recursive: true, force: true });
mkdirSync(dirname(destination), { recursive: true });
cpSync(stagedRuntime, destination, { recursive: true });
} finally {
rmSync(temporaryRoot, { recursive: true, force: true });
}
console.log(`Prepared self-contained npm ${npmPackage.version} runtime at ${destination}`);

View File

@@ -141,6 +141,9 @@ const expectedCommit = option('--expected-commit', null);
const expectedBuildId = option('--expected-build-id', null);
const manifestPath = option('--manifest', null);
const resourcesDir = join(dirname(appExecutable), 'resources');
const npmRuntimeDir = join(resourcesDir, 'publish-runtime');
const npmPackagePath = join(npmRuntimeDir, 'package.json');
const npmCliPath = join(npmRuntimeDir, 'bin', 'npm-cli.js');
const appAsarPath = join(resourcesDir, 'app.asar');
const appAsarUnpackedPath = join(resourcesDir, 'app.asar.unpacked');
const opencodeRuntimeDir = join(resourcesDir, 'opencode-ai');
@@ -154,6 +157,8 @@ for (const filePath of [
appExecutable,
installerPath,
appAsarPath,
npmPackagePath,
npmCliPath,
opencodePackagePath,
opencodeExecutable,
pythonExecutable,
@@ -317,6 +322,13 @@ if (!isPathInside(appAsarPath, probe.playwrightPackageResolved)) {
if (!isPathInside(appAsarPath, probe.playwrightCliResolved)) {
throw new Error(`@playwright/mcp CLI was not resolved from app.asar: ${probe.playwrightCliResolved}`);
}
const packagedNpm = JSON.parse(readFileSync(npmPackagePath, 'utf8'));
assertEqual(packagedNpm.version, packageJson.dependencies.npm, 'packaged npm version');
const npmVersion = run(appExecutable, [npmCliPath, '--version'], {
cwd: dirname(appExecutable),
env: { ...installLocalProbeEnv, ELECTRON_RUN_AS_NODE: '1' },
});
assertEqual(npmVersion, packageJson.dependencies.npm, 'packaged npm CLI version');
run(appExecutable, [probe.playwrightCliResolved, '--help'], {
cwd: dirname(appExecutable),
env: { ...installLocalProbeEnv, ELECTRON_RUN_AS_NODE: '1' },
@@ -371,6 +383,7 @@ const evidence = {
},
python: pythonProbe,
uv: { executable: uvExecutable, version: uvVersion },
npm: { packageJson: npmPackagePath, cli: npmCliPath, version: npmVersion },
},
nativeModules: {
msgpackr: probe.msgpackResolved,

View File

@@ -0,0 +1,29 @@
import { dirname, join, resolve } from 'node:path';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { existsSync } from 'node:fs';
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const prepare = spawnSync(process.execPath, [join(root, 'scripts', 'prepare-publish-runtime.mjs')], {
cwd: root,
encoding: 'utf8',
shell: false,
});
if (prepare.status !== 0) {
throw new Error(`Unable to stage fixed publish runtime: ${prepare.stderr || prepare.stdout}`);
}
const npmCli = join(root, 'build', 'publish-runtime', 'bin', 'npm-cli.js');
const procLogPackage = join(root, 'build', 'publish-runtime', 'node_modules', 'proc-log', 'package.json');
if (!existsSync(procLogPackage)) {
throw new Error(`Staged publish runtime is missing proc-log: ${procLogPackage}`);
}
const result = spawnSync(process.execPath, [npmCli, '--version'], {
cwd: root,
env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' },
shell: false,
encoding: 'utf8',
});
if (result.status !== 0 || !/^11\.6\.2\s*$/.test(result.stdout)) {
throw new Error(`Fixed publish npm runtime unavailable: ${result.stderr || result.stdout}`);
}
console.log(`publish npm runtime OK: ${result.stdout.trim()}`);