Skip to content
← Public packages

@kentcdodds/package-app-kit

Design tokens, PWA install/update, About/version, cache helpers, and optional realtime notes sync for Kody package apps.

FollowFork

starter-remix/scripts/prepublish-check.mjs

71 lines · 2.6 KB · JavaScript
#!/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.')