@theholocron/cli 5.0.0-alpha.8 → 5.0.0-alpha.80

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,7 +1,9 @@
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, Errors, Issue, IssueSearchFilter, Issues, LabelDef, LifecycleResult, LifecycleSlot, Logs, NormalizedAuthUser, Notifications, PagesConfig, ParseWebhookInput, ProviderApiError, ProviderIdentity, PullRequest, REQUIRED_CAPABILITIES, RepoRef, RepoSettings, ResolvedCapability, Ruleset, SecretScope, Secrets, Source, StatusCategory, Storage, StorageBranch, TeamEntry, TeamPermission, Tooling, ToolingDoctorReport, TrackerDoctorReport, TrackerUser, Vault, WebhookDashboardInfo, WebhookVerificationError, Wiki, WikiDnsRecord, WikiProvisionOpts, WikiProxyConfig, Workers, isMulti } from "./plugin/capabilities.mjs";
1
+ import { Analytics, Auth, AuthDescription, AuthEvent, AuthEventType, AuthIdentity, AuthUser, CARDINALITY, CapabilityImpls, CapabilityKey, Cardinality, CardinalityFor, Ci, CiRun, CiRunFilter, CiRunStatus, ConnectionStringOptions, CreateAuthUserInput, DeployFunctionConfig, DeployFunctionResult, DeployScriptConfig, DeployScriptResult, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, Dns, DnsRecord, DnsRecordRequest, DnsRecordType, EnsureResult, Environment, EnvironmentReviewer, Environments, Errors, Issue, IssueSearchFilter, Issues, LabelDef, LifecycleResult, LifecycleSlot, Logs, NormalizedAuthUser, Notifications, PagesConfig, ParseWebhookInput, ProviderApiError, ProviderIdentity, PullRequest, REQUIRED_CAPABILITIES, RepoRef, RepoSettings, ResolvedCapability, Ruleset, SecretScope, Secrets, Source, StatusCategory, Storage, StorageBranch, TeamEntry, TeamPermission, Tooling, ToolingDoctorReport, TrackerDoctorReport, TrackerUser, Vault, WebhookDashboardInfo, WebhookVerificationError, Wiki, WikiProvisionOpts, WikiProxyConfig, Workers, isMulti } from "./plugin/capabilities.mjs";
2
2
  import { AuthError, RequestOptions, ResolveTokenConfig as ResolveTokenConfig$1, ResolveTokenInput, RestClient, RestClientConfig, createRestClient } from "@theholocron/http-client";
3
3
  import { ConfigFileError } from "@theholocron/datapad";
4
+ import { WorkspacePackage } from "@theholocron/astromech";
4
5
  import { LogLevel } from "@theholocron/observability/core";
6
+ import { QualifiedConfig } from "@commitlint/types";
5
7
  //#region src/auth/auth-resolver.d.ts
6
8
  type ResolveTokenConfig = Omit<ResolveTokenConfig$1, "getKeyringToken">;
7
9
  /** Wraps `createResolveToken` from `@theholocron/http` and injects the
@@ -161,6 +163,21 @@ interface RepoProperties {
161
163
  interface RepoConfig {
162
164
  /** "owner/name" — the GitHub repository coordinate. Derived from the git remote when absent. */
163
165
  name?: string;
