@theholocron/cli 2.0.0-alpha.43 → 2.0.0-alpha.44

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
@@ -109,5 +109,5 @@ export default acmeConfig;
109
109
  **`v2.0.0-alpha.0`** — published on npm under the `alpha` dist-tag.
110
110
  [Release notes](https://github.com/theholocron/holocron/releases/tag/v2.0.0-alpha.0).
111
111
  Design in
112
- [`.notes/tech-architecture.spec.md`](../../.notes/tech-architecture.spec.md).
112
+ [`.notes/tech-architecture.spec.md`](../../.notes/archive/tech-architecture.spec.md).
113
113
  APIs may still shift before stable v2.0.0.
@@ -127,6 +127,16 @@ interface Source extends ProviderIdentity {
127
127
  * Optional — providers that have no label concept omit this.
128
128
  */
129
129
  syncLabels?(canonical: ReadonlyArray<LabelDef>, stale: ReadonlyArray<string>): Promise<string>;
130
+ /**
131
+ * Set org-level custom property values on the repo.
132
+ * Optional — providers that don't support custom properties omit this.
133
+ */
134
+ syncProperties?(values: Record<string, string>): Promise<string>;
135
+ /**
136
+ * Replace the repo's topic set with the supplied list.
137
+ * Optional — providers that don't support topics omit this.
138
+ */
139
+ syncTopics?(topics: string[]): Promise<string>;
130
140
  }
131
141
  type CiRunStatus = "queued" | "in_progress" | "completed" | "cancelled" | "failure" | "success" | "skipped";
132
142
  interface CiRun {
package/dist/cli.mjs CHANGED
@@ -7,7 +7,7 @@ import { ProviderApiError } from "@theholocron/http-client";
7
7
  import { Entry, findCredentials } from "@napi-rs/keyring";
8
8
  import { createHash } from "node:crypto";
9
9
  import { spawnSync } from "node:child_process";
10
- import { readFile, stat } from "node:fs/promises";
10
+ import { access, readFile, stat } from "node:fs/promises";
11
11
  import { pathToFileURL } from "node:url";
12
12
  //#region src/capabilities/index.ts
13
13
  const CARDINALITY = {
@@ -464,7 +464,8 @@ var PluginLoader = class {
464
464
  */
465
465
  projectDefaults() {
466
466
  const defaults = {};
467
- if (this.config.project.repo) defaults.repo = this.config.project.repo;
467
+ const repo = this.config.project.repo;
468
+ if (repo) defaults.repo = typeof repo === "string" ? repo : repo.name;
468
469
  return defaults;
469
470
  }
470
471
  };
@@ -3682,6 +3683,25 @@ function vaultProviderName(loader) {
3682
3683
  }
3683
3684
  //#endregion
3684
3685
  //#region src/commands/setup.ts
3686
+ /**
3687
+ * `holocron setup` — orchestrates per-capability setup actions across
3688
+ * every plugin loaded from `holocron.config.json`.
3689
+ *
3690
+ * Per CLAUDE.md soft-skip: each step is wrapped in a try/catch and
3691
+ * failures don't abort subsequent capabilities. The summary at the end
3692
+ * reports counts so the operator can see what worked + what didn't.
3693
+ *
3694
+ * Per the Standards: when `ctx.dryRun` is true, mutating calls are
3695
+ * replaced with "would" log lines. Read-only probes (e.g.,
3696
+ * `vault.list`) still run so the operator sees real state.
3697
+ *
3698
+ * The orchestrator knows about specific capability methods by name
3699
+ * (e.g., `source.enableVulnerabilityAlerts`). This deliberate coupling
3700
+ * makes the "what does setup do" contract explicit and concrete —
3701
+ * decoupling via a per-capability `setupSteps()` method would be more
3702
+ * extensible but pushes the same knowledge into N plugins instead of
3703
+ * one central place.
3704
+ */
3685
3705
  function editorconfigContent() {
3686
3706
  return [
3687
3707
  `# AUTO-GENERATED — do not edit directly.`,
@@ -4038,6 +4058,8 @@ async function runSetup(input) {
4038
4058
  const config = input.loaded.resolved;
4039
4059
  const dryRun = input.context.dryRun ?? false;
4040
4060
  const steps = [];
4061
+ const repo = config.project.repo;
4062
+ const effectivePreset = repo?.protection ?? config.project.repoPolicy?.preset;
4041
4063
  print(`Holocron setup — ${config.project.name}${dryRun ? " (dry-run)" : ""}`);
4042
4064
  print(` config: ${input.loaded.filepath}`);
4043
4065
  print("");
@@ -4062,21 +4084,19 @@ async function runSetup(input) {
4062
4084
  else return await source.enableCodeScanning();
4063
4085
  }));
4064
4086
  print(formatStep(steps[steps.length - 1]));
4065
- const policy = config.project.repoPolicy;
4066
- if (policy && policy.preset !== "none") {
4067
- const preset = policy.preset ?? "balanced";
4087
+ if (effectivePreset && effectivePreset !== "none") {
4068
4088
  steps.push(await runStep("source", "updateRepoSettings", dryRun, async () => {
4069
4089
  await source.updateRepoSettings(BALANCED_REPO_SETTINGS);
4070
4090
  }));
4071
4091
  print(formatStep(steps[steps.length - 1]));
4072
4092
  const configuredWorkflowNames = (config.project.workflows ?? []).map((entry) => typeof entry === "string" ? entry : entry.name);
4073
- const requiredChecks = preset === "strict" ? [
4093
+ const requiredChecks = effectivePreset === "strict" ? [
4074
4094
  "DCO",
4075
4095
  ...configuredWorkflowNames.flatMap((name) => {
4076
4096
  const ctx = WORKFLOW_CHECK_CONTEXTS[name];
4077
4097
  return ctx ? [ctx] : [];
4078
4098
  }),
4079
- ...policy.requiredChecks ?? []
4099
+ ...repo?.requiredChecks ?? config.project.repoPolicy?.requiredChecks ?? []
4080
4100
  ] : [];
4081
4101
  steps.push(await upsertBranchProtection(source, dryRun, requiredChecks));
4082
4102
  print(formatStep(steps[steps.length - 1]));
@@ -4112,7 +4132,7 @@ async function runSetup(input) {
4112
4132
  }));
4113
4133
  print(formatStep(steps[steps.length - 1]));
4114
4134
  }
4115
- if (loader.has("source") && config.project.repoPolicy?.preset !== "none") {
4135
+ if (loader.has("source") && effectivePreset !== "none") {
4116
4136
  const source = loader.get("source");
4117
4137
  steps.push(await runStep("source", "write .github/dependabot.yml", dryRun, async () => {
4118
4138
  await source.writeRepoFile(".github/dependabot.yml", DEPENDABOT_CONFIG);
@@ -4139,6 +4159,24 @@ async function runSetup(input) {
4139
4159
  }));
4140
4160
  print(formatStep(steps[steps.length - 1]));
4141
4161
  }
4162
+ const properties = {};
4163
+ if (effectivePreset && effectivePreset !== "none") properties["branch_protection_level"] = effectivePreset;
4164
+ const isMonorepo = await access(join(input.context.repoRoot, "pnpm-workspace.yaml")).then(() => true).catch(() => false);
4165
+ properties["monorepo"] = String(isMonorepo);
4166
+ const manual = repo?.properties ?? {};
4167
+ if (manual.lifecycle) properties["lifecycle"] = manual.lifecycle;
4168
+ if (manual.open_source !== void 0) properties["open_source"] = String(manual.open_source);
4169
+ if (manual.runtime_environment) properties["runtime_environment"] = manual.runtime_environment;
4170
+ if (manual.uses_external_packages !== void 0) properties["uses_external_packages"] = String(manual.uses_external_packages);
4171
+ if (source.syncProperties) {
4172
+ steps.push(await runStep("source", "sync properties", dryRun, () => source.syncProperties(properties)));
4173
+ print(formatStep(steps[steps.length - 1]));
4174
+ }
4175
+ const topics = repo?.topics ?? [];
4176
+ if (topics.length > 0 && source.syncTopics) {
4177
+ steps.push(await runStep("source", "sync topics", dryRun, () => source.syncTopics(topics)));
4178
+ print(formatStep(steps[steps.length - 1]));
4179
+ }
4142
4180
  }
4143
4181
  if (loader.has("environments")) {
4144
4182
  const envs = loader.get("environments");
package/dist/index.d.mts CHANGED
@@ -41,10 +41,36 @@ interface RepoPolicyConfig {
41
41
  * "none" — skips repo settings + ruleset entirely.
42
42
  *
43
43
  * @default "balanced"
44
+ * @deprecated Use `project.repo.protection` instead.
44
45
  */
45
46
  preset?: "balanced" | "strict" | "none";
46
- /** CI check context names required on the default branch (used by "strict"). */
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
+ type RepoProtection = "balanced" | "strict" | "none";
54
+ interface RepoProperties {
55
+ lifecycle?: "active" | "experimental" | "deprecated";
56
+ open_source?: boolean;
57
+ runtime_environment?: "node" | "browser" | "universal" | "none";
58
+ uses_external_packages?: boolean;
59
+ }
60
+ interface RepoConfig {
61
+ /** "owner/name" — the GitHub repository coordinate. */
62
+ name: string;
63
+ /**
64
+ * Branch protection preset applied by `holocron setup`. When omitted,
65
+ * no protection is applied and no `branch_protection_level` property is set.
66
+ */
67
+ protection?: RepoProtection;
68
+ /** CI check context names required on the default branch (only used when `protection` is "strict"). */
47
69
  requiredChecks?: string[];
70
+ /** GitHub topics set on the repository. */
71
+ topics?: string[];
72
+ /** GitHub custom properties synced to the org dashboard. */
73
+ properties?: RepoProperties;
48
74
  }
49
75
  interface AppConfig {
50
76
  name: string;
@@ -59,12 +85,12 @@ interface HolocronConfig {
59
85
  name: string;
60
86
  description?: string;
61
87
  /**
62
- * Repo coord `"owner/name"`. When set, `PluginLoader` injects
63
- * it into every plugin's `RuntimeContext.repo` so plugins that
64
- * need a repo (github, etc.) don't require `--repo` on every
65
- * invocation. `--repo` on the command line still overrides.
88
+ * Repository identity and metadata. When set, `PluginLoader` injects
89
+ * `repo.name` into every plugin's `RuntimeContext.repo` so plugins that
90
+ * need a repo (github, etc.) don't require `--repo` on every invocation.
91
+ * `--repo` on the command line still overrides.
66
92
  */
67
- repo?: string;
93
+ repo?: RepoConfig;
68
94
  /**
69
95
  * Repo-level policy applied by `holocron setup`. Defines merge
70
96
  * strategy, branch protection rulesets, and security defaults.
@@ -190,4 +216,4 @@ interface LoadedConfig {
190
216
  */
191
217
  declare function loadConfig(cwd: string): Promise<LoadedConfig>;
192
218
  //#endregion
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, RepoPolicyConfig, 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 };
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, RepoPolicyConfig, 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.43",
3
+ "version": "2.0.0-alpha.44",
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",