@theholocron/cli 2.0.0-alpha.1 → 2.0.0-alpha.11

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/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as Tooling, A as Environments, B as ProviderApiError, C as DeploymentTarget, D as DnsRecordType, E as DnsRecord, F as LifecycleSlot, G as ResolvedCapability, H as REQUIRED_CAPABILITIES, I as NormalizedAuthUser, J as Secrets, K as Ruleset, L as Notifications, M as IssueSearchFilter, N as Issues, O as Environment, P as LifecycleResult, Q as StorageBranch, R as Observability, S as DeploymentRecord, T as Dns, U as RepoRef, V as ProviderIdentity, W as RepoSettings, X as StatusCategory, Y as Source, Z as Storage, _ as ConnectionStringOptions, a as AuthEventType, at as WebhookVerificationError, b as DeploymentProject, c as CARDINALITY, d as Cardinality, et as ToolingDoctorReport, f as CardinalityFor, g as CiRunStatus, h as CiRunFilter, i as AuthEvent, it as WebhookDashboardInfo, j as Issue, k as EnvironmentReviewer, l as CapabilityImpls, m as CiRun, n as Auth, nt as TrackerUser, o as AuthIdentity, ot as isMulti, p as Ci, q as SecretScope, r as AuthDescription, rt as Vault, s as AuthUser, t as Analytics, tt as TrackerDoctorReport, u as CapabilityKey, v as CreateAuthUserInput, w as DeploymentTrigger, x as DeploymentProjectSettings, y as Deployment, z as ParseWebhookInput } from "./index-CSxf0Yc7.mjs";
1
+ import { Analytics, Auth, AuthDescription, AuthEvent, AuthEventType, AuthIdentity, AuthUser, CARDINALITY, CapabilityImpls, CapabilityKey, Cardinality, CardinalityFor, Ci, CiRun, CiRunFilter, CiRunStatus, ConnectionStringOptions, CreateAuthUserInput, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, Dns, DnsRecord, DnsRecordType, EnsureResult, Environment, EnvironmentReviewer, Environments, Issue, IssueSearchFilter, Issues, LifecycleResult, LifecycleSlot, NormalizedAuthUser, Notifications, Observability, ParseWebhookInput, ProviderApiError, ProviderIdentity, REQUIRED_CAPABILITIES, RepoRef, RepoSettings, ResolvedCapability, Ruleset, SecretScope, Secrets, Source, StatusCategory, Storage, StorageBranch, Tooling, ToolingDoctorReport, TrackerDoctorReport, TrackerUser, Vault, WebhookDashboardInfo, WebhookVerificationError, isMulti } from "./capabilities/index.mjs";
2
2
 
3
3
  //#region src/config.d.ts
4
4
  type ProviderOptions = Record<string, unknown>;
@@ -6,6 +6,22 @@ type SingleEntry = string | [provider: string, options: ProviderOptions];
6
6
  type MultiEntry = Array<string | [provider: string, options: ProviderOptions]>;
7
7
  type RawProviderEntry = SingleEntry | MultiEntry;
8
8
  type RawProvidersConfig = Partial<Record<CapabilityKey, RawProviderEntry>>;
