115 lines
4.5 KiB
TypeScript
115 lines
4.5 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
assertNodeEngineCompatible,
|
|
classifyOpenCodeResourcePaths,
|
|
collectAbsoluteManifestValues,
|
|
collectForbiddenResourcePaths,
|
|
defaultProductExecutable,
|
|
validatePiArtifactMetadata,
|
|
} from '../../scripts/lib/pi-product-artifact.mjs';
|
|
import { parsePiArtifactVerifierArgs } from '../../scripts/verify-pi-product-artifact.mjs';
|
|
|
|
const roots: string[] = [];
|
|
const PI_PACKAGE = '@earendil-works/pi-coding-agent';
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
});
|
|
|
|
function matchingMetadata() {
|
|
return {
|
|
rootPackage: { dependencies: { [PI_PACKAGE]: '0.84.2' } },
|
|
lockfile: {
|
|
importers: {
|
|
'.': { dependencies: { [PI_PACKAGE]: { specifier: '0.84.2', version: '0.84.2(ws@8.20.0)' } } },
|
|
},
|
|
},
|
|
packagedPackage: { dependencies: { [PI_PACKAGE]: '0.84.2' } },
|
|
runtimePackage: {
|
|
name: PI_PACKAGE,
|
|
version: '0.84.2',
|
|
bin: { pi: 'dist/cli.js' },
|
|
},
|
|
manifest: {
|
|
runtime: {
|
|
packageName: PI_PACKAGE,
|
|
version: '0.84.2',
|
|
cliEntry: 'dist/cli.js',
|
|
nodeEngine: '>=22.19.0',
|
|
},
|
|
target: { platform: 'win32', arch: 'x64' },
|
|
},
|
|
runtimePlatform: { platform: 'win32', arch: 'x64', node: '24.18.1' },
|
|
};
|
|
}
|
|
|
|
describe('final Pi product artifact verification', () => {
|
|
it('requires the same pinned Pi version across package, lock, app, runtime, and manifest', () => {
|
|
expect(validatePiArtifactMetadata(matchingMetadata())).toMatchObject({
|
|
expected: '0.84.2',
|
|
rootDependency: '0.84.2',
|
|
lockedVersion: '0.84.2',
|
|
manifest: '0.84.2',
|
|
});
|
|
const mismatch = matchingMetadata();
|
|
mismatch.packagedPackage.dependencies[PI_PACKAGE] = '0.84.1';
|
|
expect(() => validatePiArtifactMetadata(mismatch)).toThrow('versions do not match');
|
|
});
|
|
|
|
it('checks the packaged Node version against the exact supported engine shape', () => {
|
|
expect(() => assertNodeEngineCompatible('>=22.19.0', '24.18.1')).not.toThrow();
|
|
expect(() => assertNodeEngineCompatible('>=22.19.0', '22.18.9')).toThrow('does not satisfy');
|
|
expect(() => assertNodeEngineCompatible('^22.19.0', '24.18.1')).toThrow('Unsupported');
|
|
});
|
|
|
|
it('rejects absolute manifest values and OpenCode-named artifact resources', async () => {
|
|
expect(collectAbsoluteManifestValues({
|
|
entry: 'dist/cli.js',
|
|
asset: 'node_modules/example/file.wasm',
|
|
leaked: 'D:\\work\\pi-runtime',
|
|
})).toEqual([{ at: '$.leaked', value: 'D:\\work\\pi-runtime' }]);
|
|
|
|
const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-artifact-test-'));
|
|
roots.push(root);
|
|
await mkdir(path.join(root, 'pi-runtime', 'node_modules', 'opencode-ai'), { recursive: true });
|
|
await writeFile(path.join(root, 'pi-runtime', 'node_modules', 'opencode-ai', 'package.json'), '{}');
|
|
expect(await collectForbiddenResourcePaths(root)).toEqual([
|
|
'pi-runtime/node_modules/opencode-ai',
|
|
]);
|
|
expect(classifyOpenCodeResourcePaths([
|
|
'app.asar.unpacked/resources/opencode-runtime',
|
|
'pi-runtime/node_modules/@earendil-works/pi-ai/dist/providers/opencode.js',
|
|
])).toEqual({
|
|
productOwned: ['app.asar.unpacked/resources/opencode-runtime'],
|
|
upstreamPiProvider: [
|
|
'pi-runtime/node_modules/@earendil-works/pi-ai/dist/providers/opencode.js',
|
|
],
|
|
});
|
|
});
|
|
|
|
it('uses final unpacked-product paths and strictly parses verifier options', () => {
|
|
expect(defaultProductExecutable('/repo', 'linux'))
|
|
.toBe(path.resolve('/repo', 'release', 'linux-unpacked', 'niancode'));
|
|
expect(defaultProductExecutable('D:\\repo', 'win32'))
|
|
.toBe(path.resolve('D:\\repo', 'release', 'win-unpacked', 'Makelore.exe'));
|
|
expect(parsePiArtifactVerifierArgs([
|
|
'--app-exe', 'release/custom/Makelore.exe',
|
|
'--samples', '5',
|
|
'--timeout-ms', '12000',
|
|
'--report', 'release/evidence/pi.json',
|
|
], 'D:\\repo')).toMatchObject({ samples: 5, timeoutMs: 12_000 });
|
|
expect(parsePiArtifactVerifierArgs(['--', '--samples', '3'], 'D:\\repo'))
|
|
.toMatchObject({ samples: 3 });
|
|
expect(() => parsePiArtifactVerifierArgs(['--samples', '0'], 'D:\\repo'))
|
|
.toThrow('--samples must be a positive integer');
|
|
expect(() => parsePiArtifactVerifierArgs(['--unknown'], 'D:\\repo'))
|
|
.toThrow('Unknown argument');
|
|
});
|
|
});
|