@theholocron/cli 5.0.0-alpha.13 → 5.0.0-alpha.134

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.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 repo 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 run 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";
@@ -217,7 +383,8 @@ function resolveConfig(raw) {
217
383
  docs,
218
384
  env: raw.env,
219
385
  log: raw.log,
220
- telemetry: raw.telemetry
386
+ telemetry: raw.telemetry,
387
+ eslint: raw.eslint
221
388
  };
222
389
  }
223
390
  //#endregion
@@ -328,14 +495,19 @@ function defineConfig(config) {
328
495
  */
329
496
  const execFileAsync = promisify(execFile);
330
497
  /**
331
- * Read + parse + resolve `holocron.config.*` from the given directory.
498
+ * Read + parse + resolve `holocron.config.*` starting from the given
499
+ * directory, walking up through ancestors to the nearest `.git` boundary
500
+ * when `cwd` itself has no match — every command is usable from any
501
+ * subdirectory of a repo, not only its exact root (see
502
+ * `@theholocron/datapad`'s own doc comment for the stop conditions).
332
503
  * Throws {@link ConfigFileError} when nothing is found or a file cannot
333
- * be loaded, or `ConfigError` when the config is invalid.
504
+ * be loaded, or `ConfigError` when the config fails schema checks.
334
505
  */
335
506
  async function loadConfig(cwd) {
336
507
  const found = await loadConfigFile({
337
508
  cwd,
338
- name: "holocron"
509
+ name: "holocron",
510
+ walkUp: true
339
511
  });
340
512
  if (!found) throw new ConfigFileError$1(`no holocron.config.{ts,js,mjs,cjs,json} found in ${cwd}. Create one — see the README for the schema.`);
341
513
  return {
@@ -389,4 +561,97 @@ function parseGitRemoteUrl(url) {
389
561
  if (sshMatch) return sshMatch[1];
390
562
  }
391
563
  //#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 };
564
+ //#region src/editorconfig/config.ts
565
+ /**
566
+ * The paths editorconfig-checker skips, read from the same
567
+ * `editorconfig-checker.json` template `holocron repo setup` writes as a repo's
568
+ * `.editorconfig-checker.json` — so Sentinel's central editorconfig check
569
+ * (holocron#927) excludes exactly what CI's editorconfig-checker does, from
570
+ * one source.
571
+ *
572
+ * The exclusion that matters most is `\.md$` / `\.mdx$`: the generated
573
+ * `.editorconfig` gives every file tab indentation, but
574
+ * `@theholocron/prettier-config` formats markdown with spaces, so a README's
575
+ * indented code example would fail one tool or the other. Markdown is left
576
+ * to prettier and markdownlint.
577
+ *
578
+ * Each entry is a regular-expression source, matched against a
579
+ * repo-relative path, as editorconfig-checker itself treats them.
580
+ */
581
+ const EDITORCONFIG_CHECKER_EXCLUDE = [
582
+ "(^|.+/)LICENSE$",
583
+ "^public/.*",
584
+ "\\.md$",
585
+ "\\.mdx$"
586
+ ];
587
+ /** `true` when `path` matches any {@link EDITORCONFIG_CHECKER_EXCLUDE} pattern. */
588
+ function isEditorConfigExcluded(path) {
589
+ return EDITORCONFIG_CHECKER_EXCLUDE.some((pattern) => new RegExp(pattern).test(path));
590
+ }
591
+ //#endregion
592
+ //#region src/inclusive-language/config.ts
593
+ /**
594
+ * The org's canonical `alex` (inclusive-language) config, as raw parsed
595
+ * data rather than the pre-rendered file strings `createRcConfig()`/
596
+ * `createIgnoreConfig()` produce for `holocron repo setup` to write into a
597
+ * repo's own `.alexrc.json`/`.alexignore`.
598
+ *
599
+ * `.alexrc.json` is explicitly "generated by `holocron repo setup` — do not
600
+ * edit manually" (this repo's own `AGENTS.md`) — the real source of truth
601
+ * is this JSON file, one canonical copy, not each repo's synced one. A
602
+ * central Sentinel check (holocron#769/#793) reads it directly rather
603
+ * than fetching each repo's own copy via the Contents API — the same "one
604
+ * config, not N copies" reasoning `@theholocron/commitlint-config` already
605
+ * established for commit-message linting.
606
+ */
607
+ const ALEX_CONFIG = { allow: [
608
+ "dead",
609
+ "execute",
610
+ "execution",
611
+ "executes",
612
+ "failure",
613
+ "failures",
614
+ "hook",
615
+ "hooks",
616
+ "husky",
617
+ "period"
618
+ ] };
619
+ /**
620
+ * Glob-ish path patterns `alex` skips. Kept as a literal here rather than
621
+ * importing `../templates/configs/alexjs/alexignore` directly — that file
622
+ * (like `commit-msg`/`pre-push`) is deliberately extensionless, matching
623
+ * the real filename `holocron repo setup` writes, which ECMAScript's own
624
+ * module resolution (`moduleResolution: "nodenext"`) can't import across
625
+ * a package boundary without an extension. Keep in sync with that file by
626
+ * hand; it changes rarely.
627
+ */
628
+ const ALEX_IGNORE_PATTERNS = [
629
+ ".github/*",
630
+ "CHANGELOG.md",
631
+ "LICENSE"
632
+ ];
633
+ //#endregion
634
+ //#region src/formatting/config.ts
635
+ /**
636
+ * The org's canonical prettier ignore-pattern list for a central Sentinel
637
+ * check (holocron#769/#819) — the `PRETTIER_CONFIG` object itself doesn't
638
+ * need a home here, since `@theholocron/prettier-config`'s default export
639
+ * already *is* one canonical, importable config (the same "one config,
640
+ * not N copies" reasoning `@theholocron/commitlint-config` established for
641
+ * commit-message linting, and `ALEX_CONFIG` established for inclusive
642
+ * language) — only the ignore list is a Sentinel-specific decision that
643
+ * belongs alongside `ALEX_IGNORE_PATTERNS`, not inside the config package.
644
+ *
645
+ * Reuses `ALEX_IGNORE_PATTERNS`' own entries (`.github/*`, `CHANGELOG.md`,
646
+ * `LICENSE` all independently confirmed to fail a real `prettier.check()`
647
+ * today — machine-generated or unparseable content, same reasoning that
648
+ * excluded them from alex), plus `pnpm-lock.yaml` — prettier-specific
649
+ * (alex never reads a lockfile, but prettier would try to reformat it):
650
+ * machine-written by pnpm's own writer, and not gitignored (lockfiles are
651
+ * tracked), so prettier picks it up by default. Reformatting it just
652
+ * fights every subsequent `pnpm install` (this repo's own `.prettierignore`
653
+ * already excludes it for that exact reason).
654
+ */
655
+ const PRETTIER_IGNORE_PATTERNS = [...ALEX_IGNORE_PATTERNS, "pnpm-lock.yaml"];
656
+ //#endregion
657
+ export { ALEX_CONFIG, ALEX_IGNORE_PATTERNS, AuthError, CARDINALITY, ConfigError, ConfigFileError, EDITORCONFIG_CHECKER_EXCLUDE, PRETTIER_IGNORE_PATTERNS, ProviderApiError, REQUIRED_CAPABILITIES, WebhookVerificationError, compose, createFeatureResolver, createResolveToken, createRestClient, defineConfig, deleteToken, deriveCapabilities, deriveCompliance, deriveProfile, deriveStack, getToken, isEditorConfigExcluded, isMulti, lintCommitMessage, lintCommitMsgFile, listStoredProviders, loadConfig, missingCapabilities, readWorkspacePackageJsons, resolveConfig, resolveEntry, resolvePluginPackage, setToken };
@@ -12,7 +12,7 @@ import { ProviderApiError } from "@theholocron/http-client";
12
12
  * See `docs/wiki/specifications/tech-architecture.spec.md` for the design narrative
13
13
  * (issue: #74).
14
14
  */
15
- type CapabilityKey = "source" | "ci" | "secrets" | "environments" | "issues" | "deployment" | "storage" | "auth" | "vault" | "dns" | "tooling" | "notifications" | "analytics" | "errors" | "logs" | "wiki" | "workers";
15
+ type CapabilityKey = "source" | "ci" | "secrets" | "environments" | "issues" | "deployment" | "storage" | "auth" | "vault" | "dns" | "tooling" | "notifications" | "analytics" | "errors" | "logs" | "monitoring" | "wiki" | "workers";
16
16
  type Cardinality = "single" | "many";
17
17
  declare const CARDINALITY: {
18
18
  readonly source: "single";
@@ -30,6 +30,7 @@ declare const CARDINALITY: {
30
30
  readonly analytics: "many";
31
31
  readonly errors: "single";
32
32
  readonly logs: "single";
33
+ readonly monitoring: "single";
33
34
  readonly wiki: "single";
34
35
  readonly workers: "single";
35
36
  };
@@ -364,9 +365,35 @@ interface DeploymentRecord {
364
365
  target?: DeploymentTrigger;
365
366
  status: "queued" | "building" | "ready" | "error" | "cancelled";
366
367
  createdAt?: string;
368
+ /** The provider's own reason when `status` is `"error"` (e.g. Vercel's `Command "npm install" exited with 1`). */
369
+ errorMessage?: string;
370
+ }
371
+ /** Config for `Deployment.deployFunction()` — source files, not a Git ref. */
372
+ interface DeployFunctionConfig {
373
+ /**
374
+ * Files to deploy, keyed by path relative to the project root (e.g.
375
+ * `"api/webhook.js"`, `"package.json"`). Content is plain text — the
376
+ * provider handles any wire-format encoding.
377
+ */
378
+ files: Record<string, string>;
379
+ /**
380
+ * Named deployment target. Omit for a preview deployment — same
381
+ * semantics as `triggerDeployment`'s `target`.
382
+ */
383
+ target?: DeploymentTrigger;
384
+ }
385
+ interface DeployFunctionResult {
386
+ deploymentId: string;
387
+ url: string;
367
388
  }
368
389
  interface Deployment extends ProviderIdentity {
369
390
  readonly key: "deployment";
391
+ /**
392
+ * Custom domain declared in the deployment provider's configuration.
393
+ * `holocron repo setup` attaches it to the configured project and hands any
394
+ * returned verification record to the `dns` capability.
395
+ */
396
+ readonly domain?: string;
370
397
  listProjects(): Promise<DeploymentProject[]>;
371
398
  /** Create if missing, otherwise return existing. Idempotent. */
372
399
  ensureProject(input: {
@@ -390,6 +417,16 @@ interface Deployment extends ProviderIdentity {
390
417
  target?: DeploymentTrigger;
391
418
  }): Promise<DeploymentRecord>;
392
419
  getDeployment(deploymentId: string): Promise<DeploymentRecord>;
420
+ /**
421
+ * Deploy (or update) a project directly from source files, bypassing
422
+ * Git entirely — the non-git counterpart to `triggerDeployment`, for a
423
+ * consumer with no repo to link (a webhook receiver shipped as an npm
424
+ * package, for instance, rather than deployed from its own Git
425
+ * history). `projectId` may be a project name for providers that
426
+ * accept either. Optional — providers without a files-based deploy API
427
+ * omit this (e.g. Cloudflare Pages, git-source only today).
428
+ */
429
+ deployFunction?(projectId: string, config: DeployFunctionConfig): Promise<DeployFunctionResult>;
393
430
  /**
394
431
  * List preview deployments for a specific branch alias (e.g. "repo-pr-42").
395
432
  * Optional — providers that don't support listing by branch omit this.
@@ -402,10 +439,18 @@ interface Deployment extends ProviderIdentity {
402
439
  deletePreviewDeployments?(projectId: string, deploymentIds: string[]): Promise<number>;
403
440
  /**
404
441
  * Add a custom domain (or wildcard) to the project. Idempotent — no-op
405
- * when the domain is already present. Optional: providers without a custom
406
- * domain API omit this (e.g. Vercel manages domains separately).
442
+ * when the domain is already present. Optional: providers without a
443
+ * custom domain API omit this.
444
+ *
445
+ * Returns the DNS record needed to finish verification — `null` when
446
+ * the domain is already verified, or when the provider's API doesn't
447
+ * surface DNS-challenge details at all (e.g. Cloudflare Pages, whose
448
+ * custom-domain flow doesn't expose the same per-request verification
449
+ * handshake Vercel's does). Callers hand a non-null result straight
450
+ * to `dns.upsertRecord(result.zone, result.record)` — the exact
451
+ * pattern `setup` already uses for `Wiki.dnsRecord()`.
407
452
  */
408
- ensureCustomDomain?(projectId: string, hostname: string): Promise<void>;
453
+ ensureCustomDomain?(projectId: string, hostname: string): Promise<DnsRecordRequest | null>;
409
454
  }
410
455
  interface StorageBranch {
411
456
  id: string;
@@ -573,11 +618,31 @@ interface DnsRecord {
573
618
  priority?: number;
574
619
  proxied?: boolean;
575
620
  }
621
+ /**
622
+ * A DNS record a capability wants created, plus which zone it belongs
623
+ * to — the shape `Wiki.dnsRecord()` and `Deployment.ensureCustomDomain()`
624
+ * both hand off to `Dns.upsertRecord()`. Not tied to either capability;
625
+ * any provider that needs "here's a record, go create it" returns this.
626
+ */
627
+ interface DnsRecordRequest {
628
+ /** Zone apex passed as the first argument to `dns.upsertRecord`. */
629
+ zone: string;
630
+ record: DnsRecord;
631
+ }
576
632
  interface Dns extends ProviderIdentity {
577
633
  readonly key: "dns";
578
634
  listRecords(domain: string): Promise<DnsRecord[]>;
579
635
  upsertRecord(domain: string, record: DnsRecord): Promise<DnsRecord>;
580
636
  deleteRecord(domain: string, id: string): Promise<void>;
637
+ /**
638
+ * A `holocron doctor` smoke check with no specific domain to ask about —
639
+ * `listRecords` needs one, but doctor doesn't have one on hand. Verifies
640
+ * the token and reports how many zones it can see.
641
+ */
642
+ whoami?(): Promise<{
643
+ ok: boolean;
644
+ zones: number;
645
+ }>;
581
646
  }
582
647
  interface Errors extends ProviderIdentity {
583
648
  readonly key: "errors";
@@ -614,6 +679,50 @@ interface Logs extends ProviderIdentity {
614
679
  alreadyExists: boolean;
615
680
  }>;
616
681
  }
682
+ type MonitoringAssertionSource = "status_code" | "response_time" | "body" | "header";
683
+ type MonitoringAssertionComparison = "equals" | "not_equals" | "contains" | "not_contains" | "greater_than" | "less_than";
684
+ interface MonitoringAssertion {
685
+ source: MonitoringAssertionSource;
686
+ comparison: MonitoringAssertionComparison;
687
+ /** Header name / JSON path — required when `source` needs one (e.g. `"header"`). */
688
+ property?: string;
689
+ target?: string;
690
+ }
691
+ type MonitoringHttpMethod = "GET" | "POST" | "PUT" | "HEAD" | "DELETE" | "PATCH";
692
+ interface MonitoringCheckInput {
693
+ /** Also the idempotency key — `ensureCheck` finds-or-creates by this name. */
694
+ name: string;
695
+ url: string;
696
+ method?: MonitoringHttpMethod;
697
+ /** Minutes between runs. Adapters round to their own supported values. */
698
+ frequency?: number;
699
+ assertions?: MonitoringAssertion[];
700
+ activated?: boolean;
701
+ }
702
+ interface MonitoringCheck {
703
+ id: string;
704
+ name: string;
705
+ }
706
+ interface Monitoring extends ProviderIdentity {
707
+ readonly key: "monitoring";
708
+ /**
709
+ * Checks this provider is configured to ensure exist — populated from
710
+ * the provider's own config (`holocron.config.ts`'s provider tuple),
711
+ * the same place `Deployment.domain` comes from. `holocron repo setup`
712
+ * loops this list and calls `ensureCheck` for each; empty when none
713
+ * are configured.
714
+ */
715
+ readonly checks: readonly MonitoringCheckInput[];
716
+ /** Auth sanity-check. Throws ProviderApiError on auth failure. */
717
+ whoami?(): Promise<{
718
+ ok: boolean;
719
+ }>;
720
+ /** Idempotent find-or-create-or-update by `input.name`. */
721
+ ensureCheck(input: MonitoringCheckInput): Promise<{
722
+ check: MonitoringCheck;
723
+ alreadyExists: boolean;
724
+ }>;
725
+ }
617
726
  interface ToolingDoctorReport {
618
727
  ok: boolean;
619
728
  message: string;
@@ -651,18 +760,6 @@ interface WikiProvisionOpts {
651
760
  /** Project display name used in the wiki title (e.g. "Holocron"). */
652
761
  name?: string;
653
762
  }
654
- /**
655
- * DNS record that `setup` should create for a wiki custom domain.
656
- * Returned by `Wiki.dnsRecord()` when a custom domain is configured.
657
- */
658
- interface WikiDnsRecord {
659
- /** Zone apex passed as the first argument to `dns.upsertRecord`. */
660
- zone: string;
661
- /** Full hostname for the CNAME (e.g. "wiki.theholocron.dev"). */
662
- cname: string;
663
- /** CNAME target (e.g. "holocron.docs.buildwithfern.com"). */
664
- target: string;
665
- }
666
763
  /**
667
764
  * Reverse-proxy configuration returned by `Wiki.proxyConfig()`.
668
765
  * Used by `setup` to deploy a Worker that forwards traffic to the wiki
@@ -691,7 +788,7 @@ interface Wiki extends ProviderIdentity {
691
788
  * Returns null when no custom domain is set.
692
789
  * Called by `setup` to provision the CNAME via the `dns` capability.
693
790
  */
694
- dnsRecord?(): WikiDnsRecord | null;
791
+ dnsRecord?(): DnsRecordRequest | null;
695
792
  /**
696
793
  * Reverse-proxy config when the wiki provider requires a Worker-level
697
794
  * proxy in addition to the CNAME. Returns null when no proxy is needed.
@@ -699,12 +796,34 @@ interface Wiki extends ProviderIdentity {
699
796
  */
700
797
  proxyConfig?(): WikiProxyConfig | null;
701
798
  }
799
+ /** Config for `Workers.deployScript()` — an arbitrary Worker, not a generated reverse-proxy. */
800
+ interface DeployScriptConfig {
801
+ /** The full Worker script source — a single ES module (`export default { fetch(...) {...} }`). */
802
+ code: string;
803
+ /**
804
+ * Secret bindings — `env.<name>` in the deployed Worker. Values are
805
+ * write-only; the provider never echoes them back, here or anywhere else.
806
+ */
807
+ secrets?: Record<string, string>;
808
+ /**
809
+ * Route pattern(s) this Worker should serve (e.g.
810
+ * `"sentinel.theholocron.dev/*"`). Omit for a Worker reached only via
811
+ * its `*.workers.dev` URL.
812
+ */
813
+ routes?: string[];
814
+ }
815
+ interface DeployScriptResult {
816
+ /** The deployed script's name at the provider. */
817
+ scriptName: string;
818
+ }
702
819
  /**
703
- * Edge Worker / reverse-proxy management capability.
820
+ * Edge Worker management capability.
704
821
  *
705
- * Deploys and manages Worker scripts that proxy traffic to a configured
706
- * target. Used by `setup` to wire wiki custom-domain proxies when the
707
- * wiki provider requires a Worker in addition to the CNAME.
822
+ * Deploys and manages Worker scripts. `upsertProxy` is the existing,
823
+ * narrower case `setup` uses to wire wiki custom-domain reverse-proxies;
824
+ * `deployScript` is the general case — any Worker, any code, its own
825
+ * secrets — for a consumer that isn't a reverse-proxy (a webhook
826
+ * receiver, for instance).
708
827
  */
709
828
  interface Workers extends ProviderIdentity {
710
829
  readonly key: "workers";
@@ -714,6 +833,17 @@ interface Workers extends ProviderIdentity {
714
833
  * `config.headers` on each request.
715
834
  */
716
835
  upsertProxy(hostname: string, config: WikiProxyConfig): Promise<void>;
836
+ /** Deploy (or update) an arbitrary Worker script, optionally with secrets and routes. */
837
+ deployScript(name: string, config: DeployScriptConfig): Promise<DeployScriptResult>;
838
+ /**
839
+ * A `holocron doctor` smoke check — every other method either mutates or
840
+ * needs a specific script/zone doctor has no way to supply up front.
841
+ * Reports how many Worker scripts the token can see.
842
+ */
843
+ whoami?(): Promise<{
844
+ ok: boolean;
845
+ scripts: number;
846
+ }>;
717
847
  }
718
848
  interface CapabilityImpls {
719
849
  source: Source;
@@ -731,6 +861,7 @@ interface CapabilityImpls {
731
861
  analytics: Analytics;
732
862
  errors: Errors;
733
863
  logs: Logs;
864
+ monitoring: Monitoring;
734
865
  wiki: Wiki;
735
866
  workers: Workers;
736
867
  }
@@ -739,4 +870,4 @@ type CardinalityFor<K extends CapabilityKey> = (typeof CARDINALITY)[K];
739
870
  type ResolvedCapability<K extends CapabilityKey> = CardinalityFor<K> extends "many" ? CapabilityImpls[K][] : CapabilityImpls[K];
740
871
  declare function isMulti<K extends CapabilityKey>(key: K): CardinalityFor<K> extends "many" ? true : false;
741
872
  //#endregion
742
- 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 };
873
+ 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, Monitoring, MonitoringAssertion, MonitoringAssertionComparison, MonitoringAssertionSource, MonitoringCheck, MonitoringCheckInput, MonitoringHttpMethod, 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 };
@@ -16,6 +16,7 @@ const CARDINALITY = {
16
16
  analytics: "many",
17
17
  errors: "single",
18
18
  logs: "single",
19
+ monitoring: "single",
19
20
  wiki: "single",
20
21
  workers: "single"
21
22
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/cli",
3
- "version": "5.0.0-alpha.13",
3
+ "version": "5.0.0-alpha.134",
4
4
  "description": "The Holocron CLI - a pluggable, capability-based orchestrator for spinning up and operating software projects.",
5
5
  "keywords": [
6
6
  "cli",
@@ -41,15 +41,19 @@
41
41
  ],
42
42
  "dependencies": {
43
43
  "@axiomhq/pino": "^2.0.0",
44
+ "@commitlint/lint": "^21.2.3",
45
+ "@commitlint/load": "^21.2.3",
46
+ "@commitlint/types": "^21.2.3",
44
47
  "@inquirer/prompts": "^8.5.2",
45
48
  "@inquirer/search": "^4.3.3",
46
49
  "@napi-rs/keyring": "^1.3.0",
47
50
  "@sentry/node": "^10.70.0",
48
51
  "@theholocron/components-doc": "^1.6.0",
49
- "@theholocron/env-utils": "^1.6.0",
50
- "@theholocron/github-client": "^1.17.0",
51
- "@theholocron/http-client": "^1.17.0",
52
- "@theholocron/observability": "^0.3.0",
52
+ "@theholocron/env-utils": "^1.9.2",
53
+ "@theholocron/github-client": "^1.40.0",
54
+ "@theholocron/http-client": "^1.40.0",
55
+ "@theholocron/misc-utils": "^1.9.2",
56
+ "@theholocron/observability": "^0.5.0",
53
57
  "@theholocron/registry-doc": "^1.12.0",
54
58
  "chalk": "^6.0.0",
55
59
  "ora": "^9.4.1",
@@ -57,13 +61,15 @@
57
61
  "pino-pretty": "^13.1.3",
58
62
  "posthog-node": "^5.51.6",
59
63
  "yargs": "^18.1.0",
60
- "@theholocron/astromech": "5.0.0-alpha.13",
61
- "@theholocron/datapad": "5.0.0-alpha.13"
64
+ "@theholocron/astromech": "5.0.0-alpha.134",
65
+ "@theholocron/datapad": "5.0.0-alpha.134"
62
66
  },
63
67
  "devDependencies": {
64
- "@theholocron/eslint-config": "^8.0.0",
65
- "@theholocron/tsconfig": "^8.0.0",
66
- "@theholocron/vitest-config": "^8.0.0",
68
+ "@commitlint/config-conventional": "^21.2.3",
69
+ "@theholocron/commitlint-config": "^8.7.1",
70
+ "@theholocron/eslint-config": "^8.7.1",
71
+ "@theholocron/tsconfig": "^8.7.1",
72
+ "@theholocron/vitest-config": "^8.7.0",
67
73
  "@types/node": "^26",
68
74
  "@types/yargs": "^17.0.35",
69
75
  "@vitest/coverage-v8": "^4.1.11",
@@ -75,7 +81,7 @@
75
81
  "tsx": "4.23.12",
76
82
  "typescript": "^5.9.3",
77
83
  "vitest": "^4.1.11",
78
- "@theholocron/rollup-plugin-transform-template": "5.0.0-alpha.13"
84
+ "@theholocron/rollup-plugin-transform-template": "5.0.0-alpha.134"
79
85
  },
80
86
  "engines": {
81
87
  "node": ">=22.0.0"