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

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
@@ -4269,7 +4269,9 @@ function formatStep(step) {
4269
4269
  const SYNC_STEPS = [
4270
4270
  "labels",
4271
4271
  "properties",
4272
- "topics"
4272
+ "topics",
4273
+ "keywords",
4274
+ "description"
4273
4275
  ];
4274
4276
  async function runSync(input) {
4275
4277
  const print = input.print ?? ((line) => console.log(line));
@@ -4345,6 +4347,47 @@ async function runSync(input) {
4345
4347
  print(formatSyncStep(steps[steps.length - 1]));
4346
4348
  }
4347
4349
  }
4350
+ if (stepName === "keywords") {
4351
+ const topics = config.repo?.topics ?? [];
4352
+ if (topics.length === 0) {
4353
+ steps.push({
4354
+ capability: "source",
4355
+ step: "sync keywords",
4356
+ status: "skip",
4357
+ message: "no topics configured"
4358
+ });
4359
+ print(formatSyncStep(steps[steps.length - 1]));
4360
+ } else {
4361
+ steps.push(await runSyncStep("source", "sync keywords", dryRun, async () => {
4362
+ return await writePackageJsonField(input.context.repoRoot, "keywords", topics) ? `${topics.length} keywords written` : `${topics.length} topics (no package.json)`;
4363
+ }));
4364
+ print(formatSyncStep(steps[steps.length - 1]));
4365
+ }
4366
+ }
4367
+ if (stepName === "description") {
4368
+ const description = config.description;
4369
+ if (!description) {
4370
+ steps.push({
4371
+ capability: "source",
4372
+ step: "sync description",
4373
+ status: "skip",
4374
+ message: "no description configured"
4375
+ });
4376
+ print(formatSyncStep(steps[steps.length - 1]));
4377
+ } else {
4378
+ steps.push(await runSyncStep("source", "sync description", dryRun, async () => {
4379
+ const pkgWrote = await writePackageJsonField(input.context.repoRoot, "description", description);
4380
+ const readmeWrote = await updateReadmeDescription(input.context.repoRoot, description);
4381
+ if (source.syncDescription) await source.syncDescription(description);
4382
+ const parts = [];
4383
+ if (pkgWrote) parts.push("package.json");
4384
+ if (readmeWrote) parts.push("README.md");
4385
+ if (source.syncDescription) parts.push("GitHub");
4386
+ return parts.length > 0 ? parts.join(", ") + " updated" : "description synced";
4387
+ }));
4388
+ print(formatSyncStep(steps[steps.length - 1]));
4389
+ }
4390
+ }
4348
4391
  }
4349
4392
  if (requestedSteps) {
4350
4393
  for (const name of requestedSteps) if (!SYNC_STEPS.includes(name)) {
@@ -4406,6 +4449,44 @@ function formatSyncStep(step) {
4406
4449
  const detail = step.message ? ` (${step.message})` : "";
4407
4450
  return ` ${icon} ${step.step}${detail}`;
4408
4451
  }
4452
+ async function writePackageJsonField(repoRoot, field, value) {
4453
+ const pkgPath = join(repoRoot, "package.json");
4454
+ let content;
4455
+ try {
4456
+ content = await readFile(pkgPath, "utf8");
4457
+ } catch {
4458
+ return false;
4459
+ }
4460
+ const pkg = JSON.parse(content);
4461
+ pkg[field] = value;
4462
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
4463
+ return true;
4464
+ }
4465
+ const README_DESC_START = "<!-- holocron:description -->";
4466
+ const README_DESC_END = "<!-- /holocron:description -->";
4467
+ async function updateReadmeDescription(repoRoot, description) {
4468
+ const readmePath = join(repoRoot, "README.md");
4469
+ let content;
4470
+ try {
4471
+ content = await readFile(readmePath, "utf8");
4472
+ } catch {
4473
+ return false;
4474
+ }
4475
+ const lines = content.split("\n");
4476
+ const startIdx = lines.findIndex((l) => l.trim() === README_DESC_START);
4477
+ const endIdx = lines.findIndex((l) => l.trim() === README_DESC_END);
4478
+ if (startIdx !== -1) {
4479
+ if (endIdx === -1 || endIdx <= startIdx) return false;
4480
+ lines.splice(startIdx + 1, endIdx - startIdx - 1, description);
4481
+ await writeFile(readmePath, lines.join("\n"), "utf8");
4482
+ return true;
4483
+ }
4484
+ const h1Index = lines.findIndex((l) => /^# /.test(l));
4485
+ if (h1Index === -1) return false;
4486
+ lines.splice(h1Index + 1, 0, "", README_DESC_START, description, README_DESC_END);
4487
+ await writeFile(readmePath, lines.join("\n"), "utf8");
4488
+ return true;
4489
+ }
4409
4490
  //#endregion
4410
4491
  //#region src/load-config.ts
4411
4492
  /**
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.57",
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",