9
+ interface RepoPolicyConfig {
10
+ /**
11
+ * "balanced" — squash-only merges, delete-branch-on-merge, issues/discussions/projects
12
+ * enabled, auto-merge enabled, web sign-off required, always suggest updating, no wiki;
13
+ * plus a ruleset that blocks force-push + deletion and requires a pull request (0 reviews).
14
+ *
15
+ * "strict" — everything in "balanced" plus required status checks from `requiredChecks`.
16
+ *
17
+ * "none" — skips repo settings + ruleset entirely.
18
+ *
19
+ * @default "balanced"
20
+ */
21
+ preset?: "balanced" | "strict" | "none";
22
+ /** CI check context names required on the default branch (used by "strict"). */
23
+ requiredChecks?: string[];
24
+ }
9
25
  interface AppConfig {
10
26
  name: string;
11
27
  path: string;
@@ -18,6 +34,38 @@ interface HolocronConfig {
18
34
  project: {
19
35
  name: string;
20
36
  description?: string;
37
+ /**
38
+ * Repo coord — `"owner/name"`. When set, `PluginLoader` injects
39
+ * it into every plugin's `RuntimeContext.repo` so plugins that
40
+ * need a repo (github, etc.) don't require `--repo` on every
41
+ * invocation. `--repo` on the command line still overrides.
42
+ */
43
+ repo?: string;
44
+ /**
45
+ * Repo-level policy applied by `holocron setup`. Defines merge
46
+ * strategy, branch protection rulesets, and security defaults.
47
+ * Requires `source` capability to be configured.
48
+ */
49
+ repoPolicy?: RepoPolicyConfig;
50
+ /**
51
+ * CI workflow names to install as thin wrappers during `holocron setup`.
52
+ * Each name maps to a reusable workflow in `theholocron/.github`.
53
+ * Use the object form to pass `with:` inputs to the reusable workflow.
54
+ *
55
+ * Supported values: "lint" | "test" | "typecheck" | "codeql" | "review" |
56
+ * "release" | "stale" | "greetings" | "dependencies" | "bookkeeping-pr" | "audit"
57
+ *
58
+ * `holocron setup` writes `.github/workflows/<name>.yml` for each entry,
59
+ * calling the corresponding `ci-<name>.yml@main` reusable workflow.
60
+ * Files are overwritten on each run — they are generated artifacts.
61
+ *
62
+ * @example
63
+ * ["lint", { "name": "release", "with": { "run-build": false } }]
64
+ */
65
+ workflows?: Array<string | {
66
+ name: string;
67
+ with?: Record<string, unknown>;
68
+ }>;
21
69
  };
22
70
  providers: RawProvidersConfig;
23
71
  apps?: AppConfig[];
@@ -55,4 +103,48 @@ declare function resolvePluginPackage(provider: string): string;
55
103
  declare function resolveEntry(key: CapabilityKey, raw: RawProviderEntry): ResolvedProviderEntry;
56
104
  declare function resolveConfig(raw: HolocronConfig): ResolvedHolocronConfig;
57
105
  //#endregion
58
- export { Analytics, AppConfig, Auth, AuthDescription, AuthEvent, AuthEventType, AuthIdentity, AuthUser, CARDINALITY, CapabilityImpls, CapabilityKey, Cardinality, CardinalityFor, Ci, CiRun, CiRunFilter, CiRunStatus, ConfigError, ConnectionStringOptions, CreateAuthUserInput, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, Dns, DnsRecord, DnsRecordType, DoctorConfig, Environment, EnvironmentReviewer, Environments, HolocronConfig, Issue, IssueSearchFilter, Issues, LifecycleResult, LifecycleSlot, MultiEntry, NormalizedAuthUser, Notifications, Observability, ParseWebhookInput, ProviderApiError, ProviderIdentity, ProviderOptions, REQUIRED_CAPABILITIES, RawProviderEntry, RawProvidersConfig, RepoRef, RepoSettings, ResolvedCapability, ResolvedHolocronConfig, ResolvedProviderEntry, ResolvedProvidersConfig, ResolvedTuple, Ruleset, SecretScope, Secrets, SingleEntry, Source, StatusCategory, Storage, StorageBranch, Tooling, ToolingDoctorReport, TrackerDoctorReport, TrackerUser, Vault, WebhookDashboardInfo, WebhookVerificationError, isMulti, resolveConfig, resolveEntry, resolvePluginPackage };
106
+ //#region src/keyring.d.ts
107
+ /**
108
+ * Keyring-backed bootstrap credential store.
109
+ *
110
+ * Every holocron plugin's bootstrap token (the one it needs before it
111
+ * can talk to its vendor's API) can be stored in the OS keyring under
112
+ * a single reverse-DNS service scope. Managed via `holocron auth`
113
+ * subcommands; consulted at position 4 in every plugin's auth
114
+ * precedence chain (after --token / HOLOCRON_<X>_TOKEN / <native>_TOKEN).
115
+ *
116
+ * See `.notes/tech-auth-bootstrap.spec.md` for the design rationale.
117
+ *
118
+ * Failure model: keyring access is best-effort. Platforms without a
119
+ * supported credential store (some Linux CI images, sandboxed
120
+ * environments) will throw from the underlying library. Every export
121
+ * here catches and returns a null/empty result rather than propagating
122
+ * — the plugin's precedence chain then falls through to
123
+ * env-var-only paths, which is exactly how CI is meant to work.
124
+ */
125
+ /**
126
+ * Store or overwrite a bootstrap token for a provider. Returns true on
127
+ * success, false when the underlying keyring is unsupported or errored.
128
+ */
129
+ declare function setToken(provider: string, token: string): boolean;
130
+ /**
131
+ * Read the bootstrap token for a provider. Returns `null` for both
132
+ * "not stored" and "keyring unavailable" — callers can treat them the
133
+ * same way (fall through to env-var precedence).
134
+ */
135
+ declare function getToken(provider: string): string | null;
136
+ /**
137
+ * Delete a stored token. Returns true when a token was removed, false
138
+ * when there was nothing to delete or the keyring is unavailable.
139
+ * Distinguishing the two cases isn't worth the surface area — the
140
+ * command output makes the situation clear either way.
141
+ */
142
+ declare function deleteToken(provider: string): boolean;
143
+ /**
144
+ * List provider slugs with a stored token in this service scope.
145
+ * Uses the library's `findCredentials(service)` — supported on all
146
+ * platforms the underlying credential store supports.
147
+ */
148
+ declare function listStoredProviders(): string[];
149
+ //#endregion
150
+ export { Analytics, AppConfig, Auth, AuthDescription, AuthEvent, AuthEventType, AuthIdentity, AuthUser, CARDINALITY, CapabilityImpls, CapabilityKey, Cardinality, CardinalityFor, Ci, CiRun, CiRunFilter, CiRunStatus, ConfigError, ConnectionStringOptions, CreateAuthUserInput, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, Dns, DnsRecord, DnsRecordType, DoctorConfig, EnsureResult, Environment, EnvironmentReviewer, Environments, HolocronConfig, Issue, IssueSearchFilter, Issues, LifecycleResult, LifecycleSlot, MultiEntry, NormalizedAuthUser, Notifications, Observability, ParseWebhookInput, ProviderApiError, ProviderIdentity, ProviderOptions, REQUIRED_CAPABILITIES, RawProviderEntry, RawProvidersConfig, RepoPolicyConfig, RepoRef, RepoSettings, ResolvedCapability, ResolvedHolocronConfig, ResolvedProviderEntry, ResolvedProvidersConfig, ResolvedTuple, Ruleset, SecretScope, Secrets, SingleEntry, Source, StatusCategory, Storage, StorageBranch, Tooling, ToolingDoctorReport, TrackerDoctorReport, TrackerUser, Vault, WebhookDashboardInfo, WebhookVerificationError, deleteToken, getToken, isMulti, listStoredProviders, resolveConfig, resolveEntry, resolvePluginPackage, setToken };
package/dist/index.mjs CHANGED
@@ -1,3 +1,184 @@
1
- import { a as isMulti, i as WebhookVerificationError, n as ProviderApiError, r as REQUIRED_CAPABILITIES, t as CARDINALITY } from "./capabilities-QjjhVlDd.mjs";
2
- import { i as resolvePluginPackage, n as resolveConfig, r as resolveEntry, t as ConfigError } from "./config-DWlIfFZm.mjs";
3
- export { CARDINALITY, ConfigError, ProviderApiError, REQUIRED_CAPABILITIES, WebhookVerificationError, isMulti, resolveConfig, resolveEntry, resolvePluginPackage };
1
+ import { CARDINALITY, ProviderApiError, REQUIRED_CAPABILITIES, WebhookVerificationError, isMulti } from "./capabilities/index.mjs";
2
+ import { Entry, findCredentials } from "@napi-rs/keyring";
3
+ //#region src/config.ts
4
+ /**
5
+ * `holocron.config.json` schema, parser, and provider resolution.
6
+ *
7
+ * ESLint-style entry forms:
8
+ *
9
+ * "source": "github" ← single, short
10
+ * "deployment": ["vercel", { team: "rando" }] ← single, with options
11
+ * "notifications": ["slack", "discord"] ← multi, short
12
+ * "notifications": [
13
+ * ["slack", { channel: "#ops" }],
14
+ * ["discord", { webhook: "env:HOOK" }]
15
+ * ] ← multi, with options
16
+ *
17
+ * Discriminator: an array entry is a `[provider, options]` tuple when
18
+ * the length is 2 AND element[1] is a non-array, non-null object.
19
+ * Otherwise it's a multi-provider list (string[] or tuple[]).
20
+ *
21
+ * Validation rules:
22
+ * - `vault` is REQUIRED (every project has secrets somewhere)
23
+ * - Entries for `'many'` capabilities are normalized to an array of
24
+ * normalized tuples; entries for `'single'` capabilities are
25
+ * normalized to one tuple
26
+ * - Tokens / secret values never appear in config — providers read
27
+ * them from env (or pull from `vault` at runtime)
28
+ */
29
+ var ConfigError = class extends Error {
30
+ name = "ConfigError";
31
+ };
32
+ const PLUGIN_PREFIX = "@theholocron/holocron-plugin-";
33
+ const COMMUNITY_PREFIX = "holocron-plugin-";
34
+ /**
35
+ * Resolve `"github"` → `"@theholocron/holocron-plugin-github"`.
36
+ * Fully-qualified names (scoped or not) are honored verbatim, which
37
+ * is how third-party plugins published outside the org work.
38
+ */
39
+ function resolvePluginPackage(provider) {
40
+ if (!provider) throw new ConfigError("provider name is empty");
41
+ if (provider.startsWith("@")) return provider;
42
+ if (provider.startsWith(COMMUNITY_PREFIX)) return provider;
43
+ if (provider.includes("/")) return provider;
44
+ return PLUGIN_PREFIX + provider;
45
+ }
46
+ /** A bare `[provider, options]` tuple, with both elements present? */
47
+ function isOptionsTuple(value) {
48
+ if (!Array.isArray(value)) return false;
49
+ if (value.length !== 2) return false;
50
+ if (typeof value[0] !== "string") return false;
51
+ const opt = value[1];
52
+ return typeof opt === "object" && opt !== null && !Array.isArray(opt);
53
+ }
54
+ function normalizeEntry(entry) {
55
+ if (typeof entry === "string") return {
56
+ provider: entry,
57
+ packageName: resolvePluginPackage(entry),
58
+ options: {}
59
+ };
60
+ const [provider, options] = entry;
61
+ return {
62
+ provider,
63
+ packageName: resolvePluginPackage(provider),
64
+ options
65
+ };
66
+ }
67
+ function resolveEntry(key, raw) {
68
+ const cardinality = CARDINALITY[key];
69
+ if (typeof raw === "string") {
70
+ if (cardinality === "many") throw new ConfigError(`\`${key}\` accepts multiple providers; wrap a single one in an array: ["${raw}"]`);
71
+ return {
72
+ cardinality: "single",
73
+ tuple: normalizeEntry(raw)
74
+ };
75
+ }
76
+ if (!Array.isArray(raw)) throw new ConfigError(`\`${key}\` entry must be a string or array, got ${typeof raw}`);
77
+ if (isOptionsTuple(raw)) {
78
+ if (cardinality === "many") return {
79
+ cardinality: "many",
80
+ tuples: [normalizeEntry(raw)]
81
+ };
82
+ return {
83
+ cardinality: "single",
84
+ tuple: normalizeEntry(raw)
85
+ };
86
+ }
87
+ if (cardinality === "single") throw new ConfigError(`\`${key}\` accepts exactly one provider; got a multi-provider list with ${raw.length} entries`);
88
+ return {
89
+ cardinality: "many",
90
+ tuples: raw.map((entry, idx) => {
91
+ if (typeof entry === "string") return normalizeEntry(entry);
92
+ if (isOptionsTuple(entry)) return normalizeEntry(entry);
93
+ throw new ConfigError(`\`${key}[${idx}]\` must be a provider string or [provider, options] tuple`);
94
+ })
95
+ };
96
+ }
97
+ function resolveConfig(raw) {
98
+ if (!raw.project?.name) throw new ConfigError("`project.name` is required");
99
+ if (!raw.providers || typeof raw.providers !== "object") throw new ConfigError("`providers` block is required");
100
+ const providers = {};
101
+ for (const [key, entry] of Object.entries(raw.providers)) {
102
+ if (entry === void 0) continue;
103
+ providers[key] = resolveEntry(key, entry);
104
+ }
105
+ for (const required of REQUIRED_CAPABILITIES) if (!providers[required]) throw new ConfigError(`required capability \`${required}\` is missing from providers`);
106
+ return {
107
+ project: raw.project,
108
+ providers,
109
+ apps: raw.apps ?? [],
110
+ doctor: raw.doctor ?? {}
111
+ };
112
+ }
113
+ //#endregion
114
+ //#region src/keyring.ts
115
+ /**
116
+ * Keyring-backed bootstrap credential store.
117
+ *
118
+ * Every holocron plugin's bootstrap token (the one it needs before it
119
+ * can talk to its vendor's API) can be stored in the OS keyring under
120
+ * a single reverse-DNS service scope. Managed via `holocron auth`
121
+ * subcommands; consulted at position 4 in every plugin's auth
122
+ * precedence chain (after --token / HOLOCRON_<X>_TOKEN / <native>_TOKEN).
123
+ *
124
+ * See `.notes/tech-auth-bootstrap.spec.md` for the design rationale.
125
+ *
126
+ * Failure model: keyring access is best-effort. Platforms without a
127
+ * supported credential store (some Linux CI images, sandboxed
128
+ * environments) will throw from the underlying library. Every export
129
+ * here catches and returns a null/empty result rather than propagating
130
+ * — the plugin's precedence chain then falls through to
131
+ * env-var-only paths, which is exactly how CI is meant to work.
132
+ */
133
+ const SERVICE = "com.theholocron.cli";
134
+ /**
135
+ * Store or overwrite a bootstrap token for a provider. Returns true on
136
+ * success, false when the underlying keyring is unsupported or errored.
137
+ */
138
+ function setToken(provider, token) {
139
+ try {
140
+ new Entry(SERVICE, provider).setPassword(token);
141
+ return true;
142
+ } catch {
143
+ return false;
144
+ }
145
+ }
146
+ /**
147
+ * Read the bootstrap token for a provider. Returns `null` for both
148
+ * "not stored" and "keyring unavailable" — callers can treat them the
149
+ * same way (fall through to env-var precedence).
150
+ */
151
+ function getToken(provider) {
152
+ try {
153
+ return new Entry(SERVICE, provider).getPassword();
154
+ } catch {
155
+ return null;
156
+ }
157
+ }
158
+ /**
159
+ * Delete a stored token. Returns true when a token was removed, false
160
+ * when there was nothing to delete or the keyring is unavailable.
161
+ * Distinguishing the two cases isn't worth the surface area — the
162
+ * command output makes the situation clear either way.
163
+ */
164
+ function deleteToken(provider) {
165
+ try {
166
+ return new Entry(SERVICE, provider).deletePassword();
167
+ } catch {
168
+ return false;
169
+ }
170
+ }
171
+ /**
172
+ * List provider slugs with a stored token in this service scope.
173
+ * Uses the library's `findCredentials(service)` — supported on all
174
+ * platforms the underlying credential store supports.
175
+ */
176
+ function listStoredProviders() {
177
+ try {
178
+ return findCredentials(SERVICE).map((c) => c.account);
179
+ } catch {
180
+ return [];
181
+ }
182
+ }
183
+ //#endregion
184
+ export { CARDINALITY, ConfigError, ProviderApiError, REQUIRED_CAPABILITIES, WebhookVerificationError, deleteToken, getToken, isMulti, listStoredProviders, 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.1",
3
+ "version": "2.0.0-alpha.11",
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",
@@ -33,8 +33,8 @@
33
33
  "README.md"
34
34
  ],
35
35
  "dependencies": {
36
- "yargs": "^18.0.0",
37
- "@theholocron/cli-utils": "0.0.0"
36
+ "@napi-rs/keyring": "^1.3.0",
37
+ "yargs": "^18.0.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@theholocron/eslint-config": "^4.1.0",
@@ -46,7 +46,8 @@
46
46
  "globals": "^16.5.0",
47
47
  "tsdown": "^0.22.3",
48
48
  "typescript": "^5.9.3",
49
- "vitest": "^3.2.6"
49
+ "vitest": "^3.2.6",
50
+ "@theholocron/cli-utils": "0.0.0"
50
51
  },
51
52
  "publishConfig": {
52
53
  "access": "public"
@@ -1,43 +0,0 @@
1
- //#region src/capabilities/index.ts
2
- const CARDINALITY = {
3
- source: "single",
4
- ci: "single",
5
- secrets: "single",
6
- environments: "single",
7
- issues: "single",
8
- deployment: "single",
9
- storage: "single",
10
- auth: "single",
11
- vault: "single",
12
- dns: "single",
13
- tooling: "many",
14
- notifications: "many",
15
- analytics: "many",
16
- observability: "many"
17
- };
18
- /** Vault is required; everything else is optional in the config. */
19
- const REQUIRED_CAPABILITIES = ["vault"];
20
- /**
21
- * Surfaced from every capability call that hits a vendor API. Wraps
22
- * the underlying error with `status` (HTTP) and `details` so
23
- * orchestrators (`holocron setup`, `doctor`) can soft-skip rather
24
- * than abort.
25
- */
26
- var ProviderApiError = class extends Error {
27
- status;
28
- details;
29
- name = "ProviderApiError";
30
- constructor(message, status, details) {
31
- super(message);
32
- this.status = status;
33
- this.details = details;
34
- }
35
- };
36
- var WebhookVerificationError = class extends Error {
37
- name = "WebhookVerificationError";
38
- };
39
- function isMulti(key) {
40
- return CARDINALITY[key] === "many";
41
- }
42
- //#endregion
43
- export { isMulti as a, WebhookVerificationError as i, ProviderApiError as n, REQUIRED_CAPABILITIES as r, CARDINALITY as t };
package/dist/cli.d.mts DELETED
@@ -1 +0,0 @@
1
- export { };
@@ -1,113 +0,0 @@
1
- import { r as REQUIRED_CAPABILITIES, t as CARDINALITY } from "./capabilities-QjjhVlDd.mjs";
2
- //#region src/config.ts
3
- /**
4
- * `holocron.config.json` schema, parser, and provider resolution.
5
- *
6
- * ESLint-style entry forms:
7
- *
8
- * "source": "github" ← single, short
9
- * "deployment": ["vercel", { team: "rando" }] ← single, with options
10
- * "notifications": ["slack", "discord"] ← multi, short
11
- * "notifications": [
12
- * ["slack", { channel: "#ops" }],
13
- * ["discord", { webhook: "env:HOOK" }]
14
- * ] ← multi, with options
15
- *
16
- * Discriminator: an array entry is a `[provider, options]` tuple when
17
- * the length is 2 AND element[1] is a non-array, non-null object.
18
- * Otherwise it's a multi-provider list (string[] or tuple[]).
19
- *
20
- * Validation rules:
21
- * - `vault` is REQUIRED (every project has secrets somewhere)
22
- * - Entries for `'many'` capabilities are normalized to an array of
23
- * normalized tuples; entries for `'single'` capabilities are
24
- * normalized to one tuple
25
- * - Tokens / secret values never appear in config — providers read
26
- * them from env (or pull from `vault` at runtime)
27
- */
28
- var ConfigError = class extends Error {
29
- name = "ConfigError";
30
- };
31
- const PLUGIN_PREFIX = "@theholocron/holocron-plugin-";
32
- const COMMUNITY_PREFIX = "holocron-plugin-";
33
- /**
34
- * Resolve `"github"` → `"@theholocron/holocron-plugin-github"`.
35
- * Fully-qualified names (scoped or not) are honored verbatim, which
36
- * is how third-party plugins published outside the org work.
37
- */
38
- function resolvePluginPackage(provider) {
39
- if (!provider) throw new ConfigError("provider name is empty");
40
- if (provider.startsWith("@")) return provider;
41
- if (provider.startsWith(COMMUNITY_PREFIX)) return provider;
42
- if (provider.includes("/")) return provider;
43
- return PLUGIN_PREFIX + provider;
44
- }
45
- /** A bare `[provider, options]` tuple, with both elements present? */
46
- function isOptionsTuple(value) {
47
- if (!Array.isArray(value)) return false;
48
- if (value.length !== 2) return false;
49
- if (typeof value[0] !== "string") return false;
50
- const opt = value[1];
51
- return typeof opt === "object" && opt !== null && !Array.isArray(opt);
52
- }
53
- function normalizeEntry(entry) {
54
- if (typeof entry === "string") return {
55
- provider: entry,
56
- packageName: resolvePluginPackage(entry),
57
- options: {}
58
- };
59
- const [provider, options] = entry;
60
- return {
61
- provider,
62
- packageName: resolvePluginPackage(provider),
63
- options
64
- };
65
- }
66
- function resolveEntry(key, raw) {
67
- const cardinality = CARDINALITY[key];
68
- if (typeof raw === "string") {
69
- if (cardinality === "many") throw new ConfigError(`\`${key}\` accepts multiple providers; wrap a single one in an array: ["${raw}"]`);
70
- return {
71
- cardinality: "single",
72
- tuple: normalizeEntry(raw)
73
- };
74
- }
75
- if (!Array.isArray(raw)) throw new ConfigError(`\`${key}\` entry must be a string or array, got ${typeof raw}`);
76
- if (isOptionsTuple(raw)) {
77
- if (cardinality === "many") return {
78
- cardinality: "many",
79
- tuples: [normalizeEntry(raw)]
80
- };
81
- return {
82
- cardinality: "single",
83
- tuple: normalizeEntry(raw)
84
- };
85
- }
86
- if (cardinality === "single") throw new ConfigError(`\`${key}\` accepts exactly one provider; got a multi-provider list with ${raw.length} entries`);
87
- return {
88
- cardinality: "many",
89
- tuples: raw.map((entry, idx) => {
90
- if (typeof entry === "string") return normalizeEntry(entry);
91
- if (isOptionsTuple(entry)) return normalizeEntry(entry);
92
- throw new ConfigError(`\`${key}[${idx}]\` must be a provider string or [provider, options] tuple`);
93
- })
94
- };
95
- }
96
- function resolveConfig(raw) {
97
- if (!raw.project?.name) throw new ConfigError("`project.name` is required");
98
- if (!raw.providers || typeof raw.providers !== "object") throw new ConfigError("`providers` block is required");
99
- const providers = {};
100
- for (const [key, entry] of Object.entries(raw.providers)) {
101
- if (entry === void 0) continue;
102
- providers[key] = resolveEntry(key, entry);
103
- }
104
- for (const required of REQUIRED_CAPABILITIES) if (!providers[required]) throw new ConfigError(`required capability \`${required}\` is missing from providers`);
105
- return {
106
- project: raw.project,
107
- providers,
108
- apps: raw.apps ?? [],
109
- doctor: raw.doctor ?? {}
110
- };
111
- }
112
- //#endregion
113
- export { resolvePluginPackage as i, resolveConfig as n, resolveEntry as r, ConfigError as t };