@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
@@ -1 +1 @@
1
- {"version":3,"file":"deploy-C4NOE5S1.mjs","names":["out: WorkerEntry[]","deploySecrets: Awaited<\n ReturnType<typeof createDeploySecretsResources>\n > | null","buildEnv: Record<string, string>"],"sources":["../src/cli/commands/deploy.ts"],"sourcesContent":["import { loadConfig, getWorkers, getConfigBaseDir } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { namingFromConfig } from \"../../core/config/namingFromConfig.js\";\nimport type { NamingEngine } from \"../../core/naming/NamingEngine.js\";\nimport { wranglerConfigCliArgs } from \"../../core/wrangler/wranglerOutFile.js\";\nimport { spawnWranglerSync, spawnBuildSync } from \"../../core/wrangler/wranglerSpawn.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport {\n buildIntraStackScriptNameMap,\n mergedWorkerConfigForEnv,\n resolveDeployedWorkerName,\n resolveWorkerConfig,\n rewriteIntraStackServiceTargets,\n} from \"../../core/config/resolver.js\";\nimport { requiredSecretsForWorker } from \"../../core/secrets/declared.js\";\nimport { createDeploySecretsResources } from \"./secrets/context.js\";\nimport { pushSecretsForDeploy } from \"./secrets/push.js\";\nimport { workflowsApply } from \"../../features/workflows/workflows.apply.js\";\nimport {\n generateWranglerConfig,\n writeWranglerJson,\n} from \"../../core/wrangler/generator.js\";\nimport { runSync } from \"./sync.js\";\nimport { workerRoutesApply } from \"../../features/worker-route/index.js\";\nimport { fetchStackImports } from \"../../core/imports/fetchStackImports.js\";\nimport { assertShardRegistryPresentForDeploy } from \"../../core/codegen/shardRegistry/index.js\";\nimport type { CfiConfig, WorkerConfig } from \"../../types.js\";\n\ntype WorkerEntry = [string, WorkerConfig];\n\n/**\n * Topologically sort workers by `services[].service` so that dependencies\n * deploy before dependents. Cloudflare rejects deploys that reference\n * service bindings to workers that don't yet exist on the account.\n * Cross-config dependencies (services not in this monorepo) are ignored.\n */\nexport function topoSortWorkersByServiceBindings(\n workers: WorkerEntry[],\n config: CfiConfig,\n naming: NamingEngine,\n env: string,\n): WorkerEntry[] {\n const intraMap = buildIntraStackScriptNameMap(config, env, naming);\n const scriptNameToKey = new Map<string, string>();\n for (const [key, cfg] of workers) {\n const name = resolveDeployedWorkerName(config, key, cfg, env, naming);\n scriptNameToKey.set(name, key);\n }\n\n const byKey = new Map(workers);\n const visited = new Set<string>();\n const visiting = new Set<string>();\n const out: WorkerEntry[] = [];\n\n function visit(key: string): void {\n if (visited.has(key)) return;\n if (visiting.has(key)) return;\n visiting.add(key);\n const cfg = byKey.get(key);\n if (cfg) {\n const merged = rewriteIntraStackServiceTargets(\n mergedWorkerConfigForEnv(cfg, env, config.tenant),\n intraMap,\n );\n const serviceDeps = merged.services?.map((s) => s.service) ?? [];\n const doDeps =\n merged.durable_objects?.bindings\n ?.map((b) => b.script_name)\n .filter((s): s is string => typeof s === \"string\" && s.length > 0)\n .map((s) => intraMap.get(s) ?? s)\n ?? [];\n const deps = [...serviceDeps, ...doDeps];\n for (const dep of deps) {\n const depKey = scriptNameToKey.get(dep);\n if (depKey && depKey !== key) visit(depKey);\n }\n visiting.delete(key);\n visited.add(key);\n out.push([key, cfg]);\n }\n }\n\n for (const [key] of workers) visit(key);\n return out;\n}\n\nexport async function runDeploy(options: {\n worker?: string;\n env?: string;\n configPath?: string;\n /** Passed to `wrangler deploy --dispatch-namespace` when the worker has no `dispatchNamespace` in config. */\n dispatchNamespace?: string;\n}): Promise<void> {\n const workerFilter = options.worker;\n // `--env` is intentionally required (no silent default to \"prod\") to\n // match every other Tamer command and to avoid the classic \"ran\n // `tamer deploy` from the wrong shell, shipped a half-tested branch\n // to production\" footgun.\n const env = options.env;\n if (!env) {\n throw new Error(\n \"deploy: --env is required (e.g. --env staging). \" +\n \"Tamer no longer defaults to prod — pass the env explicitly.\",\n );\n }\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 console.log(`Syncing state for env: ${env}...`);\n await runSync({ env, configPath });\n\n const naming = namingFromConfig(config);\n const api = new CFApiClient(accountId);\n const state = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n await state.hydrate(api);\n const imports = await fetchStackImports(api, config, env);\n state.beginOperation(\"deploy\", workerFilter ? `worker:${workerFilter}` : undefined);\n\n try {\n\n const workers = await getWorkers(config, baseDir);\n const toDeploy = workerFilter\n ? workers.filter(([k]) => k === workerFilter)\n : workers;\n\n if (toDeploy.length === 0) {\n throw new Error(\n workerFilter\n ? `Worker \"${workerFilter}\" not found`\n : \"No workers configured\",\n );\n }\n\n const ordered = topoSortWorkersByServiceBindings(\n toDeploy,\n config,\n naming,\n env,\n );\n\n await assertShardRegistryPresentForDeploy({\n config,\n env,\n baseDir,\n accountId,\n naming,\n state,\n imports,\n workers,\n });\n\n let deploySecrets: Awaited<\n ReturnType<typeof createDeploySecretsResources>\n > | null = null;\n\n for (const [workerKey, workerConfig] of ordered) {\n const mergedForSecrets = mergedWorkerConfigForEnv(\n workerConfig,\n env,\n config.tenant,\n );\n const requiredSecrets = requiredSecretsForWorker(mergedForSecrets);\n\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(resolved.workerDir, wranglerConfig, resolved.wranglerOutFile);\n\n const buildConfig = mergedForSecrets.build;\n if (buildConfig) {\n console.log(`Building ${workerKey} (${buildConfig.command})...`);\n const buildEnv: Record<string, string> = {};\n if (wranglerConfig.vars) {\n for (const [k, v] of Object.entries(wranglerConfig.vars)) {\n buildEnv[k] = typeof v === \"string\" ? v : String(v);\n }\n }\n const buildResult = spawnBuildSync(buildConfig.command, {\n cwd: resolved.workerDir,\n stdio: \"inherit\",\n env: buildEnv,\n });\n if (buildResult.status !== 0) {\n throw new Error(\n `build failed for ${workerKey} (exit ${buildResult.status})`,\n );\n }\n console.log(`Built ${workerKey}`);\n }\n\n const typesArgs = [\n \"wrangler\",\n ...wranglerConfigCliArgs(resolved.wranglerOutFile),\n \"types\",\n ];\n const typesResult = spawnWranglerSync(typesArgs, {\n cwd: resolved.workerDir,\n stdio: \"inherit\",\n });\n if (typesResult.status !== 0) {\n throw new Error(`wrangler types failed for ${workerKey}`);\n }\n\n const dispatchNs =\n resolved.dispatchNamespace ?? options.dispatchNamespace;\n const deployArgs = [\n \"wrangler\",\n ...wranglerConfigCliArgs(resolved.wranglerOutFile),\n \"deploy\",\n ];\n if (dispatchNs) {\n deployArgs.push(\"--dispatch-namespace\", dispatchNs);\n }\n\n const deployResult = spawnWranglerSync(deployArgs, {\n cwd: resolved.workerDir,\n stdio: \"inherit\",\n });\n if (deployResult.status !== 0) {\n throw new Error(`wrangler deploy failed for ${workerKey}`);\n }\n console.log(`Deployed ${workerKey}`);\n\n // Push secrets after the script exists (chicken-and-egg: secrets push\n // needs the deployed worker to exist). On fresh envs (ephemeral PR\n // previews), the worker doesn't exist until wrangler deploy runs.\n if (requiredSecrets.length > 0 && env !== \"local\") {\n if (!deploySecrets) {\n deploySecrets = await createDeploySecretsResources(api, env);\n }\n await pushSecretsForDeploy({\n workerKey,\n deployedName: resolved.workerName,\n required: requiredSecrets,\n vault: deploySecrets.vault,\n state,\n api,\n masterKey: deploySecrets.masterKey,\n });\n }\n\n // Register workflows after the script exists (chicken-and-egg: workflows\n // need the deployed script). During `tamer apply`, workflow registration\n // is deferred when the script doesn't exist yet. Here the script is live.\n const workflowResources = resolved.resources?.workflows;\n if (workflowResources && workflowResources.length > 0) {\n await workflowsApply(\n workflowResources,\n config.tenant,\n env,\n api,\n state,\n naming,\n { workerKey, deployedName: resolved.workerName },\n );\n }\n }\n\n if (env !== \"local\") {\n try {\n await workerRoutesApply(\n env,\n config,\n baseDir,\n accountId,\n naming,\n state,\n api,\n { imports },\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`worker routes apply failed after deploy: ${msg}`);\n }\n }\n\n state.finishOperation();\n await state.persist(api);\n console.log(`Deploy complete for env: ${env}`);\n } catch (err) {\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"],"mappings":"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"}
1
+ {"version":3,"file":"deploy-BEaNADU6.mjs","names":["out: WorkerEntry[]","deploySecrets: Awaited<\n ReturnType<typeof createDeploySecretsResources>\n > | null","buildEnv: Record<string, string>"],"sources":["../src/cli/commands/deploy.ts"],"sourcesContent":["import { loadConfig, getWorkers, getConfigBaseDir } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { namingFromConfig } from \"../../core/config/namingFromConfig.js\";\nimport type { NamingEngine } from \"../../core/naming/NamingEngine.js\";\nimport { wranglerConfigCliArgs } from \"../../core/wrangler/wranglerOutFile.js\";\nimport { spawnWranglerSync, spawnBuildSync } from \"../../core/wrangler/wranglerSpawn.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport {\n buildIntraStackScriptNameMap,\n mergedWorkerConfigForEnv,\n resolveDeployedWorkerName,\n resolveWorkerConfig,\n rewriteIntraStackServiceTargets,\n} from \"../../core/config/resolver.js\";\nimport { requiredSecretsForWorker } from \"../../core/secrets/declared.js\";\nimport { createDeploySecretsResources } from \"./secrets/context.js\";\nimport { pushSecretsForDeploy } from \"./secrets/push.js\";\nimport { workflowsApply } from \"../../features/workflows/workflows.apply.js\";\nimport {\n generateWranglerConfig,\n writeWranglerJson,\n} from \"../../core/wrangler/generator.js\";\nimport { runSync } from \"./sync.js\";\nimport { workerRoutesApply } from \"../../features/worker-route/index.js\";\nimport { fetchStackImports } from \"../../core/imports/fetchStackImports.js\";\nimport { assertShardRegistryPresentForDeploy } from \"../../core/codegen/shardRegistry/index.js\";\nimport type { CfiConfig, WorkerConfig } from \"../../types.js\";\n\ntype WorkerEntry = [string, WorkerConfig];\n\n/**\n * Topologically sort workers by `services[].service` so that dependencies\n * deploy before dependents. Cloudflare rejects deploys that reference\n * service bindings to workers that don't yet exist on the account.\n * Cross-config dependencies (services not in this monorepo) are ignored.\n */\nexport function topoSortWorkersByServiceBindings(\n workers: WorkerEntry[],\n config: CfiConfig,\n naming: NamingEngine,\n env: string,\n): WorkerEntry[] {\n const intraMap = buildIntraStackScriptNameMap(config, env, naming);\n const scriptNameToKey = new Map<string, string>();\n for (const [key, cfg] of workers) {\n const name = resolveDeployedWorkerName(config, key, cfg, env, naming);\n scriptNameToKey.set(name, key);\n }\n\n const byKey = new Map(workers);\n const visited = new Set<string>();\n const visiting = new Set<string>();\n const out: WorkerEntry[] = [];\n\n function visit(key: string): void {\n if (visited.has(key)) return;\n if (visiting.has(key)) return;\n visiting.add(key);\n const cfg = byKey.get(key);\n if (cfg) {\n const merged = rewriteIntraStackServiceTargets(\n mergedWorkerConfigForEnv(cfg, env, config.tenant),\n intraMap,\n );\n const serviceDeps = merged.services?.map((s) => s.service) ?? [];\n const doDeps =\n merged.durable_objects?.bindings\n ?.map((b) => b.script_name)\n .filter((s): s is string => typeof s === \"string\" && s.length > 0)\n .map((s) => intraMap.get(s) ?? s)\n ?? [];\n const deps = [...serviceDeps, ...doDeps];\n for (const dep of deps) {\n const depKey = scriptNameToKey.get(dep);\n if (depKey && depKey !== key) visit(depKey);\n }\n visiting.delete(key);\n visited.add(key);\n out.push([key, cfg]);\n }\n }\n\n for (const [key] of workers) visit(key);\n return out;\n}\n\nexport async function runDeploy(options: {\n worker?: string;\n env?: string;\n configPath?: string;\n /** Passed to `wrangler deploy --dispatch-namespace` when the worker has no `dispatchNamespace` in config. */\n dispatchNamespace?: string;\n}): Promise<void> {\n const workerFilter = options.worker;\n // `--env` is intentionally required (no silent default to \"prod\") to\n // match every other Tamer command and to avoid the classic \"ran\n // `tamer deploy` from the wrong shell, shipped a half-tested branch\n // to production\" footgun.\n const env = options.env;\n if (!env) {\n throw new Error(\n \"deploy: --env is required (e.g. --env staging). \" +\n \"Tamer no longer defaults to prod — pass the env explicitly.\",\n );\n }\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 console.log(`Syncing state for env: ${env}...`);\n await runSync({ env, configPath });\n\n const naming = namingFromConfig(config);\n const api = new CFApiClient(accountId);\n const state = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n await state.hydrate(api);\n const imports = await fetchStackImports(api, config, env);\n state.beginOperation(\"deploy\", workerFilter ? `worker:${workerFilter}` : undefined);\n\n try {\n\n const workers = await getWorkers(config, baseDir);\n const toDeploy = workerFilter\n ? workers.filter(([k]) => k === workerFilter)\n : workers;\n\n if (toDeploy.length === 0) {\n throw new Error(\n workerFilter\n ? `Worker \"${workerFilter}\" not found`\n : \"No workers configured\",\n );\n }\n\n const ordered = topoSortWorkersByServiceBindings(\n toDeploy,\n config,\n naming,\n env,\n );\n\n await assertShardRegistryPresentForDeploy({\n config,\n env,\n baseDir,\n accountId,\n naming,\n state,\n imports,\n workers,\n });\n\n let deploySecrets: Awaited<\n ReturnType<typeof createDeploySecretsResources>\n > | null = null;\n\n for (const [workerKey, workerConfig] of ordered) {\n const mergedForSecrets = mergedWorkerConfigForEnv(\n workerConfig,\n env,\n config.tenant,\n );\n const requiredSecrets = requiredSecretsForWorker(mergedForSecrets);\n\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(resolved.workerDir, wranglerConfig, resolved.wranglerOutFile);\n\n const buildConfig = mergedForSecrets.build;\n if (buildConfig) {\n console.log(`Building ${workerKey} (${buildConfig.command})...`);\n const buildEnv: Record<string, string> = {};\n if (wranglerConfig.vars) {\n for (const [k, v] of Object.entries(wranglerConfig.vars)) {\n buildEnv[k] = typeof v === \"string\" ? v : String(v);\n }\n }\n const buildResult = spawnBuildSync(buildConfig.command, {\n cwd: resolved.workerDir,\n stdio: \"inherit\",\n env: buildEnv,\n });\n if (buildResult.status !== 0) {\n throw new Error(\n `build failed for ${workerKey} (exit ${buildResult.status})`,\n );\n }\n console.log(`Built ${workerKey}`);\n }\n\n const typesArgs = [\n \"wrangler\",\n ...wranglerConfigCliArgs(resolved.wranglerOutFile),\n \"types\",\n ];\n const typesResult = spawnWranglerSync(typesArgs, {\n cwd: resolved.workerDir,\n stdio: \"inherit\",\n });\n if (typesResult.status !== 0) {\n throw new Error(`wrangler types failed for ${workerKey}`);\n }\n\n const dispatchNs =\n resolved.dispatchNamespace ?? options.dispatchNamespace;\n const deployArgs = [\n \"wrangler\",\n ...wranglerConfigCliArgs(resolved.wranglerOutFile),\n \"deploy\",\n ];\n if (dispatchNs) {\n deployArgs.push(\"--dispatch-namespace\", dispatchNs);\n }\n\n const deployResult = spawnWranglerSync(deployArgs, {\n cwd: resolved.workerDir,\n stdio: \"inherit\",\n });\n if (deployResult.status !== 0) {\n throw new Error(`wrangler deploy failed for ${workerKey}`);\n }\n console.log(`Deployed ${workerKey}`);\n\n // Push secrets after the script exists (chicken-and-egg: secrets push\n // needs the deployed worker to exist). On fresh envs (ephemeral PR\n // previews), the worker doesn't exist until wrangler deploy runs.\n if (requiredSecrets.length > 0 && env !== \"local\") {\n if (!deploySecrets) {\n deploySecrets = await createDeploySecretsResources(api, env);\n }\n await pushSecretsForDeploy({\n workerKey,\n deployedName: resolved.workerName,\n required: requiredSecrets,\n vault: deploySecrets.vault,\n state,\n api,\n masterKey: deploySecrets.masterKey,\n });\n }\n\n // Register workflows after the script exists (chicken-and-egg: workflows\n // need the deployed script). During `tamer apply`, workflow registration\n // is deferred when the script doesn't exist yet. Here the script is live.\n const workflowResources = resolved.resources?.workflows;\n if (workflowResources && workflowResources.length > 0) {\n await workflowsApply(\n workflowResources,\n config.tenant,\n env,\n api,\n state,\n naming,\n { workerKey, deployedName: resolved.workerName },\n );\n }\n }\n\n if (env !== \"local\") {\n try {\n await workerRoutesApply(\n env,\n config,\n baseDir,\n accountId,\n naming,\n state,\n api,\n { imports },\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`worker routes apply failed after deploy: ${msg}`);\n }\n }\n\n state.finishOperation();\n await state.persist(api);\n console.log(`Deploy complete for env: ${env}`);\n } catch (err) {\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"],"mappings":"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"}
@@ -1,14 +1,14 @@
1
1
  import { f as getDispatchNamespaces, p as getDnsRecords } from "./normalize-DVSTRZhO.mjs";
