在 Makelore 构建并预检静态发布产物
This commit is contained in:
@@ -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
9
scripts/before-pack.cjs
Normal 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',
|
||||
});
|
||||
};
|
||||
70
scripts/prepare-publish-runtime.mjs
Normal file
70
scripts/prepare-publish-runtime.mjs
Normal 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}`);
|
||||
@@ -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,
|
||||
|
||||
29
scripts/verify-publish-runtime.mjs
Normal file
29
scripts/verify-publish-runtime.mjs
Normal 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()}`);
|
||||
Reference in New Issue
Block a user