From a4796275202be87b5d1f9f7c17e5ffb77b10eedb Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Wed, 15 Jul 2026 13:10:31 -0600 Subject: [PATCH] fix(package): make published tarballs re-packable --- .github/workflows/ci.yml | 3 + .github/workflows/publish.yml | 3 + package.json | 5 +- scripts/verify-package.mjs | 131 ++++++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 scripts/verify-package.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abe4f32..e9ddd1f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,3 +35,6 @@ jobs: - name: Build run: pnpm build + + - name: Verify packed package lifecycle + run: pnpm verify:package diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6e396d1..df0ec7e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -35,6 +35,9 @@ jobs: - name: Build run: pnpm run build + - name: Verify packed package lifecycle + run: pnpm run verify:package + - name: Verify tag/version lock run: | NPM_VERSION=$(node -p "require('./package.json').version") diff --git a/package.json b/package.json index 04caff6..665cfae 100644 --- a/package.json +++ b/package.json @@ -60,12 +60,13 @@ "scripts": { "build": "tsup", "dev": "tsup --watch", - "prepare": "tsup", + "prepublishOnly": "pnpm build", "test": "vitest run", "test:watch": "vitest", "typecheck": "tsc --noEmit", "lint": "biome check src tests", - "format": "biome format --write src tests" + "format": "biome format --write src tests", + "verify:package": "node scripts/verify-package.mjs" }, "dependencies": { "@tangle-network/agent-eval": "^0.121.0", diff --git a/scripts/verify-package.mjs b/scripts/verify-package.mjs new file mode 100644 index 0000000..919662b --- /dev/null +++ b/scripts/verify-package.mjs @@ -0,0 +1,131 @@ +import { spawnSync } from 'node:child_process' +import { + mkdirSync, + mkdtempSync, + readFileSync, + readdirSync, + rmSync, + writeFileSync, +} from 'node:fs' +import { tmpdir } from 'node:os' +import { dirname, join, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const packageName = '@tangle-network/agent-knowledge' +const publicImports = [ + packageName, + `${packageName}/viz`, + `${packageName}/memory`, + `${packageName}/sources`, + `${packageName}/benchmarks`, +] +const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const tempRoot = mkdtempSync(join(tmpdir(), 'agent-knowledge-package-')) + +try { + const packDir = join(tempRoot, 'pack') + const appDir = join(tempRoot, 'app') + const repackDir = join(tempRoot, 'repack') + mkdirSync(packDir, { recursive: true }) + mkdirSync(appDir, { recursive: true }) + mkdirSync(repackDir, { recursive: true }) + writeFileSync( + join(appDir, 'package.json'), + `${JSON.stringify( + { + name: 'agent-knowledge-package-verification', + private: true, + type: 'module', + }, + null, + 2, + )}\n`, + ) + + run('npm', ['pack', '--ignore-scripts=false', '--pack-destination', packDir], repoRoot) + const sourceTarball = onlyTarball(packDir) + + run( + 'npm', + [ + 'install', + '--ignore-scripts=false', + '--no-package-lock', + '--no-save', + '--no-audit', + '--no-fund', + sourceTarball, + ], + appDir, + ) + + const installedPackageDir = join(appDir, 'node_modules', '@tangle-network', 'agent-knowledge') + const installedPackage = JSON.parse( + readFileSync(join(installedPackageDir, 'package.json'), 'utf8'), + ) + + run( + process.execPath, + [ + '--input-type=module', + '--eval', + `for (const specifier of ${JSON.stringify(publicImports)}) await import(specifier)`, + ], + appDir, + ) + + const cliName = process.platform === 'win32' ? 'agent-knowledge.cmd' : 'agent-knowledge' + const cliVersion = run(join(appDir, 'node_modules', '.bin', cliName), ['version'], appDir).trim() + if (cliVersion !== installedPackage.version) { + throw new Error( + `CLI version mismatch: expected ${installedPackage.version}, received ${JSON.stringify(cliVersion)}`, + ) + } + + run( + 'npm', + ['pack', '--ignore-scripts=false', '--pack-destination', repackDir], + installedPackageDir, + ) + onlyTarball(repackDir) + + process.stdout.write( + `Verified ${packageName}@${installedPackage.version}: clean install, ${publicImports.length} imports, CLI version, and re-pack.\n`, + ) +} finally { + rmSync(tempRoot, { recursive: true, force: true }) +} + +function onlyTarball(directory) { + const tarballs = readdirSync(directory).filter((name) => name.endsWith('.tgz')) + if (tarballs.length !== 1) { + throw new Error(`expected exactly one tarball in ${directory}, found ${tarballs.length}`) + } + return join(directory, tarballs[0]) +} + +function run(command, args, cwd) { + const result = spawnSync(command, args, { + cwd, + encoding: 'utf8', + env: { + ...process.env, + INIT_CWD: cwd, + npm_config_ignore_scripts: 'false', + }, + stdio: ['ignore', 'pipe', 'pipe'], + }) + if (result.error || result.status !== 0) { + throw new Error( + [ + `command failed: ${command} ${args.join(' ')}`, + result.error?.message, + result.stdout?.trim(), + result.stderr?.trim(), + ] + .filter(Boolean) + .join('\n'), + ) + } + return result.stdout +}