@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":"drift-Ci368_WQ.mjs","names":["drift: ResourceDrift","drift: ResourceDrift","drift: ResourceDrift","byZone: DnsRecordsByZone","out: T[]","REGISTRY_LABELS: Record<string, string>","out: Array<{\n workerKey: string;\n required: string[];\n workerSecretNames: string[];\n }>","workerSecretNames: string[]"],"sources":["../src/features/dispatch-namespace/dispatch-namespace.drift.ts","../src/features/dns-records/dns-records.drift.ts","../src/core/drift/drift.types.ts","../src/core/drift/tenantDrift.ts","../src/cli/commands/drift.ts"],"sourcesContent":["import type {\n DispatchNamespaceResourceConfig,\n DispatchNamespaceStateEntry,\n TenantMeta,\n} from \"../../types.js\";\nimport type { StateManager } from \"../../core/state/StateManager.js\";\nimport type { ResourceDrift } from \"../../core/drift/drift.types.js\";\nimport { effectiveDispatchNamespaceName } from \"./dispatch-namespace.resolve.js\";\n\ninterface CFDispatchNamespace {\n namespace_name: string;\n}\n\nexport function dispatchNamespaceDrift(\n allDispatch: CFDispatchNamespace[],\n resources: DispatchNamespaceResourceConfig[],\n env: string,\n tenant: TenantMeta,\n state: StateManager,\n): ResourceDrift {\n const drift: ResourceDrift = {\n kind: \"dispatch_namespace\",\n missingFromCloudflare: [],\n unrecordedInState: [],\n undeployed: [],\n };\n\n const cfNames = new Set(allDispatch.map((d) => d.namespace_name));\n const allState = state.getAll();\n const nsState = Object.values(allState).filter(\n (e): e is DispatchNamespaceStateEntry => e.type === \"dispatch_namespace\",\n );\n\n for (const config of resources) {\n const derivedName = effectiveDispatchNamespaceName(config, env, tenant);\n const stateEntry = nsState.find(\n (e) => e.logicalName === config.logicalName && e.derivedName === derivedName,\n );\n const onCf = cfNames.has(derivedName);\n\n if (stateEntry && !onCf) {\n drift.missingFromCloudflare.push({\n logicalName: stateEntry.logicalName,\n derivedName: stateEntry.derivedName,\n });\n } else if (onCf && !stateEntry) {\n drift.unrecordedInState.push({\n logicalName: config.logicalName,\n derivedName,\n });\n } else if (!onCf && !stateEntry) {\n drift.undeployed.push({\n logicalName: config.logicalName,\n derivedName,\n });\n }\n }\n\n return drift;\n}\n","import type {\n DnsRecordResourceConfig,\n DnsRecordStateEntry,\n TenantMeta,\n} from \"../../types.js\";\nimport type { StateManager } from \"../../core/state/StateManager.js\";\nimport type { ResourceDrift } from \"../../core/drift/drift.types.js\";\nimport {\n dnsRecordAppliesToEnv,\n dnsRecordCommentMarker,\n dnsRecordStateKey,\n} from \"./dns-records.resolve.js\";\n\ninterface CFDnsRecord {\n id: string;\n type: string;\n name: string;\n content: string;\n ttl?: number;\n proxied?: boolean;\n priority?: number;\n comment?: string | null;\n}\n\n/**\n * Map of `zoneId → live records`. The drift caller pre-fetches because\n * other resource modules also iterate zones (worker routes), so we\n * accept the snapshot rather than refetching here.\n */\nexport type DnsRecordsByZone = Map<string, CFDnsRecord[]>;\n\nexport function dnsRecordDrift(\n byZone: DnsRecordsByZone,\n resources: DnsRecordResourceConfig[],\n tenant: TenantMeta,\n env: string,\n state: StateManager,\n): ResourceDrift {\n const drift: ResourceDrift = {\n kind: \"dns_record\",\n missingFromCloudflare: [],\n unrecordedInState: [],\n undeployed: [],\n };\n\n const stateRecords = Object.values(state.getAll()).filter(\n (e): e is DnsRecordStateEntry => e.type === \"dns_record\",\n );\n\n for (const config of resources) {\n if (!dnsRecordAppliesToEnv(config, env)) continue;\n const live = byZone.get(config.zoneId) ?? [];\n const marker = dnsRecordCommentMarker(tenant, env, config.logicalName);\n const stateKey = dnsRecordStateKey(config.zoneId, config.type, config.name);\n const entry = stateRecords.find(\n (e) =>\n e.zoneId === config.zoneId &&\n e.recordType === config.type &&\n e.logicalName === config.logicalName,\n );\n const onCf = entry\n ? live.find((r) => r.id === entry.recordId)\n : live.find(\n (r) =>\n r.type === config.type &&\n typeof r.comment === \"string\" &&\n r.comment.startsWith(marker),\n );\n\n if (entry && !onCf) {\n drift.missingFromCloudflare.push({\n logicalName: entry.logicalName,\n derivedName: `${entry.recordType} ${entry.name}`,\n cfId: entry.recordId,\n });\n } else if (onCf && !entry) {\n drift.unrecordedInState.push({\n logicalName: config.logicalName,\n derivedName: `${config.type} ${onCf.name}`,\n cfId: onCf.id,\n });\n } else if (!onCf && !entry) {\n drift.undeployed.push({\n logicalName: config.logicalName,\n derivedName: `${config.type} ${config.name}`,\n detail: stateKey,\n });\n }\n }\n\n return drift;\n}\n","/**\n * Read-only drift report comparing recorded state vs. Cloudflare reality vs.\n * the current `tamer.config.ts`.\n */\n\nexport type DriftKind =\n | \"d1\"\n | \"r2\"\n | \"kv\"\n | \"queue\"\n | \"hyperdrive\"\n | \"vectorize\"\n | \"ai_gateway\"\n | \"pipeline\"\n | \"workflow\"\n | \"secret_store\"\n | \"secret\"\n | \"dns_record\"\n | \"dispatch_namespace\"\n | \"logpush_job\"\n | \"tenant\"\n | \"worker_route\"\n | \"worker_script\";\n\nexport interface DriftEntry {\n /** Logical resource name from `tamer.config.ts`. */\n logicalName: string;\n /** Cloudflare-side name (or `(unknown)` when no CF or state side knows it). */\n derivedName: string;\n /** Cloudflare resource ID, when known (D1 uuid, KV id). */\n cfId?: string;\n /** Optional human-readable detail (e.g. shard date). */\n detail?: string;\n}\n\nexport interface ResourceDrift {\n kind: DriftKind;\n /** Tracked in state but no longer present on Cloudflare. */\n missingFromCloudflare: DriftEntry[];\n /**\n * Present on Cloudflare and matches a declared resource in this config,\n * but no state entry tracks it (e.g. created out-of-band).\n */\n unrecordedInState: DriftEntry[];\n /**\n * Declared in this stack's config but neither tracked in state nor present\n * on Cloudflare (run `tamer apply`).\n */\n undeployed: DriftEntry[];\n}\n\nexport interface DriftReport {\n tenantId: string;\n env: string;\n generatedAt: string;\n resources: ResourceDrift[];\n /** True iff any of the three categories has at least one entry. */\n hasDrift: boolean;\n}\n\nexport function resourceDriftIsClean(d: ResourceDrift): boolean {\n return (\n d.missingFromCloudflare.length === 0 &&\n d.unrecordedInState.length === 0 &&\n d.undeployed.length === 0\n );\n}\n\nexport function reportHasDrift(resources: ResourceDrift[]): boolean {\n return resources.some((d) => !resourceDriftIsClean(d));\n}\n","import type { CFApiClient } from \"../api/CFApiClient.js\";\nimport type { StateManager } from \"../state/StateManager.js\";\nimport type { ResourceDrift } from \"./drift.types.js\";\nimport { tenantStateKey } from \"../tenant/tenantKeys.js\";\n\ninterface CFD1 {\n uuid: string;\n name: string;\n}\n\n/**\n * Drift for workspace tenants in {@link CfiState.tenants}: dispatch script and\n * recorded D1 shards must still exist on Cloudflare.\n *\n * `unrecordedInState` / `undeployed` are intentionally empty here — tenant\n * discovery from CF alone is heuristic until product/script naming is fully\n * pinned (`docs/scope-remaining.md` D-1).\n */\nexport async function tenantDrift(\n state: StateManager,\n api: CFApiClient,\n allD1: CFD1[],\n): Promise<ResourceDrift> {\n const drift: ResourceDrift = {\n kind: \"tenant\",\n missingFromCloudflare: [],\n unrecordedInState: [],\n undeployed: [],\n };\n\n const d1ById = new Map(allD1.map((d) => [d.uuid, d.name]));\n const tenants = state\n .listTenants()\n .filter((t) => t.provisioningStatus !== \"tombstoned\");\n if (tenants.length === 0) return drift;\n\n const scriptLists = new Map<string, Set<string>>();\n async function scriptsInNs(ns: string): Promise<Set<string>> {\n let set = scriptLists.get(ns);\n if (!set) {\n const list = await api.dispatchNamespaceScriptList(ns);\n set = new Set(list.map((s) => s.id));\n scriptLists.set(ns, set);\n }\n return set;\n }\n\n for (const t of tenants) {\n const logical = tenantStateKey(t.product, t.workspace);\n try {\n const ids = await scriptsInNs(t.dispatchNamespaceName);\n if (!ids.has(t.scriptName)) {\n drift.missingFromCloudflare.push({\n logicalName: logical,\n derivedName: t.scriptName,\n detail: \"dispatch_script\",\n });\n }\n } catch {\n drift.missingFromCloudflare.push({\n logicalName: logical,\n derivedName: t.dispatchNamespaceName,\n detail: \"dispatch_namespace_list_failed\",\n });\n }\n\n for (const shard of t.d1Shards ?? []) {\n if (!d1ById.has(shard.cfId)) {\n drift.missingFromCloudflare.push({\n logicalName: logical,\n derivedName: shard.derivedName,\n cfId: shard.cfId,\n detail: `d1:${shard.role}`,\n });\n }\n }\n }\n\n return drift;\n}\n","import { loadConfig, getWorkers, getConfigBaseDir } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { namingFromConfig } from \"../../core/config/namingFromConfig.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { dispatchNamespaceDrift } from \"../../features/dispatch-namespace/index.js\";\nimport {\n dnsRecordDrift,\n type DnsRecordsByZone,\n} from \"../../features/dns-records/index.js\";\nimport { getDispatchNamespaces, getDnsRecords, getLogpushJobs } from \"../../types.js\";\nimport { logpushJobDrift } from \"../../features/logpush-job/index.js\";\nimport type {\n DriftReport,\n ResourceDrift,\n} from \"../../core/drift/drift.types.js\";\nimport { reportHasDrift } from \"../../core/drift/drift.types.js\";\nimport { tenantDrift } from \"../../core/drift/tenantDrift.js\";\nimport { workerRoutesDrift } from \"../../features/worker-route/index.js\";\nimport { workersDrift } from \"../../features/workers/index.js\";\nimport { resourceModules } from \"../../core/registry/registry.js\";\nimport { fetchStackImports } from \"../../core/imports/fetchStackImports.js\";\nimport { mergeWorkerConfigForResourcePick } from \"../../core/config/resolver.js\";\nimport { resolveDeployedWorkerName } from \"../../core/config/resolver.js\";\nimport { requiredSecretsForWorker } from \"../../core/secrets/declared.js\";\nimport {\n reconcileSecrets,\n secretsDrift,\n vaultReaderFromMap,\n type SecretsVaultReader,\n} from \"../../core/secrets/reconcile.js\";\n\n/**\n * Compute a read-only drift report for the given env.\n *\n * Compares Tamer state (D1 `tamer-state-{env}`) against the Cloudflare API\n * and the resources declared in `tamer.config.ts`. Reports three categories:\n *\n * - `missingFromCloudflare` — tracked in state but the CF resource is gone.\n * - `unrecordedInState` — exists on CF and matches a declared resource, but\n * no state entry tracks it (e.g. created out-of-band; run `tamer sync`).\n * - `undeployed` — declared in this stack's config, present in neither\n * state nor CF (run `tamer apply`).\n *\n * Pure: never writes to state. Returns the report so callers can choose how to\n * render or consume it.\n */\nexport async function computeDriftReport(options: {\n env?: string;\n configPath?: string;\n /** Optional vault reader for secret reconciliation (defaults to empty). */\n secretsVault?: SecretsVaultReader;\n}): Promise<DriftReport> {\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 = 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 naming = namingFromConfig(config);\n const state = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n await state.hydrate(api);\n // Tolerant pre-fetch keeps drift accurate when worker `tamerRoutes`\n // depend on sibling-stack outputs (otherwise the placeholder pattern\n // would never match anything CF returned).\n const imports = await fetchStackImports(api, config, env).catch(() => ({}));\n\n async function safeList<T>(\n label: string,\n fn: () => Promise<T[]>,\n ): Promise<T[]> {\n try {\n return await fn();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[drift] skipping ${label}: ${msg}`);\n return [];\n }\n }\n\n const lists = await Promise.all(\n resourceModules.map((m) =>\n safeList(`${m.label} list`, () => m.fetchAll(api)),\n ),\n );\n\n const allDispatch =\n getDispatchNamespaces(config).length > 0\n ? await safeList(\"dispatch namespaces\", () =>\n api.dispatchNamespaceListAll(),\n )\n : [];\n\n const allLogpushJobs =\n getLogpushJobs(config).length > 0 && env !== \"local\"\n ? await safeList(\"logpush jobs\", () => api.logpushAccountJobsList())\n : [];\n\n const workers = await getWorkers(config, baseDir);\n\n const aggregated = new Map<string, ResourceDrift>();\n function merge(d: ResourceDrift): void {\n const existing = aggregated.get(d.kind);\n if (!existing) {\n aggregated.set(d.kind, d);\n return;\n }\n existing.missingFromCloudflare.push(...d.missingFromCloudflare);\n existing.unrecordedInState.push(...d.unrecordedInState);\n existing.undeployed.push(...d.undeployed);\n }\n\n for (const [workerKey, workerConfig] of workers) {\n const mergedWorker = mergeWorkerConfigForResourcePick(\n config,\n workerKey,\n workerConfig,\n env,\n accountId,\n naming,\n state,\n { referencesMode: \"tolerant\", imports },\n );\n resourceModules.forEach((mod, i) => {\n const resources = mod.pickResources(mergedWorker);\n if (resources.length === 0) return;\n merge(\n mod.drift({\n resources,\n all: lists[i],\n tenant: config.tenant,\n env,\n api,\n state,\n naming,\n config,\n baseDir,\n }),\n );\n });\n }\n\n const dispatchResources = getDispatchNamespaces(config);\n if (dispatchResources.length > 0) {\n merge(\n dispatchNamespaceDrift(\n allDispatch,\n dispatchResources,\n env,\n config.tenant,\n state,\n ),\n );\n }\n\n const dnsResources = getDnsRecords(config);\n if (dnsResources.length > 0 && env !== \"local\") {\n const byZone: DnsRecordsByZone = new Map();\n const zones = Array.from(new Set(dnsResources.map((r) => r.zoneId)));\n for (const zoneId of zones) {\n const live = await safeList(`dns records (zone ${zoneId})`, () =>\n api.zoneDnsRecordListAll(zoneId),\n );\n byZone.set(zoneId, live);\n }\n merge(dnsRecordDrift(byZone, dnsResources, config.tenant, env, state));\n }\n\n const logpushResources = getLogpushJobs(config);\n if (logpushResources.length > 0 && env !== \"local\") {\n merge(\n logpushJobDrift(\n allLogpushJobs,\n logpushResources,\n env,\n config.tenant,\n state,\n ),\n );\n }\n\n if (state.listTenants().length > 0) {\n const allD1Idx = resourceModules.findIndex((m) => m.kind === \"d1\");\n const allD1 =\n allD1Idx >= 0\n ? (lists[allD1Idx] as Array<{ uuid: string; name: string }>)\n : [];\n merge(await tenantDrift(state, api, allD1));\n }\n\n const workerRouteReport = await workerRoutesDrift(\n env,\n config,\n baseDir,\n accountId,\n naming,\n state,\n api,\n { imports },\n );\n if (workerRouteReport) merge(workerRouteReport);\n\n const workerScriptReport = await workersDrift(\n env,\n config,\n baseDir,\n accountId,\n naming,\n state,\n api,\n { imports },\n );\n if (workerScriptReport) merge(workerScriptReport);\n\n const secretWorkers = await buildSecretWorkerInputs(\n workers,\n config,\n env,\n baseDir,\n accountId,\n naming,\n state,\n api,\n imports,\n );\n if (secretWorkers.length > 0) {\n const secretEntries = await reconcileSecrets({\n workers: secretWorkers,\n vault: options.secretsVault ?? vaultReaderFromMap({}),\n state,\n });\n merge(secretsDrift(secretEntries));\n }\n\n const dedupedResources = Array.from(aggregated.values()).map((d) => ({\n ...d,\n missingFromCloudflare: dedupe(d.missingFromCloudflare),\n unrecordedInState: dedupe(d.unrecordedInState),\n undeployed: dedupe(d.undeployed),\n }));\n\n return {\n tenantId: config.tenant.id,\n env,\n generatedAt: new Date().toISOString(),\n resources: dedupedResources,\n hasDrift: reportHasDrift(dedupedResources),\n };\n}\n\nfunction dedupe<T extends { logicalName: string; derivedName: string }>(\n list: T[],\n): T[] {\n const seen = new Set<string>();\n const out: T[] = [];\n for (const item of list) {\n const key = `${item.logicalName}::${item.derivedName}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(item);\n }\n return out;\n}\n\n/**\n * CLI entry point. Prints a human report (or JSON when `--json`) and sets a\n * non-zero process exit code when drift is found.\n */\nexport async function runDrift(options: {\n env?: string;\n configPath?: string;\n json?: boolean;\n}): Promise<number> {\n const report = await computeDriftReport({\n env: options.env,\n configPath: options.configPath,\n });\n\n if (options.json) {\n console.log(JSON.stringify(report, null, 2));\n } else {\n printHumanReport(report);\n }\n\n return report.hasDrift ? 1 : 0;\n}\n\nfunction printHumanReport(report: DriftReport): void {\n console.log(\n `\\nDrift report — tenant ${report.tenantId}, env ${report.env}\\n`,\n );\n if (report.resources.length === 0) {\n console.log(\" (no managed resource kinds in this config)\\n\");\n return;\n }\n for (const d of report.resources) {\n const total =\n d.missingFromCloudflare.length +\n d.unrecordedInState.length +\n d.undeployed.length;\n console.log(`${labelFor(d.kind)} (${total} drift):`);\n if (total === 0) {\n console.log(\" ok\");\n continue;\n }\n if (d.missingFromCloudflare.length) {\n console.log(\" missing from Cloudflare (state references gone):\");\n for (const e of d.missingFromCloudflare) {\n console.log(` - ${e.logicalName} -> ${e.derivedName}${suffix(e.cfId)}`);\n }\n }\n if (d.unrecordedInState.length) {\n console.log(\" unrecorded in state (run `tamer sync`):\");\n for (const e of d.unrecordedInState) {\n console.log(` - ${e.logicalName} -> ${e.derivedName}${suffix(e.cfId)}`);\n }\n }\n if (d.undeployed.length) {\n console.log(\" undeployed (run `tamer apply`):\");\n for (const e of d.undeployed) {\n console.log(` - ${e.logicalName} -> ${e.derivedName}`);\n }\n }\n }\n console.log(report.hasDrift ? \"\\nDrift detected.\\n\" : \"\\nNo drift.\\n\");\n}\n\nconst REGISTRY_LABELS: Record<string, string> = Object.fromEntries(\n resourceModules.map((m) => [m.kind, m.label]),\n);\n\nfunction labelFor(kind: ResourceDrift[\"kind\"]): string {\n if (REGISTRY_LABELS[kind]) return REGISTRY_LABELS[kind];\n switch (kind) {\n case \"dispatch_namespace\":\n return \"Dispatch namespaces\";\n case \"logpush_job\":\n return \"Logpush jobs\";\n case \"dns_record\":\n return \"DNS records\";\n case \"tenant\":\n return \"Workspace tenants\";\n case \"worker_route\":\n return \"HTTP routes (Workers Routes API)\";\n case \"worker_script\":\n return \"Worker scripts\";\n case \"secret\":\n return \"Worker secrets\";\n default:\n return kind;\n }\n}\n\nfunction suffix(cfId?: string): string {\n return cfId ? ` [${cfId}]` : \"\";\n}\n\nasync function buildSecretWorkerInputs(\n workers: Awaited<ReturnType<typeof getWorkers>>,\n config: Awaited<ReturnType<typeof loadConfig>>,\n env: string,\n baseDir: string,\n accountId: string,\n naming: ReturnType<typeof namingFromConfig>,\n state: StateManager,\n api: CFApiClient,\n imports: Awaited<ReturnType<typeof fetchStackImports>>,\n): Promise<\n Array<{\n workerKey: string;\n required: string[];\n workerSecretNames: string[];\n }>\n> {\n const out: Array<{\n workerKey: string;\n required: string[];\n workerSecretNames: string[];\n }> = [];\n\n for (const [workerKey, workerConfig] of workers) {\n const merged = mergeWorkerConfigForResourcePick(\n config,\n workerKey,\n workerConfig,\n env,\n accountId,\n naming,\n state,\n { referencesMode: \"tolerant\", imports },\n );\n const required = requiredSecretsForWorker(merged);\n if (required.length === 0) continue;\n\n const deployedName = resolveDeployedWorkerName(\n config,\n workerKey,\n workerConfig,\n env,\n naming,\n );\n let workerSecretNames: string[] = [];\n if (env !== \"local\") {\n try {\n workerSecretNames = await api.workersSecretsList(deployedName);\n } catch {\n workerSecretNames = [];\n }\n }\n\n out.push({ workerKey, required, workerSecretNames });\n }\n\n return out;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"drift-CLsSBorO.mjs","names":["drift: ResourceDrift","drift: ResourceDrift","drift: ResourceDrift","byZone: DnsRecordsByZone","out: T[]","REGISTRY_LABELS: Record<string, string>","out: Array<{\n workerKey: string;\n required: string[];\n workerSecretNames: string[];\n }>","workerSecretNames: string[]"],"sources":["../src/features/dispatch-namespace/dispatch-namespace.drift.ts","../src/features/dns-records/dns-records.drift.ts","../src/core/drift/drift.types.ts","../src/core/drift/tenantDrift.ts","../src/cli/commands/drift.ts"],"sourcesContent":["import type {\n DispatchNamespaceResourceConfig,\n DispatchNamespaceStateEntry,\n TenantMeta,\n} from \"../../types.js\";\nimport type { StateManager } from \"../../core/state/StateManager.js\";\nimport type { ResourceDrift } from \"../../core/drift/drift.types.js\";\nimport { effectiveDispatchNamespaceName } from \"./dispatch-namespace.resolve.js\";\n\ninterface CFDispatchNamespace {\n namespace_name: string;\n}\n\nexport function dispatchNamespaceDrift(\n allDispatch: CFDispatchNamespace[],\n resources: DispatchNamespaceResourceConfig[],\n env: string,\n tenant: TenantMeta,\n state: StateManager,\n): ResourceDrift {\n const drift: ResourceDrift = {\n kind: \"dispatch_namespace\",\n missingFromCloudflare: [],\n unrecordedInState: [],\n undeployed: [],\n };\n\n const cfNames = new Set(allDispatch.map((d) => d.namespace_name));\n const allState = state.getAll();\n const nsState = Object.values(allState).filter(\n (e): e is DispatchNamespaceStateEntry => e.type === \"dispatch_namespace\",\n );\n\n for (const config of resources) {\n const derivedName = effectiveDispatchNamespaceName(config, env, tenant);\n const stateEntry = nsState.find(\n (e) => e.logicalName === config.logicalName && e.derivedName === derivedName,\n );\n const onCf = cfNames.has(derivedName);\n\n if (stateEntry && !onCf) {\n drift.missingFromCloudflare.push({\n logicalName: stateEntry.logicalName,\n derivedName: stateEntry.derivedName,\n });\n } else if (onCf && !stateEntry) {\n drift.unrecordedInState.push({\n logicalName: config.logicalName,\n derivedName,\n });\n } else if (!onCf && !stateEntry) {\n drift.undeployed.push({\n logicalName: config.logicalName,\n derivedName,\n });\n }\n }\n\n return drift;\n}\n","import type {\n DnsRecordResourceConfig,\n DnsRecordStateEntry,\n TenantMeta,\n} from \"../../types.js\";\nimport type { StateManager } from \"../../core/state/StateManager.js\";\nimport type { ResourceDrift } from \"../../core/drift/drift.types.js\";\nimport {\n dnsRecordAppliesToEnv,\n dnsRecordCommentMarker,\n dnsRecordStateKey,\n} from \"./dns-records.resolve.js\";\n\ninterface CFDnsRecord {\n id: string;\n type: string;\n name: string;\n content: string;\n ttl?: number;\n proxied?: boolean;\n priority?: number;\n comment?: string | null;\n}\n\n/**\n * Map of `zoneId → live records`. The drift caller pre-fetches because\n * other resource modules also iterate zones (worker routes), so we\n * accept the snapshot rather than refetching here.\n */\nexport type DnsRecordsByZone = Map<string, CFDnsRecord[]>;\n\nexport function dnsRecordDrift(\n byZone: DnsRecordsByZone,\n resources: DnsRecordResourceConfig[],\n tenant: TenantMeta,\n env: string,\n state: StateManager,\n): ResourceDrift {\n const drift: ResourceDrift = {\n kind: \"dns_record\",\n missingFromCloudflare: [],\n unrecordedInState: [],\n undeployed: [],\n };\n\n const stateRecords = Object.values(state.getAll()).filter(\n (e): e is DnsRecordStateEntry => e.type === \"dns_record\",\n );\n\n for (const config of resources) {\n if (!dnsRecordAppliesToEnv(config, env)) continue;\n const live = byZone.get(config.zoneId) ?? [];\n const marker = dnsRecordCommentMarker(tenant, env, config.logicalName);\n const stateKey = dnsRecordStateKey(config.zoneId, config.type, config.name);\n const entry = stateRecords.find(\n (e) =>\n e.zoneId === config.zoneId &&\n e.recordType === config.type &&\n e.logicalName === config.logicalName,\n );\n const onCf = entry\n ? live.find((r) => r.id === entry.recordId)\n : live.find(\n (r) =>\n r.type === config.type &&\n typeof r.comment === \"string\" &&\n r.comment.startsWith(marker),\n );\n\n if (entry && !onCf) {\n drift.missingFromCloudflare.push({\n logicalName: entry.logicalName,\n derivedName: `${entry.recordType} ${entry.name}`,\n cfId: entry.recordId,\n });\n } else if (onCf && !entry) {\n drift.unrecordedInState.push({\n logicalName: config.logicalName,\n derivedName: `${config.type} ${onCf.name}`,\n cfId: onCf.id,\n });\n } else if (!onCf && !entry) {\n drift.undeployed.push({\n logicalName: config.logicalName,\n derivedName: `${config.type} ${config.name}`,\n detail: stateKey,\n });\n }\n }\n\n return drift;\n}\n","/**\n * Read-only drift report comparing recorded state vs. Cloudflare reality vs.\n * the current `tamer.config.ts`.\n */\n\nexport type DriftKind =\n | \"d1\"\n | \"r2\"\n | \"kv\"\n | \"queue\"\n | \"hyperdrive\"\n | \"vectorize\"\n | \"ai_gateway\"\n | \"pipeline\"\n | \"workflow\"\n | \"secret_store\"\n | \"secret\"\n | \"dns_record\"\n | \"dispatch_namespace\"\n | \"logpush_job\"\n | \"tenant\"\n | \"worker_route\"\n | \"worker_script\";\n\nexport interface DriftEntry {\n /** Logical resource name from `tamer.config.ts`. */\n logicalName: string;\n /** Cloudflare-side name (or `(unknown)` when no CF or state side knows it). */\n derivedName: string;\n /** Cloudflare resource ID, when known (D1 uuid, KV id). */\n cfId?: string;\n /** Optional human-readable detail (e.g. shard date). */\n detail?: string;\n}\n\nexport interface ResourceDrift {\n kind: DriftKind;\n /** Tracked in state but no longer present on Cloudflare. */\n missingFromCloudflare: DriftEntry[];\n /**\n * Present on Cloudflare and matches a declared resource in this config,\n * but no state entry tracks it (e.g. created out-of-band).\n */\n unrecordedInState: DriftEntry[];\n /**\n * Declared in this stack's config but neither tracked in state nor present\n * on Cloudflare (run `tamer apply`).\n */\n undeployed: DriftEntry[];\n}\n\nexport interface DriftReport {\n tenantId: string;\n env: string;\n generatedAt: string;\n resources: ResourceDrift[];\n /** True iff any of the three categories has at least one entry. */\n hasDrift: boolean;\n}\n\nexport function resourceDriftIsClean(d: ResourceDrift): boolean {\n return (\n d.missingFromCloudflare.length === 0 &&\n d.unrecordedInState.length === 0 &&\n d.undeployed.length === 0\n );\n}\n\nexport function reportHasDrift(resources: ResourceDrift[]): boolean {\n return resources.some((d) => !resourceDriftIsClean(d));\n}\n","import type { CFApiClient } from \"../api/CFApiClient.js\";\nimport type { StateManager } from \"../state/StateManager.js\";\nimport type { ResourceDrift } from \"./drift.types.js\";\nimport { tenantStateKey } from \"../tenant/tenantKeys.js\";\n\ninterface CFD1 {\n uuid: string;\n name: string;\n}\n\n/**\n * Drift for workspace tenants in {@link CfiState.tenants}: dispatch script and\n * recorded D1 shards must still exist on Cloudflare.\n *\n * `unrecordedInState` / `undeployed` are intentionally empty here — tenant\n * discovery from CF alone is heuristic until product/script naming is fully\n * pinned (`docs/scope-remaining.md` D-1).\n */\nexport async function tenantDrift(\n state: StateManager,\n api: CFApiClient,\n allD1: CFD1[],\n): Promise<ResourceDrift> {\n const drift: ResourceDrift = {\n kind: \"tenant\",\n missingFromCloudflare: [],\n unrecordedInState: [],\n undeployed: [],\n };\n\n const d1ById = new Map(allD1.map((d) => [d.uuid, d.name]));\n const tenants = state\n .listTenants()\n .filter((t) => t.provisioningStatus !== \"tombstoned\");\n if (tenants.length === 0) return drift;\n\n const scriptLists = new Map<string, Set<string>>();\n async function scriptsInNs(ns: string): Promise<Set<string>> {\n let set = scriptLists.get(ns);\n if (!set) {\n const list = await api.dispatchNamespaceScriptList(ns);\n set = new Set(list.map((s) => s.id));\n scriptLists.set(ns, set);\n }\n return set;\n }\n\n for (const t of tenants) {\n const logical = tenantStateKey(t.product, t.workspace);\n try {\n const ids = await scriptsInNs(t.dispatchNamespaceName);\n if (!ids.has(t.scriptName)) {\n drift.missingFromCloudflare.push({\n logicalName: logical,\n derivedName: t.scriptName,\n detail: \"dispatch_script\",\n });\n }\n } catch {\n drift.missingFromCloudflare.push({\n logicalName: logical,\n derivedName: t.dispatchNamespaceName,\n detail: \"dispatch_namespace_list_failed\",\n });\n }\n\n for (const shard of t.d1Shards ?? []) {\n if (!d1ById.has(shard.cfId)) {\n drift.missingFromCloudflare.push({\n logicalName: logical,\n derivedName: shard.derivedName,\n cfId: shard.cfId,\n detail: `d1:${shard.role}`,\n });\n }\n }\n }\n\n return drift;\n}\n","import { loadConfig, getWorkers, getConfigBaseDir } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { namingFromConfig } from \"../../core/config/namingFromConfig.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { dispatchNamespaceDrift } from \"../../features/dispatch-namespace/index.js\";\nimport {\n dnsRecordDrift,\n type DnsRecordsByZone,\n} from \"../../features/dns-records/index.js\";\nimport { getDispatchNamespaces, getDnsRecords, getLogpushJobs } from \"../../types.js\";\nimport { logpushJobDrift } from \"../../features/logpush-job/index.js\";\nimport type {\n DriftReport,\n ResourceDrift,\n} from \"../../core/drift/drift.types.js\";\nimport { reportHasDrift } from \"../../core/drift/drift.types.js\";\nimport { tenantDrift } from \"../../core/drift/tenantDrift.js\";\nimport { workerRoutesDrift } from \"../../features/worker-route/index.js\";\nimport { workersDrift } from \"../../features/workers/index.js\";\nimport { resourceModules } from \"../../core/registry/registry.js\";\nimport { fetchStackImports } from \"../../core/imports/fetchStackImports.js\";\nimport { mergeWorkerConfigForResourcePick } from \"../../core/config/resolver.js\";\nimport { resolveDeployedWorkerName } from \"../../core/config/resolver.js\";\nimport { requiredSecretsForWorker } from \"../../core/secrets/declared.js\";\nimport {\n reconcileSecrets,\n secretsDrift,\n vaultReaderFromMap,\n type SecretsVaultReader,\n} from \"../../core/secrets/reconcile.js\";\n\n/**\n * Compute a read-only drift report for the given env.\n *\n * Compares Tamer state (D1 `tamer-state-{env}`) against the Cloudflare API\n * and the resources declared in `tamer.config.ts`. Reports three categories:\n *\n * - `missingFromCloudflare` — tracked in state but the CF resource is gone.\n * - `unrecordedInState` — exists on CF and matches a declared resource, but\n * no state entry tracks it (e.g. created out-of-band; run `tamer sync`).\n * - `undeployed` — declared in this stack's config, present in neither\n * state nor CF (run `tamer apply`).\n *\n * Pure: never writes to state. Returns the report so callers can choose how to\n * render or consume it.\n */\nexport async function computeDriftReport(options: {\n env?: string;\n configPath?: string;\n /** Optional vault reader for secret reconciliation (defaults to empty). */\n secretsVault?: SecretsVaultReader;\n}): Promise<DriftReport> {\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 = 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 naming = namingFromConfig(config);\n const state = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n await state.hydrate(api);\n // Tolerant pre-fetch keeps drift accurate when worker `tamerRoutes`\n // depend on sibling-stack outputs (otherwise the placeholder pattern\n // would never match anything CF returned).\n const imports = await fetchStackImports(api, config, env).catch(() => ({}));\n\n async function safeList<T>(\n label: string,\n fn: () => Promise<T[]>,\n ): Promise<T[]> {\n try {\n return await fn();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[drift] skipping ${label}: ${msg}`);\n return [];\n }\n }\n\n const lists = await Promise.all(\n resourceModules.map((m) =>\n safeList(`${m.label} list`, () => m.fetchAll(api)),\n ),\n );\n\n const allDispatch =\n getDispatchNamespaces(config).length > 0\n ? await safeList(\"dispatch namespaces\", () =>\n api.dispatchNamespaceListAll(),\n )\n : [];\n\n const allLogpushJobs =\n getLogpushJobs(config).length > 0 && env !== \"local\"\n ? await safeList(\"logpush jobs\", () => api.logpushAccountJobsList())\n : [];\n\n const workers = await getWorkers(config, baseDir);\n\n const aggregated = new Map<string, ResourceDrift>();\n function merge(d: ResourceDrift): void {\n const existing = aggregated.get(d.kind);\n if (!existing) {\n aggregated.set(d.kind, d);\n return;\n }\n existing.missingFromCloudflare.push(...d.missingFromCloudflare);\n existing.unrecordedInState.push(...d.unrecordedInState);\n existing.undeployed.push(...d.undeployed);\n }\n\n for (const [workerKey, workerConfig] of workers) {\n const mergedWorker = mergeWorkerConfigForResourcePick(\n config,\n workerKey,\n workerConfig,\n env,\n accountId,\n naming,\n state,\n { referencesMode: \"tolerant\", imports },\n );\n resourceModules.forEach((mod, i) => {\n const resources = mod.pickResources(mergedWorker);\n if (resources.length === 0) return;\n merge(\n mod.drift({\n resources,\n all: lists[i],\n tenant: config.tenant,\n env,\n api,\n state,\n naming,\n config,\n baseDir,\n }),\n );\n });\n }\n\n const dispatchResources = getDispatchNamespaces(config);\n if (dispatchResources.length > 0) {\n merge(\n dispatchNamespaceDrift(\n allDispatch,\n dispatchResources,\n env,\n config.tenant,\n state,\n ),\n );\n }\n\n const dnsResources = getDnsRecords(config);\n if (dnsResources.length > 0 && env !== \"local\") {\n const byZone: DnsRecordsByZone = new Map();\n const zones = Array.from(new Set(dnsResources.map((r) => r.zoneId)));\n for (const zoneId of zones) {\n const live = await safeList(`dns records (zone ${zoneId})`, () =>\n api.zoneDnsRecordListAll(zoneId),\n );\n byZone.set(zoneId, live);\n }\n merge(dnsRecordDrift(byZone, dnsResources, config.tenant, env, state));\n }\n\n const logpushResources = getLogpushJobs(config);\n if (logpushResources.length > 0 && env !== \"local\") {\n merge(\n logpushJobDrift(\n allLogpushJobs,\n logpushResources,\n env,\n config.tenant,\n state,\n ),\n );\n }\n\n if (state.listTenants().length > 0) {\n const allD1Idx = resourceModules.findIndex((m) => m.kind === \"d1\");\n const allD1 =\n allD1Idx >= 0\n ? (lists[allD1Idx] as Array<{ uuid: string; name: string }>)\n : [];\n merge(await tenantDrift(state, api, allD1));\n }\n\n const workerRouteReport = await workerRoutesDrift(\n env,\n config,\n baseDir,\n accountId,\n naming,\n state,\n api,\n { imports },\n );\n if (workerRouteReport) merge(workerRouteReport);\n\n const workerScriptReport = await workersDrift(\n env,\n config,\n baseDir,\n accountId,\n naming,\n state,\n api,\n { imports },\n );\n if (workerScriptReport) merge(workerScriptReport);\n\n const secretWorkers = await buildSecretWorkerInputs(\n workers,\n config,\n env,\n baseDir,\n accountId,\n naming,\n state,\n api,\n imports,\n );\n if (secretWorkers.length > 0) {\n const secretEntries = await reconcileSecrets({\n workers: secretWorkers,\n vault: options.secretsVault ?? vaultReaderFromMap({}),\n state,\n });\n merge(secretsDrift(secretEntries));\n }\n\n const dedupedResources = Array.from(aggregated.values()).map((d) => ({\n ...d,\n missingFromCloudflare: dedupe(d.missingFromCloudflare),\n unrecordedInState: dedupe(d.unrecordedInState),\n undeployed: dedupe(d.undeployed),\n }));\n\n return {\n tenantId: config.tenant.id,\n env,\n generatedAt: new Date().toISOString(),\n resources: dedupedResources,\n hasDrift: reportHasDrift(dedupedResources),\n };\n}\n\nfunction dedupe<T extends { logicalName: string; derivedName: string }>(\n list: T[],\n): T[] {\n const seen = new Set<string>();\n const out: T[] = [];\n for (const item of list) {\n const key = `${item.logicalName}::${item.derivedName}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(item);\n }\n return out;\n}\n\n/**\n * CLI entry point. Prints a human report (or JSON when `--json`) and sets a\n * non-zero process exit code when drift is found.\n */\nexport async function runDrift(options: {\n env?: string;\n configPath?: string;\n json?: boolean;\n}): Promise<number> {\n const report = await computeDriftReport({\n env: options.env,\n configPath: options.configPath,\n });\n\n if (options.json) {\n console.log(JSON.stringify(report, null, 2));\n } else {\n printHumanReport(report);\n }\n\n return report.hasDrift ? 1 : 0;\n}\n\nfunction printHumanReport(report: DriftReport): void {\n console.log(\n `\\nDrift report — tenant ${report.tenantId}, env ${report.env}\\n`,\n );\n if (report.resources.length === 0) {\n console.log(\" (no managed resource kinds in this config)\\n\");\n return;\n }\n for (const d of report.resources) {\n const total =\n d.missingFromCloudflare.length +\n d.unrecordedInState.length +\n d.undeployed.length;\n console.log(`${labelFor(d.kind)} (${total} drift):`);\n if (total === 0) {\n console.log(\" ok\");\n continue;\n }\n if (d.missingFromCloudflare.length) {\n console.log(\" missing from Cloudflare (state references gone):\");\n for (const e of d.missingFromCloudflare) {\n console.log(` - ${e.logicalName} -> ${e.derivedName}${suffix(e.cfId)}`);\n }\n }\n if (d.unrecordedInState.length) {\n console.log(\" unrecorded in state (run `tamer sync`):\");\n for (const e of d.unrecordedInState) {\n console.log(` - ${e.logicalName} -> ${e.derivedName}${suffix(e.cfId)}`);\n }\n }\n if (d.undeployed.length) {\n console.log(\" undeployed (run `tamer apply`):\");\n for (const e of d.undeployed) {\n console.log(` - ${e.logicalName} -> ${e.derivedName}`);\n }\n }\n }\n console.log(report.hasDrift ? \"\\nDrift detected.\\n\" : \"\\nNo drift.\\n\");\n}\n\nconst REGISTRY_LABELS: Record<string, string> = Object.fromEntries(\n resourceModules.map((m) => [m.kind, m.label]),\n);\n\nfunction labelFor(kind: ResourceDrift[\"kind\"]): string {\n if (REGISTRY_LABELS[kind]) return REGISTRY_LABELS[kind];\n switch (kind) {\n case \"dispatch_namespace\":\n return \"Dispatch namespaces\";\n case \"logpush_job\":\n return \"Logpush jobs\";\n case \"dns_record\":\n return \"DNS records\";\n case \"tenant\":\n return \"Workspace tenants\";\n case \"worker_route\":\n return \"HTTP routes (Workers Routes API)\";\n case \"worker_script\":\n return \"Worker scripts\";\n case \"secret\":\n return \"Worker secrets\";\n default:\n return kind;\n }\n}\n\nfunction suffix(cfId?: string): string {\n return cfId ? ` [${cfId}]` : \"\";\n}\n\nasync function buildSecretWorkerInputs(\n workers: Awaited<ReturnType<typeof getWorkers>>,\n config: Awaited<ReturnType<typeof loadConfig>>,\n env: string,\n baseDir: string,\n accountId: string,\n naming: ReturnType<typeof namingFromConfig>,\n state: StateManager,\n api: CFApiClient,\n imports: Awaited<ReturnType<typeof fetchStackImports>>,\n): Promise<\n Array<{\n workerKey: string;\n required: string[];\n workerSecretNames: string[];\n }>\n> {\n const out: Array<{\n workerKey: string;\n required: string[];\n workerSecretNames: string[];\n }> = [];\n\n for (const [workerKey, workerConfig] of workers) {\n const merged = mergeWorkerConfigForResourcePick(\n config,\n workerKey,\n workerConfig,\n env,\n accountId,\n naming,\n state,\n { referencesMode: \"tolerant\", imports },\n );\n const required = requiredSecretsForWorker(merged);\n if (required.length === 0) continue;\n\n const deployedName = resolveDeployedWorkerName(\n config,\n workerKey,\n workerConfig,\n env,\n naming,\n );\n let workerSecretNames: string[] = [];\n if (env !== \"local\") {\n try {\n workerSecretNames = await api.workersSecretsList(deployedName);\n } catch {\n workerSecretNames = [];\n }\n }\n\n out.push({ workerKey, required, workerSecretNames });\n }\n\n return out;\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { k as resolveWorkerConfig } from "./tamer.mjs";
2
- import { o as d1CloudflareDatabaseName, s as d1SkipsProvisionAndMigrate } from "./registry-BrOxbA2i.mjs";
2
+ import { o as d1CloudflareDatabaseName, s as d1SkipsProvisionAndMigrate } from "./registry-X9dlQxG3.mjs";
3
3
  import { dirname, join } from "path";
