Files
makelore/scripts/lib/pi-product-artifact.mjs

372 lines
13 KiB
JavaScript

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 LINUX_EXECUTABLE_NAME = 'niancode';
const EXTENSION_CONTRACT_MARKERS = Object.freeze([
'Makelore runtime bridge rejected the request',
'subagent.dispatch',
'makelore.write-lease',
'MAKELORE_PI_BRIDGE_URL',
]);
const PI_AI_PROVIDER_PREFIX = 'pi-runtime/node_modules/@earendil-works/pi-ai/dist/providers/';
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', LINUX_EXECUTABLE_NAME);
}
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();
}
export function classifyOpenCodeResourcePaths(paths) {
const upstreamPiProvider = paths.filter((path) => path.startsWith(PI_AI_PROVIDER_PREFIX));
const productOwned = paths.filter((path) => !path.startsWith(PI_AI_PROVIDER_PREFIX));
return { productOwned, upstreamPiProvider };
}
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 openCodeResourcePaths = classifyOpenCodeResourcePaths(
await collectForbiddenResourcePaths(resourcesDirectory),
);
if (openCodeResourcePaths.productOwned.length > 0) {
throw new Error(
`Product-owned resources contain OpenCode paths: ${openCodeResourcePaths.productOwned.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);
const missingExtensionMarkers = EXTENSION_CONTRACT_MARKERS.filter(
(marker) => !appAsarContents.includes(Buffer.from(marker)),
);
if (missingExtensionMarkers.length > 0) {
throw new Error(
`Packaged app.asar does not contain Pi extension contract markers: ${missingExtensionMarkers.join(', ')}`,
);
}
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: {
contractMarkers: EXTENSION_CONTRACT_MARKERS,
packaged: true,
executionProof: 'smoke:pi:real final-product extension/subagent run',
},
skills: actualSkills,
openCodeResourcePaths: {
...openCodeResourcePaths,
upstreamDecision: openCodeResourcePaths.upstreamPiProvider.length > 0
? 'retained-required-files-from-exact-pinned-pi-production-package'
: 'none',
},
developmentPathResidue: [],
result: 'pass',
};
}
export const PI_PRODUCT_ARTIFACT_DEFAULTS = Object.freeze({
platform: hostPlatform(),
arch: hostArch(),
extensionContractMarkers: EXTENSION_CONTRACT_MARKERS,
});