166
+ /**
167
+ * GitHub's repo-level default branch (`HEAD`) — what a fresh clone checks
168
+ * out, what `gh pr create`/the compare UI target by default, and what
169
+ * Sentinel's `validateConfig()` reads (D6: the App only ever reads config
170
+ * from the repo's default branch, never a PR ref, as a security boundary
171
+ * — never widen that to read PR branches instead of fixing this field).
172
+ * Synced by `holocron setup`. Omit to leave GitHub's current setting
173
+ * untouched — most repos never need this. Exists for a repo using a
174
+ * `main`/`alpha` prerelease-channel split (CLAUDE.md's Releases section)
175
+ * where active development happens on a non-`main` branch: set this to
176
+ * that branch so Sentinel validates against what's actually being
177
+ * developed, then change it back once that branch merges to `main` for a
178
+ * stable cut and stops being the active branch.
179
+ */
180
+ defaultBranch?: string;
164
181
  /**
165
182
  * Branch protection preset applied by `holocron setup`. When omitted,
166
183
  * no protection is applied and no `branch_protection_level` property is set.
@@ -539,6 +556,98 @@ declare function resolvePluginPackage(provider: string): string;
539
556
  declare function resolveEntry(key: CapabilityKey, raw: RawProviderEntry): ResolvedProviderEntry;
540
557
  declare function resolveConfig(raw: HolocronConfig): ResolvedHolocronConfig;
541
558
  //#endregion
559
+ //#region src/commands/setup/derived-properties.d.ts
560
+ /**
561
+ * Derived custom-properties (#677). Unlike the existing 6 GitHub
562
+ * custom-properties fields (`lifecycle`, `open_source`, `runtime_environment`,
563
+ * `uses_external_packages`, `monorepo`, `branch_protection_level`) — which
564
+ * mirror what `holocron.config.ts` already states explicitly — these four are
565
+ * *derived*: signal that isn't an explicit config field today (framework
566
+ * choice, repo archetype, capability drift). See
567
+ * `.notes/tech-holocron-platform.spec.md` → "Custom-properties sync — field
568
+ * definitions (#677)" for the full design.
569
+ */
570
+ type HolocronProfile = "library" | "cli" | "plugin" | "template" | "app" | "docs" | "platform";
571
+ interface PackageJsonLike {
572
+ name?: string;
573
+ private?: boolean;
574
+ bin?: unknown;
575
+ dependencies?: Record<string, string>;
576
+ devDependencies?: Record<string, string>;
577
+ }
578
+ interface DeriveProfileInput {
579
+ rootPackageJson: PackageJsonLike | null;
580
+ repoName: string;
581
+ isMonorepo: boolean;
582
+ /** `packages/*` package.json contents, monorepo repos only — used to find the primary published artifact. */
583
+ workspacePackageJsons: PackageJsonLike[];
584
+ }
585
+ /**
586
+ * Repo archetype, in priority order:
587
+ * 1. `*-template` repo name → `template` (independent of package shape).
588
+ * 2. Monorepo whose primary published artifact is a CLI (some workspace
589
+ * package has `bin`) → `platform` — matches `holocron` itself: the CLI is
590
+ * the deliverable, the plugin packages are supporting infrastructure for
591
+ * it, not separate products.
592
+ * 3. Monorepo with no CLI but at least one non-private workspace package →
593
+ * `library` — matches `clients`/`configs`/`utils`/`themes`/
594
+ * `observability`: a family of published packages, none of them a CLI.
595
+ * Checked *before* falling into the root-package branch below, because
596
+ * these repos' own root `package.json` is private and often carries
597
+ * `@theholocron/astro-config` for a docs site built from the monorepo
598
+ * root — without this check they'd wrongly resolve to `docs` off that
599
+ * root-level signal alone, when the docs site is a secondary concern of
600
+ * a library collection, not what the repo fundamentally is.
601
+ * 4. Single-package repo with `package.json#bin` → `cli`.
602
+ * 5. Docs-only site (astro/starlight present, root package private, no
603
+ * publishable exports) → `docs`.
604
+ * 6. Private, non-publishable, non-docs → `app`.
605
+ * 7. Otherwise, a single publishable package → `library`.
606
+ */
607
+ declare function deriveProfile(input: DeriveProfileInput): HolocronProfile;
608
+ /**
609
+ * Detected build/framework tooling — the literal "does this repo need
610
+ * next/vite/tsdown" signal `holocron.config.ts` has no field for today
611
+ * (framework choice isn't a config concept, unlike `providers`/`tasks`).
612
+ * Curated table, not exhaustive by design — add to `KNOWN_STACK_DEPS` as new
613
+ * frameworks show up rather than trying to detect anything on npm.
614
+ */
615
+ declare function deriveStack(pkg: PackageJsonLike | null): string[];
616
+ /** The provider capability keys actually wired in `providers: {}` — zero heuristics. */
617
+ declare function deriveCapabilities(providers: ResolvedProvidersConfig | undefined): string[];
618
+ declare function deriveCompliance(capabilities: readonly string[]): "compliant" | "non-compliant";
619
+ /**
620
+ * The Phase B refinement `deriveCompliance()`'s own doc comment names —
621
+ * *why* a repo is non-compliant, not just that it is. `[]` when compliant.
622
+ * Same `REQUIRED_BASELINE` `deriveCompliance()` checks against — one
623
+ * table, not two independently-maintained baselines.
624
+ */
625
+ declare function missingCapabilities(capabilities: readonly string[]): string[];
626
+ /**
627
+ * Reads each workspace package's actual `package.json` content (not just
628
+ * `readWorkspacePackages()`'s `{ slug, name, dir }` — `deriveProfile()`'s
629
+ * `platform` detection needs `bin`, which that helper doesn't carry).
630
+ * Missing/invalid files are skipped, matching `readWorkspacePackages()`'s
631
+ * own soft-fail behavior.
632
+ */
633
+ declare function readWorkspacePackageJsons(repoRoot: string, workspacePackages: readonly WorkspacePackage[]): Promise<PackageJsonLike[]>;
634
+ //#endregion
635
+ //#region src/commit-lint/lint-message.d.ts
636
+ interface CommitMessageViolation {
637
+ /** Rule name, e.g. `"subject-empty"`. */
638
+ rule: string;
639
+ /** commitlint's own message for the failure, e.g. `"subject may not be empty"`. */
640
+ message: string;
641
+ }
642
+ declare function lintCommitMessage(message: string, loaded: QualifiedConfig): Promise<CommitMessageViolation[]>;
643
+ //#endregion
644
+ //#region src/commit-lint/lint-commit-msg-file.d.ts
645
+ interface LintCommitMsgFileResult {
646
+ valid: boolean;
647
+ violations: CommitMessageViolation[];
648
+ }
649
+ declare function lintCommitMsgFile(filePath: string, cwd?: string): Promise<LintCommitMsgFileResult>;
650
+ //#endregion
542
651
  //#region src/config/compose.d.ts
543
652
  type TaskEntry = NonNullable<HolocronConfig["tasks"]>[number];
544
653
  /** A single composable capability fragment. */
