feat: add npm build process, version bump, and forbidden token enforcement

- Add esbuild config to bundle CLI with all workspace code for npm publishing
- Add build-npm.sh script that runs forbidden token check, type-check,
  esbuild bundle, and generates publishable package.json
- Add generate-npm-package-json.mjs to resolve workspace:* refs to actual
  npm dependencies for publishing
- Add version-bump.sh for patch/minor/major/explicit version bumping
- Add check-forbidden-tokens.mjs that scans codebase for forbidden tokens
  (mirrors git hook logic, safe if token list is missing)
- Add esbuild as dev dependency
- Add build:npm, version:bump, check:tokens scripts to root package.json
- Update .gitignore for build artifacts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dotta
2026-03-03 09:25:10 -06:00
parent b6c321f30a
commit 4c6fe04700
10 changed files with 375 additions and 3 deletions

View File

@@ -0,0 +1,67 @@
#!/usr/bin/env node
/**
* check-forbidden-tokens.mjs
*
* Scans the codebase for forbidden tokens before publishing to npm.
* Mirrors the git pre-commit hook logic, but runs against the full
* working tree (not just staged changes).
*
* Token list: .git/hooks/forbidden-tokens.txt (one per line, # comments ok).
* If the file is missing, the check passes silently — other developers
* on the project won't have this list, and that's fine.
*/
import { execSync } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
const repoRoot = execSync("git rev-parse --show-toplevel", { encoding: "utf8" }).trim();
const gitDir = execSync("git rev-parse --git-dir", { encoding: "utf8", cwd: repoRoot }).trim();
const tokensFile = resolve(repoRoot, gitDir, "hooks/forbidden-tokens.txt");
if (!existsSync(tokensFile)) {
console.log(" Forbidden tokens list not found — skipping check.");
process.exit(0);
}
const tokens = readFileSync(tokensFile, "utf8")
.split("\n")
.map((l) => l.trim())
.filter((l) => l && !l.startsWith("#"));
if (tokens.length === 0) {
console.log(" Forbidden tokens list is empty — skipping check.");
process.exit(0);
}
// Use git grep to search tracked files only (avoids node_modules, dist, etc.)
let found = false;
for (const token of tokens) {
try {
const result = execSync(
`git grep -in --no-color -- ${JSON.stringify(token)} -- ':!pnpm-lock.yaml' ':!.git'`,
{ encoding: "utf8", cwd: repoRoot, stdio: ["pipe", "pipe", "pipe"] },
);
if (result.trim()) {
if (!found) {
console.error("ERROR: Forbidden tokens found in tracked files:\n");
}
found = true;
// Print matches but DO NOT print which token was matched (avoids leaking the list)
const lines = result.trim().split("\n");
for (const line of lines) {
console.error(` ${line}`);
}
}
} catch {
// git grep returns exit code 1 when no matches — that's fine
}
}
if (found) {
console.error("\nBuild blocked. Remove the forbidden token(s) before publishing.");
process.exit(1);
} else {
console.log(" ✓ No forbidden tokens found.");
}