@theholocron/cli 3.13.0 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -264,7 +264,8 @@ function resolveConfig(raw) {
264
264
  doctor: raw.doctor ?? {},
265
265
  agent: raw.agent,
266
266
  skills: raw.skills,
267
- docs
267
+ docs,
268
+ env: raw.env
268
269
  };
269
270
  }
270
271
  //#endregion
@@ -3453,11 +3454,13 @@ async function runSetup(input) {
3453
3454
  print(style.hint(" · Workflows — read and write"));
3454
3455
  print(style.hint(" · Metadata — read (added automatically)"));
3455
3456
  print("");
3456
- print(style.hint(" Org-scoped operations (teams, custom properties) additionally require"));
3457
- print(style.hint(" HOLOCRON_ORG_TOKEN — a PAT scoped to the org with:"));
3457
+ print(style.hint(" Org-scoped operations (teams, custom properties) require"));
3458
+ print(style.hint(" HOLOCRON_ORG_TOKEN — a fine-grained PAT with resource owner set to the org:"));
3458
3459
  print("");
3459
- print(style.hint(" · Members — read and write"));
3460
- print(style.hint(" · Organization custom properties — read and write"));
3460
+ print(style.hint(" · Administration — read and write (repository permission)"));
3461
+ print(style.hint(" · Members — read (organization permission)"));
3462
+ print(style.hint(" · Organization custom properties — read and write (organization permission)"));
3463
+ print(style.hint(" · Metadata — read (repository permission, auto-included)"));
3461
3464
  print("");
3462
3465
  print(style.hint(" Create tokens at: https://github.com/settings/personal-access-tokens/new"));
3463
3466
  print(style.hint(" Then re-run: holocron setup --token <your-admin-pat>"));
@@ -4441,6 +4444,86 @@ async function runSyncGithub(input) {
4441
4444
  };
4442
4445
  }
4443
4446
  //#endregion
4447
+ //#region src/commands/sync-readme.ts
4448
+ const README_INSTALL_START = "<!-- holocron:installation -->";
4449
+ const README_INSTALL_END = "<!-- /holocron:installation -->";
4450
+ function generateInstallBlock(pkg) {
4451
+ const { name = "", bin, peerDependencies = {} } = pkg;
4452
+ const isCli = Boolean(bin);
4453
+ const isReact = Boolean(peerDependencies["react"]);
4454
+ const lines = [];
4455
+ lines.push("## Installation", "");
4456
+ if (isCli) lines.push("```bash", `npm install --global ${name}`, "```");
4457
+ else lines.push("```bash", `pnpm install ${name}`, "```");
4458
+ lines.push("", "## Usage", "");
4459
+ if (isCli) {
4460
+ const commands = typeof bin === "string" ? [name.split("/").at(-1)] : Object.keys(bin ?? {});
4461
+ lines.push("```bash");
4462
+ for (const cmd of commands) lines.push(`${cmd} --help`);
4463
+ lines.push("```");
4464
+ } else if (isReact) lines.push("```tsx", `import { } from "${name}";`, "", "function App() {", ` return <></>;`, "}", "```");
4465
+ else lines.push("```typescript", `import { } from "${name}";`, "```");
4466
+ return lines.join("\n");
4467
+ }
4468
+ async function updateReadmeInstallation(repoRoot, block, dryRun, readFileFn, writeFileFn) {
4469
+ const readmePath = join(repoRoot, "README.md");
4470
+ let content;
4471
+ try {
4472
+ content = await readFileFn(readmePath, "utf8");
4473
+ } catch {
4474
+ return false;
4475
+ }
4476
+ const lines = content.split("\n");
4477
+ const startIdx = lines.findIndex((l) => l.trim() === README_INSTALL_START);
4478
+ const endIdx = lines.findIndex((l) => l.trim() === README_INSTALL_END);
4479
+ const blockLines = block.split("\n");
4480
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
4481
+ if (dryRun) return true;
4482
+ lines.splice(startIdx + 1, endIdx - startIdx - 1, "", ...blockLines, "");
4483
+ await writeFileFn(readmePath, lines.join("\n"), "utf8");
4484
+ return true;
4485
+ }
4486
+ const descEnd = lines.findIndex((l) => l.trim() === "<!-- /holocron:description -->");
4487
+ const insertAfter = descEnd !== -1 ? descEnd : lines.findIndex((l) => /^# /.test(l));
4488
+ if (insertAfter === -1) return false;
4489
+ if (dryRun) return true;
4490
+ lines.splice(insertAfter + 1, 0, "", README_INSTALL_START, "", ...blockLines, "", README_INSTALL_END);
4491
+ await writeFileFn(readmePath, lines.join("\n"), "utf8");
4492
+ return true;
4493
+ }
4494
+ async function runSyncReadme(input) {
4495
+ const { loaded, context, print = console.log, readFileFn = readFile, writeFileFn = writeFile } = input;
4496
+ const { repoRoot, dryRun = false } = context;
4497
+ const namespaces = loaded.resolved.env?.namespaces ?? [];
4498
+ print(style.header(`Syncing README installation block${dryRun ? " (dry-run)" : ""}…`));
4499
+ if (namespaces.length > 0) print(style.hint(` namespaces: ${namespaces.join(", ")}`));
4500
+ let pkg;
4501
+ try {
4502
+ const raw = await readFileFn(join(repoRoot, "package.json"), "utf8");
4503
+ pkg = JSON.parse(raw);
4504
+ } catch {
4505
+ return {
4506
+ status: "fail",
4507
+ updated: false,
4508
+ message: "Could not read package.json"
4509
+ };
4510
+ }
4511
+ const updated = await updateReadmeInstallation(repoRoot, generateInstallBlock(pkg), dryRun, readFileFn, writeFileFn);
4512
+ if (!updated) {
4513
+ print(style.warn("README.md not found or has no writable location for installation block"));
4514
+ return {
4515
+ status: "fail",
4516
+ updated: false,
4517
+ message: "README.md update failed"
4518
+ };
4519
+ }
4520
+ print(dryRun ? style.hint(" would update README.md") : style.success(" updated README.md"));
4521
+ return {
4522
+ status: dryRun ? "dry-run" : "ok",
4523
+ updated
4524
+ };
4525
+ }
4526
+ //#endregion
4444
4527
  //#region src/commands/upgrade-node.ts
4445
4528
  const SKIP_DIRS = /* @__PURE__ */ new Set([
4446
4529
  "node_modules",
@@ -5217,6 +5300,18 @@ try {
5217
5300
  ...argv.message ? { message: argv.message } : {},
5218
5301
  ...outputDir ? { outputDir } : {}
5219
5302
  })).status === "fail") process.exitCode = 1;
5303
+ }).command("sync-readme", "Sync the Installation + Usage block in README.md from package.json", (y) => y.option("dry-run", {
5304
+ type: "boolean",
5305
+ describe: "Print what would change without writing",
5306
+ default: false
5307
+ }), async (argv) => {
5308
+ if ((await runSyncReadme({
5309
+ loaded: await loadConfig(argv.cwd),
5310
+ context: {
5311
+ repoRoot: argv.cwd,
5312
+ dryRun: argv.dryRun
5313
+ }
5314
+ })).status === "fail") process.exitCode = 1;
5220
5315
  }).command("config show", "Print the resolved holocron config", () => {}, async (argv) => {
5221
5316
  const loaded = await loadConfig(argv.cwd);
5222
5317
  console.log(JSON.stringify(loaded.resolved, null, 2));