2
- import { B as getConfigBaseDir, F as deleteEnvSecretRows, H as loadConfig, L as CFApiClient, P as isEphemeralEnv, R as cloudflareAccountIdFromEnv, V as getWorkers, h as StateManager, u as namingFromConfig, w as stackNameForConfig, x as deleteEnvStateRows } from "./tamer.mjs";
3
- import { a as logicalNamesForResourceKind, n as resourceModules } from "./registry-BrOxbA2i.mjs";
4
- import "./r2S3EmptyBucket-B9_pHfvB.mjs";
5
- import { n as logpushJobDestroy } from "./logpush-job-GqVKG_HI.mjs";
6
- import { n as workerRoutesDestroy } from "./worker-route-CUQBu9xe.mjs";
7
- import { t as runSync } from "./sync-kl7MaCQV.mjs";
8
- import { i as hashCloudflareSnapshot, t as buildCloudflareSnapshot } from "./cloudflareSnapshot-CjXNMr4X.mjs";
9
- import { t as verifyPlanFile } from "./verifyPlanFile-D_-Qbh1J.mjs";
10
- import { t as deleteEnvArtifacts } from "./tamerArtifactsR2-COndFmk5.mjs";
11
- import { n as workersDestroy } from "./workers-DWXnZAzG.mjs";
2
+ import { F as isEphemeralEnv, H as getWorkers, I as deleteEnvSecretRows, R as CFApiClient, U as loadConfig, V as getConfigBaseDir, h as StateManager, u as namingFromConfig, w as stackNameForConfig, x as deleteEnvStateRows, z as cloudflareAccountIdFromEnv } from "./tamer.mjs";
3
+ import { a as logicalNamesForResourceKind, n as resourceModules } from "./registry-X9dlQxG3.mjs";
4
+ import "./r2S3EmptyBucket-CXLmOrYF.mjs";
5
+ import { n as logpushJobDestroy } from "./logpush-job-C_6uzGUC.mjs";
6
+ import { n as workerRoutesDestroy } from "./worker-route-CvuUPq1k.mjs";
7
+ import { t as runSync } from "./sync-DfJGkOME.mjs";
8
+ import { i as hashCloudflareSnapshot, t as buildCloudflareSnapshot } from "./cloudflareSnapshot-BAeNVohz.mjs";
9
+ import { t as verifyPlanFile } from "./verifyPlanFile-CoAOsD3W.mjs";
10
+ import { t as deleteEnvArtifacts } from "./tamerArtifactsR2-B9myb-IA.mjs";
11
+ import { n as workersDestroy } from "./workers-DSlrKeNL.mjs";
12
12
 
13
13
  //#region src/features/dispatch-namespace/dispatch-namespace.destroy.ts
