@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 +4 -4
- package/dist/capabilities/index.d.mts +5 -0
- package/dist/cli.mjs +93 -9
- package/package.json +1 -1
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
|
|
60
|
-
|
|
61
|
-
| `name`
|
|
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
|
|
701
|
-
engines.node
|
|
702
|
-
|
|
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.
|
|
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",
|