@dragonmastery/tamer 0.39.0 → 0.40.1

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 (99) hide show
  1. package/README.md +14 -12
  2. package/dist/{apply-ByHaKpxD.mjs → apply-CV4_3Jv4.mjs} +15 -15
  3. package/dist/{apply-ByHaKpxD.mjs.map → apply-CV4_3Jv4.mjs.map} +1 -1
  4. package/dist/{applyTarget-BkBg8MFW.mjs → applyTarget-B1YPgkb3.mjs} +3 -3
  5. package/dist/{applyTarget-BkBg8MFW.mjs.map → applyTarget-B1YPgkb3.mjs.map} +1 -1
  6. package/dist/{bootstrap-DvOce6vA.mjs → bootstrap-ilkixdmD.mjs} +4 -4
  7. package/dist/{bootstrap-DvOce6vA.mjs.map → bootstrap-ilkixdmD.mjs.map} +1 -1
  8. package/dist/{buildDispatchUploadForm-D9ZrefZX.mjs → buildDispatchUploadForm-D_fM8JaL.mjs} +11 -14
  9. package/dist/buildDispatchUploadForm-D_fM8JaL.mjs.map +1 -0
  10. package/dist/{cloudflareSnapshot-CjXNMr4X.mjs → cloudflareSnapshot-BAeNVohz.mjs} +5 -5
  11. package/dist/{cloudflareSnapshot-CjXNMr4X.mjs.map → cloudflareSnapshot-BAeNVohz.mjs.map} +1 -1
  12. package/dist/{deploy-C4NOE5S1.mjs → deploy-BEaNADU6.mjs} +10 -10
  13. package/dist/{deploy-C4NOE5S1.mjs.map → deploy-BEaNADU6.mjs.map} +1 -1
  14. package/dist/{destroy-BeOYY2U6.mjs → destroy-Krf35oqE.mjs} +11 -11
  15. package/dist/{destroy-BeOYY2U6.mjs.map → destroy-Krf35oqE.mjs.map} +1 -1
  16. package/dist/{destroy-tenant-B9ZTeUDk.mjs → destroy-tenant-C95ljuon.mjs} +2 -2
  17. package/dist/{destroy-tenant-B9ZTeUDk.mjs.map → destroy-tenant-C95ljuon.mjs.map} +1 -1
  18. package/dist/{dev-0zkF2iqF.mjs → dev-C__1rLos.mjs} +8 -8
  19. package/dist/{dev-0zkF2iqF.mjs.map → dev-C__1rLos.mjs.map} +1 -1
  20. package/dist/{dns-records.resolve-BBTlY3T5.mjs → dns-records.resolve-C2T0m4NG.mjs} +1 -1
  21. package/dist/{dns-records.resolve-DV6XBZf3.mjs → dns-records.resolve-DwBR_1WI.mjs} +1 -1
  22. package/dist/{dns-records.resolve-DV6XBZf3.mjs.map → dns-records.resolve-DwBR_1WI.mjs.map} +1 -1
  23. package/dist/{dns-records.sync-FyzKl-Ph.mjs → dns-records.sync-Dfwk76J_.mjs} +3 -3
  24. package/dist/{dns-records.sync-FyzKl-Ph.mjs.map → dns-records.sync-Dfwk76J_.mjs.map} +1 -1
  25. package/dist/{doctor-fm_vGe2C.mjs → doctor-BIaLEVFR.mjs} +2 -2
  26. package/dist/{doctor-fm_vGe2C.mjs.map → doctor-BIaLEVFR.mjs.map} +1 -1
  27. package/dist/drift-08k11FV6.mjs +8 -0
  28. package/dist/{drift-Ci368_WQ.mjs → drift-CLsSBorO.mjs} +7 -7
  29. package/dist/{drift-Ci368_WQ.mjs.map → drift-CLsSBorO.mjs.map} +1 -1
  30. package/dist/{emit-DDTQVfi_.mjs → emit-Dh68dvo5.mjs} +2 -2
  31. package/dist/{emit-DDTQVfi_.mjs.map → emit-Dh68dvo5.mjs.map} +1 -1
  32. package/dist/{env-gc-DlQxkZPj.mjs → env-gc-0vX5Av4i.mjs} +11 -11
  33. package/dist/{env-gc-DlQxkZPj.mjs.map → env-gc-0vX5Av4i.mjs.map} +1 -1
  34. package/dist/{env-list-DhbYisDn.mjs → env-list-DYCprcLb.mjs} +2 -2
  35. package/dist/{env-list-DhbYisDn.mjs.map → env-list-DYCprcLb.mjs.map} +1 -1
  36. package/dist/{events-C7wAGJae.mjs → events-CnWvxyX_.mjs} +2 -2
  37. package/dist/{events-C7wAGJae.mjs.map → events-CnWvxyX_.mjs.map} +1 -1
  38. package/dist/{generator-MX8MAHd9.mjs → generator-DAU5K77L.mjs} +2 -2
  39. package/dist/{generator-MX8MAHd9.mjs.map → generator-DAU5K77L.mjs.map} +1 -1
  40. package/dist/{import-Bzow4TPf.mjs → import-BNbHjR9t.mjs} +6 -6
  41. package/dist/{import-Bzow4TPf.mjs.map → import-BNbHjR9t.mjs.map} +1 -1
  42. package/dist/index.d.mts +39 -0
  43. package/dist/index.d.mts.map +1 -1
  44. package/dist/{logpush-job-GqVKG_HI.mjs → logpush-job-C_6uzGUC.mjs} +2 -2
  45. package/dist/{logpush-job-GqVKG_HI.mjs.map → logpush-job-C_6uzGUC.mjs.map} +1 -1
  46. package/dist/{migrate-YfRtATkG.mjs → migrate-EVfFlJOM.mjs} +6 -6
  47. package/dist/{migrate-YfRtATkG.mjs.map → migrate-EVfFlJOM.mjs.map} +1 -1
  48. package/dist/normalize-DVSTRZhO.mjs.map +1 -1
  49. package/dist/{plan-C0XRZK_J.mjs → plan-tnUWkiM1.mjs} +12 -12
  50. package/dist/{plan-C0XRZK_J.mjs.map → plan-tnUWkiM1.mjs.map} +1 -1
  51. package/dist/{planFormat-5XMJK879.mjs → planFormat-DpA8XhzX.mjs} +1 -1
  52. package/dist/{planFormat-5XMJK879.mjs.map → planFormat-DpA8XhzX.mjs.map} +1 -1
  53. package/dist/{provision-tenant-B4VgWlbl.mjs → provision-tenant-R6Xa3IUJ.mjs} +73 -13
  54. package/dist/provision-tenant-R6Xa3IUJ.mjs.map +1 -0
  55. package/dist/{r2S3EmptyBucket-B9_pHfvB.mjs → r2S3EmptyBucket-CXLmOrYF.mjs} +1 -1
  56. package/dist/{r2S3EmptyBucket-B9_pHfvB.mjs.map → r2S3EmptyBucket-CXLmOrYF.mjs.map} +1 -1
  57. package/dist/{registry-BrOxbA2i.mjs → registry-X9dlQxG3.mjs} +4 -4
  58. package/dist/{registry-BrOxbA2i.mjs.map → registry-X9dlQxG3.mjs.map} +1 -1
  59. package/dist/resolveTenantBindings-4grVKHIG.mjs +58 -0
  60. package/dist/resolveTenantBindings-4grVKHIG.mjs.map +1 -0
  61. package/dist/{stackOutputs-CkpNSng8.mjs → stackOutputs-CU2oxjpU.mjs} +1 -1
  62. package/dist/{stackOutputs-CkpNSng8.mjs.map → stackOutputs-CU2oxjpU.mjs.map} +1 -1
  63. package/dist/{status-B-ei_QXO.mjs → status-srUxsBIB.mjs} +7 -7
  64. package/dist/{status-B-ei_QXO.mjs.map → status-srUxsBIB.mjs.map} +1 -1
  65. package/dist/sync-CfNyelDN.mjs +7 -0
  66. package/dist/{sync-kl7MaCQV.mjs → sync-DfJGkOME.mjs} +6 -6
  67. package/dist/{sync-kl7MaCQV.mjs.map → sync-DfJGkOME.mjs.map} +1 -1
  68. package/dist/tamer.mjs +40 -28
  69. package/dist/tamer.mjs.map +1 -1
  70. package/dist/{tamerArtifactsR2-COndFmk5.mjs → tamerArtifactsR2-B9myb-IA.mjs} +2 -2
  71. package/dist/{tamerArtifactsR2-COndFmk5.mjs.map → tamerArtifactsR2-B9myb-IA.mjs.map} +1 -1
  72. package/dist/{tenant-0dRh3gLI.mjs → tenant-MWIs0esz.mjs} +2 -2
  73. package/dist/{tenant-0dRh3gLI.mjs.map → tenant-MWIs0esz.mjs.map} +1 -1
  74. package/dist/tenant-migrate-BfvYL0HH.mjs +88 -0
  75. package/dist/tenant-migrate-BfvYL0HH.mjs.map +1 -0
  76. package/dist/{types-BpTmpzBy.mjs → types-BPxuutXk.mjs} +6 -6
  77. package/dist/{types-BpTmpzBy.mjs.map → types-BPxuutXk.mjs.map} +1 -1
  78. package/dist/{verifyPlanFile-D_-Qbh1J.mjs → verifyPlanFile-CoAOsD3W.mjs} +2 -2
  79. package/dist/{verifyPlanFile-D_-Qbh1J.mjs.map → verifyPlanFile-CoAOsD3W.mjs.map} +1 -1
  80. package/dist/{wfp-delete-COWm9F8p.mjs → wfp-delete-CwWQFxxj.mjs} +2 -2
  81. package/dist/{wfp-delete-COWm9F8p.mjs.map → wfp-delete-CwWQFxxj.mjs.map} +1 -1
  82. package/dist/{wfp-put-Dzs2zAj2.mjs → wfp-put-BBitXJep.mjs} +3 -3
  83. package/dist/{wfp-put-Dzs2zAj2.mjs.map → wfp-put-BBitXJep.mjs.map} +1 -1
  84. package/dist/{worker-route-CUQBu9xe.mjs → worker-route-CvuUPq1k.mjs} +3 -3
  85. package/dist/{worker-route-CUQBu9xe.mjs.map → worker-route-CvuUPq1k.mjs.map} +1 -1
  86. package/dist/{workers-DWXnZAzG.mjs → workers-DSlrKeNL.mjs} +3 -3
  87. package/dist/{workers-DWXnZAzG.mjs.map → workers-DSlrKeNL.mjs.map} +1 -1
  88. package/dist/wranglerOutFile-f08VsoAj.mjs +3 -0
  89. package/dist/{wranglerSpawn-Dx4I0Wu-.mjs → wranglerSpawn-DWdgrsmQ.mjs} +2 -2
  90. package/dist/{wranglerSpawn-Dx4I0Wu-.mjs.map → wranglerSpawn-DWdgrsmQ.mjs.map} +1 -1
  91. package/dist/wranglerSpawn-aARBLHpA.mjs +3 -0
  92. package/dist/{zoneResolver-D9bz6-0l.mjs → zoneResolver-VoxLHM4N.mjs} +1 -1
  93. package/dist/{zoneResolver-D9bz6-0l.mjs.map → zoneResolver-VoxLHM4N.mjs.map} +1 -1
  94. package/package.json +1 -1
  95. package/dist/buildDispatchUploadForm-D9ZrefZX.mjs.map +0 -1
  96. package/dist/drift-CryXFwSh.mjs +0 -8
  97. package/dist/provision-tenant-B4VgWlbl.mjs.map +0 -1
  98. package/dist/sync-Bky8pptf.mjs +0 -7
  99. /package/dist/{secrets-CnzjvndT.mjs → secrets-2Hy5LMHs.mjs} +0 -0
package/README.md CHANGED
@@ -67,15 +67,17 @@ tamer drift Compare state vs Cloudflare and report differences (read-only)
67
67
  tamer plan CloudFormation-style preview: what `apply`+`deploy` would create (read-only)
68
68
  tamer import Register an existing Cloudflare resource into state by logical name
69
69
  tamer doctor Verify `CLOUDFLARE_*` credentials against the account API (`--json` supported)
70
- tamer provision-tenant Runtime: create tenant D1 + upload dispatch script (`--main` or `--artifact-key`)
71
- tamer destroy-tenant Runtime: remove tenant script + D1 + state (shared envs: `--confirm-tenant`)
72
- tamer destroy Remove workers + storage + namespaces for an env
73
- tamer wfp put Upload a single-module Worker to a dispatch namespace
74
- tamer wfp delete Delete a Worker from a dispatch namespace
70
+ tamer destroy Remove workers + storage + namespaces for an env
71
+ tamer wfp put Upload a single-module Worker to a dispatch namespace
72
+ tamer wfp delete Delete a Worker from a dispatch namespace
73
+ tamer wfp tenant provision Runtime: create tenant D1 + upload dispatch script (`--main` or `--artifact-key`)
74
+ tamer wfp tenant destroy Runtime: remove tenant script + D1 + state (shared envs: `--confirm-tenant`)
75
+ tamer wfp tenant list Runtime: list tenants (`product:workspace`) and their provisioning status (`--json`)
76
+ tamer wfp tenant status Runtime: show one tenant's dispatch script, D1 shards, and provisioning status
75
77
  tamer secrets Encrypted vault: init, set, load, get, list, rm, verify, push (see docs/secrets.md)
76
78
  ```
77
79
 
78
- Common flags: `--env <name>` (required for every command; `tamer deploy --env <name>` no longer silently defaults to `prod`), `--worker <name>`, `--config <path>`, `--force`, `--confirm-env <name>` (destroy: required for any env in `tenant.protectedEnvs` from `tamer.config.ts` — defaults to `["prod","production"]` when unset; pass `protectedEnvs: ["prod","production","production-eu","qa"]` etc. to widen the gate, or `[]` to opt out — unless `--force`), `--confirm-tenant <workspace>` (destroy-tenant: same rule, sourced from `tenant.protectedEnvs`), `--skip-workers` (destroy), `--wipe-metadata` (destroy: delete shared `tamer-state-<env>` D1 **and** `tamer-artifacts-<env>` R2; use on the last stack in a multi-stack teardown), `--dispatch-namespace <name>` (deploy), `--json` (drift / plan: emit machine-readable JSON), `--detailed-exitcode` (plan: exit `2` instead of `0` when there are pending changes — Terraform-style CI gate), `--destroy` (plan: preview deletions instead of creates/updates — read-only `tamer destroy` dry-run).
80
+ Common flags: `--env <name>` (required for every command; `tamer deploy --env <name>` no longer silently defaults to `prod`), `--worker <name>`, `--config <path>`, `--force`, `--confirm-env <name>` (destroy: required for any env in `tenant.protectedEnvs` from `tamer.config.ts` — defaults to `["prod","production"]` when unset; pass `protectedEnvs: ["prod","production","production-eu","qa"]` etc. to widen the gate, or `[]` to opt out — unless `--force`), `--confirm-tenant <workspace>` (wfp tenant destroy: same rule, sourced from `tenant.protectedEnvs`), `--skip-workers` (destroy), `--wipe-metadata` (destroy: delete shared `tamer-state-<env>` D1 **and** `tamer-artifacts-<env>` R2; use on the last stack in a multi-stack teardown), `--dispatch-namespace <name>` (deploy), `--json` (drift / plan: emit machine-readable JSON), `--detailed-exitcode` (plan: exit `2` instead of `0` when there are pending changes — Terraform-style CI gate), `--destroy` (plan: preview deletions instead of creates/updates — read-only `tamer destroy` dry-run).
79
81
 
80
82
  `tamer import` flags: `--kind d1|r2|kv|queue|hyperdrive|vectorize|ai_gateway|pipeline|workflow|secret_store|dns_record|dispatch_namespace|worker_route`, `--logical <name>` (logical name from `tamer.config.ts`, or worker key for `worker_route`), `--cf-id <id>` (D1 uuid, KV id, R2 bucket name, queue id, hyperdrive config id, vectorize index name, AI gateway id, pipeline id, workflow id, secrets store id, DNS record id, or dispatch namespace name), `--shard-date <YYYY-MM-DD>` (sharded D1 only), `--created-date <YYYY-MM-DD>` (R2: optional, defaults to extracting from name), `--route-id <id>` and `--zone-name <z>` (worker_route).
81
83
 
@@ -419,8 +421,8 @@ To opt out per env, set `skipEnvs` (default `["local"]`) or `prodEnvs` (default
419
421
 
420
422
  `CfiState` (D1 `tamer-state-{env}`) includes a `tenants` map keyed `product:workspace` for signup-time resources **not** declared in `tamer.config.ts`.
421
423
 
422
- - `tamer provision-tenant --env dev --product todo --workspace acme --main ./worker.js` — for every role declared in `tenant.d1Shards` in `tamer.config.ts`, creates the per-tenant D1 (`db_{role}_{w}_{p}_t_{tid}_{env}`); then uploads the dispatch script to the first configured dispatch namespace and records `TenantStateEntry` as `ready`. Tamer ships **no built-in shard layout** — the engine is opinion-free, so a Dragoncore-style product picks `d1Shards: ["system", "app", "history"]`, a single-DB tenant picks `["main"]`, a billing/content split picks `["billing", "content"]`, and a tenant that needs no per-tenant DB at all simply omits `d1Shards` and gets the dispatch script alone. Use `--artifact-key <path/in/tamer-artifacts-{env}>` instead of `--main` to deploy from R2. Pass `--shards a,b` to **trim** the configured layout to a subset for ephemeral previews; the CLI flag cannot extend the configured set (config is the source of truth — typos surface with the configured roles in the error message). Re-runs are idempotent: existing shards are adopted, missing shards are added in place, so editing `tenant.d1Shards` to add a new role and re-running picks up the new shard without disturbing the others. Pass `--json` for a single trailing JSON line on stdout `{ status: "ready"|"noop"|"failed", tenantKey, scriptName, dispatchNamespaceName, shards: [{role, derivedName, cfId}] }` — designed for the Cloudflare Container caller (`provision-workflow`, see [container invocation contract](#container-invocation-contract)).
423
- - `tamer destroy-tenant --env dev --product todo --workspace acme --confirm-tenant acme` — deletes the dispatch script, every tenant D1 shard recorded in state, and removes the tenant record (shared envs require confirmation or `--force`). Pass `--json` for `{ status: "destroyed"|"noop"|"failed", removed: { scriptName, dispatchNamespaceName, shards }, errors }`.
424
+ - `tamer wfp tenant provision --env dev --product todo --workspace acme --main ./worker.js` — for every role declared in `tenant.d1Shards` in `tamer.config.ts`, creates the per-tenant D1 (`db_{role}_{w}_{p}_t_{tid}_{env}`); then uploads the dispatch script to the first configured dispatch namespace and records `TenantStateEntry` as `ready`. Tamer ships **no built-in shard layout** — the engine is opinion-free, so a Dragoncore-style product picks `d1Shards: ["system", "app", "history"]`, a single-DB tenant picks `["main"]`, a billing/content split picks `["billing", "content"]`, and a tenant that needs no per-tenant DB at all simply omits `d1Shards` and gets the dispatch script alone. Use `--artifact-key <path/in/tamer-artifacts-{env}>` instead of `--main` to deploy from R2. Pass `--shards a,b` to **trim** the configured layout to a subset for ephemeral previews; the CLI flag cannot extend the configured set (config is the source of truth — typos surface with the configured roles in the error message). Re-runs are idempotent: existing shards are adopted, missing shards are added in place, so editing `tenant.d1Shards` to add a new role and re-running picks up the new shard without disturbing the others. Pass `--json` for a single trailing JSON line on stdout `{ status: "ready"|"noop"|"failed", tenantKey, scriptName, dispatchNamespaceName, shards: [{role, derivedName, cfId}] }` — designed for the Cloudflare Container caller (`provision-workflow`, see [container invocation contract](#container-invocation-contract)).
425
+ - `tamer wfp tenant destroy --env dev --product todo --workspace acme --confirm-tenant acme` — deletes the dispatch script, every tenant D1 shard recorded in state, and removes the tenant record (shared envs require confirmation or `--force`). Pass `--json` for `{ status: "destroyed"|"noop"|"failed", removed: { scriptName, dispatchNamespaceName, shards }, errors }`.
424
426
  - `tamer status --env dev --tenant todo:acme` — shows provisioning status and bound resources.
425
427
 
426
428
  #### Container invocation contract
@@ -432,7 +434,7 @@ docker build -t tamer .
432
434
  docker run --rm \
433
435
  -e CLOUDFLARE_ACCOUNT_ID -e CLOUDFLARE_API_TOKEN \
434
436
  -v "$PWD":/work -w /work \
435
- tamer provision-tenant --env dev --product todo --workspace acme \
437
+ tamer wfp tenant provision --env dev --product todo --workspace acme \
436
438
  --artifact-key todo/worker.js --json
437
439
  ```