4
4
  import { existsSync, mkdirSync, writeFileSync } from "fs";
5
5
 
@@ -138,4 +138,4 @@ async function assertShardRegistryPresentForDeploy(args) {
138
138
 
139
139
  //#endregion
140
140
  export { emitShardRegistryOnApply as n, assertShardRegistryPresentForDeploy as t };
141
- //# sourceMappingURL=emit-DDTQVfi_.mjs.map
141
+ //# sourceMappingURL=emit-Dh68dvo5.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"emit-DDTQVfi_.mjs","names":["row: ShardRegistryEntryV1","shards: ShardRegistryEntryV1[]"],"sources":["../src/core/codegen/shardRegistry/build.ts","../src/core/codegen/shardRegistry/emit.ts"],"sourcesContent":["import type { WranglerD1Database } from \"../../../generated/wrangler-types.js\";\nimport type { NamingEngine } from \"../../naming/NamingEngine.js\";\nimport type { StateManager } from \"../../state/StateManager.js\";\nimport type {\n D1ResourceConfig,\n D1StateEntry,\n ShardRegistryEntryV1,\n ShardRegistryV1,\n} from \"../../../types.js\";\nimport {\n d1CloudflareDatabaseName,\n d1SkipsProvisionAndMigrate,\n} from \"../../../features/d1/d1.ownership.js\";\n\nfunction registryRoleFor(config: D1ResourceConfig): string {\n return config.registryRole?.trim() || config.logicalName;\n}\n\nfunction optionalAliases(values: string[] | undefined): string[] | undefined {\n if (!values?.length) return undefined;\n const trimmed = values.map((v) => v.trim()).filter(Boolean);\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction entryFromState(\n config: D1ResourceConfig,\n databaseName: string,\n binding: string,\n shardDate?: string,\n): ShardRegistryEntryV1 {\n const row: ShardRegistryEntryV1 = {\n id: databaseName,\n role: registryRoleFor(config),\n binding,\n databaseName,\n };\n if (shardDate) row.shardDate = shardDate;\n if (config.current === false) row.current = false;\n const legacyIds = optionalAliases(config.legacyIds);\n const legacyBindings = optionalAliases(config.legacyBindings);\n if (legacyIds) row.legacyIds = legacyIds;\n if (legacyBindings) row.legacyBindings = legacyBindings;\n return row;\n}\n\n/**\n * Build {@link ShardRegistryV1} for env `E` from merged worker D1 config +\n * hydrated state. Uses the same binding/name resolution path as wrangler\n * generation (`d1DeriveBindingKey`, state `bindingKey`, ownership helpers).\n */\nexport function buildShardRegistryV1(\n resources: D1ResourceConfig[],\n env: string,\n state: StateManager,\n naming: NamingEngine,\n): ShardRegistryV1 {\n const shards: ShardRegistryEntryV1[] = [];\n\n for (const config of resources) {\n if (config.type === \"single\") {\n const derivedName = d1CloudflareDatabaseName(config, env, naming);\n const entry = state.get(derivedName);\n if (!entry || entry.type !== \"d1_database\") {\n if (d1SkipsProvisionAndMigrate(config)) {\n throw new Error(\n `shard registry: cross-stack D1 \"${config.logicalName}\" (${derivedName}) not in state — ` +\n `deploy the owning stack first, then re-run apply for env \"${env}\".`,\n );\n }\n throw new Error(\n `shard registry: D1 \"${config.logicalName}\" not in state — run \\`tamer apply --env ${env}\\` first.`,\n );\n }\n shards.push(\n entryFromState(\n config,\n derivedName,\n entry.bindingKey,\n ),\n );\n continue;\n }\n\n const allResources = state.getAll();\n const stateShards = Object.values(allResources).filter(\n (e): e is D1StateEntry =>\n e.type === \"d1_database\" && e.logicalName === config.logicalName,\n );\n if (stateShards.length === 0) {\n throw new Error(\n `shard registry: no shards in state for \"${config.logicalName}\" — run \\`tamer apply --env ${env}\\` first.`,\n );\n }\n\n for (const shard of stateShards) {\n shards.push(\n entryFromState(\n config,\n shard.derivedName,\n shard.bindingKey,\n shard.shardDate,\n ),\n );\n }\n }\n\n shards.sort((a, b) => a.binding.localeCompare(b.binding));\n\n return { version: 1, shards };\n}\n\nexport class ShardRegistryDriftError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ShardRegistryDriftError\";\n }\n}\n\n/**\n * Assert generated wrangler D1 bindings match the registry for apply env `E`.\n */\nexport function assertShardRegistryWranglerDrift(\n registry: ShardRegistryV1,\n wranglerD1: WranglerD1Database[] | undefined,\n env: string,\n): void {\n const d1List = wranglerD1 ?? [];\n const byBinding = new Map(registry.shards.map((s) => [s.binding, s]));\n\n for (const d1 of d1List) {\n const row = byBinding.get(d1.binding);\n if (!row) {\n throw new ShardRegistryDriftError(\n `shard registry drift (${env}): wrangler binding \"${d1.binding}\" has no registry row — ` +\n `re-run \\`tamer apply --env ${env}\\` or fix resources.d1 / state.`,\n );\n }\n if (d1.database_name !== row.databaseName) {\n throw new ShardRegistryDriftError(\n `shard registry drift (${env}): binding \"${d1.binding}\" wrangler database_name ` +\n `\"${d1.database_name}\" !== registry databaseName \"${row.databaseName}\".`,\n );\n }\n if (d1.database_name !== row.id) {\n throw new ShardRegistryDriftError(\n `shard registry drift (${env}): binding \"${d1.binding}\" wrangler database_name ` +\n `\"${d1.database_name}\" !== registry id \"${row.id}\".`,\n );\n }\n byBinding.delete(d1.binding);\n }\n\n for (const [binding, row] of byBinding) {\n throw new ShardRegistryDriftError(\n `shard registry drift (${env}): registry binding \"${binding}\" (${row.role}) ` +\n `missing from generated wrangler d1_databases — re-run \\`tamer apply --env ${env}\\`.`,\n );\n }\n}\n","import { existsSync, mkdirSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport type { WranglerConfig } from \"../../../generated/wrangler-types.js\";\nimport type { NamingEngine } from \"../../naming/NamingEngine.js\";\nimport type { StateManager } from \"../../state/StateManager.js\";\nimport { resolveWorkerConfig } from \"../../config/resolver.js\";\nimport type {\n CfiConfig,\n ShardRegistryCodegenConfig,\n WorkerConfig,\n} from \"../../../types.js\";\nimport {\n assertShardRegistryWranglerDrift,\n buildShardRegistryV1,\n} from \"./build.js\";\n\nconst DEFAULT_OUT_FILE = \"src/db/shard-registry.ts\";\nconst DEFAULT_EXPORT_NAME = \"shardRegistry\";\n\nexport function resolveShardRegistryWorkerKey(\n workers: Array<[string, WorkerConfig]>,\n spec?: ShardRegistryCodegenConfig,\n): string {\n if (spec?.worker) {\n const hit = workers.find(([key]) => key === spec.worker);\n if (!hit) {\n throw new Error(\n `codegen.shardRegistry.worker \"${spec.worker}\" not found in stack workers`,\n );\n }\n return spec.worker;\n }\n if (workers.length === 1) {\n return workers[0]![0];\n }\n throw new Error(\n \"codegen.shardRegistry.worker is required when the stack declares multiple workers\",\n );\n}\n\nexport function shardRegistryOutPath(\n workerDir: string,\n spec?: ShardRegistryCodegenConfig,\n): string {\n return join(workerDir, spec?.outFile?.trim() || DEFAULT_OUT_FILE);\n}\n\nexport function renderShardRegistryModule(\n registry: ReturnType<typeof buildShardRegistryV1>,\n env: string,\n exportName: string = DEFAULT_EXPORT_NAME,\n): string {\n const body = JSON.stringify(registry, null, 2);\n return `/** Generated by @dragonmastery/tamer — do not edit. Re-run \\`tamer apply --env ${env}\\`. */\nimport type { ShardRegistryV1 } from \"@dragonmastery/tamer\";\n\nexport const ${exportName}: ShardRegistryV1 = ${body};\n`;\n}\n\nexport function writeShardRegistryModule(\n workerDir: string,\n content: string,\n spec?: ShardRegistryCodegenConfig,\n): string {\n const path = shardRegistryOutPath(workerDir, spec);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, content, \"utf-8\");\n return path;\n}\n\nexport async function emitShardRegistryOnApply(args: {\n config: CfiConfig;\n env: string;\n baseDir: string;\n accountId: string;\n naming: NamingEngine;\n state: StateManager;\n imports: Awaited<\n ReturnType<\n typeof import(\"../../imports/fetchStackImports.js\").fetchStackImports\n >\n >;\n workers: Array<[string, WorkerConfig]>;\n wranglerByWorker: Map<string, WranglerConfig>;\n}): Promise<string | undefined> {\n const spec = args.config.codegen?.shardRegistry;\n if (!spec) return undefined;\n\n const workerKey = resolveShardRegistryWorkerKey(args.workers, spec);\n const workerEntry = args.workers.find(([key]) => key === workerKey);\n if (!workerEntry) {\n throw new Error(`codegen.shardRegistry: worker \"${workerKey}\" not found`);\n }\n const [, workerConfig] = workerEntry;\n\n const resolved = await resolveWorkerConfig(\n args.config,\n workerKey,\n workerConfig,\n args.env,\n args.baseDir,\n args.accountId,\n args.naming,\n args.state,\n { imports: args.imports },\n );\n const d1Resources = resolved.resources?.d1 ?? [];\n if (d1Resources.length === 0) {\n throw new Error(\n `codegen.shardRegistry: worker \"${workerKey}\" has no resources.d1 entries`,\n );\n }\n\n const registry = buildShardRegistryV1(\n d1Resources,\n args.env,\n args.state,\n args.naming,\n );\n\n const wrangler = args.wranglerByWorker.get(workerKey);\n if (!wrangler) {\n throw new Error(\n `codegen.shardRegistry: missing generated wrangler config for worker \"${workerKey}\"`,\n );\n }\n assertShardRegistryWranglerDrift(registry, wrangler.d1_databases, args.env);\n\n const exportName = spec.exportName?.trim() || DEFAULT_EXPORT_NAME;\n const content = renderShardRegistryModule(registry, args.env, exportName);\n const written = writeShardRegistryModule(resolved.workerDir, content, spec);\n console.log(\n `Generated shard registry for ${workerKey}: ${spec.outFile ?? DEFAULT_OUT_FILE}`,\n );\n return written;\n}\n\nexport async function assertShardRegistryPresentForDeploy(args: {\n config: CfiConfig;\n env: string;\n baseDir: string;\n accountId: string;\n naming: NamingEngine;\n state: StateManager;\n imports: Awaited<\n ReturnType<\n typeof import(\"../../imports/fetchStackImports.js\").fetchStackImports\n >\n >;\n workers: Array<[string, WorkerConfig]>;\n}): Promise<void> {\n const spec = args.config.codegen?.shardRegistry;\n if (!spec) return;\n\n const workerKey = resolveShardRegistryWorkerKey(args.workers, spec);\n const workerEntry = args.workers.find(([key]) => key === workerKey);\n if (!workerEntry) {\n throw new Error(`codegen.shardRegistry: worker \"${workerKey}\" not found`);\n }\n const [, workerConfig] = workerEntry;\n\n const resolved = await resolveWorkerConfig(\n args.config,\n workerKey,\n workerConfig,\n args.env,\n args.baseDir,\n args.accountId,\n args.naming,\n args.state,\n { imports: args.imports },\n );\n const path = shardRegistryOutPath(resolved.workerDir, spec);\n if (!existsSync(path)) {\n throw new Error(\n `Missing shard registry at ${spec.outFile ?? DEFAULT_OUT_FILE} for worker \"${workerKey}\". ` +\n `Run \\`tamer apply --env ${args.env}\\` before deploy.`,\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"emit-Dh68dvo5.mjs","names":["row: ShardRegistryEntryV1","shards: ShardRegistryEntryV1[]"],"sources":["../src/core/codegen/shardRegistry/build.ts","../src/core/codegen/shardRegistry/emit.ts"],"sourcesContent":["import type { WranglerD1Database } from \"../../../generated/wrangler-types.js\";\nimport type { NamingEngine } from \"../../naming/NamingEngine.js\";\nimport type { StateManager } from \"../../state/StateManager.js\";\nimport type {\n D1ResourceConfig,\n D1StateEntry,\n ShardRegistryEntryV1,\n ShardRegistryV1,\n} from \"../../../types.js\";\nimport {\n d1CloudflareDatabaseName,\n d1SkipsProvisionAndMigrate,\n} from \"../../../features/d1/d1.ownership.js\";\n\nfunction registryRoleFor(config: D1ResourceConfig): string {\n return config.registryRole?.trim() || config.logicalName;\n}\n\nfunction optionalAliases(values: string[] | undefined): string[] | undefined {\n if (!values?.length) return undefined;\n const trimmed = values.map((v) => v.trim()).filter(Boolean);\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction entryFromState(\n config: D1ResourceConfig,\n databaseName: string,\n binding: string,\n shardDate?: string,\n): ShardRegistryEntryV1 {\n const row: ShardRegistryEntryV1 = {\n id: databaseName,\n role: registryRoleFor(config),\n binding,\n databaseName,\n };\n if (shardDate) row.shardDate = shardDate;\n if (config.current === false) row.current = false;\n const legacyIds = optionalAliases(config.legacyIds);\n const legacyBindings = optionalAliases(config.legacyBindings);\n if (legacyIds) row.legacyIds = legacyIds;\n if (legacyBindings) row.legacyBindings = legacyBindings;\n return row;\n}\n\n/**\n * Build {@link ShardRegistryV1} for env `E` from merged worker D1 config +\n * hydrated state. Uses the same binding/name resolution path as wrangler\n * generation (`d1DeriveBindingKey`, state `bindingKey`, ownership helpers).\n */\nexport function buildShardRegistryV1(\n resources: D1ResourceConfig[],\n env: string,\n state: StateManager,\n naming: NamingEngine,\n): ShardRegistryV1 {\n const shards: ShardRegistryEntryV1[] = [];\n\n for (const config of resources) {\n if (config.type === \"single\") {\n const derivedName = d1CloudflareDatabaseName(config, env, naming);\n const entry = state.get(derivedName);\n if (!entry || entry.type !== \"d1_database\") {\n if (d1SkipsProvisionAndMigrate(config)) {\n throw new Error(\n `shard registry: cross-stack D1 \"${config.logicalName}\" (${derivedName}) not in state — ` +\n `deploy the owning stack first, then re-run apply for env \"${env}\".`,\n );\n }\n throw new Error(\n `shard registry: D1 \"${config.logicalName}\" not in state — run \\`tamer apply --env ${env}\\` first.`,\n );\n }\n shards.push(\n entryFromState(\n config,\n derivedName,\n entry.bindingKey,\n ),\n );\n continue;\n }\n\n const allResources = state.getAll();\n const stateShards = Object.values(allResources).filter(\n (e): e is D1StateEntry =>\n e.type === \"d1_database\" && e.logicalName === config.logicalName,\n );\n if (stateShards.length === 0) {\n throw new Error(\n `shard registry: no shards in state for \"${config.logicalName}\" — run \\`tamer apply --env ${env}\\` first.`,\n );\n }\n\n for (const shard of stateShards) {\n shards.push(\n entryFromState(\n config,\n shard.derivedName,\n shard.bindingKey,\n shard.shardDate,\n ),\n );\n }\n }\n\n shards.sort((a, b) => a.binding.localeCompare(b.binding));\n\n return { version: 1, shards };\n}\n\nexport class ShardRegistryDriftError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ShardRegistryDriftError\";\n }\n}\n\n/**\n * Assert generated wrangler D1 bindings match the registry for apply env `E`.\n */\nexport function assertShardRegistryWranglerDrift(\n registry: ShardRegistryV1,\n wranglerD1: WranglerD1Database[] | undefined,\n env: string,\n): void {\n const d1List = wranglerD1 ?? [];\n const byBinding = new Map(registry.shards.map((s) => [s.binding, s]));\n\n for (const d1 of d1List) {\n const row = byBinding.get(d1.binding);\n if (!row) {\n throw new ShardRegistryDriftError(\n `shard registry drift (${env}): wrangler binding \"${d1.binding}\" has no registry row — ` +\n `re-run \\`tamer apply --env ${env}\\` or fix resources.d1 / state.`,\n );\n }\n if (d1.database_name !== row.databaseName) {\n throw new ShardRegistryDriftError(\n `shard registry drift (${env}): binding \"${d1.binding}\" wrangler database_name ` +\n `\"${d1.database_name}\" !== registry databaseName \"${row.databaseName}\".`,\n );\n }\n if (d1.database_name !== row.id) {\n throw new ShardRegistryDriftError(\n `shard registry drift (${env}): binding \"${d1.binding}\" wrangler database_name ` +\n `\"${d1.database_name}\" !== registry id \"${row.id}\".`,\n );\n }\n byBinding.delete(d1.binding);\n }\n\n for (const [binding, row] of byBinding) {\n throw new ShardRegistryDriftError(\n `shard registry drift (${env}): registry binding \"${binding}\" (${row.role}) ` +\n `missing from generated wrangler d1_databases — re-run \\`tamer apply --env ${env}\\`.`,\n );\n }\n}\n","import { existsSync, mkdirSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport type { WranglerConfig } from \"../../../generated/wrangler-types.js\";\nimport type { NamingEngine } from \"../../naming/NamingEngine.js\";\nimport type { StateManager } from \"../../state/StateManager.js\";\nimport { resolveWorkerConfig } from \"../../config/resolver.js\";\nimport type {\n CfiConfig,\n ShardRegistryCodegenConfig,\n WorkerConfig,\n} from \"../../../types.js\";\nimport {\n assertShardRegistryWranglerDrift,\n buildShardRegistryV1,\n} from \"./build.js\";\n\nconst DEFAULT_OUT_FILE = \"src/db/shard-registry.ts\";\nconst DEFAULT_EXPORT_NAME = \"shardRegistry\";\n\nexport function resolveShardRegistryWorkerKey(\n workers: Array<[string, WorkerConfig]>,\n spec?: ShardRegistryCodegenConfig,\n): string {\n if (spec?.worker) {\n const hit = workers.find(([key]) => key === spec.worker);\n if (!hit) {\n throw new Error(\n `codegen.shardRegistry.worker \"${spec.worker}\" not found in stack workers`,\n );\n }\n return spec.worker;\n }\n if (workers.length === 1) {\n return workers[0]![0];\n }\n throw new Error(\n \"codegen.shardRegistry.worker is required when the stack declares multiple workers\",\n );\n}\n\nexport function shardRegistryOutPath(\n workerDir: string,\n spec?: ShardRegistryCodegenConfig,\n): string {\n return join(workerDir, spec?.outFile?.trim() || DEFAULT_OUT_FILE);\n}\n\nexport function renderShardRegistryModule(\n registry: ReturnType<typeof buildShardRegistryV1>,\n env: string,\n exportName: string = DEFAULT_EXPORT_NAME,\n): string {\n const body = JSON.stringify(registry, null, 2);\n return `/** Generated by @dragonmastery/tamer — do not edit. Re-run \\`tamer apply --env ${env}\\`. */\nimport type { ShardRegistryV1 } from \"@dragonmastery/tamer\";\n\nexport const ${exportName}: ShardRegistryV1 = ${body};\n`;\n}\n\nexport function writeShardRegistryModule(\n workerDir: string,\n content: string,\n spec?: ShardRegistryCodegenConfig,\n): string {\n const path = shardRegistryOutPath(workerDir, spec);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, content, \"utf-8\");\n return path;\n}\n\nexport async function emitShardRegistryOnApply(args: {\n config: CfiConfig;\n env: string;\n baseDir: string;\n accountId: string;\n naming: NamingEngine;\n state: StateManager;\n imports: Awaited<\n ReturnType<\n typeof import(\"../../imports/fetchStackImports.js\").fetchStackImports\n >\n >;\n workers: Array<[string, WorkerConfig]>;\n wranglerByWorker: Map<string, WranglerConfig>;\n}): Promise<string | undefined> {\n const spec = args.config.codegen?.shardRegistry;\n if (!spec) return undefined;\n\n const workerKey = resolveShardRegistryWorkerKey(args.workers, spec);\n const workerEntry = args.workers.find(([key]) => key === workerKey);\n if (!workerEntry) {\n throw new Error(`codegen.shardRegistry: worker \"${workerKey}\" not found`);\n }\n const [, workerConfig] = workerEntry;\n\n const resolved = await resolveWorkerConfig(\n args.config,\n workerKey,\n workerConfig,\n args.env,\n args.baseDir,\n args.accountId,\n args.naming,\n args.state,\n { imports: args.imports },\n );\n const d1Resources = resolved.resources?.d1 ?? [];\n if (d1Resources.length === 0) {\n throw new Error(\n `codegen.shardRegistry: worker \"${workerKey}\" has no resources.d1 entries`,\n );\n }\n\n const registry = buildShardRegistryV1(\n d1Resources,\n args.env,\n args.state,\n args.naming,\n );\n\n const wrangler = args.wranglerByWorker.get(workerKey);\n if (!wrangler) {\n throw new Error(\n `codegen.shardRegistry: missing generated wrangler config for worker \"${workerKey}\"`,\n );\n }\n assertShardRegistryWranglerDrift(registry, wrangler.d1_databases, args.env);\n\n const exportName = spec.exportName?.trim() || DEFAULT_EXPORT_NAME;\n const content = renderShardRegistryModule(registry, args.env, exportName);\n const written = writeShardRegistryModule(resolved.workerDir, content, spec);\n console.log(\n `Generated shard registry for ${workerKey}: ${spec.outFile ?? DEFAULT_OUT_FILE}`,\n );\n return written;\n}\n\nexport async function assertShardRegistryPresentForDeploy(args: {\n config: CfiConfig;\n env: string;\n baseDir: string;\n accountId: string;\n naming: NamingEngine;\n state: StateManager;\n imports: Awaited<\n ReturnType<\n typeof import(\"../../imports/fetchStackImports.js\").fetchStackImports\n >\n >;\n workers: Array<[string, WorkerConfig]>;\n}): Promise<void> {\n const spec = args.config.codegen?.shardRegistry;\n if (!spec) return;\n\n const workerKey = resolveShardRegistryWorkerKey(args.workers, spec);\n const workerEntry = args.workers.find(([key]) => key === workerKey);\n if (!workerEntry) {\n throw new Error(`codegen.shardRegistry: worker \"${workerKey}\" not found`);\n }\n const [, workerConfig] = workerEntry;\n\n const resolved = await resolveWorkerConfig(\n args.config,\n workerKey,\n workerConfig,\n args.env,\n args.baseDir,\n args.accountId,\n args.naming,\n args.state,\n { imports: args.imports },\n );\n const path = shardRegistryOutPath(resolved.workerDir, spec);\n if (!existsSync(path)) {\n throw new Error(\n `Missing shard registry at ${spec.outFile ?? DEFAULT_OUT_FILE} for worker \"${workerKey}\". ` +\n `Run \\`tamer apply --env ${args.env}\\` before deploy.`,\n );\n }\n}\n"],"mappings":"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"}
@@ -1,13 +1,13 @@
1
- import { C as listEnvRows, H as loadConfig, L as CFApiClient, P as isEphemeralEnv, R as cloudflareAccountIdFromEnv } from "./tamer.mjs";
2
- import "./registry-BrOxbA2i.mjs";
3
- import "./r2S3EmptyBucket-B9_pHfvB.mjs";
4
- import "./logpush-job-GqVKG_HI.mjs";
5
- import "./worker-route-CUQBu9xe.mjs";
6
- import "./sync-kl7MaCQV.mjs";
7
- import "./cloudflareSnapshot-CjXNMr4X.mjs";
8
- import "./tamerArtifactsR2-COndFmk5.mjs";
9
- import "./workers-DWXnZAzG.mjs";
10
- import { runDestroy } from "./destroy-BeOYY2U6.mjs";
1
+ import { C as listEnvRows, F as isEphemeralEnv, R as CFApiClient, U as loadConfig, z as cloudflareAccountIdFromEnv } from "./tamer.mjs";
2
+ import "./registry-X9dlQxG3.mjs";
3
+ import "./r2S3EmptyBucket-CXLmOrYF.mjs";
4
+ import "./logpush-job-C_6uzGUC.mjs";
5
+ import "./worker-route-CvuUPq1k.mjs";
6
+ import "./sync-DfJGkOME.mjs";
7
+ import "./cloudflareSnapshot-BAeNVohz.mjs";
8
+ import "./tamerArtifactsR2-B9myb-IA.mjs";
9
+ import "./workers-DSlrKeNL.mjs";
10
+ import { runDestroy } from "./destroy-Krf35oqE.mjs";
11
11
 
12
12
  //#region src/cli/commands/env-gc.ts
13
13
  async function runEnvGc(options) {
@@ -119,4 +119,4 @@ function formatDuration(ms) {
119
119
 
120
120
  //#endregion
121
121
  export { runEnvGc };
122
- //# sourceMappingURL=env-gc-DlQxkZPj.mjs.map
122
+ //# sourceMappingURL=env-gc-0vX5Av4i.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"env-gc-DlQxkZPj.mjs","names":["result: GcResult"],"sources":["../src/cli/commands/env-gc.ts"],"sourcesContent":["import { loadConfig } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { listEnvRows } from \"../../core/state/tamerStateDb.js\";\nimport { isEphemeralEnv } from \"../../features/dispatch-namespace/dispatch-namespace.resolve.js\";\nimport { runDestroy } from \"./destroy.js\";\n\ninterface GcResult {\n scanned: number;\n destroyed: Array<{ env: string; success: boolean; error?: string }>;\n}\n\nexport async function runEnvGc(options: {\n maxAge: string;\n force?: boolean;\n dryRun?: boolean;\n configPath?: string;\n json?: boolean;\n}): Promise<void> {\n const { maxAge, force, dryRun, configPath } = options;\n\n const maxAgeMs = parseDuration(maxAge);\n if (maxAgeMs === null) {\n throw new Error(\n `Invalid --max-age \"${maxAge}\". Use formats like \"72h\", \"3d\", \"30m\".`,\n );\n }\n\n const config = await loadConfig(configPath);\n const accountId =\n config.account_id ?? cloudflareAccountIdFromEnv();\n if (!accountId) {\n throw new Error(\n \"account_id required in config or CLOUDFLARE_ACCOUNT_ID env var\",\n );\n }\n\n const api = new CFApiClient(accountId);\n const allEnvs = await listEnvRows(api);\n\n const ephemeralEnvs = allEnvs.filter((e) =>\n isEphemeralEnv(e.env, config.tenant),\n );\n\n const now = Date.now();\n const stale = ephemeralEnvs.filter((e) => {\n if (!e.syncedAt) return true;\n const age = now - new Date(e.syncedAt).getTime();\n return age > maxAgeMs;\n });\n\n const result: GcResult = {\n scanned: ephemeralEnvs.length,\n destroyed: [],\n };\n\n if (options.json) {\n // For JSON output, collect results silently\n for (const env of stale) {\n if (dryRun) {\n result.destroyed.push({ env: env.env, success: true });\n continue;\n }\n try {\n await runDestroy({\n env: env.env,\n force: true,\n confirmEnv: env.env,\n wipeMetadata: true,\n configPath,\n });\n result.destroyed.push({ env: env.env, success: true });\n } catch (err) {\n result.destroyed.push({\n env: env.env,\n success: false,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n\n const maxAgeLabel = formatDuration(maxAgeMs);\n console.log(\n `\\nScanning for ephemeral envs older than ${maxAgeLabel}...`,\n );\n console.log(`Found ${ephemeralEnvs.length} ephemeral env(s), ${stale.length} stale.`);\n\n if (stale.length === 0) {\n console.log(\"Nothing to clean up.\\n\");\n return;\n }\n\n const envList = stale.map((e) => e.env).join(\", \");\n if (dryRun) {\n console.log(`\\nDry run — would destroy: ${envList}\\n`);\n return;\n }\n\n for (const env of stale) {\n console.log(`\\nDestroying ${env.env}...`);\n try {\n await runDestroy({\n env: env.env,\n force: true,\n confirmEnv: env.env,\n wipeMetadata: true,\n configPath,\n });\n result.destroyed.push({ env: env.env, success: true });\n console.log(`✓ Destroyed ${env.env}`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n result.destroyed.push({ env: env.env, success: false, error: msg });\n console.warn(`✗ Failed to destroy ${env.env}: ${msg}`);\n }\n }\n\n const succeeded = result.destroyed.filter((d) => d.success).length;\n const failed = result.destroyed.filter((d) => !d.success).length;\n console.log(\n `\\nGC complete: ${succeeded} env(s) destroyed, ${failed} failed.\\n`,\n );\n}\n\nfunction parseDuration(s: string): number | null {\n const m = s.match(/^(\\d+)([hmd])$/);\n if (!m) return null;\n const n = parseInt(m[1]!, 10);\n const unit = m[2];\n switch (unit) {\n case \"m\": return n * 60 * 1000;\n case \"h\": return n * 3600 * 1000;\n case \"d\": return n * 86400 * 1000;\n default: return null;\n }\n}\n\nfunction formatDuration(ms: number): string {\n const hours = Math.round(ms / 3600000);\n if (hours >= 24) return `${Math.round(hours / 24)}d`;\n return `${hours}h`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"env-gc-0vX5Av4i.mjs","names":["result: GcResult"],"sources":["../src/cli/commands/env-gc.ts"],"sourcesContent":["import { loadConfig } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { listEnvRows } from \"../../core/state/tamerStateDb.js\";\nimport { isEphemeralEnv } from \"../../features/dispatch-namespace/dispatch-namespace.resolve.js\";\nimport { runDestroy } from \"./destroy.js\";\n\ninterface GcResult {\n scanned: number;\n destroyed: Array<{ env: string; success: boolean; error?: string }>;\n}\n\nexport async function runEnvGc(options: {\n maxAge: string;\n force?: boolean;\n dryRun?: boolean;\n configPath?: string;\n json?: boolean;\n}): Promise<void> {\n const { maxAge, force, dryRun, configPath } = options;\n\n const maxAgeMs = parseDuration(maxAge);\n if (maxAgeMs === null) {\n throw new Error(\n `Invalid --max-age \"${maxAge}\". Use formats like \"72h\", \"3d\", \"30m\".`,\n );\n }\n\n const config = await loadConfig(configPath);\n const accountId =\n config.account_id ?? cloudflareAccountIdFromEnv();\n if (!accountId) {\n throw new Error(\n \"account_id required in config or CLOUDFLARE_ACCOUNT_ID env var\",\n );\n }\n\n const api = new CFApiClient(accountId);\n const allEnvs = await listEnvRows(api);\n\n const ephemeralEnvs = allEnvs.filter((e) =>\n isEphemeralEnv(e.env, config.tenant),\n );\n\n const now = Date.now();\n const stale = ephemeralEnvs.filter((e) => {\n if (!e.syncedAt) return true;\n const age = now - new Date(e.syncedAt).getTime();\n return age > maxAgeMs;\n });\n\n const result: GcResult = {\n scanned: ephemeralEnvs.length,\n destroyed: [],\n };\n\n if (options.json) {\n // For JSON output, collect results silently\n for (const env of stale) {\n if (dryRun) {\n result.destroyed.push({ env: env.env, success: true });\n continue;\n }\n try {\n await runDestroy({\n env: env.env,\n force: true,\n confirmEnv: env.env,\n wipeMetadata: true,\n configPath,\n });\n result.destroyed.push({ env: env.env, success: true });\n } catch (err) {\n result.destroyed.push({\n env: env.env,\n success: false,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n\n const maxAgeLabel = formatDuration(maxAgeMs);\n console.log(\n `\\nScanning for ephemeral envs older than ${maxAgeLabel}...`,\n );\n console.log(`Found ${ephemeralEnvs.length} ephemeral env(s), ${stale.length} stale.`);\n\n if (stale.length === 0) {\n console.log(\"Nothing to clean up.\\n\");\n return;\n }\n\n const envList = stale.map((e) => e.env).join(\", \");\n if (dryRun) {\n console.log(`\\nDry run — would destroy: ${envList}\\n`);\n return;\n }\n\n for (const env of stale) {\n console.log(`\\nDestroying ${env.env}...`);\n try {\n await runDestroy({\n env: env.env,\n force: true,\n confirmEnv: env.env,\n wipeMetadata: true,\n configPath,\n });\n result.destroyed.push({ env: env.env, success: true });\n console.log(`✓ Destroyed ${env.env}`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n result.destroyed.push({ env: env.env, success: false, error: msg });\n console.warn(`✗ Failed to destroy ${env.env}: ${msg}`);\n }\n }\n\n const succeeded = result.destroyed.filter((d) => d.success).length;\n const failed = result.destroyed.filter((d) => !d.success).length;\n console.log(\n `\\nGC complete: ${succeeded} env(s) destroyed, ${failed} failed.\\n`,\n );\n}\n\nfunction parseDuration(s: string): number | null {\n const m = s.match(/^(\\d+)([hmd])$/);\n if (!m) return null;\n const n = parseInt(m[1]!, 10);\n const unit = m[2];\n switch (unit) {\n case \"m\": return n * 60 * 1000;\n case \"h\": return n * 3600 * 1000;\n case \"d\": return n * 86400 * 1000;\n default: return null;\n }\n}\n\nfunction formatDuration(ms: number): string {\n const hours = Math.round(ms / 3600000);\n if (hours >= 24) return `${Math.round(hours / 24)}d`;\n return `${hours}h`;\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { C as listEnvRows, H as loadConfig, L as CFApiClient, R as cloudflareAccountIdFromEnv } from "./tamer.mjs";
1
+ import { C as listEnvRows, R as CFApiClient, U as loadConfig, z as cloudflareAccountIdFromEnv } from "./tamer.mjs";
2
2
 
3
3
  //#region src/cli/commands/env-list.ts
4
4
  async function runEnvList(options) {
@@ -29,4 +29,4 @@ async function runEnvList(options) {
29
29
 
30
30
  //#endregion
31
31
  export { runEnvList };
32
- //# sourceMappingURL=env-list-DhbYisDn.mjs.map
32
+ //# sourceMappingURL=env-list-DYCprcLb.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"env-list-DhbYisDn.mjs","names":[],"sources":["../src/cli/commands/env-list.ts"],"sourcesContent":["import { loadConfig } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { listEnvRows, type EnvRowInfo } from \"../../core/state/tamerStateDb.js\";\n\nexport async function runEnvList(options: {\n configPath?: string;\n json?: boolean;\n}): Promise<void> {\n const config = await loadConfig(options.configPath);\n const accountId =\n config.account_id ?? cloudflareAccountIdFromEnv();\n if (!accountId) {\n throw new Error(\n \"account_id required in config or CLOUDFLARE_ACCOUNT_ID env var\",\n );\n }\n\n const api = new CFApiClient(accountId);\n const envs = await listEnvRows(api);\n\n if (options.json) {\n console.log(JSON.stringify(envs, null, 2));\n return;\n }\n\n if (envs.length === 0) {\n console.log(\"No environments found in state.\");\n return;\n }\n\n const ephemeralPattern = config.tenant.ephemeralEnvPattern;\n const now = Date.now();\n\n console.log(\n `\\n${\"Env\".padEnd(20)} ${\"Stack\".padEnd(12)} ${\"Resources\".padEnd(10)} ${\"Last Activity\".padEnd(26)} Status`,\n );\n console.log(`${\"─\".repeat(20)} ${\"─\".repeat(12)} ${\"─\".repeat(10)} ${\"─\".repeat(26)} ${\"─\".repeat(10)}`);\n\n for (const env of envs.sort((a, b) => a.env.localeCompare(b.env))) {\n const isEphemeral = ephemeralPattern\n ? new RegExp(ephemeralPattern).test(env.env)\n : false;\n const tag = isEphemeral ? \" (ephemeral)\" : \"\";\n const age = env.syncedAt\n ? `${Math.round((now - new Date(env.syncedAt).getTime()) / 3600000)}h ago`\n : \"unknown\";\n const status = env.lastOperationStatus ?? \"\";\n console.log(\n `${(env.env + tag).padEnd(20)} ${env.stackName.padEnd(12)} ${String(env.resourceCount).padEnd(10)} ${age.padEnd(26)} ${status}`,\n );\n }\n console.log(\"\");\n}\n"],"mappings":";;;AAKA,eAAsB,WAAW,SAGf;CAChB,MAAM,SAAS,MAAM,WAAW,QAAQ,WAAW;CACnD,MAAM,YACJ,OAAO,cAAc,4BAA4B;AACnD,KAAI,CAAC,UACH,OAAM,IAAI,MACR,iEACD;CAIH,MAAM,OAAO,MAAM,YADP,IAAI,YAAY,UAAU,CACH;AAEnC,KAAI,QAAQ,MAAM;AAChB,UAAQ,IAAI,KAAK,UAAU,MAAM,MAAM,EAAE,CAAC;AAC1C;;AAGF,KAAI,KAAK,WAAW,GAAG;AACrB,UAAQ,IAAI,kCAAkC;AAC9C;;CAGF,MAAM,mBAAmB,OAAO,OAAO;CACvC,MAAM,MAAM,KAAK,KAAK;AAEtB,SAAQ,IACN,KAAK,MAAM,OAAO,GAAG,CAAC,GAAG,QAAQ,OAAO,GAAG,CAAC,GAAG,YAAY,OAAO,GAAG,CAAC,GAAG,gBAAgB,OAAO,GAAG,CAAC,SACrG;AACD,SAAQ,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,GAAG,IAAI,OAAO,GAAG,GAAG;AAExG,MAAK,MAAM,OAAO,KAAK,MAAM,GAAG,MAAM,EAAE,IAAI,cAAc,EAAE,IAAI,CAAC,EAAE;EAIjE,MAAM,OAHc,mBAChB,IAAI,OAAO,iBAAiB,CAAC,KAAK,IAAI,IAAI,GAC1C,SACsB,iBAAiB;EAC3C,MAAM,MAAM,IAAI,WACZ,GAAG,KAAK,OAAO,MAAM,IAAI,KAAK,IAAI,SAAS,CAAC,SAAS,IAAI,KAAQ,CAAC,SAClE;EACJ,MAAM,SAAS,IAAI,uBAAuB;AAC1C,UAAQ,IACN,IAAI,IAAI,MAAM,KAAK,OAAO,GAAG,CAAC,GAAG,IAAI,UAAU,OAAO,GAAG,CAAC,GAAG,OAAO,IAAI,cAAc,CAAC,OAAO,GAAG,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,GAAG,SACxH;;AAEH,SAAQ,IAAI,GAAG"}
1
+ {"version":3,"file":"env-list-DYCprcLb.mjs","names":[],"sources":["../src/cli/commands/env-list.ts"],"sourcesContent":["import { loadConfig } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { listEnvRows, type EnvRowInfo } from \"../../core/state/tamerStateDb.js\";\n\nexport async function runEnvList(options: {\n configPath?: string;\n json?: boolean;\n}): Promise<void> {\n const config = await loadConfig(options.configPath);\n const accountId =\n config.account_id ?? cloudflareAccountIdFromEnv();\n if (!accountId) {\n throw new Error(\n \"account_id required in config or CLOUDFLARE_ACCOUNT_ID env var\",\n );\n }\n\n const api = new CFApiClient(accountId);\n const envs = await listEnvRows(api);\n\n if (options.json) {\n console.log(JSON.stringify(envs, null, 2));\n return;\n }\n\n if (envs.length === 0) {\n console.log(\"No environments found in state.\");\n return;\n }\n\n const ephemeralPattern = config.tenant.ephemeralEnvPattern;\n const now = Date.now();\n\n console.log(\n `\\n${\"Env\".padEnd(20)} ${\"Stack\".padEnd(12)} ${\"Resources\".padEnd(10)} ${\"Last Activity\".padEnd(26)} Status`,\n );\n console.log(`${\"─\".repeat(20)} ${\"─\".repeat(12)} ${\"─\".repeat(10)} ${\"─\".repeat(26)} ${\"─\".repeat(10)}`);\n\n for (const env of envs.sort((a, b) => a.env.localeCompare(b.env))) {\n const isEphemeral = ephemeralPattern\n ? new RegExp(ephemeralPattern).test(env.env)\n : false;\n const tag = isEphemeral ? \" (ephemeral)\" : \"\";\n const age = env.syncedAt\n ? `${Math.round((now - new Date(env.syncedAt).getTime()) / 3600000)}h ago`\n : \"unknown\";\n const status = env.lastOperationStatus ?? \"\";\n console.log(\n `${(env.env + tag).padEnd(20)} ${env.stackName.padEnd(12)} ${String(env.resourceCount).padEnd(10)} ${age.padEnd(26)} ${status}`,\n );\n }\n console.log(\"\");\n}\n"],"mappings":";;;AAKA,eAAsB,WAAW,SAGf;CAChB,MAAM,SAAS,MAAM,WAAW,QAAQ,WAAW;CACnD,MAAM,YACJ,OAAO,cAAc,4BAA4B;AACnD,KAAI,CAAC,UACH,OAAM,IAAI,MACR,iEACD;CAIH,MAAM,OAAO,MAAM,YADP,IAAI,YAAY,UAAU,CACH;AAEnC,KAAI,QAAQ,MAAM;AAChB,UAAQ,IAAI,KAAK,UAAU,MAAM,MAAM,EAAE,CAAC;AAC1C;;AAGF,KAAI,KAAK,WAAW,GAAG;AACrB,UAAQ,IAAI,kCAAkC;AAC9C;;CAGF,MAAM,mBAAmB,OAAO,OAAO;CACvC,MAAM,MAAM,KAAK,KAAK;AAEtB,SAAQ,IACN,KAAK,MAAM,OAAO,GAAG,CAAC,GAAG,QAAQ,OAAO,GAAG,CAAC,GAAG,YAAY,OAAO,GAAG,CAAC,GAAG,gBAAgB,OAAO,GAAG,CAAC,SACrG;AACD,SAAQ,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,GAAG,IAAI,OAAO,GAAG,GAAG;AAExG,MAAK,MAAM,OAAO,KAAK,MAAM,GAAG,MAAM,EAAE,IAAI,cAAc,EAAE,IAAI,CAAC,EAAE;EAIjE,MAAM,OAHc,mBAChB,IAAI,OAAO,iBAAiB,CAAC,KAAK,IAAI,IAAI,GAC1C,SACsB,iBAAiB;EAC3C,MAAM,MAAM,IAAI,WACZ,GAAG,KAAK,OAAO,MAAM,IAAI,KAAK,IAAI,SAAS,CAAC,SAAS,IAAI,KAAQ,CAAC,SAClE;EACJ,MAAM,SAAS,IAAI,uBAAuB;AAC1C,UAAQ,IACN,IAAI,IAAI,MAAM,KAAK,OAAO,GAAG,CAAC,GAAG,IAAI,UAAU,OAAO,GAAG,CAAC,GAAG,OAAO,IAAI,cAAc,CAAC,OAAO,GAAG,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,GAAG,SACxH;;AAEH,SAAQ,IAAI,GAAG"}
@@ -1,4 +1,4 @@
1
- import { H as loadConfig, L as CFApiClient, R as cloudflareAccountIdFromEnv, h as StateManager, w as stackNameForConfig } from "./tamer.mjs";
1
+ import { R as CFApiClient, U as loadConfig, h as StateManager, w as stackNameForConfig, z as cloudflareAccountIdFromEnv } from "./tamer.mjs";
2
2
 
3
3
  //#region src/cli/commands/events.ts
4
4
  function formatDuration(startedAt, completedAt) {
@@ -63,4 +63,4 @@ async function runEvents(options) {
63
63
 
64
64
  //#endregion
65
65
  export { runEvents };
66
- //# sourceMappingURL=events-C7wAGJae.mjs.map
66
+ //# sourceMappingURL=events-CnWvxyX_.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"events-C7wAGJae.mjs","names":[],"sources":["../src/cli/commands/events.ts"],"sourcesContent":["import { loadConfig } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport type { CfiOperationRecord } from \"../../types.js\";\n\nfunction formatDuration(\n startedAt: string,\n completedAt: string | undefined,\n): string | undefined {\n if (!completedAt) return undefined;\n const a = Date.parse(startedAt);\n const b = Date.parse(completedAt);\n if (!Number.isFinite(a) || !Number.isFinite(b) || b < a) return undefined;\n const ms = b - a;\n if (ms < 1000) return `${ms}ms`;\n return `${(ms / 1000).toFixed(1)}s`;\n}\n\nfunction printOp(label: string, op: CfiOperationRecord): void {\n const dur = formatDuration(op.startedAt, op.completedAt);\n console.log(` [${label}] ${op.command}`);\n console.log(` started: ${op.startedAt}`);\n if (op.completedAt) {\n console.log(` completed: ${op.completedAt}`);\n }\n if (dur) console.log(` duration: ${dur}`);\n if (op.detail) console.log(` detail: ${op.detail}`);\n if (op.errorMessage) console.log(` error: ${op.errorMessage}`);\n console.log();\n}\n\n/**\n * Read-only: print recent completed stack operations from D1 state\n * (`operationHistory`) plus any in-progress marker (`lastOperation`).\n */\nexport async function runEvents(options: {\n env?: string;\n configPath?: string;\n limit?: number;\n json?: boolean;\n}): Promise<void> {\n const env = options.env ?? \"local\";\n const config = await loadConfig(options.configPath, { env });\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 const stackName = stackNameForConfig(config);\n const api = new CFApiClient(accountId);\n const state = new StateManager(config.tenant.id, env, stackName);\n await state.hydrate(api);\n\n const last = state.getLastOperation();\n const inProgress =\n last?.status === \"in_progress\" ? last : undefined;\n let history = state.getOperationHistory();\n if (options.limit !== undefined) {\n history = history.slice(0, options.limit);\n }\n\n if (options.json) {\n const out = {\n stackName,\n env,\n tenantId: config.tenant.id,\n inProgress: inProgress ?? null,\n history,\n };\n console.log(JSON.stringify(out, null, 2));\n return;\n }\n\n console.log(\n `\\nStack events — ${config.tenant.name} (${config.tenant.slug}) — stack \"${stackName}\" env ${env}\\n`,\n );\n\n if (inProgress) {\n console.log(\"In progress:\\n\");\n printOp(\"in_progress\", inProgress);\n }\n\n if (history.length === 0) {\n console.log(\n inProgress\n ? \"(no completed operations in history yet)\\n\"\n : \"No completed operations recorded in state yet.\\n\",\n );\n return;\n }\n\n console.log(\"Recent completed operations (newest first):\\n\");\n for (const op of history) {\n printOp(op.status, op);\n }\n}\n"],"mappings":";;;AAOA,SAAS,eACP,WACA,aACoB;AACpB,KAAI,CAAC,YAAa,QAAO;CACzB,MAAM,IAAI,KAAK,MAAM,UAAU;CAC/B,MAAM,IAAI,KAAK,MAAM,YAAY;AACjC,KAAI,CAAC,OAAO,SAAS,EAAE,IAAI,CAAC,OAAO,SAAS,EAAE,IAAI,IAAI,EAAG,QAAO;CAChE,MAAM,KAAK,IAAI;AACf,KAAI,KAAK,IAAM,QAAO,GAAG,GAAG;AAC5B,QAAO,IAAI,KAAK,KAAM,QAAQ,EAAE,CAAC;;AAGnC,SAAS,QAAQ,OAAe,IAA8B;CAC5D,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,YAAY;AACxD,SAAQ,IAAI,MAAM,MAAM,IAAI,GAAG,UAAU;AACzC,SAAQ,IAAI,kBAAkB,GAAG,YAAY;AAC7C,KAAI,GAAG,YACL,SAAQ,IAAI,kBAAkB,GAAG,cAAc;AAEjD,KAAI,IAAK,SAAQ,IAAI,kBAAkB,MAAM;AAC7C,KAAI,GAAG,OAAQ,SAAQ,IAAI,kBAAkB,GAAG,SAAS;AACzD,KAAI,GAAG,aAAc,SAAQ,IAAI,kBAAkB,GAAG,eAAe;AACrE,SAAQ,KAAK;;;;;;AAOf,eAAsB,UAAU,SAKd;CAChB,MAAM,MAAM,QAAQ,OAAO;CAC3B,MAAM,SAAS,MAAM,WAAW,QAAQ,YAAY,EAAE,KAAK,CAAC;CAC5D,MAAM,YACJ,OAAO,cAAc,4BAA4B;AACnD,KAAI,CAAC,UACH,OAAM,IAAI,MACR,iEACD;CAEH,MAAM,YAAY,mBAAmB,OAAO;CAC5C,MAAM,MAAM,IAAI,YAAY,UAAU;CACtC,MAAM,QAAQ,IAAI,aAAa,OAAO,OAAO,IAAI,KAAK,UAAU;AAChE,OAAM,MAAM,QAAQ,IAAI;CAExB,MAAM,OAAO,MAAM,kBAAkB;CACrC,MAAM,aACJ,MAAM,WAAW,gBAAgB,OAAO;CAC1C,IAAI,UAAU,MAAM,qBAAqB;AACzC,KAAI,QAAQ,UAAU,OACpB,WAAU,QAAQ,MAAM,GAAG,QAAQ,MAAM;AAG3C,KAAI,QAAQ,MAAM;EAChB,MAAM,MAAM;GACV;GACA;GACA,UAAU,OAAO,OAAO;GACxB,YAAY,cAAc;GAC1B;GACD;AACD,UAAQ,IAAI,KAAK,UAAU,KAAK,MAAM,EAAE,CAAC;AACzC;;AAGF,SAAQ,IACN,oBAAoB,OAAO,OAAO,KAAK,IAAI,OAAO,OAAO,KAAK,aAAa,UAAU,QAAQ,IAAI,IAClG;AAED,KAAI,YAAY;AACd,UAAQ,IAAI,iBAAiB;AAC7B,UAAQ,eAAe,WAAW;;AAGpC,KAAI,QAAQ,WAAW,GAAG;AACxB,UAAQ,IACN,aACI,+CACA,mDACL;AACD;;AAGF,SAAQ,IAAI,gDAAgD;AAC5D,MAAK,MAAM,MAAM,QACf,SAAQ,GAAG,QAAQ,GAAG"}
1
+ {"version":3,"file":"events-CnWvxyX_.mjs","names":[],"sources":["../src/cli/commands/events.ts"],"sourcesContent":["import { loadConfig } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport type { CfiOperationRecord } from \"../../types.js\";\n\nfunction formatDuration(\n startedAt: string,\n completedAt: string | undefined,\n): string | undefined {\n if (!completedAt) return undefined;\n const a = Date.parse(startedAt);\n const b = Date.parse(completedAt);\n if (!Number.isFinite(a) || !Number.isFinite(b) || b < a) return undefined;\n const ms = b - a;\n if (ms < 1000) return `${ms}ms`;\n return `${(ms / 1000).toFixed(1)}s`;\n}\n\nfunction printOp(label: string, op: CfiOperationRecord): void {\n const dur = formatDuration(op.startedAt, op.completedAt);\n console.log(` [${label}] ${op.command}`);\n console.log(` started: ${op.startedAt}`);\n if (op.completedAt) {\n console.log(` completed: ${op.completedAt}`);\n }\n if (dur) console.log(` duration: ${dur}`);\n if (op.detail) console.log(` detail: ${op.detail}`);\n if (op.errorMessage) console.log(` error: ${op.errorMessage}`);\n console.log();\n}\n\n/**\n * Read-only: print recent completed stack operations from D1 state\n * (`operationHistory`) plus any in-progress marker (`lastOperation`).\n */\nexport async function runEvents(options: {\n env?: string;\n configPath?: string;\n limit?: number;\n json?: boolean;\n}): Promise<void> {\n const env = options.env ?? \"local\";\n const config = await loadConfig(options.configPath, { env });\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 const stackName = stackNameForConfig(config);\n const api = new CFApiClient(accountId);\n const state = new StateManager(config.tenant.id, env, stackName);\n await state.hydrate(api);\n\n const last = state.getLastOperation();\n const inProgress =\n last?.status === \"in_progress\" ? last : undefined;\n let history = state.getOperationHistory();\n if (options.limit !== undefined) {\n history = history.slice(0, options.limit);\n }\n\n if (options.json) {\n const out = {\n stackName,\n env,\n tenantId: config.tenant.id,\n inProgress: inProgress ?? null,\n history,\n };\n console.log(JSON.stringify(out, null, 2));\n return;\n }\n\n console.log(\n `\\nStack events — ${config.tenant.name} (${config.tenant.slug}) — stack \"${stackName}\" env ${env}\\n`,\n );\n\n if (inProgress) {\n console.log(\"In progress:\\n\");\n printOp(\"in_progress\", inProgress);\n }\n\n if (history.length === 0) {\n console.log(\n inProgress\n ? \"(no completed operations in history yet)\\n\"\n : \"No completed operations recorded in state yet.\\n\",\n );\n return;\n }\n\n console.log(\"Recent completed operations (newest first):\\n\");\n for (const op of history) {\n printOp(op.status, op);\n }\n}\n"],"mappings":";;;AAOA,SAAS,eACP,WACA,aACoB;AACpB,KAAI,CAAC,YAAa,QAAO;CACzB,MAAM,IAAI,KAAK,MAAM,UAAU;CAC/B,MAAM,IAAI,KAAK,MAAM,YAAY;AACjC,KAAI,CAAC,OAAO,SAAS,EAAE,IAAI,CAAC,OAAO,SAAS,EAAE,IAAI,IAAI,EAAG,QAAO;CAChE,MAAM,KAAK,IAAI;AACf,KAAI,KAAK,IAAM,QAAO,GAAG,GAAG;AAC5B,QAAO,IAAI,KAAK,KAAM,QAAQ,EAAE,CAAC;;AAGnC,SAAS,QAAQ,OAAe,IAA8B;CAC5D,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,YAAY;AACxD,SAAQ,IAAI,MAAM,MAAM,IAAI,GAAG,UAAU;AACzC,SAAQ,IAAI,kBAAkB,GAAG,YAAY;AAC7C,KAAI,GAAG,YACL,SAAQ,IAAI,kBAAkB,GAAG,cAAc;AAEjD,KAAI,IAAK,SAAQ,IAAI,kBAAkB,MAAM;AAC7C,KAAI,GAAG,OAAQ,SAAQ,IAAI,kBAAkB,GAAG,SAAS;AACzD,KAAI,GAAG,aAAc,SAAQ,IAAI,kBAAkB,GAAG,eAAe;AACrE,SAAQ,KAAK;;;;;;AAOf,eAAsB,UAAU,SAKd;CAChB,MAAM,MAAM,QAAQ,OAAO;CAC3B,MAAM,SAAS,MAAM,WAAW,QAAQ,YAAY,EAAE,KAAK,CAAC;CAC5D,MAAM,YACJ,OAAO,cAAc,4BAA4B;AACnD,KAAI,CAAC,UACH,OAAM,IAAI,MACR,iEACD;CAEH,MAAM,YAAY,mBAAmB,OAAO;CAC5C,MAAM,MAAM,IAAI,YAAY,UAAU;CACtC,MAAM,QAAQ,IAAI,aAAa,OAAO,OAAO,IAAI,KAAK,UAAU;AAChE,OAAM,MAAM,QAAQ,IAAI;CAExB,MAAM,OAAO,MAAM,kBAAkB;CACrC,MAAM,aACJ,MAAM,WAAW,gBAAgB,OAAO;CAC1C,IAAI,UAAU,MAAM,qBAAqB;AACzC,KAAI,QAAQ,UAAU,OACpB,WAAU,QAAQ,MAAM,GAAG,QAAQ,MAAM;AAG3C,KAAI,QAAQ,MAAM;EAChB,MAAM,MAAM;GACV;GACA;GACA,UAAU,OAAO,OAAO;GACxB,YAAY,cAAc;GAC1B;GACD;AACD,UAAQ,IAAI,KAAK,UAAU,KAAK,MAAM,EAAE,CAAC;AACzC;;AAGF,SAAQ,IACN,oBAAoB,OAAO,OAAO,KAAK,IAAI,OAAO,OAAO,KAAK,aAAa,UAAU,QAAQ,IAAI,IAClG;AAED,KAAI,YAAY;AACd,UAAQ,IAAI,iBAAiB;AAC7B,UAAQ,eAAe,WAAW;;AAGpC,KAAI,QAAQ,WAAW,GAAG;AACxB,UAAQ,IACN,aACI,+CACA,mDACL;AACD;;AAGF,SAAQ,IAAI,gDAAgD;AAC5D,MAAK,MAAM,MAAM,QACf,SAAQ,GAAG,QAAQ,GAAG"}
@@ -1,4 +1,4 @@
1
- import { n as resourceModules, r as secretsStoreDeriveName } from "./registry-BrOxbA2i.mjs";
1
+ import { n as resourceModules, r as secretsStoreDeriveName } from "./registry-X9dlQxG3.mjs";
2
2
  import { join } from "path";
3
3
  import { writeFileSync } from "fs";
4
4
 
@@ -75,4 +75,4 @@ function writeWranglerJson(workerDir, config, outFile = "wrangler.json") {
75
75
 
76
76
  //#endregion
77
77
  export { writeWranglerJson as n, generateWranglerConfig as t };
78
- //# sourceMappingURL=generator-MX8MAHd9.mjs.map
78
+ //# sourceMappingURL=generator-DAU5K77L.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"generator-MX8MAHd9.mjs","names":["out: GeneratedSecretsStoreSecretBinding[]","config: WranglerConfig"],"sources":["../src/features/secrets-store/secrets-store.generate.ts","../src/core/wrangler/generator.ts"],"sourcesContent":["import type { NamingEngine } from \"../../core/naming/NamingEngine.js\";\nimport type {\n StateEntry,\n SecretsStoreResourceConfig,\n SecretsStoreSecretBinding,\n SecretsStoreStateEntry,\n} from \"../../types.js\";\nimport { secretsStoreDeriveName } from \"./secrets-store.naming.js\";\n\nexport interface SecretsStoreStateLike {\n get(key: string): StateEntry | undefined;\n getAll(): Record<string, StateEntry>;\n}\n\n/**\n * One row of wrangler's `secrets_store_secrets[]` array. Resolved from a\n * worker's declared `secretsStoreSecrets[]` by looking up the\n * Tamer-managed store's `store_id` in state. The named secret itself must\n * already exist inside the store (created out-of-band via wrangler\n * `secrets-store secret create`); Tamer never reads or writes secret\n * values.\n */\nexport interface GeneratedSecretsStoreSecretBinding {\n binding: string;\n store_id: string;\n secret_name: string;\n}\n\n/**\n * Resolve `secretsStoreSecrets[]` worker bindings into wrangler-shape\n * `secrets_store_secrets[]` entries by looking up the referenced\n * Tamer-managed store's `store_id` in state. Throws with an actionable\n * message if a referenced store has never been applied — the binding\n * would otherwise be deployed against a non-existent store.\n */\nexport function secretsStoreSecretsGenerate(\n bindings: SecretsStoreSecretBinding[],\n env: string,\n state: SecretsStoreStateLike,\n naming: NamingEngine,\n secretsStores?: SecretsStoreResourceConfig[],\n): GeneratedSecretsStoreSecretBinding[] {\n const out: GeneratedSecretsStoreSecretBinding[] = [];\n for (const b of bindings) {\n const storeConfig = secretsStores?.find((s) => s.logicalName === b.store);\n const derivedName = storeConfig\n ? secretsStoreDeriveName(storeConfig, env, naming)\n : naming.secretsStoreName(b.store, env);\n const entry = state.get(`secrets_store:${derivedName}`) as\n | SecretsStoreStateEntry\n | undefined;\n if (!entry || entry.type !== \"secrets_store\") {\n throw new Error(\n `Secrets Store binding \"${b.binding}\" references store \"${b.store}\" — ` +\n `not in state. Declare it under \\`resources.secretsStores\\` and run ` +\n `'tamer apply --env ${env}' first.`,\n );\n }\n out.push({\n binding: b.binding,\n store_id: entry.cfId,\n secret_name: b.secretName,\n });\n }\n return out;\n}\n","import { writeFileSync } from \"fs\";\nimport { join } from \"path\";\nimport type { WranglerConfig } from \"../../generated/wrangler-types.js\";\nimport type { ResolvedWorkerConfig } from \"../config/resolver.js\";\nimport type { StateManager } from \"../state/StateManager.js\";\nimport type { NamingEngine } from \"../naming/NamingEngine.js\";\nimport { resourceModules } from \"../registry/registry.js\";\nimport { secretsStoreSecretsGenerate } from \"../../features/secrets-store/secrets-store.generate.js\";\n\nexport class CfiError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CfiError\";\n }\n}\n\n/**\n * Build the worker's `wrangler.json` by asking every registered resource\n * module for its bindings fragment, then merging on top of the\n * passthrough config (`resolved.wranglerConfig`). Adding a new resource\n * kind requires zero edits here — register the module, done.\n */\nexport function generateWranglerConfig(\n resolved: ResolvedWorkerConfig,\n state: StateManager,\n naming: NamingEngine,\n): WranglerConfig {\n let config: WranglerConfig = {\n ...resolved.wranglerConfig,\n $schema: \"node_modules/wrangler/config-schema.json\",\n };\n\n const worker = {\n workerKey: resolved.workerKey,\n deployedName: resolved.workerName,\n };\n for (const mod of resourceModules) {\n const resources = mod.pickResources(resolved.resources);\n const fragment = mod.generate({\n resources,\n env: resolved.env,\n state,\n naming,\n passthrough: resolved.wranglerConfig,\n worker,\n });\n config = { ...config, ...fragment };\n }\n\n const secretBindings = resolved.resources?.secretsStoreSecrets ?? [];\n if (secretBindings.length > 0) {\n const passthroughSecrets =\n resolved.wranglerConfig.secrets_store_secrets ?? [];\n const generated = secretsStoreSecretsGenerate(\n secretBindings,\n resolved.env,\n state,\n naming,\n resolved.resources?.secretsStores,\n );\n config = {\n ...config,\n secrets_store_secrets: [...passthroughSecrets, ...generated],\n };\n }\n\n return config;\n}\n\nexport function writeWranglerJson(\n workerDir: string,\n config: WranglerConfig,\n outFile: string = \"wrangler.json\",\n): void {\n const path = join(workerDir, outFile);\n writeFileSync(path, JSON.stringify(config, null, 2), \"utf-8\");\n}\n"],"mappings":";;;;;;;;;;;;AAmCA,SAAgB,4BACd,UACA,KACA,OACA,QACA,eACsC;CACtC,MAAMA,MAA4C,EAAE;AACpD,MAAK,MAAM,KAAK,UAAU;EACxB,MAAM,cAAc,eAAe,MAAM,MAAM,EAAE,gBAAgB,EAAE,MAAM;EACzE,MAAM,cAAc,cAChB,uBAAuB,aAAa,KAAK,OAAO,GAChD,OAAO,iBAAiB,EAAE,OAAO,IAAI;EACzC,MAAM,QAAQ,MAAM,IAAI,iBAAiB,cAAc;AAGvD,MAAI,CAAC,SAAS,MAAM,SAAS,gBAC3B,OAAM,IAAI,MACR,0BAA0B,EAAE,QAAQ,sBAAsB,EAAE,MAAM,4FAE1C,IAAI,UAC7B;AAEH,MAAI,KAAK;GACP,SAAS,EAAE;GACX,UAAU,MAAM;GAChB,aAAa,EAAE;GAChB,CAAC;;AAEJ,QAAO;;;;;;;;;;;AC1CT,SAAgB,uBACd,UACA,OACA,QACgB;CAChB,IAAIC,SAAyB;EAC3B,GAAG,SAAS;EACZ,SAAS;EACV;CAED,MAAM,SAAS;EACb,WAAW,SAAS;EACpB,cAAc,SAAS;EACxB;AACD,MAAK,MAAM,OAAO,iBAAiB;EACjC,MAAM,YAAY,IAAI,cAAc,SAAS,UAAU;EACvD,MAAM,WAAW,IAAI,SAAS;GAC5B;GACA,KAAK,SAAS;GACd;GACA;GACA,aAAa,SAAS;GACtB;GACD,CAAC;AACF,WAAS;GAAE,GAAG;GAAQ,GAAG;GAAU;;CAGrC,MAAM,iBAAiB,SAAS,WAAW,uBAAuB,EAAE;AACpE,KAAI,eAAe,SAAS,GAAG;EAC7B,MAAM,qBACJ,SAAS,eAAe,yBAAyB,EAAE;EACrD,MAAM,YAAY,4BAChB,gBACA,SAAS,KACT,OACA,QACA,SAAS,WAAW,cACrB;AACD,WAAS;GACP,GAAG;GACH,uBAAuB,CAAC,GAAG,oBAAoB,GAAG,UAAU;GAC7D;;AAGH,QAAO;;AAGT,SAAgB,kBACd,WACA,QACA,UAAkB,iBACZ;AAEN,eADa,KAAK,WAAW,QAAQ,EACjB,KAAK,UAAU,QAAQ,MAAM,EAAE,EAAE,QAAQ"}
1
+ {"version":3,"file":"generator-DAU5K77L.mjs","names":["out: GeneratedSecretsStoreSecretBinding[]","config: WranglerConfig"],"sources":["../src/features/secrets-store/secrets-store.generate.ts","../src/core/wrangler/generator.ts"],"sourcesContent":["import type { NamingEngine } from \"../../core/naming/NamingEngine.js\";\nimport type {\n StateEntry,\n SecretsStoreResourceConfig,\n SecretsStoreSecretBinding,\n SecretsStoreStateEntry,\n} from \"../../types.js\";\nimport { secretsStoreDeriveName } from \"./secrets-store.naming.js\";\n\nexport interface SecretsStoreStateLike {\n get(key: string): StateEntry | undefined;\n getAll(): Record<string, StateEntry>;\n}\n\n/**\n * One row of wrangler's `secrets_store_secrets[]` array. Resolved from a\n * worker's declared `secretsStoreSecrets[]` by looking up the\n * Tamer-managed store's `store_id` in state. The named secret itself must\n * already exist inside the store (created out-of-band via wrangler\n * `secrets-store secret create`); Tamer never reads or writes secret\n * values.\n */\nexport interface GeneratedSecretsStoreSecretBinding {\n binding: string;\n store_id: string;\n secret_name: string;\n}\n\n/**\n * Resolve `secretsStoreSecrets[]` worker bindings into wrangler-shape\n * `secrets_store_secrets[]` entries by looking up the referenced\n * Tamer-managed store's `store_id` in state. Throws with an actionable\n * message if a referenced store has never been applied — the binding\n * would otherwise be deployed against a non-existent store.\n */\nexport function secretsStoreSecretsGenerate(\n bindings: SecretsStoreSecretBinding[],\n env: string,\n state: SecretsStoreStateLike,\n naming: NamingEngine,\n secretsStores?: SecretsStoreResourceConfig[],\n): GeneratedSecretsStoreSecretBinding[] {\n const out: GeneratedSecretsStoreSecretBinding[] = [];\n for (const b of bindings) {\n const storeConfig = secretsStores?.find((s) => s.logicalName === b.store);\n const derivedName = storeConfig\n ? secretsStoreDeriveName(storeConfig, env, naming)\n : naming.secretsStoreName(b.store, env);\n const entry = state.get(`secrets_store:${derivedName}`) as\n | SecretsStoreStateEntry\n | undefined;\n if (!entry || entry.type !== \"secrets_store\") {\n throw new Error(\n `Secrets Store binding \"${b.binding}\" references store \"${b.store}\" — ` +\n `not in state. Declare it under \\`resources.secretsStores\\` and run ` +\n `'tamer apply --env ${env}' first.`,\n );\n }\n out.push({\n binding: b.binding,\n store_id: entry.cfId,\n secret_name: b.secretName,\n });\n }\n return out;\n}\n","import { writeFileSync } from \"fs\";\nimport { join } from \"path\";\nimport type { WranglerConfig } from \"../../generated/wrangler-types.js\";\nimport type { ResolvedWorkerConfig } from \"../config/resolver.js\";\nimport type { StateManager } from \"../state/StateManager.js\";\nimport type { NamingEngine } from \"../naming/NamingEngine.js\";\nimport { resourceModules } from \"../registry/registry.js\";\nimport { secretsStoreSecretsGenerate } from \"../../features/secrets-store/secrets-store.generate.js\";\n\nexport class CfiError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CfiError\";\n }\n}\n\n/**\n * Build the worker's `wrangler.json` by asking every registered resource\n * module for its bindings fragment, then merging on top of the\n * passthrough config (`resolved.wranglerConfig`). Adding a new resource\n * kind requires zero edits here — register the module, done.\n */\nexport function generateWranglerConfig(\n resolved: ResolvedWorkerConfig,\n state: StateManager,\n naming: NamingEngine,\n): WranglerConfig {\n let config: WranglerConfig = {\n ...resolved.wranglerConfig,\n $schema: \"node_modules/wrangler/config-schema.json\",\n };\n\n const worker = {\n workerKey: resolved.workerKey,\n deployedName: resolved.workerName,\n };\n for (const mod of resourceModules) {\n const resources = mod.pickResources(resolved.resources);\n const fragment = mod.generate({\n resources,\n env: resolved.env,\n state,\n naming,\n passthrough: resolved.wranglerConfig,\n worker,\n });\n config = { ...config, ...fragment };\n }\n\n const secretBindings = resolved.resources?.secretsStoreSecrets ?? [];\n if (secretBindings.length > 0) {\n const passthroughSecrets =\n resolved.wranglerConfig.secrets_store_secrets ?? [];\n const generated = secretsStoreSecretsGenerate(\n secretBindings,\n resolved.env,\n state,\n naming,\n resolved.resources?.secretsStores,\n );\n config = {\n ...config,\n secrets_store_secrets: [...passthroughSecrets, ...generated],\n };\n }\n\n return config;\n}\n\nexport function writeWranglerJson(\n workerDir: string,\n config: WranglerConfig,\n outFile: string = \"wrangler.json\",\n): void {\n const path = join(workerDir, outFile);\n writeFileSync(path, JSON.stringify(config, null, 2), \"utf-8\");\n}\n"],"mappings":";;;;;;;;;;;;AAmCA,SAAgB,4BACd,UACA,KACA,OACA,QACA,eACsC;CACtC,MAAMA,MAA4C,EAAE;AACpD,MAAK,MAAM,KAAK,UAAU;EACxB,MAAM,cAAc,eAAe,MAAM,MAAM,EAAE,gBAAgB,EAAE,MAAM;EACzE,MAAM,cAAc,cAChB,uBAAuB,aAAa,KAAK,OAAO,GAChD,OAAO,iBAAiB,EAAE,OAAO,IAAI;EACzC,MAAM,QAAQ,MAAM,IAAI,iBAAiB,cAAc;AAGvD,MAAI,CAAC,SAAS,MAAM,SAAS,gBAC3B,OAAM,IAAI,MACR,0BAA0B,EAAE,QAAQ,sBAAsB,EAAE,MAAM,4FAE1C,IAAI,UAC7B;AAEH,MAAI,KAAK;GACP,SAAS,EAAE;GACX,UAAU,MAAM;GAChB,aAAa,EAAE;GAChB,CAAC;;AAEJ,QAAO;;;;;;;;;;;AC1CT,SAAgB,uBACd,UACA,OACA,QACgB;CAChB,IAAIC,SAAyB;EAC3B,GAAG,SAAS;EACZ,SAAS;EACV;CAED,MAAM,SAAS;EACb,WAAW,SAAS;EACpB,cAAc,SAAS;EACxB;AACD,MAAK,MAAM,OAAO,iBAAiB;EACjC,MAAM,YAAY,IAAI,cAAc,SAAS,UAAU;EACvD,MAAM,WAAW,IAAI,SAAS;GAC5B;GACA,KAAK,SAAS;GACd;GACA;GACA,aAAa,SAAS;GACtB;GACD,CAAC;AACF,WAAS;GAAE,GAAG;GAAQ,GAAG;GAAU;;CAGrC,MAAM,iBAAiB,SAAS,WAAW,uBAAuB,EAAE;AACpE,KAAI,eAAe,SAAS,GAAG;EAC7B,MAAM,qBACJ,SAAS,eAAe,yBAAyB,EAAE;EACrD,MAAM,YAAY,4BAChB,gBACA,SAAS,KACT,OACA,QACA,SAAS,WAAW,cACrB;AACD,WAAS;GACP,GAAG;GACH,uBAAuB,CAAC,GAAG,oBAAoB,GAAG,UAAU;GAC7D;;AAGH,QAAO;;AAGT,SAAgB,kBACd,WACA,QACA,UAAkB,iBACZ;AAEN,eADa,KAAK,WAAW,QAAQ,EACjB,KAAK,UAAU,QAAQ,MAAM,EAAE,EAAE,QAAQ"}
@@ -1,7 +1,7 @@
1
- import { B as getConfigBaseDir, H as loadConfig, L as CFApiClient, 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 { n as resourceModules, t as getResourceModule } from "./registry-BrOxbA2i.mjs";
3
- import "./r2S3EmptyBucket-B9_pHfvB.mjs";
4
- import { n as workerRouteStateKey, t as findZoneIdByName } from "./zoneResolver-D9bz6-0l.mjs";
1
+ import { H as getWorkers, 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 { n as resourceModules, t as getResourceModule } from "./registry-X9dlQxG3.mjs";
3
+ import "./r2S3EmptyBucket-CXLmOrYF.mjs";
4
+ import { n as workerRouteStateKey, t as findZoneIdByName } from "./zoneResolver-VoxLHM4N.mjs";
5
5
 
6
6
  //#region src/cli/commands/import.ts
7
7
  const REGISTRY_KINDS = new Set(resourceModules.map((m) => m.kind));
@@ -110,7 +110,7 @@ async function importDnsRecord(ctx) {
110
110
  const hit = (await api.zoneDnsRecordListAll(declared.zoneId)).find((r) => r.id === options.cfId);
111
111
  if (!hit) throw new Error(`import dns_record: record id "${options.cfId}" not found in zone "${declared.zoneId}"`);
112
112
  if (hit.type !== declared.type) throw new Error(`import dns_record: cf record type "${hit.type}" does not match declared type "${declared.type}"`);
113
- const { dnsRecordStateKey } = await import("./dns-records.resolve-BBTlY3T5.mjs");
113
+ const { dnsRecordStateKey } = await import("./dns-records.resolve-C2T0m4NG.mjs");
114
114
  const key = dnsRecordStateKey(declared.zoneId, declared.type, declared.name);
115
115
  const existing = state.get(key);
116
116
  state.set(key, {
@@ -161,4 +161,4 @@ async function importWorkerRoute(ctx) {
161
161
 
162
162
  //#endregion
163
163
  export { runImport };
164
- //# sourceMappingURL=import-Bzow4TPf.mjs.map
164
+ //# sourceMappingURL=import-BNbHjR9t.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"import-Bzow4TPf.mjs","names":[],"sources":["../src/cli/commands/import.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 { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { resolveWorkerConfig } from \"../../core/config/resolver.js\";\nimport { fetchStackImports } from \"../../core/imports/fetchStackImports.js\";\nimport { workerRouteStateKey } from \"../../features/worker-route/worker-route.stateKey.js\";\nimport { findZoneIdByName } from \"../../core/routes/zoneResolver.js\";\nimport {\n getResourceModule,\n resourceModules,\n type ResourceKind,\n} from \"../../core/registry/registry.js\";\n\n/** All registered resource kinds plus the non-registry imports. */\nexport type ImportKind =\n | ResourceKind\n | \"dispatch_namespace\"\n | \"dns_record\"\n | \"worker_route\";\n\nexport interface ImportOptions {\n env: string;\n kind: ImportKind;\n /** Logical name from `tamer.config.ts` (or worker key, for `worker_route`). */\n logical: string;\n /** Cloudflare resource id (D1 uuid, KV id, R2 bucket name, or namespace name). */\n cfId?: string;\n /** Required for sharded D1 imports (YYYY-MM-DD). */\n shardDate?: string;\n /** R2 buckets only: creation date (YYYY-MM-DD) used in the binding key. */\n createdDate?: string;\n /** worker_route only: the route id from `/zones/{id}/workers/routes`. */\n routeId?: string;\n /** worker_route only: zone name from `tamerRoutes`. */\n zoneName?: string;\n configPath?: string;\n}\n\nconst REGISTRY_KINDS = new Set<string>(resourceModules.map((m) => m.kind));\n\n/**\n * `tamer import` — register an existing Cloudflare resource into Tamer state\n * by logical name (CloudFormation `import_resources` analogue). For all\n * resource-registry kinds the work is delegated to the module's\n * `importOne` hook; `dispatch_namespace` and `worker_route` remain inline\n * because they aren't registry resources today.\n */\nexport async function runImport(options: ImportOptions): Promise<void> {\n const env = options.env;\n if (env === \"local\") {\n throw new Error(\"import: --env local is not supported (no persisted state)\");\n }\n const config = await loadConfig(options.configPath, { env });\n const baseDir = getConfigBaseDir();\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 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(\"import\", `${options.kind}:${options.logical}`);\n\n const ts = new Date().toISOString();\n\n try {\n if (REGISTRY_KINDS.has(options.kind)) {\n const mod = getResourceModule(options.kind as ResourceKind);\n if (!mod) throw new Error(`unknown import kind \"${options.kind}\"`);\n await mod.importOne({\n options: {\n env,\n kind: options.kind,\n logical: options.logical,\n cfId: options.cfId,\n shardDate: options.shardDate,\n createdDate: options.createdDate,\n routeId: options.routeId,\n zoneName: options.zoneName,\n },\n env,\n api,\n state,\n naming,\n config,\n baseDir,\n ts,\n });\n } else if (options.kind === \"dispatch_namespace\") {\n await importDispatchNamespace({ options, env, api, state, ts });\n } else if (options.kind === \"dns_record\") {\n await importDnsRecord({ options, env, api, state, config, ts });\n } else if (options.kind === \"worker_route\") {\n await importWorkerRoute({\n options,\n env,\n baseDir,\n accountId,\n api,\n naming,\n state,\n config,\n ts,\n });\n } else {\n throw new Error(`unknown import kind \"${options.kind}\"`);\n }\n\n state.finishOperation(\"imported\");\n await state.persist(api);\n console.log(`Imported ${options.kind} \"${options.logical}\" into ${env} state.`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n state.failOperation(msg);\n try {\n await state.persist(api);\n } catch {\n /* swallow secondary persist failure */\n }\n throw err;\n }\n}\n\nasync function importDispatchNamespace(ctx: {\n options: ImportOptions;\n env: string;\n api: CFApiClient;\n state: StateManager;\n ts: string;\n}): Promise<void> {\n const { options, api, state, ts } = ctx;\n const namespaces = await api.dispatchNamespaceListAll();\n const hit = namespaces.find((n) => n.namespace_name === options.cfId);\n if (!hit) {\n throw new Error(\n `import dispatch_namespace: namespace \"${options.cfId}\" not found`,\n );\n }\n const derivedName = hit.namespace_name;\n const key = `dispatch_ns:${derivedName}`;\n const existing = state.get(key);\n state.set(key, {\n type: \"dispatch_namespace\",\n logicalName: options.logical,\n derivedName,\n createdAt:\n existing?.type === \"dispatch_namespace\" ? existing.createdAt : ts,\n updatedAt: ts,\n });\n}\n\nasync function importDnsRecord(ctx: {\n options: ImportOptions;\n env: string;\n api: CFApiClient;\n state: StateManager;\n config: Awaited<ReturnType<typeof loadConfig>>;\n ts: string;\n}): Promise<void> {\n const { options, api, state, config, ts } = ctx;\n if (!options.cfId) {\n throw new Error(\n \"import dns_record: --cf-id <recordId> is required (the record id from `tamer drift` or the Cloudflare dashboard URL)\",\n );\n }\n const declared = (config.dnsRecords ?? []).find(\n (r) => r.logicalName === options.logical,\n );\n if (!declared) {\n throw new Error(\n `import dns_record: no dnsRecords entry with logicalName \"${options.logical}\" in the Tamer project config`,\n );\n }\n const list = await api.zoneDnsRecordListAll(declared.zoneId);\n const hit = list.find((r) => r.id === options.cfId);\n if (!hit) {\n throw new Error(\n `import dns_record: record id \"${options.cfId}\" not found in zone \"${declared.zoneId}\"`,\n );\n }\n if (hit.type !== declared.type) {\n throw new Error(\n `import dns_record: cf record type \"${hit.type}\" does not match declared type \"${declared.type}\"`,\n );\n }\n const { dnsRecordStateKey } = await import(\n \"../../features/dns-records/dns-records.resolve.js\"\n );\n const key = dnsRecordStateKey(declared.zoneId, declared.type, declared.name);\n const existing = state.get(key);\n state.set(key, {\n type: \"dns_record\",\n logicalName: options.logical,\n zoneId: declared.zoneId,\n recordType: declared.type,\n name: hit.name,\n content: hit.content,\n ttl: hit.ttl ?? 1,\n proxied: hit.proxied ?? false,\n priority: hit.priority,\n comment: hit.comment ?? \"\",\n recordId: hit.id,\n createdAt: existing?.type === \"dns_record\" ? existing.createdAt : ts,\n updatedAt: ts,\n });\n}\n\nasync function importWorkerRoute(ctx: {\n options: ImportOptions;\n env: string;\n baseDir: string;\n accountId: string;\n api: CFApiClient;\n naming: ReturnType<typeof namingFromConfig>;\n state: StateManager;\n config: Awaited<ReturnType<typeof loadConfig>>;\n ts: string;\n}): Promise<void> {\n const { options, env, baseDir, accountId, api, naming, state, config, ts } =\n ctx;\n if (!options.routeId) {\n throw new Error(\"import worker_route: --route-id <id> is required\");\n }\n const workers = await getWorkers(config, baseDir);\n const wc = workers.find(([k]) => k === options.logical)?.[1];\n if (!wc) {\n throw new Error(\n `import worker_route: no worker key \"${options.logical}\" in the Tamer project config`,\n );\n }\n const imports = await fetchStackImports(api, config, env);\n const resolved = await resolveWorkerConfig(\n config,\n options.logical,\n wc,\n env,\n baseDir,\n accountId,\n naming,\n state,\n { imports },\n );\n if (resolved.apiManagedRoutes.length === 0) {\n throw new Error(\n `import worker_route: worker \"${options.logical}\" declares no api-managed tamerRoutes for env ${env}`,\n );\n }\n\n const route = options.zoneName\n ? resolved.apiManagedRoutes.find((r) => r.zone_name === options.zoneName)\n : resolved.apiManagedRoutes[0];\n if (!route) {\n throw new Error(\n `import worker_route: no route for zone \"${options.zoneName}\" in resolved config`,\n );\n }\n const zoneId = await findZoneIdByName(api, route.zone_name);\n if (!zoneId) {\n throw new Error(`import worker_route: zone \"${route.zone_name}\" not found`);\n }\n const list = await api.zoneWorkerRoutesList(zoneId);\n const hit = list.find((r) => r.id === options.routeId);\n if (!hit) {\n throw new Error(\n `import worker_route: route id \"${options.routeId}\" not found in zone \"${route.zone_name}\"`,\n );\n }\n if (hit.pattern !== route.pattern) {\n throw new Error(\n `import worker_route: cf pattern \"${hit.pattern}\" does not match resolved \"${route.pattern}\"`,\n );\n }\n const key = workerRouteStateKey(zoneId, options.routeId);\n const existing = state.get(key);\n state.set(key, {\n type: \"worker_route\",\n workerKey: options.logical,\n workerName: resolved.workerName,\n zoneId,\n zoneName: route.zone_name,\n routeId: options.routeId,\n pattern: route.pattern,\n createdAt: existing?.type === \"worker_route\" ? existing.createdAt : ts,\n updatedAt: ts,\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"import-BNbHjR9t.mjs","names":[],"sources":["../src/cli/commands/import.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 { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport { resolveWorkerConfig } from \"../../core/config/resolver.js\";\nimport { fetchStackImports } from \"../../core/imports/fetchStackImports.js\";\nimport { workerRouteStateKey } from \"../../features/worker-route/worker-route.stateKey.js\";\nimport { findZoneIdByName } from \"../../core/routes/zoneResolver.js\";\nimport {\n getResourceModule,\n resourceModules,\n type ResourceKind,\n} from \"../../core/registry/registry.js\";\n\n/** All registered resource kinds plus the non-registry imports. */\nexport type ImportKind =\n | ResourceKind\n | \"dispatch_namespace\"\n | \"dns_record\"\n | \"worker_route\";\n\nexport interface ImportOptions {\n env: string;\n kind: ImportKind;\n /** Logical name from `tamer.config.ts` (or worker key, for `worker_route`). */\n logical: string;\n /** Cloudflare resource id (D1 uuid, KV id, R2 bucket name, or namespace name). */\n cfId?: string;\n /** Required for sharded D1 imports (YYYY-MM-DD). */\n shardDate?: string;\n /** R2 buckets only: creation date (YYYY-MM-DD) used in the binding key. */\n createdDate?: string;\n /** worker_route only: the route id from `/zones/{id}/workers/routes`. */\n routeId?: string;\n /** worker_route only: zone name from `tamerRoutes`. */\n zoneName?: string;\n configPath?: string;\n}\n\nconst REGISTRY_KINDS = new Set<string>(resourceModules.map((m) => m.kind));\n\n/**\n * `tamer import` — register an existing Cloudflare resource into Tamer state\n * by logical name (CloudFormation `import_resources` analogue). For all\n * resource-registry kinds the work is delegated to the module's\n * `importOne` hook; `dispatch_namespace` and `worker_route` remain inline\n * because they aren't registry resources today.\n */\nexport async function runImport(options: ImportOptions): Promise<void> {\n const env = options.env;\n if (env === \"local\") {\n throw new Error(\"import: --env local is not supported (no persisted state)\");\n }\n const config = await loadConfig(options.configPath, { env });\n const baseDir = getConfigBaseDir();\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 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(\"import\", `${options.kind}:${options.logical}`);\n\n const ts = new Date().toISOString();\n\n try {\n if (REGISTRY_KINDS.has(options.kind)) {\n const mod = getResourceModule(options.kind as ResourceKind);\n if (!mod) throw new Error(`unknown import kind \"${options.kind}\"`);\n await mod.importOne({\n options: {\n env,\n kind: options.kind,\n logical: options.logical,\n cfId: options.cfId,\n shardDate: options.shardDate,\n createdDate: options.createdDate,\n routeId: options.routeId,\n zoneName: options.zoneName,\n },\n env,\n api,\n state,\n naming,\n config,\n baseDir,\n ts,\n });\n } else if (options.kind === \"dispatch_namespace\") {\n await importDispatchNamespace({ options, env, api, state, ts });\n } else if (options.kind === \"dns_record\") {\n await importDnsRecord({ options, env, api, state, config, ts });\n } else if (options.kind === \"worker_route\") {\n await importWorkerRoute({\n options,\n env,\n baseDir,\n accountId,\n api,\n naming,\n state,\n config,\n ts,\n });\n } else {\n throw new Error(`unknown import kind \"${options.kind}\"`);\n }\n\n state.finishOperation(\"imported\");\n await state.persist(api);\n console.log(`Imported ${options.kind} \"${options.logical}\" into ${env} state.`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n state.failOperation(msg);\n try {\n await state.persist(api);\n } catch {\n /* swallow secondary persist failure */\n }\n throw err;\n }\n}\n\nasync function importDispatchNamespace(ctx: {\n options: ImportOptions;\n env: string;\n api: CFApiClient;\n state: StateManager;\n ts: string;\n}): Promise<void> {\n const { options, api, state, ts } = ctx;\n const namespaces = await api.dispatchNamespaceListAll();\n const hit = namespaces.find((n) => n.namespace_name === options.cfId);\n if (!hit) {\n throw new Error(\n `import dispatch_namespace: namespace \"${options.cfId}\" not found`,\n );\n }\n const derivedName = hit.namespace_name;\n const key = `dispatch_ns:${derivedName}`;\n const existing = state.get(key);\n state.set(key, {\n type: \"dispatch_namespace\",\n logicalName: options.logical,\n derivedName,\n createdAt:\n existing?.type === \"dispatch_namespace\" ? existing.createdAt : ts,\n updatedAt: ts,\n });\n}\n\nasync function importDnsRecord(ctx: {\n options: ImportOptions;\n env: string;\n api: CFApiClient;\n state: StateManager;\n config: Awaited<ReturnType<typeof loadConfig>>;\n ts: string;\n}): Promise<void> {\n const { options, api, state, config, ts } = ctx;\n if (!options.cfId) {\n throw new Error(\n \"import dns_record: --cf-id <recordId> is required (the record id from `tamer drift` or the Cloudflare dashboard URL)\",\n );\n }\n const declared = (config.dnsRecords ?? []).find(\n (r) => r.logicalName === options.logical,\n );\n if (!declared) {\n throw new Error(\n `import dns_record: no dnsRecords entry with logicalName \"${options.logical}\" in the Tamer project config`,\n );\n }\n const list = await api.zoneDnsRecordListAll(declared.zoneId);\n const hit = list.find((r) => r.id === options.cfId);\n if (!hit) {\n throw new Error(\n `import dns_record: record id \"${options.cfId}\" not found in zone \"${declared.zoneId}\"`,\n );\n }\n if (hit.type !== declared.type) {\n throw new Error(\n `import dns_record: cf record type \"${hit.type}\" does not match declared type \"${declared.type}\"`,\n );\n }\n const { dnsRecordStateKey } = await import(\n \"../../features/dns-records/dns-records.resolve.js\"\n );\n const key = dnsRecordStateKey(declared.zoneId, declared.type, declared.name);\n const existing = state.get(key);\n state.set(key, {\n type: \"dns_record\",\n logicalName: options.logical,\n zoneId: declared.zoneId,\n recordType: declared.type,\n name: hit.name,\n content: hit.content,\n ttl: hit.ttl ?? 1,\n proxied: hit.proxied ?? false,\n priority: hit.priority,\n comment: hit.comment ?? \"\",\n recordId: hit.id,\n createdAt: existing?.type === \"dns_record\" ? existing.createdAt : ts,\n updatedAt: ts,\n });\n}\n\nasync function importWorkerRoute(ctx: {\n options: ImportOptions;\n env: string;\n baseDir: string;\n accountId: string;\n api: CFApiClient;\n naming: ReturnType<typeof namingFromConfig>;\n state: StateManager;\n config: Awaited<ReturnType<typeof loadConfig>>;\n ts: string;\n}): Promise<void> {\n const { options, env, baseDir, accountId, api, naming, state, config, ts } =\n ctx;\n if (!options.routeId) {\n throw new Error(\"import worker_route: --route-id <id> is required\");\n }\n const workers = await getWorkers(config, baseDir);\n const wc = workers.find(([k]) => k === options.logical)?.[1];\n if (!wc) {\n throw new Error(\n `import worker_route: no worker key \"${options.logical}\" in the Tamer project config`,\n );\n }\n const imports = await fetchStackImports(api, config, env);\n const resolved = await resolveWorkerConfig(\n config,\n options.logical,\n wc,\n env,\n baseDir,\n accountId,\n naming,\n state,\n { imports },\n );\n if (resolved.apiManagedRoutes.length === 0) {\n throw new Error(\n `import worker_route: worker \"${options.logical}\" declares no api-managed tamerRoutes for env ${env}`,\n );\n }\n\n const route = options.zoneName\n ? resolved.apiManagedRoutes.find((r) => r.zone_name === options.zoneName)\n : resolved.apiManagedRoutes[0];\n if (!route) {\n throw new Error(\n `import worker_route: no route for zone \"${options.zoneName}\" in resolved config`,\n );\n }\n const zoneId = await findZoneIdByName(api, route.zone_name);\n if (!zoneId) {\n throw new Error(`import worker_route: zone \"${route.zone_name}\" not found`);\n }\n const list = await api.zoneWorkerRoutesList(zoneId);\n const hit = list.find((r) => r.id === options.routeId);\n if (!hit) {\n throw new Error(\n `import worker_route: route id \"${options.routeId}\" not found in zone \"${route.zone_name}\"`,\n );\n }\n if (hit.pattern !== route.pattern) {\n throw new Error(\n `import worker_route: cf pattern \"${hit.pattern}\" does not match resolved \"${route.pattern}\"`,\n );\n }\n const key = workerRouteStateKey(zoneId, options.routeId);\n const existing = state.get(key);\n state.set(key, {\n type: \"worker_route\",\n workerKey: options.logical,\n workerName: resolved.workerName,\n zoneId,\n zoneName: route.zone_name,\n routeId: options.routeId,\n pattern: route.pattern,\n createdAt: existing?.type === \"worker_route\" ? existing.createdAt : ts,\n updatedAt: ts,\n });\n}\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -5720,6 +5720,26 @@ interface TenantMeta {
5720
5720
  * — the config is the source of truth, the flag only trims.
5721
5721
  */
5722
5722
  d1Shards?: string[];
5723
+ /**
5724
+ * Maps each shard role (from `d1Shards[]`) to its binding name and
5725
+ * migration directory. Used by `wfp tenant provision` to resolve D1
5726
+ * bindings on the dispatch script, and by `wfp tenant migrate` to find
5727
+ * the migration source per shard.
5728
+ *
5729
+ * Example:
5730
+ * ```ts
5731
+ * shardBindings: {
5732
+ * system: { binding: "DB_SYSTEM", migrationsDir: "db/system" },
5733
+ * app: { binding: "DB_APP", migrationsDir: "db/app" },
5734
+ * history: { binding: "DB_HISTORY", migrationsDir: "db/history" },
5735
+ * }
5736
+ * ```
5737
+ */
5738
+ shardBindings?: Record<string, {
5739
+ binding: string;
5740
+ migrationsDir: string;
5741
+ migrationsTable?: string;
5742
+ }>;
5723
5743
  /**
5724
5744
  * Envs that require an explicit `--confirm-tenant <workspace>` (or
5725
5745
  * `--force`) before `destroy-tenant` will run. Defaults to
@@ -5746,6 +5766,25 @@ interface TenantMeta {
5746
5766
  * config-load time; an invalid regex fails at parse, not at apply.
5747
5767
  */
5748
5768
  ephemeralEnvPattern?: string;
5769
+ /**
5770
+ * Environment variables injected into the tenant dispatch script's
5771
+ * metadata on `wfp tenant provision`. Values may contain
5772
+ * `${tamer:...}` references (resolved against state at provision time).
5773
+ * D1 bindings are derived automatically from `resources.d1[].registryRole`
5774
+ * matched to `d1Shards[]` — only non-D1 vars go here.
5775
+ */
5776
+ dispatchVars?: Record<string, TamerResolvableString>;
5777
+ /**
5778
+ * Service bindings injected into the tenant dispatch script's metadata.
5779
+ * Each entry produces `env.BINDING` on the tenant Worker pointing at
5780
+ * the named service (e.g. the portal-api Worker). `service` may contain
5781
+ * `${tamer:...}` references.
5782
+ */
5783
+ dispatchServices?: Array<{
5784
+ name: string;
5785
+ service: TamerResolvableString;
5786
+ environment?: string;
5787
+ }>;
5749
5788
  }
5750
5789
  /**
5751
5790
  * D1 database declared on a worker's `resources.d1[]`.