@theholocron/cli 2.0.0-alpha.56 → 2.0.0-alpha.58

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/README.md CHANGED
@@ -56,10 +56,10 @@ export default defineConfig({
56
56
  `name` and `repo.name` are optional. When absent, Holocron fills them
57
57
  in at load time:
58
58
 
59
- | Field | Derived from | Fallback |
60
- |---|---|---|
61
- | `name` | `package.json` → `name` field (scope stripped) | directory basename |
62
- | `repo.name` | `git remote get-url origin` (parsed to `owner/repo`) | not set |
59
+ | Field | Derived from | Fallback |
60
+ | ----------- | ---------------------------------------------------- | ------------------ |
61
+ | `name` | `package.json` → `name` field (scope stripped) | directory basename |
62
+ | `repo.name` | `git remote get-url origin` (parsed to `owner/repo`) | not set |
63
63
 
64
64
  A minimal config — for repos with a `package.json` and a GitHub remote
65
65
  — only needs `providers`:
@@ -137,6 +137,11 @@ interface Source extends ProviderIdentity {
137
137
  * Optional — providers that don't support topics omit this.
138
138
  */
139
139
  syncTopics?(topics: string[]): Promise<string>;
140
+ /**
141
+ * Set the repository description.
142
+ * Optional — providers that don't support setting descriptions omit this.
143
+ */
144
+ syncDescription?(description: string): Promise<string>;
140
145
  }
141
146
  type CiRunStatus = "queued" | "in_progress" | "completed" | "cancelled" | "failure" | "success" | "skipped";
142
147
  interface CiRun {
package/dist/cli.mjs CHANGED
@@ -8,7 +8,7 @@ import { Entry, findCredentials } from "@napi-rs/keyring";
8
8
  import { createHash } from "node:crypto";
9
9
  import { createGitHubClient } from "@theholocron/github-client";
10
10
  import { execFile, spawnSync } from "node:child_process";
11
- import { access, readFile, stat } from "node:fs/promises";
11
+ import { access, readFile, stat, writeFile } from "node:fs/promises";
12
12
  import { pathToFileURL } from "node:url";
13
13
  import { promisify } from "node:util";
14
14
  //#region src/capabilities/index.ts
@@ -697,9 +697,11 @@ function patchPackageJson(content, from, to) {
697
697
  }
698
698
  let changed = false;
699
699
  const engines = pkg.engines;
700
- if (engines?.node === `>=${from}.0.0`) {
701
- engines.node = `>=${to}.0.0`;
702
- changed = true;
700
+ if (engines?.node && /^>=\d+(?:\.0\.0)?$/.test(engines.node)) {
701
+ if (parseInt(engines.node.match(/\d+/)[0], 10) === from) {
702
+ engines.node = `>=${to}.0.0`;
703
+ changed = true;
704
+ }
703
705
  }
704
706
  for (const field of ["devDependencies", "dependencies"]) {
705
707
  const deps = pkg[field];
@@ -1355,6 +1357,8 @@ jobs:
1355
1357
  steps:
1356
1358
  - name: Checkout repository
1357
1359
  uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
1360
+ with:
1361
+ fetch-depth: 0
1358
1362
 
1359
1363
  - name: Setup
1360
1364
  if: \${{ hashFiles('pnpm-lock.yaml') != '' }}
@@ -1396,15 +1400,14 @@ jobs:
1396
1400
  uses: reviewdog/action-gitleaks@2b7b5685e3e3eecddab5d30cfa04f18123031421 # v1
1397
1401
  with:
1398
1402
  reporter: github-pr-check
1403
+ gitleaks_flags: --log-opts=\${{ github.event.pull_request.base.sha }}..\${{ github.event.pull_request.head.sha }}
1399
1404
 
1400
1405
  - name: YamlLint
1406
+ if: \${{ hashFiles('yamllint.config.yml') != '' }}
1401
1407
  uses: reviewdog/action-yamllint@b5f7217d8c815ae374d1d55840d5e569d82f01f0 # v1
1402
1408
  with:
1403
1409
  reporter: github-pr-check
1404
- yamllint_flags: >-
1405
- \${{ hashFiles('yamllint.config.yml') != ''
1406
- && format('-c {0}/yamllint.config.yml {0}', github.workspace)
1407
- || github.workspace }}
1410
+ yamllint_flags: -c \${{ github.workspace }}/yamllint.config.yml \${{ github.workspace }}
1408
1411
 
1409
1412
  - name: ActionLint (GitHub Actions)
1410
1413
  if: \${{ hashFiles('.github/workflows/*.yml', '.github/workflows/*.yaml') != '' }}
@@ -4269,7 +4272,9 @@ function formatStep(step) {
4269
4272
  const SYNC_STEPS = [
4270
4273
  "labels",
4271
4274
  "properties",
4272
- "topics"
4275
+ "topics",
4276
+ "keywords",
4277
+ "description"
4273
4278
  ];
4274
4279
  async function runSync(input) {
4275
4280
  const print = input.print ?? ((line) => console.log(line));
@@ -4345,6 +4350,47 @@ async function runSync(input) {
4345
4350
  print(formatSyncStep(steps[steps.length - 1]));
4346
4351
  }
4347
4352
  }
4353
+ if (stepName === "keywords") {
4354
+ const topics = config.repo?.topics ?? [];
4355
+ if (topics.length === 0) {
4356
+ steps.push({
4357
+ capability: "source",
4358
+ step: "sync keywords",
4359
+ status: "skip",
4360
+ message: "no topics configured"
4361
+ });
4362
+ print(formatSyncStep(steps[steps.length - 1]));
4363
+ } else {
4364
+ steps.push(await runSyncStep("source", "sync keywords", dryRun, async () => {
4365
+ return await writePackageJsonField(input.context.repoRoot, "keywords", topics) ? `${topics.length} keywords written` : `${topics.length} topics (no package.json)`;
4366
+ }));
4367
+ print(formatSyncStep(steps[steps.length - 1]));
4368
+ }
4369
+ }
4370
+ if (stepName === "description") {
4371
+ const description = config.description;
4372
+ if (!description) {
4373
+ steps.push({
4374
+ capability: "source",
4375
+ step: "sync description",
4376
+ status: "skip",
4377
+ message: "no description configured"
4378
+ });
4379
+ print(formatSyncStep(steps[steps.length - 1]));
4380
+ } else {
4381
+ steps.push(await runSyncStep("source", "sync description", dryRun, async () => {
4382
+ const pkgWrote = await writePackageJsonField(input.context.repoRoot, "description", description);
4383
+ const readmeWrote = await updateReadmeDescription(input.context.repoRoot, description);
4384
+ if (source.syncDescription) await source.syncDescription(description);
4385
+ const parts = [];
4386
+ if (pkgWrote) parts.push("package.json");
4387
+ if (readmeWrote) parts.push("README.md");
4388
+ if (source.syncDescription) parts.push("GitHub");
4389
+ return parts.length > 0 ? parts.join(", ") + " updated" : "description synced";
4390
+ }));
4391
+ print(formatSyncStep(steps[steps.length - 1]));
4392
+ }
4393
+ }
4348
4394
  }
4349
4395
  if (requestedSteps) {
4350
4396
  for (const name of requestedSteps) if (!SYNC_STEPS.includes(name)) {
@@ -4406,6 +4452,44 @@ function formatSyncStep(step) {
4406
4452
  const detail = step.message ? ` (${step.message})` : "";
4407
4453
  return ` ${icon} ${step.step}${detail}`;
4408
4454
  }
4455
+ async function writePackageJsonField(repoRoot, field, value) {
4456
+ const pkgPath = join(repoRoot, "package.json");
4457
+ let content;
4458
+ try {
4459
+ content = await readFile(pkgPath, "utf8");
4460
+ } catch {
4461
+ return false;
4462
+ }
4463
+ const pkg = JSON.parse(content);
4464
+ pkg[field] = value;
4465
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
4466
+ return true;
4467
+ }
4468
+ const README_DESC_START = "<!-- holocron:description -->";
4469
+ const README_DESC_END = "<!-- /holocron:description -->";
4470
+ async function updateReadmeDescription(repoRoot, description) {
4471
+ const readmePath = join(repoRoot, "README.md");
4472
+ let content;
4473
+ try {
4474
+ content = await readFile(readmePath, "utf8");
4475
+ } catch {
4476
+ return false;
4477
+ }
4478
+ const lines = content.split("\n");
4479
+ const startIdx = lines.findIndex((l) => l.trim() === README_DESC_START);
4480
+ const endIdx = lines.findIndex((l) => l.trim() === README_DESC_END);
4481
+ if (startIdx !== -1) {
4482
+ if (endIdx === -1 || endIdx <= startIdx) return false;
4483
+ lines.splice(startIdx + 1, endIdx - startIdx - 1, description);
4484
+ await writeFile(readmePath, lines.join("\n"), "utf8");
4485
+ return true;
4486
+ }
4487
+ const h1Index = lines.findIndex((l) => /^# /.test(l));
4488
+ if (h1Index === -1) return false;
4489
+ lines.splice(h1Index + 1, 0, "", README_DESC_START, description, README_DESC_END);
4490
+ await writeFile(readmePath, lines.join("\n"), "utf8");
4491
+ return true;
4492
+ }
4409
4493
  //#endregion
4410
4494
  //#region src/load-config.ts
4411
4495
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/cli",
3
- "version": "2.0.0-alpha.56",
3
+ "version": "2.0.0-alpha.58",
4
4
  "description": "The Holocron CLI — a pluggable, capability-based orchestrator for spinning up and operating software projects.",
5
5
  "homepage": "https://github.com/theholocron/holocron/tree/main/packages/cli#readme",
6
6
  "bugs": "https://github.com/theholocron/holocron/issues",