#!/usr/bin/env bun /** * help — 脚本目录的单一真相源呈现层。 * * bun run help 按分组彩色打印脚本目录 * bun run help --check 校验 package.json 的 scripts 与 scripts.json 双向对齐 * (有脚本没说明 / 有说明没脚本 / group 未定义 → 退出 1) * bun run help --readme 用 scripts.json 重新生成 README 的「## Scripts」表 * (在一对 HTML 注释锚点之间幂等替换) * * 描述来自 scripts/scripts.json(唯一真相源);实际 shell 命令只存在于 * package.json(不在此重复,避免双写漂移)。新增脚本的契约:package.json 加键 * + scripts.json 加一行说明,二者必须对齐(--check 守门),README 表 --readme 再生。 * * 纯 node:fs,无第三方依赖;非 TTY 或设置 NO_COLOR 时自动降级为无色。 */ import { readFileSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); const PKG = join(ROOT, 'package.json'); const MANIFEST = join(ROOT, 'scripts', 'scripts.json'); const README = join(ROOT, 'README.md'); const BEGIN = ''; const END = ''; const useColor = process.stdout.isTTY && !process.env.NO_COLOR; const paint = (code, s) => (useColor ? `\x1b[${code}m${s}\x1b[0m` : s); const bold = (s) => paint('1', s); const cyan = (s) => paint('36', s); const dim = (s) => paint('2', s); const red = (s) => paint('31', s); const green = (s) => paint('32', s); const pkg = JSON.parse(readFileSync(PKG, 'utf8')); const manifest = JSON.parse(readFileSync(MANIFEST, 'utf8')); const scripts = pkg.scripts ?? {}; const groups = manifest.groups ?? []; const entries = manifest.scripts ?? {}; /** 返回对齐问题列表(空 = 对齐)。 */ function alignmentProblems() { const inPkg = Object.keys(scripts); const inManifest = Object.keys(entries); const manifestSet = new Set(inManifest); const pkgSet = new Set(inPkg); const groupIds = new Set(groups.map((g) => g.id)); const missingDesc = inPkg.filter((k) => !manifestSet.has(k)); const orphanDesc = inManifest.filter((k) => !pkgSet.has(k)); const badGroup = inManifest.filter((k) => !groupIds.has(entries[k].group)); const problems = []; if (missingDesc.length) problems.push(`package.json 脚本缺 scripts.json 说明: ${missingDesc.join(', ')}`); if (orphanDesc.length) problems.push(`scripts.json 有说明但 package.json 无脚本: ${orphanDesc.join(', ')}`); if (badGroup.length) problems.push(`scripts.json 脚本 group 未在 groups 定义: ${badGroup.join(', ')}`); return problems; } /** 按 groups 顺序分组;组内按 scripts.json 键序。 */ function groupedRows() { const rows = []; for (const g of groups) { const keys = Object.keys(entries).filter((k) => entries[k].group === g.id); if (keys.length) rows.push({ group: g, keys }); } return rows; } function printList() { const width = Object.keys(entries).reduce((m, k) => Math.max(m, k.length), 0); console.log('\n' + bold('NomiFun 脚本目录') + dim(' bun run