@@ -617,4 +726,56 @@ interface LoadedConfig {
617
726
  */
618
727
  declare function loadConfig(cwd: string): Promise<LoadedConfig>;
619
728
  //#endregion
620
- export { Analytics, AppConfig, Auth, AuthDescription, AuthError, AuthEvent, AuthEventType, AuthIdentity, AuthUser, CARDINALITY, Capability, CapabilityConfigPackage, CapabilityImpls, CapabilityKey, Cardinality, CardinalityFor, ChromaticProjectConfig, Ci, CiRun, CiRunFilter, CiRunStatus, ComposedPreset, ConfigError, ConfigFileError, ConnectionStringOptions, CreateAuthUserInput, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, Dns, DnsRecord, DnsRecordType, DocsConfig, DoctorConfig, EnsureResult, EnvConfig, Environment, EnvironmentReviewer, Environments, Errors, FeatureResolverConfig, HolocronConfig, Issue, IssueSearchFilter, Issues, LabelDef, LifecycleResult, LifecycleSlot, LoadedConfig, LogConfig, Logs, MultiEntry, NormalizedAuthUser, Notifications, PagesConfig, ParseWebhookInput, ProviderApiError, ProviderIdentity, ProviderOptions, PullRequest, 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, StorybookDeployProject, TaskEntryConfig, TeamEntry, TeamPermission, TelemetryConfig, Tooling, ToolingDoctorReport, TrackerDoctorReport, TrackerUser, Vault, WebhookDashboardInfo, WebhookVerificationError, Wiki, WikiDnsRecord, WikiProvisionOpts, WikiProxyConfig, Workers, WorkflowWithConfig, compose, createFeatureResolver, createResolveToken, createRestClient, defineConfig, deleteToken, getToken, isMulti, listStoredProviders, loadConfig, resolveConfig, resolveEntry, resolvePluginPackage, setToken };
729
+ //#region src/formatting/config.d.ts
730
+ /**
731
+ * The org's canonical prettier ignore-pattern list for a central Sentinel
732
+ * check (holocron#769/#819) — the `PRETTIER_CONFIG` object itself doesn't
733
+ * need a home here, since `@theholocron/prettier-config`'s default export
734
+ * already *is* one canonical, importable config (the same "one config,
735
+ * not N copies" reasoning `@theholocron/commitlint-config` established for
736
+ * commit-message linting, and `ALEX_CONFIG` established for inclusive
737
+ * language) — only the ignore list is a Sentinel-specific decision that
738
+ * belongs alongside `ALEX_IGNORE_PATTERNS`, not inside the config package.
739
+ *
740
+ * Reuses `ALEX_IGNORE_PATTERNS`' own entries (`.github/*`, `CHANGELOG.md`,
741
+ * `LICENSE` all independently confirmed to fail a real `prettier.check()`
742
+ * today — machine-generated or unparseable content, same reasoning that
743
+ * excluded them from alex), plus `pnpm-lock.yaml` — prettier-specific
744
+ * (alex never reads a lockfile, but prettier would try to reformat it):
745
+ * machine-written by pnpm's own writer, and not gitignored (lockfiles are
746
+ * tracked), so prettier picks it up by default. Reformatting it just
747
+ * fights every subsequent `pnpm install` (this repo's own `.prettierignore`
748
+ * already excludes it for that exact reason).
749
+ */
750
+ declare const PRETTIER_IGNORE_PATTERNS: string[];
751
+ //#endregion
752
+ //#region src/inclusive-language/config.d.ts
753
+ /**
754
+ * The org's canonical `alex` (inclusive-language) config, as raw parsed
755
+ * data rather than the pre-rendered file strings `createRcConfig()`/
756
+ * `createIgnoreConfig()` produce for `holocron setup` to write into a
757
+ * repo's own `.alexrc.json`/`.alexignore`.
758
+ *
759
+ * `.alexrc.json` is explicitly "generated by `holocron setup` — do not
760
+ * edit manually" (this repo's own `AGENTS.md`) — the real source of truth
761
+ * is this JSON file, one canonical copy, not each repo's synced one. A
762
+ * central Sentinel check (holocron#769/#793) reads it directly rather
763
+ * than fetching each repo's own copy via the Contents API — the same "one
764
+ * config, not N copies" reasoning `@theholocron/commitlint-config` already
765
+ * established for commit-message linting.
766
+ */
767
+ declare const ALEX_CONFIG: {
768
+ allow: string[];
769
+ };
770
+ /**
771
+ * Glob-ish path patterns `alex` skips. Kept as a literal here rather than
772
+ * importing `../templates/configs/alexjs/alexignore` directly — that file
773
+ * (like `commit-msg`/`pre-push`) is deliberately extensionless, matching
774
+ * the real filename `holocron setup` writes, which ECMAScript's own
775
+ * module resolution (`moduleResolution: "nodenext"`) can't import across
776
+ * a package boundary without an extension. Keep in sync with that file by
777
+ * hand; it changes rarely.
778
+ */
779
+ declare const ALEX_IGNORE_PATTERNS: string[];
780
+ //#endregion
781
+ export { ALEX_CONFIG, ALEX_IGNORE_PATTERNS, Analytics, AppConfig, Auth, AuthDescription, AuthError, AuthEvent, AuthEventType, AuthIdentity, AuthUser, CARDINALITY, Capability, CapabilityConfigPackage, CapabilityImpls, CapabilityKey, Cardinality, CardinalityFor, ChromaticProjectConfig, Ci, CiRun, CiRunFilter, CiRunStatus, CommitMessageViolation, ComposedPreset, ConfigError, ConfigFileError, ConnectionStringOptions, CreateAuthUserInput, DeployFunctionConfig, DeployFunctionResult, DeployScriptConfig, DeployScriptResult, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, DeriveProfileInput, Dns, DnsRecord, DnsRecordRequest, DnsRecordType, DocsConfig, DoctorConfig, EnsureResult, EnvConfig, Environment, EnvironmentReviewer, Environments, Errors, FeatureResolverConfig, HolocronConfig, HolocronProfile, Issue, IssueSearchFilter, Issues, LabelDef, LifecycleResult, LifecycleSlot, LintCommitMsgFileResult, LoadedConfig, LogConfig, Logs, MultiEntry, NormalizedAuthUser, Notifications, PRETTIER_IGNORE_PATTERNS, PackageJsonLike, PagesConfig, ParseWebhookInput, ProviderApiError, ProviderIdentity, ProviderOptions, PullRequest, 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, StorybookDeployProject, TaskEntryConfig, TeamEntry, TeamPermission, TelemetryConfig, Tooling, ToolingDoctorReport, TrackerDoctorReport, TrackerUser, Vault, WebhookDashboardInfo, WebhookVerificationError, Wiki, WikiProvisionOpts, WikiProxyConfig, Workers, WorkflowWithConfig, compose, createFeatureResolver, createResolveToken, createRestClient, defineConfig, deleteToken, deriveCapabilities, deriveCompliance, deriveProfile, deriveStack, getToken, isMulti, lintCommitMessage, lintCommitMsgFile, listStoredProviders, loadConfig, missingCapabilities, readWorkspacePackageJsons, resolveConfig, resolveEntry, resolvePluginPackage, setToken };
package/dist/index.mjs CHANGED
@@ -2,9 +2,11 @@ import { CARDINALITY, ProviderApiError, REQUIRED_CAPABILITIES, WebhookVerificati
2
2
  import { AuthError, createResolveToken as createResolveToken$1, createRestClient } from "@theholocron/http-client";
3
3
  import { createEnvLookup } from "@theholocron/env-utils";
4
4
  import { Entry, findCredentials } from "@napi-rs/keyring";
5
- import { execFile } from "node:child_process";
6
5
  import { readFile } from "node:fs/promises";
7
6
  import { basename, dirname, join } from "node:path";
7
+ import load from "@commitlint/load";
8
+ import lint from "@commitlint/lint";
9
+ import { execFile } from "node:child_process";
8
10
  import { promisify } from "node:util";
9
11
  import { loadTasksConfig } from "@theholocron/astromech/config";
10
12
  import { ConfigFileError, ConfigFileError as ConfigFileError$1, loadConfigFile } from "@theholocron/datapad";
@@ -113,6 +115,170 @@ function createFeatureResolver(config) {
113
115
  };
114
116
  }
115
117
  //#endregion
118
+ //#region src/commands/setup/derived-properties.ts
119
+ /**
120
+ * Repo archetype, in priority order:
121
+ * 1. `*-template` repo name → `template` (independent of package shape).
122
+ * 2. Monorepo whose primary published artifact is a CLI (some workspace
123
+ * package has `bin`) → `platform` — matches `holocron` itself: the CLI is
124
+ * the deliverable, the plugin packages are supporting infrastructure for
125
+ * it, not separate products.
126
+ * 3. Monorepo with no CLI but at least one non-private workspace package →
127
+ * `library` — matches `clients`/`configs`/`utils`/`themes`/
128
+ * `observability`: a family of published packages, none of them a CLI.
129
+ * Checked *before* falling into the root-package branch below, because
130
+ * these repos' own root `package.json` is private and often carries
131
+ * `@theholocron/astro-config` for a docs site built from the monorepo
132
+ * root — without this check they'd wrongly resolve to `docs` off that
133
+ * root-level signal alone, when the docs site is a secondary concern of
134
+ * a library collection, not what the repo fundamentally is.
135
+ * 4. Single-package repo with `package.json#bin` → `cli`.
136
+ * 5. Docs-only site (astro/starlight present, root package private, no
137
+ * publishable exports) → `docs`.
138
+ * 6. Private, non-publishable, non-docs → `app`.
139
+ * 7. Otherwise, a single publishable package → `library`.
140
+ */
141
+ function deriveProfile(input) {
142
+ if (/-template$/.test(input.repoName)) return "template";
143
+ if (input.isMonorepo) {
144
+ if (input.workspacePackageJsons.some((pkg) => Boolean(pkg.bin))) return "platform";
145
+ if (input.workspacePackageJsons.some((pkg) => pkg.private !== true)) return "library";
146
+ }
147
+ const pkg = input.rootPackageJson;
148
+ if (pkg?.bin) return "cli";
149
+ const deps = {
150
+ ...pkg?.dependencies,
151
+ ...pkg?.devDependencies
152
+ };
153
+ const isDocsSite = Boolean(deps["@theholocron/astro-config"] || deps["astro"]);
154
+ if (pkg?.private && isDocsSite) return "docs";
155
+ if (pkg?.private) return "app";
156
+ return "library";
157
+ }
158
+ const KNOWN_STACK_DEPS = [
159
+ "next",
160
+ "vite",
161
+ "astro",
162
+ "tsdown",
163
+ "rollup",
164
+ "webpack",
165
+ "storybook",
166
+ "vitest",
167
+ "playwright",
168
+ "turbo"
169
+ ];
170
+ /**
171
+ * Detected build/framework tooling — the literal "does this repo need
172
+ * next/vite/tsdown" signal `holocron.config.ts` has no field for today
173
+ * (framework choice isn't a config concept, unlike `providers`/`tasks`).
174
+ * Curated table, not exhaustive by design — add to `KNOWN_STACK_DEPS` as new
175
+ * frameworks show up rather than trying to detect anything on npm.
176
+ */
177
+ function deriveStack(pkg) {
178
+ const deps = {
179
+ ...pkg?.dependencies,
180
+ ...pkg?.devDependencies
181
+ };
182
+ return KNOWN_STACK_DEPS.filter((name) => name in deps);
183
+ }
184
+ /** The provider capability keys actually wired in `providers: {}` — zero heuristics. */
185
+ function deriveCapabilities(providers) {
186
+ return Object.keys(providers ?? {}).sort();
187
+ }
188
+ /**
189
+ * `source` + `ci` are the minimal baseline every repo `holocron setup`
190
+ * touches wires — every `holocron.config.ts` in this org already satisfies
191
+ * this, so `non-compliant` here means drift (a provider manually removed
192
+ * after setup), not an unmet aspirational policy. Deliberately not a richer
193
+ * per-profile policy yet (e.g. "a `library` must have `deployment`") — that's
194
+ * a Phase B (GitHub App) refinement once it can post *why* on a check run.
195
+ */
196
+ const REQUIRED_BASELINE = ["source", "ci"];
197
+ function deriveCompliance(capabilities) {
198
+ return REQUIRED_BASELINE.every((key) => capabilities.includes(key)) ? "compliant" : "non-compliant";
199
+ }
200
+ /**
201
+ * The Phase B refinement `deriveCompliance()`'s own doc comment names —
202
+ * *why* a repo is non-compliant, not just that it is. `[]` when compliant.
203
+ * Same `REQUIRED_BASELINE` `deriveCompliance()` checks against — one
204
+ * table, not two independently-maintained baselines.
205
+ */
206
+ function missingCapabilities(capabilities) {
207
+ return REQUIRED_BASELINE.filter((key) => !capabilities.includes(key));
208
+ }
209
+ /**
210
+ * Reads each workspace package's actual `package.json` content (not just
211
+ * `readWorkspacePackages()`'s `{ slug, name, dir }` — `deriveProfile()`'s
212
+ * `platform` detection needs `bin`, which that helper doesn't carry).
213
+ * Missing/invalid files are skipped, matching `readWorkspacePackages()`'s
214
+ * own soft-fail behavior.
215
+ */
216
+ async function readWorkspacePackageJsons(repoRoot, workspacePackages) {
217
+ const results = [];
218
+ for (const { slug, dir = "packages" } of workspacePackages) try {
219
+ const raw = await readFile(join(repoRoot, dir, slug, "package.json"), "utf8");
220
+ results.push(JSON.parse(raw));
221
+ } catch {}
222
+ return results;
223
+ }
224
+ //#endregion
225
+ //#region src/commit-lint/lint-message.ts
226
+ /**
227
+ * Lints one commit message string against an already-loaded commitlint
228
+ * config. The genuinely environment-independent half of commit-message
229
+ * linting (holocron#789) — *obtaining* a `QualifiedConfig` differs by
230
+ * caller (Sentinel isolates itself from any ambient config since it runs
231
+ * standalone, away from a real repo checkout; `lintCommitMsgFile` below
232
+ * just lets cosmiconfig discover the invoking repo's own
233
+ * `commitlint.config.*`, matching `commitlint --edit`'s own contract) —
234
+ * but linting one message against a loaded config is identical either way.
235
+ *
236
+ * Real `@commitlint/lint` — the same programmatic API `@commitlint/cli`
237
+ * itself uses internally — never a reimplementation of commitlint's rules.
238
+ */
239
+ /** `parserPreset.parserOpts`, if the loaded config sets one — same lookup `@commitlint/cli`'s own `selectParserOpts()` does. */
240
+ function selectParserOpts(parserPreset) {
241
+ return parserPreset?.parserOpts;
242
+ }
243
+ async function lintCommitMessage(message, loaded) {
244
+ const opts = {
245
+ /* istanbul ignore next -- see comment above */
246
+ parserOpts: selectParserOpts(loaded.parserPreset) ?? {},
247
+ plugins: loaded.plugins,
248
+ /* istanbul ignore next -- see comment above */
249
+ ignores: loaded.ignores ?? [],
250
+ defaultIgnores: loaded.defaultIgnores !== false
251
+ };
252
+ const outcome = await lint(message, loaded.rules, opts);
253
+ if (outcome.valid) return [];
254
+ return outcome.errors.map((error) => ({
255
+ rule: error.name,
256
+ message: error.message
257
+ }));
258
+ }
259
+ //#endregion
260
+ //#region src/commit-lint/lint-commit-msg-file.ts
261
+ /**
262
+ * `holocron lint commit-msg <file>` — a real programmatic replacement for
263
+ * shelling out to `commitlint --edit "$1"` from `.husky/commit-msg`
264
+ * (holocron#789). Matches that CLI's own contract exactly: `file` is a
265
+ * path to the commit message being written (the arg git's `commit-msg`
266
+ * hook passes as `$1`), no `--config` override needed — `@commitlint/load`
267
+ * runs from `cwd` with no seed, so cosmiconfig's own normal upward search
268
+ * discovers the invoking repo's real `commitlint.config.*` file, exactly
269
+ * as `commitlint --edit` (no `--config` flag) already does today. Unlike
270
+ * Sentinel's `loadIsolatedConfig()`, no isolation is needed here — this
271
+ * runs inside a real repo checkout with a real `node_modules`, not
272
+ * standalone on a server.
273
+ */
274
+ async function lintCommitMsgFile(filePath, cwd = process.cwd()) {
275
+ const violations = await lintCommitMessage(await readFile(filePath, "utf8"), await load({}, { cwd }));
276
+ return {
277
+ valid: violations.length === 0,
278
+ violations
279
+ };
280
+ }
281
+ //#endregion
116
282
  //#region src/config/config.ts
117
283
  var ConfigError = class extends Error {
118
284
  name = "ConfigError";
@@ -389,4 +555,69 @@ function parseGitRemoteUrl(url) {
389
555
  if (sshMatch) return sshMatch[1];
390
556
  }
391
557
  //#endregion
392
- export { AuthError, CARDINALITY, ConfigError, ConfigFileError, ProviderApiError, REQUIRED_CAPABILITIES, WebhookVerificationError, compose, createFeatureResolver, createResolveToken, createRestClient, defineConfig, deleteToken, getToken, isMulti, listStoredProviders, loadConfig, resolveConfig, resolveEntry, resolvePluginPackage, setToken };
558
+ //#region src/inclusive-language/config.ts
559
+ /**
560
+ * The org's canonical `alex` (inclusive-language) config, as raw parsed
561
+ * data rather than the pre-rendered file strings `createRcConfig()`/
562
+ * `createIgnoreConfig()` produce for `holocron setup` to write into a
563
+ * repo's own `.alexrc.json`/`.alexignore`.
564
+ *
565
+ * `.alexrc.json` is explicitly "generated by `holocron setup` — do not
566
+ * edit manually" (this repo's own `AGENTS.md`) — the real source of truth
567
+ * is this JSON file, one canonical copy, not each repo's synced one. A
568
+ * central Sentinel check (holocron#769/#793) reads it directly rather
569
+ * than fetching each repo's own copy via the Contents API — the same "one
570
+ * config, not N copies" reasoning `@theholocron/commitlint-config` already
571
+ * established for commit-message linting.
572
+ */
573
+ const ALEX_CONFIG = { allow: [
574
+ "dead",
575
+ "execute",
576
+ "execution",
577
+ "executes",
578
+ "failure",
579
+ "failures",
580
+ "hook",
581
+ "hooks",
582
+ "husky",
583
+ "period"
584
+ ] };
585
+ /**
586
+ * Glob-ish path patterns `alex` skips. Kept as a literal here rather than
587
+ * importing `../templates/configs/alexjs/alexignore` directly — that file
588
+ * (like `commit-msg`/`pre-push`) is deliberately extensionless, matching
589
+ * the real filename `holocron setup` writes, which ECMAScript's own
590
+ * module resolution (`moduleResolution: "nodenext"`) can't import across
591
+ * a package boundary without an extension. Keep in sync with that file by
592
+ * hand; it changes rarely.
593
+ */
594
+ const ALEX_IGNORE_PATTERNS = [
595
+ ".github/*",
596
+ "CHANGELOG.md",
597
+ "LICENSE"
598
+ ];
599
+ //#endregion
600
+ //#region src/formatting/config.ts
601
+ /**
602
+ * The org's canonical prettier ignore-pattern list for a central Sentinel
603
+ * check (holocron#769/#819) — the `PRETTIER_CONFIG` object itself doesn't
604
+ * need a home here, since `@theholocron/prettier-config`'s default export
605
+ * already *is* one canonical, importable config (the same "one config,
606
+ * not N copies" reasoning `@theholocron/commitlint-config` established for
607
+ * commit-message linting, and `ALEX_CONFIG` established for inclusive
608
+ * language) — only the ignore list is a Sentinel-specific decision that
609
+ * belongs alongside `ALEX_IGNORE_PATTERNS`, not inside the config package.
610
+ *
611
+ * Reuses `ALEX_IGNORE_PATTERNS`' own entries (`.github/*`, `CHANGELOG.md`,
612
+ * `LICENSE` all independently confirmed to fail a real `prettier.check()`
613
+ * today — machine-generated or unparseable content, same reasoning that
614
+ * excluded them from alex), plus `pnpm-lock.yaml` — prettier-specific
615
+ * (alex never reads a lockfile, but prettier would try to reformat it):
616
+ * machine-written by pnpm's own writer, and not gitignored (lockfiles are
617
+ * tracked), so prettier picks it up by default. Reformatting it just
618
+ * fights every subsequent `pnpm install` (this repo's own `.prettierignore`
619
+ * already excludes it for that exact reason).
620
+ */
621
+ const PRETTIER_IGNORE_PATTERNS = [...ALEX_IGNORE_PATTERNS, "pnpm-lock.yaml"];
622
+ //#endregion
623
+ export { ALEX_CONFIG, ALEX_IGNORE_PATTERNS, AuthError, CARDINALITY, ConfigError, ConfigFileError, PRETTIER_IGNORE_PATTERNS, ProviderApiError, REQUIRED_CAPABILITIES, WebhookVerificationError, compose, createFeatureResolver, createResolveToken, createRestClient, defineConfig, deleteToken, deriveCapabilities, deriveCompliance, deriveProfile, deriveStack, getToken, isMulti, lintCommitMessage, lintCommitMsgFile, listStoredProviders, loadConfig, missingCapabilities, readWorkspacePackageJsons, resolveConfig, resolveEntry, resolvePluginPackage, setToken };
@@ -136,10 +136,11 @@ interface Source extends ProviderIdentity {
136
136
  */
137
137
  syncLabels?(canonical: ReadonlyArray<LabelDef>, stale: ReadonlyArray<string>): Promise<string>;
138
138
  /**
139
- * Set org-level custom property values on the repo.
139
+ * Set org-level custom property values on the repo. `string[]` for
140
+ * `multi_select` properties, plain `string` for everything else.
140
141
  * Optional — providers that don't support custom properties omit this.
141
142
  */
142
- syncProperties?(values: Record<string, string>): Promise<string>;
143
+ syncProperties?(values: Record<string, string | string[]>): Promise<string>;
143
144
  /**
144
145
  * Replace the repo's topic set with the supplied list.
145
146
  * Optional — providers that don't support topics omit this.
@@ -364,8 +365,32 @@ interface DeploymentRecord {
364
365
  status: "queued" | "building" | "ready" | "error" | "cancelled";
365
366
  createdAt?: string;
366
367
  }
368
+ /** Config for `Deployment.deployFunction()` — source files, not a Git ref. */
369
+ interface DeployFunctionConfig {
370
+ /**
371
+ * Files to deploy, keyed by path relative to the project root (e.g.
372
+ * `"api/webhook.js"`, `"package.json"`). Content is plain text — the
373
+ * provider handles any wire-format encoding.
374
+ */
375
+ files: Record<string, string>;
376
+ /**
377
+ * Named deployment target. Omit for a preview deployment — same
378
+ * semantics as `triggerDeployment`'s `target`.
379
+ */
380
+ target?: DeploymentTrigger;
381
+ }
382
+ interface DeployFunctionResult {
383
+ deploymentId: string;
384
+ url: string;
385
+ }
367
386
  interface Deployment extends ProviderIdentity {
368
387
  readonly key: "deployment";
388
+ /**
389
+ * Custom domain declared in the deployment provider's configuration.
390
+ * `holocron setup` attaches it to the configured project and hands any
391
+ * returned verification record to the `dns` capability.
392
+ */
393
+ readonly domain?: string;
369
394
  listProjects(): Promise<DeploymentProject[]>;
370
395
  /** Create if missing, otherwise return existing. Idempotent. */
371
396
  ensureProject(input: {
@@ -389,6 +414,16 @@ interface Deployment extends ProviderIdentity {
389
414
  target?: DeploymentTrigger;
390
415
  }): Promise<DeploymentRecord>;
391
416
  getDeployment(deploymentId: string): Promise<DeploymentRecord>;
417
+ /**
418
+ * Deploy (or update) a project directly from source files, bypassing
419
+ * Git entirely — the non-git counterpart to `triggerDeployment`, for a
420
+ * consumer with no repo to link (a webhook receiver shipped as an npm
421
+ * package, for instance, rather than deployed from its own Git
422
+ * history). `projectId` may be a project name for providers that
423
+ * accept either. Optional — providers without a files-based deploy API
424
+ * omit this (e.g. Cloudflare Pages, git-source only today).
425
+ */
426
+ deployFunction?(projectId: string, config: DeployFunctionConfig): Promise<DeployFunctionResult>;
392
427
  /**
393
428
  * List preview deployments for a specific branch alias (e.g. "repo-pr-42").
394
429
  * Optional — providers that don't support listing by branch omit this.
@@ -401,10 +436,18 @@ interface Deployment extends ProviderIdentity {
401
436
  deletePreviewDeployments?(projectId: string, deploymentIds: string[]): Promise<number>;
402
437
  /**
403
438
  * Add a custom domain (or wildcard) to the project. Idempotent — no-op
404
- * when the domain is already present. Optional: providers without a custom
405
- * domain API omit this (e.g. Vercel manages domains separately).
439
+ * when the domain is already present. Optional: providers without a
440
+ * custom domain API omit this.
441
+ *
442
+ * Returns the DNS record needed to finish verification — `null` when
443
+ * the domain is already verified, or when the provider's API doesn't
444
+ * surface DNS-challenge details at all (e.g. Cloudflare Pages, whose
445
+ * custom-domain flow doesn't expose the same per-request verification
446
+ * handshake Vercel's does). Callers hand a non-null result straight
447
+ * to `dns.upsertRecord(result.zone, result.record)` — the exact
448
+ * pattern `setup` already uses for `Wiki.dnsRecord()`.
406
449
  */
407
- ensureCustomDomain?(projectId: string, hostname: string): Promise<void>;
450
+ ensureCustomDomain?(projectId: string, hostname: string): Promise<DnsRecordRequest | null>;
408
451
  }
409
452
  interface StorageBranch {
410
453
  id: string;
@@ -572,6 +615,17 @@ interface DnsRecord {
572
615
  priority?: number;
573
616
  proxied?: boolean;
574
617
  }
618
+ /**
619
+ * A DNS record a capability wants created, plus which zone it belongs
620
+ * to — the shape `Wiki.dnsRecord()` and `Deployment.ensureCustomDomain()`
621
+ * both hand off to `Dns.upsertRecord()`. Not tied to either capability;
622
+ * any provider that needs "here's a record, go create it" returns this.
623
+ */
624
+ interface DnsRecordRequest {
625
+ /** Zone apex passed as the first argument to `dns.upsertRecord`. */
626
+ zone: string;
627
+ record: DnsRecord;
628
+ }
575
629
  interface Dns extends ProviderIdentity {
576
630
  readonly key: "dns";
577
631
  listRecords(domain: string): Promise<DnsRecord[]>;
@@ -650,18 +704,6 @@ interface WikiProvisionOpts {
650
704
  /** Project display name used in the wiki title (e.g. "Holocron"). */
651
705
  name?: string;
652
706
  }
653
- /**
654
- * DNS record that `setup` should create for a wiki custom domain.
655
- * Returned by `Wiki.dnsRecord()` when a custom domain is configured.
656
- */
657
- interface WikiDnsRecord {
658
- /** Zone apex passed as the first argument to `dns.upsertRecord`. */
659
- zone: string;
660
- /** Full hostname for the CNAME (e.g. "wiki.theholocron.dev"). */
661
- cname: string;
662
- /** CNAME target (e.g. "holocron.docs.buildwithfern.com"). */
663
- target: string;
664
- }
665
707
  /**
666
708
  * Reverse-proxy configuration returned by `Wiki.proxyConfig()`.
667
709
  * Used by `setup` to deploy a Worker that forwards traffic to the wiki
@@ -690,7 +732,7 @@ interface Wiki extends ProviderIdentity {
690
732
  * Returns null when no custom domain is set.
691
733
  * Called by `setup` to provision the CNAME via the `dns` capability.
692
734
  */
693
- dnsRecord?(): WikiDnsRecord | null;
735
+ dnsRecord?(): DnsRecordRequest | null;
694
736
  /**
695
737
  * Reverse-proxy config when the wiki provider requires a Worker-level
696
738
  * proxy in addition to the CNAME. Returns null when no proxy is needed.
@@ -698,12 +740,34 @@ interface Wiki extends ProviderIdentity {
698
740
  */
699
741
  proxyConfig?(): WikiProxyConfig | null;
700
742
  }
743
+ /** Config for `Workers.deployScript()` — an arbitrary Worker, not a generated reverse-proxy. */
744
+ interface DeployScriptConfig {
745
+ /** The full Worker script source — a single ES module (`export default { fetch(...) {...} }`). */
746
+ code: string;
747
+ /**
748
+ * Secret bindings — `env.<name>` in the deployed Worker. Values are
749
+ * write-only; the provider never echoes them back, here or anywhere else.
750
+ */
751
+ secrets?: Record<string, string>;
752
+ /**
753
+ * Route pattern(s) this Worker should serve (e.g.
754
+ * `"sentinel.theholocron.dev/*"`). Omit for a Worker reached only via
755
+ * its `*.workers.dev` URL.
756
+ */
757
+ routes?: string[];
758
+ }
759
+ interface DeployScriptResult {
760
+ /** The deployed script's name at the provider. */
761
+ scriptName: string;
762
+ }
701
763
  /**
702
- * Edge Worker / reverse-proxy management capability.
764
+ * Edge Worker management capability.
703
765
  *
704
- * Deploys and manages Worker scripts that proxy traffic to a configured
705
- * target. Used by `setup` to wire wiki custom-domain proxies when the
706
- * wiki provider requires a Worker in addition to the CNAME.
766
+ * Deploys and manages Worker scripts. `upsertProxy` is the existing,
767
+ * narrower case `setup` uses to wire wiki custom-domain reverse-proxies;
768
+ * `deployScript` is the general case — any Worker, any code, its own
769
+ * secrets — for a consumer that isn't a reverse-proxy (a webhook
770
+ * receiver, for instance).
707
771
  */
708
772
  interface Workers extends ProviderIdentity {
709
773
  readonly key: "workers";
@@ -713,6 +777,8 @@ interface Workers extends ProviderIdentity {
713
777
  * `config.headers` on each request.
714
778
  */
715
779
  upsertProxy(hostname: string, config: WikiProxyConfig): Promise<void>;
780
+ /** Deploy (or update) an arbitrary Worker script, optionally with secrets and routes. */
781
+ deployScript(name: string, config: DeployScriptConfig): Promise<DeployScriptResult>;
716
782
  }
717
783
  interface CapabilityImpls {
718
784
  source: Source;
@@ -738,4 +804,4 @@ type CardinalityFor<K extends CapabilityKey> = (typeof CARDINALITY)[K];
738
804
  type ResolvedCapability<K extends CapabilityKey> = CardinalityFor<K> extends "many" ? CapabilityImpls[K][] : CapabilityImpls[K];
739
805
  declare function isMulti<K extends CapabilityKey>(key: K): CardinalityFor<K> extends "many" ? true : false;
740
806
  //#endregion
741
- export { 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, Errors, Issue, IssueSearchFilter, Issues, LabelDef, LifecycleResult, LifecycleSlot, Logs, NormalizedAuthUser, Notifications, PagesConfig, ParseWebhookInput, ProviderApiError, ProviderIdentity, PullRequest, REQUIRED_CAPABILITIES, RepoRef, RepoSettings, ResolvedCapability, Ruleset, SecretScope, Secrets, Source, StatusCategory, Storage, StorageBranch, TeamEntry, TeamPermission, Tooling, ToolingDoctorReport, TrackerDoctorReport, TrackerUser, Vault, WebhookDashboardInfo, WebhookVerificationError, Wiki, WikiDnsRecord, WikiProvisionOpts, WikiProxyConfig, Workers, isMulti };
807
+ export { Analytics, Auth, AuthDescription, AuthEvent, AuthEventType, AuthIdentity, AuthUser, CARDINALITY, CapabilityImpls, CapabilityKey, Cardinality, CardinalityFor, Ci, CiRun, CiRunFilter, CiRunStatus, ConnectionStringOptions, CreateAuthUserInput, DeployFunctionConfig, DeployFunctionResult, DeployScriptConfig, DeployScriptResult, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, Dns, DnsRecord, DnsRecordRequest, DnsRecordType, EnsureResult, Environment, EnvironmentReviewer, Environments, Errors, Issue, IssueSearchFilter, Issues, LabelDef, LifecycleResult, LifecycleSlot, Logs, NormalizedAuthUser, Notifications, PagesConfig, ParseWebhookInput, ProviderApiError, ProviderIdentity, PullRequest, REQUIRED_CAPABILITIES, RepoRef, RepoSettings, ResolvedCapability, Ruleset, SecretScope, Secrets, Source, StatusCategory, Storage, StorageBranch, TeamEntry, TeamPermission, Tooling, ToolingDoctorReport, TrackerDoctorReport, TrackerUser, Vault, WebhookDashboardInfo, WebhookVerificationError, Wiki, WikiProvisionOpts, WikiProxyConfig, Workers, isMulti };