Files
PaperClipAI/scripts/retain-runner-qualification-packages.mjs
T

74 lines
4.6 KiB
JavaScript

// Retain normal build/pack outputs for installed Product E2E and Runner evals.
// This runs only after the clean public installation and Pi admission pass.
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { basename, join, resolve } from 'node:path';
export function retainRunnerQualificationPackages({ repo, output, sourceRevision, releaseVersion, publicArchives, env = process.env }) {
assert.match(sourceRevision, /^[a-f0-9]{40}$/);
assert.equal(JSON.parse(readFileSync(join(repo, 'server/dist/build-info.json'))).commit, sourceRevision);
assert.ok(publicArchives.some(a => a.name === '@paperclipai/server'));
assert.ok(publicArchives.some(a => a.name === 'paperclipai'));
assert.ok(publicArchives.some(a => a.name === '@paperclipai/plugin-sdk'));
assert.equal(new Set(publicArchives.map(a => a.name)).size, publicArchives.length);
output = resolve(output);
assert.ok(!existsSync(output), 'Never replace retained qualification packages');
mkdirSync(output, { recursive: true });
const staging = mkdtempSync(join(tmpdir(), 'paperclip-qualification-pack-'));
const archives = [];
const hash = file => createHash('sha256').update(readFileSync(file)).digest('hex');
const retain = (name, file) => {
const destination = join(output, basename(file));
assert.ok(!existsSync(destination), 'Duplicate archive filename');
cpSync(file, destination);
archives.push({ name, file: basename(file), sha256: hash(destination) });
};
const pack = (directory, name, ignoreScripts) => {
const previous = new Set(readdirSync(staging));
execFileSync('npm', ['pack', ...(ignoreScripts ? ['--ignore-scripts'] : []), '--pack-destination', staging], {
cwd: directory, env: { ...env, npm_config_cache: join(staging, 'npm-cache'), npm_config_ignore_scripts: String(ignoreScripts) }, maxBuffer: 32 * 1024 * 1024,
});
const files = readdirSync(staging).filter(file => file.endsWith('.tgz') && !previous.has(file));
assert.equal(files.length, 1);
retain(name, join(staging, files[0]));
};
try {
for (const archive of publicArchives) retain(archive.name, archive.file);
// Match the plugin's normal release prepack. Its source manifest omits
// the SDK dependency; the hook adds the publishable SDK version.
const pluginRelative = 'packages/plugins/sandbox-providers/daytona';
const plugin = join(staging, pluginRelative);
mkdirSync(plugin, { recursive: true });
cpSync(join(repo, pluginRelative, 'dist'), join(plugin, 'dist'), { recursive: true });
const pluginManifest = JSON.parse(readFileSync(join(repo, pluginRelative, 'package.json')));
writeFileSync(join(plugin, 'package.json'), JSON.stringify({ ...pluginManifest, version: releaseVersion }));
mkdirSync(join(staging, 'scripts'));
cpSync(join(repo, 'scripts/generate-plugin-package-json.mjs'), join(staging, 'scripts/generate-plugin-package-json.mjs'));
mkdirSync(join(staging, 'packages/plugins/sdk'), { recursive: true });
const sdk = JSON.parse(readFileSync(join(repo, 'packages/plugins/sdk/package.json')));
writeFileSync(join(staging, 'packages/plugins/sdk/package.json'), JSON.stringify({ ...sdk, version: releaseVersion }));
pack(plugin, pluginManifest.name, false);
// The separate private eval SDK is already built by pnpm build. Packing
// it does not substitute for the public server's packaged runner.
const runner = join(staging, 'runner');
mkdirSync(runner);
const runnerManifest = JSON.parse(readFileSync(join(repo, 'packages/paperclip-runner/package.json')));
for (const file of runnerManifest.files) {
const input = join(repo, 'packages/paperclip-runner', file);
if (existsSync(input)) cpSync(input, join(runner, file), { recursive: true });
}
assert.ok(existsSync(join(runner, 'dist/cli/eval-session.js')));
writeFileSync(join(runner, 'package.json'), JSON.stringify(runnerManifest));
pack(runner, runnerManifest.name, true);
assert.equal(new Set(archives.map(a => a.name)).size, archives.length);
const receipt = { schema: 'paperclip.runner.qualification-packages/v1', sourceRevision, releaseVersion, publicInstallationVerified: true, piAdmissionVerified: true, providerCalls: 0, archives };
writeFileSync(join(output, 'manifest.json'), JSON.stringify(receipt, null, 2) + '\n');
return receipt;
} finally {
rmSync(staging, { recursive: true, force: true });
}
}