mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-18 15:39:47 +02:00
74 lines
4.6 KiB
JavaScript
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 });
|
|
}
|
|
}
|