@pithy-sh/cli 0.1.0

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.
Files changed (234) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +72 -0
  3. package/scripts/templateManifest.ts +49 -0
  4. package/scripts/tsconfig.json +26 -0
  5. package/scripts/vendorTemplate.ts +84 -0
  6. package/scripts/verifyPack.ts +88 -0
  7. package/src/audit/cliAudit.ts +406 -0
  8. package/src/bin.ts +111 -0
  9. package/src/capabilities/add.ts +288 -0
  10. package/src/capabilities/addBootstrap.ts +275 -0
  11. package/src/capabilities/catalog.ts +175 -0
  12. package/src/capabilities/compose.ts +39 -0
  13. package/src/capabilities/configConstants.ts +74 -0
  14. package/src/capabilities/configImports.ts +397 -0
  15. package/src/capabilities/eject.ts +331 -0
  16. package/src/capabilities/emailProvisioner.ts +346 -0
  17. package/src/capabilities/entitlementGap.ts +70 -0
  18. package/src/capabilities/entryExports.ts +162 -0
  19. package/src/capabilities/flow.ts +550 -0
  20. package/src/capabilities/hostRegistry.ts +368 -0
  21. package/src/capabilities/loadFailure.ts +208 -0
  22. package/src/capabilities/manifests.ts +238 -0
  23. package/src/capabilities/mediaProvisioner.ts +471 -0
  24. package/src/capabilities/mintSecrets.ts +306 -0
  25. package/src/capabilities/paymentsProvisioner.ts +207 -0
  26. package/src/capabilities/prerequisites.ts +168 -0
  27. package/src/capabilities/r2Bucket.ts +113 -0
  28. package/src/capabilities/reconcile.ts +1483 -0
  29. package/src/capabilities/remove.ts +597 -0
  30. package/src/capabilities/requiredOptions.ts +92 -0
  31. package/src/capabilities/rotateSecrets.ts +305 -0
  32. package/src/capabilities/secrets.ts +178 -0
  33. package/src/capabilities/secretsDispatcher.ts +29 -0
  34. package/src/capabilities/secretsProvisioner.ts +389 -0
  35. package/src/capabilities/storageProvisioner.ts +414 -0
  36. package/src/capabilities/supportProvisioner.ts +515 -0
  37. package/src/capabilities/testersLoader.ts +52 -0
  38. package/src/capabilities/testersProvisioner.ts +236 -0
  39. package/src/capabilities/turnstileProvisioner.ts +347 -0
  40. package/src/capabilities/vectorProvisioner.ts +260 -0
  41. package/src/ci/fileModes.ts +223 -0
  42. package/src/ci/sourceFiles.ts +200 -0
  43. package/src/ci/workflowDrivers.ts +524 -0
  44. package/src/cloudflare/accountAnswer.ts +110 -0
  45. package/src/cloudflare/config.ts +685 -0
  46. package/src/cloudflare/storeId.ts +129 -0
  47. package/src/commands/add.ts +372 -0
  48. package/src/commands/alias.ts +205 -0
  49. package/src/commands/dashboard.ts +651 -0
  50. package/src/commands/deploy.ts +150 -0
  51. package/src/commands/dev.ts +37 -0
  52. package/src/commands/doctor.ts +2059 -0
  53. package/src/commands/email.ts +425 -0
  54. package/src/commands/env.ts +155 -0
  55. package/src/commands/feature.ts +359 -0
  56. package/src/commands/init.ts +538 -0
  57. package/src/commands/media.ts +303 -0
  58. package/src/commands/migrate.ts +129 -0
  59. package/src/commands/payments.ts +336 -0
  60. package/src/commands/provision.ts +368 -0
  61. package/src/commands/remove.ts +151 -0
  62. package/src/commands/secrets.ts +652 -0
  63. package/src/commands/seed.ts +229 -0
  64. package/src/commands/storage.ts +309 -0
  65. package/src/commands/support.ts +331 -0
  66. package/src/commands/testers.ts +1020 -0
  67. package/src/commands/token.ts +364 -0
  68. package/src/commands/turnstile.ts +271 -0
  69. package/src/commands/ui.ts +222 -0
  70. package/src/commands/upgrade.ts +517 -0
  71. package/src/commands/vector.ts +390 -0
  72. package/src/commands/worker.ts +295 -0
  73. package/src/dashboard/api.ts +323 -0
  74. package/src/dashboard/connect.ts +758 -0
  75. package/src/dashboard/contract.ts +289 -0
  76. package/src/dashboard/grant.ts +124 -0
  77. package/src/dashboard/registry.ts +519 -0
  78. package/src/dashboard/resolveTarget.ts +119 -0
  79. package/src/dev/delivery.ts +174 -0
  80. package/src/dev/devLogin.ts +155 -0
  81. package/src/dev/devLoginTargets.ts +91 -0
  82. package/src/dev/env.ts +206 -0
  83. package/src/dev/hostWorkers.ts +290 -0
  84. package/src/dev/keys.ts +111 -0
  85. package/src/dev/logging.ts +87 -0
  86. package/src/dev/openUrl.ts +75 -0
  87. package/src/dev/orchestrator.ts +1014 -0
  88. package/src/dev/ports.ts +220 -0
  89. package/src/dev/readyWatch.ts +142 -0
  90. package/src/dev/state.ts +90 -0
  91. package/src/devSecrets/bootstrapVars.ts +265 -0
  92. package/src/devSecrets/devVars.ts +240 -0
  93. package/src/devSecrets/edit.ts +256 -0
  94. package/src/devSecrets/file.ts +277 -0
  95. package/src/devSecrets/generate.ts +428 -0
  96. package/src/devSecrets/location.ts +80 -0
  97. package/src/devSecrets/mode.ts +71 -0
  98. package/src/devSecrets/records.ts +30 -0
  99. package/src/devSecrets/report.ts +99 -0
  100. package/src/devSecrets/seed.ts +344 -0
  101. package/src/devSecrets/store.ts +262 -0
  102. package/src/devSecrets/targets.ts +204 -0
  103. package/src/dispatch.ts +147 -0
  104. package/src/docs/catalog.ts +246 -0
  105. package/src/docs/writeCatalog.ts +45 -0
  106. package/src/doctor/cloudflare.ts +287 -0
  107. package/src/doctor/devPreferences.ts +155 -0
  108. package/src/doctor/devSecrets.ts +464 -0
  109. package/src/doctor/devVars.ts +414 -0
  110. package/src/doctor/devVarsLocal.ts +138 -0
  111. package/src/doctor/environments.ts +155 -0
  112. package/src/doctor/health.ts +354 -0
  113. package/src/doctor/localDelivery.ts +91 -0
  114. package/src/doctor/portsRegistry.ts +252 -0
  115. package/src/doctor/projectName.ts +584 -0
  116. package/src/doctor/secretBindings.ts +166 -0
  117. package/src/doctor/settings.ts +274 -0
  118. package/src/doctor/settingsSources.ts +202 -0
  119. package/src/doctor/workerName.ts +174 -0
  120. package/src/doctor/wranglerVars.ts +33 -0
  121. package/src/feature/bindings.ts +93 -0
  122. package/src/feature/create.ts +179 -0
  123. package/src/feature/destroy.ts +160 -0
  124. package/src/feature/devConfig.ts +201 -0
  125. package/src/feature/identity.ts +100 -0
  126. package/src/feature/manifest.ts +132 -0
  127. package/src/feature/ports.ts +615 -0
  128. package/src/feature/provision.ts +362 -0
  129. package/src/feature/sync.ts +148 -0
  130. package/src/feature/worktree.ts +282 -0
  131. package/src/help/groups.ts +47 -0
  132. package/src/help/rootUsage.ts +135 -0
  133. package/src/main.ts +73 -0
  134. package/src/migrations/ledger.ts +129 -0
  135. package/src/migrations/registry.ts +47 -0
  136. package/src/migrations/run.ts +1066 -0
  137. package/src/notifier/check.ts +129 -0
  138. package/src/notifier/installer.ts +48 -0
  139. package/src/notifier/notify.ts +152 -0
  140. package/src/notifier/state.ts +248 -0
  141. package/src/notifier/version.ts +59 -0
  142. package/src/platform/editor.ts +333 -0
  143. package/src/platform/rc.ts +118 -0
  144. package/src/platform/shell.ts +83 -0
  145. package/src/project/appBindings.ts +184 -0
  146. package/src/project/appWorkflows.ts +266 -0
  147. package/src/project/applyDomains.ts +166 -0
  148. package/src/project/askDomains.ts +220 -0
  149. package/src/project/atomic.ts +466 -0
  150. package/src/project/bindingEntries.ts +425 -0
  151. package/src/project/config.ts +701 -0
  152. package/src/project/dashboard.ts +118 -0
  153. package/src/project/deploy.ts +364 -0
  154. package/src/project/devVars.ts +113 -0
  155. package/src/project/domainPrompt.ts +191 -0
  156. package/src/project/domains.ts +386 -0
  157. package/src/project/envInventory.ts +356 -0
  158. package/src/project/environment.ts +125 -0
  159. package/src/project/extensions.ts +69 -0
  160. package/src/project/jsonc.ts +289 -0
  161. package/src/project/packageManager.ts +238 -0
  162. package/src/project/readOptionalFile.ts +342 -0
  163. package/src/project/rollback.ts +145 -0
  164. package/src/project/scaffold.ts +1088 -0
  165. package/src/project/templateFiles.ts +53 -0
  166. package/src/project/verifyDeploy.ts +230 -0
  167. package/src/project/versionMetadata.ts +77 -0
  168. package/src/project/workerAddress.ts +176 -0
  169. package/src/project/workerCommand.ts +564 -0
  170. package/src/project/workerIdentity.ts +50 -0
  171. package/src/project/workerManifest.ts +135 -0
  172. package/src/project/workerScaffold.ts +289 -0
  173. package/src/project/workerScope.ts +394 -0
  174. package/src/project/workers.ts +86 -0
  175. package/src/project/workflows.ts +281 -0
  176. package/src/project/wrangler.ts +168 -0
  177. package/src/provision/confirm.ts +86 -0
  178. package/src/provision/environment.ts +407 -0
  179. package/src/provision/featureConfig.ts +98 -0
  180. package/src/provision/mode.ts +62 -0
  181. package/src/provision/pendingSecrets.ts +96 -0
  182. package/src/provision/resources.ts +126 -0
  183. package/src/provision/secretBindings.ts +149 -0
  184. package/src/provision/store.ts +33 -0
  185. package/src/provision/unprovisioned.ts +114 -0
  186. package/src/provision/wranglerEnv.ts +220 -0
  187. package/src/rootFlags.ts +48 -0
  188. package/src/seed/drivers.ts +423 -0
  189. package/src/seed/media.ts +187 -0
  190. package/src/seed/plan.ts +137 -0
  191. package/src/seed/prepare.ts +224 -0
  192. package/src/seed/registry.ts +25 -0
  193. package/src/seed/run.ts +793 -0
  194. package/src/seed/safety.ts +206 -0
  195. package/src/terminal/logger.ts +42 -0
  196. package/src/terminal/output.ts +64 -0
  197. package/src/terminal/style.ts +132 -0
  198. package/src/test-utils/doctorHarness.ts +190 -0
  199. package/src/test-utils/migrateHarness.ts +126 -0
  200. package/src/test-utils/seedHarness.ts +173 -0
  201. package/src/test-utils/tempRepo.ts +45 -0
  202. package/src/tokens/config.ts +16 -0
  203. package/src/tokens/engine.ts +345 -0
  204. package/src/tokens/mintedTokens.ts +233 -0
  205. package/src/tokens/sinks.ts +84 -0
  206. package/src/ui/flow.ts +451 -0
  207. package/src/ui/react.ts +112 -0
  208. package/src/ui/routeAllowlist.ts +208 -0
  209. package/src/ui/scaffold.ts +113 -0
  210. package/src/ui/screenStyles.ts +127 -0
  211. package/src/ui/stubs.ts +135 -0
  212. package/src/ui/templates.ts +52 -0
  213. package/src/ui/wire.ts +311 -0
  214. package/src/ui/workerUi.ts +172 -0
  215. package/templates/starter/.dev.secrets.example.jsonc +43 -0
  216. package/templates/starter/.dev.vars.example +30 -0
  217. package/templates/starter/apps/api/package.json +22 -0
  218. package/templates/starter/apps/api/pithy.config.ts +65 -0
  219. package/templates/starter/apps/api/pithy.worker.jsonc +11 -0
  220. package/templates/starter/apps/api/src/bindings.workers.test.ts +18 -0
  221. package/templates/starter/apps/api/src/cloudflare-test.d.ts +11 -0
  222. package/templates/starter/apps/api/src/index.ts +8 -0
  223. package/templates/starter/apps/api/tsconfig.json +26 -0
  224. package/templates/starter/apps/api/wrangler.jsonc +68 -0
  225. package/templates/starter/biome.template.jsonc +75 -0
  226. package/templates/starter/gitignore +37 -0
  227. package/templates/starter/package.json +28 -0
  228. package/templates/starter/pithy.config.ts +67 -0
  229. package/templates/starter/plugins/no-console.grit +25 -0
  230. package/templates/starter/plugins/no-process-io.grit +25 -0
  231. package/templates/starter/tsconfig.json +14 -0
  232. package/templates/starter/tsconfig.tools.json +30 -0
  233. package/templates/starter/vitest.config.ts +124 -0
  234. package/templates/starter/vitest.workers.config.ts +26 -0
