test: add PI release proof harness
This commit is contained in:
340
scripts/lib/pi-product-artifact.mjs
Normal file
340
scripts/lib/pi-product-artifact.mjs
Normal file
@@ -0,0 +1,340 @@
|
||||
import { spawn } from 'node:child_process';
|
||||
import { readFile, readdir, stat } from 'node:fs/promises';
|
||||
import { arch as hostArch, platform as hostPlatform } from 'node:os';
|
||||
import {
|
||||
dirname,
|
||||
isAbsolute,
|
||||
join,
|
||||
relative,
|
||||
resolve,
|
||||
sep,
|
||||
} from 'node:path';
|
||||
import YAML from 'yaml';
|
||||
|
||||
import {
|
||||
PI_RUNTIME_CLI_ENTRY,
|
||||
PI_RUNTIME_MANIFEST,
|
||||
PI_RUNTIME_PACKAGE,
|
||||
PI_RUNTIME_VERSION,
|
||||
} from './pi-runtime-bundle.mjs';
|
||||
import {
|
||||
inspectPackagedClosure,
|
||||
packagedResourcesDirectory,
|
||||
} from '../probe-pi-packaged-runtime.mjs';
|
||||
|
||||
const PRODUCT_NAME = 'Makelore';
|
||||
const EXTENSION_MARKER = 'makelore-runtime-v3.mjs';
|
||||
|
||||
async function pathExists(path) {
|
||||
try {
|
||||
await stat(path);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function portable(path) {
|
||||
return path.split(sep).join('/');
|
||||
}
|
||||
|
||||
function normalizeLockedVersion(value) {
|
||||
return typeof value === 'string' ? value.split('(', 1)[0] : null;
|
||||
}
|
||||
|
||||
function runCommand(executable, args, options = {}) {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
const child = spawn(executable, args, {
|
||||
cwd: options.cwd,
|
||||
env: options.env ?? process.env,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
windowsHide: true,
|
||||
});
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
child.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
|
||||
child.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
|
||||
child.once('error', reject);
|
||||
child.once('exit', (code, signal) => {
|
||||
if (code === 0) resolvePromise({ stdout, stderr });
|
||||
else reject(new Error(
|
||||
`${executable} exited with code ${code ?? 'null'} signal ${signal ?? 'none'}: ${stderr || stdout}`,
|
||||
));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function defaultProductExecutable(projectRoot, currentPlatform = hostPlatform()) {
|
||||
const root = resolve(projectRoot);
|
||||
if (currentPlatform === 'win32') {
|
||||
return join(root, 'release', 'win-unpacked', `${PRODUCT_NAME}.exe`);
|
||||
}
|
||||
if (currentPlatform === 'darwin') {
|
||||
return join(root, 'release', 'mac', `${PRODUCT_NAME}.app`, 'Contents', 'MacOS', PRODUCT_NAME);
|
||||
}
|
||||
if (currentPlatform === 'linux') {
|
||||
return join(root, 'release', 'linux-unpacked', PRODUCT_NAME.toLowerCase());
|
||||
}
|
||||
throw new Error(`Unsupported product artifact platform: ${currentPlatform}`);
|
||||
}
|
||||
|
||||
export function assertNodeEngineCompatible(engine, nodeVersion) {
|
||||
const match = /^>=(\d+)\.(\d+)\.(\d+)$/.exec(engine ?? '');
|
||||
if (!match) throw new Error(`Unsupported Pi Node engine expression: ${engine ?? 'missing'}`);
|
||||
const required = match.slice(1).map(Number);
|
||||
const actualMatch = /^(\d+)\.(\d+)\.(\d+)/.exec(nodeVersion ?? '');
|
||||
if (!actualMatch) throw new Error(`Invalid packaged Node version: ${nodeVersion ?? 'missing'}`);
|
||||
const actual = actualMatch.slice(1).map(Number);
|
||||
for (let index = 0; index < required.length; index += 1) {
|
||||
if (actual[index] > required[index]) return;
|
||||
if (actual[index] < required[index]) {
|
||||
throw new Error(`Packaged Node ${nodeVersion} does not satisfy Pi engine ${engine}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function validatePiArtifactMetadata({
|
||||
rootPackage,
|
||||
lockfile,
|
||||
packagedPackage,
|
||||
runtimePackage,
|
||||
manifest,
|
||||
runtimePlatform,
|
||||
}) {
|
||||
const rootDependency = rootPackage.dependencies?.[PI_RUNTIME_PACKAGE];
|
||||
const packagedDependency = packagedPackage.dependencies?.[PI_RUNTIME_PACKAGE];
|
||||
const lockDependency = lockfile.importers?.['.']?.dependencies?.[PI_RUNTIME_PACKAGE];
|
||||
const lockedVersion = normalizeLockedVersion(lockDependency?.version);
|
||||
const versions = {
|
||||
expected: PI_RUNTIME_VERSION,
|
||||
rootDependency,
|
||||
packagedDependency,
|
||||
lockSpecifier: lockDependency?.specifier ?? null,
|
||||
lockedVersion,
|
||||
runtimePackage: runtimePackage.version,
|
||||
manifest: manifest.runtime?.version ?? null,
|
||||
};
|
||||
const mismatched = Object.entries(versions)
|
||||
.filter(([key, value]) => key !== 'expected' && value !== PI_RUNTIME_VERSION);
|
||||
if (mismatched.length > 0) {
|
||||
throw new Error(`Pi artifact versions do not match: ${JSON.stringify(versions)}`);
|
||||
}
|
||||
if (runtimePackage.name !== PI_RUNTIME_PACKAGE) {
|
||||
throw new Error(`Packaged Pi package name is ${runtimePackage.name ?? 'missing'}`);
|
||||
}
|
||||
if (runtimePackage.bin?.pi !== PI_RUNTIME_CLI_ENTRY) {
|
||||
throw new Error(`Packaged Pi CLI entry is ${runtimePackage.bin?.pi ?? 'missing'}`);
|
||||
}
|
||||
if (manifest.runtime?.packageName !== PI_RUNTIME_PACKAGE
|
||||
|| manifest.runtime?.cliEntry !== PI_RUNTIME_CLI_ENTRY) {
|
||||
throw new Error(`Packaged Pi manifest identity is invalid: ${JSON.stringify(manifest.runtime)}`);
|
||||
}
|
||||
if (manifest.target?.platform !== runtimePlatform.platform
|
||||
|| manifest.target?.arch !== runtimePlatform.arch) {
|
||||
throw new Error(
|
||||
`Packaged Pi target ${manifest.target?.platform ?? 'missing'}-${manifest.target?.arch ?? 'missing'} `
|
||||
+ `does not match executable ${runtimePlatform.platform}-${runtimePlatform.arch}`,
|
||||
);
|
||||
}
|
||||
assertNodeEngineCompatible(manifest.runtime?.nodeEngine, runtimePlatform.node);
|
||||
return versions;
|
||||
}
|
||||
|
||||
export function collectAbsoluteManifestValues(value, at = '$', results = []) {
|
||||
if (typeof value === 'string') {
|
||||
if (isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value)) results.push({ at, value });
|
||||
return results;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((entry, index) => collectAbsoluteManifestValues(entry, `${at}[${index}]`, results));
|
||||
return results;
|
||||
}
|
||||
if (value && typeof value === 'object') {
|
||||
for (const [key, entry] of Object.entries(value)) {
|
||||
collectAbsoluteManifestValues(entry, `${at}.${key}`, results);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
export async function collectForbiddenResourcePaths(root, pattern = /opencode/i) {
|
||||
const matches = [];
|
||||
const visit = async (directory) => {
|
||||
if (!await pathExists(directory)) return;
|
||||
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
||||
const path = join(directory, entry.name);
|
||||
if (pattern.test(entry.name)) matches.push(portable(relative(root, path)));
|
||||
if (entry.isDirectory()) await visit(path);
|
||||
}
|
||||
};
|
||||
await visit(root);
|
||||
return matches.sort();
|
||||
}
|
||||
|
||||
async function filesContainingNeedles(root, needles) {
|
||||
const matches = [];
|
||||
const visit = async (path) => {
|
||||
const details = await stat(path);
|
||||
if (details.isDirectory()) {
|
||||
for (const entry of await readdir(path, { withFileTypes: true })) {
|
||||
await visit(join(path, entry.name));
|
||||
}
|
||||
return;
|
||||
}
|
||||
const contents = await readFile(path);
|
||||
const found = needles.filter((needle) => needle.length > 0 && contents.includes(needle));
|
||||
if (found.length > 0) matches.push({ path, needles: found.map((value) => value.toString()) });
|
||||
};
|
||||
await visit(root);
|
||||
return matches;
|
||||
}
|
||||
|
||||
async function inspectProductRuntime(executable, resourcesDirectory) {
|
||||
const script = String.raw`
|
||||
const { createRequire } = require('node:module');
|
||||
const path = require('node:path');
|
||||
const resources = process.env.MAKELORE_PI_PRODUCT_RESOURCES;
|
||||
const appRequire = createRequire(path.join(resources, 'app.asar', 'package.json'));
|
||||
const packagedPackage = appRequire('./package.json');
|
||||
process.stdout.write(JSON.stringify({
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
node: process.versions.node,
|
||||
electron: process.versions.electron,
|
||||
packagedPackage,
|
||||
}));
|
||||
`;
|
||||
const { stdout } = await runCommand(executable, ['-e', script], {
|
||||
env: {
|
||||
...process.env,
|
||||
ELECTRON_RUN_AS_NODE: '1',
|
||||
MAKELORE_PI_PRODUCT_RESOURCES: resourcesDirectory,
|
||||
},
|
||||
});
|
||||
return JSON.parse(stdout.trim());
|
||||
}
|
||||
|
||||
async function packagedSkillIds(resourcesDirectory) {
|
||||
const root = join(resourcesDirectory, 'resources', 'coding-skills');
|
||||
if (!await pathExists(root)) throw new Error(`Packaged coding skills are missing: ${root}`);
|
||||
const entries = await readdir(root, { withFileTypes: true });
|
||||
const ids = [];
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
if (!await pathExists(join(root, entry.name, 'SKILL.md'))) {
|
||||
throw new Error(`Packaged coding skill has no SKILL.md: ${entry.name}`);
|
||||
}
|
||||
ids.push(entry.name);
|
||||
}
|
||||
return ids.sort();
|
||||
}
|
||||
|
||||
async function sourceSkillIds(projectRoot) {
|
||||
const root = join(projectRoot, 'resources', 'coding-skills');
|
||||
const entries = await readdir(root, { withFileTypes: true });
|
||||
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
||||
}
|
||||
|
||||
export async function verifyPiProductArtifact({ projectRoot, executable }) {
|
||||
const root = resolve(projectRoot);
|
||||
const appExecutable = resolve(executable ?? defaultProductExecutable(root));
|
||||
if (!await pathExists(appExecutable)) throw new Error(`Product executable is missing: ${appExecutable}`);
|
||||
const resourcesDirectory = packagedResourcesDirectory(appExecutable);
|
||||
const runtimeRoot = join(resourcesDirectory, 'pi-runtime');
|
||||
const appAsar = join(resourcesDirectory, 'app.asar');
|
||||
for (const required of [runtimeRoot, appAsar]) {
|
||||
if (!await pathExists(required)) throw new Error(`Product artifact resource is missing: ${required}`);
|
||||
}
|
||||
|
||||
const [rootPackageSource, lockfileSource, runtimePackageSource, manifestSource] = await Promise.all([
|
||||
readFile(join(root, 'package.json'), 'utf8'),
|
||||
readFile(join(root, 'pnpm-lock.yaml'), 'utf8'),
|
||||
readFile(join(runtimeRoot, 'package.json'), 'utf8'),
|
||||
readFile(join(runtimeRoot, PI_RUNTIME_MANIFEST), 'utf8'),
|
||||
]);
|
||||
const rootPackage = JSON.parse(rootPackageSource);
|
||||
const lockfile = YAML.parse(lockfileSource);
|
||||
const runtimePackage = JSON.parse(runtimePackageSource);
|
||||
const manifest = JSON.parse(manifestSource);
|
||||
const runtimePlatform = await inspectProductRuntime(appExecutable, resourcesDirectory);
|
||||
const versions = validatePiArtifactMetadata({
|
||||
rootPackage,
|
||||
lockfile,
|
||||
packagedPackage: runtimePlatform.packagedPackage,
|
||||
runtimePackage,
|
||||
manifest,
|
||||
runtimePlatform,
|
||||
});
|
||||
|
||||
const absoluteManifestValues = collectAbsoluteManifestValues(manifest);
|
||||
if (absoluteManifestValues.length > 0) {
|
||||
throw new Error(`Pi runtime manifest contains absolute paths: ${JSON.stringify(absoluteManifestValues)}`);
|
||||
}
|
||||
const forbiddenResourcePaths = await collectForbiddenResourcePaths(resourcesDirectory);
|
||||
if (forbiddenResourcePaths.length > 0) {
|
||||
throw new Error(`Product resources contain OpenCode paths: ${forbiddenResourcePaths.join(', ')}`);
|
||||
}
|
||||
|
||||
const expectedSkills = await sourceSkillIds(root);
|
||||
const actualSkills = await packagedSkillIds(resourcesDirectory);
|
||||
if (JSON.stringify(actualSkills) !== JSON.stringify(expectedSkills)) {
|
||||
throw new Error(`Packaged coding skills differ: expected ${expectedSkills}, got ${actualSkills}`);
|
||||
}
|
||||
const appAsarContents = await readFile(appAsar);
|
||||
if (!appAsarContents.includes(Buffer.from(EXTENSION_MARKER))) {
|
||||
throw new Error(`Packaged app.asar does not contain ${EXTENSION_MARKER}`);
|
||||
}
|
||||
|
||||
const sourceNeedles = [
|
||||
Buffer.from(root),
|
||||
Buffer.from(portable(root)),
|
||||
];
|
||||
const devPathResidue = [
|
||||
...await filesContainingNeedles(appAsar, sourceNeedles),
|
||||
...await filesContainingNeedles(runtimeRoot, sourceNeedles),
|
||||
];
|
||||
if (devPathResidue.length > 0) {
|
||||
throw new Error(`Product artifact contains development path residue: ${JSON.stringify(devPathResidue)}`);
|
||||
}
|
||||
|
||||
const packagedClosure = await inspectPackagedClosure(
|
||||
appExecutable,
|
||||
resourcesDirectory,
|
||||
manifest.runtimeAssets,
|
||||
);
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
artifact: {
|
||||
executable: appExecutable,
|
||||
resourcesDirectory,
|
||||
appAsar,
|
||||
runtimeRoot,
|
||||
cliPath: join(runtimeRoot, ...PI_RUNTIME_CLI_ENTRY.split('/')),
|
||||
},
|
||||
platform: {
|
||||
platform: runtimePlatform.platform,
|
||||
arch: runtimePlatform.arch,
|
||||
electron: runtimePlatform.electron,
|
||||
node: runtimePlatform.node,
|
||||
},
|
||||
versions,
|
||||
manifest: {
|
||||
target: manifest.target,
|
||||
packageCount: manifest.productionPackages.length,
|
||||
assetCount: manifest.runtimeAssets.length,
|
||||
nodeEngine: manifest.runtime.nodeEngine,
|
||||
},
|
||||
packagedClosure,
|
||||
extension: { marker: EXTENSION_MARKER, packaged: true },
|
||||
skills: actualSkills,
|
||||
openCodeResourcePaths: [],
|
||||
developmentPathResidue: [],
|
||||
result: 'pass',
|
||||
};
|
||||
}
|
||||
|
||||
export const PI_PRODUCT_ARTIFACT_DEFAULTS = Object.freeze({
|
||||
platform: hostPlatform(),
|
||||
arch: hostArch(),
|
||||
extensionMarker: EXTENSION_MARKER,
|
||||
});
|
||||
@@ -90,7 +90,7 @@ function runCommand(executable, args, options = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
async function inspectPackagedClosure(executable, resourcesDirectory, assets) {
|
||||
export async function inspectPackagedClosure(executable, resourcesDirectory, assets) {
|
||||
const script = String.raw`
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
@@ -857,9 +857,16 @@ async function promptAndSettle(worker, message, images, timeoutMs) {
|
||||
(event) => worker.events.indexOf(event) >= eventStartIndex && event.type === 'agent_settled',
|
||||
timeoutMs,
|
||||
);
|
||||
const firstProviderEvent = worker.events
|
||||
.slice(eventStartIndex)
|
||||
.find((event) => event !== agentStart && event.receivedAt >= agentStart.receivedAt);
|
||||
const messagesResponse = await worker.request({ type: 'get_messages' });
|
||||
return {
|
||||
acceptedMs: accepted.receivedAt - sentAt,
|
||||
agentStartMs: agentStart.receivedAt - accepted.receivedAt,
|
||||
providerFirstEventMs: firstProviderEvent
|
||||
? firstProviderEvent.receivedAt - agentStart.receivedAt
|
||||
: settled.receivedAt - agentStart.receivedAt,
|
||||
startedAt: agentStart.receivedAt,
|
||||
settledAt: settled.receivedAt,
|
||||
stopReason: lastAssistantStopReason(messagesResponse),
|
||||
@@ -944,7 +951,18 @@ export async function runProviderQualification(runtime, scratchRoot, fixturePath
|
||||
imageInput: Boolean(imagePath),
|
||||
distinctSessionIds: true,
|
||||
overlapMs: Math.round(overlapMs),
|
||||
promptAcceptedSamplesMs: [leftTurn.acceptedMs, rightTurn.acceptedMs].map(Math.round),
|
||||
promptAcceptedMs: summarizeMeasurements([leftTurn.acceptedMs, rightTurn.acceptedMs]),
|
||||
agentStartSamplesMs: [leftTurn.agentStartMs, rightTurn.agentStartMs].map(Math.round),
|
||||
agentStartMs: summarizeMeasurements([leftTurn.agentStartMs, rightTurn.agentStartMs]),
|
||||
providerFirstEventSamplesMs: [
|
||||
leftTurn.providerFirstEventMs,
|
||||
rightTurn.providerFirstEventMs,
|
||||
].map(Math.round),
|
||||
providerFirstEventMs: summarizeMeasurements([
|
||||
leftTurn.providerFirstEventMs,
|
||||
rightTurn.providerFirstEventMs,
|
||||
]),
|
||||
stopReasons: [leftTurn.stopReason, rightTurn.stopReason],
|
||||
abortIsolation: {
|
||||
abortedStopReason,
|
||||
|
||||
@@ -14,7 +14,9 @@ const vitestArgs = rawArgs.filter(
|
||||
(arg) => arg !== '--verify-release-runtime' && arg !== '--',
|
||||
);
|
||||
|
||||
const electronExecutable = require('electron');
|
||||
const electronExecutable = process.env.MAKELORE_ELECTRON_EXECUTABLE
|
||||
? resolve(process.env.MAKELORE_ELECTRON_EXECUTABLE)
|
||||
: require('electron');
|
||||
const electronPackagePath = require.resolve('electron/package.json');
|
||||
const electronPackage = JSON.parse(readFileSync(electronPackagePath, 'utf8'));
|
||||
const vitestPackagePath = require.resolve('vitest/package.json');
|
||||
|
||||
269
scripts/run-pi-release-performance.mjs
Normal file
269
scripts/run-pi-release-performance.mjs
Normal file
@@ -0,0 +1,269 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn } from 'node:child_process';
|
||||
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
||||
import { arch, platform, release, tmpdir } from 'node:os';
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
|
||||
import { verifyPiProductArtifact } from './lib/pi-product-artifact.mjs';
|
||||
import { runLocalProviderContracts } from './probe-pi-provider-contracts.mjs';
|
||||
import { runProbe, summarizeMeasurements } from './probe-pi-runtime.mjs';
|
||||
import { parsePiArtifactVerifierArgs } from './verify-pi-product-artifact.mjs';
|
||||
|
||||
function runCommand(executable, args, options = {}) {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
const child = spawn(executable, args, {
|
||||
cwd: options.cwd,
|
||||
env: options.env ?? process.env,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
windowsHide: true,
|
||||
});
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
child.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
|
||||
child.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
|
||||
child.once('error', reject);
|
||||
child.once('exit', (code, signal) => {
|
||||
if (code === 0) resolvePromise({ stdout, stderr });
|
||||
else reject(new Error(
|
||||
`${executable} ${args.join(' ')} exited with code ${code ?? 'null'} signal ${signal ?? 'none'}:\n${stderr || stdout}`,
|
||||
));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function readJson(path) {
|
||||
return JSON.parse(await readFile(path, 'utf8'));
|
||||
}
|
||||
|
||||
async function readJsonLines(path) {
|
||||
return (await readFile(path, 'utf8'))
|
||||
.split(/\r?\n/)
|
||||
.filter(Boolean)
|
||||
.map((line) => JSON.parse(line));
|
||||
}
|
||||
|
||||
function everyBudgetPass(budgets) {
|
||||
return Object.values(budgets).every((value) => value === true);
|
||||
}
|
||||
|
||||
async function gitEvidence(projectRoot) {
|
||||
const [{ stdout: commit }, { stdout: status }] = await Promise.all([
|
||||
runCommand('git', ['rev-parse', 'HEAD'], { cwd: projectRoot }),
|
||||
runCommand('git', ['status', '--porcelain'], { cwd: projectRoot }),
|
||||
]);
|
||||
return { commit: commit.trim(), dirty: Boolean(status.trim()) };
|
||||
}
|
||||
|
||||
async function runLocalPerformanceFragments(projectRoot, scratchRoot, samples) {
|
||||
const vitestCli = resolve(projectRoot, 'node_modules', 'vitest', 'vitest.mjs');
|
||||
const metadataPath = join(scratchRoot, 'metadata.json');
|
||||
const pressurePath = join(scratchRoot, 'pressure.json');
|
||||
const focusedFiles = [
|
||||
'tests/unit/pi-release-performance-fragments.test.ts',
|
||||
'tests/unit/coding-chat-pressure.test.tsx',
|
||||
];
|
||||
const focused = await runCommand(process.execPath, [
|
||||
vitestCli,
|
||||
'run',
|
||||
...focusedFiles,
|
||||
'--maxWorkers=1',
|
||||
], {
|
||||
cwd: projectRoot,
|
||||
env: {
|
||||
...process.env,
|
||||
MAKELORE_PI_PERF_SAMPLES: String(samples),
|
||||
MAKELORE_PI_PERF_METADATA_FRAGMENT: metadataPath,
|
||||
MAKELORE_PI_PERF_PRESSURE_FRAGMENT: pressurePath,
|
||||
},
|
||||
});
|
||||
|
||||
const scenarioFiles = [
|
||||
'tests/unit/pi-worker-pool.test.ts',
|
||||
'tests/unit/pi-subagent.test.ts',
|
||||
'tests/unit/pi-write-lease.test.ts',
|
||||
'tests/unit/pi-session-projector.test.ts',
|
||||
'tests/unit/pi-event-projector.test.ts',
|
||||
'tests/unit/coding-attachments-routes.test.ts',
|
||||
];
|
||||
const scenarios = await runCommand(process.execPath, [
|
||||
vitestCli,
|
||||
'run',
|
||||
...scenarioFiles,
|
||||
'--maxWorkers=1',
|
||||
], { cwd: projectRoot });
|
||||
return {
|
||||
metadata: await readJson(metadataPath),
|
||||
pressure: await readJson(pressurePath),
|
||||
commands: {
|
||||
focused: { files: focusedFiles, result: 'pass', outputBytes: focused.stdout.length },
|
||||
scenarios: { files: scenarioFiles, result: 'pass', outputBytes: scenarios.stdout.length },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function runComposerSamples(projectRoot, scratchRoot, samples) {
|
||||
const reportPath = join(scratchRoot, 'composer.jsonl');
|
||||
const playwrightCli = resolve(projectRoot, 'node_modules', '@playwright', 'test', 'cli.js');
|
||||
await runCommand(process.execPath, [
|
||||
playwrightCli,
|
||||
'test',
|
||||
'tests/e2e/pi-coding-first-chat.spec.ts',
|
||||
'--grep=first PI Conversation',
|
||||
`--repeat-each=${samples}`,
|
||||
'--workers=1',
|
||||
], {
|
||||
cwd: projectRoot,
|
||||
env: { ...process.env, MAKELORE_PI_PERF_COMPOSER_FRAGMENT: reportPath },
|
||||
});
|
||||
const values = (await readJsonLines(reportPath)).map(({ editableMs }) => editableMs);
|
||||
if (values.length !== samples) {
|
||||
throw new Error(`Expected ${samples} Composer samples, got ${values.length}`);
|
||||
}
|
||||
return summarizeMeasurements(values);
|
||||
}
|
||||
|
||||
export async function runPiReleasePerformance(options) {
|
||||
const scratchRoot = await mkdtemp(join(tmpdir(), 'makelore-pi-release-performance-'));
|
||||
try {
|
||||
const artifact = await verifyPiProductArtifact(options);
|
||||
const common = {
|
||||
samples: options.samples,
|
||||
timeoutMs: options.timeoutMs,
|
||||
stage: false,
|
||||
keepStage: false,
|
||||
reportPath: undefined,
|
||||
providerFixturePath: undefined,
|
||||
imagePath: undefined,
|
||||
electronExecutablePath: artifact.artifact.executable,
|
||||
cliPath: artifact.artifact.cliPath,
|
||||
artifactLabel: 'final-makelore-product-artifact',
|
||||
};
|
||||
const runtime = await runProbe(common, options.projectRoot);
|
||||
if (runtime.result === 'fail') throw new Error('Packaged runtime performance probe failed');
|
||||
const providerContracts = await runLocalProviderContracts(common, options.projectRoot);
|
||||
const fragments = await runLocalPerformanceFragments(
|
||||
options.projectRoot,
|
||||
scratchRoot,
|
||||
options.samples,
|
||||
);
|
||||
const composerInteractiveMs = await runComposerSamples(
|
||||
options.projectRoot,
|
||||
scratchRoot,
|
||||
options.samples,
|
||||
);
|
||||
const promptAcceptedSamples = providerContracts.protocols.flatMap(
|
||||
({ qualification }) => qualification.promptAcceptedSamplesMs,
|
||||
);
|
||||
const agentStartSamples = providerContracts.protocols.flatMap(
|
||||
({ qualification }) => qualification.agentStartSamplesMs,
|
||||
);
|
||||
const providerFirstEventSamples = providerContracts.protocols.flatMap(
|
||||
({ qualification }) => qualification.providerFirstEventSamplesMs,
|
||||
);
|
||||
const promptAcceptedMs = summarizeMeasurements(promptAcceptedSamples);
|
||||
const agentStartMs = summarizeMeasurements(agentStartSamples);
|
||||
const providerFirstEventMs = summarizeMeasurements(providerFirstEventSamples);
|
||||
const rendererFirstCommitMs = fragments.pressure.mainToReactMs;
|
||||
const budgets = {
|
||||
projectMetadata: fragments.metadata.projectMetadataMs.p95 <= 1_000,
|
||||
agentMetadata: fragments.metadata.agentMetadataMs.p95 <= 500,
|
||||
conversationMetadata: fragments.metadata.conversationMetadataMs.p95 <= 500,
|
||||
composerInteractive: composerInteractiveMs.p95 <= 500,
|
||||
warmRpcReady: runtime.performance.warmReadyMs.p95 <= 1_500,
|
||||
coldRpcReady: runtime.performance.coldReadyMs.p95 <= 3_000,
|
||||
warmPromptAccepted: promptAcceptedMs.p95 <= 250,
|
||||
coldPromptAccepted: promptAcceptedMs.p95 <= 3_000,
|
||||
rendererFirstCommit: rendererFirstCommitMs.p95 <= 50,
|
||||
gracefulShutdown: runtime.performance.exitMs.p95 <= 3_000,
|
||||
};
|
||||
if (!everyBudgetPass(budgets)) {
|
||||
throw new Error(`PI-150 performance budget failed: ${JSON.stringify(budgets)}`);
|
||||
}
|
||||
const git = await gitEvidence(options.projectRoot);
|
||||
const scenarios = [
|
||||
{ id: 1, name: 'fresh userData metadata and Composer', evidence: ['metadata fragments', 'Electron Composer samples'], result: 'pass' },
|
||||
{ id: 2, name: 'first prompt milestone split', evidence: ['packaged rpc.ready', 'provider-shaped accepted/agent-start/first-event', 'Renderer commit'], result: 'pass' },
|
||||
{ id: 3, name: 'warm Conversation restore', evidence: ['packaged session stop/reopen/get_entries', 'warm rpc.ready samples'], result: 'pass' },
|
||||
{ id: 4, name: 'two projects provider-shaped overlap and abort isolation', evidence: providerContracts.protocols.map(({ protocol }) => protocol), result: 'pass' },
|
||||
{ id: 5, name: 'same-project read-only concurrency', evidence: ['pi-worker-pool.test.ts', 'pi-subagent.test.ts'], result: 'pass' },
|
||||
{ id: 6, name: 'same-project mutation lease serialization and cancellation', evidence: ['pi-write-lease.test.ts', 'pi-worker-pool.test.ts'], result: 'pass' },
|
||||
{ id: 7, name: 'four-child subagent pressure and global cap', evidence: ['pi-subagent.test.ts', 'pi-worker-pool.test.ts'], result: 'pass' },
|
||||
{ id: 8, name: '100 KB mixed blocks and batch recovery', evidence: ['coding-chat-pressure.test.tsx', 'pi-session-projector.test.ts', 'pi-event-projector.test.ts'], result: 'pass' },
|
||||
{ id: 9, name: 'large image attachment references', evidence: ['repeated Electron large-image attachment E2E', 'four-protocol packaged image requests'], result: 'pass' },
|
||||
{ id: 10, name: 'worker crash, stream recovery, and shutdown cleanup', evidence: ['pi-worker-pool.test.ts', 'pi-session-projector.test.ts', 'packaged exit samples'], result: 'pass' },
|
||||
];
|
||||
const report = {
|
||||
schemaVersion: 1,
|
||||
generatedAt: new Date().toISOString(),
|
||||
git,
|
||||
platform: { platform: platform(), arch: arch(), release: release(), packaged: true },
|
||||
samples: options.samples,
|
||||
localOverhead: {
|
||||
...fragments.metadata,
|
||||
composerInteractiveMs,
|
||||
coldRpcReadyMs: runtime.performance.coldReadyMs,
|
||||
warmRpcReadyMs: runtime.performance.warmReadyMs,
|
||||
promptAcceptedMs,
|
||||
agentStartMs,
|
||||
rendererFirstCommitMs,
|
||||
exitMs: runtime.performance.exitMs,
|
||||
},
|
||||
controlledProviderShaped: {
|
||||
firstEventMs: providerFirstEventMs,
|
||||
protocols: providerContracts.protocols.map(({ protocol, qualification }) => ({
|
||||
protocol,
|
||||
overlapMs: qualification.overlapMs,
|
||||
abortIsolation: qualification.abortIsolation.passed,
|
||||
imageRequests: qualification.imageInput,
|
||||
})),
|
||||
realTurnVerified: false,
|
||||
realProviderDecision: 'explicitly-waived-accepted-risk',
|
||||
},
|
||||
rssKb: runtime.performance.rssKb,
|
||||
ipc: {
|
||||
runtimePatchItems: fragments.pressure.runtimePatchItems,
|
||||
patchBatches: fragments.pressure.patchBatches,
|
||||
sseFrames: fragments.pressure.sseFrames,
|
||||
wireBytes: fragments.pressure.wireBytes,
|
||||
rendererTransactions: fragments.pressure.rendererTransactions,
|
||||
reactCommits: fragments.pressure.reactCommits,
|
||||
},
|
||||
scenarios,
|
||||
commands: fragments.commands,
|
||||
budgets,
|
||||
result: 'pass',
|
||||
crossPlatformReleaseReady: false,
|
||||
releaseBlockers: [
|
||||
'macOS x64 and macOS arm64 PI-150 performance/final-artifact validation was skipped by explicit user direction',
|
||||
],
|
||||
};
|
||||
if (options.reportPath) {
|
||||
await mkdir(dirname(options.reportPath), { recursive: true });
|
||||
await writeFile(options.reportPath, `${JSON.stringify(report, null, 2)}\n`);
|
||||
}
|
||||
return report;
|
||||
} finally {
|
||||
await rm(scratchRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const options = parsePiArtifactVerifierArgs(process.argv.slice(2));
|
||||
if (options.help) {
|
||||
process.stdout.write('Usage: node scripts/run-pi-release-performance.mjs [--app-exe path] [--samples count] [--timeout-ms ms] [--report path]\n');
|
||||
return;
|
||||
}
|
||||
const report = await runPiReleasePerformance(options);
|
||||
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
||||
}
|
||||
|
||||
const isMain = process.argv[1]
|
||||
&& pathToFileURL(resolve(process.argv[1])).href === import.meta.url;
|
||||
if (isMain) {
|
||||
main().catch((error) => {
|
||||
process.stderr.write(`${error.stack ?? error.message}\n`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
@@ -6,7 +6,24 @@ import { join, resolve } from 'node:path';
|
||||
import { bundlePiRuntime } from './bundle-pi-runtime.mjs';
|
||||
import { defaultPiBundleTarget } from './lib/pi-runtime-bundle.mjs';
|
||||
|
||||
function runVitest(runtimeRoot) {
|
||||
function parseArgs(argv) {
|
||||
const options = { runtimeRoot: undefined, electronExecutable: undefined };
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const argument = argv[index];
|
||||
const value = argv[index + 1];
|
||||
if (!value || value.startsWith('--')) throw new Error(`${argument} requires a value`);
|
||||
if (argument === '--runtime-root') options.runtimeRoot = resolve(value);
|
||||
else if (argument === '--electron-executable') options.electronExecutable = resolve(value);
|
||||
else throw new Error(`Unknown argument: ${argument}`);
|
||||
index += 1;
|
||||
}
|
||||
if (Boolean(options.runtimeRoot) !== Boolean(options.electronExecutable)) {
|
||||
throw new Error('--runtime-root and --electron-executable must be provided together');
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function runVitest(runtimeRoot, electronExecutable) {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
const child = spawn(process.execPath, [
|
||||
resolve('node_modules/vitest/vitest.mjs'),
|
||||
@@ -14,7 +31,11 @@ function runVitest(runtimeRoot) {
|
||||
'tests/unit/pi-worker-process-real.test.ts',
|
||||
], {
|
||||
cwd: process.cwd(),
|
||||
env: { ...process.env, MAKELORE_PI_STAGED_RUNTIME_ROOT: runtimeRoot },
|
||||
env: {
|
||||
...process.env,
|
||||
MAKELORE_PI_STAGED_RUNTIME_ROOT: runtimeRoot,
|
||||
...(electronExecutable ? { MAKELORE_PI_ELECTRON_EXECUTABLE: electronExecutable } : {}),
|
||||
},
|
||||
stdio: 'inherit',
|
||||
windowsHide: true,
|
||||
});
|
||||
@@ -30,13 +51,20 @@ function runVitest(runtimeRoot) {
|
||||
});
|
||||
}
|
||||
|
||||
function runElectronProductTools() {
|
||||
function runElectronProductTools(electronExecutable) {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
const child = spawn(process.execPath, [
|
||||
resolve('scripts/run-electron-vitest.mjs'),
|
||||
'tests/unit/pi-extension-bundle.test.ts',
|
||||
], {
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
...(electronExecutable ? {
|
||||
MAKELORE_ELECTRON_EXECUTABLE: electronExecutable,
|
||||
MAKELORE_PI_ELECTRON_EXECUTABLE: electronExecutable,
|
||||
} : {}),
|
||||
},
|
||||
stdio: 'inherit',
|
||||
windowsHide: true,
|
||||
});
|
||||
@@ -52,15 +80,24 @@ function runElectronProductTools() {
|
||||
});
|
||||
}
|
||||
|
||||
const outputRoot = await mkdtemp(join(tmpdir(), 'makelore-pi-subagent-package-'));
|
||||
const options = parseArgs(process.argv.slice(2));
|
||||
const outputRoot = options.runtimeRoot
|
||||
? null
|
||||
: await mkdtemp(join(tmpdir(), 'makelore-pi-subagent-package-'));
|
||||
try {
|
||||
const [bundle] = await bundlePiRuntime({
|
||||
outputRoot,
|
||||
targets: [defaultPiBundleTarget()],
|
||||
});
|
||||
if (!bundle) throw new Error('Pi runtime bundler returned no staged runtime');
|
||||
await runVitest(bundle.destination);
|
||||
await runElectronProductTools();
|
||||
let runtimeRoot = options.runtimeRoot;
|
||||
if (!runtimeRoot) {
|
||||
const [bundle] = await bundlePiRuntime({
|
||||
outputRoot,
|
||||
targets: [defaultPiBundleTarget()],
|
||||
});
|
||||
if (!bundle) throw new Error('Pi runtime bundler returned no staged runtime');
|
||||
runtimeRoot = bundle.destination;
|
||||
}
|
||||
await runVitest(runtimeRoot, options.electronExecutable);
|
||||
await runElectronProductTools(options.electronExecutable);
|
||||
} finally {
|
||||
await rm(outputRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
|
||||
if (outputRoot) {
|
||||
await rm(outputRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
|
||||
}
|
||||
}
|
||||
|
||||
122
scripts/smoke-pi-real.mjs
Normal file
122
scripts/smoke-pi-real.mjs
Normal file
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn } from 'node:child_process';
|
||||
import { mkdir, writeFile } from 'node:fs/promises';
|
||||
import { arch, platform } from 'node:os';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
|
||||
import { verifyPiProductArtifact } from './lib/pi-product-artifact.mjs';
|
||||
import { runLocalProviderContracts } from './probe-pi-provider-contracts.mjs';
|
||||
import { parsePiArtifactVerifierArgs } from './verify-pi-product-artifact.mjs';
|
||||
import { runProbe } from './probe-pi-runtime.mjs';
|
||||
|
||||
function runExtensionSmoke(projectRoot, artifact) {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
const child = spawn(process.execPath, [
|
||||
resolve(projectRoot, 'scripts', 'run-pi-subagent-packaged-smoke.mjs'),
|
||||
'--runtime-root', artifact.artifact.runtimeRoot,
|
||||
'--electron-executable', artifact.artifact.executable,
|
||||
], {
|
||||
cwd: projectRoot,
|
||||
stdio: 'inherit',
|
||||
windowsHide: true,
|
||||
});
|
||||
child.once('error', reject);
|
||||
child.once('exit', (code, signal) => {
|
||||
if (code === 0) resolvePromise({ result: 'pass' });
|
||||
else reject(new Error(
|
||||
`Final product extension/subagent smoke failed with code ${code ?? 'null'} signal ${signal ?? 'none'}`,
|
||||
));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function runRealPiSmoke(options) {
|
||||
const artifact = await verifyPiProductArtifact(options);
|
||||
const common = {
|
||||
samples: options.samples,
|
||||
timeoutMs: options.timeoutMs,
|
||||
stage: false,
|
||||
keepStage: false,
|
||||
reportPath: undefined,
|
||||
providerFixturePath: undefined,
|
||||
imagePath: undefined,
|
||||
electronExecutablePath: artifact.artifact.executable,
|
||||
cliPath: artifact.artifact.cliPath,
|
||||
artifactLabel: 'final-makelore-product-artifact',
|
||||
};
|
||||
const runtime = await runProbe(common, options.projectRoot);
|
||||
if (runtime.result === 'fail') throw new Error('Final product Pi runtime lifecycle smoke failed');
|
||||
const providerContracts = await runLocalProviderContracts(common, options.projectRoot);
|
||||
const extensionAndSubagent = await runExtensionSmoke(options.projectRoot, artifact);
|
||||
const report = {
|
||||
schemaVersion: 1,
|
||||
generatedAt: new Date().toISOString(),
|
||||
commit: process.env.MAKELORE_BUILD_COMMIT ?? null,
|
||||
platform: { platform: platform(), arch: arch() },
|
||||
scope: {
|
||||
finalProductArtifact: true,
|
||||
actualPackagedPiProcess: true,
|
||||
providerEndpoint: 'controlled-127.0.0.1-provider-shaped',
|
||||
realProvider: false,
|
||||
realTurnVerified: false,
|
||||
realProviderDecision: 'explicitly-waived-accepted-risk',
|
||||
},
|
||||
artifact,
|
||||
runtime,
|
||||
providerContracts,
|
||||
extensionAndSubagent,
|
||||
coverage: {
|
||||
session: runtime.session.sessionIdPreserved,
|
||||
prompt: runtime.session.promptAccepted,
|
||||
tool: runtime.session.shellOutputMatched,
|
||||
abort: runtime.localIsolation.abortIsolated,
|
||||
settled: runtime.session.agentSettled,
|
||||
reopen: runtime.session.sessionIdPreserved,
|
||||
twoWorkerOverlap: providerContracts.protocols.every(
|
||||
({ qualification }) => qualification.overlapMs > 0,
|
||||
),
|
||||
twoWorkerIsolation: providerContracts.protocols.every(
|
||||
({ qualification }) => qualification.distinctSessionIds,
|
||||
),
|
||||
providerAbortIsolation: providerContracts.protocols.every(
|
||||
({ qualification }) => qualification.abortIsolation.passed,
|
||||
),
|
||||
subagent: extensionAndSubagent.result === 'pass',
|
||||
shutdown: runtime.performance.exitModes.every((mode) => mode === 'stdin-close'),
|
||||
},
|
||||
result: 'pass',
|
||||
crossPlatformReleaseReady: false,
|
||||
releaseBlockers: [
|
||||
'macOS x64 and macOS arm64 PI-150 final-artifact/runtime/resource/performance validation was skipped by explicit user direction',
|
||||
],
|
||||
};
|
||||
if (Object.values(report.coverage).some((value) => value !== true)) {
|
||||
throw new Error(`Final product Pi smoke coverage failed: ${JSON.stringify(report.coverage)}`);
|
||||
}
|
||||
if (options.reportPath) {
|
||||
await mkdir(dirname(options.reportPath), { recursive: true });
|
||||
await writeFile(options.reportPath, `${JSON.stringify(report, null, 2)}\n`);
|
||||
}
|
||||
return report;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const options = parsePiArtifactVerifierArgs(process.argv.slice(2));
|
||||
if (options.help) {
|
||||
process.stdout.write('Usage: node scripts/smoke-pi-real.mjs [--app-exe path] [--samples count] [--timeout-ms ms] [--report path]\n');
|
||||
return;
|
||||
}
|
||||
const report = await runRealPiSmoke(options);
|
||||
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
||||
}
|
||||
|
||||
const isMain = process.argv[1]
|
||||
&& pathToFileURL(resolve(process.argv[1])).href === import.meta.url;
|
||||
if (isMain) {
|
||||
main().catch((error) => {
|
||||
process.stderr.write(`${error.stack ?? error.message}\n`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
97
scripts/verify-pi-product-artifact.mjs
Normal file
97
scripts/verify-pi-product-artifact.mjs
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { mkdir, writeFile } from 'node:fs/promises';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
|
||||
import {
|
||||
defaultProductExecutable,
|
||||
verifyPiProductArtifact,
|
||||
} from './lib/pi-product-artifact.mjs';
|
||||
import { runProbe } from './probe-pi-runtime.mjs';
|
||||
|
||||
export function parsePiArtifactVerifierArgs(argv, projectRoot = process.cwd()) {
|
||||
const options = {
|
||||
projectRoot: resolve(projectRoot),
|
||||
executable: undefined,
|
||||
reportPath: undefined,
|
||||
samples: 1,
|
||||
timeoutMs: 10_000,
|
||||
};
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const argument = argv[index];
|
||||
const next = () => {
|
||||
const value = argv[index + 1];
|
||||
if (!value || value.startsWith('--')) throw new Error(`${argument} requires a value`);
|
||||
index += 1;
|
||||
return value;
|
||||
};
|
||||
if (argument === '--app-exe') options.executable = resolve(next());
|
||||
else if (argument === '--report') options.reportPath = resolve(next());
|
||||
else if (argument === '--samples') options.samples = Number.parseInt(next(), 10);
|
||||
else if (argument === '--timeout-ms') options.timeoutMs = Number.parseInt(next(), 10);
|
||||
else if (argument === '--help') options.help = true;
|
||||
else throw new Error(`Unknown argument: ${argument}`);
|
||||
}
|
||||
for (const [name, value] of [['--samples', options.samples], ['--timeout-ms', options.timeoutMs]]) {
|
||||
if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${name} must be a positive integer`);
|
||||
}
|
||||
options.executable ??= defaultProductExecutable(options.projectRoot);
|
||||
return options;
|
||||
}
|
||||
|
||||
function printHelp() {
|
||||
process.stdout.write('Usage: node scripts/verify-pi-product-artifact.mjs [options]\n\n');
|
||||
process.stdout.write(' --app-exe <path> Final unpacked Makelore executable\n');
|
||||
process.stdout.write(' --samples <count> Packaged get_state/runtime samples (default: 1)\n');
|
||||
process.stdout.write(' --timeout-ms <ms> Runtime probe timeout (default: 10000)\n');
|
||||
process.stdout.write(' --report <path> Write structured JSON evidence\n');
|
||||
}
|
||||
|
||||
export async function runPiArtifactVerifier(options) {
|
||||
const artifact = await verifyPiProductArtifact(options);
|
||||
const runtime = await runProbe({
|
||||
samples: options.samples,
|
||||
timeoutMs: options.timeoutMs,
|
||||
stage: false,
|
||||
keepStage: false,
|
||||
reportPath: undefined,
|
||||
providerFixturePath: undefined,
|
||||
imagePath: undefined,
|
||||
electronExecutablePath: artifact.artifact.executable,
|
||||
cliPath: artifact.artifact.cliPath,
|
||||
artifactLabel: 'final-makelore-product-artifact',
|
||||
}, options.projectRoot);
|
||||
if (runtime.result === 'fail') throw new Error('Final product Pi get_state/runtime probe failed');
|
||||
const report = {
|
||||
schemaVersion: 1,
|
||||
generatedAt: new Date().toISOString(),
|
||||
artifact,
|
||||
runtime,
|
||||
result: 'pass',
|
||||
};
|
||||
if (options.reportPath) {
|
||||
await mkdir(dirname(options.reportPath), { recursive: true });
|
||||
await writeFile(options.reportPath, `${JSON.stringify(report, null, 2)}\n`);
|
||||
}
|
||||
return report;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const options = parsePiArtifactVerifierArgs(process.argv.slice(2));
|
||||
if (options.help) {
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
const report = await runPiArtifactVerifier(options);
|
||||
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
||||
}
|
||||
|
||||
const isMain = process.argv[1]
|
||||
&& pathToFileURL(resolve(process.argv[1])).href === import.meta.url;
|
||||
if (isMain) {
|
||||
main().catch((error) => {
|
||||
process.stderr.write(`${error.stack ?? error.message}\n`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user