438
440
 
@@ -441,13 +443,13 @@ Contract:
441
443
  - **Exit code is the source of truth.** `0` on success or no-op, non-zero on failure. `provision-workflow` should branch on this, not on log scraping.
442
444
  - **Stdout's last line is a JSON envelope** when `--json` is passed (other lines remain human-readable). Failure path also emits an envelope (`status: "failed"`, `error: <message>`) before the non-zero exit.
443
445
  - **No interactive prompts.** Shared-env safety still applies — destroy callers must pass `--confirm-tenant <workspace>` (or `--force`) explicitly.
444
- - **Idempotent.** Workflow retries are safe: re-invoking `provision-tenant` with the same args produces `status: "noop"` once the tenant is `ready`; partial-failure resumes pick up from the last persisted shard.
446
+ - **Idempotent.** Workflow retries are safe: re-invoking `wfp tenant provision` with the same args produces `status: "noop"` once the tenant is `ready`; partial-failure resumes pick up from the last persisted shard.
445
447
 
446
448
  If two writers update state concurrently, `persist` throws `StateConflictError` and the CLI exits with code **3**; re-run after refreshing state.
447
449
 
448
450
  ### Plan (`tamer plan`)
449
451
 
450
- CloudFormation-style preview of `apply` + `deploy`. Reads only — never mutates state or Cloudflare. Shows every D1 / R2 / KV / dispatch namespace / Workers zone route declared in `tamer.config.ts` that isn't yet on Cloudflare, plus declared **global Worker scripts** that have no deployment (queried directly via `GET /accounts/.../workers/scripts/{name}`; dispatch-namespace tenant scripts are skipped — those belong to `provision-tenant` / WFP).
452
+ CloudFormation-style preview of `apply` + `deploy`. Reads only — never mutates state or Cloudflare. Shows every D1 / R2 / KV / dispatch namespace / Workers zone route declared in `tamer.config.ts` that isn't yet on Cloudflare, plus declared **global Worker scripts** that have no deployment (queried directly via `GET /accounts/.../workers/scripts/{name}`; dispatch-namespace tenant scripts are skipped — those belong to `wfp tenant provision` / WFP).
451
453
 