@@ -0,0 +1,129 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import { ConflictError } from "@pithy-sh/core/src/error/pithyError";
5
+ import { MIGRATION_TABLE } from "@pithy-sh/core/src/migrations/bookkeeping";
6
+ import { readMigrationLedger } from "@pithy-sh/core/src/migrations/runner";
7
+ import { z } from "zod";
8
+ import type { DatabaseGroup, MigrationDriver } from "./run";
9
+
10
+ /**
11
+ * **What the project declares is what the ledger records.**
12
+ *
13
+ * Two halves of one fact, in two places — the migrations a Worker's capabilities compose, and the rows
14
+ * `pithy_migrations` carries — and until #282 nothing compared them in the direction that matters.
15
+ * `pithy doctor` subtracted applied from declared and called the remainder "pending", which sees a
16
+ * migration that has not run and is structurally blind to one that ran and is no longer declared.
17
+ * Nothing is missing, so nothing is pending, so the check passes. Meanwhile Kysely reads an applied
18
+ * migration its provider does not carry as a corrupted chain and refuses to run *anything* — so the
19
+ * command that says whether a project is healthy said yes about a database that could not be migrated.
20
+ *
21
+ * The same shape as #264 and #267: a declaration, a state, and no reader holding them together.
22
+ *
23
+ * ## One comparison, both directions, one sentence
24
+ *
25
+ * {@link readMigrationLedger} asks the whole question of one database — what is declared and unapplied,
26
+ * and what is applied and undeclared — so no caller can ask half of it. `pithy doctor` renders both
27
+ * halves and fails its exit on either; `pithy migrate` refuses on the second before it writes anything,
28
+ * with the same sentence doctor prints, from {@link describeUndeclared} and {@link undeclaredRemedy}.
29
+ * Two commands reporting the same state in two wordings is how the two disagreed in the first place.
30
+ *
31
+ * ## The remedy is not "fix the migration"
32
+ *
33
+ * Nothing is broken about any migration here — the files are fine, the state moved. And what to do about
34
+ * it depends on something the tool already knows: for `dev` this is the Miniflare store under
35
+ * `.wrangler/state`, which is recreated in seconds, so deleting it is correct and cheap. For a deployed
36
+ * environment it is a database with real rows in it, where the same advice would be a data loss the
37
+ * adopter was told to cause. So the remedy names which of the two applies rather than leaving it to be
38
+ * guessed.
39
+ */
40
+
41
+ /** One applied migration this project no longer declares. */
42
+ export const UndeclaredMigration = z
43
+ .object({
44
+ database: z.string().describe("The database name — a capability's `databases` key."),
45
+ binding: z
46
+ .string()
47
+ .describe("The D1 binding it resolves to, as wrangler.jsonc declares it — the name an adopter recognizes."),
48
+ name: z.string().describe("The composed migration name, exactly as the ledger records it."),
49
+ })
50
+ .describe("One migration a database has applied that this project no longer declares.");
51
+ export type UndeclaredMigration = z.infer<typeof UndeclaredMigration>;
52
+
53
+ /**
54
+ * The problem line: which binding records what, and that nothing declares it any more.
55
+ *
56
+ * Grouped by binding, because a project migrates several databases and "records" is a claim about one of
57
+ * them. The names are the ledger's own, unabridged — a truncated migration name is not something anyone
58
+ * can grep a repository for.
59
+ */
60
+ export function describeUndeclared(entries: readonly UndeclaredMigration[]): string {
61
+ const byBinding = new Map<string, string[]>();
62
+ for (const entry of entries) {
63
+ const names = byBinding.get(entry.binding) ?? [];
64
+ names.push(entry.name);
65
+ byBinding.set(entry.binding, names);
66
+ }
67
+ const sentences = [...byBinding].map(([binding, names]) => `${binding} records ${names.join(", ")}.`);
68
+ return `${sentences.join(" ")} This project no longer declares ${entries.length === 1 ? "it" : "them"}.`;
69
+ }
70
+
71
+ /**
72
+ * The action line, and it depends on which database this is.
73
+ *
74
+ * `dev` is the local Miniflare store: throwing it away costs a re-migrate and nothing else, so that is
75
+ * the advice, and it is the one an adopter who just deleted a migration wants. Every other environment
76
+ * is a real database whose tables hold real rows — there the ledger row is the thing to reconcile, and a
77
+ * reset would be the tool telling someone to destroy production to fix a bookkeeping mismatch.
78
+ */
79
+ export function undeclaredRemedy(env: string): string {
80
+ const opening = "Nothing migrates until the ledger and the declaration agree.";
81
+ return env === "dev"
82
+ ? `${opening} This is the local dev store, so wiping it is cheap: delete .wrangler/state, then run pithy migrate --env dev again.`
83
+ : `${opening} ${env} holds real rows, so don't reset it: restore the migration to this project, or delete its row from ${MIGRATION_TABLE} if the schema it created is meant to stay.`;
84
+ }
85
+
86
+ /**
87
+ * Refuse a run against a database whose ledger records something this project no longer declares.
88
+ *
89
+ * Placed at {@link runGroups}, beside `claimGroups` and for the same reason: it is the one line every
90
+ * path that writes to a database goes through, so the refusal cannot be honored by two commands and
91
+ * skipped by six. It runs after the ownership claim — whose database this is comes before what is in it.
92
+ *
93
+ * **Only for a pass whose provider spans the whole ledger.** `pithy remove --drop` runs a deliberately
94
+ * partial provider, one capability's migrations against a database full of other capabilities' rows, so
95
+ * every one of those rows is "undeclared" to it and refusing there would break the command outright.
96
+ *
97
+ * Kysely would refuse anyway, one layer down and with `corrupted migrations: previously executed
98
+ * migration X is missing` — which is a real sentence, and one nobody ever saw, because the runner
99
+ * flattened it into `Migration run failed.` on the way out (#282). This refuses first so the sentence is
100
+ * ours: it names the binding, the migration, and a remedy that fits the database it is talking about.
101
+ */
102
+ export async function assertLedgerDeclared(options: {
103
+ /** The environment being migrated — what decides which remedy is true. */
104
+ env: string;
105
+ /** The open driver, already holding a D1 per group. */
106
+ driver: MigrationDriver;
107
+ /** The groups this run will execute, in run order. */
108
+ groups: DatabaseGroup[];
109
+ }): Promise<void> {
110
+ const undeclared: UndeclaredMigration[] = [];
111
+ const workers = new Set<string>();
112
+ for (const group of options.groups) {
113
+ const ledger = await readMigrationLedger(options.driver.database(group), group.provider);
114
+ for (const name of ledger.undeclared) {
115
+ undeclared.push({ database: group.database, binding: group.binding, name });
116
+ }
117
+ for (const entry of group.entries) workers.add(entry.worker);
118
+ }
119
+ if (undeclared.length === 0) return;
120
+
121
+ throw new ConflictError({
122
+ message: describeUndeclared(undeclared),
123
+ action: undeclaredRemedy(options.env),
124
+ // The registries that were compared against, because the comparison is only as complete as they are:
125
+ // a Worker whose config could not be loaded contributes nothing, and its applied migrations would
126
+ // look exactly like a deleted one from here. Naming them makes that diagnosable instead of baffling.
127
+ detail: `Compared against the migrations composed by ${[...workers].join(", ") || "no workers"} for ${options.env}.`,
128
+ });
129
+ }
@@ -0,0 +1,47 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import type { Capability } from "@pithy-sh/core/src/capability/capability";
5
+ import { InternalError } from "@pithy-sh/core/src/error/pithyError";
6
+ import { createMigrationRegistry, type NamespacedMigrations } from "@pithy-sh/core/src/migrations/registry";
7
+ import type { MigrationProvider } from "kysely/migration";
8
+
9
+ /**
10
+ * Every capability's per-database migration set (`DatabaseSpec.migrations`, namespaced by capability
11
+ * name), in composition order — the raw material both {@link buildRegistryFromCapabilities} and the
12
+ * fan-out runner build on. The runner needs the sets rather than a finished provider because two
13
+ * Workers can be bound to one physical D1: their sets merge into a single ordered provider so that
14
+ * database migrates once, with every Worker's capabilities in it. Migrations without a
15
+ * `migrationOrder` fail here, attributed to their capability.
16
+ */
17
+ export function collectMigrationSets(capabilities: Capability[]): NamespacedMigrations[] {
18
+ const sets: NamespacedMigrations[] = [];
19
+ for (const capability of capabilities) {
20
+ for (const [database, spec] of Object.entries(capability.databases ?? {})) {
21
+ if (!spec.migrations) continue;
22
+ if (spec.migrationOrder === undefined) {
23
+ throw new InternalError({
24
+ message: `Capability "${capability.name}" has migrations for database "${database}" but no migrationOrder.`,
25
+ action: "Set migrationOrder on that database spec (core low, app high).",
26
+ });
27
+ }
28
+ sets.push({
29
+ database,
30
+ namespace: capability.name,
31
+ order: spec.migrationOrder,
32
+ migrations: spec.migrations,
33
+ });
34
+ }
35
+ }
36
+ return sets;
37
+ }
38
+
39
+ /**
40
+ * Compose every capability's per-database migration sets (`DatabaseSpec.migrations`,
41
+ * namespaced by capability name) into one ordered provider per database. A database
42
+ * nobody migrates gets no provider; migrations without a `migrationOrder` fail here,
43
+ * attributed to their capability.
44
+ */
45
+ export function buildRegistryFromCapabilities(capabilities: Capability[]): Record<string, MigrationProvider> {
46
+ return createMigrationRegistry(collectMigrationSets(capabilities));
47
+ }