@dragonmastery/tamer 0.36.3 → 0.36.4

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 (37) hide show
  1. package/dist/{apply-CSQ2lLYY.mjs → apply-DPkTNVwz.mjs} +8 -8
  2. package/dist/{apply-CSQ2lLYY.mjs.map → apply-DPkTNVwz.mjs.map} +1 -1
  3. package/dist/{applyTarget-q8lQlmL6.mjs → applyTarget-IwJLJY-2.mjs} +2 -2
  4. package/dist/{applyTarget-q8lQlmL6.mjs.map → applyTarget-IwJLJY-2.mjs.map} +1 -1
  5. package/dist/{cloudflareSnapshot-D9LAgUVP.mjs → cloudflareSnapshot-DOphOBnH.mjs} +2 -2
  6. package/dist/{cloudflareSnapshot-D9LAgUVP.mjs.map → cloudflareSnapshot-DOphOBnH.mjs.map} +1 -1
  7. package/dist/{deploy-CNQ9lhnp.mjs → deploy-BvWkPRlh.mjs} +5 -5
  8. package/dist/{deploy-CNQ9lhnp.mjs.map → deploy-BvWkPRlh.mjs.map} +1 -1
  9. package/dist/{destroy-D18ZIufa.mjs → destroy-OozAEB7_.mjs} +5 -5
  10. package/dist/{destroy-D18ZIufa.mjs.map → destroy-OozAEB7_.mjs.map} +1 -1
  11. package/dist/{dev-CsZ0sDe0.mjs → dev-CcM6_SJV.mjs} +4 -4
  12. package/dist/{dev-CsZ0sDe0.mjs.map → dev-CcM6_SJV.mjs.map} +1 -1
  13. package/dist/{drift-Bdt91dCs.mjs → drift-DxrfuuD0.mjs} +2 -2
  14. package/dist/{drift-Bdt91dCs.mjs.map → drift-DxrfuuD0.mjs.map} +1 -1
  15. package/dist/{drift-ATV3NuQ0.mjs → drift-ODjZs4io.mjs} +2 -2
  16. package/dist/{emit-D0p7CKFk.mjs → emit-BxHhy1ZC.mjs} +2 -2
  17. package/dist/{emit-D0p7CKFk.mjs.map → emit-BxHhy1ZC.mjs.map} +1 -1
  18. package/dist/{generator-vDl0esL-.mjs → generator-qyqB1_Yv.mjs} +2 -2
  19. package/dist/{generator-vDl0esL-.mjs.map → generator-qyqB1_Yv.mjs.map} +1 -1
  20. package/dist/{import-7TWYhptE.mjs → import-Bp07LFfK.mjs} +2 -2
  21. package/dist/{import-7TWYhptE.mjs.map → import-Bp07LFfK.mjs.map} +1 -1
  22. package/dist/{migrate-DaxG75UZ.mjs → migrate-CeUZBQtV.mjs} +3 -3
  23. package/dist/{migrate-DaxG75UZ.mjs.map → migrate-CeUZBQtV.mjs.map} +1 -1
  24. package/dist/{plan-NIPM6cF3.mjs → plan-NOBO-FYD.mjs} +5 -5
  25. package/dist/{plan-NIPM6cF3.mjs.map → plan-NOBO-FYD.mjs.map} +1 -1
  26. package/dist/{registry-Du91dVUE.mjs → registry-BlTu3xMp.mjs} +25 -17
  27. package/dist/{registry-Du91dVUE.mjs.map → registry-BlTu3xMp.mjs.map} +1 -1
  28. package/dist/{status-BOlsYClM.mjs → status-Cj14l-ax.mjs} +2 -2
  29. package/dist/{status-BOlsYClM.mjs.map → status-Cj14l-ax.mjs.map} +1 -1
  30. package/dist/{sync-nkUmYv5B.mjs → sync-B6vBSAKa.mjs} +2 -2
  31. package/dist/{sync-nkUmYv5B.mjs.map → sync-B6vBSAKa.mjs.map} +1 -1
  32. package/dist/tamer.mjs +11 -11
  33. package/dist/{types-OTsgglTT.mjs → types-CcuMLDrv.mjs} +3 -3
  34. package/dist/{types-OTsgglTT.mjs.map → types-CcuMLDrv.mjs.map} +1 -1
  35. package/dist/{verifyPlanFile-B6Wxl2m2.mjs → verifyPlanFile-CuG9sooo.mjs} +2 -2
  36. package/dist/{verifyPlanFile-B6Wxl2m2.mjs.map → verifyPlanFile-CuG9sooo.mjs.map} +1 -1
  37. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"plan-NIPM6cF3.mjs","names":["items: PlanItem[]","byKind: Record<PlanResourceKind, number>","byAction: Record<PlanAction, number>","attestation"],"sources":["../src/core/plan/computePlan.ts","../src/core/plan/plan.types.ts","../src/cli/commands/plan.ts"],"sourcesContent":["import { loadConfig, getWorkers } from \"../config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../cloudflareEnv.js\";\nimport { namingFromConfig } from \"../config/namingFromConfig.js\";\nimport { StateManager } from \"../state/StateManager.js\";\nimport { stackNameForConfig } from \"../state/stackName.js\";\nimport { fetchStackImports } from \"../imports/fetchStackImports.js\";\nimport { CFApiClient } from \"../api/CFApiClient.js\";\nimport { computeDriftReport } from \"../../cli/commands/drift.js\";\nimport { resolveWorkerConfig } from \"../config/resolver.js\";\nimport { resourceModules } from \"../registry/registry.js\";\nimport { logicalNamesForResourceKind } from \"../config/resourcesFromConfig.js\";\nimport { resolveDeployedWorkerName } from \"../config/resolver.js\";\nimport { requiredSecretsForWorker } from \"../secrets/declared.js\";\nimport {\n reconcileSecrets,\n secretsPlanItems,\n vaultReaderFromMap,\n type SecretsVaultReader,\n type VaultSecretMeta,\n} from \"../secrets/reconcile.js\";\nimport { getDispatchNamespaces, getDnsRecords, getLogpushJobs } from \"../../types.js\";\nimport { logpushJobStateKey } from \"../../features/logpush-job/logpush-job.resolve.js\";\nimport { logpushJobDiffPlanItems } from \"../../features/logpush-job/logpush-job.diff.js\";\nimport {\n dnsRecordDiffPlanItems,\n} from \"../../features/dns-records/dns-records.diff.js\";\nimport { dnsRecordStateKey } from \"../../features/dns-records/dns-records.resolve.js\";\nimport type { StateEntry } from \"../../types.js\";\nimport type { PlanItem, PlanReport, PlanResourceKind } from \"./plan.types.js\";\nimport {\n filterPlanItemsForTarget,\n type ApplyTarget,\n} from \"../apply/applyTarget.js\";\n\n/**\n * Compute the planned set of changes for `env` given the current\n * `tamer.config.ts` and recorded state. Read-only: hits the Cloudflare API to\n * detect what is missing but never writes.\n *\n * The \"would create\" set is derived from `computeDriftReport().undeployed`:\n * declared in config, present neither in state nor on Cloudflare. Worker\n * **scripts** are queried directly (`workersScriptGet`) since they are not\n * tracked as state resources today.\n */\nexport async function computePlan(options: {\n env?: string;\n configPath?: string;\n /** When set, only plan items for this `kind:logical` are returned. */\n target?: ApplyTarget;\n /** Optional vault reader for secret reconciliation (defaults to empty). */\n secretsVault?: SecretsVaultReader;\n}): Promise<PlanReport> {\n const env = options.env ?? \"local\";\n const configPath = options.configPath;\n const baseDir = process.cwd();\n const target = options.target;\n\n const config = await loadConfig(configPath, { env });\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 drift = await computeDriftReport({ env, configPath });\n\n const items: PlanItem[] = [];\n\n for (const r of drift.resources) {\n const kind = mapDriftKind(r.kind);\n if (!kind) continue;\n for (const e of r.undeployed) {\n items.push({\n kind,\n action: \"create\",\n logicalName: e.logicalName,\n derivedName: e.derivedName,\n detail: e.detail,\n });\n }\n }\n\n if (env !== \"local\") {\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: missing sibling state isn't fatal here (plan\n // is read-only) but populating imports lets the resolver substitute\n // real values instead of leaving placeholders.\n const imports = await fetchStackImports(api, config, env);\n\n const dnsItems = dnsRecordDiffPlanItems({\n resources: getDnsRecords(config),\n tenant: config.tenant,\n env,\n state,\n });\n items.push(...dnsItems);\n\n const logpushPlanItems = await logpushJobDiffPlanItems({\n resources: getLogpushJobs(config),\n tenant: config.tenant,\n env,\n state,\n api,\n accountId,\n });\n items.push(...logpushPlanItems);\n\n const workers = await getWorkers(config, baseDir);\n for (const [workerKey, wc] of workers) {\n const resolved = await resolveWorkerConfig(\n config,\n workerKey,\n wc,\n env,\n baseDir,\n accountId,\n naming,\n state,\n { referencesMode: \"tolerant\", imports },\n );\n if (resolved.dispatchNamespace) continue;\n\n const deployedName = resolveDeployedWorkerName(\n config,\n workerKey,\n wc,\n env,\n naming,\n );\n for (const mod of resourceModules) {\n if (!mod.diff) continue;\n const resources = mod.pickResources(wc);\n if (resources.length === 0) continue;\n const diffItems = mod.diff({\n resources,\n tenant: config.tenant,\n env,\n state,\n naming,\n worker: { workerKey, deployedName },\n });\n items.push(...diffItems);\n }\n\n let script;\n try {\n script = await api.workersScriptGet(resolved.workerName);\n } catch {\n continue;\n }\n if (!script) {\n items.push({\n kind: \"worker_script\",\n action: \"create\",\n logicalName: workerKey,\n derivedName: resolved.workerName,\n });\n }\n\n const required = requiredSecretsForWorker(wc);\n if (required.length > 0) {\n const workerSecretNames = await safeWorkersSecretsList(\n api,\n resolved.workerName,\n );\n const secretEntries = await reconcileSecrets({\n workers: [\n {\n workerKey,\n required,\n workerSecretNames,\n },\n ],\n vault: options.secretsVault ?? emptySecretsVault(),\n state,\n });\n items.push(...secretsPlanItems(secretEntries));\n }\n }\n }\n\n const filtered = target ? filterPlanItemsForTarget(items, target) : items;\n\n return {\n tenantId: config.tenant.id,\n env,\n generatedAt: new Date().toISOString(),\n items: filtered,\n hasChanges: filtered.some((i) => i.action !== \"no_change\"),\n mode: \"forward\",\n };\n}\n\n/**\n * Compute the planned set of **deletions** for `env` — what `tamer destroy`\n * would remove given the current `tamer.config.ts` and recorded state.\n *\n * Read-only: hits the Cloudflare API only to confirm worker scripts exist,\n * never writes. Items are stack-scoped: only state entries whose\n * `logicalName` is declared on a worker in the current config are emitted —\n * resources owned by another stack sharing the same `tamer-state-{env}` row\n * are **not** included (matches `runDestroy` semantics).\n *\n * Worker scripts are listed when the deployed name actually exists on\n * Cloudflare; dispatch-namespace tenant scripts are intentionally excluded\n * (they are managed via `provision-tenant` / `destroy-tenant`).\n */\nexport async function computeDestroyPlan(options: {\n env?: string;\n configPath?: string;\n target?: ApplyTarget;\n}): Promise<PlanReport> {\n const env = options.env ?? \"local\";\n const configPath = options.configPath;\n const baseDir = process.cwd();\n const target = options.target;\n\n const config = await loadConfig(configPath, { env });\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 items: PlanItem[] = [];\n const generatedAt = new Date().toISOString();\n\n if (env === \"local\") {\n return {\n tenantId: config.tenant.id,\n env,\n generatedAt,\n items,\n hasChanges: false,\n mode: \"destroy\",\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 const imports = await fetchStackImports(api, config, env);\n\n const stateEntries = state.getAll();\n\n for (const mod of resourceModules) {\n const owned = await logicalNamesForResourceKind(config, baseDir, mod.kind);\n if (owned.size === 0) continue;\n const kind = mapDriftKind(mod.kind);\n if (!kind) continue;\n for (const [, entry] of Object.entries(stateEntries)) {\n if (entry.type !== mod.stateEntryType) continue;\n const logicalName = (entry as { logicalName?: string }).logicalName;\n if (!logicalName || !owned.has(logicalName)) continue;\n items.push({\n kind,\n action: \"delete\",\n logicalName,\n derivedName: derivedNameOf(entry),\n });\n }\n }\n\n for (const [, entry] of Object.entries(stateEntries)) {\n if (entry.type !== \"worker_route\") continue;\n items.push({\n kind: \"worker_route\",\n action: \"delete\",\n logicalName: entry.workerKey,\n derivedName: entry.pattern,\n detail: entry.zoneName,\n });\n }\n\n for (const ns of getDispatchNamespaces(config)) {\n const stateKey = `dispatch_namespace:${ns.logicalName}`;\n const entry = stateEntries[stateKey];\n if (!entry || entry.type !== \"dispatch_namespace\") continue;\n items.push({\n kind: \"dispatch_namespace\",\n action: \"delete\",\n logicalName: ns.logicalName,\n derivedName: entry.derivedName,\n });\n }\n\n for (const dns of getDnsRecords(config)) {\n if (dns.preserveOnDestroy) continue;\n const key = dnsRecordStateKey(dns.zoneId, dns.type, dns.name);\n const entry = stateEntries[key];\n if (!entry || entry.type !== \"dns_record\") continue;\n items.push({\n kind: \"dns_record\",\n action: \"delete\",\n logicalName: dns.logicalName,\n derivedName: `${entry.recordType} ${entry.name}`,\n detail: entry.recordId,\n });\n }\n\n for (const lp of getLogpushJobs(config)) {\n const key = logpushJobStateKey(config.tenant.id, lp.logicalName, env);\n const entry = stateEntries[key];\n if (!entry || entry.type !== \"logpush_job\") continue;\n items.push({\n kind: \"logpush_job\",\n action: \"delete\",\n logicalName: lp.logicalName,\n derivedName: entry.derivedName,\n detail: String(entry.cfJobId),\n });\n }\n\n const workers = await getWorkers(config, baseDir);\n for (const [workerKey, wc] of workers) {\n const resolved = await resolveWorkerConfig(\n config,\n workerKey,\n wc,\n env,\n baseDir,\n accountId,\n naming,\n state,\n { referencesMode: \"tolerant\", imports },\n );\n if (resolved.dispatchNamespace) continue;\n const deployedName = resolveDeployedWorkerName(\n config,\n workerKey,\n wc,\n env,\n naming,\n );\n let script;\n try {\n script = await api.workersScriptGet(deployedName);\n } catch {\n continue;\n }\n if (script) {\n items.push({\n kind: \"worker_script\",\n action: \"delete\",\n logicalName: workerKey,\n derivedName: deployedName,\n });\n }\n }\n\n const filtered = target ? filterPlanItemsForTarget(items, target) : items;\n\n return {\n tenantId: config.tenant.id,\n env,\n generatedAt,\n items: filtered,\n hasChanges: filtered.length > 0,\n mode: \"destroy\",\n };\n}\n\nfunction derivedNameOf(entry: StateEntry): string {\n switch (entry.type) {\n case \"d1_database\":\n case \"r2_bucket\":\n case \"kv_namespace\":\n case \"queue\":\n case \"hyperdrive\":\n case \"vectorize\":\n case \"ai_gateway\":\n case \"pipeline\":\n case \"workflow\":\n case \"secrets_store\":\n case \"dispatch_namespace\":\n return entry.derivedName;\n case \"dns_record\":\n return `${entry.recordType} ${entry.name}`;\n case \"worker_route\":\n return entry.pattern;\n case \"logpush_job\":\n return entry.derivedName;\n case \"logpush_pipelines\":\n return entry.pipelineName;\n case \"secret\":\n return `${entry.worker}:${entry.name}`;\n }\n}\n\nfunction emptySecretsVault(): SecretsVaultReader {\n return vaultReaderFromMap({});\n}\n\nasync function safeWorkersSecretsList(\n api: CFApiClient,\n scriptName: string,\n): Promise<string[]> {\n try {\n return await api.workersSecretsList(scriptName);\n } catch {\n return [];\n }\n}\n\nfunction mapDriftKind(kind: string): PlanResourceKind | undefined {\n switch (kind) {\n case \"d1\":\n case \"r2\":\n case \"kv\":\n case \"queue\":\n case \"hyperdrive\":\n case \"vectorize\":\n case \"ai_gateway\":\n case \"pipeline\":\n case \"workflow\":\n case \"secret_store\":\n case \"secret\":\n case \"dns_record\":\n case \"dispatch_namespace\":\n case \"logpush_job\":\n case \"worker_route\":\n return kind;\n default:\n return undefined;\n }\n}\n","/**\n * Read-only \"plan\" — the CloudFormation-style preview of changes Tamer would\n * make if you ran `apply` and then `deploy` against the current state +\n * config. Pure: never writes to state or to Cloudflare.\n */\n\nexport type PlanResourceKind =\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 | \"worker_route\"\n | \"worker_script\";\n\n/**\n * Plan actions, in CloudFormation / Terraform terms:\n *\n * - `create` — declared in config, missing on Cloudflare and in state.\n * - `update` — tracked + present, but **mutable** fields drifted from config →\n * `apply` would PATCH in place. No replacement.\n * - `replace` — tracked + present, but **immutable** fields drifted from\n * config → `apply` must delete + recreate (Cloudflare APIs that reject\n * PATCH on the changed field, e.g. DNS record `type`, Vectorize\n * `dimensions` / `metric`, Pipelines V1 `sql`). The state row's `cfId`\n * will change.\n * - `delete` — destroy-plan only.\n * - `no_change` — declared, present, fully in sync.\n */\nexport type PlanAction =\n | \"create\"\n | \"update\"\n | \"replace\"\n | \"delete\"\n | \"no_change\";\n\n/**\n * Direction of the plan: forward (the default — what `apply` + `deploy` would\n * change) or destroy (what `destroy` would remove). Plans are mutually\n * exclusive: a forward plan never includes `delete`, and a destroy plan\n * never includes `create` / `update` / `replace`.\n */\nexport type PlanMode = \"forward\" | \"destroy\";\n\n/**\n * Describes one drifted field in an `update` / `replace` plan item — the\n * field path, what state currently holds, and what config wants. Surfaced in\n * `tamer plan --json` so reviewers can audit the change exactly the way\n * `terraform plan` shows attribute diffs.\n */\nexport interface PlanFieldChange {\n field: string;\n from: unknown;\n to: unknown;\n /**\n * `mutable` → would patch in place; `immutable` → forces `replace` (the\n * underlying Cloudflare resource will be destroyed + recreated, and the\n * state row's `cfId` will change).\n */\n kind: \"mutable\" | \"immutable\";\n}\n\nexport interface PlanItem {\n kind: PlanResourceKind;\n action: PlanAction;\n /** Logical name from `tamer.config.ts` (or worker key for scripts/routes). */\n logicalName: string;\n /** Cloudflare-side identifier (resource name or pattern). */\n derivedName: string;\n /** Optional human-readable detail. */\n detail?: string;\n /**\n * Field-level diff for `update` / `replace` items. Empty for `create` /\n * `delete` / `no_change`. Each entry shows what state has (`from`) and what\n * config asks for (`to`).\n */\n changes?: PlanFieldChange[];\n}\n\nexport interface PlanReport {\n tenantId: string;\n env: string;\n generatedAt: string;\n items: PlanItem[];\n /** True iff at least one item has `action !== \"no_change\"`. */\n hasChanges: boolean;\n /** Plan direction. Defaults to `\"forward\"` for back-compat with v0.11 plans. */\n mode?: PlanMode;\n}\n\nexport function planSummary(items: PlanItem[]): {\n byKind: Record<PlanResourceKind, number>;\n byAction: Record<PlanAction, number>;\n} {\n const byKind: Record<PlanResourceKind, number> = {\n d1: 0,\n r2: 0,\n kv: 0,\n queue: 0,\n hyperdrive: 0,\n vectorize: 0,\n ai_gateway: 0,\n pipeline: 0,\n workflow: 0,\n secret_store: 0,\n secret: 0,\n dns_record: 0,\n dispatch_namespace: 0,\n logpush_job: 0,\n worker_route: 0,\n worker_script: 0,\n };\n const byAction: Record<PlanAction, number> = {\n create: 0,\n update: 0,\n replace: 0,\n delete: 0,\n no_change: 0,\n };\n for (const it of items) {\n if (it.action === \"no_change\") continue;\n byKind[it.kind]++;\n byAction[it.action]++;\n }\n return { byKind, byAction };\n}\n","import { computePlan, computeDestroyPlan } from \"../../core/plan/computePlan.js\";\nimport { planSummary } from \"../../core/plan/plan.types.js\";\nimport type {\n PlanItem,\n PlanReport,\n PlanResourceKind,\n} from \"../../core/plan/plan.types.js\";\nimport { formatPlanFieldChanges, signFor } from \"../../core/plan/planFormat.js\";\nimport { loadConfig } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport {\n PLAN_FILE_FORMAT,\n computeAttestation,\n writePlanFile,\n} from \"../../core/plan/planFile.js\";\nimport { createEmptyCfiState } from \"../../core/state/tamerStateDb.js\";\nimport { buildCloudflareSnapshot } from \"../../core/plan/cloudflareSnapshot.js\";\nimport {\n parseApplyTarget,\n assertApplyTargetDeclared,\n} from \"../../core/apply/applyTarget.js\";\n\n/**\n * `tamer plan` — print a CloudFormation-style preview of `apply` + `deploy`\n * against the current state, config, and Cloudflare reality. Never mutates.\n *\n * Exit codes:\n * 0 — plan succeeded; with `--detailed-exitcode`, also \"no changes\".\n * 2 — plan succeeded **and** has at least one create/update (with\n * `--detailed-exitcode`); useful for CI gates that want to fail when\n * infra would change.\n */\nexport async function runPlan(options: {\n env?: string;\n configPath?: string;\n json?: boolean;\n detailedExitcode?: boolean;\n /** When set, write the plan + attestation hashes to this path. */\n out?: string;\n /**\n * When true, compute a **destroy plan** (what `tamer destroy` would remove\n * from state and Cloudflare) instead of the default forward plan. Mutually\n * exclusive with the forward plan — destroy plans only contain `delete`\n * items, never `create` / `update`. Read-only.\n */\n destroy?: boolean;\n /** `kind:logicalName` — restrict plan items to that resource. */\n target?: string;\n}): Promise<number> {\n if (options.out && options.target) {\n throw new Error(\n \"Cannot combine --out with --target: saved plans must cover the full stack.\",\n );\n }\n\n const target = options.target\n ? parseApplyTarget(options.target)\n : undefined;\n if (target) {\n const config = await loadConfig(options.configPath, {\n env: options.env ?? \"local\",\n });\n await assertApplyTargetDeclared(config, process.cwd(), target);\n }\n\n const report = options.destroy\n ? await computeDestroyPlan({\n env: options.env,\n configPath: options.configPath,\n target,\n })\n : await computePlan({\n env: options.env,\n configPath: options.configPath,\n target,\n });\n\n if (options.out) {\n await writePlanForApply(options.out, report, options.configPath);\n }\n\n if (options.json) {\n console.log(JSON.stringify(report, null, 2));\n } else {\n printHumanPlan(report);\n if (options.out) {\n console.log(`Plan written to ${options.out}\\n`);\n }\n }\n\n if (options.detailedExitcode && report.hasChanges) {\n return 2;\n }\n return 0;\n}\n\n/**\n * Hydrate the same `(config, state)` pair `computePlan` consumed and write\n * a {@link PlanFile} with the attestation hashes pinned. `apply --plan`\n * recomputes both hashes and refuses to proceed if either drifted, so a\n * stale plan can never silently apply against changed inputs.\n */\nasync function writePlanForApply(\n outPath: string,\n report: PlanReport,\n configPath: string | undefined,\n): Promise<void> {\n const config = await loadConfig(configPath, { env: report.env });\n if (report.env === \"local\") {\n const attestation = computeAttestation(\n config,\n createEmptyCfiState(config.tenant.id, \"local\"),\n );\n writePlanFile(outPath, {\n format: PLAN_FILE_FORMAT,\n tenantId: config.tenant.id,\n env: report.env,\n generatedAt: report.generatedAt,\n report,\n attestation,\n });\n return;\n }\n\n const accountId = config.account_id ?? cloudflareAccountIdFromEnv();\n if (!accountId) {\n throw new Error(\n \"account_id required in config or CLOUDFLARE_ACCOUNT_ID env var\",\n );\n }\n const api = new CFApiClient(accountId);\n const state = new StateManager(\n config.tenant.id,\n report.env,\n stackNameForConfig(config),\n );\n await state.hydrate(api);\n const cloudflareSnapshot = await buildCloudflareSnapshot({\n config,\n env: report.env,\n api,\n baseDir: process.cwd(),\n });\n const attestation = computeAttestation(\n config,\n state.load(),\n cloudflareSnapshot,\n );\n writePlanFile(outPath, {\n format: PLAN_FILE_FORMAT,\n tenantId: config.tenant.id,\n env: report.env,\n generatedAt: report.generatedAt,\n report,\n attestation,\n });\n}\n\nfunction printHumanPlan(report: PlanReport): void {\n const isDestroy = report.mode === \"destroy\";\n const heading = isDestroy ? \"Destroy plan\" : \"Plan\";\n console.log(\n `\\n${heading} — tenant ${report.tenantId}, env ${report.env}\\n`,\n );\n if (report.items.length === 0) {\n if (isDestroy) {\n console.log(\" (nothing to destroy — no managed resources in state)\\n\");\n } else {\n console.log(\" (no changes — infrastructure matches config)\\n\");\n }\n return;\n }\n const grouped = new Map<PlanResourceKind, PlanItem[]>();\n for (const it of report.items) {\n const arr = grouped.get(it.kind) ?? [];\n arr.push(it);\n grouped.set(it.kind, arr);\n }\n for (const [kind, items] of grouped) {\n console.log(`${labelFor(kind)} (${items.length}):`);\n for (const it of items) {\n const sign = signFor(it.action);\n // Suppress `detail` on update/replace items when we have a structured\n // `changes[]` block — the per-field renderer below already shows the\n // same drift in terraform-style form, and printing both duplicates\n // the diff and clutters the output.\n const hasChanges = it.changes && it.changes.length > 0;\n const detail = it.detail && !hasChanges ? ` (${it.detail})` : \"\";\n console.log(` ${sign} ${it.logicalName} -> ${it.derivedName}${detail}`);\n for (const line of formatPlanFieldChanges(it.changes)) {\n console.log(line);\n }\n }\n }\n const { byAction } = planSummary(report.items);\n if (isDestroy) {\n console.log(`\\nSummary: ${byAction.delete} to delete.\\n`);\n } else {\n console.log(\n `\\nSummary: ${byAction.create} to create, ${byAction.update} to update, ${byAction.replace} to replace.\\n`,\n );\n }\n}\n\nfunction labelFor(kind: PlanResourceKind): string {\n switch (kind) {\n case \"d1\":\n return \"D1\";\n case \"r2\":\n return \"R2\";\n case \"kv\":\n return \"KV\";\n case \"queue\":\n return \"Queues\";\n case \"hyperdrive\":\n return \"Hyperdrive\";\n case \"vectorize\":\n return \"Vectorize\";\n case \"ai_gateway\":\n return \"AI Gateway\";\n case \"pipeline\":\n return \"Pipeline\";\n case \"workflow\":\n return \"Workflow\";\n case \"secret_store\":\n return \"Secrets Store\";\n case \"secret\":\n return \"Worker secrets\";\n case \"dns_record\":\n return \"DNS records\";\n case \"dispatch_namespace\":\n return \"Dispatch namespaces\";\n case \"logpush_job\":\n return \"Logpush jobs\";\n case \"worker_route\":\n return \"Workers zone routes\";\n case \"worker_script\":\n return \"Worker scripts\";\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"plan-NOBO-FYD.mjs","names":["items: PlanItem[]","byKind: Record<PlanResourceKind, number>","byAction: Record<PlanAction, number>","attestation"],"sources":["../src/core/plan/computePlan.ts","../src/core/plan/plan.types.ts","../src/cli/commands/plan.ts"],"sourcesContent":["import { loadConfig, getWorkers } from \"../config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../cloudflareEnv.js\";\nimport { namingFromConfig } from \"../config/namingFromConfig.js\";\nimport { StateManager } from \"../state/StateManager.js\";\nimport { stackNameForConfig } from \"../state/stackName.js\";\nimport { fetchStackImports } from \"../imports/fetchStackImports.js\";\nimport { CFApiClient } from \"../api/CFApiClient.js\";\nimport { computeDriftReport } from \"../../cli/commands/drift.js\";\nimport { resolveWorkerConfig } from \"../config/resolver.js\";\nimport { resourceModules } from \"../registry/registry.js\";\nimport { logicalNamesForResourceKind } from \"../config/resourcesFromConfig.js\";\nimport { resolveDeployedWorkerName } from \"../config/resolver.js\";\nimport { requiredSecretsForWorker } from \"../secrets/declared.js\";\nimport {\n reconcileSecrets,\n secretsPlanItems,\n vaultReaderFromMap,\n type SecretsVaultReader,\n type VaultSecretMeta,\n} from \"../secrets/reconcile.js\";\nimport { getDispatchNamespaces, getDnsRecords, getLogpushJobs } from \"../../types.js\";\nimport { logpushJobStateKey } from \"../../features/logpush-job/logpush-job.resolve.js\";\nimport { logpushJobDiffPlanItems } from \"../../features/logpush-job/logpush-job.diff.js\";\nimport {\n dnsRecordDiffPlanItems,\n} from \"../../features/dns-records/dns-records.diff.js\";\nimport { dnsRecordStateKey } from \"../../features/dns-records/dns-records.resolve.js\";\nimport type { StateEntry } from \"../../types.js\";\nimport type { PlanItem, PlanReport, PlanResourceKind } from \"./plan.types.js\";\nimport {\n filterPlanItemsForTarget,\n type ApplyTarget,\n} from \"../apply/applyTarget.js\";\n\n/**\n * Compute the planned set of changes for `env` given the current\n * `tamer.config.ts` and recorded state. Read-only: hits the Cloudflare API to\n * detect what is missing but never writes.\n *\n * The \"would create\" set is derived from `computeDriftReport().undeployed`:\n * declared in config, present neither in state nor on Cloudflare. Worker\n * **scripts** are queried directly (`workersScriptGet`) since they are not\n * tracked as state resources today.\n */\nexport async function computePlan(options: {\n env?: string;\n configPath?: string;\n /** When set, only plan items for this `kind:logical` are returned. */\n target?: ApplyTarget;\n /** Optional vault reader for secret reconciliation (defaults to empty). */\n secretsVault?: SecretsVaultReader;\n}): Promise<PlanReport> {\n const env = options.env ?? \"local\";\n const configPath = options.configPath;\n const baseDir = process.cwd();\n const target = options.target;\n\n const config = await loadConfig(configPath, { env });\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 drift = await computeDriftReport({ env, configPath });\n\n const items: PlanItem[] = [];\n\n for (const r of drift.resources) {\n const kind = mapDriftKind(r.kind);\n if (!kind) continue;\n for (const e of r.undeployed) {\n items.push({\n kind,\n action: \"create\",\n logicalName: e.logicalName,\n derivedName: e.derivedName,\n detail: e.detail,\n });\n }\n }\n\n if (env !== \"local\") {\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: missing sibling state isn't fatal here (plan\n // is read-only) but populating imports lets the resolver substitute\n // real values instead of leaving placeholders.\n const imports = await fetchStackImports(api, config, env);\n\n const dnsItems = dnsRecordDiffPlanItems({\n resources: getDnsRecords(config),\n tenant: config.tenant,\n env,\n state,\n });\n items.push(...dnsItems);\n\n const logpushPlanItems = await logpushJobDiffPlanItems({\n resources: getLogpushJobs(config),\n tenant: config.tenant,\n env,\n state,\n api,\n accountId,\n });\n items.push(...logpushPlanItems);\n\n const workers = await getWorkers(config, baseDir);\n for (const [workerKey, wc] of workers) {\n const resolved = await resolveWorkerConfig(\n config,\n workerKey,\n wc,\n env,\n baseDir,\n accountId,\n naming,\n state,\n { referencesMode: \"tolerant\", imports },\n );\n if (resolved.dispatchNamespace) continue;\n\n const deployedName = resolveDeployedWorkerName(\n config,\n workerKey,\n wc,\n env,\n naming,\n );\n for (const mod of resourceModules) {\n if (!mod.diff) continue;\n const resources = mod.pickResources(wc);\n if (resources.length === 0) continue;\n const diffItems = mod.diff({\n resources,\n tenant: config.tenant,\n env,\n state,\n naming,\n worker: { workerKey, deployedName },\n });\n items.push(...diffItems);\n }\n\n let script;\n try {\n script = await api.workersScriptGet(resolved.workerName);\n } catch {\n continue;\n }\n if (!script) {\n items.push({\n kind: \"worker_script\",\n action: \"create\",\n logicalName: workerKey,\n derivedName: resolved.workerName,\n });\n }\n\n const required = requiredSecretsForWorker(wc);\n if (required.length > 0) {\n const workerSecretNames = await safeWorkersSecretsList(\n api,\n resolved.workerName,\n );\n const secretEntries = await reconcileSecrets({\n workers: [\n {\n workerKey,\n required,\n workerSecretNames,\n },\n ],\n vault: options.secretsVault ?? emptySecretsVault(),\n state,\n });\n items.push(...secretsPlanItems(secretEntries));\n }\n }\n }\n\n const filtered = target ? filterPlanItemsForTarget(items, target) : items;\n\n return {\n tenantId: config.tenant.id,\n env,\n generatedAt: new Date().toISOString(),\n items: filtered,\n hasChanges: filtered.some((i) => i.action !== \"no_change\"),\n mode: \"forward\",\n };\n}\n\n/**\n * Compute the planned set of **deletions** for `env` — what `tamer destroy`\n * would remove given the current `tamer.config.ts` and recorded state.\n *\n * Read-only: hits the Cloudflare API only to confirm worker scripts exist,\n * never writes. Items are stack-scoped: only state entries whose\n * `logicalName` is declared on a worker in the current config are emitted —\n * resources owned by another stack sharing the same `tamer-state-{env}` row\n * are **not** included (matches `runDestroy` semantics).\n *\n * Worker scripts are listed when the deployed name actually exists on\n * Cloudflare; dispatch-namespace tenant scripts are intentionally excluded\n * (they are managed via `provision-tenant` / `destroy-tenant`).\n */\nexport async function computeDestroyPlan(options: {\n env?: string;\n configPath?: string;\n target?: ApplyTarget;\n}): Promise<PlanReport> {\n const env = options.env ?? \"local\";\n const configPath = options.configPath;\n const baseDir = process.cwd();\n const target = options.target;\n\n const config = await loadConfig(configPath, { env });\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 items: PlanItem[] = [];\n const generatedAt = new Date().toISOString();\n\n if (env === \"local\") {\n return {\n tenantId: config.tenant.id,\n env,\n generatedAt,\n items,\n hasChanges: false,\n mode: \"destroy\",\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 const imports = await fetchStackImports(api, config, env);\n\n const stateEntries = state.getAll();\n\n for (const mod of resourceModules) {\n const owned = await logicalNamesForResourceKind(config, baseDir, mod.kind);\n if (owned.size === 0) continue;\n const kind = mapDriftKind(mod.kind);\n if (!kind) continue;\n for (const [, entry] of Object.entries(stateEntries)) {\n if (entry.type !== mod.stateEntryType) continue;\n const logicalName = (entry as { logicalName?: string }).logicalName;\n if (!logicalName || !owned.has(logicalName)) continue;\n items.push({\n kind,\n action: \"delete\",\n logicalName,\n derivedName: derivedNameOf(entry),\n });\n }\n }\n\n for (const [, entry] of Object.entries(stateEntries)) {\n if (entry.type !== \"worker_route\") continue;\n items.push({\n kind: \"worker_route\",\n action: \"delete\",\n logicalName: entry.workerKey,\n derivedName: entry.pattern,\n detail: entry.zoneName,\n });\n }\n\n for (const ns of getDispatchNamespaces(config)) {\n const stateKey = `dispatch_namespace:${ns.logicalName}`;\n const entry = stateEntries[stateKey];\n if (!entry || entry.type !== \"dispatch_namespace\") continue;\n items.push({\n kind: \"dispatch_namespace\",\n action: \"delete\",\n logicalName: ns.logicalName,\n derivedName: entry.derivedName,\n });\n }\n\n for (const dns of getDnsRecords(config)) {\n if (dns.preserveOnDestroy) continue;\n const key = dnsRecordStateKey(dns.zoneId, dns.type, dns.name);\n const entry = stateEntries[key];\n if (!entry || entry.type !== \"dns_record\") continue;\n items.push({\n kind: \"dns_record\",\n action: \"delete\",\n logicalName: dns.logicalName,\n derivedName: `${entry.recordType} ${entry.name}`,\n detail: entry.recordId,\n });\n }\n\n for (const lp of getLogpushJobs(config)) {\n const key = logpushJobStateKey(config.tenant.id, lp.logicalName, env);\n const entry = stateEntries[key];\n if (!entry || entry.type !== \"logpush_job\") continue;\n items.push({\n kind: \"logpush_job\",\n action: \"delete\",\n logicalName: lp.logicalName,\n derivedName: entry.derivedName,\n detail: String(entry.cfJobId),\n });\n }\n\n const workers = await getWorkers(config, baseDir);\n for (const [workerKey, wc] of workers) {\n const resolved = await resolveWorkerConfig(\n config,\n workerKey,\n wc,\n env,\n baseDir,\n accountId,\n naming,\n state,\n { referencesMode: \"tolerant\", imports },\n );\n if (resolved.dispatchNamespace) continue;\n const deployedName = resolveDeployedWorkerName(\n config,\n workerKey,\n wc,\n env,\n naming,\n );\n let script;\n try {\n script = await api.workersScriptGet(deployedName);\n } catch {\n continue;\n }\n if (script) {\n items.push({\n kind: \"worker_script\",\n action: \"delete\",\n logicalName: workerKey,\n derivedName: deployedName,\n });\n }\n }\n\n const filtered = target ? filterPlanItemsForTarget(items, target) : items;\n\n return {\n tenantId: config.tenant.id,\n env,\n generatedAt,\n items: filtered,\n hasChanges: filtered.length > 0,\n mode: \"destroy\",\n };\n}\n\nfunction derivedNameOf(entry: StateEntry): string {\n switch (entry.type) {\n case \"d1_database\":\n case \"r2_bucket\":\n case \"kv_namespace\":\n case \"queue\":\n case \"hyperdrive\":\n case \"vectorize\":\n case \"ai_gateway\":\n case \"pipeline\":\n case \"workflow\":\n case \"secrets_store\":\n case \"dispatch_namespace\":\n return entry.derivedName;\n case \"dns_record\":\n return `${entry.recordType} ${entry.name}`;\n case \"worker_route\":\n return entry.pattern;\n case \"logpush_job\":\n return entry.derivedName;\n case \"logpush_pipelines\":\n return entry.pipelineName;\n case \"secret\":\n return `${entry.worker}:${entry.name}`;\n }\n}\n\nfunction emptySecretsVault(): SecretsVaultReader {\n return vaultReaderFromMap({});\n}\n\nasync function safeWorkersSecretsList(\n api: CFApiClient,\n scriptName: string,\n): Promise<string[]> {\n try {\n return await api.workersSecretsList(scriptName);\n } catch {\n return [];\n }\n}\n\nfunction mapDriftKind(kind: string): PlanResourceKind | undefined {\n switch (kind) {\n case \"d1\":\n case \"r2\":\n case \"kv\":\n case \"queue\":\n case \"hyperdrive\":\n case \"vectorize\":\n case \"ai_gateway\":\n case \"pipeline\":\n case \"workflow\":\n case \"secret_store\":\n case \"secret\":\n case \"dns_record\":\n case \"dispatch_namespace\":\n case \"logpush_job\":\n case \"worker_route\":\n return kind;\n default:\n return undefined;\n }\n}\n","/**\n * Read-only \"plan\" — the CloudFormation-style preview of changes Tamer would\n * make if you ran `apply` and then `deploy` against the current state +\n * config. Pure: never writes to state or to Cloudflare.\n */\n\nexport type PlanResourceKind =\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 | \"worker_route\"\n | \"worker_script\";\n\n/**\n * Plan actions, in CloudFormation / Terraform terms:\n *\n * - `create` — declared in config, missing on Cloudflare and in state.\n * - `update` — tracked + present, but **mutable** fields drifted from config →\n * `apply` would PATCH in place. No replacement.\n * - `replace` — tracked + present, but **immutable** fields drifted from\n * config → `apply` must delete + recreate (Cloudflare APIs that reject\n * PATCH on the changed field, e.g. DNS record `type`, Vectorize\n * `dimensions` / `metric`, Pipelines V1 `sql`). The state row's `cfId`\n * will change.\n * - `delete` — destroy-plan only.\n * - `no_change` — declared, present, fully in sync.\n */\nexport type PlanAction =\n | \"create\"\n | \"update\"\n | \"replace\"\n | \"delete\"\n | \"no_change\";\n\n/**\n * Direction of the plan: forward (the default — what `apply` + `deploy` would\n * change) or destroy (what `destroy` would remove). Plans are mutually\n * exclusive: a forward plan never includes `delete`, and a destroy plan\n * never includes `create` / `update` / `replace`.\n */\nexport type PlanMode = \"forward\" | \"destroy\";\n\n/**\n * Describes one drifted field in an `update` / `replace` plan item — the\n * field path, what state currently holds, and what config wants. Surfaced in\n * `tamer plan --json` so reviewers can audit the change exactly the way\n * `terraform plan` shows attribute diffs.\n */\nexport interface PlanFieldChange {\n field: string;\n from: unknown;\n to: unknown;\n /**\n * `mutable` → would patch in place; `immutable` → forces `replace` (the\n * underlying Cloudflare resource will be destroyed + recreated, and the\n * state row's `cfId` will change).\n */\n kind: \"mutable\" | \"immutable\";\n}\n\nexport interface PlanItem {\n kind: PlanResourceKind;\n action: PlanAction;\n /** Logical name from `tamer.config.ts` (or worker key for scripts/routes). */\n logicalName: string;\n /** Cloudflare-side identifier (resource name or pattern). */\n derivedName: string;\n /** Optional human-readable detail. */\n detail?: string;\n /**\n * Field-level diff for `update` / `replace` items. Empty for `create` /\n * `delete` / `no_change`. Each entry shows what state has (`from`) and what\n * config asks for (`to`).\n */\n changes?: PlanFieldChange[];\n}\n\nexport interface PlanReport {\n tenantId: string;\n env: string;\n generatedAt: string;\n items: PlanItem[];\n /** True iff at least one item has `action !== \"no_change\"`. */\n hasChanges: boolean;\n /** Plan direction. Defaults to `\"forward\"` for back-compat with v0.11 plans. */\n mode?: PlanMode;\n}\n\nexport function planSummary(items: PlanItem[]): {\n byKind: Record<PlanResourceKind, number>;\n byAction: Record<PlanAction, number>;\n} {\n const byKind: Record<PlanResourceKind, number> = {\n d1: 0,\n r2: 0,\n kv: 0,\n queue: 0,\n hyperdrive: 0,\n vectorize: 0,\n ai_gateway: 0,\n pipeline: 0,\n workflow: 0,\n secret_store: 0,\n secret: 0,\n dns_record: 0,\n dispatch_namespace: 0,\n logpush_job: 0,\n worker_route: 0,\n worker_script: 0,\n };\n const byAction: Record<PlanAction, number> = {\n create: 0,\n update: 0,\n replace: 0,\n delete: 0,\n no_change: 0,\n };\n for (const it of items) {\n if (it.action === \"no_change\") continue;\n byKind[it.kind]++;\n byAction[it.action]++;\n }\n return { byKind, byAction };\n}\n","import { computePlan, computeDestroyPlan } from \"../../core/plan/computePlan.js\";\nimport { planSummary } from \"../../core/plan/plan.types.js\";\nimport type {\n PlanItem,\n PlanReport,\n PlanResourceKind,\n} from \"../../core/plan/plan.types.js\";\nimport { formatPlanFieldChanges, signFor } from \"../../core/plan/planFormat.js\";\nimport { loadConfig } from \"../../core/config/loader.js\";\nimport { cloudflareAccountIdFromEnv } from \"../../core/cloudflareEnv.js\";\nimport { CFApiClient } from \"../../core/api/CFApiClient.js\";\nimport { StateManager } from \"../../core/state/StateManager.js\";\nimport { stackNameForConfig } from \"../../core/state/stackName.js\";\nimport {\n PLAN_FILE_FORMAT,\n computeAttestation,\n writePlanFile,\n} from \"../../core/plan/planFile.js\";\nimport { createEmptyCfiState } from \"../../core/state/tamerStateDb.js\";\nimport { buildCloudflareSnapshot } from \"../../core/plan/cloudflareSnapshot.js\";\nimport {\n parseApplyTarget,\n assertApplyTargetDeclared,\n} from \"../../core/apply/applyTarget.js\";\n\n/**\n * `tamer plan` — print a CloudFormation-style preview of `apply` + `deploy`\n * against the current state, config, and Cloudflare reality. Never mutates.\n *\n * Exit codes:\n * 0 — plan succeeded; with `--detailed-exitcode`, also \"no changes\".\n * 2 — plan succeeded **and** has at least one create/update (with\n * `--detailed-exitcode`); useful for CI gates that want to fail when\n * infra would change.\n */\nexport async function runPlan(options: {\n env?: string;\n configPath?: string;\n json?: boolean;\n detailedExitcode?: boolean;\n /** When set, write the plan + attestation hashes to this path. */\n out?: string;\n /**\n * When true, compute a **destroy plan** (what `tamer destroy` would remove\n * from state and Cloudflare) instead of the default forward plan. Mutually\n * exclusive with the forward plan — destroy plans only contain `delete`\n * items, never `create` / `update`. Read-only.\n */\n destroy?: boolean;\n /** `kind:logicalName` — restrict plan items to that resource. */\n target?: string;\n}): Promise<number> {\n if (options.out && options.target) {\n throw new Error(\n \"Cannot combine --out with --target: saved plans must cover the full stack.\",\n );\n }\n\n const target = options.target\n ? parseApplyTarget(options.target)\n : undefined;\n if (target) {\n const config = await loadConfig(options.configPath, {\n env: options.env ?? \"local\",\n });\n await assertApplyTargetDeclared(config, process.cwd(), target);\n }\n\n const report = options.destroy\n ? await computeDestroyPlan({\n env: options.env,\n configPath: options.configPath,\n target,\n })\n : await computePlan({\n env: options.env,\n configPath: options.configPath,\n target,\n });\n\n if (options.out) {\n await writePlanForApply(options.out, report, options.configPath);\n }\n\n if (options.json) {\n console.log(JSON.stringify(report, null, 2));\n } else {\n printHumanPlan(report);\n if (options.out) {\n console.log(`Plan written to ${options.out}\\n`);\n }\n }\n\n if (options.detailedExitcode && report.hasChanges) {\n return 2;\n }\n return 0;\n}\n\n/**\n * Hydrate the same `(config, state)` pair `computePlan` consumed and write\n * a {@link PlanFile} with the attestation hashes pinned. `apply --plan`\n * recomputes both hashes and refuses to proceed if either drifted, so a\n * stale plan can never silently apply against changed inputs.\n */\nasync function writePlanForApply(\n outPath: string,\n report: PlanReport,\n configPath: string | undefined,\n): Promise<void> {\n const config = await loadConfig(configPath, { env: report.env });\n if (report.env === \"local\") {\n const attestation = computeAttestation(\n config,\n createEmptyCfiState(config.tenant.id, \"local\"),\n );\n writePlanFile(outPath, {\n format: PLAN_FILE_FORMAT,\n tenantId: config.tenant.id,\n env: report.env,\n generatedAt: report.generatedAt,\n report,\n attestation,\n });\n return;\n }\n\n const accountId = config.account_id ?? cloudflareAccountIdFromEnv();\n if (!accountId) {\n throw new Error(\n \"account_id required in config or CLOUDFLARE_ACCOUNT_ID env var\",\n );\n }\n const api = new CFApiClient(accountId);\n const state = new StateManager(\n config.tenant.id,\n report.env,\n stackNameForConfig(config),\n );\n await state.hydrate(api);\n const cloudflareSnapshot = await buildCloudflareSnapshot({\n config,\n env: report.env,\n api,\n baseDir: process.cwd(),\n });\n const attestation = computeAttestation(\n config,\n state.load(),\n cloudflareSnapshot,\n );\n writePlanFile(outPath, {\n format: PLAN_FILE_FORMAT,\n tenantId: config.tenant.id,\n env: report.env,\n generatedAt: report.generatedAt,\n report,\n attestation,\n });\n}\n\nfunction printHumanPlan(report: PlanReport): void {\n const isDestroy = report.mode === \"destroy\";\n const heading = isDestroy ? \"Destroy plan\" : \"Plan\";\n console.log(\n `\\n${heading} — tenant ${report.tenantId}, env ${report.env}\\n`,\n );\n if (report.items.length === 0) {\n if (isDestroy) {\n console.log(\" (nothing to destroy — no managed resources in state)\\n\");\n } else {\n console.log(\" (no changes — infrastructure matches config)\\n\");\n }\n return;\n }\n const grouped = new Map<PlanResourceKind, PlanItem[]>();\n for (const it of report.items) {\n const arr = grouped.get(it.kind) ?? [];\n arr.push(it);\n grouped.set(it.kind, arr);\n }\n for (const [kind, items] of grouped) {\n console.log(`${labelFor(kind)} (${items.length}):`);\n for (const it of items) {\n const sign = signFor(it.action);\n // Suppress `detail` on update/replace items when we have a structured\n // `changes[]` block — the per-field renderer below already shows the\n // same drift in terraform-style form, and printing both duplicates\n // the diff and clutters the output.\n const hasChanges = it.changes && it.changes.length > 0;\n const detail = it.detail && !hasChanges ? ` (${it.detail})` : \"\";\n console.log(` ${sign} ${it.logicalName} -> ${it.derivedName}${detail}`);\n for (const line of formatPlanFieldChanges(it.changes)) {\n console.log(line);\n }\n }\n }\n const { byAction } = planSummary(report.items);\n if (isDestroy) {\n console.log(`\\nSummary: ${byAction.delete} to delete.\\n`);\n } else {\n console.log(\n `\\nSummary: ${byAction.create} to create, ${byAction.update} to update, ${byAction.replace} to replace.\\n`,\n );\n }\n}\n\nfunction labelFor(kind: PlanResourceKind): string {\n switch (kind) {\n case \"d1\":\n return \"D1\";\n case \"r2\":\n return \"R2\";\n case \"kv\":\n return \"KV\";\n case \"queue\":\n return \"Queues\";\n case \"hyperdrive\":\n return \"Hyperdrive\";\n case \"vectorize\":\n return \"Vectorize\";\n case \"ai_gateway\":\n return \"AI Gateway\";\n case \"pipeline\":\n return \"Pipeline\";\n case \"workflow\":\n return \"Workflow\";\n case \"secret_store\":\n return \"Secrets Store\";\n case \"secret\":\n return \"Worker secrets\";\n case \"dns_record\":\n return \"DNS records\";\n case \"dispatch_namespace\":\n return \"Dispatch namespaces\";\n case \"logpush_job\":\n return \"Logpush jobs\";\n case \"worker_route\":\n return \"Workers zone routes\";\n case \"worker_script\":\n return \"Worker scripts\";\n }\n}\n"],"mappings":"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"}
