// @vitest-environment node import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; import { createRequire } from 'node:module'; import { tmpdir } from 'node:os'; import path from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; import { assertNodeEngineCompatible, classifyOpenCodeResourcePaths, collectAbsoluteManifestValues, collectForbiddenAsarPaths, 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'; const { createPackage } = createRequire(import.meta.url)('@electron/asar'); 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', 'app.asar/node_modules/@earendil-works/pi-ai/dist/providers/opencode-codex-responses.js', ])).toEqual({ productOwned: ['app.asar.unpacked/resources/opencode-runtime'], upstreamPiProvider: [ 'pi-runtime/node_modules/@earendil-works/pi-ai/dist/providers/opencode.js', 'app.asar/node_modules/@earendil-works/pi-ai/dist/providers/opencode-codex-responses.js', ], }); }); it('enumerates OpenCode-named paths inside app.asar instead of scanning only physical resources', async () => { const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-asar-paths-')); roots.push(root); const source = path.join(root, 'source'); await mkdir(path.join(source, 'node_modules', '@earendil-works', 'pi-ai', 'dist', 'providers'), { recursive: true, }); await mkdir(path.join(source, 'resources', 'opencode-runtime'), { recursive: true }); await writeFile( path.join(source, 'node_modules', '@earendil-works', 'pi-ai', 'dist', 'providers', 'opencode.js'), 'export {};', ); await writeFile(path.join(source, 'resources', 'opencode-runtime', 'legacy.js'), 'export {};'); const archive = path.join(root, 'app.asar'); await createPackage(source, archive); const inventory = collectForbiddenAsarPaths(archive); expect(inventory.entryCount).toBeGreaterThan(2); expect(inventory.matches).toEqual(expect.arrayContaining([ 'app.asar/node_modules/@earendil-works/pi-ai/dist/providers/opencode.js', 'app.asar/resources/opencode-runtime', ])); expect(classifyOpenCodeResourcePaths(inventory.matches)).toMatchObject({ productOwned: expect.arrayContaining(['app.asar/resources/opencode-runtime']), upstreamPiProvider: expect.arrayContaining([ 'app.asar/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'); }); });