@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,150 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import { defineCommand } from "citty";
5
+ import { createProjectCliAudit } from "../audit/cliAudit";
6
+ import { type CloudflareAccountSelection, cloudflareEnv } from "../cloudflare/config";
7
+ import { readProjectLedger } from "../migrations/run";
8
+ import { projectCloudflareAccount } from "../project/config";
9
+ import { deployProject, deployVerificationFailed, pendingWarning, summarizeDeploy } from "../project/deploy";
10
+ import { assertOriginsDeclared } from "../project/domains";
11
+ import { optionalEnvArg, requireEnvironment } from "../project/environment";
12
+ import { assertWorkflowsBound } from "../project/workflows";
13
+ import { assertEnvironmentProvisioned } from "../provision/unprovisioned";
14
+ import { formatDone, formatJsonLine, withErrorReporting } from "../terminal/output";
15
+
16
+ /**
17
+ * How many migrations are unapplied for the target env — best-effort, never blocking. Deploy and
18
+ * migrate are orthogonal (deploy never migrates), so a config that can't load or a database it can't
19
+ * reach yields `undefined` (no warning) rather than failing the deploy.
20
+ *
21
+ * Deploy ships every Worker, so the count does too — it fans out over `apps/*` exactly as `pithy migrate`
22
+ * would. The warning is about the project's schema being behind, and a table any Worker owns is one this
23
+ * deploy's code may read.
24
+ *
25
+ * **Best-effort is not unattributed.** The count reaches a remote D1 for any `--env`, so it takes the
26
+ * same account the deploy beside it does. Omitting it resolved `<config>/cloudflare.json` while the
27
+ * deploy resolved the project's named account — one command, one run, two tenants, and a number about
28
+ * somebody else's schema printed as though it were this project's (#206, #226).
29
+ */
30
+ async function pendingFor(
31
+ projectDir: string,
32
+ env: string,
33
+ account: CloudflareAccountSelection | null,
34
+ ): Promise<number | undefined> {
35
+ try {
36
+ // Only the pending half here. A drifted ledger is a fault `pithy doctor` names and `pithy migrate`
37
+ // refuses on, and deploy never migrates — it reports how far the schema is behind, which stays a
38
+ // truthful number either way.
39
+ const ledger = await readProjectLedger({ projectDir, env, account });
40
+ // A partial read still warns, and the number it warns with is the one it established (#371). A sum
41
+ // over the databases that answered can only understate how far behind the project is, so it never
42
+ // raises a false alarm — and this line is warn-only. Which database went unread is `pithy doctor`'s
43
+ // sentence to say, in the report whose job is naming faults.
44
+ if (ledger.state === "partial") return ledger.counted.pending;
45
+ return ledger.state === "read" ? ledger.pending : undefined;
46
+ } catch {
47
+ return undefined;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * The audit emitter for `pithy deploy`. Shipping code to an environment is exactly the kind of action
53
+ * an audit trail exists for, so every worker deploy — success or failure — is recorded when the project
54
+ * has audit wired. A bare `pithy deploy` (no `--env`) still targets the project's own app database, the
55
+ * same one `dev` reads (see `resolveAuditDatabaseId`), so the fallback lines up with the real target.
56
+ */
57
+ async function buildAudit(projectDir: string, env: string, account: CloudflareAccountSelection | null) {
58
+ const vars = cloudflareEnv({ account });
59
+ // `audit` composed by any Worker means the project has a trail; deploy spans them all. Here `env` really
60
+ // is the environment acted on, so it is also the recorded origin.
61
+ return createProjectCliAudit({
62
+ projectDir,
63
+ accountId: vars.CLOUDFLARE_ACCOUNT_ID,
64
+ apiToken: vars.CLOUDFLARE_API_TOKEN,
65
+ env,
66
+ actedOn: env,
67
+ });
68
+ }
69
+
70
+ export default defineCommand({
71
+ meta: { name: "deploy", description: "Deploy to Cloudflare Workers" },
72
+ args: {
73
+ env: optionalEnvArg("Target environment (omit for each worker's top-level config)"),
74
+ json: { type: "boolean", default: false, description: "Machine-readable output" },
75
+ },
76
+ run: ({ args }) =>
77
+ withErrorReporting(args.json, async () => {
78
+ // Optional here, and only here: a bare `pithy deploy` ships each worker's top-level stanza, which
79
+ // is not an environment at all. A value that *is* given is held to the same rule as everywhere else.
80
+ const env = args.env === undefined ? undefined : requireEnvironment(args.env);
81
+ const projectDir = process.cwd();
82
+
83
+ // The migration warning only makes sense against a concrete remote target. A bare `pithy deploy`
84
+ // ships each worker's top-level config, whose deployed schema is not the local dev D1 — so skip the
85
+ // check (and its REST round trip) unless an `--env` names the environment being deployed.
86
+ // The account first, before anything resolves a credential. It is read from the project's own
87
+ // config and passed to every step below, rather than left to whatever a later load happens to
88
+ // publish — a deploy that authenticates against the wrong tenant succeeds, and says nothing.
89
+ // Refuse before anything is built or spawned. A binding with no id fails *inside* wrangler, on a
90
+ // field the adopter never wrote, after `init`, `add`, `migrate` and `dev` have all succeeded —
91
+ // and with no hint that provisioning was a step they had missed (#240). Only for a named `--env`:
92
+ // a bare deploy ships the top-level stanza, whose ids `pithy dev` resolves from Miniflare.
93
+ if (env) await assertEnvironmentProvisioned(projectDir, env);
94
+ // And the other half of "is this environment ready to be real": does its config name every origin
95
+ // it will answer on (#253). Refused here for the same reason the binding check is — deploy knows
96
+ // the environment and the config, and this is the last moment before a staging Worker starts
97
+ // emailing real users magic links into production. Beside it rather than inside it because the two
98
+ // are different questions with different fixes; `assertOriginsDeclared` exempts a feature
99
+ // environment itself, which has no declared domain by design.
100
+ if (env) await assertOriginsDeclared(projectDir, env);
101
+ // And the third half of it, on the same evidence and at the same moment: does this environment's
102
+ // stanza bind what its Workers' app capabilities declare (#267)? `reconcileAppWorkflows` writes
103
+ // that table and `pithy worker sync` is its only caller, so an adopter who declared a job and
104
+ // never ran it shipped a Worker with no `workflows` entry and no `triggers.crons` — the binding
105
+ // fails on the first request, and the cron simply never fires and says nothing at all. Beside the
106
+ // origins gate rather than inside it for the same reason that one is beside the binding gate: a
107
+ // different question with a different fix, and a feature environment is exempt from this one too.
108
+ if (env) await assertWorkflowsBound(projectDir, env);
109
+
110
+ const account = await projectCloudflareAccount(projectDir);
111
+ // **The account is settled here, before anything best-effort runs (#236).** `pendingFor` swallows
112
+ // every failure on purpose — a database it cannot reach costs a warning line, not the deploy — and
113
+ // a pin the credentials contradict is not a reachability failure. Swallowed, it becomes
114
+ // `pendingMigrations: null`: an absence that reads as a fact. `cloudflareEnv` refuses it in the one
115
+ // sentence `pithy doctor` already reads out. A *missing* pair still passes, because `wrangler
116
+ // deploy` authenticates on its own OAuth login and always could.
117
+ cloudflareEnv({ account });
118
+ const pending = env ? await pendingFor(projectDir, env, account) : undefined;
119
+ const audit = await buildAudit(projectDir, env ?? "dev", account);
120
+ const deploys = await deployProject({ projectDir, account, env, audit });
121
+ // A deploy that shipped but is not the thing answering at the declared address is a failure too,
122
+ // and it fails the pipeline rather than printing a line nobody reads. Two shapes count: a
123
+ // *consistent* mismatch, and nothing answering at all (#264). A gradual rollout and a Worker that
124
+ // answered without a version are both inconclusive, and failing on either would train everyone to
125
+ // ignore the check.
126
+ const failed = deploys.some((deploy) => !deploy.ok) || deployVerificationFailed(deploys);
127
+
128
+ if (args.json) {
129
+ process.stdout.write(
130
+ `${formatJsonLine({
131
+ command: "deploy",
132
+ env: env ?? null,
133
+ pendingMigrations: pending ?? null,
134
+ workers: deploys,
135
+ })}\n`,
136
+ );
137
+ if (failed) process.exitCode = 1;
138
+ return;
139
+ }
140
+
141
+ const warning = env ? pendingWarning(pending, env) : undefined;
142
+ if (warning) process.stdout.write(`${warning}\n`);
143
+ for (const deploy of deploys) process.stdout.write(`${summarizeDeploy(deploy)}\n`);
144
+ if (failed) {
145
+ process.exitCode = 1; // The per-worker failure lines are the report; exit non-zero for CI.
146
+ return;
147
+ }
148
+ process.stdout.write(`${formatDone()}\n`);
149
+ }),
150
+ });
@@ -0,0 +1,37 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import { defineCommand } from "citty";
5
+ import { startDev } from "../dev/orchestrator";
6
+ import { formatJsonLine, withErrorReporting } from "../terminal/output";
7
+
8
+ /**
9
+ * `pithy dev` — run every autostart worker locally under one supervisor.
10
+ *
11
+ * Discovers the workers from `apps/`, resolves each one's pinned port from `.dev.config.json` (verifying it
12
+ * is free, never drifting), spawns them as process groups, tees their labeled output to the terminal and
13
+ * `logs/dev.log`, and prints one ready banner. Ctrl-C (SIGINT) or SIGTERM tears the whole session down; any
14
+ * worker exiting brings the rest down with it. The real work lives in `startDev`; this stays thin.
15
+ */
16
+ export default defineCommand({
17
+ meta: { name: "dev", description: "Run every worker locally under one supervisor" },
18
+ args: {
19
+ json: { type: "boolean", default: false, description: "Machine-readable output" },
20
+ },
21
+ run: ({ args }) =>
22
+ withErrorReporting(args.json, async () => {
23
+ const projectDir = process.cwd();
24
+ const handle = await startDev({ projectDir, json: args.json });
25
+
26
+ if (args.json) {
27
+ const workers = Object.fromEntries(handle.workers.map((w) => [w.name, { port: w.port, origin: w.origin }]));
28
+ process.stdout.write(`${formatJsonLine({ command: "dev", workers })}\n`);
29
+ }
30
+
31
+ process.once("SIGINT", () => void handle.shutdown("interrupted"));
32
+ process.once("SIGTERM", () => void handle.shutdown("terminated"));
33
+
34
+ await handle.closed;
35
+ process.exit(0);
36
+ }),
37
+ });