@@ -2726,30 +2726,38 @@ async function workflowsDestroy(_env, state, api, config, baseDir, _force) {
2726
2726
  //#endregion
2727
2727
  //#region src/features/workflows/workflows.generate.ts
2728
2728
  /**
2729
- * Emit `workflows[]` bindings for declared workflows. Requires `tamer apply`
2730
- * to have run (state row present) so we can fail fast with a clear message
2731
- * before `wrangler deploy` would surface a less actionable error.
2732
- *
2733
- * `script_name` is only emitted when the user pinned an explicit override
2734
- * without it, Wrangler binds to the current worker's own script, which
2735
- * matches our default-to-owning-worker semantics in apply.
2729
+ * Emit `workflows[]` bindings for declared workflows. When the workflow is
2730
+ * already in state (normal case after apply), uses the recorded values.
2731
+ * When not in state (deferred during apply because the script wasn't
2732
+ * deployed yet), derives binding values from config + naming engine so
2733
+ * wrangler.json can be generated deploy will register the workflow
2734
+ * after the script is live.
2736
2735
  */
2737
2736
  function workflowsGenerate(resources, env, state, naming, worker) {
2738
2737
  const out = [];
2739
2738
  for (const config of resources) {
2740
2739
  const derivedName = workflowDeriveName(config, env, naming);
2741
2740
  const entry = state.get(`workflow:${derivedName}`);
2742
- if (!entry || entry.type !== "workflow") throw new Error(`Workflow "${config.logicalName}" not in state. Run 'tamer apply --env ${env}' first.`);
2743
2741
  const explicitScript = config.scriptName?.trim();
2744
2742
  const ownerScript = worker?.deployedName;
2745
- const binding = {
2746
- binding: entry.bindingKey,
2747
- name: entry.derivedName,
2748
- class_name: entry.className
2749
- };
2750
- if (explicitScript) binding.script_name = explicitScript;
2751
- else if (ownerScript && ownerScript !== entry.scriptName) binding.script_name = entry.scriptName;
2752
- out.push(binding);
2743
+ if (entry && entry.type === "workflow") {
2744
+ const binding = {
2745
+ binding: entry.bindingKey,
2746
+ name: entry.derivedName,
2747
+ class_name: entry.className
2748
+ };
2749
+ if (explicitScript) binding.script_name = explicitScript;
2750
+ else if (ownerScript && ownerScript !== entry.scriptName) binding.script_name = entry.scriptName;
2751
+ out.push(binding);
2752
+ } else {
2753
+ const binding = {
2754
+ binding: config.binding?.trim() ?? naming.workflowBindingKey(config.logicalName),
2755
+ name: derivedName,
2756
+ class_name: config.className
2757
+ };
2758
+ if (explicitScript) binding.script_name = explicitScript;
2759
+ out.push(binding);
2760
+ }
2753
2761
  }
2754
2762
  return out;
2755
2763
  }
@@ -3086,4 +3094,4 @@ function getResourceModule(kind) {
3086
3094
 
3087
3095
  //#endregion
3088
3096
  export { logicalNamesForResourceKind as a, workflowsApply as i, resourceModules as n, d1CloudflareDatabaseName as o, secretsStoreDeriveName as r, d1SkipsProvisionAndMigrate as s, getResourceModule as t };
3089
- //# sourceMappingURL=registry-Du91dVUE.mjs.map
3097
+ //# sourceMappingURL=registry-BlTu3xMp.mjs.map