@theholocron/cli 2.0.0-alpha.44 → 2.0.0-alpha.45
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 -3
- package/dist/cli.mjs +165 -6
- package/dist/index.d.mts +1 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,13 +101,14 @@ export default acmeConfig;
|
|
|
101
101
|
- `src/loader.ts` — `PluginLoader` — dynamic-imports plugins, resolves
|
|
102
102
|
capability config packages, builds the capability registry
|
|
103
103
|
- `src/cli.ts` — yargs entry, dispatches subcommands
|
|
104
|
-
- `src/commands/` — `setup`, `doctor`, `deploy`, `secret set`,
|
|
105
|
-
`secrets sync`, `npm publish-initial`
|
|
104
|
+
- `src/commands/` — `setup`, `sync`, `doctor`, `deploy`, `secret set`,
|
|
105
|
+
`secrets sync`, `npm publish-initial`, `sync-github`, `upgrade node`,
|
|
106
|
+
`plugin create`, `auth`
|
|
106
107
|
|
|
107
108
|
## Status
|
|
108
109
|
|
|
109
110
|
**`v2.0.0-alpha.0`** — published on npm under the `alpha` dist-tag.
|
|
110
111
|
[Release notes](https://github.com/theholocron/holocron/releases/tag/v2.0.0-alpha.0).
|
|
111
112
|
Design in
|
|
112
|
-
[`.notes/tech-architecture.spec.md`](../../.notes/archive/tech-architecture.spec.md).
|
|
113
|
+
[`.notes/archive/tech-architecture.spec.md`](../../.notes/archive/tech-architecture.spec.md).
|
|
113
114
|
APIs may still shift before stable v2.0.0.
|
package/dist/cli.mjs
CHANGED
|
@@ -464,8 +464,7 @@ var PluginLoader = class {
|
|
|
464
464
|
*/
|
|
465
465
|
projectDefaults() {
|
|
466
466
|
const defaults = {};
|
|
467
|
-
|
|
468
|
-
if (repo) defaults.repo = typeof repo === "string" ? repo : repo.name;
|
|
467
|
+
if (this.config.project.repo) defaults.repo = this.config.project.repo.name;
|
|
469
468
|
return defaults;
|
|
470
469
|
}
|
|
471
470
|
};
|
|
@@ -1059,7 +1058,7 @@ jobs:
|
|
|
1059
1058
|
id: metadata
|
|
1060
1059
|
|
|
1061
1060
|
- run: gh pr merge --auto --squash "$PR_URL"
|
|
1062
|
-
# --squash is intentional:
|
|
1061
|
+
# --squash is intentional: repo protection sets allow_merge_commit: false,
|
|
1063
1062
|
# so --merge would fail on any repo using the standard preset.
|
|
1064
1063
|
name: Enable auto-merge for Dependabot PRs
|
|
1065
1064
|
if: steps.metadata.outputs.update-type == 'version-update:semver-patch'
|
|
@@ -4059,7 +4058,7 @@ async function runSetup(input) {
|
|
|
4059
4058
|
const dryRun = input.context.dryRun ?? false;
|
|
4060
4059
|
const steps = [];
|
|
4061
4060
|
const repo = config.project.repo;
|
|
4062
|
-
const effectivePreset = repo?.protection
|
|
4061
|
+
const effectivePreset = repo?.protection;
|
|
4063
4062
|
print(`Holocron setup — ${config.project.name}${dryRun ? " (dry-run)" : ""}`);
|
|
4064
4063
|
print(` config: ${input.loaded.filepath}`);
|
|
4065
4064
|
print("");
|
|
@@ -4096,7 +4095,7 @@ async function runSetup(input) {
|
|
|
4096
4095
|
const ctx = WORKFLOW_CHECK_CONTEXTS[name];
|
|
4097
4096
|
return ctx ? [ctx] : [];
|
|
4098
4097
|
}),
|
|
4099
|
-
...repo?.requiredChecks ??
|
|
4098
|
+
...repo?.requiredChecks ?? []
|
|
4100
4099
|
] : [];
|
|
4101
4100
|
steps.push(await upsertBranchProtection(source, dryRun, requiredChecks));
|
|
4102
4101
|
print(formatStep(steps[steps.length - 1]));
|
|
@@ -4310,6 +4309,148 @@ function formatStep(step) {
|
|
|
4310
4309
|
return ` ${icon} ${step.step}${detail}`;
|
|
4311
4310
|
}
|
|
4312
4311
|
//#endregion
|
|
4312
|
+
//#region src/commands/sync.ts
|
|
4313
|
+
const SYNC_STEPS = [
|
|
4314
|
+
"labels",
|
|
4315
|
+
"properties",
|
|
4316
|
+
"topics"
|
|
4317
|
+
];
|
|
4318
|
+
async function runSync(input) {
|
|
4319
|
+
const print = input.print ?? ((line) => console.log(line));
|
|
4320
|
+
const loader = input.loader ?? new PluginLoader(input.loaded.resolved, input.context);
|
|
4321
|
+
await loader.load();
|
|
4322
|
+
const config = input.loaded.resolved;
|
|
4323
|
+
const dryRun = input.context.dryRun ?? false;
|
|
4324
|
+
const requestedSteps = input.steps;
|
|
4325
|
+
const steps = [];
|
|
4326
|
+
print(`Holocron sync — ${config.project.name}${dryRun ? " (dry-run)" : ""}`);
|
|
4327
|
+
print(` config: ${input.loaded.filepath}`);
|
|
4328
|
+
print("");
|
|
4329
|
+
if (loader.has("source")) {
|
|
4330
|
+
const source = loader.get("source");
|
|
4331
|
+
print(" → source");
|
|
4332
|
+
for (const stepName of SYNC_STEPS) {
|
|
4333
|
+
if (requestedSteps !== void 0 && !requestedSteps.includes(stepName)) continue;
|
|
4334
|
+
if (stepName === "labels") if (source.syncLabels) {
|
|
4335
|
+
steps.push(await runSyncStep("source", "sync labels", dryRun, () => source.syncLabels(CANONICAL_LABELS, STALE_LABELS)));
|
|
4336
|
+
print(formatSyncStep(steps[steps.length - 1]));
|
|
4337
|
+
} else {
|
|
4338
|
+
steps.push({
|
|
4339
|
+
capability: "source",
|
|
4340
|
+
step: "sync labels",
|
|
4341
|
+
status: "skip",
|
|
4342
|
+
message: "provider does not implement syncLabels"
|
|
4343
|
+
});
|
|
4344
|
+
print(formatSyncStep(steps[steps.length - 1]));
|
|
4345
|
+
}
|
|
4346
|
+
if (stepName === "properties") if (source.syncProperties) {
|
|
4347
|
+
const repo = config.project.repo;
|
|
4348
|
+
const properties = {};
|
|
4349
|
+
const effectivePreset = repo?.protection;
|
|
4350
|
+
if (effectivePreset && effectivePreset !== "none") properties["branch_protection_level"] = effectivePreset;
|
|
4351
|
+
const isMonorepo = await access(join(input.context.repoRoot, "pnpm-workspace.yaml")).then(() => true).catch(() => false);
|
|
4352
|
+
properties["monorepo"] = String(isMonorepo);
|
|
4353
|
+
const manual = repo?.properties ?? {};
|
|
4354
|
+
if (manual.lifecycle) properties["lifecycle"] = manual.lifecycle;
|
|
4355
|
+
if (manual.open_source !== void 0) properties["open_source"] = String(manual.open_source);
|
|
4356
|
+
if (manual.runtime_environment) properties["runtime_environment"] = manual.runtime_environment;
|
|
4357
|
+
if (manual.uses_external_packages !== void 0) properties["uses_external_packages"] = String(manual.uses_external_packages);
|
|
4358
|
+
steps.push(await runSyncStep("source", "sync properties", dryRun, () => source.syncProperties(properties)));
|
|
4359
|
+
print(formatSyncStep(steps[steps.length - 1]));
|
|
4360
|
+
} else {
|
|
4361
|
+
steps.push({
|
|
4362
|
+
capability: "source",
|
|
4363
|
+
step: "sync properties",
|
|
4364
|
+
status: "skip",
|
|
4365
|
+
message: "provider does not implement syncProperties"
|
|
4366
|
+
});
|
|
4367
|
+
print(formatSyncStep(steps[steps.length - 1]));
|
|
4368
|
+
}
|
|
4369
|
+
if (stepName === "topics") {
|
|
4370
|
+
const topics = config.project.repo?.topics ?? [];
|
|
4371
|
+
if (topics.length === 0) {
|
|
4372
|
+
steps.push({
|
|
4373
|
+
capability: "source",
|
|
4374
|
+
step: "sync topics",
|
|
4375
|
+
status: "skip",
|
|
4376
|
+
message: "no topics configured"
|
|
4377
|
+
});
|
|
4378
|
+
print(formatSyncStep(steps[steps.length - 1]));
|
|
4379
|
+
} else if (source.syncTopics) {
|
|
4380
|
+
steps.push(await runSyncStep("source", "sync topics", dryRun, () => source.syncTopics(topics)));
|
|
4381
|
+
print(formatSyncStep(steps[steps.length - 1]));
|
|
4382
|
+
} else {
|
|
4383
|
+
steps.push({
|
|
4384
|
+
capability: "source",
|
|
4385
|
+
step: "sync topics",
|
|
4386
|
+
status: "skip",
|
|
4387
|
+
message: "provider does not implement syncTopics"
|
|
4388
|
+
});
|
|
4389
|
+
print(formatSyncStep(steps[steps.length - 1]));
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
}
|
|
4393
|
+
if (requestedSteps) {
|
|
4394
|
+
for (const name of requestedSteps) if (!SYNC_STEPS.includes(name)) {
|
|
4395
|
+
steps.push({
|
|
4396
|
+
capability: "source",
|
|
4397
|
+
step: `sync ${name}`,
|
|
4398
|
+
status: "skip",
|
|
4399
|
+
message: `unknown step "${name}"`
|
|
4400
|
+
});
|
|
4401
|
+
print(formatSyncStep(steps[steps.length - 1]));
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
}
|
|
4405
|
+
const summary = steps.reduce((acc, s) => {
|
|
4406
|
+
if (s.status === "ok") acc.ok += 1;
|
|
4407
|
+
else if (s.status === "fail") acc.fail += 1;
|
|
4408
|
+
else if (s.status === "skip") acc.skip += 1;
|
|
4409
|
+
else if (s.status === "dry-run") acc.dryRun += 1;
|
|
4410
|
+
return acc;
|
|
4411
|
+
}, {
|
|
4412
|
+
ok: 0,
|
|
4413
|
+
fail: 0,
|
|
4414
|
+
skip: 0,
|
|
4415
|
+
dryRun: 0
|
|
4416
|
+
});
|
|
4417
|
+
print("");
|
|
4418
|
+
print(` ${summary.ok} ok, ${summary.fail} fail, ${summary.skip} skipped${dryRun ? `, ${summary.dryRun} would-do` : ""}`);
|
|
4419
|
+
return {
|
|
4420
|
+
steps,
|
|
4421
|
+
summary
|
|
4422
|
+
};
|
|
4423
|
+
}
|
|
4424
|
+
async function runSyncStep(capability, step, dryRun, body) {
|
|
4425
|
+
if (dryRun) return {
|
|
4426
|
+
capability,
|
|
4427
|
+
step,
|
|
4428
|
+
status: "dry-run"
|
|
4429
|
+
};
|
|
4430
|
+
try {
|
|
4431
|
+
const note = await body();
|
|
4432
|
+
const result = {
|
|
4433
|
+
capability,
|
|
4434
|
+
step,
|
|
4435
|
+
status: "ok"
|
|
4436
|
+
};
|
|
4437
|
+
if (typeof note === "string") result.message = note;
|
|
4438
|
+
return result;
|
|
4439
|
+
} catch (err) {
|
|
4440
|
+
return {
|
|
4441
|
+
capability,
|
|
4442
|
+
step,
|
|
4443
|
+
status: "fail",
|
|
4444
|
+
message: err instanceof Error ? err.message : String(err)
|
|
4445
|
+
};
|
|
4446
|
+
}
|
|
4447
|
+
}
|
|
4448
|
+
function formatSyncStep(step) {
|
|
4449
|
+
const icon = step.status === "ok" ? "✓" : step.status === "fail" ? "✗" : step.status === "dry-run" ? "…" : "·";
|
|
4450
|
+
const detail = step.message ? ` (${step.message})` : "";
|
|
4451
|
+
return ` ${icon} ${step.step}${detail}`;
|
|
4452
|
+
}
|
|
4453
|
+
//#endregion
|
|
4313
4454
|
//#region src/load-config.ts
|
|
4314
4455
|
/**
|
|
4315
4456
|
* `holocron.config.{json,js,ts}` file loader.
|
|
@@ -4532,7 +4673,25 @@ await yargs(hideBin(process.argv)).scriptName("holocron").usage("$0 <command> [o
|
|
|
4532
4673
|
dryRun: argv.dryRun,
|
|
4533
4674
|
...argv.otp ? { otp: argv.otp } : {}
|
|
4534
4675
|
})).status === "fail") process.exitCode = 1;
|
|
4535
|
-
}).demandCommand(1, "Run `holocron npm --help` to see available npm subcommands."), () => {}).command("sync
|
|
4676
|
+
}).demandCommand(1, "Run `holocron npm --help` to see available npm subcommands."), () => {}).command("sync [steps..]", "Sync source-level state (labels, properties, topics) from config to the provider", (y) => y.positional("steps", {
|
|
4677
|
+
type: "string",
|
|
4678
|
+
array: true,
|
|
4679
|
+
describe: "Steps to run: labels, properties, topics (default: all)"
|
|
4680
|
+
}).option("repo", {
|
|
4681
|
+
type: "string",
|
|
4682
|
+
describe: "Repo coords (\"owner/name\"). Defaults to plugin-specific resolution."
|
|
4683
|
+
}), async (argv) => {
|
|
4684
|
+
if ((await runSync({
|
|
4685
|
+
loaded: await loadConfig(argv.cwd),
|
|
4686
|
+
context: {
|
|
4687
|
+
repoRoot: argv.cwd,
|
|
4688
|
+
dryRun: argv.dryRun,
|
|
4689
|
+
...argv.repo ? { repo: argv.repo } : {},
|
|
4690
|
+
...argv.token ? { cliToken: argv.token } : {}
|
|
4691
|
+
},
|
|
4692
|
+
...argv.steps && argv.steps.length > 0 ? { steps: argv.steps } : {}
|
|
4693
|
+
})).summary.fail > 0) process.exitCode = 1;
|
|
4694
|
+
}).command("sync-github", "Sync workflow templates and composite actions to theholocron/.github via the GitHub API", (y) => y.option("repo", {
|
|
4536
4695
|
type: "string",
|
|
4537
4696
|
default: "theholocron/.github",
|
|
4538
4697
|
describe: "Target org/repo (default: theholocron/.github)"
|
package/dist/index.d.mts
CHANGED
|
@@ -30,26 +30,6 @@ type SingleEntry = string | [provider: string, options: ProviderOptions];
|
|
|
30
30
|
type MultiEntry = Array<string | [provider: string, options: ProviderOptions]>;
|
|
31
31
|
type RawProviderEntry = SingleEntry | MultiEntry;
|
|
32
32
|
type RawProvidersConfig = Partial<Record<CapabilityKey, RawProviderEntry>>;
|
|
33
|
-
interface RepoPolicyConfig {
|
|
34
|
-
/**
|
|
35
|
-
* "balanced" — squash-only merges, delete-branch-on-merge, issues/discussions/projects
|
|
36
|
-
* enabled, auto-merge enabled, web sign-off required, always suggest updating, no wiki;
|
|
37
|
-
* plus a ruleset that blocks force-push + deletion and requires a pull request (0 reviews).
|
|
38
|
-
*
|
|
39
|
-
* "strict" — everything in "balanced" plus required status checks from `requiredChecks`.
|
|
40
|
-
*
|
|
41
|
-
* "none" — skips repo settings + ruleset entirely.
|
|
42
|
-
*
|
|
43
|
-
* @default "balanced"
|
|
44
|
-
* @deprecated Use `project.repo.protection` instead.
|
|
45
|
-
*/
|
|
46
|
-
preset?: "balanced" | "strict" | "none";
|
|
47
|
-
/**
|
|
48
|
-
* CI check context names required on the default branch (used by "strict").
|
|
49
|
-
* @deprecated Use `project.repo.requiredChecks` instead.
|
|
50
|
-
*/
|
|
51
|
-
requiredChecks?: string[];
|
|
52
|
-
}
|
|
53
33
|
type RepoProtection = "balanced" | "strict" | "none";
|
|
54
34
|
interface RepoProperties {
|
|
55
35
|
lifecycle?: "active" | "experimental" | "deprecated";
|
|
@@ -91,12 +71,6 @@ interface HolocronConfig {
|
|
|
91
71
|
* `--repo` on the command line still overrides.
|
|
92
72
|
*/
|
|
93
73
|
repo?: RepoConfig;
|
|
94
|
-
/**
|
|
95
|
-
* Repo-level policy applied by `holocron setup`. Defines merge
|
|
96
|
-
* strategy, branch protection rulesets, and security defaults.
|
|
97
|
-
* Requires `source` capability to be configured.
|
|
98
|
-
*/
|
|
99
|
-
repoPolicy?: RepoPolicyConfig;
|
|
100
74
|
/**
|
|
101
75
|
* CI workflow names to install as thin wrappers during `holocron setup`.
|
|
102
76
|
* Each name maps to a reusable workflow in `theholocron/.github`.
|
|
@@ -216,4 +190,4 @@ interface LoadedConfig {
|
|
|
216
190
|
*/
|
|
217
191
|
declare function loadConfig(cwd: string): Promise<LoadedConfig>;
|
|
218
192
|
//#endregion
|
|
219
|
-
export { Analytics, AppConfig, Auth, AuthDescription, AuthError, AuthEvent, AuthEventType, AuthIdentity, AuthUser, CARDINALITY, CapabilityConfigPackage, CapabilityImpls, CapabilityKey, Cardinality, CardinalityFor, Ci, CiRun, CiRunFilter, CiRunStatus, ConfigError, ConfigFileError, ConnectionStringOptions, CreateAuthUserInput, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, Dns, DnsRecord, DnsRecordType, DoctorConfig, EnsureResult, Environment, EnvironmentReviewer, Environments, HolocronConfig, Issue, IssueSearchFilter, Issues, LabelDef, LifecycleResult, LifecycleSlot, LoadedConfig, MultiEntry, NormalizedAuthUser, Notifications, Observability, ParseWebhookInput, ProviderApiError, ProviderIdentity, ProviderOptions, REQUIRED_CAPABILITIES, RawProviderEntry, RawProvidersConfig, RepoConfig,
|
|
193
|
+
export { Analytics, AppConfig, Auth, AuthDescription, AuthError, AuthEvent, AuthEventType, AuthIdentity, AuthUser, CARDINALITY, CapabilityConfigPackage, CapabilityImpls, CapabilityKey, Cardinality, CardinalityFor, Ci, CiRun, CiRunFilter, CiRunStatus, ConfigError, ConfigFileError, ConnectionStringOptions, CreateAuthUserInput, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, Dns, DnsRecord, DnsRecordType, DoctorConfig, EnsureResult, Environment, EnvironmentReviewer, Environments, HolocronConfig, Issue, IssueSearchFilter, Issues, LabelDef, LifecycleResult, LifecycleSlot, LoadedConfig, MultiEntry, NormalizedAuthUser, Notifications, Observability, ParseWebhookInput, ProviderApiError, ProviderIdentity, ProviderOptions, REQUIRED_CAPABILITIES, RawProviderEntry, RawProvidersConfig, RepoConfig, RepoProperties, RepoProtection, RepoRef, RepoSettings, type RequestOptions, ResolveTokenConfig, type ResolveTokenInput, ResolvedCapability, ResolvedHolocronConfig, ResolvedProviderEntry, ResolvedProvidersConfig, ResolvedTuple, type RestClient, type RestClientConfig, Ruleset, SecretScope, Secrets, SingleEntry, Source, StatusCategory, Storage, StorageBranch, Tooling, ToolingDoctorReport, TrackerDoctorReport, TrackerUser, Vault, WebhookDashboardInfo, WebhookVerificationError, createResolveToken, createRestClient, defineConfig, deleteToken, getToken, isMulti, listStoredProviders, loadConfig, resolveConfig, resolveEntry, resolvePluginPackage, setToken };
|
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.45",
|
|
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",
|