452
454
  ```bash
453
455
  tamer plan --env dev # human-readable summary, exit 0
@@ -482,7 +484,7 @@ Summary: 0 to create, 1 to update, 1 to replace.
482
484
 
483
485
  #### Destroy preview (`tamer plan --destroy`)
484
486
 
485
- Mutually exclusive with the forward plan. Walks `tamer-state-{env}`, filters to entries owned by the current `tamer.config.ts` stack (same scoping as `tamer destroy` / `tamer drift`), and emits a `delete` `PlanItem` per managed kind — D1, R2, KV, Queues, Hyperdrive, Vectorize, AI Gateway, Pipelines, Workflows, Secrets Stores, dispatch namespaces, DNS records, and zone `tamerRoutes`. Declared global Worker scripts that currently exist on Cloudflare also appear as `worker_script` deletes. Dispatch-namespace tenant scripts are intentionally excluded — those are managed via `provision-tenant` / `destroy-tenant`. Read-only: never mutates state or Cloudflare.
487
+ Mutually exclusive with the forward plan. Walks `tamer-state-{env}`, filters to entries owned by the current `tamer.config.ts` stack (same scoping as `tamer destroy` / `tamer drift`), and emits a `delete` `PlanItem` per managed kind — D1, R2, KV, Queues, Hyperdrive, Vectorize, AI Gateway, Pipelines, Workflows, Secrets Stores, dispatch namespaces, DNS records, and zone `tamerRoutes`. Declared global Worker scripts that currently exist on Cloudflare also appear as `worker_script` deletes. Dispatch-namespace tenant scripts are intentionally excluded — those are managed via `wfp tenant provision` / `wfp tenant destroy`. Read-only: never mutates state or Cloudflare.
486
488
 
487
489
  `--out destroy.json` writes the destroy plan with the same `(config, state, cloudflare)` attestation as forward plans (`report.mode: "destroy"`); `tamer destroy --plan destroy.json` then recomputes the three hashes and refuses to proceed unless they all match (override with `--allow-stale`). Mode mismatch is **non-overridable**: `apply --plan` rejects destroy plans and `destroy --plan` rejects forward plans regardless of `--allow-stale`, so a saved plan can only ever execute the operation the operator reviewed.
488
490
 
@@ -1,18 +1,18 @@
1
1
  import { f as getDispatchNamespaces, m as getLogpushJobs, p as getDnsRecords } from "./normalize-DVSTRZhO.mjs";
2
- import { B as getConfigBaseDir, E as mergeWorkerConfigForResourcePick, H as loadConfig, L as CFApiClient, N as effectiveDispatchNamespaceName, O as resolveDeployedWorkerName, R as cloudflareAccountIdFromEnv, V as getWorkers, f as fetchStackImports, h as StateManager, k as resolveWorkerConfig, p as importedStackNames, u as namingFromConfig, w as stackNameForConfig } from "./tamer.mjs";
3
- import { n as resourceModules } from "./registry-BrOxbA2i.mjs";
4
- import "./r2S3EmptyBucket-B9_pHfvB.mjs";
5
- import { i as resetApplySummary, n as logApplyChange, r as printApplySummary } from "./planFormat-5XMJK879.mjs";
6
- import { n as writeWranglerJson, t as generateWranglerConfig } from "./generator-MX8MAHd9.mjs";
7
- import { n as emitShardRegistryOnApply } from "./emit-DDTQVfi_.mjs";
8
- import { a as effectiveDnsRecordProxied, i as effectiveDnsRecordComment, n as dnsRecordCommentMarker, o as effectiveDnsRecordTtl, r as dnsRecordStateKey, t as dnsRecordAppliesToEnv } from "./dns-records.resolve-DV6XBZf3.mjs";
9
- import { i as computeDnsRecordMutableChanges, r as parseApplyTarget, t as assertApplyTargetDeclared } from "./applyTarget-BkBg8MFW.mjs";
10
- import { a as logpushJobApply } from "./logpush-job-GqVKG_HI.mjs";
11
- import { n as resolveStackOutputs } from "./stackOutputs-CkpNSng8.mjs";
12
- import "./worker-route-CUQBu9xe.mjs";
13
- import { t as runSync } from "./sync-kl7MaCQV.mjs";
14
- import { i as hashCloudflareSnapshot, t as buildCloudflareSnapshot } from "./cloudflareSnapshot-CjXNMr4X.mjs";
15
- import { t as verifyPlanFile } from "./verifyPlanFile-D_-Qbh1J.mjs";
2
+ import { E as mergeWorkerConfigForResourcePick, H as getWorkers, O as resolveDeployedWorkerName, P as effectiveDispatchNamespaceName, R as CFApiClient, U as loadConfig, V as getConfigBaseDir, f as fetchStackImports, h as StateManager, k as resolveWorkerConfig, p as importedStackNames, u as namingFromConfig, w as stackNameForConfig, z as cloudflareAccountIdFromEnv } from "./tamer.mjs";
3
+ import { n as resourceModules } from "./registry-X9dlQxG3.mjs";
4
+ import "./r2S3EmptyBucket-CXLmOrYF.mjs";
5
+ import { i as resetApplySummary, n as logApplyChange, r as printApplySummary } from "./planFormat-DpA8XhzX.mjs";
6
+ import { n as writeWranglerJson, t as generateWranglerConfig } from "./generator-DAU5K77L.mjs";
7
+ import { n as emitShardRegistryOnApply } from "./emit-Dh68dvo5.mjs";
8
+ import { a as effectiveDnsRecordProxied, i as effectiveDnsRecordComment, n as dnsRecordCommentMarker, o as effectiveDnsRecordTtl, r as dnsRecordStateKey, t as dnsRecordAppliesToEnv } from "./dns-records.resolve-DwBR_1WI.mjs";
9
+ import { i as computeDnsRecordMutableChanges, r as parseApplyTarget, t as assertApplyTargetDeclared } from "./applyTarget-B1YPgkb3.mjs";
10
+ import { a as logpushJobApply } from "./logpush-job-C_6uzGUC.mjs";
11
+ import { n as resolveStackOutputs } from "./stackOutputs-CU2oxjpU.mjs";
12
+ import "./worker-route-CvuUPq1k.mjs";
13
+ import { t as runSync } from "./sync-DfJGkOME.mjs";
14
+ import { i as hashCloudflareSnapshot, t as buildCloudflareSnapshot } from "./cloudflareSnapshot-BAeNVohz.mjs";
15
+ import { t as verifyPlanFile } from "./verifyPlanFile-CoAOsD3W.mjs";
16
16
 
17
17
  //#region src/features/dispatch-namespace/dispatch-namespace.apply.ts
18
18
  function uniqueResolvedNamespaces(configs, env, tenant) {
@@ -432,4 +432,4 @@ async function rollbackCreatedResources(args) {
432
432
 
433
433
  //#endregion
434
434
  export { runApply };
435
- //# sourceMappingURL=apply-ByHaKpxD.mjs.map
435
+ //# sourceMappingURL=apply-CV4_3Jv4.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"apply-ByHaKpxD.mjs","names":["out: Array<{ config: DispatchNamespaceResourceConfig; name: string }>","adoptedEntry: DnsRecordStateEntry","patch: Partial<{\n content: string;\n ttl: number;\n proxied: boolean;\n priority: number;\n comment: string;\n }>","entry: DnsRecordStateEntry","target: ApplyTarget | undefined","opDetail: string[]"],"sources":["../src/features/dispatch-namespace/dispatch-namespace.apply.ts","../src/features/dns-records/dns-records.apply.ts","../src/cli/commands/apply.ts"],"sourcesContent":["import type { TenantMeta, DispatchNamespaceResourceConfig } from \"../../types.js\";\nimport type { StateManager } from \"../../core/state/StateManager.js\";\nimport type { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { effectiveDispatchNamespaceName } from \"./dispatch-namespace.resolve.js\";\n\nfunction uniqueResolvedNamespaces(\n configs: DispatchNamespaceResourceConfig[],\n env: string,\n tenant: TenantMeta,\n): Array<{ config: DispatchNamespaceResourceConfig; name: string }> {\n const seen = new Set<string>();\n const out: Array<{ config: DispatchNamespaceResourceConfig; name: string }> =\n [];\n for (const c of configs) {\n const name = effectiveDispatchNamespaceName(c, env, tenant);\n if (seen.has(name)) continue;\n seen.add(name);\n out.push({ config: c, name });\n }\n return out;\n}\n\nexport async function dispatchNamespaceApply(\n resources: DispatchNamespaceResourceConfig[],\n tenant: TenantMeta,\n env: string,\n api: CFApiClient,\n state: StateManager,\n): Promise<void> {\n if (resources.length === 0) return;\n if (env === \"local\") return;\n\n const unique = uniqueResolvedNamespaces(resources, env, tenant);\n const needsCreateLookup = unique.some(\n ({ name }) => !state.get(`dispatch_ns:${name}`),\n );\n\n // CF is the source of truth: avoid POSTing for namespaces that already exist\n // (state may be empty in CI / first-time apply). Cloudflare's create error\n // for duplicates conflates with \"invalid name\", so a pre-check is clearer.\n const existingNames = needsCreateLookup\n ? new Set((await api.dispatchNamespaceListAll()).map((n) => n.namespace_name))\n : new Set<string>();\n\n for (const { config, name } of unique) {\n const key = `dispatch_ns:${name}`;\n if (state.get(key)) continue;\n\n if (!existingNames.has(name)) {\n await api.dispatchNamespaceCreate(name);\n }\n\n state.set(key, {\n type: \"dispatch_namespace\",\n logicalName: config.logicalName,\n derivedName: name,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n });\n }\n}\n","import type {\n DnsRecordResourceConfig,\n DnsRecordStateEntry,\n TenantMeta,\n} from \"../../types.js\";\nimport type { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport type { StateManager } from \"../../core/state/StateManager.js\";\nimport {\n dnsRecordAppliesToEnv,\n dnsRecordCommentMarker,\n dnsRecordStateKey,\n effectiveDnsRecordComment,\n effectiveDnsRecordProxied,\n effectiveDnsRecordTtl,\n} from \"./dns-records.resolve.js\";\nimport { computeDnsRecordMutableChanges } from \"./dns-records.diff.js\";\nimport { logApplyChange } from \"../../core/plan/planFormat.js\";\n\n/**\n * Reconcile every declared DNS record for `env` with Cloudflare. For each\n * config entry:\n *\n * 1. **Skip** if `env` is excluded (always: `local`; or via `skipEnvs`).\n * 2. **Resume after state loss** by scanning `/zones/{id}/dns_records` for\n * any record carrying Tamer's attribution marker comment (`tamer:\n * <tenant>:<env>:<logical>`) on the matching `(type, name)` tuple, and\n * re-adopting it into state.\n * 3. **Create** when nothing matches in state or on the zone.\n * 4. **Patch** mutable fields (`content`, `ttl`, `proxied`, `priority`,\n * `comment`) when state knows about the record but its fields drifted.\n * 5. **Replace** (delete + create) when the record `type` itself changed —\n * Cloudflare rejects type changes via PATCH per the API docs.\n */\nexport async function dnsRecordApply(\n resources: DnsRecordResourceConfig[],\n tenant: TenantMeta,\n env: string,\n api: CFApiClient,\n state: StateManager,\n): Promise<void> {\n if (resources.length === 0) return;\n const applicable = resources.filter((r) => dnsRecordAppliesToEnv(r, env));\n if (applicable.length === 0) return;\n\n const zoneCache = new Map<\n string,\n Awaited<ReturnType<CFApiClient[\"zoneDnsRecordListAll\"]>>\n >();\n const loadZone = async (\n zoneId: string,\n ): Promise<\n Awaited<ReturnType<CFApiClient[\"zoneDnsRecordListAll\"]>>\n > => {\n let cached = zoneCache.get(zoneId);\n if (cached) return cached;\n cached = await api.zoneDnsRecordListAll(zoneId);\n zoneCache.set(zoneId, cached);\n return cached;\n };\n\n for (const config of applicable) {\n const stateKey = dnsRecordStateKey(config.zoneId, config.type, config.name);\n const expected = expectedFromConfig(config, tenant, env);\n const existingEntry = state.get(stateKey);\n\n const stateEntry =\n existingEntry?.type === \"dns_record\"\n ? (existingEntry as DnsRecordStateEntry)\n : undefined;\n\n if (stateEntry) {\n if (recordTypeChanged(stateEntry, config)) {\n logApplyChange({\n kind: \"dns_record\",\n action: \"replace\",\n logical: config.logicalName,\n derived: `${config.type} ${config.name}`,\n changes: [\n {\n field: \"type\",\n from: stateEntry.recordType,\n to: config.type,\n kind: \"immutable\",\n },\n ],\n });\n await api.zoneDnsRecordDelete(config.zoneId, stateEntry.recordId);\n const created = await api.zoneDnsRecordCreate(config.zoneId, {\n type: config.type,\n name: config.name,\n content: expected.content,\n ttl: expected.ttl,\n proxied: expected.proxied,\n priority: expected.priority,\n comment: expected.comment,\n });\n persistCreated(state, stateKey, config, expected, created.id);\n continue;\n }\n const patch = computePatch(stateEntry, expected);\n if (patch) {\n const changes = computeDnsRecordMutableChanges(\n stateEntry,\n config,\n tenant,\n env,\n );\n logApplyChange({\n kind: \"dns_record\",\n action: \"update\",\n logical: config.logicalName,\n derived: `${config.type} ${config.name}`,\n changes,\n });\n await api.zoneDnsRecordPatch(config.zoneId, stateEntry.recordId, patch);\n persistUpdated(state, stateKey, stateEntry, expected);\n }\n continue;\n }\n\n const live = await loadZone(config.zoneId);\n const marker = dnsRecordCommentMarker(tenant, env, config.logicalName);\n const adopted = live.find(\n (r) =>\n r.type === config.type &&\n normalizeName(r.name, config.name) &&\n typeof r.comment === \"string\" &&\n r.comment.startsWith(marker),\n );\n if (adopted) {\n const adoptedEntry: DnsRecordStateEntry = {\n type: \"dns_record\",\n logicalName: config.logicalName,\n zoneId: config.zoneId,\n recordType: config.type,\n name: adopted.name,\n content: adopted.content,\n ttl: adopted.ttl ?? expected.ttl,\n proxied: adopted.proxied ?? false,\n priority: adopted.priority,\n comment: adopted.comment ?? marker,\n recordId: adopted.id,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n state.set(stateKey, adoptedEntry);\n const patch = computePatch(adoptedEntry, expected);\n if (patch) {\n const changes = computeDnsRecordMutableChanges(\n adoptedEntry,\n config,\n tenant,\n env,\n );\n logApplyChange({\n kind: \"dns_record\",\n action: \"update\",\n logical: config.logicalName,\n derived: `${config.type} ${config.name}`,\n changes,\n });\n await api.zoneDnsRecordPatch(config.zoneId, adopted.id, patch);\n persistUpdated(state, stateKey, adoptedEntry, expected);\n }\n continue;\n }\n\n logApplyChange({\n kind: \"dns_record\",\n action: \"create\",\n logical: config.logicalName,\n derived: `${config.type} ${config.name}`,\n });\n const created = await api.zoneDnsRecordCreate(config.zoneId, {\n type: config.type,\n name: config.name,\n content: expected.content,\n ttl: expected.ttl,\n proxied: expected.proxied,\n priority: expected.priority,\n comment: expected.comment,\n });\n persistCreated(state, stateKey, config, expected, created.id);\n }\n}\n\ninterface ExpectedFields {\n content: string;\n ttl: number;\n proxied: boolean;\n priority?: number;\n comment: string;\n}\n\nfunction expectedFromConfig(\n config: DnsRecordResourceConfig,\n tenant: TenantMeta,\n env: string,\n): ExpectedFields {\n return {\n content: config.content,\n ttl: effectiveDnsRecordTtl(config),\n proxied: effectiveDnsRecordProxied(config),\n priority: config.priority,\n comment: effectiveDnsRecordComment(config, tenant, env),\n };\n}\n\nfunction recordTypeChanged(\n state: DnsRecordStateEntry,\n config: DnsRecordResourceConfig,\n): boolean {\n return state.recordType !== config.type;\n}\n\nfunction computePatch(\n state: DnsRecordStateEntry,\n expected: ExpectedFields,\n): Partial<{\n content: string;\n ttl: number;\n proxied: boolean;\n priority: number;\n comment: string;\n}> | undefined {\n const patch: Partial<{\n content: string;\n ttl: number;\n proxied: boolean;\n priority: number;\n comment: string;\n }> = {};\n if (state.content !== expected.content) patch.content = expected.content;\n if (state.ttl !== expected.ttl) patch.ttl = expected.ttl;\n if (state.proxied !== expected.proxied) patch.proxied = expected.proxied;\n if (\n expected.priority !== undefined &&\n state.priority !== expected.priority\n ) {\n patch.priority = expected.priority;\n }\n if (state.comment !== expected.comment) patch.comment = expected.comment;\n return Object.keys(patch).length > 0 ? patch : undefined;\n}\n\nfunction persistCreated(\n state: StateManager,\n key: string,\n config: DnsRecordResourceConfig,\n expected: ExpectedFields,\n recordId: string,\n): void {\n const ts = new Date().toISOString();\n const entry: DnsRecordStateEntry = {\n type: \"dns_record\",\n logicalName: config.logicalName,\n zoneId: config.zoneId,\n recordType: config.type,\n name: config.name,\n content: expected.content,\n ttl: expected.ttl,\n proxied: expected.proxied,\n priority: expected.priority,\n comment: expected.comment,\n recordId,\n createdAt: ts,\n updatedAt: ts,\n };\n state.set(key, entry);\n}\n\nfunction persistUpdated(\n state: StateManager,\n key: string,\n prior: DnsRecordStateEntry,\n expected: ExpectedFields,\n): void {\n const entry: DnsRecordStateEntry = {\n ...prior,\n content: expected.content,\n ttl: expected.ttl,\n proxied: expected.proxied,\n priority: expected.priority,\n comment: expected.comment,\n updatedAt: new Date().toISOString(),\n };\n state.set(key, entry);\n}\n\n/**\n * Cloudflare normalizes DNS names to FQDN ('app.example.com'). The user\n * may declare them either as an apex (`@`) or as a partial — match by\n * exact equality when both look fully-qualified, otherwise by suffix.\n */\nfunction normalizeName(cfName: string, configName: string): boolean {\n if (cfName === configName) return true;\n if (configName === \"@\") return true;\n return cfName.endsWith(`.${configName}`) || cfName.endsWith(configName);\n}\n","import { loadConfig, getWorkers, getConfigBaseDir } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { namingFromConfig } from \"../../core/config/namingFromConfig.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport {\n resolveWorkerConfig,\n resolveDeployedWorkerName,\n mergeWorkerConfigForResourcePick,\n} from \"../../core/config/resolver.js\";\nimport {\n generateWranglerConfig,\n writeWranglerJson,\n} from \"../../core/wrangler/generator.js\";\nimport type { WranglerConfig } from \"../../generated/wrangler-types.js\";\nimport { emitShardRegistryOnApply } from \"../../core/codegen/shardRegistry/index.js\";\nimport { dispatchNamespaceApply } from \"../../features/dispatch-namespace/index.js\";\nimport { dnsRecordApply } from \"../../features/dns-records/index.js\";\nimport {\n getDispatchNamespaces,\n getDnsRecords,\n getLogpushJobs,\n} from \"../../types.js\";\nimport { logpushJobApply } from \"../../features/logpush-job/index.js\";\nimport { resolveStackOutputs } from \"../../core/outputs/stackOutputs.js\";\nimport {\n fetchStackImports,\n importedStackNames,\n} from \"../../core/imports/fetchStackImports.js\";\nimport { runSync } from \"./sync.js\";\nimport { resourceModules } from \"../../core/registry/registry.js\";\nimport { hashCloudflareSnapshot } from \"../../core/plan/planFile.js\";\nimport { buildCloudflareSnapshot } from \"../../core/plan/cloudflareSnapshot.js\";\nimport { verifyPlanFile } from \"../../core/plan/verifyPlanFile.js\";\nimport {\n printApplySummary,\n resetApplySummary,\n} from \"../../core/plan/planFormat.js\";\nimport type { StateEntry } from \"../../types.js\";\nimport {\n parseApplyTarget,\n assertApplyTargetDeclared,\n type ApplyTarget,\n} from \"../../core/apply/applyTarget.js\";\n\nexport async function runApply(options: {\n env?: string;\n addShard?: string;\n configPath?: string;\n /**\n * Path to a plan file from `tamer plan --out`. Apply recomputes the\n * `(config, state)` attestation hashes and refuses to proceed if either\n * differs (override with `allowStale`).\n */\n planFile?: string;\n allowStale?: boolean;\n /**\n * Track every state entry created during this apply. On any failure,\n * delete the matching Cloudflare resources (best-effort) so partial\n * applies do not leak orphans into the account.\n */\n rollbackOnFailure?: boolean;\n /**\n * `kind:logicalName` — create/update only that declared resource; still\n * regenerates all wrangler.json files and resolves full `outputs:`.\n */\n target?: string;\n}): Promise<void> {\n const env = options.env ?? \"local\";\n const addShard = options.addShard;\n const configPath = options.configPath;\n\n const config = await loadConfig(configPath, { env });\n const baseDir = getConfigBaseDir();\n const accountId =\n config.account_id ?? cloudflareAccountIdFromEnv();\n if (!accountId) {\n throw new Error(\n \"account_id required in config or CLOUDFLARE_ACCOUNT_ID env var\",\n );\n }\n\n let target: ApplyTarget | undefined;\n if (options.target) {\n target = parseApplyTarget(options.target);\n if (options.planFile) {\n throw new Error(\n \"Cannot combine --plan with --target: plan files attest the full stack. Omit --target, or apply without --plan.\",\n );\n }\n }\n\n if (options.planFile) {\n const verifyApi = new CFApiClient(accountId);\n const verifyState = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n await verifyState.hydrate(verifyApi);\n const liveSnapshot =\n env === \"local\"\n ? undefined\n : await buildCloudflareSnapshot({\n config,\n env,\n api: verifyApi,\n baseDir,\n });\n verifyPlanFile({\n planPath: options.planFile,\n command: \"apply\",\n expectedMode: \"forward\",\n env,\n tenantId: config.tenant.id,\n config,\n stateAtPlanCheck: verifyState.load(),\n liveCloudflareHash: liveSnapshot\n ? hashCloudflareSnapshot(liveSnapshot)\n : undefined,\n allowStale: !!options.allowStale,\n });\n }\n\n if (env !== \"local\") {\n console.log(`Syncing state from Cloudflare for env: ${env}...`);\n await runSync({ env, configPath });\n }\n\n const api = new CFApiClient(accountId);\n const naming = namingFromConfig(config);\n const state = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n await state.hydrate(api);\n const referencedStacks = importedStackNames(config);\n if (referencedStacks.length > 0) {\n console.log(\n `Pre-fetching tamer:import outputs from stack(s): ${referencedStacks.join(\", \")}…`,\n );\n }\n const imports = await fetchStackImports(api, config, env);\n console.log(\"Applying…\");\n\n if (target) {\n if (addShard) {\n if (target.kind !== \"d1\") {\n throw new Error(\n \"--add-shard only applies to D1; drop --target or use --target d1:<logical>.\",\n );\n }\n if (addShard !== target.logical) {\n throw new Error(\n `--add-shard ${addShard} does not match --target d1:${target.logical}.`,\n );\n }\n }\n await assertApplyTargetDeclared(config, baseDir, target);\n }\n\n // Imports were pre-fetched above (before this block) so every subsequent\n // `${tamer:import:<stack>.<output>}` resolution is a pure map lookup.\n\n const opDetail: string[] = [];\n if (addShard) opDetail.push(`add-shard ${addShard}`);\n if (target) opDetail.push(`target=${target.kind}:${target.logical}`);\n state.beginOperation(\n \"apply\",\n opDetail.length ? opDetail.join(\"; \") : undefined,\n );\n\n if (target) {\n console.log(\n `Scoped apply: only ${target.kind}:${target.logical} (wrangler outputs still generated for every worker)`,\n );\n }\n\n const baselineKeys = options.rollbackOnFailure\n ? new Set(Object.keys(state.getAll()))\n : null;\n\n // Reset the per-action counter so the summary at the end of this run\n // reflects only what *this* `runApply` did (matters for tests and for\n // back-to-back apply invocations from a single process).\n resetApplySummary();\n\n try {\n const dispatchResources =\n !target\n ? getDispatchNamespaces(config)\n : target.kind === \"dispatch_namespace\"\n ? getDispatchNamespaces(config).filter(\n (n) => n.logicalName === target!.logical,\n )\n : [];\n\n await dispatchNamespaceApply(\n dispatchResources,\n config.tenant,\n env,\n api,\n state,\n );\n\n const dnsResources =\n !target\n ? getDnsRecords(config)\n : target.kind === \"dns_record\"\n ? getDnsRecords(config).filter(\n (d) => d.logicalName === target!.logical,\n )\n : [];\n\n await dnsRecordApply(\n dnsResources,\n config.tenant,\n env,\n api,\n state,\n );\n\n const workers = await getWorkers(config, baseDir);\n for (const [workerKey, workerConfig] of workers) {\n const deployedName = resolveDeployedWorkerName(\n config,\n workerKey,\n workerConfig,\n env,\n naming,\n );\n const mergedWorker = mergeWorkerConfigForResourcePick(\n config,\n workerKey,\n workerConfig,\n env,\n accountId,\n naming,\n state,\n { imports },\n );\n for (const mod of resourceModules) {\n if (target) {\n if (target.kind === \"logpush_job\") {\n if (mod.kind !== \"r2\") continue;\n } else if (mod.kind !== target.kind) {\n continue;\n }\n }\n const picked = mod.pickResources(mergedWorker);\n const resources =\n target && mod.kind === target.kind\n ? picked.filter(\n (r) =>\n (r as { logicalName?: string }).logicalName ===\n target.logical,\n )\n : picked;\n if (resources.length === 0) continue;\n await mod.apply({\n resources,\n tenant: config.tenant,\n env,\n api,\n state,\n naming,\n config,\n baseDir,\n addShard,\n worker: { workerKey, deployedName },\n });\n }\n }\n\n const logpushResources = !target\n ? getLogpushJobs(config)\n : target.kind === \"logpush_job\"\n ? getLogpushJobs(config).filter(\n (j) => j.logicalName === target.logical,\n )\n : [];\n\n await logpushJobApply(\n logpushResources,\n config.tenant,\n env,\n accountId,\n api,\n state,\n );\n\n await state.persist(api);\n\n const wranglerByWorker = new Map<string, WranglerConfig>();\n for (const [workerKey, workerConfig] of workers) {\n const resolved = await resolveWorkerConfig(\n config,\n workerKey,\n workerConfig,\n env,\n baseDir,\n accountId,\n naming,\n state,\n { imports },\n );\n const wranglerConfig = generateWranglerConfig(resolved, state, naming);\n writeWranglerJson(\n resolved.workerDir,\n wranglerConfig,\n resolved.wranglerOutFile,\n );\n wranglerByWorker.set(workerKey, wranglerConfig);\n console.log(`Generated ${resolved.wranglerOutFile} for ${workerKey}`);\n }\n\n await emitShardRegistryOnApply({\n config,\n env,\n baseDir,\n accountId,\n naming,\n state,\n imports,\n workers,\n wranglerByWorker,\n });\n\n // Resolve + persist `outputs:` last — every dependency is already in\n // state by this point, so a strict resolve here surfaces typos in\n // `outputs` as a fail-the-apply error instead of a silent half-write.\n // `replaceStackOutputs` is a no-op when nothing changed, so reruns\n // don't bump `revision` unnecessarily.\n const resolvedOutputs = resolveStackOutputs(config, {\n env,\n state,\n naming,\n imports,\n accountId,\n });\n state.replaceStackOutputs(resolvedOutputs);\n if (Object.keys(resolvedOutputs).length > 0) {\n console.log(\n `Stack outputs: resolved ${Object.keys(resolvedOutputs).length} (${Object.keys(resolvedOutputs).join(\", \")})`,\n );\n } else if (config.outputs == null) {\n // Nothing declared, nothing to print — quiet path.\n } else {\n // `outputs: {}` (empty record) — config explicitly cleared.\n console.log(\"Stack outputs: cleared (empty `outputs` block in config)\");\n }\n\n state.finishOperation();\n await state.persist(api);\n console.log(`Apply complete for env: ${env}`);\n printApplySummary();\n } catch (err) {\n if (baselineKeys) {\n await rollbackCreatedResources({\n api,\n state,\n baselineKeys,\n cause: err,\n });\n }\n state.failOperation(err instanceof Error ? err.message : String(err));\n try {\n await state.persist(api);\n } catch {\n /* swallow secondary persist failure */\n }\n throw err;\n }\n}\n\n/**\n * Best-effort undo for a partially failed apply. Walks every state entry\n * created during this run (i.e. not present at apply start) in reverse\n * insertion order and asks the owning module to delete it. Failures are\n * logged and swallowed — rollback never throws so the original error from\n * apply propagates intact.\n */\nasync function rollbackCreatedResources(args: {\n api: CFApiClient;\n state: StateManager;\n baselineKeys: Set<string>;\n cause: unknown;\n}): Promise<void> {\n const { api, state, baselineKeys, cause } = args;\n const all = state.getAll();\n const newKeys = Object.keys(all).filter((k) => !baselineKeys.has(k));\n if (newKeys.length === 0) {\n console.warn(\n `Apply failed; --rollback-on-failure had nothing to undo (no new state entries).`,\n );\n return;\n }\n const causeMsg = cause instanceof Error ? cause.message : String(cause);\n console.warn(\n `Apply failed (${causeMsg}); rolling back ${newKeys.length} new resource(s)...`,\n );\n const moduleByEntryType = new Map<\n StateEntry[\"type\"],\n (typeof resourceModules)[number]\n >(resourceModules.map((m) => [m.stateEntryType, m]));\n for (const key of newKeys.reverse()) {\n const entry = all[key];\n if (!entry) continue;\n if (entry.type === \"logpush_job\") {\n try {\n await api.logpushAccountJobDelete(entry.cfJobId);\n state.delete(key);\n } catch (err) {\n console.warn(\n `Rollback: failed to delete Logpush job ${entry.cfJobId}:`,\n err,\n );\n }\n continue;\n }\n const mod = moduleByEntryType.get(entry.type);\n if (!mod) {\n console.warn(\n `Rollback: no module owns state type \"${entry.type}\" (key \"${key}\"); leaving in place.`,\n );\n continue;\n }\n await mod.destroyOne({ api, state, key, entry });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"apply-CV4_3Jv4.mjs","names":["out: Array<{ config: DispatchNamespaceResourceConfig; name: string }>","adoptedEntry: DnsRecordStateEntry","patch: Partial<{\n content: string;\n ttl: number;\n proxied: boolean;\n priority: number;\n comment: string;\n }>","entry: DnsRecordStateEntry","target: ApplyTarget | undefined","opDetail: string[]"],"sources":["../src/features/dispatch-namespace/dispatch-namespace.apply.ts","../src/features/dns-records/dns-records.apply.ts","../src/cli/commands/apply.ts"],"sourcesContent":["import type { TenantMeta, DispatchNamespaceResourceConfig } from \"../../types.js\";\nimport type { StateManager } from \"../../core/state/StateManager.js\";\nimport type { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { effectiveDispatchNamespaceName } from \"./dispatch-namespace.resolve.js\";\n\nfunction uniqueResolvedNamespaces(\n configs: DispatchNamespaceResourceConfig[],\n env: string,\n tenant: TenantMeta,\n): Array<{ config: DispatchNamespaceResourceConfig; name: string }> {\n const seen = new Set<string>();\n const out: Array<{ config: DispatchNamespaceResourceConfig; name: string }> =\n [];\n for (const c of configs) {\n const name = effectiveDispatchNamespaceName(c, env, tenant);\n if (seen.has(name)) continue;\n seen.add(name);\n out.push({ config: c, name });\n }\n return out;\n}\n\nexport async function dispatchNamespaceApply(\n resources: DispatchNamespaceResourceConfig[],\n tenant: TenantMeta,\n env: string,\n api: CFApiClient,\n state: StateManager,\n): Promise<void> {\n if (resources.length === 0) return;\n if (env === \"local\") return;\n\n const unique = uniqueResolvedNamespaces(resources, env, tenant);\n const needsCreateLookup = unique.some(\n ({ name }) => !state.get(`dispatch_ns:${name}`),\n );\n\n // CF is the source of truth: avoid POSTing for namespaces that already exist\n // (state may be empty in CI / first-time apply). Cloudflare's create error\n // for duplicates conflates with \"invalid name\", so a pre-check is clearer.\n const existingNames = needsCreateLookup\n ? new Set((await api.dispatchNamespaceListAll()).map((n) => n.namespace_name))\n : new Set<string>();\n\n for (const { config, name } of unique) {\n const key = `dispatch_ns:${name}`;\n if (state.get(key)) continue;\n\n if (!existingNames.has(name)) {\n await api.dispatchNamespaceCreate(name);\n }\n\n state.set(key, {\n type: \"dispatch_namespace\",\n logicalName: config.logicalName,\n derivedName: name,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n });\n }\n}\n","import type {\n DnsRecordResourceConfig,\n DnsRecordStateEntry,\n TenantMeta,\n} from \"../../types.js\";\nimport type { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport type { StateManager } from \"../../core/state/StateManager.js\";\nimport {\n dnsRecordAppliesToEnv,\n dnsRecordCommentMarker,\n dnsRecordStateKey,\n effectiveDnsRecordComment,\n effectiveDnsRecordProxied,\n effectiveDnsRecordTtl,\n} from \"./dns-records.resolve.js\";\nimport { computeDnsRecordMutableChanges } from \"./dns-records.diff.js\";\nimport { logApplyChange } from \"../../core/plan/planFormat.js\";\n\n/**\n * Reconcile every declared DNS record for `env` with Cloudflare. For each\n * config entry:\n *\n * 1. **Skip** if `env` is excluded (always: `local`; or via `skipEnvs`).\n * 2. **Resume after state loss** by scanning `/zones/{id}/dns_records` for\n * any record carrying Tamer's attribution marker comment (`tamer:\n * <tenant>:<env>:<logical>`) on the matching `(type, name)` tuple, and\n * re-adopting it into state.\n * 3. **Create** when nothing matches in state or on the zone.\n * 4. **Patch** mutable fields (`content`, `ttl`, `proxied`, `priority`,\n * `comment`) when state knows about the record but its fields drifted.\n * 5. **Replace** (delete + create) when the record `type` itself changed —\n * Cloudflare rejects type changes via PATCH per the API docs.\n */\nexport async function dnsRecordApply(\n resources: DnsRecordResourceConfig[],\n tenant: TenantMeta,\n env: string,\n api: CFApiClient,\n state: StateManager,\n): Promise<void> {\n if (resources.length === 0) return;\n const applicable = resources.filter((r) => dnsRecordAppliesToEnv(r, env));\n if (applicable.length === 0) return;\n\n const zoneCache = new Map<\n string,\n Awaited<ReturnType<CFApiClient[\"zoneDnsRecordListAll\"]>>\n >();\n const loadZone = async (\n zoneId: string,\n ): Promise<\n Awaited<ReturnType<CFApiClient[\"zoneDnsRecordListAll\"]>>\n > => {\n let cached = zoneCache.get(zoneId);\n if (cached) return cached;\n cached = await api.zoneDnsRecordListAll(zoneId);\n zoneCache.set(zoneId, cached);\n return cached;\n };\n\n for (const config of applicable) {\n const stateKey = dnsRecordStateKey(config.zoneId, config.type, config.name);\n const expected = expectedFromConfig(config, tenant, env);\n const existingEntry = state.get(stateKey);\n\n const stateEntry =\n existingEntry?.type === \"dns_record\"\n ? (existingEntry as DnsRecordStateEntry)\n : undefined;\n\n if (stateEntry) {\n if (recordTypeChanged(stateEntry, config)) {\n logApplyChange({\n kind: \"dns_record\",\n action: \"replace\",\n logical: config.logicalName,\n derived: `${config.type} ${config.name}`,\n changes: [\n {\n field: \"type\",\n from: stateEntry.recordType,\n to: config.type,\n kind: \"immutable\",\n },\n ],\n });\n await api.zoneDnsRecordDelete(config.zoneId, stateEntry.recordId);\n const created = await api.zoneDnsRecordCreate(config.zoneId, {\n type: config.type,\n name: config.name,\n content: expected.content,\n ttl: expected.ttl,\n proxied: expected.proxied,\n priority: expected.priority,\n comment: expected.comment,\n });\n persistCreated(state, stateKey, config, expected, created.id);\n continue;\n }\n const patch = computePatch(stateEntry, expected);\n if (patch) {\n const changes = computeDnsRecordMutableChanges(\n stateEntry,\n config,\n tenant,\n env,\n );\n logApplyChange({\n kind: \"dns_record\",\n action: \"update\",\n logical: config.logicalName,\n derived: `${config.type} ${config.name}`,\n changes,\n });\n await api.zoneDnsRecordPatch(config.zoneId, stateEntry.recordId, patch);\n persistUpdated(state, stateKey, stateEntry, expected);\n }\n continue;\n }\n\n const live = await loadZone(config.zoneId);\n const marker = dnsRecordCommentMarker(tenant, env, config.logicalName);\n const adopted = live.find(\n (r) =>\n r.type === config.type &&\n normalizeName(r.name, config.name) &&\n typeof r.comment === \"string\" &&\n r.comment.startsWith(marker),\n );\n if (adopted) {\n const adoptedEntry: DnsRecordStateEntry = {\n type: \"dns_record\",\n logicalName: config.logicalName,\n zoneId: config.zoneId,\n recordType: config.type,\n name: adopted.name,\n content: adopted.content,\n ttl: adopted.ttl ?? expected.ttl,\n proxied: adopted.proxied ?? false,\n priority: adopted.priority,\n comment: adopted.comment ?? marker,\n recordId: adopted.id,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n state.set(stateKey, adoptedEntry);\n const patch = computePatch(adoptedEntry, expected);\n if (patch) {\n const changes = computeDnsRecordMutableChanges(\n adoptedEntry,\n config,\n tenant,\n env,\n );\n logApplyChange({\n kind: \"dns_record\",\n action: \"update\",\n logical: config.logicalName,\n derived: `${config.type} ${config.name}`,\n changes,\n });\n await api.zoneDnsRecordPatch(config.zoneId, adopted.id, patch);\n persistUpdated(state, stateKey, adoptedEntry, expected);\n }\n continue;\n }\n\n logApplyChange({\n kind: \"dns_record\",\n action: \"create\",\n logical: config.logicalName,\n derived: `${config.type} ${config.name}`,\n });\n const created = await api.zoneDnsRecordCreate(config.zoneId, {\n type: config.type,\n name: config.name,\n content: expected.content,\n ttl: expected.ttl,\n proxied: expected.proxied,\n priority: expected.priority,\n comment: expected.comment,\n });\n persistCreated(state, stateKey, config, expected, created.id);\n }\n}\n\ninterface ExpectedFields {\n content: string;\n ttl: number;\n proxied: boolean;\n priority?: number;\n comment: string;\n}\n\nfunction expectedFromConfig(\n config: DnsRecordResourceConfig,\n tenant: TenantMeta,\n env: string,\n): ExpectedFields {\n return {\n content: config.content,\n ttl: effectiveDnsRecordTtl(config),\n proxied: effectiveDnsRecordProxied(config),\n priority: config.priority,\n comment: effectiveDnsRecordComment(config, tenant, env),\n };\n}\n\nfunction recordTypeChanged(\n state: DnsRecordStateEntry,\n config: DnsRecordResourceConfig,\n): boolean {\n return state.recordType !== config.type;\n}\n\nfunction computePatch(\n state: DnsRecordStateEntry,\n expected: ExpectedFields,\n): Partial<{\n content: string;\n ttl: number;\n proxied: boolean;\n priority: number;\n comment: string;\n}> | undefined {\n const patch: Partial<{\n content: string;\n ttl: number;\n proxied: boolean;\n priority: number;\n comment: string;\n }> = {};\n if (state.content !== expected.content) patch.content = expected.content;\n if (state.ttl !== expected.ttl) patch.ttl = expected.ttl;\n if (state.proxied !== expected.proxied) patch.proxied = expected.proxied;\n if (\n expected.priority !== undefined &&\n state.priority !== expected.priority\n ) {\n patch.priority = expected.priority;\n }\n if (state.comment !== expected.comment) patch.comment = expected.comment;\n return Object.keys(patch).length > 0 ? patch : undefined;\n}\n\nfunction persistCreated(\n state: StateManager,\n key: string,\n config: DnsRecordResourceConfig,\n expected: ExpectedFields,\n recordId: string,\n): void {\n const ts = new Date().toISOString();\n const entry: DnsRecordStateEntry = {\n type: \"dns_record\",\n logicalName: config.logicalName,\n zoneId: config.zoneId,\n recordType: config.type,\n name: config.name,\n content: expected.content,\n ttl: expected.ttl,\n proxied: expected.proxied,\n priority: expected.priority,\n comment: expected.comment,\n recordId,\n createdAt: ts,\n updatedAt: ts,\n };\n state.set(key, entry);\n}\n\nfunction persistUpdated(\n state: StateManager,\n key: string,\n prior: DnsRecordStateEntry,\n expected: ExpectedFields,\n): void {\n const entry: DnsRecordStateEntry = {\n ...prior,\n content: expected.content,\n ttl: expected.ttl,\n proxied: expected.proxied,\n priority: expected.priority,\n comment: expected.comment,\n updatedAt: new Date().toISOString(),\n };\n state.set(key, entry);\n}\n\n/**\n * Cloudflare normalizes DNS names to FQDN ('app.example.com'). The user\n * may declare them either as an apex (`@`) or as a partial — match by\n * exact equality when both look fully-qualified, otherwise by suffix.\n */\nfunction normalizeName(cfName: string, configName: string): boolean {\n if (cfName === configName) return true;\n if (configName === \"@\") return true;\n return cfName.endsWith(`.${configName}`) || cfName.endsWith(configName);\n}\n","import { loadConfig, getWorkers, getConfigBaseDir } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { namingFromConfig } from \"../../core/config/namingFromConfig.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport {\n resolveWorkerConfig,\n resolveDeployedWorkerName,\n mergeWorkerConfigForResourcePick,\n} from \"../../core/config/resolver.js\";\nimport {\n generateWranglerConfig,\n writeWranglerJson,\n} from \"../../core/wrangler/generator.js\";\nimport type { WranglerConfig } from \"../../generated/wrangler-types.js\";\nimport { emitShardRegistryOnApply } from \"../../core/codegen/shardRegistry/index.js\";\nimport { dispatchNamespaceApply } from \"../../features/dispatch-namespace/index.js\";\nimport { dnsRecordApply } from \"../../features/dns-records/index.js\";\nimport {\n getDispatchNamespaces,\n getDnsRecords,\n getLogpushJobs,\n} from \"../../types.js\";\nimport { logpushJobApply } from \"../../features/logpush-job/index.js\";\nimport { resolveStackOutputs } from \"../../core/outputs/stackOutputs.js\";\nimport {\n fetchStackImports,\n importedStackNames,\n} from \"../../core/imports/fetchStackImports.js\";\nimport { runSync } from \"./sync.js\";\nimport { resourceModules } from \"../../core/registry/registry.js\";\nimport { hashCloudflareSnapshot } from \"../../core/plan/planFile.js\";\nimport { buildCloudflareSnapshot } from \"../../core/plan/cloudflareSnapshot.js\";\nimport { verifyPlanFile } from \"../../core/plan/verifyPlanFile.js\";\nimport {\n printApplySummary,\n resetApplySummary,\n} from \"../../core/plan/planFormat.js\";\nimport type { StateEntry } from \"../../types.js\";\nimport {\n parseApplyTarget,\n assertApplyTargetDeclared,\n type ApplyTarget,\n} from \"../../core/apply/applyTarget.js\";\n\nexport async function runApply(options: {\n env?: string;\n addShard?: string;\n configPath?: string;\n /**\n * Path to a plan file from `tamer plan --out`. Apply recomputes the\n * `(config, state)` attestation hashes and refuses to proceed if either\n * differs (override with `allowStale`).\n */\n planFile?: string;\n allowStale?: boolean;\n /**\n * Track every state entry created during this apply. On any failure,\n * delete the matching Cloudflare resources (best-effort) so partial\n * applies do not leak orphans into the account.\n */\n rollbackOnFailure?: boolean;\n /**\n * `kind:logicalName` — create/update only that declared resource; still\n * regenerates all wrangler.json files and resolves full `outputs:`.\n */\n target?: string;\n}): Promise<void> {\n const env = options.env ?? \"local\";\n const addShard = options.addShard;\n const configPath = options.configPath;\n\n const config = await loadConfig(configPath, { env });\n const baseDir = getConfigBaseDir();\n const accountId =\n config.account_id ?? cloudflareAccountIdFromEnv();\n if (!accountId) {\n throw new Error(\n \"account_id required in config or CLOUDFLARE_ACCOUNT_ID env var\",\n );\n }\n\n let target: ApplyTarget | undefined;\n if (options.target) {\n target = parseApplyTarget(options.target);\n if (options.planFile) {\n throw new Error(\n \"Cannot combine --plan with --target: plan files attest the full stack. Omit --target, or apply without --plan.\",\n );\n }\n }\n\n if (options.planFile) {\n const verifyApi = new CFApiClient(accountId);\n const verifyState = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n await verifyState.hydrate(verifyApi);\n const liveSnapshot =\n env === \"local\"\n ? undefined\n : await buildCloudflareSnapshot({\n config,\n env,\n api: verifyApi,\n baseDir,\n });\n verifyPlanFile({\n planPath: options.planFile,\n command: \"apply\",\n expectedMode: \"forward\",\n env,\n tenantId: config.tenant.id,\n config,\n stateAtPlanCheck: verifyState.load(),\n liveCloudflareHash: liveSnapshot\n ? hashCloudflareSnapshot(liveSnapshot)\n : undefined,\n allowStale: !!options.allowStale,\n });\n }\n\n if (env !== \"local\") {\n console.log(`Syncing state from Cloudflare for env: ${env}...`);\n await runSync({ env, configPath });\n }\n\n const api = new CFApiClient(accountId);\n const naming = namingFromConfig(config);\n const state = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n await state.hydrate(api);\n const referencedStacks = importedStackNames(config);\n if (referencedStacks.length > 0) {\n console.log(\n `Pre-fetching tamer:import outputs from stack(s): ${referencedStacks.join(\", \")}…`,\n );\n }\n const imports = await fetchStackImports(api, config, env);\n console.log(\"Applying…\");\n\n if (target) {\n if (addShard) {\n if (target.kind !== \"d1\") {\n throw new Error(\n \"--add-shard only applies to D1; drop --target or use --target d1:<logical>.\",\n );\n }\n if (addShard !== target.logical) {\n throw new Error(\n `--add-shard ${addShard} does not match --target d1:${target.logical}.`,\n );\n }\n }\n await assertApplyTargetDeclared(config, baseDir, target);\n }\n\n // Imports were pre-fetched above (before this block) so every subsequent\n // `${tamer:import:<stack>.<output>}` resolution is a pure map lookup.\n\n const opDetail: string[] = [];\n if (addShard) opDetail.push(`add-shard ${addShard}`);\n if (target) opDetail.push(`target=${target.kind}:${target.logical}`);\n state.beginOperation(\n \"apply\",\n opDetail.length ? opDetail.join(\"; \") : undefined,\n );\n\n if (target) {\n console.log(\n `Scoped apply: only ${target.kind}:${target.logical} (wrangler outputs still generated for every worker)`,\n );\n }\n\n const baselineKeys = options.rollbackOnFailure\n ? new Set(Object.keys(state.getAll()))\n : null;\n\n // Reset the per-action counter so the summary at the end of this run\n // reflects only what *this* `runApply` did (matters for tests and for\n // back-to-back apply invocations from a single process).\n resetApplySummary();\n\n try {\n const dispatchResources =\n !target\n ? getDispatchNamespaces(config)\n : target.kind === \"dispatch_namespace\"\n ? getDispatchNamespaces(config).filter(\n (n) => n.logicalName === target!.logical,\n )\n : [];\n\n await dispatchNamespaceApply(\n dispatchResources,\n config.tenant,\n env,\n api,\n state,\n );\n\n const dnsResources =\n !target\n ? getDnsRecords(config)\n : target.kind === \"dns_record\"\n ? getDnsRecords(config).filter(\n (d) => d.logicalName === target!.logical,\n )\n : [];\n\n await dnsRecordApply(\n dnsResources,\n config.tenant,\n env,\n api,\n state,\n );\n\n const workers = await getWorkers(config, baseDir);\n for (const [workerKey, workerConfig] of workers) {\n const deployedName = resolveDeployedWorkerName(\n config,\n workerKey,\n workerConfig,\n env,\n naming,\n );\n const mergedWorker = mergeWorkerConfigForResourcePick(\n config,\n workerKey,\n workerConfig,\n env,\n accountId,\n naming,\n state,\n { imports },\n );\n for (const mod of resourceModules) {\n if (target) {\n if (target.kind === \"logpush_job\") {\n if (mod.kind !== \"r2\") continue;\n } else if (mod.kind !== target.kind) {\n continue;\n }\n }\n const picked = mod.pickResources(mergedWorker);\n const resources =\n target && mod.kind === target.kind\n ? picked.filter(\n (r) =>\n (r as { logicalName?: string }).logicalName ===\n target.logical,\n )\n : picked;\n if (resources.length === 0) continue;\n await mod.apply({\n resources,\n tenant: config.tenant,\n env,\n api,\n state,\n naming,\n config,\n baseDir,\n addShard,\n worker: { workerKey, deployedName },\n });\n }\n }\n\n const logpushResources = !target\n ? getLogpushJobs(config)\n : target.kind === \"logpush_job\"\n ? getLogpushJobs(config).filter(\n (j) => j.logicalName === target.logical,\n )\n : [];\n\n await logpushJobApply(\n logpushResources,\n config.tenant,\n env,\n accountId,\n api,\n state,\n );\n\n await state.persist(api);\n\n const wranglerByWorker = new Map<string, WranglerConfig>();\n for (const [workerKey, workerConfig] of workers) {\n const resolved = await resolveWorkerConfig(\n config,\n workerKey,\n workerConfig,\n env,\n baseDir,\n accountId,\n naming,\n state,\n { imports },\n );\n const wranglerConfig = generateWranglerConfig(resolved, state, naming);\n writeWranglerJson(\n resolved.workerDir,\n wranglerConfig,\n resolved.wranglerOutFile,\n );\n wranglerByWorker.set(workerKey, wranglerConfig);\n console.log(`Generated ${resolved.wranglerOutFile} for ${workerKey}`);\n }\n\n await emitShardRegistryOnApply({\n config,\n env,\n baseDir,\n accountId,\n naming,\n state,\n imports,\n workers,\n wranglerByWorker,\n });\n\n // Resolve + persist `outputs:` last — every dependency is already in\n // state by this point, so a strict resolve here surfaces typos in\n // `outputs` as a fail-the-apply error instead of a silent half-write.\n // `replaceStackOutputs` is a no-op when nothing changed, so reruns\n // don't bump `revision` unnecessarily.\n const resolvedOutputs = resolveStackOutputs(config, {\n env,\n state,\n naming,\n imports,\n accountId,\n });\n state.replaceStackOutputs(resolvedOutputs);\n if (Object.keys(resolvedOutputs).length > 0) {\n console.log(\n `Stack outputs: resolved ${Object.keys(resolvedOutputs).length} (${Object.keys(resolvedOutputs).join(\", \")})`,\n );\n } else if (config.outputs == null) {\n // Nothing declared, nothing to print — quiet path.\n } else {\n // `outputs: {}` (empty record) — config explicitly cleared.\n console.log(\"Stack outputs: cleared (empty `outputs` block in config)\");\n }\n\n state.finishOperation();\n await state.persist(api);\n console.log(`Apply complete for env: ${env}`);\n printApplySummary();\n } catch (err) {\n if (baselineKeys) {\n await rollbackCreatedResources({\n api,\n state,\n baselineKeys,\n cause: err,\n });\n }\n state.failOperation(err instanceof Error ? err.message : String(err));\n try {\n await state.persist(api);\n } catch {\n /* swallow secondary persist failure */\n }\n throw err;\n }\n}\n\n/**\n * Best-effort undo for a partially failed apply. Walks every state entry\n * created during this run (i.e. not present at apply start) in reverse\n * insertion order and asks the owning module to delete it. Failures are\n * logged and swallowed — rollback never throws so the original error from\n * apply propagates intact.\n */\nasync function rollbackCreatedResources(args: {\n api: CFApiClient;\n state: StateManager;\n baselineKeys: Set<string>;\n cause: unknown;\n}): Promise<void> {\n const { api, state, baselineKeys, cause } = args;\n const all = state.getAll();\n const newKeys = Object.keys(all).filter((k) => !baselineKeys.has(k));\n if (newKeys.length === 0) {\n console.warn(\n `Apply failed; --rollback-on-failure had nothing to undo (no new state entries).`,\n );\n return;\n }\n const causeMsg = cause instanceof Error ? cause.message : String(cause);\n console.warn(\n `Apply failed (${causeMsg}); rolling back ${newKeys.length} new resource(s)...`,\n );\n const moduleByEntryType = new Map<\n StateEntry[\"type\"],\n (typeof resourceModules)[number]\n >(resourceModules.map((m) => [m.stateEntryType, m]));\n for (const key of newKeys.reverse()) {\n const entry = all[key];\n if (!entry) continue;\n if (entry.type === \"logpush_job\") {\n try {\n await api.logpushAccountJobDelete(entry.cfJobId);\n state.delete(key);\n } catch (err) {\n console.warn(\n `Rollback: failed to delete Logpush job ${entry.cfJobId}:`,\n err,\n );\n }\n continue;\n }\n const mod = moduleByEntryType.get(entry.type);\n if (!mod) {\n console.warn(\n `Rollback: no module owns state type \"${entry.type}\" (key \"${key}\"); leaving in place.`,\n );\n continue;\n }\n await mod.destroyOne({ api, state, key, entry });\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { f as getDispatchNamespaces, m as getLogpushJobs, p as getDnsRecords } from "./normalize-DVSTRZhO.mjs";
2
- import { a as logicalNamesForResourceKind, n as resourceModules } from "./registry-BrOxbA2i.mjs";
3
- import { a as effectiveDnsRecordProxied, i as effectiveDnsRecordComment, o as effectiveDnsRecordTtl, r as dnsRecordStateKey, t as dnsRecordAppliesToEnv } from "./dns-records.resolve-DV6XBZf3.mjs";
2
+ import { a as logicalNamesForResourceKind, n as resourceModules } from "./registry-X9dlQxG3.mjs";
3
+ import { a as effectiveDnsRecordProxied, i as effectiveDnsRecordComment, o as effectiveDnsRecordTtl, r as dnsRecordStateKey, t as dnsRecordAppliesToEnv } from "./dns-records.resolve-DwBR_1WI.mjs";
4
4
 
5
5
  //#region src/features/dns-records/dns-records.diff.ts
6
6
  function dnsRecordDiffPlanItems(args) {
@@ -149,4 +149,4 @@ function filterPlanItemsForTarget(items, target) {
149
149
 
150
150
  //#endregion
151
151
  export { dnsRecordDiffPlanItems as a, computeDnsRecordMutableChanges as i, filterPlanItemsForTarget as n, parseApplyTarget as r, assertApplyTargetDeclared as t };
152
- //# sourceMappingURL=applyTarget-BkBg8MFW.mjs.map
152
+ //# sourceMappingURL=applyTarget-B1YPgkb3.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"applyTarget-BkBg8MFW.mjs","names":["items: PlanItem[]","changes: PlanFieldChange[]","APPLY_TARGET_KINDS: ReadonlySet<string>"],"sources":["../src/features/dns-records/dns-records.diff.ts","../src/core/apply/applyTarget.ts"],"sourcesContent":["/**\n * Plan-time field-level diff for declared DNS records. Pure: takes the\n * already-recorded {@link DnsRecordStateEntry} and the {@link\n * DnsRecordResourceConfig} the user wrote, and returns the\n * `update` / `replace` items `tamer plan` should emit.\n *\n * Mirrors the patch + delete-and-recreate decision tree in\n * {@link dnsRecordApply}:\n * - `type` change → `replace` (Cloudflare's API rejects PATCH on type per\n * https://developers.cloudflare.com/fundamentals/api/reference/deprecations/).\n * - any of `content` / `ttl` / `proxied` / `priority` / `comment` differ →\n * `update` (in-place PATCH).\n *\n * Records that don't yet have a state row are reported by `dnsRecordDrift`\n * as `undeployed` instead — those become `create` items. Records that\n * `apply` would skip (env-skipped, missing zone) are excluded here.\n */\n\nimport type {\n DnsRecordResourceConfig,\n DnsRecordStateEntry,\n TenantMeta,\n} from \"../../types.js\";\nimport type { StateManager } from \"../../core/state/StateManager.js\";\nimport type { PlanFieldChange, PlanItem } from \"../../core/plan/plan.types.js\";\nimport {\n dnsRecordAppliesToEnv,\n dnsRecordStateKey,\n effectiveDnsRecordComment,\n effectiveDnsRecordProxied,\n effectiveDnsRecordTtl,\n} from \"./dns-records.resolve.js\";\n\nexport function dnsRecordDiffPlanItems(args: {\n resources: DnsRecordResourceConfig[];\n tenant: TenantMeta;\n env: string;\n state: StateManager;\n}): PlanItem[] {\n const { resources, tenant, env, state } = args;\n const items: PlanItem[] = [];\n\n for (const config of resources) {\n if (!dnsRecordAppliesToEnv(config, env)) continue;\n const key = dnsRecordStateKey(config.zoneId, config.type, config.name);\n const entry = state.get(key);\n if (!entry || entry.type !== \"dns_record\") continue;\n\n const stateEntry = entry as DnsRecordStateEntry;\n if (stateEntry.recordType !== config.type) {\n items.push({\n kind: \"dns_record\",\n action: \"replace\",\n logicalName: config.logicalName,\n derivedName: `${config.type} ${config.name}`,\n detail: `type ${stateEntry.recordType} -> ${config.type} (delete + recreate)`,\n changes: [\n {\n field: \"type\",\n from: stateEntry.recordType,\n to: config.type,\n kind: \"immutable\",\n },\n ],\n });\n continue;\n }\n\n const changes = computeMutableChanges(stateEntry, config, tenant, env);\n if (changes.length === 0) continue;\n items.push({\n kind: \"dns_record\",\n action: \"update\",\n logicalName: config.logicalName,\n derivedName: `${config.type} ${config.name}`,\n detail: changes\n .map((c) => `${c.field}: ${formatVal(c.from)} -> ${formatVal(c.to)}`)\n .join(\", \"),\n changes,\n });\n }\n\n return items;\n}\n\n/**\n * Pure comparison shared with `dnsRecordApply` so plan and apply agree\n * on what counts as drift on the mutable record fields. Excludes the\n * `type` change — that's an immutable replace handled separately.\n */\nexport function computeDnsRecordMutableChanges(\n state: DnsRecordStateEntry,\n config: DnsRecordResourceConfig,\n tenant: TenantMeta,\n env: string,\n): PlanFieldChange[] {\n return computeMutableChanges(state, config, tenant, env);\n}\n\nfunction computeMutableChanges(\n state: DnsRecordStateEntry,\n config: DnsRecordResourceConfig,\n tenant: TenantMeta,\n env: string,\n): PlanFieldChange[] {\n const expectedTtl = effectiveDnsRecordTtl(config);\n const expectedProxied = effectiveDnsRecordProxied(config);\n const expectedComment = effectiveDnsRecordComment(\n config,\n tenant,\n env,\n );\n\n const changes: PlanFieldChange[] = [];\n if (state.content !== config.content) {\n changes.push({\n field: \"content\",\n from: state.content,\n to: config.content,\n kind: \"mutable\",\n });\n }\n if (state.ttl !== expectedTtl) {\n changes.push({\n field: \"ttl\",\n from: state.ttl,\n to: expectedTtl,\n kind: \"mutable\",\n });\n }\n if (state.proxied !== expectedProxied) {\n changes.push({\n field: \"proxied\",\n from: state.proxied,\n to: expectedProxied,\n kind: \"mutable\",\n });\n }\n if (\n config.priority !== undefined &&\n state.priority !== config.priority\n ) {\n changes.push({\n field: \"priority\",\n from: state.priority,\n to: config.priority,\n kind: \"mutable\",\n });\n }\n if (state.comment !== expectedComment) {\n changes.push({\n field: \"comment\",\n from: state.comment,\n to: expectedComment,\n kind: \"mutable\",\n });\n }\n return changes;\n}\n\nfunction formatVal(v: unknown): string {\n if (v === undefined) return \"(unset)\";\n if (typeof v === \"string\") return v.length > 32 ? `${v.slice(0, 29)}...` : v;\n return String(v);\n}\n","import type { CfiConfig } from \"../../types.js\";\nimport {\n getDispatchNamespaces,\n getDnsRecords,\n getLogpushJobs,\n} from \"../../types.js\";\nimport { logicalNamesForResourceKind } from \"../config/resourcesFromConfig.js\";\nimport type { ResourceKind } from \"../registry/registry.js\";\nimport { resourceModules } from \"../registry/registry.js\";\nimport type { PlanItem } from \"../plan/plan.types.js\";\n\nconst APPLY_TARGET_KINDS: ReadonlySet<string> = new Set([\n ...resourceModules.map((m) => m.kind),\n \"dispatch_namespace\",\n \"dns_record\",\n \"logpush_job\",\n]);\n\nexport type ApplyTargetKind =\n | ResourceKind\n | \"dispatch_namespace\"\n | \"dns_record\"\n | \"logpush_job\";\n\nexport interface ApplyTarget {\n kind: ApplyTargetKind;\n logical: string;\n}\n\n/**\n * Parse `tamer apply --target` / `tamer plan --target` (`d1:settings`,\n * `dispatch_namespace:workspace`, …). Not valid for `worker_route` /\n * `worker_script` (routes deploy with `tamer deploy`; scripts with wrangler).\n */\nexport function parseApplyTarget(raw: string): ApplyTarget {\n const idx = raw.indexOf(\":\");\n if (idx < 1 || idx === raw.length - 1) {\n throw new Error(\n `Invalid --target \"${raw}\": expected <kind>:<logicalName> (e.g. d1:settings, dns_record:apex_txt)`,\n );\n }\n const kind = raw.slice(0, idx);\n const logical = raw.slice(idx + 1);\n if (!logical.trim()) {\n throw new Error(\n `Invalid --target \"${raw}\": missing logical name after \":\"`,\n );\n }\n if (kind === \"worker_route\" || kind === \"worker_script\") {\n throw new Error(\n `--target ${kind}:… is not supported here (worker routes are applied on \\`tamer deploy\\`; scripts via wrangler). Omit --target or pass a resource kind.`,\n );\n }\n if (!APPLY_TARGET_KINDS.has(kind)) {\n const list = [...APPLY_TARGET_KINDS].sort().join(\", \");\n throw new Error(\n `Unknown resource kind in --target: \"${kind}\". Expected one of: ${list}`,\n );\n }\n return { kind: kind as ApplyTargetKind, logical };\n}\n\nexport async function assertApplyTargetDeclared(\n config: CfiConfig,\n baseDir: string,\n target: ApplyTarget,\n): Promise<void> {\n if (target.kind === \"dispatch_namespace\") {\n const names = getDispatchNamespaces(config).map((n) => n.logicalName);\n if (!names.includes(target.logical)) {\n throw new Error(\n `No dispatch_namespace \"${target.logical}\" in the Tamer project config. Declared: ${names.length ? names.join(\", \") : \"(none)\"}`,\n );\n }\n return;\n }\n if (target.kind === \"dns_record\") {\n const names = getDnsRecords(config).map((d) => d.logicalName);\n if (!names.includes(target.logical)) {\n throw new Error(\n `No dns_record \"${target.logical}\" in the Tamer project config. Declared: ${names.length ? names.join(\", \") : \"(none)\"}`,\n );\n }\n return;\n }\n if (target.kind === \"logpush_job\") {\n const names = getLogpushJobs(config).map((j) => j.logicalName);\n if (!names.includes(target.logical)) {\n throw new Error(\n `No logpush_job \"${target.logical}\" in the Tamer project config. Declared: ${names.length ? names.join(\", \") : \"(none)\"}`,\n );\n }\n return;\n }\n const set = await logicalNamesForResourceKind(config, baseDir, target.kind);\n if (!set.has(target.logical)) {\n const arr = [...set].sort();\n throw new Error(\n `No ${target.kind} resource \"${target.logical}\" in the Tamer project config for this stack. Declared ${target.kind}: ${arr.length ? arr.join(\", \") : \"(none)\"}`,\n );\n }\n}\n\nexport function filterPlanItemsForTarget(\n items: PlanItem[],\n target: ApplyTarget,\n): PlanItem[] {\n return items.filter(\n (it) => it.kind === target.kind && it.logicalName === target.logical,\n );\n}\n"],"mappings":";;;;;AAiCA,SAAgB,uBAAuB,MAKxB;CACb,MAAM,EAAE,WAAW,QAAQ,KAAK,UAAU;CAC1C,MAAMA,QAAoB,EAAE;AAE5B,MAAK,MAAM,UAAU,WAAW;AAC9B,MAAI,CAAC,sBAAsB,QAAQ,IAAI,CAAE;EACzC,MAAM,MAAM,kBAAkB,OAAO,QAAQ,OAAO,MAAM,OAAO,KAAK;EACtE,MAAM,QAAQ,MAAM,IAAI,IAAI;AAC5B,MAAI,CAAC,SAAS,MAAM,SAAS,aAAc;EAE3C,MAAM,aAAa;AACnB,MAAI,WAAW,eAAe,OAAO,MAAM;AACzC,SAAM,KAAK;IACT,MAAM;IACN,QAAQ;IACR,aAAa,OAAO;IACpB,aAAa,GAAG,OAAO,KAAK,GAAG,OAAO;IACtC,QAAQ,QAAQ,WAAW,WAAW,MAAM,OAAO,KAAK;IACxD,SAAS,CACP;KACE,OAAO;KACP,MAAM,WAAW;KACjB,IAAI,OAAO;KACX,MAAM;KACP,CACF;IACF,CAAC;AACF;;EAGF,MAAM,UAAU,sBAAsB,YAAY,QAAQ,QAAQ,IAAI;AACtE,MAAI,QAAQ,WAAW,EAAG;AAC1B,QAAM,KAAK;GACT,MAAM;GACN,QAAQ;GACR,aAAa,OAAO;GACpB,aAAa,GAAG,OAAO,KAAK,GAAG,OAAO;GACtC,QAAQ,QACL,KAAK,MAAM,GAAG,EAAE,MAAM,IAAI,UAAU,EAAE,KAAK,CAAC,MAAM,UAAU,EAAE,GAAG,GAAG,CACpE,KAAK,KAAK;GACb;GACD,CAAC;;AAGJ,QAAO;;;;;;;AAQT,SAAgB,+BACd,OACA,QACA,QACA,KACmB;AACnB,QAAO,sBAAsB,OAAO,QAAQ,QAAQ,IAAI;;AAG1D,SAAS,sBACP,OACA,QACA,QACA,KACmB;CACnB,MAAM,cAAc,sBAAsB,OAAO;CACjD,MAAM,kBAAkB,0BAA0B,OAAO;CACzD,MAAM,kBAAkB,0BACtB,QACA,QACA,IACD;CAED,MAAMC,UAA6B,EAAE;AACrC,KAAI,MAAM,YAAY,OAAO,QAC3B,SAAQ,KAAK;EACX,OAAO;EACP,MAAM,MAAM;EACZ,IAAI,OAAO;EACX,MAAM;EACP,CAAC;AAEJ,KAAI,MAAM,QAAQ,YAChB,SAAQ,KAAK;EACX,OAAO;EACP,MAAM,MAAM;EACZ,IAAI;EACJ,MAAM;EACP,CAAC;AAEJ,KAAI,MAAM,YAAY,gBACpB,SAAQ,KAAK;EACX,OAAO;EACP,MAAM,MAAM;EACZ,IAAI;EACJ,MAAM;EACP,CAAC;AAEJ,KACE,OAAO,aAAa,UACpB,MAAM,aAAa,OAAO,SAE1B,SAAQ,KAAK;EACX,OAAO;EACP,MAAM,MAAM;EACZ,IAAI,OAAO;EACX,MAAM;EACP,CAAC;AAEJ,KAAI,MAAM,YAAY,gBACpB,SAAQ,KAAK;EACX,OAAO;EACP,MAAM,MAAM;EACZ,IAAI;EACJ,MAAM;EACP,CAAC;AAEJ,QAAO;;AAGT,SAAS,UAAU,GAAoB;AACrC,KAAI,MAAM,OAAW,QAAO;AAC5B,KAAI,OAAO,MAAM,SAAU,QAAO,EAAE,SAAS,KAAK,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,OAAO;AAC3E,QAAO,OAAO,EAAE;;;;;ACxJlB,MAAMC,qBAA0C,IAAI,IAAI;CACtD,GAAG,gBAAgB,KAAK,MAAM,EAAE,KAAK;CACrC;CACA;CACA;CACD,CAAC;;;;;;AAkBF,SAAgB,iBAAiB,KAA0B;CACzD,MAAM,MAAM,IAAI,QAAQ,IAAI;AAC5B,KAAI,MAAM,KAAK,QAAQ,IAAI,SAAS,EAClC,OAAM,IAAI,MACR,qBAAqB,IAAI,0EAC1B;CAEH,MAAM,OAAO,IAAI,MAAM,GAAG,IAAI;CAC9B,MAAM,UAAU,IAAI,MAAM,MAAM,EAAE;AAClC,KAAI,CAAC,QAAQ,MAAM,CACjB,OAAM,IAAI,MACR,qBAAqB,IAAI,mCAC1B;AAEH,KAAI,SAAS,kBAAkB,SAAS,gBACtC,OAAM,IAAI,MACR,YAAY,KAAK,wIAClB;AAEH,KAAI,CAAC,mBAAmB,IAAI,KAAK,EAAE;EACjC,MAAM,OAAO,CAAC,GAAG,mBAAmB,CAAC,MAAM,CAAC,KAAK,KAAK;AACtD,QAAM,IAAI,MACR,uCAAuC,KAAK,sBAAsB,OACnE;;AAEH,QAAO;EAAQ;EAAyB;EAAS;;AAGnD,eAAsB,0BACpB,QACA,SACA,QACe;AACf,KAAI,OAAO,SAAS,sBAAsB;EACxC,MAAM,QAAQ,sBAAsB,OAAO,CAAC,KAAK,MAAM,EAAE,YAAY;AACrE,MAAI,CAAC,MAAM,SAAS,OAAO,QAAQ,CACjC,OAAM,IAAI,MACR,0BAA0B,OAAO,QAAQ,2CAA2C,MAAM,SAAS,MAAM,KAAK,KAAK,GAAG,WACvH;AAEH;;AAEF,KAAI,OAAO,SAAS,cAAc;EAChC,MAAM,QAAQ,cAAc,OAAO,CAAC,KAAK,MAAM,EAAE,YAAY;AAC7D,MAAI,CAAC,MAAM,SAAS,OAAO,QAAQ,CACjC,OAAM,IAAI,MACR,kBAAkB,OAAO,QAAQ,2CAA2C,MAAM,SAAS,MAAM,KAAK,KAAK,GAAG,WAC/G;AAEH;;AAEF,KAAI,OAAO,SAAS,eAAe;EACjC,MAAM,QAAQ,eAAe,OAAO,CAAC,KAAK,MAAM,EAAE,YAAY;AAC9D,MAAI,CAAC,MAAM,SAAS,OAAO,QAAQ,CACjC,OAAM,IAAI,MACR,mBAAmB,OAAO,QAAQ,2CAA2C,MAAM,SAAS,MAAM,KAAK,KAAK,GAAG,WAChH;AAEH;;CAEF,MAAM,MAAM,MAAM,4BAA4B,QAAQ,SAAS,OAAO,KAAK;AAC3E,KAAI,CAAC,IAAI,IAAI,OAAO,QAAQ,EAAE;EAC5B,MAAM,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM;AAC3B,QAAM,IAAI,MACR,MAAM,OAAO,KAAK,aAAa,OAAO,QAAQ,yDAAyD,OAAO,KAAK,IAAI,IAAI,SAAS,IAAI,KAAK,KAAK,GAAG,WACtJ;;;AAIL,SAAgB,yBACd,OACA,QACY;AACZ,QAAO,MAAM,QACV,OAAO,GAAG,SAAS,OAAO,QAAQ,GAAG,gBAAgB,OAAO,QAC9D"}
1
+ {"version":3,"file":"applyTarget-B1YPgkb3.mjs","names":["items: PlanItem[]","changes: PlanFieldChange[]","APPLY_TARGET_KINDS: ReadonlySet<string>"],"sources":["../src/features/dns-records/dns-records.diff.ts","../src/core/apply/applyTarget.ts"],"sourcesContent":["/**\n * Plan-time field-level diff for declared DNS records. Pure: takes the\n * already-recorded {@link DnsRecordStateEntry} and the {@link\n * DnsRecordResourceConfig} the user wrote, and returns the\n * `update` / `replace` items `tamer plan` should emit.\n *\n * Mirrors the patch + delete-and-recreate decision tree in\n * {@link dnsRecordApply}:\n * - `type` change → `replace` (Cloudflare's API rejects PATCH on type per\n * https://developers.cloudflare.com/fundamentals/api/reference/deprecations/).\n * - any of `content` / `ttl` / `proxied` / `priority` / `comment` differ →\n * `update` (in-place PATCH).\n *\n * Records that don't yet have a state row are reported by `dnsRecordDrift`\n * as `undeployed` instead — those become `create` items. Records that\n * `apply` would skip (env-skipped, missing zone) are excluded here.\n */\n\nimport type {\n DnsRecordResourceConfig,\n DnsRecordStateEntry,\n TenantMeta,\n} from \"../../types.js\";\nimport type { StateManager } from \"../../core/state/StateManager.js\";\nimport type { PlanFieldChange, PlanItem } from \"../../core/plan/plan.types.js\";\nimport {\n dnsRecordAppliesToEnv,\n dnsRecordStateKey,\n effectiveDnsRecordComment,\n effectiveDnsRecordProxied,\n effectiveDnsRecordTtl,\n} from \"./dns-records.resolve.js\";\n\nexport function dnsRecordDiffPlanItems(args: {\n resources: DnsRecordResourceConfig[];\n tenant: TenantMeta;\n env: string;\n state: StateManager;\n}): PlanItem[] {\n const { resources, tenant, env, state } = args;\n const items: PlanItem[] = [];\n\n for (const config of resources) {\n if (!dnsRecordAppliesToEnv(config, env)) continue;\n const key = dnsRecordStateKey(config.zoneId, config.type, config.name);\n const entry = state.get(key);\n if (!entry || entry.type !== \"dns_record\") continue;\n\n const stateEntry = entry as DnsRecordStateEntry;\n if (stateEntry.recordType !== config.type) {\n items.push({\n kind: \"dns_record\",\n action: \"replace\",\n logicalName: config.logicalName,\n derivedName: `${config.type} ${config.name}`,\n detail: `type ${stateEntry.recordType} -> ${config.type} (delete + recreate)`,\n changes: [\n {\n field: \"type\",\n from: stateEntry.recordType,\n to: config.type,\n kind: \"immutable\",\n },\n ],\n });\n continue;\n }\n\n const changes = computeMutableChanges(stateEntry, config, tenant, env);\n if (changes.length === 0) continue;\n items.push({\n kind: \"dns_record\",\n action: \"update\",\n logicalName: config.logicalName,\n derivedName: `${config.type} ${config.name}`,\n detail: changes\n .map((c) => `${c.field}: ${formatVal(c.from)} -> ${formatVal(c.to)}`)\n .join(\", \"),\n changes,\n });\n }\n\n return items;\n}\n\n/**\n * Pure comparison shared with `dnsRecordApply` so plan and apply agree\n * on what counts as drift on the mutable record fields. Excludes the\n * `type` change — that's an immutable replace handled separately.\n */\nexport function computeDnsRecordMutableChanges(\n state: DnsRecordStateEntry,\n config: DnsRecordResourceConfig,\n tenant: TenantMeta,\n env: string,\n): PlanFieldChange[] {\n return computeMutableChanges(state, config, tenant, env);\n}\n\nfunction computeMutableChanges(\n state: DnsRecordStateEntry,\n config: DnsRecordResourceConfig,\n tenant: TenantMeta,\n env: string,\n): PlanFieldChange[] {\n const expectedTtl = effectiveDnsRecordTtl(config);\n const expectedProxied = effectiveDnsRecordProxied(config);\n const expectedComment = effectiveDnsRecordComment(\n config,\n tenant,\n env,\n );\n\n const changes: PlanFieldChange[] = [];\n if (state.content !== config.content) {\n changes.push({\n field: \"content\",\n from: state.content,\n to: config.content,\n kind: \"mutable\",\n });\n }\n if (state.ttl !== expectedTtl) {\n changes.push({\n field: \"ttl\",\n from: state.ttl,\n to: expectedTtl,\n kind: \"mutable\",\n });\n }\n if (state.proxied !== expectedProxied) {\n changes.push({\n field: \"proxied\",\n from: state.proxied,\n to: expectedProxied,\n kind: \"mutable\",\n });\n }\n if (\n config.priority !== undefined &&\n state.priority !== config.priority\n ) {\n changes.push({\n field: \"priority\",\n from: state.priority,\n to: config.priority,\n kind: \"mutable\",\n });\n }\n if (state.comment !== expectedComment) {\n changes.push({\n field: \"comment\",\n from: state.comment,\n to: expectedComment,\n kind: \"mutable\",\n });\n }\n return changes;\n}\n\nfunction formatVal(v: unknown): string {\n if (v === undefined) return \"(unset)\";\n if (typeof v === \"string\") return v.length > 32 ? `${v.slice(0, 29)}...` : v;\n return String(v);\n}\n","import type { CfiConfig } from \"../../types.js\";\nimport {\n getDispatchNamespaces,\n getDnsRecords,\n getLogpushJobs,\n} from \"../../types.js\";\nimport { logicalNamesForResourceKind } from \"../config/resourcesFromConfig.js\";\nimport type { ResourceKind } from \"../registry/registry.js\";\nimport { resourceModules } from \"../registry/registry.js\";\nimport type { PlanItem } from \"../plan/plan.types.js\";\n\nconst APPLY_TARGET_KINDS: ReadonlySet<string> = new Set([\n ...resourceModules.map((m) => m.kind),\n \"dispatch_namespace\",\n \"dns_record\",\n \"logpush_job\",\n]);\n\nexport type ApplyTargetKind =\n | ResourceKind\n | \"dispatch_namespace\"\n | \"dns_record\"\n | \"logpush_job\";\n\nexport interface ApplyTarget {\n kind: ApplyTargetKind;\n logical: string;\n}\n\n/**\n * Parse `tamer apply --target` / `tamer plan --target` (`d1:settings`,\n * `dispatch_namespace:workspace`, …). Not valid for `worker_route` /\n * `worker_script` (routes deploy with `tamer deploy`; scripts with wrangler).\n */\nexport function parseApplyTarget(raw: string): ApplyTarget {\n const idx = raw.indexOf(\":\");\n if (idx < 1 || idx === raw.length - 1) {\n throw new Error(\n `Invalid --target \"${raw}\": expected <kind>:<logicalName> (e.g. d1:settings, dns_record:apex_txt)`,\n );\n }\n const kind = raw.slice(0, idx);\n const logical = raw.slice(idx + 1);\n if (!logical.trim()) {\n throw new Error(\n `Invalid --target \"${raw}\": missing logical name after \":\"`,\n );\n }\n if (kind === \"worker_route\" || kind === \"worker_script\") {\n throw new Error(\n `--target ${kind}:… is not supported here (worker routes are applied on \\`tamer deploy\\`; scripts via wrangler). Omit --target or pass a resource kind.`,\n );\n }\n if (!APPLY_TARGET_KINDS.has(kind)) {\n const list = [...APPLY_TARGET_KINDS].sort().join(\", \");\n throw new Error(\n `Unknown resource kind in --target: \"${kind}\". Expected one of: ${list}`,\n );\n }\n return { kind: kind as ApplyTargetKind, logical };\n}\n\nexport async function assertApplyTargetDeclared(\n config: CfiConfig,\n baseDir: string,\n target: ApplyTarget,\n): Promise<void> {\n if (target.kind === \"dispatch_namespace\") {\n const names = getDispatchNamespaces(config).map((n) => n.logicalName);\n if (!names.includes(target.logical)) {\n throw new Error(\n `No dispatch_namespace \"${target.logical}\" in the Tamer project config. Declared: ${names.length ? names.join(\", \") : \"(none)\"}`,\n );\n }\n return;\n }\n if (target.kind === \"dns_record\") {\n const names = getDnsRecords(config).map((d) => d.logicalName);\n if (!names.includes(target.logical)) {\n throw new Error(\n `No dns_record \"${target.logical}\" in the Tamer project config. Declared: ${names.length ? names.join(\", \") : \"(none)\"}`,\n );\n }\n return;\n }\n if (target.kind === \"logpush_job\") {\n const names = getLogpushJobs(config).map((j) => j.logicalName);\n if (!names.includes(target.logical)) {\n throw new Error(\n `No logpush_job \"${target.logical}\" in the Tamer project config. Declared: ${names.length ? names.join(\", \") : \"(none)\"}`,\n );\n }\n return;\n }\n const set = await logicalNamesForResourceKind(config, baseDir, target.kind);\n if (!set.has(target.logical)) {\n const arr = [...set].sort();\n throw new Error(\n `No ${target.kind} resource \"${target.logical}\" in the Tamer project config for this stack. Declared ${target.kind}: ${arr.length ? arr.join(\", \") : \"(none)\"}`,\n );\n }\n}\n\nexport function filterPlanItemsForTarget(\n items: PlanItem[],\n target: ApplyTarget,\n): PlanItem[] {\n return items.filter(\n (it) => it.kind === target.kind && it.logicalName === target.logical,\n );\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import { H as loadConfig, I as ensureTamerSecretsDatabase, L as CFApiClient, R as cloudflareAccountIdFromEnv, S as ensureTamerStateDatabase } from "./tamer.mjs";
2
- import "./r2S3EmptyBucket-B9_pHfvB.mjs";
3
- import { n as ensureTamerArtifactsBucket } from "./tamerArtifactsR2-COndFmk5.mjs";
1
+ import { L as ensureTamerSecretsDatabase, R as CFApiClient, S as ensureTamerStateDatabase, U as loadConfig, z as cloudflareAccountIdFromEnv } from "./tamer.mjs";
2
+ import "./r2S3EmptyBucket-CXLmOrYF.mjs";
3
+ import { n as ensureTamerArtifactsBucket } from "./tamerArtifactsR2-B9myb-IA.mjs";
4
4
 
5
5
  //#region src/cli/commands/bootstrap.ts
6
6
  /**
@@ -31,4 +31,4 @@ async function runBootstrap(options) {
31
31
 
32
32
  //#endregion
33
33
  export { runBootstrap };
34
- //# sourceMappingURL=bootstrap-DvOce6vA.mjs.map
34
+ //# sourceMappingURL=bootstrap-ilkixdmD.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap-DvOce6vA.mjs","names":[],"sources":["../src/cli/commands/bootstrap.ts"],"sourcesContent":["import { loadConfig } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { ensureTamerStateDatabase } from \"../../core/state/tamerStateDb.js\";\nimport { ensureTamerArtifactsBucket } from \"../../core/state/tamerArtifactsR2.js\";\nimport { ensureTamerSecretsDatabase } from \"../../core/secrets/secretsDb.js\";\n\n/**\n * Create the three account-scoped Tamer metadata resources if they don't\n * exist yet. Run once per account — subsequent calls are idempotent no-ops.\n *\n * - `tamer-state` (D1) — deployment state for all envs\n * - `tamer-artifacts` (R2) — build bundles\n * - `tamer-secrets` (D1) — encrypted secrets vault\n *\n * No `--env` needed. Per-env state rows are seeded automatically on first\n * `tamer apply --env <env>` when the StateManager finds no row.\n */\nexport async function runBootstrap(options: {\n configPath?: string;\n}): Promise<void> {\n const { configPath } = options;\n\n const config = await loadConfig(configPath);\n const accountId =\n config.account_id ?? cloudflareAccountIdFromEnv();\n if (!accountId) {\n throw new Error(\n \"account_id required in config or CLOUDFLARE_ACCOUNT_ID env var\",\n );\n }\n\n const api = new CFApiClient(accountId);\n\n const stateUuid = await ensureTamerStateDatabase(api, config.tenant.id);\n console.log(`Tamer state ready: D1 uuid=${stateUuid}`);\n\n const bucketName = await ensureTamerArtifactsBucket(api);\n console.log(`Tamer artifacts ready: R2 bucket=${bucketName}`);\n\n const secretsUuid = await ensureTamerSecretsDatabase(api);\n console.log(`Tamer secrets vault ready: D1 uuid=${secretsUuid}`);\n\n console.log(\n \"\\nBootstrap complete. Run `tamer apply --env <env>` to provision resources.\",\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,eAAsB,aAAa,SAEjB;CAChB,MAAM,EAAE,eAAe;CAEvB,MAAM,SAAS,MAAM,WAAW,WAAW;CAC3C,MAAM,YACJ,OAAO,cAAc,4BAA4B;AACnD,KAAI,CAAC,UACH,OAAM,IAAI,MACR,iEACD;CAGH,MAAM,MAAM,IAAI,YAAY,UAAU;CAEtC,MAAM,YAAY,MAAM,yBAAyB,KAAK,OAAO,OAAO,GAAG;AACvE,SAAQ,IAAI,8BAA8B,YAAY;CAEtD,MAAM,aAAa,MAAM,2BAA2B,IAAI;AACxD,SAAQ,IAAI,oCAAoC,aAAa;CAE7D,MAAM,cAAc,MAAM,2BAA2B,IAAI;AACzD,SAAQ,IAAI,sCAAsC,cAAc;AAEhE,SAAQ,IACN,8EACD"}
1
+ {"version":3,"file":"bootstrap-ilkixdmD.mjs","names":[],"sources":["../src/cli/commands/bootstrap.ts"],"sourcesContent":["import { loadConfig } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { ensureTamerStateDatabase } from \"../../core/state/tamerStateDb.js\";\nimport { ensureTamerArtifactsBucket } from \"../../core/state/tamerArtifactsR2.js\";\nimport { ensureTamerSecretsDatabase } from \"../../core/secrets/secretsDb.js\";\n\n/**\n * Create the three account-scoped Tamer metadata resources if they don't\n * exist yet. Run once per account — subsequent calls are idempotent no-ops.\n *\n * - `tamer-state` (D1) — deployment state for all envs\n * - `tamer-artifacts` (R2) — build bundles\n * - `tamer-secrets` (D1) — encrypted secrets vault\n *\n * No `--env` needed. Per-env state rows are seeded automatically on first\n * `tamer apply --env <env>` when the StateManager finds no row.\n */\nexport async function runBootstrap(options: {\n configPath?: string;\n}): Promise<void> {\n const { configPath } = options;\n\n const config = await loadConfig(configPath);\n const accountId =\n config.account_id ?? cloudflareAccountIdFromEnv();\n if (!accountId) {\n throw new Error(\n \"account_id required in config or CLOUDFLARE_ACCOUNT_ID env var\",\n );\n }\n\n const api = new CFApiClient(accountId);\n\n const stateUuid = await ensureTamerStateDatabase(api, config.tenant.id);\n console.log(`Tamer state ready: D1 uuid=${stateUuid}`);\n\n const bucketName = await ensureTamerArtifactsBucket(api);\n console.log(`Tamer artifacts ready: R2 bucket=${bucketName}`);\n\n const secretsUuid = await ensureTamerSecretsDatabase(api);\n console.log(`Tamer secrets vault ready: D1 uuid=${secretsUuid}`);\n\n console.log(\n \"\\nBootstrap complete. Run `tamer apply --env <env>` to provision resources.\",\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,eAAsB,aAAa,SAEjB;CAChB,MAAM,EAAE,eAAe;CAEvB,MAAM,SAAS,MAAM,WAAW,WAAW;CAC3C,MAAM,YACJ,OAAO,cAAc,4BAA4B;AACnD,KAAI,CAAC,UACH,OAAM,IAAI,MACR,iEACD;CAGH,MAAM,MAAM,IAAI,YAAY,UAAU;CAEtC,MAAM,YAAY,MAAM,yBAAyB,KAAK,OAAO,OAAO,GAAG;AACvE,SAAQ,IAAI,8BAA8B,YAAY;CAEtD,MAAM,aAAa,MAAM,2BAA2B,IAAI;AACxD,SAAQ,IAAI,oCAAoC,aAAa;CAE7D,MAAM,cAAc,MAAM,2BAA2B,IAAI;AACzD,SAAQ,IAAI,sCAAsC,cAAc;AAEhE,SAAQ,IACN,8EACD"}
@@ -7,32 +7,29 @@ import { readFileSync } from "fs";
7
7
  * with a single Worker module file.
8
8
  */
9
9
  function buildSingleModuleDispatchForm(mainFilePath, opts) {
10
- const moduleName = basename(mainFilePath);
11
- const body = readFileSync(mainFilePath);
12
- const metadata = {
13
- main_module: moduleName,
14
- compatibility_date: opts.compatibility_date
15
- };
16
- if (opts.compatibility_flags?.length) metadata.compatibility_flags = opts.compatibility_flags;
17
- const form = new FormData();
18
- form.append("metadata", new Blob([JSON.stringify(metadata)], { type: "application/json" }));
19
- form.append(moduleName, new Blob([body], { type: "application/javascript+module" }), moduleName);
20
- return form;
10
+ return buildForm(basename(mainFilePath), readFileSync(mainFilePath), opts);
21
11
  }
22
12
  /** Same as {@link buildSingleModuleDispatchForm} but from in-memory bytes (e.g. R2 download). */
23
13
  function buildSingleModuleDispatchFormFromBuffer(moduleName, body, opts) {
24
- const bytes = body instanceof ArrayBuffer ? new Uint8Array(body) : body;
14
+ return buildForm(moduleName, body instanceof ArrayBuffer ? new Uint8Array(body) : body, opts);
15
+ }
16
+ function buildForm(moduleName, body, opts) {
25
17
  const metadata = {
26
18
  main_module: moduleName,
27
19
  compatibility_date: opts.compatibility_date
28
20
  };
29
21
  if (opts.compatibility_flags?.length) metadata.compatibility_flags = opts.compatibility_flags;
22
+ if (opts.bindings) {
23
+ if (opts.bindings.d1_databases?.length) metadata.d1_databases = opts.bindings.d1_databases;
24
+ if (opts.bindings.vars && Object.keys(opts.bindings.vars).length > 0) metadata.vars = opts.bindings.vars;
25
+ if (opts.bindings.services?.length) metadata.services = opts.bindings.services;
26
+ }
30
27
  const form = new FormData();
31
28
  form.append("metadata", new Blob([JSON.stringify(metadata)], { type: "application/json" }));
32
- form.append(moduleName, new Blob([bytes], { type: "application/javascript+module" }), moduleName);
29
+ form.append(moduleName, new Blob([body], { type: "application/javascript+module" }), moduleName);
33
30
  return form;
34
31
  }
35
32
 
36
33
  //#endregion
37
34
  export { buildSingleModuleDispatchFormFromBuffer as n, buildSingleModuleDispatchForm as t };
38
- //# sourceMappingURL=buildDispatchUploadForm-D9ZrefZX.mjs.map
35
+ //# sourceMappingURL=buildDispatchUploadForm-D_fM8JaL.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildDispatchUploadForm-D_fM8JaL.mjs","names":["metadata: Record<string, unknown>"],"sources":["../src/core/wfp/buildDispatchUploadForm.ts"],"sourcesContent":["import { basename } from \"path\";\nimport { readFileSync } from \"fs\";\n\n/**\n * Optional bindings for the dispatch script metadata.\n * D1 bindings use `name` (the binding key the Worker reads as `env.NAME`)\n * and `id` (the D1 database UUID). Vars and services follow the Wrangler\n * metadata format.\n */\nexport interface DispatchScriptBindings {\n d1_databases?: Array<{ name: string; id: string }>;\n vars?: Record<string, string>;\n services?: Array<{ name: string; service: string; environment?: string }>;\n}\n\n/**\n * Build multipart body for PUT .../dispatch/namespaces/{ns}/scripts/{script}\n * with a single Worker module file.\n */\nexport function buildSingleModuleDispatchForm(\n mainFilePath: string,\n opts: {\n compatibility_date: string;\n compatibility_flags?: string[];\n bindings?: DispatchScriptBindings;\n },\n): FormData {\n const moduleName = basename(mainFilePath);\n const body = readFileSync(mainFilePath);\n return buildForm(moduleName, body, opts);\n}\n\n/** Same as {@link buildSingleModuleDispatchForm} but from in-memory bytes (e.g. R2 download). */\nexport function buildSingleModuleDispatchFormFromBuffer(\n moduleName: string,\n body: Uint8Array | ArrayBuffer,\n opts: {\n compatibility_date: string;\n compatibility_flags?: string[];\n bindings?: DispatchScriptBindings;\n },\n): FormData {\n const bytes = body instanceof ArrayBuffer ? new Uint8Array(body) : body;\n return buildForm(moduleName, bytes, opts);\n}\n\nfunction buildForm(\n moduleName: string,\n body: Uint8Array,\n opts: {\n compatibility_date: string;\n compatibility_flags?: string[];\n bindings?: DispatchScriptBindings;\n },\n): FormData {\n const metadata: Record<string, unknown> = {\n main_module: moduleName,\n compatibility_date: opts.compatibility_date,\n };\n if (opts.compatibility_flags?.length) {\n metadata.compatibility_flags = opts.compatibility_flags;\n }\n if (opts.bindings) {\n if (opts.bindings.d1_databases?.length) {\n metadata.d1_databases = opts.bindings.d1_databases;\n }\n if (opts.bindings.vars && Object.keys(opts.bindings.vars).length > 0) {\n metadata.vars = opts.bindings.vars;\n }\n if (opts.bindings.services?.length) {\n metadata.services = opts.bindings.services;\n }\n }\n const form = new FormData();\n form.append(\n \"metadata\",\n new Blob([JSON.stringify(metadata)], { type: \"application/json\" }),\n );\n // CRITICAL: Cloudflare reads `Content-Type: application/javascript+module`\n // off the module part to decide whether to treat it as ESM. Without this it\n // errors with `Main module must be an ES module.` even though metadata uses\n // `main_module`.\n form.append(\n moduleName,\n new Blob([body], { type: \"application/javascript+module\" }),\n moduleName,\n );\n return form;\n}\n"],"mappings":";;;;;;;;AAmBA,SAAgB,8BACd,cACA,MAKU;AAGV,QAAO,UAFY,SAAS,aAAa,EAC5B,aAAa,aAAa,EACJ,KAAK;;;AAI1C,SAAgB,wCACd,YACA,MACA,MAKU;AAEV,QAAO,UAAU,YADH,gBAAgB,cAAc,IAAI,WAAW,KAAK,GAAG,MAC/B,KAAK;;AAG3C,SAAS,UACP,YACA,MACA,MAKU;CACV,MAAMA,WAAoC;EACxC,aAAa;EACb,oBAAoB,KAAK;EAC1B;AACD,KAAI,KAAK,qBAAqB,OAC5B,UAAS,sBAAsB,KAAK;AAEtC,KAAI,KAAK,UAAU;AACjB,MAAI,KAAK,SAAS,cAAc,OAC9B,UAAS,eAAe,KAAK,SAAS;AAExC,MAAI,KAAK,SAAS,QAAQ,OAAO,KAAK,KAAK,SAAS,KAAK,CAAC,SAAS,EACjE,UAAS,OAAO,KAAK,SAAS;AAEhC,MAAI,KAAK,SAAS,UAAU,OAC1B,UAAS,WAAW,KAAK,SAAS;;CAGtC,MAAM,OAAO,IAAI,UAAU;AAC3B,MAAK,OACH,YACA,IAAI,KAAK,CAAC,KAAK,UAAU,SAAS,CAAC,EAAE,EAAE,MAAM,oBAAoB,CAAC,CACnE;AAKD,MAAK,OACH,YACA,IAAI,KAAK,CAAC,KAAK,EAAE,EAAE,MAAM,iCAAiC,CAAC,EAC3D,WACD;AACD,QAAO"}
@@ -1,8 +1,8 @@
1
1
  import { f as getDispatchNamespaces, m as getLogpushJobs, p as getDnsRecords } from "./normalize-DVSTRZhO.mjs";
2
- import { E as mergeWorkerConfigForResourcePick, R as cloudflareAccountIdFromEnv, V as getWorkers, f as fetchStackImports, h as StateManager, u as namingFromConfig, w as stackNameForConfig } from "./tamer.mjs";
3
- import { n as resourceModules } from "./registry-BrOxbA2i.mjs";
4
- import { n as dispatchNamespaceSync, t as dnsRecordSync } from "./dns-records.sync-FyzKl-Ph.mjs";
5
- import { i as logpushJobSync } from "./logpush-job-GqVKG_HI.mjs";
2
+ import { E as mergeWorkerConfigForResourcePick, H as getWorkers, f as fetchStackImports, h as StateManager, u as namingFromConfig, w as stackNameForConfig, z as cloudflareAccountIdFromEnv } from "./tamer.mjs";
3
+ import { n as resourceModules } from "./registry-X9dlQxG3.mjs";
4
+ import { n as dispatchNamespaceSync, t as dnsRecordSync } from "./dns-records.sync-Dfwk76J_.mjs";
5
+ import { i as logpushJobSync } from "./logpush-job-C_6uzGUC.mjs";
6
6
  import { readFileSync, writeFileSync } from "fs";
7
7
  import { createHash } from "crypto";
8
8
 
@@ -158,4 +158,4 @@ function stripVolatile(entry) {
158
158
 
159
159
  //#endregion
160
160
  export { readPlanFile as a, hashCloudflareSnapshot as i, PLAN_FILE_FORMAT as n, writePlanFile as o, computeAttestation as r, buildCloudflareSnapshot as t };
161
- //# sourceMappingURL=cloudflareSnapshot-CjXNMr4X.mjs.map
161
+ //# sourceMappingURL=cloudflareSnapshot-BAeNVohz.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"cloudflareSnapshot-CjXNMr4X.mjs","names":["att: PlanAttestation","raw: string","parsed: unknown","stable: CloudflareSnapshot","out: Record<string, unknown>"],"sources":["../src/core/plan/planFile.ts","../src/core/plan/cloudflareSnapshot.ts"],"sourcesContent":["import { createHash } from \"crypto\";\nimport { readFileSync, writeFileSync } from \"fs\";\nimport type { CfiConfig, CfiState } from \"../../types.js\";\nimport type { PlanReport } from \"./plan.types.js\";\nimport type { CloudflareSnapshot } from \"./cloudflareSnapshot.js\";\n\n/** Stable on-disk format identifier for `tamer plan --out`. */\nexport const PLAN_FILE_FORMAT = \"tamer-plan/v1\" as const;\n\n/**\n * Serialized `tamer plan` output, suitable for `tamer apply --plan`.\n *\n * The {@link attestation} hashes pin the `(config, state)` pair the plan\n * was generated against. Apply recomputes the same hashes at run time and\n * refuses to proceed if either drifted, so a stale plan can never silently\n * apply against a different reality. Use `--allow-stale` only when you\n * understand the risk.\n */\nexport interface PlanFile {\n format: typeof PLAN_FILE_FORMAT;\n tenantId: string;\n env: string;\n generatedAt: string;\n /** The plan as it would be printed by `tamer plan` (kept for review). */\n report: PlanReport;\n attestation: PlanAttestation;\n}\n\nexport interface PlanAttestation {\n /** SHA-256 over the canonical JSON of the parsed `tamer.config.ts`. */\n configHash: string;\n /** SHA-256 over the canonical JSON of `state.load()` at plan time. */\n stateHash: string;\n /** Revision at plan time (informational; primary check is stateHash). */\n stateRevision?: number;\n /**\n * Drift-aware refresh: SHA-256 over the normalized **Cloudflare-side**\n * snapshot at plan time (per-kind `sync()` against a fresh in-memory\n * state, with timestamps stripped). Apply re-fetches and recomputes; a\n * mismatch means infrastructure drifted out-of-band between plan and\n * apply, so the plan is no longer accurate. Omitted for `env: local`.\n */\n cloudflareHash?: string;\n}\n\n/**\n * Stable JSON stringify: object keys are sorted recursively. The hash must\n * be deterministic across machines, so insertion order can't matter.\n */\nexport function canonicalJson(value: unknown): string {\n return stableStringify(value);\n}\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== \"object\") {\n return JSON.stringify(value);\n }\n if (Array.isArray(value)) {\n return `[${value.map((v) => stableStringify(v)).join(\",\")}]`;\n }\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n const body = keys\n .map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`)\n .join(\",\");\n return `{${body}}`;\n}\n\nexport function sha256Hex(input: string): string {\n return `sha256:${createHash(\"sha256\").update(input).digest(\"hex\")}`;\n}\n\n/**\n * Compute the attestation hashes for `(config, state)` exactly as the\n * apply-side will recompute them. Keep both sides in sync — the canonical\n * JSON algorithm above is the contract.\n */\nexport function computeAttestation(\n config: CfiConfig,\n state: CfiState,\n cloudflareSnapshot?: CloudflareSnapshot,\n): PlanAttestation {\n const att: PlanAttestation = {\n configHash: sha256Hex(canonicalJson(config)),\n stateHash: sha256Hex(canonicalJson(stateForHash(state))),\n stateRevision: state.revision,\n };\n if (cloudflareSnapshot) {\n att.cloudflareHash = sha256Hex(canonicalJson(cloudflareSnapshot));\n }\n return att;\n}\n\n/**\n * Stable hash of a Cloudflare snapshot; same algorithm as\n * `attestation.cloudflareHash` so apply can recompute and compare.\n */\nexport function hashCloudflareSnapshot(\n snapshot: CloudflareSnapshot,\n): string {\n return sha256Hex(canonicalJson(snapshot));\n}\n\n/**\n * State view used for hashing — strips fields that legitimately drift\n * between consecutive reads (timestamps, revision counter) so the hash\n * only changes when *resources* / *tenants* / *stack* change.\n */\nfunction stateForHash(state: CfiState): unknown {\n const { resources, tenants, stack } = state;\n return { resources: resources ?? {}, tenants: tenants ?? {}, stack };\n}\n\nexport function writePlanFile(path: string, file: PlanFile): void {\n writeFileSync(path, JSON.stringify(file, null, 2), \"utf-8\");\n}\n\nexport function readPlanFile(path: string): PlanFile {\n let raw: string;\n try {\n raw = readFileSync(path, \"utf-8\");\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`--plan: cannot read plan file \"${path}\": ${msg}`);\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`--plan: plan file \"${path}\" is not valid JSON: ${msg}`);\n }\n if (!isPlanFile(parsed)) {\n throw new Error(\n `--plan: plan file \"${path}\" is missing required fields or has wrong format ` +\n `(expected ${PLAN_FILE_FORMAT})`,\n );\n }\n if (parsed.format !== PLAN_FILE_FORMAT) {\n throw new Error(\n `--plan: plan file format \"${parsed.format}\" is not supported by this Tamer build (expected ${PLAN_FILE_FORMAT})`,\n );\n }\n return parsed;\n}\n\nfunction isPlanFile(v: unknown): v is PlanFile {\n if (!v || typeof v !== \"object\") return false;\n const o = v as Record<string, unknown>;\n return (\n typeof o.format === \"string\" &&\n typeof o.tenantId === \"string\" &&\n typeof o.env === \"string\" &&\n typeof o.generatedAt === \"string\" &&\n !!o.report &&\n typeof o.report === \"object\" &&\n !!o.attestation &&\n typeof o.attestation === \"object\"\n );\n}\n","import type { CfiConfig, StateEntry } from \"../../types.js\";\nimport type { CFApiClient } from \"../api/CFApiClient.js\";\nimport { StateManager } from \"../state/StateManager.js\";\nimport { stackNameForConfig } from \"../state/stackName.js\";\nimport { resourceModules } from \"../registry/registry.js\";\nimport { namingFromConfig } from \"../config/namingFromConfig.js\";\nimport { getWorkers } from \"../config/loader.js\";\nimport { dispatchNamespaceSync } from \"../../features/dispatch-namespace/index.js\";\nimport { dnsRecordSync } from \"../../features/dns-records/index.js\";\nimport { getDispatchNamespaces, getDnsRecords, getLogpushJobs } from \"../../types.js\";\nimport { logpushJobSync } from \"../../features/logpush-job/index.js\";\nimport { fetchStackImports } from \"../imports/fetchStackImports.js\";\nimport { cloudflareAccountIdFromEnv } from \"../cloudflareEnv.js\";\nimport { mergeWorkerConfigForResourcePick } from \"../config/resolver.js\";\n\n/**\n * Drift-aware Cloudflare snapshot used by `tamer plan --out` and\n * `tamer apply --plan`.\n *\n * Drives the registry's `sync` hook for every kind against a **fresh\n * in-memory state** so the result reflects only what currently exists on\n * Cloudflare (no carryover from D1). The shape mirrors the post-sync\n * resource map but strips volatile fields (timestamps) so consecutive\n * snapshots of an unchanged account hash to the same value.\n *\n * Apply recomputes this against live Cloudflare and refuses to proceed if\n * `cloudflareHash` differs from the plan's pinned hash — that's how a\n * stale plan against drifted infra is detected.\n */\nexport async function buildCloudflareSnapshot(args: {\n config: CfiConfig;\n env: string;\n api: CFApiClient;\n baseDir: string;\n}): Promise<CloudflareSnapshot> {\n const { config, env, api, baseDir } = args;\n const naming = namingFromConfig(config);\n // In-memory snapshot — never round-trips to D1, so stack name only\n // matters for diagnostics; passing it keeps logs consistent with the\n // owning stack.\n const state = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n state.hydrateInMemory();\n\n const accountId = config.account_id ?? cloudflareAccountIdFromEnv() ?? \"\";\n const imports =\n env !== \"local\"\n ? await fetchStackImports(api, config, env).catch(\n () => ({} as Record<string, Record<string, string>>),\n )\n : ({} as Record<string, Record<string, string>>);\n\n const workers = await getWorkers(config, baseDir);\n\n for (const mod of resourceModules) {\n const all = await mod.fetchAll(api);\n for (const [workerKey, wc] of workers) {\n const mergedWorker =\n env !== \"local\" && accountId\n ? mergeWorkerConfigForResourcePick(\n config,\n workerKey,\n wc,\n env,\n accountId,\n naming,\n state,\n { referencesMode: \"tolerant\", imports },\n )\n : wc;\n const resources = mod.pickResources(mergedWorker);\n if (resources.length === 0) continue;\n mod.sync({\n all,\n resources,\n tenant: config.tenant,\n env,\n api,\n state,\n naming,\n config,\n baseDir,\n });\n }\n }\n\n if (getDispatchNamespaces(config).length > 0 && env !== \"local\") {\n await dispatchNamespaceSync(\n getDispatchNamespaces(config),\n config.tenant,\n env,\n api,\n state,\n );\n }\n\n if (getDnsRecords(config).length > 0 && env !== \"local\") {\n await dnsRecordSync(\n getDnsRecords(config),\n config.tenant,\n env,\n api,\n state,\n );\n }\n\n if (getLogpushJobs(config).length > 0 && env !== \"local\") {\n await logpushJobSync(\n getLogpushJobs(config),\n config.tenant,\n env,\n api,\n state,\n );\n }\n\n const entries = state.getAll();\n const stable: CloudflareSnapshot = { entries: {} };\n for (const key of Object.keys(entries).sort()) {\n stable.entries[key] = stripVolatile(entries[key]!);\n }\n return stable;\n}\n\n/**\n * Per-entry projection used in {@link CloudflareSnapshot}. Drops fields\n * that legitimately move between two reads of the same Cloudflare resource\n * (timestamps); keeps anything that, if it changed, would mean the plan is\n * no longer accurate (cfId, derivedName, kind-specific identity bits).\n */\nfunction stripVolatile(entry: StateEntry): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(entry)) {\n if (k === \"createdAt\" || k === \"updatedAt\") continue;\n out[k] = v;\n }\n return out;\n}\n\nexport interface CloudflareSnapshot {\n /** Sorted by key (== `derivedName`) for deterministic hashing. */\n entries: Record<string, Record<string, unknown>>;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cloudflareSnapshot-BAeNVohz.mjs","names":["att: PlanAttestation","raw: string","parsed: unknown","stable: CloudflareSnapshot","out: Record<string, unknown>"],"sources":["../src/core/plan/planFile.ts","../src/core/plan/cloudflareSnapshot.ts"],"sourcesContent":["import { createHash } from \"crypto\";\nimport { readFileSync, writeFileSync } from \"fs\";\nimport type { CfiConfig, CfiState } from \"../../types.js\";\nimport type { PlanReport } from \"./plan.types.js\";\nimport type { CloudflareSnapshot } from \"./cloudflareSnapshot.js\";\n\n/** Stable on-disk format identifier for `tamer plan --out`. */\nexport const PLAN_FILE_FORMAT = \"tamer-plan/v1\" as const;\n\n/**\n * Serialized `tamer plan` output, suitable for `tamer apply --plan`.\n *\n * The {@link attestation} hashes pin the `(config, state)` pair the plan\n * was generated against. Apply recomputes the same hashes at run time and\n * refuses to proceed if either drifted, so a stale plan can never silently\n * apply against a different reality. Use `--allow-stale` only when you\n * understand the risk.\n */\nexport interface PlanFile {\n format: typeof PLAN_FILE_FORMAT;\n tenantId: string;\n env: string;\n generatedAt: string;\n /** The plan as it would be printed by `tamer plan` (kept for review). */\n report: PlanReport;\n attestation: PlanAttestation;\n}\n\nexport interface PlanAttestation {\n /** SHA-256 over the canonical JSON of the parsed `tamer.config.ts`. */\n configHash: string;\n /** SHA-256 over the canonical JSON of `state.load()` at plan time. */\n stateHash: string;\n /** Revision at plan time (informational; primary check is stateHash). */\n stateRevision?: number;\n /**\n * Drift-aware refresh: SHA-256 over the normalized **Cloudflare-side**\n * snapshot at plan time (per-kind `sync()` against a fresh in-memory\n * state, with timestamps stripped). Apply re-fetches and recomputes; a\n * mismatch means infrastructure drifted out-of-band between plan and\n * apply, so the plan is no longer accurate. Omitted for `env: local`.\n */\n cloudflareHash?: string;\n}\n\n/**\n * Stable JSON stringify: object keys are sorted recursively. The hash must\n * be deterministic across machines, so insertion order can't matter.\n */\nexport function canonicalJson(value: unknown): string {\n return stableStringify(value);\n}\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== \"object\") {\n return JSON.stringify(value);\n }\n if (Array.isArray(value)) {\n return `[${value.map((v) => stableStringify(v)).join(\",\")}]`;\n }\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n const body = keys\n .map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`)\n .join(\",\");\n return `{${body}}`;\n}\n\nexport function sha256Hex(input: string): string {\n return `sha256:${createHash(\"sha256\").update(input).digest(\"hex\")}`;\n}\n\n/**\n * Compute the attestation hashes for `(config, state)` exactly as the\n * apply-side will recompute them. Keep both sides in sync — the canonical\n * JSON algorithm above is the contract.\n */\nexport function computeAttestation(\n config: CfiConfig,\n state: CfiState,\n cloudflareSnapshot?: CloudflareSnapshot,\n): PlanAttestation {\n const att: PlanAttestation = {\n configHash: sha256Hex(canonicalJson(config)),\n stateHash: sha256Hex(canonicalJson(stateForHash(state))),\n stateRevision: state.revision,\n };\n if (cloudflareSnapshot) {\n att.cloudflareHash = sha256Hex(canonicalJson(cloudflareSnapshot));\n }\n return att;\n}\n\n/**\n * Stable hash of a Cloudflare snapshot; same algorithm as\n * `attestation.cloudflareHash` so apply can recompute and compare.\n */\nexport function hashCloudflareSnapshot(\n snapshot: CloudflareSnapshot,\n): string {\n return sha256Hex(canonicalJson(snapshot));\n}\n\n/**\n * State view used for hashing — strips fields that legitimately drift\n * between consecutive reads (timestamps, revision counter) so the hash\n * only changes when *resources* / *tenants* / *stack* change.\n */\nfunction stateForHash(state: CfiState): unknown {\n const { resources, tenants, stack } = state;\n return { resources: resources ?? {}, tenants: tenants ?? {}, stack };\n}\n\nexport function writePlanFile(path: string, file: PlanFile): void {\n writeFileSync(path, JSON.stringify(file, null, 2), \"utf-8\");\n}\n\nexport function readPlanFile(path: string): PlanFile {\n let raw: string;\n try {\n raw = readFileSync(path, \"utf-8\");\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`--plan: cannot read plan file \"${path}\": ${msg}`);\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`--plan: plan file \"${path}\" is not valid JSON: ${msg}`);\n }\n if (!isPlanFile(parsed)) {\n throw new Error(\n `--plan: plan file \"${path}\" is missing required fields or has wrong format ` +\n `(expected ${PLAN_FILE_FORMAT})`,\n );\n }\n if (parsed.format !== PLAN_FILE_FORMAT) {\n throw new Error(\n `--plan: plan file format \"${parsed.format}\" is not supported by this Tamer build (expected ${PLAN_FILE_FORMAT})`,\n );\n }\n return parsed;\n}\n\nfunction isPlanFile(v: unknown): v is PlanFile {\n if (!v || typeof v !== \"object\") return false;\n const o = v as Record<string, unknown>;\n return (\n typeof o.format === \"string\" &&\n typeof o.tenantId === \"string\" &&\n typeof o.env === \"string\" &&\n typeof o.generatedAt === \"string\" &&\n !!o.report &&\n typeof o.report === \"object\" &&\n !!o.attestation &&\n typeof o.attestation === \"object\"\n );\n}\n","import type { CfiConfig, StateEntry } from \"../../types.js\";\nimport type { CFApiClient } from \"../api/CFApiClient.js\";\nimport { StateManager } from \"../state/StateManager.js\";\nimport { stackNameForConfig } from \"../state/stackName.js\";\nimport { resourceModules } from \"../registry/registry.js\";\nimport { namingFromConfig } from \"../config/namingFromConfig.js\";\nimport { getWorkers } from \"../config/loader.js\";\nimport { dispatchNamespaceSync } from \"../../features/dispatch-namespace/index.js\";\nimport { dnsRecordSync } from \"../../features/dns-records/index.js\";\nimport { getDispatchNamespaces, getDnsRecords, getLogpushJobs } from \"../../types.js\";\nimport { logpushJobSync } from \"../../features/logpush-job/index.js\";\nimport { fetchStackImports } from \"../imports/fetchStackImports.js\";\nimport { cloudflareAccountIdFromEnv } from \"../cloudflareEnv.js\";\nimport { mergeWorkerConfigForResourcePick } from \"../config/resolver.js\";\n\n/**\n * Drift-aware Cloudflare snapshot used by `tamer plan --out` and\n * `tamer apply --plan`.\n *\n * Drives the registry's `sync` hook for every kind against a **fresh\n * in-memory state** so the result reflects only what currently exists on\n * Cloudflare (no carryover from D1). The shape mirrors the post-sync\n * resource map but strips volatile fields (timestamps) so consecutive\n * snapshots of an unchanged account hash to the same value.\n *\n * Apply recomputes this against live Cloudflare and refuses to proceed if\n * `cloudflareHash` differs from the plan's pinned hash — that's how a\n * stale plan against drifted infra is detected.\n */\nexport async function buildCloudflareSnapshot(args: {\n config: CfiConfig;\n env: string;\n api: CFApiClient;\n baseDir: string;\n}): Promise<CloudflareSnapshot> {\n const { config, env, api, baseDir } = args;\n const naming = namingFromConfig(config);\n // In-memory snapshot — never round-trips to D1, so stack name only\n // matters for diagnostics; passing it keeps logs consistent with the\n // owning stack.\n const state = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n state.hydrateInMemory();\n\n const accountId = config.account_id ?? cloudflareAccountIdFromEnv() ?? \"\";\n const imports =\n env !== \"local\"\n ? await fetchStackImports(api, config, env).catch(\n () => ({} as Record<string, Record<string, string>>),\n )\n : ({} as Record<string, Record<string, string>>);\n\n const workers = await getWorkers(config, baseDir);\n\n for (const mod of resourceModules) {\n const all = await mod.fetchAll(api);\n for (const [workerKey, wc] of workers) {\n const mergedWorker =\n env !== \"local\" && accountId\n ? mergeWorkerConfigForResourcePick(\n config,\n workerKey,\n wc,\n env,\n accountId,\n naming,\n state,\n { referencesMode: \"tolerant\", imports },\n )\n : wc;\n const resources = mod.pickResources(mergedWorker);\n if (resources.length === 0) continue;\n mod.sync({\n all,\n resources,\n tenant: config.tenant,\n env,\n api,\n state,\n naming,\n config,\n baseDir,\n });\n }\n }\n\n if (getDispatchNamespaces(config).length > 0 && env !== \"local\") {\n await dispatchNamespaceSync(\n getDispatchNamespaces(config),\n config.tenant,\n env,\n api,\n state,\n );\n }\n\n if (getDnsRecords(config).length > 0 && env !== \"local\") {\n await dnsRecordSync(\n getDnsRecords(config),\n config.tenant,\n env,\n api,\n state,\n );\n }\n\n if (getLogpushJobs(config).length > 0 && env !== \"local\") {\n await logpushJobSync(\n getLogpushJobs(config),\n config.tenant,\n env,\n api,\n state,\n );\n }\n\n const entries = state.getAll();\n const stable: CloudflareSnapshot = { entries: {} };\n for (const key of Object.keys(entries).sort()) {\n stable.entries[key] = stripVolatile(entries[key]!);\n }\n return stable;\n}\n\n/**\n * Per-entry projection used in {@link CloudflareSnapshot}. Drops fields\n * that legitimately move between two reads of the same Cloudflare resource\n * (timestamps); keeps anything that, if it changed, would mean the plan is\n * no longer accurate (cfId, derivedName, kind-specific identity bits).\n */\nfunction stripVolatile(entry: StateEntry): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(entry)) {\n if (k === \"createdAt\" || k === \"updatedAt\") continue;\n out[k] = v;\n }\n return out;\n}\n\nexport interface CloudflareSnapshot {\n /** Sorted by key (== `derivedName`) for deterministic hashing. */\n entries: Record<string, Record<string, unknown>>;\n}\n"],"mappings":"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"}
@@ -1,12 +1,12 @@
1
- import { A as rewriteIntraStackServiceTargets, B as getConfigBaseDir, D as mergedWorkerConfigForEnv, H as loadConfig, L as CFApiClient, M as wranglerConfigCliArgs, O as resolveDeployedWorkerName, R as cloudflareAccountIdFromEnv, T as buildIntraStackScriptNameMap, V as getWorkers, d as requiredSecretsForWorker, f as fetchStackImports, h as StateManager, i as pushSecretsForDeploy, k as resolveWorkerConfig, l as createDeploySecretsResources, u as namingFromConfig, w as stackNameForConfig } from "./tamer.mjs";
2
- import { i as workflowsApply } from "./registry-BrOxbA2i.mjs";
3
- import "./r2S3EmptyBucket-B9_pHfvB.mjs";
4
- import { n as writeWranglerJson, t as generateWranglerConfig } from "./generator-MX8MAHd9.mjs";
5
- import { t as assertShardRegistryPresentForDeploy } from "./emit-DDTQVfi_.mjs";
6
- import "./logpush-job-GqVKG_HI.mjs";
7
- import { r as workerRoutesApply } from "./worker-route-CUQBu9xe.mjs";
8
- import { t as runSync } from "./sync-kl7MaCQV.mjs";
9
- import { n as spawnWranglerSync, t as spawnBuildSync } from "./wranglerSpawn-Dx4I0Wu-.mjs";
1
+ import { A as rewriteIntraStackServiceTargets, D as mergedWorkerConfigForEnv, H as getWorkers, N as wranglerConfigCliArgs, O as resolveDeployedWorkerName, R as CFApiClient, T as buildIntraStackScriptNameMap, U as loadConfig, V as getConfigBaseDir, d as requiredSecretsForWorker, f as fetchStackImports, h as StateManager, i as pushSecretsForDeploy, k as resolveWorkerConfig, l as createDeploySecretsResources, u as namingFromConfig, w as stackNameForConfig, z as cloudflareAccountIdFromEnv } from "./tamer.mjs";
2
+ import { i as workflowsApply } from "./registry-X9dlQxG3.mjs";
3
+ import "./r2S3EmptyBucket-CXLmOrYF.mjs";
4
+ import { n as writeWranglerJson, t as generateWranglerConfig } from "./generator-DAU5K77L.mjs";
5
+ import { t as assertShardRegistryPresentForDeploy } from "./emit-Dh68dvo5.mjs";
6
+ import "./logpush-job-C_6uzGUC.mjs";
7
+ import { r as workerRoutesApply } from "./worker-route-CvuUPq1k.mjs";
8
+ import { t as runSync } from "./sync-DfJGkOME.mjs";
9
+ import { n as spawnWranglerSync, t as spawnBuildSync } from "./wranglerSpawn-DWdgrsmQ.mjs";
10
10
 
11
11
  //#region src/cli/commands/deploy.ts
12
12
  /**
@@ -161,4 +161,4 @@ async function runDeploy(options) {
161
161
 
162
162
  //#endregion
163
163
  export { runDeploy };
164
- //# sourceMappingURL=deploy-C4NOE5S1.mjs.map
164
+ //# sourceMappingURL=deploy-BEaNADU6.mjs.map