#!/usr/bin/env node // Pre-publish guard: run before `kody package publish` (npm run publish:kody // does this for you). Never prints credential values, only file names. import { execFileSync, spawnSync } from 'node:child_process' import fs from 'node:fs' const problems = [] const warnings = [] const sh = (cmd, args) => execFileSync(cmd, args, { encoding: 'utf8' }).trim() const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')) if (!pkg.name || pkg.name.includes('__')) problems.push('package.json#name is not set to @owner/name.') if (!pkg.kody?.id) problems.push('package.json#kody.id is missing.') if (!pkg.kody?.description) warnings.push('package.json#kody.description is empty.') for (const file of ['README.md', 'AGENTS.md']) { if (!fs.existsSync(file)) problems.push(`${file} is missing.`) } // Credential-shaped strings in tracked files (values are never printed). const credentialPatterns = [ /kody_(at|bc)_[A-Za-z0-9]{8,}/, /gh[pousr]_[A-Za-z0-9]{20,}/, /github_pat_[A-Za-z0-9_]{20,}/, /sk_(live|test)_[A-Za-z0-9]{10,}/, /xox[abp]-[A-Za-z0-9-]{10,}/, /AKIA[0-9A-Z]{16}/, /-----BEGIN [A-Z ]*PRIVATE KEY-----/, ] let tracked = [] try { tracked = sh('git', ['ls-files']).split('\n').filter(Boolean) } catch { problems.push('Not a git repository. Clone with `git clone https://kody.codes/@owner/name.git`.') } for (const file of tracked) { if (file === 'scripts/prepublish-check.mjs') continue let text try { text = fs.readFileSync(file, 'utf8') } catch { continue } if (credentialPatterns.some((re) => re.test(text))) { problems.push(`${file} contains something that looks like a credential. Remove it and use Kody credentials instead.`) } } // Publishing ships what is pushed, so the tree must be clean and pushed. if (tracked.length) { if (sh('git', ['status', '--porcelain'])) problems.push('Uncommitted changes. Commit them first.') try { sh('git', ['fetch', '--quiet']) const ahead = sh('git', ['rev-list', '--count', '@{u}..HEAD']) if (ahead !== '0') problems.push(`${ahead} local commit(s) not pushed. Run \`git push\` first.`) } catch { problems.push('Could not compare with the remote. Check `git remote -v` and run `npm run setup` (or `kody auth setup-git`).') } } if (!process.argv.includes('--skip-dev-check')) { const check = spawnSync('npx', ['--no-install', 'kody', 'dev', 'check', '--target', 'mock'], { stdio: 'inherit' }) if (check.status !== 0) problems.push('`kody dev check` failed (see output above).') } for (const w of warnings) console.warn(`warn: ${w}`) if (problems.length) { console.error('\nNot ready to publish:') for (const p of problems) console.error(`- ${p}`) process.exit(1) } console.log('\nReady to publish.')