14
14
  /**
@@ -269,4 +269,4 @@ async function runDestroy(options) {
269
269
 
270
270
  //#endregion
271
271
  export { runDestroy };
272
- //# sourceMappingURL=destroy-BeOYY2U6.mjs.map
272
+ //# sourceMappingURL=destroy-Krf35oqE.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"destroy-BeOYY2U6.mjs","names":["destroyErrors: Array<{ step: string; error: string }>","byType: Record<string, string[]>"],"sources":["../src/features/dispatch-namespace/dispatch-namespace.destroy.ts","../src/features/dns-records/dns-records.destroy.ts","../src/cli/destroyGuard.ts","../src/cli/commands/destroy.ts"],"sourcesContent":["import type { CfiConfig } from \"../../types.js\";\nimport { getDispatchNamespaces } from \"../../types.js\";\nimport type { StateManager } from \"../../core/state/StateManager.js\";\nimport type { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport type { DispatchNamespaceStateEntry } from \"../../types.js\";\nimport { isEphemeralEnv } from \"./dispatch-namespace.resolve.js\";\n\n/**\n * Tear down every dispatch namespace recorded in state.\n *\n * Cloudflare refuses to delete a namespace that still contains scripts, so we\n * enumerate `/dispatch/namespaces/{ns}/scripts` and delete each (with `force`\n * so dependents like service-bind targets don't block the removal). This\n * covers tenant scripts uploaded by `tamer wfp put` / `provision-workflow`\n * that aren't otherwise tracked in Tamer state.\n */\nexport async function dispatchNamespaceDestroy(\n env: string,\n state: StateManager,\n api: CFApiClient,\n config: CfiConfig,\n _force?: boolean,\n): Promise<void> {\n const allowedLogical = new Set(\n getDispatchNamespaces(config).map((d) => d.logicalName),\n );\n if (allowedLogical.size === 0) return;\n\n for (const [key, entry] of Object.entries(state.getAll())) {\n if (entry.type !== \"dispatch_namespace\") continue;\n const ns = entry as DispatchNamespaceStateEntry;\n if (!allowedLogical.has(ns.logicalName)) continue;\n const isSharedEphemeral = ns.derivedName.endsWith(\"-ephemeral\");\n try {\n const scripts = await api.dispatchNamespaceScriptList(ns.derivedName);\n for (const s of scripts) {\n if (isEphemeralEnv(env, config.tenant) && isSharedEphemeral) {\n if (!s.id.endsWith(`-${env}`)) continue;\n }\n try {\n await api.dispatchNamespaceScriptDelete(ns.derivedName, s.id, {\n force: true,\n });\n console.log(\n `Deleted tenant script \"${s.id}\" from namespace ${ns.derivedName}.`,\n );\n } catch (err) {\n console.warn(\n `Failed to delete tenant script ${s.id} in ${ns.derivedName}:`,\n err instanceof Error ? err.message : err,\n );\n }\n }\n if (isEphemeralEnv(env, config.tenant) && isSharedEphemeral) {\n console.log(\n `Left shared dispatch namespace ${ns.derivedName} (removed only scripts suffixed -${env}).`,\n );\n continue;\n }\n await api.dispatchNamespaceDelete(ns.derivedName);\n state.delete(key);\n } catch (err) {\n console.warn(\n `Failed to delete dispatch namespace ${ns.derivedName}:`,\n err instanceof Error ? err.message : err,\n );\n }\n }\n}\n","import type { CfiConfig, DnsRecordStateEntry } from \"../../types.js\";\nimport { getDnsRecords } from \"../../types.js\";\nimport type { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport type { StateManager } from \"../../core/state/StateManager.js\";\n\n/**\n * Tear down every DNS record this stack owns. Restricted to records\n * whose `logicalName` is declared in the current `CfiConfig.dnsRecords`\n * (matches `runDestroy` semantics for shared state rows). Records flagged\n * `preserveOnDestroy: true` are left in place but still dropped from\n * state — the operator is responsible for re-importing them later.\n */\nexport async function dnsRecordDestroy(\n env: string,\n state: StateManager,\n api: CFApiClient,\n config: CfiConfig,\n _force?: boolean,\n): Promise<void> {\n if (env === \"local\") return;\n const declared = getDnsRecords(config);\n if (declared.length === 0) return;\n const preserve = new Map<string, boolean>(\n declared.map((c) => [c.logicalName, !!c.preserveOnDestroy]),\n );\n const allowedLogical = new Set(declared.map((c) => c.logicalName));\n\n for (const [key, entry] of Object.entries(state.getAll())) {\n if (entry.type !== \"dns_record\") continue;\n const rec = entry as DnsRecordStateEntry;\n if (!allowedLogical.has(rec.logicalName)) continue;\n if (preserve.get(rec.logicalName)) {\n console.log(\n `Preserved DNS record ${rec.recordType} ${rec.name} (preserveOnDestroy).`,\n );\n state.delete(key);\n continue;\n }\n try {\n await api.zoneDnsRecordDelete(rec.zoneId, rec.recordId);\n state.delete(key);\n } catch (err) {\n console.warn(\n `Failed to delete DNS record ${rec.recordType} ${rec.name}:`,\n err instanceof Error ? err.message : err,\n );\n }\n }\n}\n","/** Shared envs where destroy must be confirmed with `--confirm-env <same>`. */\nexport const SHARED_ENV_DESTROY = [\n \"dev\",\n \"staging\",\n \"prod\",\n \"production\",\n] as const;\n\n/**\n * @param force When true, skips the typed confirmation (break-glass).\n */\nexport function assertDestroyEnvAllowed(\n env: string,\n force: boolean,\n confirmEnv?: string,\n): void {\n if (force) return;\n if (!SHARED_ENV_DESTROY.includes(env as (typeof SHARED_ENV_DESTROY)[number])) {\n return;\n }\n if (confirmEnv !== env) {\n throw new Error(\n `Destroying shared environment \"${env}\" requires --confirm-env ${env}`,\n );\n }\n}\n","import { loadConfig, getWorkers, getConfigBaseDir } from \"../../core/config/loader.js\";\nimport { logicalNamesForResourceKind } from \"../../core/config/resourcesFromConfig.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { deleteEnvStateRows } from \"../../core/state/tamerStateDb.js\";\nimport { deleteEnvArtifacts } from \"../../core/state/tamerArtifactsR2.js\";\nimport { deleteEnvSecretRows } from \"../../core/secrets/secretsDb.js\";\nimport { getDispatchNamespaces, getDnsRecords } from \"../../types.js\";\nimport { logpushJobDestroy } from \"../../features/logpush-job/index.js\";\nimport { assertDestroyEnvAllowed } from \"../destroyGuard.js\";\nimport { dispatchNamespaceDestroy } from \"../../features/dispatch-namespace/index.js\";\nimport { dnsRecordDestroy } from \"../../features/dns-records/index.js\";\nimport { workersDestroy } from \"../../features/workers/index.js\";\nimport { workerRoutesDestroy } from \"../../features/worker-route/index.js\";\nimport { runSync } from \"./sync.js\";\nimport { resourceModules } from \"../../core/registry/registry.js\";\nimport { namingFromConfig } from \"../../core/config/namingFromConfig.js\";\nimport { verifyPlanFile } from \"../../core/plan/verifyPlanFile.js\";\nimport { hashCloudflareSnapshot } from \"../../core/plan/planFile.js\";\nimport { buildCloudflareSnapshot } from \"../../core/plan/cloudflareSnapshot.js\";\n\nexport async function runDestroy(options: {\n env: string;\n force?: boolean;\n skipWorkers?: boolean;\n confirmEnv?: string;\n configPath?: string;\n /** When true, delete the shared `tamer-state-{env}` D1 after other resources (use on last stack teardown). */\n wipeMetadata?: boolean;\n /**\n * Path to a destroy plan file from `tamer plan --destroy --out`. Destroy\n * recomputes the `(config, state, cloudflare)` attestation hashes and\n * refuses to proceed if any drifted (override with `allowStale`). The\n * pinned plan ensures the operator destroys exactly what they reviewed.\n */\n planFile?: string;\n allowStale?: boolean;\n}): Promise<void> {\n const {\n env,\n force = false,\n skipWorkers = false,\n confirmEnv,\n configPath,\n wipeMetadata = false,\n } = options;\n assertDestroyEnvAllowed(env, force, confirmEnv);\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 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: \"destroy\",\n expectedMode: \"destroy\",\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 state.beginOperation(\"destroy\", wipeMetadata ? \"wipe-metadata\" : undefined);\n try {\n await state.persist(api);\n } catch {\n /* in-progress marker best-effort */\n }\n\n const destroyErrors: Array<{ step: string; error: string }> = [];\n\n async function tryStep(step: string, fn: () => Promise<void>): Promise<void> {\n try {\n await fn();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (/404|not found|does not exist|already/i.test(msg)) {\n console.log(` ~ ${step}: already gone`);\n } else {\n console.warn(` ✗ ${step}: ${msg}`);\n destroyErrors.push({ step, error: msg });\n }\n }\n }\n\n try {\n // Inventory: show what's in state so the operator can verify\n // nothing will be missed (or left behind).\n const allState = state.getAll();\n const stateKeys = Object.keys(allState);\n if (stateKeys.length > 0) {\n console.log(`\\nDestroy inventory (${stateKeys.length} resource(s) in state for env ${env}):`);\n const byType: Record<string, string[]> = {};\n for (const [key, entry] of Object.entries(allState)) {\n const type = entry.type ?? \"unknown\";\n if (!byType[type]) byType[type] = [];\n byType[type].push(key);\n }\n for (const [type, keys] of Object.entries(byType).sort()) {\n console.log(` ${type} (${keys.length}):`);\n for (const key of keys) {\n const entry = allState[key] as { derivedName?: string };\n console.log(` ${entry.derivedName ?? key}`);\n }\n }\n console.log(\"\");\n }\n\n if (!skipWorkers) {\n await tryStep(\"worker routes\", async () => {\n await workerRoutesDestroy(env, config, baseDir, state, api);\n await state.persist(api);\n });\n await tryStep(\"worker scripts\", () =>\n workersDestroy(env, baseDir, accountId, config, state, api, force),\n );\n }\n\n const ownedByKind = await Promise.all(\n resourceModules.map((m) =>\n logicalNamesForResourceKind(config, baseDir, m.kind).then((set) => ({\n mod: m,\n owned: set,\n })),\n ),\n );\n const workers = await getWorkers(config, baseDir);\n\n await tryStep(\"Logpush + Pipelines\", async () => {\n await logpushJobDestroy(env, state, api, config);\n await state.persist(api);\n });\n\n for (const { mod, owned } of ownedByKind) {\n if (owned.size === 0) continue;\n const resources = workers.flatMap(([, wc]) => mod.pickResources(wc));\n await tryStep(`${mod.label} (${[...owned].join(\", \")})`, () =>\n mod.destroy({\n resources,\n tenant: config.tenant,\n env,\n api,\n state,\n naming,\n config,\n baseDir,\n force,\n }),\n );\n }\n\n if (getDispatchNamespaces(config).length > 0) {\n await tryStep(\"dispatch namespaces\", () =>\n dispatchNamespaceDestroy(env, state, api, config, force),\n );\n }\n\n if (getDnsRecords(config).length > 0) {\n await tryStep(\"DNS records\", () =>\n dnsRecordDestroy(env, state, api, config, force),\n );\n }\n\n // Clear `stackOutputs` — the values pointed at resources we just deleted.\n state.replaceStackOutputs({});\n\n if (env !== \"local\" && wipeMetadata && destroyErrors.length === 0) {\n state.clearDirty();\n const deletedState = await deleteEnvStateRows(api, env);\n if (deletedState) {\n console.log(`Cleared Tamer state rows for env ${env}.`);\n }\n try {\n await deleteEnvArtifacts(api, env);\n } catch (err) {\n console.warn(\n `Failed to clean Tamer artifacts for env ${env}:`,\n err instanceof Error ? err.message : err,\n );\n }\n try {\n const deletedSecrets = await deleteEnvSecretRows(api, env);\n if (deletedSecrets) {\n console.log(`Cleared Tamer secret rows for env ${env}.`);\n }\n } catch (err) {\n console.warn(\n `Failed to clean Tamer secrets for env ${env}:`,\n err instanceof Error ? err.message : err,\n );\n }\n }\n\n if (env !== \"local\" && (!wipeMetadata || destroyErrors.length > 0)) {\n if (destroyErrors.length > 0) {\n state.failOperation(\n `${destroyErrors.length} step(s) failed: ${destroyErrors.map((e) => e.step).join(\", \")}`,\n );\n } else {\n state.finishOperation();\n }\n try {\n await state.persist(api);\n } catch {\n /* state row may have been wiped by sub-steps */\n }\n }\n state.clearDirty();\n\n // Post-destroy check: what's still in state?\n const remaining = Object.keys(state.getAll());\n if (remaining.length > 0) {\n console.warn(\n `\\nWarning: ${remaining.length} resource(s) still in state after destroy:`,\n );\n for (const key of remaining) {\n const entry = state.get(key) as { derivedName?: string; type?: string } | undefined;\n console.warn(` ${entry?.derivedName ?? key} (${entry?.type})`);\n }\n }\n\n if (destroyErrors.length > 0) {\n console.warn(`\\n${destroyErrors.length} step(s) failed during destroy:`);\n for (const { step, error } of destroyErrors) {\n console.warn(` ${step}: ${error}`);\n }\n throw new Error(\n `destroy completed with ${destroyErrors.length} failure(s); ${remaining.length} resource(s) may be orphaned`,\n );\n }\n\n console.log(`Destroyed all resources for env: ${env}`);\n } catch (err) {\n if (env !== \"local\" && !(err instanceof Error && err.message.includes(\"destroy completed with\"))) {\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 }\n throw err;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"destroy-Krf35oqE.mjs","names":["destroyErrors: Array<{ step: string; error: string }>","byType: Record<string, string[]>"],"sources":["../src/features/dispatch-namespace/dispatch-namespace.destroy.ts","../src/features/dns-records/dns-records.destroy.ts","../src/cli/destroyGuard.ts","../src/cli/commands/destroy.ts"],"sourcesContent":["import type { CfiConfig } from \"../../types.js\";\nimport { getDispatchNamespaces } from \"../../types.js\";\nimport type { StateManager } from \"../../core/state/StateManager.js\";\nimport type { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport type { DispatchNamespaceStateEntry } from \"../../types.js\";\nimport { isEphemeralEnv } from \"./dispatch-namespace.resolve.js\";\n\n/**\n * Tear down every dispatch namespace recorded in state.\n *\n * Cloudflare refuses to delete a namespace that still contains scripts, so we\n * enumerate `/dispatch/namespaces/{ns}/scripts` and delete each (with `force`\n * so dependents like service-bind targets don't block the removal). This\n * covers tenant scripts uploaded by `tamer wfp put` / `provision-workflow`\n * that aren't otherwise tracked in Tamer state.\n */\nexport async function dispatchNamespaceDestroy(\n env: string,\n state: StateManager,\n api: CFApiClient,\n config: CfiConfig,\n _force?: boolean,\n): Promise<void> {\n const allowedLogical = new Set(\n getDispatchNamespaces(config).map((d) => d.logicalName),\n );\n if (allowedLogical.size === 0) return;\n\n for (const [key, entry] of Object.entries(state.getAll())) {\n if (entry.type !== \"dispatch_namespace\") continue;\n const ns = entry as DispatchNamespaceStateEntry;\n if (!allowedLogical.has(ns.logicalName)) continue;\n const isSharedEphemeral = ns.derivedName.endsWith(\"-ephemeral\");\n try {\n const scripts = await api.dispatchNamespaceScriptList(ns.derivedName);\n for (const s of scripts) {\n if (isEphemeralEnv(env, config.tenant) && isSharedEphemeral) {\n if (!s.id.endsWith(`-${env}`)) continue;\n }\n try {\n await api.dispatchNamespaceScriptDelete(ns.derivedName, s.id, {\n force: true,\n });\n console.log(\n `Deleted tenant script \"${s.id}\" from namespace ${ns.derivedName}.`,\n );\n } catch (err) {\n console.warn(\n `Failed to delete tenant script ${s.id} in ${ns.derivedName}:`,\n err instanceof Error ? err.message : err,\n );\n }\n }\n if (isEphemeralEnv(env, config.tenant) && isSharedEphemeral) {\n console.log(\n `Left shared dispatch namespace ${ns.derivedName} (removed only scripts suffixed -${env}).`,\n );\n continue;\n }\n await api.dispatchNamespaceDelete(ns.derivedName);\n state.delete(key);\n } catch (err) {\n console.warn(\n `Failed to delete dispatch namespace ${ns.derivedName}:`,\n err instanceof Error ? err.message : err,\n );\n }\n }\n}\n","import type { CfiConfig, DnsRecordStateEntry } from \"../../types.js\";\nimport { getDnsRecords } from \"../../types.js\";\nimport type { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport type { StateManager } from \"../../core/state/StateManager.js\";\n\n/**\n * Tear down every DNS record this stack owns. Restricted to records\n * whose `logicalName` is declared in the current `CfiConfig.dnsRecords`\n * (matches `runDestroy` semantics for shared state rows). Records flagged\n * `preserveOnDestroy: true` are left in place but still dropped from\n * state — the operator is responsible for re-importing them later.\n */\nexport async function dnsRecordDestroy(\n env: string,\n state: StateManager,\n api: CFApiClient,\n config: CfiConfig,\n _force?: boolean,\n): Promise<void> {\n if (env === \"local\") return;\n const declared = getDnsRecords(config);\n if (declared.length === 0) return;\n const preserve = new Map<string, boolean>(\n declared.map((c) => [c.logicalName, !!c.preserveOnDestroy]),\n );\n const allowedLogical = new Set(declared.map((c) => c.logicalName));\n\n for (const [key, entry] of Object.entries(state.getAll())) {\n if (entry.type !== \"dns_record\") continue;\n const rec = entry as DnsRecordStateEntry;\n if (!allowedLogical.has(rec.logicalName)) continue;\n if (preserve.get(rec.logicalName)) {\n console.log(\n `Preserved DNS record ${rec.recordType} ${rec.name} (preserveOnDestroy).`,\n );\n state.delete(key);\n continue;\n }\n try {\n await api.zoneDnsRecordDelete(rec.zoneId, rec.recordId);\n state.delete(key);\n } catch (err) {\n console.warn(\n `Failed to delete DNS record ${rec.recordType} ${rec.name}:`,\n err instanceof Error ? err.message : err,\n );\n }\n }\n}\n","/** Shared envs where destroy must be confirmed with `--confirm-env <same>`. */\nexport const SHARED_ENV_DESTROY = [\n \"dev\",\n \"staging\",\n \"prod\",\n \"production\",\n] as const;\n\n/**\n * @param force When true, skips the typed confirmation (break-glass).\n */\nexport function assertDestroyEnvAllowed(\n env: string,\n force: boolean,\n confirmEnv?: string,\n): void {\n if (force) return;\n if (!SHARED_ENV_DESTROY.includes(env as (typeof SHARED_ENV_DESTROY)[number])) {\n return;\n }\n if (confirmEnv !== env) {\n throw new Error(\n `Destroying shared environment \"${env}\" requires --confirm-env ${env}`,\n );\n }\n}\n","import { loadConfig, getWorkers, getConfigBaseDir } from \"../../core/config/loader.js\";\nimport { logicalNamesForResourceKind } from \"../../core/config/resourcesFromConfig.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { deleteEnvStateRows } from \"../../core/state/tamerStateDb.js\";\nimport { deleteEnvArtifacts } from \"../../core/state/tamerArtifactsR2.js\";\nimport { deleteEnvSecretRows } from \"../../core/secrets/secretsDb.js\";\nimport { getDispatchNamespaces, getDnsRecords } from \"../../types.js\";\nimport { logpushJobDestroy } from \"../../features/logpush-job/index.js\";\nimport { assertDestroyEnvAllowed } from \"../destroyGuard.js\";\nimport { dispatchNamespaceDestroy } from \"../../features/dispatch-namespace/index.js\";\nimport { dnsRecordDestroy } from \"../../features/dns-records/index.js\";\nimport { workersDestroy } from \"../../features/workers/index.js\";\nimport { workerRoutesDestroy } from \"../../features/worker-route/index.js\";\nimport { runSync } from \"./sync.js\";\nimport { resourceModules } from \"../../core/registry/registry.js\";\nimport { namingFromConfig } from \"../../core/config/namingFromConfig.js\";\nimport { verifyPlanFile } from \"../../core/plan/verifyPlanFile.js\";\nimport { hashCloudflareSnapshot } from \"../../core/plan/planFile.js\";\nimport { buildCloudflareSnapshot } from \"../../core/plan/cloudflareSnapshot.js\";\n\nexport async function runDestroy(options: {\n env: string;\n force?: boolean;\n skipWorkers?: boolean;\n confirmEnv?: string;\n configPath?: string;\n /** When true, delete the shared `tamer-state-{env}` D1 after other resources (use on last stack teardown). */\n wipeMetadata?: boolean;\n /**\n * Path to a destroy plan file from `tamer plan --destroy --out`. Destroy\n * recomputes the `(config, state, cloudflare)` attestation hashes and\n * refuses to proceed if any drifted (override with `allowStale`). The\n * pinned plan ensures the operator destroys exactly what they reviewed.\n */\n planFile?: string;\n allowStale?: boolean;\n}): Promise<void> {\n const {\n env,\n force = false,\n skipWorkers = false,\n confirmEnv,\n configPath,\n wipeMetadata = false,\n } = options;\n assertDestroyEnvAllowed(env, force, confirmEnv);\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 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: \"destroy\",\n expectedMode: \"destroy\",\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 state.beginOperation(\"destroy\", wipeMetadata ? \"wipe-metadata\" : undefined);\n try {\n await state.persist(api);\n } catch {\n /* in-progress marker best-effort */\n }\n\n const destroyErrors: Array<{ step: string; error: string }> = [];\n\n async function tryStep(step: string, fn: () => Promise<void>): Promise<void> {\n try {\n await fn();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (/404|not found|does not exist|already/i.test(msg)) {\n console.log(` ~ ${step}: already gone`);\n } else {\n console.warn(` ✗ ${step}: ${msg}`);\n destroyErrors.push({ step, error: msg });\n }\n }\n }\n\n try {\n // Inventory: show what's in state so the operator can verify\n // nothing will be missed (or left behind).\n const allState = state.getAll();\n const stateKeys = Object.keys(allState);\n if (stateKeys.length > 0) {\n console.log(`\\nDestroy inventory (${stateKeys.length} resource(s) in state for env ${env}):`);\n const byType: Record<string, string[]> = {};\n for (const [key, entry] of Object.entries(allState)) {\n const type = entry.type ?? \"unknown\";\n if (!byType[type]) byType[type] = [];\n byType[type].push(key);\n }\n for (const [type, keys] of Object.entries(byType).sort()) {\n console.log(` ${type} (${keys.length}):`);\n for (const key of keys) {\n const entry = allState[key] as { derivedName?: string };\n console.log(` ${entry.derivedName ?? key}`);\n }\n }\n console.log(\"\");\n }\n\n if (!skipWorkers) {\n await tryStep(\"worker routes\", async () => {\n await workerRoutesDestroy(env, config, baseDir, state, api);\n await state.persist(api);\n });\n await tryStep(\"worker scripts\", () =>\n workersDestroy(env, baseDir, accountId, config, state, api, force),\n );\n }\n\n const ownedByKind = await Promise.all(\n resourceModules.map((m) =>\n logicalNamesForResourceKind(config, baseDir, m.kind).then((set) => ({\n mod: m,\n owned: set,\n })),\n ),\n );\n const workers = await getWorkers(config, baseDir);\n\n await tryStep(\"Logpush + Pipelines\", async () => {\n await logpushJobDestroy(env, state, api, config);\n await state.persist(api);\n });\n\n for (const { mod, owned } of ownedByKind) {\n if (owned.size === 0) continue;\n const resources = workers.flatMap(([, wc]) => mod.pickResources(wc));\n await tryStep(`${mod.label} (${[...owned].join(\", \")})`, () =>\n mod.destroy({\n resources,\n tenant: config.tenant,\n env,\n api,\n state,\n naming,\n config,\n baseDir,\n force,\n }),\n );\n }\n\n if (getDispatchNamespaces(config).length > 0) {\n await tryStep(\"dispatch namespaces\", () =>\n dispatchNamespaceDestroy(env, state, api, config, force),\n );\n }\n\n if (getDnsRecords(config).length > 0) {\n await tryStep(\"DNS records\", () =>\n dnsRecordDestroy(env, state, api, config, force),\n );\n }\n\n // Clear `stackOutputs` — the values pointed at resources we just deleted.\n state.replaceStackOutputs({});\n\n if (env !== \"local\" && wipeMetadata && destroyErrors.length === 0) {\n state.clearDirty();\n const deletedState = await deleteEnvStateRows(api, env);\n if (deletedState) {\n console.log(`Cleared Tamer state rows for env ${env}.`);\n }\n try {\n await deleteEnvArtifacts(api, env);\n } catch (err) {\n console.warn(\n `Failed to clean Tamer artifacts for env ${env}:`,\n err instanceof Error ? err.message : err,\n );\n }\n try {\n const deletedSecrets = await deleteEnvSecretRows(api, env);\n if (deletedSecrets) {\n console.log(`Cleared Tamer secret rows for env ${env}.`);\n }\n } catch (err) {\n console.warn(\n `Failed to clean Tamer secrets for env ${env}:`,\n err instanceof Error ? err.message : err,\n );\n }\n }\n\n if (env !== \"local\" && (!wipeMetadata || destroyErrors.length > 0)) {\n if (destroyErrors.length > 0) {\n state.failOperation(\n `${destroyErrors.length} step(s) failed: ${destroyErrors.map((e) => e.step).join(\", \")}`,\n );\n } else {\n state.finishOperation();\n }\n try {\n await state.persist(api);\n } catch {\n /* state row may have been wiped by sub-steps */\n }\n }\n state.clearDirty();\n\n // Post-destroy check: what's still in state?\n const remaining = Object.keys(state.getAll());\n if (remaining.length > 0) {\n console.warn(\n `\\nWarning: ${remaining.length} resource(s) still in state after destroy:`,\n );\n for (const key of remaining) {\n const entry = state.get(key) as { derivedName?: string; type?: string } | undefined;\n console.warn(` ${entry?.derivedName ?? key} (${entry?.type})`);\n }\n }\n\n if (destroyErrors.length > 0) {\n console.warn(`\\n${destroyErrors.length} step(s) failed during destroy:`);\n for (const { step, error } of destroyErrors) {\n console.warn(` ${step}: ${error}`);\n }\n throw new Error(\n `destroy completed with ${destroyErrors.length} failure(s); ${remaining.length} resource(s) may be orphaned`,\n );\n }\n\n console.log(`Destroyed all resources for env: ${env}`);\n } catch (err) {\n if (env !== \"local\" && !(err instanceof Error && err.message.includes(\"destroy completed with\"))) {\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 }\n throw err;\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { H as loadConfig, L as CFApiClient, R as cloudflareAccountIdFromEnv, h as StateManager, w as stackNameForConfig, y as tenantStateKey } from "./tamer.mjs";
1
+ import { R as CFApiClient, U as loadConfig, h as StateManager, w as stackNameForConfig, y as tenantStateKey, z as cloudflareAccountIdFromEnv } from "./tamer.mjs";
2
2
 
3
3
  //#region src/core/env/protectedEnvs.ts
4
4
  /**
@@ -98,4 +98,4 @@ async function runDestroyTenant(options) {
98
98
 
99
99
  //#endregion
100
100
  export { runDestroyTenant };
101
- //# sourceMappingURL=destroy-tenant-B9ZTeUDk.mjs.map
101
+ //# sourceMappingURL=destroy-tenant-C95ljuon.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"destroy-tenant-B9ZTeUDk.mjs","names":["DEFAULT_PROTECTED_ENVS: readonly string[]","result: DestroyTenantResult","errors: string[]","removedShards: { role: string; derivedName: string; cfId: string }[]"],"sources":["../src/core/env/protectedEnvs.ts","../src/cli/commands/destroy-tenant.ts"],"sourcesContent":["import type { CfiConfig } from \"../../types.js\";\n\n/**\n * Default set of envs that require explicit confirmation before\n * `destroy-tenant` will run. Used when the loaded config doesn't pin\n * `tenant.protectedEnvs` — these two names are universal-enough across\n * accounts that \"destroying prod by accident\" stays guarded by default,\n * but the operator is free to override the list (e.g. a multi-region\n * account with `production-eu` / `production-us` / `canary` adds those\n * here, and a personal account passes `[]` to disable the prompt).\n */\nconst DEFAULT_PROTECTED_ENVS: readonly string[] = [\"prod\", \"production\"];\n\n/**\n * `true` when `env` is in `tenant.protectedEnvs` from the loaded\n * `tamer.config.ts` (or in the default set when the config doesn't\n * pin its own list). Single source of truth for the destroy\n * confirmation check — call this **after** `loadConfig` so the\n * config-pinned list is honored.\n */\nexport function isProtectedEnv(env: string, config: CfiConfig): boolean {\n const list = config.tenant.protectedEnvs ?? DEFAULT_PROTECTED_ENVS;\n return list.includes(env);\n}\n\nexport { DEFAULT_PROTECTED_ENVS };\n","import { loadConfig } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { tenantStateKey } from \"../../core/tenant/tenantKeys.js\";\nimport { isProtectedEnv } from \"../../core/env/protectedEnvs.js\";\n\n/**\n * Machine-readable result envelope emitted on the final stdout line\n * when `--json` is passed. Mirrors `ProvisionTenantResult` so the\n * Cloudflare Container caller (`provision-workflow`, see\n * `docs/handoff.md` §7) can consume both commands with the same\n * parsing path. `removed.shards` lists every D1 we attempted to\n * delete; `errors` lists any best-effort delete failures so the\n * Workflow can surface them without re-parsing logs.\n */\nexport interface DestroyTenantResult {\n status: \"destroyed\" | \"noop\" | \"failed\";\n tenantKey: string;\n product: string;\n workspace: string;\n env: string;\n removed: {\n scriptName?: string;\n dispatchNamespaceName?: string;\n shards: { role: string; derivedName: string; cfId: string }[];\n };\n errors: string[];\n error?: string;\n}\n\nexport async function runDestroyTenant(options: {\n env: string;\n product: string;\n workspace: string;\n force?: boolean;\n confirmTenant?: string;\n configPath?: string;\n json?: boolean;\n}): Promise<void> {\n const env = options.env;\n if (env === \"local\") {\n throw new Error(\"destroy-tenant requires a non-local --env.\");\n }\n\n // Load config FIRST so the protection prompt can read\n // `tenant.protectedEnvs` from `tamer.config.ts`. This intentionally\n // ignores `--force` so a misconfigured CLI invocation can't\n // bypass even the load step — we want the parsed config in hand\n // before we accept any confirmation flag.\n const config = await loadConfig(options.configPath, { env });\n\n if (isProtectedEnv(env, config) && !options.force) {\n if (options.confirmTenant !== options.workspace) {\n throw new Error(\n `destroy-tenant: env \"${env}\" is in tenant.protectedEnvs ` +\n `(or the default [\"prod\",\"production\"]); pass ` +\n `--confirm-tenant ${options.workspace} (must match --workspace) or use --force`,\n );\n }\n }\n\n const accountId = 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 const state = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n await state.hydrate(api);\n\n const t = state.getTenant(options.product, options.workspace);\n if (!t) {\n if (!options.force) {\n throw new Error(\n `No tenant state for ${tenantStateKey(options.product, options.workspace)}; pass --force to skip state check`,\n );\n }\n console.log(\"No tenant record in state; nothing to remove.\");\n if (options.json) {\n const result: DestroyTenantResult = {\n status: \"noop\",\n tenantKey: tenantStateKey(options.product, options.workspace),\n product: options.product,\n workspace: options.workspace,\n env,\n removed: { shards: [] },\n errors: [],\n };\n process.stdout.write(JSON.stringify(result) + \"\\n\");\n }\n return;\n }\n\n const errors: string[] = [];\n try {\n await api.dispatchNamespaceScriptDelete(\n t.dispatchNamespaceName,\n t.scriptName,\n { force: true },\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[destroy-tenant] script delete: ${msg}`);\n errors.push(`script:${t.scriptName}:${msg}`);\n }\n\n const removedShards: { role: string; derivedName: string; cfId: string }[] =\n [];\n for (const shard of t.d1Shards ?? []) {\n try {\n await api.d1Delete(shard.cfId);\n removedShards.push({\n role: shard.role,\n derivedName: shard.derivedName,\n cfId: shard.cfId,\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[destroy-tenant] D1 ${shard.derivedName}: ${msg}`);\n errors.push(`d1:${shard.derivedName}:${msg}`);\n }\n }\n\n state.deleteTenant(options.product, options.workspace);\n await state.persist(api);\n\n console.log(\n `Destroyed tenant ${tenantStateKey(options.product, options.workspace)}`,\n );\n\n if (options.json) {\n const result: DestroyTenantResult = {\n status: \"destroyed\",\n tenantKey: tenantStateKey(options.product, options.workspace),\n product: options.product,\n workspace: options.workspace,\n env,\n removed: {\n scriptName: t.scriptName,\n dispatchNamespaceName: t.dispatchNamespaceName,\n shards: removedShards,\n },\n errors,\n };\n process.stdout.write(JSON.stringify(result) + \"\\n\");\n }\n}\n"],"mappings":";;;;;;;;;;;;AAWA,MAAMA,yBAA4C,CAAC,QAAQ,aAAa;;;;;;;;AASxE,SAAgB,eAAe,KAAa,QAA4B;AAEtE,SADa,OAAO,OAAO,iBAAiB,wBAChC,SAAS,IAAI;;;;;ACU3B,eAAsB,iBAAiB,SAQrB;CAChB,MAAM,MAAM,QAAQ;AACpB,KAAI,QAAQ,QACV,OAAM,IAAI,MAAM,6CAA6C;CAQ/D,MAAM,SAAS,MAAM,WAAW,QAAQ,YAAY,EAAE,KAAK,CAAC;AAE5D,KAAI,eAAe,KAAK,OAAO,IAAI,CAAC,QAAQ,OAC1C;MAAI,QAAQ,kBAAkB,QAAQ,UACpC,OAAM,IAAI,MACR,wBAAwB,IAAI,6FAEN,QAAQ,UAAU,0CACzC;;CAIL,MAAM,YAAY,OAAO,cAAc,4BAA4B;AACnE,KAAI,CAAC,UACH,OAAM,IAAI,MACR,iEACD;CAGH,MAAM,MAAM,IAAI,YAAY,UAAU;CACtC,MAAM,QAAQ,IAAI,aAChB,OAAO,OAAO,IACd,KACA,mBAAmB,OAAO,CAC3B;AACD,OAAM,MAAM,QAAQ,IAAI;CAExB,MAAM,IAAI,MAAM,UAAU,QAAQ,SAAS,QAAQ,UAAU;AAC7D,KAAI,CAAC,GAAG;AACN,MAAI,CAAC,QAAQ,MACX,OAAM,IAAI,MACR,uBAAuB,eAAe,QAAQ,SAAS,QAAQ,UAAU,CAAC,oCAC3E;AAEH,UAAQ,IAAI,gDAAgD;AAC5D,MAAI,QAAQ,MAAM;GAChB,MAAMC,SAA8B;IAClC,QAAQ;IACR,WAAW,eAAe,QAAQ,SAAS,QAAQ,UAAU;IAC7D,SAAS,QAAQ;IACjB,WAAW,QAAQ;IACnB;IACA,SAAS,EAAE,QAAQ,EAAE,EAAE;IACvB,QAAQ,EAAE;IACX;AACD,WAAQ,OAAO,MAAM,KAAK,UAAU,OAAO,GAAG,KAAK;;AAErD;;CAGF,MAAMC,SAAmB,EAAE;AAC3B,KAAI;AACF,QAAM,IAAI,8BACR,EAAE,uBACF,EAAE,YACF,EAAE,OAAO,MAAM,CAChB;UACM,KAAK;EACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,UAAQ,KAAK,mCAAmC,MAAM;AACtD,SAAO,KAAK,UAAU,EAAE,WAAW,GAAG,MAAM;;CAG9C,MAAMC,gBACJ,EAAE;AACJ,MAAK,MAAM,SAAS,EAAE,YAAY,EAAE,CAClC,KAAI;AACF,QAAM,IAAI,SAAS,MAAM,KAAK;AAC9B,gBAAc,KAAK;GACjB,MAAM,MAAM;GACZ,aAAa,MAAM;GACnB,MAAM,MAAM;GACb,CAAC;UACK,KAAK;EACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,UAAQ,KAAK,uBAAuB,MAAM,YAAY,IAAI,MAAM;AAChE,SAAO,KAAK,MAAM,MAAM,YAAY,GAAG,MAAM;;AAIjD,OAAM,aAAa,QAAQ,SAAS,QAAQ,UAAU;AACtD,OAAM,MAAM,QAAQ,IAAI;AAExB,SAAQ,IACN,oBAAoB,eAAe,QAAQ,SAAS,QAAQ,UAAU,GACvE;AAED,KAAI,QAAQ,MAAM;EAChB,MAAMF,SAA8B;GAClC,QAAQ;GACR,WAAW,eAAe,QAAQ,SAAS,QAAQ,UAAU;GAC7D,SAAS,QAAQ;GACjB,WAAW,QAAQ;GACnB;GACA,SAAS;IACP,YAAY,EAAE;IACd,uBAAuB,EAAE;IACzB,QAAQ;IACT;GACD;GACD;AACD,UAAQ,OAAO,MAAM,KAAK,UAAU,OAAO,GAAG,KAAK"}
1
+ {"version":3,"file":"destroy-tenant-C95ljuon.mjs","names":["DEFAULT_PROTECTED_ENVS: readonly string[]","result: DestroyTenantResult","errors: string[]","removedShards: { role: string; derivedName: string; cfId: string }[]"],"sources":["../src/core/env/protectedEnvs.ts","../src/cli/commands/destroy-tenant.ts"],"sourcesContent":["import type { CfiConfig } from \"../../types.js\";\n\n/**\n * Default set of envs that require explicit confirmation before\n * `destroy-tenant` will run. Used when the loaded config doesn't pin\n * `tenant.protectedEnvs` — these two names are universal-enough across\n * accounts that \"destroying prod by accident\" stays guarded by default,\n * but the operator is free to override the list (e.g. a multi-region\n * account with `production-eu` / `production-us` / `canary` adds those\n * here, and a personal account passes `[]` to disable the prompt).\n */\nconst DEFAULT_PROTECTED_ENVS: readonly string[] = [\"prod\", \"production\"];\n\n/**\n * `true` when `env` is in `tenant.protectedEnvs` from the loaded\n * `tamer.config.ts` (or in the default set when the config doesn't\n * pin its own list). Single source of truth for the destroy\n * confirmation check — call this **after** `loadConfig` so the\n * config-pinned list is honored.\n */\nexport function isProtectedEnv(env: string, config: CfiConfig): boolean {\n const list = config.tenant.protectedEnvs ?? DEFAULT_PROTECTED_ENVS;\n return list.includes(env);\n}\n\nexport { DEFAULT_PROTECTED_ENVS };\n","import { loadConfig } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { tenantStateKey } from \"../../core/tenant/tenantKeys.js\";\nimport { isProtectedEnv } from \"../../core/env/protectedEnvs.js\";\n\n/**\n * Machine-readable result envelope emitted on the final stdout line\n * when `--json` is passed. Mirrors `ProvisionTenantResult` so the\n * Cloudflare Container caller (`provision-workflow`, see\n * `docs/handoff.md` §7) can consume both commands with the same\n * parsing path. `removed.shards` lists every D1 we attempted to\n * delete; `errors` lists any best-effort delete failures so the\n * Workflow can surface them without re-parsing logs.\n */\nexport interface DestroyTenantResult {\n status: \"destroyed\" | \"noop\" | \"failed\";\n tenantKey: string;\n product: string;\n workspace: string;\n env: string;\n removed: {\n scriptName?: string;\n dispatchNamespaceName?: string;\n shards: { role: string; derivedName: string; cfId: string }[];\n };\n errors: string[];\n error?: string;\n}\n\nexport async function runDestroyTenant(options: {\n env: string;\n product: string;\n workspace: string;\n force?: boolean;\n confirmTenant?: string;\n configPath?: string;\n json?: boolean;\n}): Promise<void> {\n const env = options.env;\n if (env === \"local\") {\n throw new Error(\"destroy-tenant requires a non-local --env.\");\n }\n\n // Load config FIRST so the protection prompt can read\n // `tenant.protectedEnvs` from `tamer.config.ts`. This intentionally\n // ignores `--force` so a misconfigured CLI invocation can't\n // bypass even the load step — we want the parsed config in hand\n // before we accept any confirmation flag.\n const config = await loadConfig(options.configPath, { env });\n\n if (isProtectedEnv(env, config) && !options.force) {\n if (options.confirmTenant !== options.workspace) {\n throw new Error(\n `destroy-tenant: env \"${env}\" is in tenant.protectedEnvs ` +\n `(or the default [\"prod\",\"production\"]); pass ` +\n `--confirm-tenant ${options.workspace} (must match --workspace) or use --force`,\n );\n }\n }\n\n const accountId = 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 const state = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n await state.hydrate(api);\n\n const t = state.getTenant(options.product, options.workspace);\n if (!t) {\n if (!options.force) {\n throw new Error(\n `No tenant state for ${tenantStateKey(options.product, options.workspace)}; pass --force to skip state check`,\n );\n }\n console.log(\"No tenant record in state; nothing to remove.\");\n if (options.json) {\n const result: DestroyTenantResult = {\n status: \"noop\",\n tenantKey: tenantStateKey(options.product, options.workspace),\n product: options.product,\n workspace: options.workspace,\n env,\n removed: { shards: [] },\n errors: [],\n };\n process.stdout.write(JSON.stringify(result) + \"\\n\");\n }\n return;\n }\n\n const errors: string[] = [];\n try {\n await api.dispatchNamespaceScriptDelete(\n t.dispatchNamespaceName,\n t.scriptName,\n { force: true },\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[destroy-tenant] script delete: ${msg}`);\n errors.push(`script:${t.scriptName}:${msg}`);\n }\n\n const removedShards: { role: string; derivedName: string; cfId: string }[] =\n [];\n for (const shard of t.d1Shards ?? []) {\n try {\n await api.d1Delete(shard.cfId);\n removedShards.push({\n role: shard.role,\n derivedName: shard.derivedName,\n cfId: shard.cfId,\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[destroy-tenant] D1 ${shard.derivedName}: ${msg}`);\n errors.push(`d1:${shard.derivedName}:${msg}`);\n }\n }\n\n state.deleteTenant(options.product, options.workspace);\n await state.persist(api);\n\n console.log(\n `Destroyed tenant ${tenantStateKey(options.product, options.workspace)}`,\n );\n\n if (options.json) {\n const result: DestroyTenantResult = {\n status: \"destroyed\",\n tenantKey: tenantStateKey(options.product, options.workspace),\n product: options.product,\n workspace: options.workspace,\n env,\n removed: {\n scriptName: t.scriptName,\n dispatchNamespaceName: t.dispatchNamespaceName,\n shards: removedShards,\n },\n errors,\n };\n process.stdout.write(JSON.stringify(result) + \"\\n\");\n }\n}\n"],"mappings":";;;;;;;;;;;;AAWA,MAAMA,yBAA4C,CAAC,QAAQ,aAAa;;;;;;;;AASxE,SAAgB,eAAe,KAAa,QAA4B;AAEtE,SADa,OAAO,OAAO,iBAAiB,wBAChC,SAAS,IAAI;;;;;ACU3B,eAAsB,iBAAiB,SAQrB;CAChB,MAAM,MAAM,QAAQ;AACpB,KAAI,QAAQ,QACV,OAAM,IAAI,MAAM,6CAA6C;CAQ/D,MAAM,SAAS,MAAM,WAAW,QAAQ,YAAY,EAAE,KAAK,CAAC;AAE5D,KAAI,eAAe,KAAK,OAAO,IAAI,CAAC,QAAQ,OAC1C;MAAI,QAAQ,kBAAkB,QAAQ,UACpC,OAAM,IAAI,MACR,wBAAwB,IAAI,6FAEN,QAAQ,UAAU,0CACzC;;CAIL,MAAM,YAAY,OAAO,cAAc,4BAA4B;AACnE,KAAI,CAAC,UACH,OAAM,IAAI,MACR,iEACD;CAGH,MAAM,MAAM,IAAI,YAAY,UAAU;CACtC,MAAM,QAAQ,IAAI,aAChB,OAAO,OAAO,IACd,KACA,mBAAmB,OAAO,CAC3B;AACD,OAAM,MAAM,QAAQ,IAAI;CAExB,MAAM,IAAI,MAAM,UAAU,QAAQ,SAAS,QAAQ,UAAU;AAC7D,KAAI,CAAC,GAAG;AACN,MAAI,CAAC,QAAQ,MACX,OAAM,IAAI,MACR,uBAAuB,eAAe,QAAQ,SAAS,QAAQ,UAAU,CAAC,oCAC3E;AAEH,UAAQ,IAAI,gDAAgD;AAC5D,MAAI,QAAQ,MAAM;GAChB,MAAMC,SAA8B;IAClC,QAAQ;IACR,WAAW,eAAe,QAAQ,SAAS,QAAQ,UAAU;IAC7D,SAAS,QAAQ;IACjB,WAAW,QAAQ;IACnB;IACA,SAAS,EAAE,QAAQ,EAAE,EAAE;IACvB,QAAQ,EAAE;IACX;AACD,WAAQ,OAAO,MAAM,KAAK,UAAU,OAAO,GAAG,KAAK;;AAErD;;CAGF,MAAMC,SAAmB,EAAE;AAC3B,KAAI;AACF,QAAM,IAAI,8BACR,EAAE,uBACF,EAAE,YACF,EAAE,OAAO,MAAM,CAChB;UACM,KAAK;EACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,UAAQ,KAAK,mCAAmC,MAAM;AACtD,SAAO,KAAK,UAAU,EAAE,WAAW,GAAG,MAAM;;CAG9C,MAAMC,gBACJ,EAAE;AACJ,MAAK,MAAM,SAAS,EAAE,YAAY,EAAE,CAClC,KAAI;AACF,QAAM,IAAI,SAAS,MAAM,KAAK;AAC9B,gBAAc,KAAK;GACjB,MAAM,MAAM;GACZ,aAAa,MAAM;GACnB,MAAM,MAAM;GACb,CAAC;UACK,KAAK;EACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,UAAQ,KAAK,uBAAuB,MAAM,YAAY,IAAI,MAAM;AAChE,SAAO,KAAK,MAAM,MAAM,YAAY,GAAG,MAAM;;AAIjD,OAAM,aAAa,QAAQ,SAAS,QAAQ,UAAU;AACtD,OAAM,MAAM,QAAQ,IAAI;AAExB,SAAQ,IACN,oBAAoB,eAAe,QAAQ,SAAS,QAAQ,UAAU,GACvE;AAED,KAAI,QAAQ,MAAM;EAChB,MAAMF,SAA8B;GAClC,QAAQ;GACR,WAAW,eAAe,QAAQ,SAAS,QAAQ,UAAU;GAC7D,SAAS,QAAQ;GACjB,WAAW,QAAQ;GACnB;GACA,SAAS;IACP,YAAY,EAAE;IACd,uBAAuB,EAAE;IACzB,QAAQ;IACT;GACD;GACD;AACD,UAAQ,OAAO,MAAM,KAAK,UAAU,OAAO,GAAG,KAAK"}
@@ -1,10 +1,10 @@
1
- import { B as getConfigBaseDir, H as loadConfig, L as CFApiClient, M as wranglerConfigCliArgs, R as cloudflareAccountIdFromEnv, V as getWorkers, f as fetchStackImports, h as StateManager, k as resolveWorkerConfig, u as namingFromConfig, w as stackNameForConfig } from "./tamer.mjs";
2
- import "./registry-BrOxbA2i.mjs";
3
- import "./r2S3EmptyBucket-B9_pHfvB.mjs";
4
- import { n as writeWranglerJson, t as generateWranglerConfig } from "./generator-MX8MAHd9.mjs";
5
- import "./logpush-job-GqVKG_HI.mjs";
6
- import "./worker-route-CUQBu9xe.mjs";
7
- import { t as runSync } from "./sync-kl7MaCQV.mjs";
1
+ import { H as getWorkers, N as wranglerConfigCliArgs, R as CFApiClient, U as loadConfig, V as getConfigBaseDir, f as fetchStackImports, h as StateManager, k as resolveWorkerConfig, u as namingFromConfig, w as stackNameForConfig, z as cloudflareAccountIdFromEnv } from "./tamer.mjs";
2
+ import "./registry-X9dlQxG3.mjs";
3
+ import "./r2S3EmptyBucket-CXLmOrYF.mjs";
4
+ import { n as writeWranglerJson, t as generateWranglerConfig } from "./generator-DAU5K77L.mjs";
5
+ import "./logpush-job-C_6uzGUC.mjs";
6
+ import "./worker-route-CvuUPq1k.mjs";
7
+ import { t as runSync } from "./sync-DfJGkOME.mjs";
8
8
  import { spawn } from "child_process";
9
9
 
10
10
  //#region src/cli/commands/dev.ts
@@ -98,4 +98,4 @@ async function runDev(options) {
98
98
 
99
99
  //#endregion
100
100
  export { runDev };
101
- //# sourceMappingURL=dev-0zkF2iqF.mjs.map
101
+ //# sourceMappingURL=dev-C__1rLos.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"dev-0zkF2iqF.mjs","names":["workerKey","workerConfig","resolved","children: ChildProcess[]"],"sources":["../src/cli/commands/dev.ts"],"sourcesContent":["import type { ChildProcess } from \"child_process\";\nimport { spawn } from \"child_process\";\nimport { loadConfig, getWorkers, getConfigBaseDir } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { namingFromConfig } from \"../../core/config/namingFromConfig.js\";\nimport { wranglerConfigCliArgs } from \"../../core/wrangler/wranglerOutFile.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { resolveWorkerConfig } from \"../../core/config/resolver.js\";\nimport {\n generateWranglerConfig,\n writeWranglerJson,\n} from \"../../core/wrangler/generator.js\";\nimport { runSync } from \"./sync.js\";\nimport { fetchStackImports } from \"../../core/imports/fetchStackImports.js\";\n\nexport async function runDev(options: {\n worker?: string;\n env?: string;\n configPath?: string;\n /** Run every selected worker as a separate `wrangler dev` on incrementing ports (from TAMER_DEV_BASE_PORT or 8787). */\n all?: boolean;\n}): Promise<void> {\n const workerFilter = options.worker;\n const env = options.env ?? \"local\";\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 const naming = namingFromConfig(config);\n const api = new CFApiClient(accountId);\n const state = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n await state.hydrate(api);\n\n if (\n env !== \"local\" &&\n Object.keys(state.load().resources).length === 0\n ) {\n console.log(\"Tamer state is empty; running sync...\");\n await runSync({ env, configPath });\n state.reset();\n await state.hydrate(api);\n }\n\n // Pre-fetch sibling stack outputs so worker `vars` / `tamerRoutes`\n // can reference `${tamer:import:<stack>.<output>}` even in dev mode.\n // No-op in local env (returns `{}`); when missing in non-local, the\n // import resolver will throw with a clear \"run apply on <stack>\" hint.\n const imports = await fetchStackImports(api, config, env);\n\n const workers = await getWorkers(config, baseDir);\n const toRun = workerFilter\n ? workers.filter(([k]) => k === workerFilter)\n : workers;\n\n if (toRun.length === 0) {\n throw new Error(\n workerFilter\n ? `Worker \"${workerFilter}\" not found`\n : \"No workers configured\",\n );\n }\n\n for (const [workerKey, workerConfig] of toRun) {\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(resolved.workerDir, wranglerConfig, resolved.wranglerOutFile);\n }\n\n if (options.all && toRun.length > 0) {\n const basePort = Number(process.env.TAMER_DEV_BASE_PORT) || 8787;\n const children: ChildProcess[] = [];\n\n for (let i = 0; i < toRun.length; i++) {\n const [workerKey, workerConfig] = toRun[i];\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 port = basePort + i;\n const devArgs = [\n \"wrangler\",\n ...wranglerConfigCliArgs(resolved.wranglerOutFile),\n \"dev\",\n \"--port\",\n String(port),\n ];\n console.log(`Starting ${workerKey} on http://127.0.0.1:${port}`);\n const proc = spawn(\"bunx\", devArgs, {\n cwd: resolved.workerDir,\n stdio: \"inherit\",\n shell: true,\n });\n children.push(proc);\n }\n\n const shutdown = () => {\n for (const c of children) {\n if (!c.killed) c.kill(\"SIGTERM\");\n }\n };\n process.once(\"SIGINT\", () => {\n shutdown();\n process.exit(0);\n });\n process.once(\"SIGTERM\", () => {\n shutdown();\n process.exit(0);\n });\n\n await new Promise<void>((resolve) => {\n let remaining = children.length;\n for (const c of children) {\n c.on(\"exit\", () => {\n remaining -= 1;\n if (remaining <= 0) resolve();\n });\n }\n });\n return;\n }\n\n const [workerKey, workerConfig] = toRun[0];\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\n console.log(`Starting wrangler dev for ${workerKey}...`);\n const devArgs = [\n \"wrangler\",\n ...wranglerConfigCliArgs(resolved.wranglerOutFile),\n \"dev\",\n ];\n const proc = spawn(\"bunx\", devArgs, {\n cwd: resolved.workerDir,\n stdio: \"inherit\",\n shell: true,\n });\n\n proc.on(\"exit\", (code) => {\n process.exit(code ?? 0);\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"dev-C__1rLos.mjs","names":["workerKey","workerConfig","resolved","children: ChildProcess[]"],"sources":["../src/cli/commands/dev.ts"],"sourcesContent":["import type { ChildProcess } from \"child_process\";\nimport { spawn } from \"child_process\";\nimport { loadConfig, getWorkers, getConfigBaseDir } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { namingFromConfig } from \"../../core/config/namingFromConfig.js\";\nimport { wranglerConfigCliArgs } from \"../../core/wrangler/wranglerOutFile.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { resolveWorkerConfig } from \"../../core/config/resolver.js\";\nimport {\n generateWranglerConfig,\n writeWranglerJson,\n} from \"../../core/wrangler/generator.js\";\nimport { runSync } from \"./sync.js\";\nimport { fetchStackImports } from \"../../core/imports/fetchStackImports.js\";\n\nexport async function runDev(options: {\n worker?: string;\n env?: string;\n configPath?: string;\n /** Run every selected worker as a separate `wrangler dev` on incrementing ports (from TAMER_DEV_BASE_PORT or 8787). */\n all?: boolean;\n}): Promise<void> {\n const workerFilter = options.worker;\n const env = options.env ?? \"local\";\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 const naming = namingFromConfig(config);\n const api = new CFApiClient(accountId);\n const state = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n await state.hydrate(api);\n\n if (\n env !== \"local\" &&\n Object.keys(state.load().resources).length === 0\n ) {\n console.log(\"Tamer state is empty; running sync...\");\n await runSync({ env, configPath });\n state.reset();\n await state.hydrate(api);\n }\n\n // Pre-fetch sibling stack outputs so worker `vars` / `tamerRoutes`\n // can reference `${tamer:import:<stack>.<output>}` even in dev mode.\n // No-op in local env (returns `{}`); when missing in non-local, the\n // import resolver will throw with a clear \"run apply on <stack>\" hint.\n const imports = await fetchStackImports(api, config, env);\n\n const workers = await getWorkers(config, baseDir);\n const toRun = workerFilter\n ? workers.filter(([k]) => k === workerFilter)\n : workers;\n\n if (toRun.length === 0) {\n throw new Error(\n workerFilter\n ? `Worker \"${workerFilter}\" not found`\n : \"No workers configured\",\n );\n }\n\n for (const [workerKey, workerConfig] of toRun) {\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(resolved.workerDir, wranglerConfig, resolved.wranglerOutFile);\n }\n\n if (options.all && toRun.length > 0) {\n const basePort = Number(process.env.TAMER_DEV_BASE_PORT) || 8787;\n const children: ChildProcess[] = [];\n\n for (let i = 0; i < toRun.length; i++) {\n const [workerKey, workerConfig] = toRun[i];\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 port = basePort + i;\n const devArgs = [\n \"wrangler\",\n ...wranglerConfigCliArgs(resolved.wranglerOutFile),\n \"dev\",\n \"--port\",\n String(port),\n ];\n console.log(`Starting ${workerKey} on http://127.0.0.1:${port}`);\n const proc = spawn(\"bunx\", devArgs, {\n cwd: resolved.workerDir,\n stdio: \"inherit\",\n shell: true,\n });\n children.push(proc);\n }\n\n const shutdown = () => {\n for (const c of children) {\n if (!c.killed) c.kill(\"SIGTERM\");\n }\n };\n process.once(\"SIGINT\", () => {\n shutdown();\n process.exit(0);\n });\n process.once(\"SIGTERM\", () => {\n shutdown();\n process.exit(0);\n });\n\n await new Promise<void>((resolve) => {\n let remaining = children.length;\n for (const c of children) {\n c.on(\"exit\", () => {\n remaining -= 1;\n if (remaining <= 0) resolve();\n });\n }\n });\n return;\n }\n\n const [workerKey, workerConfig] = toRun[0];\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\n console.log(`Starting wrangler dev for ${workerKey}...`);\n const devArgs = [\n \"wrangler\",\n ...wranglerConfigCliArgs(resolved.wranglerOutFile),\n \"dev\",\n ];\n const proc = spawn(\"bunx\", devArgs, {\n cwd: resolved.workerDir,\n stdio: \"inherit\",\n shell: true,\n });\n\n proc.on(\"exit\", (code) => {\n process.exit(code ?? 0);\n });\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- 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";
1
+ 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";
2
2
 
3
3
  export { dnsRecordStateKey };
@@ -44,4 +44,4 @@ function dnsRecordStateKey(zoneId, type, name) {
44
44
 
45
45
  //#endregion
46
46
  export { effectiveDnsRecordProxied as a, effectiveDnsRecordComment as i, dnsRecordCommentMarker as n, effectiveDnsRecordTtl as o, dnsRecordStateKey as r, dnsRecordAppliesToEnv as t };
47
- //# sourceMappingURL=dns-records.resolve-DV6XBZf3.mjs.map
47
+ //# sourceMappingURL=dns-records.resolve-DwBR_1WI.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"dns-records.resolve-DV6XBZf3.mjs","names":[],"sources":["../src/features/dns-records/dns-records.resolve.ts"],"sourcesContent":["import type { DnsRecordResourceConfig, TenantMeta } from \"../../types.js\";\n\n/**\n * Default skip list — `local` is always implicitly skipped on top of any\n * envs the user lists in `skipEnvs`. Wrangler dev does not own real DNS;\n * Tamer never reaches into Cloudflare for `local` apply/destroy.\n */\nconst ALWAYS_SKIPPED = new Set([\"local\"]);\n\n/** Whether the record should be created/updated/destroyed for `env`. */\nexport function dnsRecordAppliesToEnv(\n config: DnsRecordResourceConfig,\n env: string,\n): boolean {\n if (ALWAYS_SKIPPED.has(env)) return false;\n return !(config.skipEnvs ?? []).includes(env);\n}\n\n/**\n * Stable comment marker Tamer attaches to every record it creates so\n * `tamer sync` and `tamer import` can rediscover orphans after state\n * loss. Format: `tamer:<tenantId>:<env>:<logicalName>` followed by the\n * user's own free-form comment when set.\n */\nexport function dnsRecordCommentMarker(\n tenant: TenantMeta,\n env: string,\n logicalName: string,\n): string {\n return `tamer:${tenant.id}:${env}:${logicalName}`;\n}\n\n/** Compose the comment Tamer writes (marker + optional user-supplied tail). */\nexport function effectiveDnsRecordComment(\n config: DnsRecordResourceConfig,\n tenant: TenantMeta,\n env: string,\n): string {\n const marker = dnsRecordCommentMarker(tenant, env, config.logicalName);\n if (!config.comment) return marker;\n return `${marker} ${config.comment}`;\n}\n\n/** TTL default — `1` is Cloudflare's \"auto\" sentinel. */\nexport function effectiveDnsRecordTtl(config: DnsRecordResourceConfig): number {\n return config.ttl ?? 1;\n}\n\n/** Proxied default — `false`, since most managed records (TXT, MX, NS, etc.) cannot be proxied. */\nexport function effectiveDnsRecordProxied(\n config: DnsRecordResourceConfig,\n): boolean {\n return config.proxied ?? false;\n}\n\n/**\n * State key for one DNS record. Includes zone + type + name so multiple\n * records of different types on the same hostname (e.g. A + AAAA + TXT)\n * each get their own row.\n */\nexport function dnsRecordStateKey(\n zoneId: string,\n type: string,\n name: string,\n): string {\n return `dns_record:${zoneId}:${type}:${name}`;\n}\n"],"mappings":";;;;;;AAOA,MAAM,iBAAiB,IAAI,IAAI,CAAC,QAAQ,CAAC;;AAGzC,SAAgB,sBACd,QACA,KACS;AACT,KAAI,eAAe,IAAI,IAAI,CAAE,QAAO;AACpC,QAAO,EAAE,OAAO,YAAY,EAAE,EAAE,SAAS,IAAI;;;;;;;;AAS/C,SAAgB,uBACd,QACA,KACA,aACQ;AACR,QAAO,SAAS,OAAO,GAAG,GAAG,IAAI,GAAG;;;AAItC,SAAgB,0BACd,QACA,QACA,KACQ;CACR,MAAM,SAAS,uBAAuB,QAAQ,KAAK,OAAO,YAAY;AACtE,KAAI,CAAC,OAAO,QAAS,QAAO;AAC5B,QAAO,GAAG,OAAO,GAAG,OAAO;;;AAI7B,SAAgB,sBAAsB,QAAyC;AAC7E,QAAO,OAAO,OAAO;;;AAIvB,SAAgB,0BACd,QACS;AACT,QAAO,OAAO,WAAW;;;;;;;AAQ3B,SAAgB,kBACd,QACA,MACA,MACQ;AACR,QAAO,cAAc,OAAO,GAAG,KAAK,GAAG"}
1
+ {"version":3,"file":"dns-records.resolve-DwBR_1WI.mjs","names":[],"sources":["../src/features/dns-records/dns-records.resolve.ts"],"sourcesContent":["import type { DnsRecordResourceConfig, TenantMeta } from \"../../types.js\";\n\n/**\n * Default skip list — `local` is always implicitly skipped on top of any\n * envs the user lists in `skipEnvs`. Wrangler dev does not own real DNS;\n * Tamer never reaches into Cloudflare for `local` apply/destroy.\n */\nconst ALWAYS_SKIPPED = new Set([\"local\"]);\n\n/** Whether the record should be created/updated/destroyed for `env`. */\nexport function dnsRecordAppliesToEnv(\n config: DnsRecordResourceConfig,\n env: string,\n): boolean {\n if (ALWAYS_SKIPPED.has(env)) return false;\n return !(config.skipEnvs ?? []).includes(env);\n}\n\n/**\n * Stable comment marker Tamer attaches to every record it creates so\n * `tamer sync` and `tamer import` can rediscover orphans after state\n * loss. Format: `tamer:<tenantId>:<env>:<logicalName>` followed by the\n * user's own free-form comment when set.\n */\nexport function dnsRecordCommentMarker(\n tenant: TenantMeta,\n env: string,\n logicalName: string,\n): string {\n return `tamer:${tenant.id}:${env}:${logicalName}`;\n}\n\n/** Compose the comment Tamer writes (marker + optional user-supplied tail). */\nexport function effectiveDnsRecordComment(\n config: DnsRecordResourceConfig,\n tenant: TenantMeta,\n env: string,\n): string {\n const marker = dnsRecordCommentMarker(tenant, env, config.logicalName);\n if (!config.comment) return marker;\n return `${marker} ${config.comment}`;\n}\n\n/** TTL default — `1` is Cloudflare's \"auto\" sentinel. */\nexport function effectiveDnsRecordTtl(config: DnsRecordResourceConfig): number {\n return config.ttl ?? 1;\n}\n\n/** Proxied default — `false`, since most managed records (TXT, MX, NS, etc.) cannot be proxied. */\nexport function effectiveDnsRecordProxied(\n config: DnsRecordResourceConfig,\n): boolean {\n return config.proxied ?? false;\n}\n\n/**\n * State key for one DNS record. Includes zone + type + name so multiple\n * records of different types on the same hostname (e.g. A + AAAA + TXT)\n * each get their own row.\n */\nexport function dnsRecordStateKey(\n zoneId: string,\n type: string,\n name: string,\n): string {\n return `dns_record:${zoneId}:${type}:${name}`;\n}\n"],"mappings":";;;;;;AAOA,MAAM,iBAAiB,IAAI,IAAI,CAAC,QAAQ,CAAC;;AAGzC,SAAgB,sBACd,QACA,KACS;AACT,KAAI,eAAe,IAAI,IAAI,CAAE,QAAO;AACpC,QAAO,EAAE,OAAO,YAAY,EAAE,EAAE,SAAS,IAAI;;;;;;;;AAS/C,SAAgB,uBACd,QACA,KACA,aACQ;AACR,QAAO,SAAS,OAAO,GAAG,GAAG,IAAI,GAAG;;;AAItC,SAAgB,0BACd,QACA,QACA,KACQ;CACR,MAAM,SAAS,uBAAuB,QAAQ,KAAK,OAAO,YAAY;AACtE,KAAI,CAAC,OAAO,QAAS,QAAO;AAC5B,QAAO,GAAG,OAAO,GAAG,OAAO;;;AAI7B,SAAgB,sBAAsB,QAAyC;AAC7E,QAAO,OAAO,OAAO;;;AAIvB,SAAgB,0BACd,QACS;AACT,QAAO,OAAO,WAAW;;;;;;;AAQ3B,SAAgB,kBACd,QACA,MACA,MACQ;AACR,QAAO,cAAc,OAAO,GAAG,KAAK,GAAG"}
@@ -1,5 +1,5 @@
1
- import { N as effectiveDispatchNamespaceName } from "./tamer.mjs";
2
- import { n as dnsRecordCommentMarker, r as dnsRecordStateKey, t as dnsRecordAppliesToEnv } from "./dns-records.resolve-DV6XBZf3.mjs";
1
+ import { P as effectiveDispatchNamespaceName } from "./tamer.mjs";
2
+ import { n as dnsRecordCommentMarker, r as dnsRecordStateKey, t as dnsRecordAppliesToEnv } from "./dns-records.resolve-DwBR_1WI.mjs";
3
3
 
4
4
  //#region src/features/dispatch-namespace/dispatch-namespace.sync.ts
5
5
  async function dispatchNamespaceSync(resources, tenant, env, api, state) {
@@ -72,4 +72,4 @@ async function dnsRecordSync(resources, tenant, env, api, state) {
72
72
 
73
73
  //#endregion
74
74
  export { dispatchNamespaceSync as n, dnsRecordSync as t };
75
- //# sourceMappingURL=dns-records.sync-FyzKl-Ph.mjs.map
75
+ //# sourceMappingURL=dns-records.sync-Dfwk76J_.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"dns-records.sync-FyzKl-Ph.mjs","names":["entry: DnsRecordStateEntry"],"sources":["../src/features/dispatch-namespace/dispatch-namespace.sync.ts","../src/features/dns-records/dns-records.sync.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\nexport async function dispatchNamespaceSync(\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 list = await api.dispatchNamespaceListAll();\n const names = new Set(list.map((n) => n.namespace_name));\n\n for (const config of resources) {\n const resolved = effectiveDispatchNamespaceName(config, env, tenant);\n const key = `dispatch_ns:${resolved}`;\n if (!names.has(resolved)) continue;\n\n state.set(key, {\n type: \"dispatch_namespace\",\n logicalName: config.logicalName,\n derivedName: resolved,\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} from \"./dns-records.resolve.js\";\n\n/**\n * Re-adopt every Tamer-attributed DNS record found on the live zones into\n * state. Mirrors the dispatch-namespace sync pattern: Tamer never\n * **destroys** records here, just imports rows that match its\n * attribution comment so a `apply` straight after `sync` is a no-op when\n * Cloudflare and config agree.\n *\n * Records on Cloudflare without Tamer's marker comment are ignored even\n * if they happen to match the declared `(zone, type, name)` — they are\n * presumed to be hand-managed and surface in `tamer drift` as\n * `unrecordedInState` so the operator can decide.\n */\nexport async function dnsRecordSync(\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\n for (const config of applicable) {\n let live = zoneCache.get(config.zoneId);\n if (!live) {\n live = await api.zoneDnsRecordListAll(config.zoneId);\n zoneCache.set(config.zoneId, live);\n }\n const marker = dnsRecordCommentMarker(tenant, env, config.logicalName);\n const adopted = live.find(\n (r) =>\n r.type === config.type &&\n typeof r.comment === \"string\" &&\n r.comment.startsWith(marker),\n );\n if (!adopted) continue;\n const stateKey = dnsRecordStateKey(config.zoneId, config.type, config.name);\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: adopted.name,\n content: adopted.content,\n ttl: adopted.ttl ?? 1,\n proxied: adopted.proxied ?? false,\n priority: adopted.priority,\n comment: adopted.comment ?? marker,\n recordId: adopted.id,\n createdAt: state.get(stateKey)?.type === \"dns_record\"\n ? (state.get(stateKey) as DnsRecordStateEntry).createdAt\n : ts,\n updatedAt: ts,\n };\n state.set(stateKey, entry);\n }\n}\n"],"mappings":";;;;AAKA,eAAsB,sBACpB,WACA,QACA,KACA,KACA,OACe;AACf,KAAI,UAAU,WAAW,EAAG;AAC5B,KAAI,QAAQ,QAAS;CAErB,MAAM,OAAO,MAAM,IAAI,0BAA0B;CACjD,MAAM,QAAQ,IAAI,IAAI,KAAK,KAAK,MAAM,EAAE,eAAe,CAAC;AAExD,MAAK,MAAM,UAAU,WAAW;EAC9B,MAAM,WAAW,+BAA+B,QAAQ,KAAK,OAAO;EACpE,MAAM,MAAM,eAAe;AAC3B,MAAI,CAAC,MAAM,IAAI,SAAS,CAAE;AAE1B,QAAM,IAAI,KAAK;GACb,MAAM;GACN,aAAa,OAAO;GACpB,aAAa;GACb,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC,4BAAW,IAAI,MAAM,EAAC,aAAa;GACpC,CAAC;;;;;;;;;;;;;;;;;;ACJN,eAAsB,cACpB,WACA,QACA,KACA,KACA,OACe;AACf,KAAI,UAAU,WAAW,EAAG;CAC5B,MAAM,aAAa,UAAU,QAAQ,MAAM,sBAAsB,GAAG,IAAI,CAAC;AACzE,KAAI,WAAW,WAAW,EAAG;CAE7B,MAAM,4BAAY,IAAI,KAGnB;AAEH,MAAK,MAAM,UAAU,YAAY;EAC/B,IAAI,OAAO,UAAU,IAAI,OAAO,OAAO;AACvC,MAAI,CAAC,MAAM;AACT,UAAO,MAAM,IAAI,qBAAqB,OAAO,OAAO;AACpD,aAAU,IAAI,OAAO,QAAQ,KAAK;;EAEpC,MAAM,SAAS,uBAAuB,QAAQ,KAAK,OAAO,YAAY;EACtE,MAAM,UAAU,KAAK,MAClB,MACC,EAAE,SAAS,OAAO,QAClB,OAAO,EAAE,YAAY,YACrB,EAAE,QAAQ,WAAW,OAAO,CAC/B;AACD,MAAI,CAAC,QAAS;EACd,MAAM,WAAW,kBAAkB,OAAO,QAAQ,OAAO,MAAM,OAAO,KAAK;EAC3E,MAAM,sBAAK,IAAI,MAAM,EAAC,aAAa;EACnC,MAAMA,QAA6B;GACjC,MAAM;GACN,aAAa,OAAO;GACpB,QAAQ,OAAO;GACf,YAAY,OAAO;GACnB,MAAM,QAAQ;GACd,SAAS,QAAQ;GACjB,KAAK,QAAQ,OAAO;GACpB,SAAS,QAAQ,WAAW;GAC5B,UAAU,QAAQ;GAClB,SAAS,QAAQ,WAAW;GAC5B,UAAU,QAAQ;GAClB,WAAW,MAAM,IAAI,SAAS,EAAE,SAAS,eACpC,MAAM,IAAI,SAAS,CAAyB,YAC7C;GACJ,WAAW;GACZ;AACD,QAAM,IAAI,UAAU,MAAM"}
1
+ {"version":3,"file":"dns-records.sync-Dfwk76J_.mjs","names":["entry: DnsRecordStateEntry"],"sources":["../src/features/dispatch-namespace/dispatch-namespace.sync.ts","../src/features/dns-records/dns-records.sync.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\nexport async function dispatchNamespaceSync(\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 list = await api.dispatchNamespaceListAll();\n const names = new Set(list.map((n) => n.namespace_name));\n\n for (const config of resources) {\n const resolved = effectiveDispatchNamespaceName(config, env, tenant);\n const key = `dispatch_ns:${resolved}`;\n if (!names.has(resolved)) continue;\n\n state.set(key, {\n type: \"dispatch_namespace\",\n logicalName: config.logicalName,\n derivedName: resolved,\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} from \"./dns-records.resolve.js\";\n\n/**\n * Re-adopt every Tamer-attributed DNS record found on the live zones into\n * state. Mirrors the dispatch-namespace sync pattern: Tamer never\n * **destroys** records here, just imports rows that match its\n * attribution comment so a `apply` straight after `sync` is a no-op when\n * Cloudflare and config agree.\n *\n * Records on Cloudflare without Tamer's marker comment are ignored even\n * if they happen to match the declared `(zone, type, name)` — they are\n * presumed to be hand-managed and surface in `tamer drift` as\n * `unrecordedInState` so the operator can decide.\n */\nexport async function dnsRecordSync(\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\n for (const config of applicable) {\n let live = zoneCache.get(config.zoneId);\n if (!live) {\n live = await api.zoneDnsRecordListAll(config.zoneId);\n zoneCache.set(config.zoneId, live);\n }\n const marker = dnsRecordCommentMarker(tenant, env, config.logicalName);\n const adopted = live.find(\n (r) =>\n r.type === config.type &&\n typeof r.comment === \"string\" &&\n r.comment.startsWith(marker),\n );\n if (!adopted) continue;\n const stateKey = dnsRecordStateKey(config.zoneId, config.type, config.name);\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: adopted.name,\n content: adopted.content,\n ttl: adopted.ttl ?? 1,\n proxied: adopted.proxied ?? false,\n priority: adopted.priority,\n comment: adopted.comment ?? marker,\n recordId: adopted.id,\n createdAt: state.get(stateKey)?.type === \"dns_record\"\n ? (state.get(stateKey) as DnsRecordStateEntry).createdAt\n : ts,\n updatedAt: ts,\n };\n state.set(stateKey, entry);\n }\n}\n"],"mappings":";;;;AAKA,eAAsB,sBACpB,WACA,QACA,KACA,KACA,OACe;AACf,KAAI,UAAU,WAAW,EAAG;AAC5B,KAAI,QAAQ,QAAS;CAErB,MAAM,OAAO,MAAM,IAAI,0BAA0B;CACjD,MAAM,QAAQ,IAAI,IAAI,KAAK,KAAK,MAAM,EAAE,eAAe,CAAC;AAExD,MAAK,MAAM,UAAU,WAAW;EAC9B,MAAM,WAAW,+BAA+B,QAAQ,KAAK,OAAO;EACpE,MAAM,MAAM,eAAe;AAC3B,MAAI,CAAC,MAAM,IAAI,SAAS,CAAE;AAE1B,QAAM,IAAI,KAAK;GACb,MAAM;GACN,aAAa,OAAO;GACpB,aAAa;GACb,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC,4BAAW,IAAI,MAAM,EAAC,aAAa;GACpC,CAAC;;;;;;;;;;;;;;;;;;ACJN,eAAsB,cACpB,WACA,QACA,KACA,KACA,OACe;AACf,KAAI,UAAU,WAAW,EAAG;CAC5B,MAAM,aAAa,UAAU,QAAQ,MAAM,sBAAsB,GAAG,IAAI,CAAC;AACzE,KAAI,WAAW,WAAW,EAAG;CAE7B,MAAM,4BAAY,IAAI,KAGnB;AAEH,MAAK,MAAM,UAAU,YAAY;EAC/B,IAAI,OAAO,UAAU,IAAI,OAAO,OAAO;AACvC,MAAI,CAAC,MAAM;AACT,UAAO,MAAM,IAAI,qBAAqB,OAAO,OAAO;AACpD,aAAU,IAAI,OAAO,QAAQ,KAAK;;EAEpC,MAAM,SAAS,uBAAuB,QAAQ,KAAK,OAAO,YAAY;EACtE,MAAM,UAAU,KAAK,MAClB,MACC,EAAE,SAAS,OAAO,QAClB,OAAO,EAAE,YAAY,YACrB,EAAE,QAAQ,WAAW,OAAO,CAC/B;AACD,MAAI,CAAC,QAAS;EACd,MAAM,WAAW,kBAAkB,OAAO,QAAQ,OAAO,MAAM,OAAO,KAAK;EAC3E,MAAM,sBAAK,IAAI,MAAM,EAAC,aAAa;EACnC,MAAMA,QAA6B;GACjC,MAAM;GACN,aAAa,OAAO;GACpB,QAAQ,OAAO;GACf,YAAY,OAAO;GACnB,MAAM,QAAQ;GACd,SAAS,QAAQ;GACjB,KAAK,QAAQ,OAAO;GACpB,SAAS,QAAQ,WAAW;GAC5B,UAAU,QAAQ;GAClB,SAAS,QAAQ,WAAW;GAC5B,UAAU,QAAQ;GAClB,WAAW,MAAM,IAAI,SAAS,EAAE,SAAS,eACpC,MAAM,IAAI,SAAS,CAAyB,YAC7C;GACJ,WAAW;GACZ;AACD,QAAM,IAAI,UAAU,MAAM"}
@@ -1,4 +1,4 @@
1
- import { L as CFApiClient, R as cloudflareAccountIdFromEnv, z as cloudflareApiTokenFromEnv } from "./tamer.mjs";
1
+ import { B as cloudflareApiTokenFromEnv, R as CFApiClient, z as cloudflareAccountIdFromEnv } from "./tamer.mjs";
2
2
 
3
3
  //#region src/cli/commands/doctor.ts
4
4
  async function runDoctor(options) {
@@ -31,4 +31,4 @@ async function runDoctor(options) {
31
31
 
32
32
  //#endregion
33
33
  export { runDoctor };
34
- //# sourceMappingURL=doctor-fm_vGe2C.mjs.map
34
+ //# sourceMappingURL=doctor-BIaLEVFR.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"doctor-fm_vGe2C.mjs","names":["report: DoctorReport"],"sources":["../src/cli/commands/doctor.ts"],"sourcesContent":["import {\n cloudflareAccountIdFromEnv,\n cloudflareApiTokenFromEnv,\n} from \"../../core/cloudflareEnv.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\n\nexport interface DoctorReport {\n ok: boolean;\n accountIdPresent: boolean;\n apiTokenPresent: boolean;\n accountReadable: boolean;\n error?: string;\n}\n\nexport async function runDoctor(options: { json?: boolean }): Promise<number> {\n const accountId = cloudflareAccountIdFromEnv();\n const token = cloudflareApiTokenFromEnv();\n const report: DoctorReport = {\n ok: false,\n accountIdPresent: !!accountId,\n apiTokenPresent: !!token && token.length > 0,\n accountReadable: false,\n };\n\n if (!accountId || token.length === 0) {\n report.error = \"CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN must be set\";\n if (options.json) console.log(JSON.stringify(report, null, 2));\n else console.error(report.error);\n return 1;\n }\n\n try {\n const api = new CFApiClient(accountId, token);\n await api.accountRead();\n report.accountReadable = true;\n report.ok = true;\n } catch (err) {\n report.error = err instanceof Error ? err.message : String(err);\n }\n\n if (options.json) {\n console.log(JSON.stringify(report, null, 2));\n } else {\n console.log(\n report.ok\n ? \"doctor: account token can read Cloudflare account API.\"\n : `doctor: failed — ${report.error ?? \"unknown\"}`,\n );\n }\n\n if (!report.ok) {\n return 1;\n }\n return 0;\n}\n"],"mappings":";;;AAcA,eAAsB,UAAU,SAA8C;CAC5E,MAAM,YAAY,4BAA4B;CAC9C,MAAM,QAAQ,2BAA2B;CACzC,MAAMA,SAAuB;EAC3B,IAAI;EACJ,kBAAkB,CAAC,CAAC;EACpB,iBAAiB,CAAC,CAAC,SAAS,MAAM,SAAS;EAC3C,iBAAiB;EAClB;AAED,KAAI,CAAC,aAAa,MAAM,WAAW,GAAG;AACpC,SAAO,QAAQ;AACf,MAAI,QAAQ,KAAM,SAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;MACzD,SAAQ,MAAM,OAAO,MAAM;AAChC,SAAO;;AAGT,KAAI;AAEF,QADY,IAAI,YAAY,WAAW,MAAM,CACnC,aAAa;AACvB,SAAO,kBAAkB;AACzB,SAAO,KAAK;UACL,KAAK;AACZ,SAAO,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;AAGjE,KAAI,QAAQ,KACV,SAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;KAE5C,SAAQ,IACN,OAAO,KACH,2DACA,oBAAoB,OAAO,SAAS,YACzC;AAGH,KAAI,CAAC,OAAO,GACV,QAAO;AAET,QAAO"}
1
+ {"version":3,"file":"doctor-BIaLEVFR.mjs","names":["report: DoctorReport"],"sources":["../src/cli/commands/doctor.ts"],"sourcesContent":["import {\n cloudflareAccountIdFromEnv,\n cloudflareApiTokenFromEnv,\n} from \"../../core/cloudflareEnv.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\n\nexport interface DoctorReport {\n ok: boolean;\n accountIdPresent: boolean;\n apiTokenPresent: boolean;\n accountReadable: boolean;\n error?: string;\n}\n\nexport async function runDoctor(options: { json?: boolean }): Promise<number> {\n const accountId = cloudflareAccountIdFromEnv();\n const token = cloudflareApiTokenFromEnv();\n const report: DoctorReport = {\n ok: false,\n accountIdPresent: !!accountId,\n apiTokenPresent: !!token && token.length > 0,\n accountReadable: false,\n };\n\n if (!accountId || token.length === 0) {\n report.error = \"CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN must be set\";\n if (options.json) console.log(JSON.stringify(report, null, 2));\n else console.error(report.error);\n return 1;\n }\n\n try {\n const api = new CFApiClient(accountId, token);\n await api.accountRead();\n report.accountReadable = true;\n report.ok = true;\n } catch (err) {\n report.error = err instanceof Error ? err.message : String(err);\n }\n\n if (options.json) {\n console.log(JSON.stringify(report, null, 2));\n } else {\n console.log(\n report.ok\n ? \"doctor: account token can read Cloudflare account API.\"\n : `doctor: failed — ${report.error ?? \"unknown\"}`,\n );\n }\n\n if (!report.ok) {\n return 1;\n }\n return 0;\n}\n"],"mappings":";;;AAcA,eAAsB,UAAU,SAA8C;CAC5E,MAAM,YAAY,4BAA4B;CAC9C,MAAM,QAAQ,2BAA2B;CACzC,MAAMA,SAAuB;EAC3B,IAAI;EACJ,kBAAkB,CAAC,CAAC;EACpB,iBAAiB,CAAC,CAAC,SAAS,MAAM,SAAS;EAC3C,iBAAiB;EAClB;AAED,KAAI,CAAC,aAAa,MAAM,WAAW,GAAG;AACpC,SAAO,QAAQ;AACf,MAAI,QAAQ,KAAM,SAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;MACzD,SAAQ,MAAM,OAAO,MAAM;AAChC,SAAO;;AAGT,KAAI;AAEF,QADY,IAAI,YAAY,WAAW,MAAM,CACnC,aAAa;AACvB,SAAO,kBAAkB;AACzB,SAAO,KAAK;UACL,KAAK;AACZ,SAAO,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;;AAGjE,KAAI,QAAQ,KACV,SAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;KAE5C,SAAQ,IACN,OAAO,KACH,2DACA,oBAAoB,OAAO,SAAS,YACzC;AAGH,KAAI,CAAC,OAAO,GACV,QAAO;AAET,QAAO"}
@@ -0,0 +1,8 @@
1
+ import "./registry-X9dlQxG3.mjs";
2
+ import "./r2S3EmptyBucket-CXLmOrYF.mjs";
3
+ import { n as runDrift, t as computeDriftReport } from "./drift-CLsSBorO.mjs";
4
+ import "./logpush-job-C_6uzGUC.mjs";
5
+ import "./worker-route-CvuUPq1k.mjs";
6
+ import "./workers-DSlrKeNL.mjs";
7
+
8
+ export { runDrift };
@@ -1,10 +1,10 @@
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, a as reconcileSecrets, c as vaultReaderFromMap, d as requiredSecretsForWorker, f as fetchStackImports, h as StateManager, o as secretsDrift, u as namingFromConfig, w as stackNameForConfig, y as tenantStateKey } from "./tamer.mjs";
3
- import { n as resourceModules } from "./registry-BrOxbA2i.mjs";
4
- import { n as dnsRecordCommentMarker, r as dnsRecordStateKey, t as dnsRecordAppliesToEnv } from "./dns-records.resolve-DV6XBZf3.mjs";
5
- import { r as logpushJobDrift } from "./logpush-job-GqVKG_HI.mjs";
6
- import { t as workerRoutesDrift } from "./worker-route-CUQBu9xe.mjs";
7
- import { t as workersDrift } from "./workers-DWXnZAzG.mjs";
2
+ import { E as mergeWorkerConfigForResourcePick, H as getWorkers, O as resolveDeployedWorkerName, P as effectiveDispatchNamespaceName, R as CFApiClient, U as loadConfig, V as getConfigBaseDir, a as reconcileSecrets, c as vaultReaderFromMap, d as requiredSecretsForWorker, f as fetchStackImports, h as StateManager, o as secretsDrift, u as namingFromConfig, w as stackNameForConfig, y as tenantStateKey, z as cloudflareAccountIdFromEnv } from "./tamer.mjs";
3
+ import { n as resourceModules } from "./registry-X9dlQxG3.mjs";
4
+ import { n as dnsRecordCommentMarker, r as dnsRecordStateKey, t as dnsRecordAppliesToEnv } from "./dns-records.resolve-DwBR_1WI.mjs";
5
+ import { r as logpushJobDrift } from "./logpush-job-C_6uzGUC.mjs";
6
+ import { t as workerRoutesDrift } from "./worker-route-CvuUPq1k.mjs";
7
+ import { t as workersDrift } from "./workers-DSlrKeNL.mjs";
8
8
 
9
9
  //#region src/features/dispatch-namespace/dispatch-namespace.drift.ts
10
10
  function dispatchNamespaceDrift(allDispatch, resources, env, tenant, state) {
@@ -348,4 +348,4 @@ async function buildSecretWorkerInputs(workers, config, env, baseDir, accountId,
348
348
 
349
349
  //#endregion
350
350
  export { runDrift as n, computeDriftReport as t };
351
- //# sourceMappingURL=drift-Ci368_WQ.mjs.map
351
+ //# sourceMappingURL=drift-CLsSBorO.mjs.map