import { spawnSync } from 'node:child_process'; import { copyFileSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync, } from 'node:fs'; import { tmpdir } from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(__dirname, '..'); const distDir = path.join(repoRoot, 'dist'); const suspiciousPathPatterns = [ /\/Users\/[^\s'"`]+(?:node_modules|oh-my-opencode-slim)[^\s'"`]*/, /\/home\/[^\s'"`]+(?:node_modules|oh-my-opencode-slim)[^\s'"`]*/, ]; const suspiciousImportPatterns = [/from\s+["']vscode-jsonrpc\/node["']/]; const packagedRequiredFiles = [ 'package.json', 'README.md', 'LICENSE', 'dist/index.js', 'dist/index.d.ts', 'dist/cli/index.js', 'oh-my-opencode-slim.schema.json', 'src/companion/companion-manifest.json', 'src/skills/simplify/SKILL.md', 'src/skills/codemap/SKILL.md', 'src/skills/clonedeps/SKILL.md', 'src/skills/deepwork/SKILL.md', 'src/skills/verification-planning/SKILL.md', 'src/skills/reflect/SKILL.md', 'src/skills/oh-my-opencode-slim/SKILL.md', 'src/skills/worktrees/SKILL.md', ]; function fail(message: string): never { throw new Error(message); } function run(command: string, args: string[], options: { cwd?: string } = {}) { const result = spawnSync(command, args, { cwd: options.cwd ?? repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], }); if (result.status !== 0) { const detail = [result.stdout, result.stderr].filter(Boolean).join('\n'); fail( `Command failed: ${command} ${args.join(' ')}${detail ? `\n${detail}` : ''}`, ); } return result.stdout.trim(); } function parsePackJson(output: string) { const start = output.indexOf('['); const end = output.lastIndexOf(']'); if (start === -1 || end === -1 || end < start) { fail(`Could not locate npm pack JSON output:\n${output}`); } return JSON.parse(output.slice(start, end + 1)) as Array<{ filename?: string; files?: Array<{ path: string }>; }>; } function walkFiles(dir: string): string[] { const entries = readdirSync(dir, { withFileTypes: true }); return entries.flatMap((entry) => { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) return walkFiles(fullPath); return [fullPath]; }); } function verifyDistHasNoLeakedPaths() { console.log('Checking dist for leaked machine paths...'); const files = walkFiles(distDir).filter((file) => /\.(?:js|d\.ts|map|json)$/.test(file), ); const leaks: string[] = []; for (const file of files) { const content = readFileSync(file, 'utf8'); for (const pattern of suspiciousPathPatterns) { const match = content.match(pattern); if (!match) continue; leaks.push(`${path.relative(repoRoot, file)}: ${match[0]}`); } for (const pattern of suspiciousImportPatterns) { const match = content.match(pattern); if (!match) continue; leaks.push(`${path.relative(repoRoot, file)}: ${match[0]}`); } } if (leaks.length > 0) { fail( `Built artifact contains machine-specific paths:\n${leaks.join('\n')}`, ); } } function packArtifact() { console.log('Packing npm artifact...'); const output = run('npm', ['pack', '--json', '--ignore-scripts'], { cwd: repoRoot, }); const parsed = parsePackJson(output); const tarball = parsed[0]?.filename; if (!tarball) { fail(`npm pack did not return a tarball filename:\n${output}`); } const packagedFiles = new Set( (parsed[0]?.files ?? []).map((file) => file.path), ); for (const requiredFile of packagedRequiredFiles) { if (!packagedFiles.has(requiredFile)) { fail(`npm pack artifact is missing required file: ${requiredFile}`); } } return path.join(repoRoot, tarball); } function verifyFreshInstall(tarballPath: string) { const tempRoot = mkdtempSync(path.join(tmpdir(), 'omos-release-')); try { console.log('Installing packed artifact into clean temp project...'); const installDir = path.join(tempRoot, 'install'); const tarballTarget = path.join(tempRoot, path.basename(tarballPath)); copyFileSync(tarballPath, tarballTarget); mkdirSync(installDir, { recursive: true }); writeFileSync( path.join(installDir, 'package.json'), JSON.stringify( { name: 'verify-release-artifact', private: true }, null, 2, ), ); run('bun', ['add', '--ignore-scripts', tarballTarget], { cwd: installDir, }); const installedEntry = path.join( installDir, 'node_modules', 'oh-my-opencode-slim', 'dist', 'index.js', ); const installedEntryContent = readFileSync(installedEntry, 'utf8'); for (const pattern of suspiciousPathPatterns) { const match = installedEntryContent.match(pattern); if (match) { fail( `Installed package still contains machine-specific path: ${match[0]}`, ); } } const smokeScript = [ "import pkg from 'oh-my-opencode-slim';", "if (typeof pkg !== 'function') throw new Error('default export is not a function');", "console.log('package loads');", 'process.exit(0);', ].join('\n'); console.log('Importing installed package entrypoint...'); run('node', ['--input-type=module', '--eval', smokeScript], { cwd: installDir, }); } finally { rmSync(tempRoot, { recursive: true, force: true }); } } function cleanupTarball(tarballPath: string) { rmSync(tarballPath, { force: true }); } function main() { verifyDistHasNoLeakedPaths(); const tarballPath = packArtifact(); try { verifyFreshInstall(tarballPath); } finally { cleanupTarball(tarballPath); } console.log('Release artifact verification passed.'); } main();