@dragonmastery/tamer 0.43.0 → 0.43.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.
- package/dist/SecretsVault-DfizRmnN.mjs +3 -0
- package/dist/{apply-J081Hfqc.mjs → apply--ZUmkGmn.mjs} +15 -15
- package/dist/{apply-J081Hfqc.mjs.map → apply--ZUmkGmn.mjs.map} +1 -1
- package/dist/{applyTarget-CTsOrwco.mjs → applyTarget-wzX8VpIA.mjs} +3 -3
- package/dist/{applyTarget-CTsOrwco.mjs.map → applyTarget-wzX8VpIA.mjs.map} +1 -1
- package/dist/{bootstrap-D663FPHF.mjs → bootstrap-5Y32Z1bF.mjs} +4 -4
- package/dist/{bootstrap-D663FPHF.mjs.map → bootstrap-5Y32Z1bF.mjs.map} +1 -1
- package/dist/{cloudflareSnapshot-dKYupX1L.mjs → cloudflareSnapshot-4-UXu92B.mjs} +5 -5
- package/dist/{cloudflareSnapshot-dKYupX1L.mjs.map → cloudflareSnapshot-4-UXu92B.mjs.map} +1 -1
- package/dist/crypto-Dsq43xcZ.mjs +3 -0
- package/dist/{deploy-N8cVT5M9.mjs → deploy-Don2YRuA.mjs} +10 -10
- package/dist/{deploy-N8cVT5M9.mjs.map → deploy-Don2YRuA.mjs.map} +1 -1
- package/dist/{destroy-kkOR_ZBj.mjs → destroy-DgSIeg8b.mjs} +11 -11
- package/dist/{destroy-kkOR_ZBj.mjs.map → destroy-DgSIeg8b.mjs.map} +1 -1
- package/dist/{destroy-tenant-DdObj9pd.mjs → destroy-tenant-BKnB1Edp.mjs} +2 -2
- package/dist/{destroy-tenant-DdObj9pd.mjs.map → destroy-tenant-BKnB1Edp.mjs.map} +1 -1
- package/dist/{dev-zQRvUhdd.mjs → dev-BicIlxwR.mjs} +8 -8
- package/dist/{dev-zQRvUhdd.mjs.map → dev-BicIlxwR.mjs.map} +1 -1
- package/dist/{dns-records.resolve-CUQz6Ivz.mjs → dns-records.resolve-CKl_m1wD.mjs} +1 -1
- package/dist/{dns-records.resolve-CUQz6Ivz.mjs.map → dns-records.resolve-CKl_m1wD.mjs.map} +1 -1
- package/dist/{dns-records.resolve-Dge2kukJ.mjs → dns-records.resolve-N4VzbSlZ.mjs} +1 -1
- package/dist/{dns-records.sync-BnOga2LS.mjs → dns-records.sync-CgZrG_ok.mjs} +3 -3
- package/dist/{dns-records.sync-BnOga2LS.mjs.map → dns-records.sync-CgZrG_ok.mjs.map} +1 -1
- package/dist/{doctor-yTe9cUO5.mjs → doctor-ClV1tg9D.mjs} +2 -2
- package/dist/{doctor-yTe9cUO5.mjs.map → doctor-ClV1tg9D.mjs.map} +1 -1
- package/dist/{drift-B-oPsgPW.mjs → drift-BnJdi6-8.mjs} +7 -7
- package/dist/{drift-B-oPsgPW.mjs.map → drift-BnJdi6-8.mjs.map} +1 -1
- package/dist/drift-CjNvtmAK.mjs +8 -0
- package/dist/{emit-DDH99lzS.mjs → emit-C_sNJiL_.mjs} +3 -3
- package/dist/{emit-DDH99lzS.mjs.map → emit-C_sNJiL_.mjs.map} +1 -1
- package/dist/{env-gc-DrXu7DlG.mjs → env-gc-DcOtNJkG.mjs} +11 -11
- package/dist/{env-gc-DrXu7DlG.mjs.map → env-gc-DcOtNJkG.mjs.map} +1 -1
- package/dist/{env-list-D-wnLl8W.mjs → env-list-B1c0zuhI.mjs} +2 -2
- package/dist/{env-list-D-wnLl8W.mjs.map → env-list-B1c0zuhI.mjs.map} +1 -1
- package/dist/{events-C86vo1gV.mjs → events-tGFCV-57.mjs} +2 -2
- package/dist/{events-C86vo1gV.mjs.map → events-tGFCV-57.mjs.map} +1 -1
- package/dist/{generator-Cu5XUkjr.mjs → generator--De7tWLA.mjs} +2 -2
- package/dist/{generator-Cu5XUkjr.mjs.map → generator--De7tWLA.mjs.map} +1 -1
- package/dist/{import-QJc0fI1h.mjs → import-DbHvqNRp.mjs} +6 -6
- package/dist/{import-QJc0fI1h.mjs.map → import-DbHvqNRp.mjs.map} +1 -1
- package/dist/loader-O14iKXe5.mjs +3 -0
- package/dist/{logpush-job-DZG-3nkJ.mjs → logpush-job-CfBhpy6e.mjs} +2 -2
- package/dist/{logpush-job-DZG-3nkJ.mjs.map → logpush-job-CfBhpy6e.mjs.map} +1 -1
- package/dist/masterKey-DjpTmsn9.mjs +3 -0
- package/dist/{migrate-v1UQCzNi.mjs → migrate-y4tazUUK.mjs} +6 -6
- package/dist/{migrate-v1UQCzNi.mjs.map → migrate-y4tazUUK.mjs.map} +1 -1
- package/dist/{plan-Dp2aTzEU.mjs → plan-WLsyswyB.mjs} +12 -12
- package/dist/{plan-Dp2aTzEU.mjs.map → plan-WLsyswyB.mjs.map} +1 -1
- package/dist/{planFormat-C8mq3sbD.mjs → planFormat-DHjPdYzr.mjs} +1 -1
- package/dist/{planFormat-C8mq3sbD.mjs.map → planFormat-DHjPdYzr.mjs.map} +1 -1
- package/dist/{provision-tenant-DEscl-jo.mjs → provision-tenant-wGPK8MRJ.mjs} +18 -21
- package/dist/{provision-tenant-DEscl-jo.mjs.map → provision-tenant-wGPK8MRJ.mjs.map} +1 -1
- package/dist/{r2S3EmptyBucket-CDE2lTM8.mjs → r2S3EmptyBucket-OKIQdlms.mjs} +1 -1
- package/dist/{r2S3EmptyBucket-CDE2lTM8.mjs.map → r2S3EmptyBucket-OKIQdlms.mjs.map} +1 -1
- package/dist/{registry-CRgobiU4.mjs → registry-D3heJge_.mjs} +4 -4
- package/dist/{registry-CRgobiU4.mjs.map → registry-D3heJge_.mjs.map} +1 -1
- package/dist/{resolveTenantBindings-CXLwAG0F.mjs → resolveTenantBindings-C-3w6e22.mjs} +2 -2
- package/dist/{resolveTenantBindings-CXLwAG0F.mjs.map → resolveTenantBindings-C-3w6e22.mjs.map} +1 -1
- package/dist/secretsDb-B_xO1fwY.mjs +3 -0
- package/dist/{stackOutputs-BMPahRAl.mjs → stackOutputs-DAarU7Q-.mjs} +2 -2
- package/dist/{stackOutputs-BMPahRAl.mjs.map → stackOutputs-DAarU7Q-.mjs.map} +1 -1
- package/dist/{status-CQpgTTeG.mjs → status-h6i0BF9r.mjs} +7 -7
- package/dist/{status-CQpgTTeG.mjs.map → status-h6i0BF9r.mjs.map} +1 -1
- package/dist/{sync-DqMMQ7mQ.mjs → sync-DEflZagk.mjs} +6 -6
- package/dist/{sync-DqMMQ7mQ.mjs.map → sync-DEflZagk.mjs.map} +1 -1
- package/dist/sync-DWsBVffD.mjs +7 -0
- package/dist/tamer.mjs +26 -25
- package/dist/tamer.mjs.map +1 -1
- package/dist/{tamerArtifactsR2-BePICoT8.mjs → tamerArtifactsR2--lL7OBaD.mjs} +2 -2
- package/dist/{tamerArtifactsR2-BePICoT8.mjs.map → tamerArtifactsR2--lL7OBaD.mjs.map} +1 -1
- package/dist/{tenant-2XTWe9j0.mjs → tenant-BH_JoT9H.mjs} +2 -2
- package/dist/{tenant-2XTWe9j0.mjs.map → tenant-BH_JoT9H.mjs.map} +1 -1
- package/dist/{tenant-migrate-B-n4ppSk.mjs → tenant-migrate-C8OXJtYf.mjs} +5 -5
- package/dist/{tenant-migrate-B-n4ppSk.mjs.map → tenant-migrate-C8OXJtYf.mjs.map} +1 -1
- package/dist/{types-BCTgIotG.mjs → types-ChGaMTT3.mjs} +6 -6
- package/dist/{types-BCTgIotG.mjs.map → types-ChGaMTT3.mjs.map} +1 -1
- package/dist/{verifyPlanFile-DrFZjkVY.mjs → verifyPlanFile-BX1NO3yj.mjs} +2 -2
- package/dist/{verifyPlanFile-DrFZjkVY.mjs.map → verifyPlanFile-BX1NO3yj.mjs.map} +1 -1
- package/dist/{wfp-delete-BblKos51.mjs → wfp-delete-Bt00-azB.mjs} +2 -2
- package/dist/{wfp-delete-BblKos51.mjs.map → wfp-delete-Bt00-azB.mjs.map} +1 -1
- package/dist/{wfp-put-oYvLiM_c.mjs → wfp-put-ZLt3oVZo.mjs} +2 -2
- package/dist/{wfp-put-oYvLiM_c.mjs.map → wfp-put-ZLt3oVZo.mjs.map} +1 -1
- package/dist/{worker-route-BPDAwUL0.mjs → worker-route-u4qd5ddC.mjs} +3 -3
- package/dist/{worker-route-BPDAwUL0.mjs.map → worker-route-u4qd5ddC.mjs.map} +1 -1
- package/dist/{workers-D08-ri2Q.mjs → workers-CEPTZmdi.mjs} +2 -2
- package/dist/{workers-D08-ri2Q.mjs.map → workers-CEPTZmdi.mjs.map} +1 -1
- package/dist/wranglerOutFile-BwTiiBhi.mjs +3 -0
- package/dist/{wranglerSpawn-Dg-lr-rf.mjs → wranglerSpawn-DX7fppPf.mjs} +1 -1
- package/dist/{wranglerSpawn-Dg-lr-rf.mjs.map → wranglerSpawn-DX7fppPf.mjs.map} +1 -1
- package/dist/wranglerSpawn-PEDw4vkF.mjs +3 -0
- package/dist/{zoneResolver-Dfp2glm8.mjs → zoneResolver-BPrCHiEV.mjs} +1 -1
- package/dist/{zoneResolver-Dfp2glm8.mjs.map → zoneResolver-BPrCHiEV.mjs.map} +1 -1
- package/package.json +1 -1
- package/dist/SecretsVault-D_JTJm5s.mjs +0 -3
- package/dist/crypto-5HwDmS4B.mjs +0 -3
- package/dist/drift-RsT8UGQw.mjs +0 -8
- package/dist/fingerprint-D6E5FTkq.mjs +0 -3
- package/dist/loader-DY6STQU3.mjs +0 -3
- package/dist/masterKey-D9gq3eNP.mjs +0 -3
- package/dist/secretsDb-CMEcKvSi.mjs +0 -3
- package/dist/sync-Blz5emYO.mjs +0 -7
- package/dist/wranglerOutFile-eViugwTw.mjs +0 -3
- package/dist/wranglerSpawn-BUOsdo5R.mjs +0 -3
- /package/dist/{secrets-DVNZwGUS.mjs → secrets-CUBA2cK4.mjs} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan-Dp2aTzEU.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-WLsyswyB.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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"}
|
|
@@ -116,4 +116,4 @@ function printApplySummary() {
|
|
|
116
116
|
|
|
117
117
|
//#endregion
|
|
118
118
|
export { signFor as a, resetApplySummary as i, logApplyChange as n, printApplySummary as r, formatPlanFieldChanges as t };
|
|
119
|
-
//# sourceMappingURL=planFormat-
|
|
119
|
+
//# sourceMappingURL=planFormat-DHjPdYzr.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planFormat-
|
|
1
|
+
{"version":3,"file":"planFormat-DHjPdYzr.mjs","names":["applySummary: Record<PlanAction, number>"],"sources":["../src/core/plan/planFormat.ts"],"sourcesContent":["/**\n * Shared rendering for terraform-style plan output. Used by:\n *\n * - `tamer plan` (human mode) — field-level diffs under each\n * `update` / `replace` line so reviewers see exactly which fields\n * would change before they sign off on a saved plan.\n * - `tamer apply` — per-field log lines when each in-place patch fires,\n * so the operator sees the same diffs at apply time that they\n * reviewed at plan time. Without this they'd just see \"applied\" and\n * have no feedback that the PATCH actually targeted the same fields.\n * - `*Apply.ts` modules — register `logApplyChange()` at the PATCH /\n * PUT call site so the console output is uniform across every\n * resource kind.\n *\n * Pure functions only — no I/O, no mutation. Stays trivially testable\n * and reusable from the JSON serializer if we ever need a structured\n * audit log alongside the human stream.\n */\n\nimport type {\n PlanAction,\n PlanFieldChange,\n PlanResourceKind,\n} from \"./plan.types.js\";\n\n/**\n * Format a single field value for human display. Truncates long strings\n * (Hyperdrive origins / DNS TXT content can be enormous) and uses a\n * sentinel for `undefined` so reviewers can tell a missing value apart\n * from an empty string.\n */\nexport function formatPlanValue(v: unknown): string {\n if (v === undefined) return \"(unset)\";\n if (v === null) return \"(null)\";\n if (typeof v === \"string\") {\n return v.length > 64 ? `${v.slice(0, 61)}...` : v;\n }\n if (typeof v === \"object\") {\n try {\n const json = JSON.stringify(v);\n return json.length > 64 ? `${json.slice(0, 61)}...` : json;\n } catch {\n return String(v);\n }\n }\n return String(v);\n}\n\n/**\n * One line of terraform-style attribute diff:\n *\n * ~ origin.host: postgres-old.example.com -> postgres-new.example.com\n *\n * Mutable changes use `~`; immutable changes use `±` (matches the\n * action sign in `signFor` so the eye can follow what triggers a\n * `replace`).\n */\nexport function formatPlanFieldChange(c: PlanFieldChange): string {\n const sign = c.kind === \"immutable\" ? \"±\" : \"~\";\n return `${sign} ${c.field}: ${formatPlanValue(c.from)} -> ${formatPlanValue(\n c.to,\n )}`;\n}\n\n/**\n * Render a block of field changes with consistent indentation. Used by\n * both `tamer plan` (under each item) and `tamer apply` (after each\n * PATCH / PUT). Returns one line per change; the caller is responsible\n * for `console.log`ing them.\n */\nexport function formatPlanFieldChanges(\n changes: readonly PlanFieldChange[] | undefined,\n indent: string = \" \",\n): string[] {\n if (!changes || changes.length === 0) return [];\n return changes.map((c) => `${indent}${formatPlanFieldChange(c)}`);\n}\n\n/**\n * Sign character for a plan action. Matches what the human plan\n * renderer prints next to each item (and what apply echoes back so the\n * eye can correlate plan rows to apply rows).\n */\nexport function signFor(action: PlanAction): string {\n switch (action) {\n case \"create\":\n return \"+\";\n case \"update\":\n return \"~\";\n case \"replace\":\n return \"±\";\n case \"delete\":\n return \"-\";\n case \"no_change\":\n return \" \";\n }\n}\n\n/**\n * Apply-time log emitter. Modules call this at the PATCH / PUT / POST\n * call site so the console output is uniform across every kind:\n *\n * ~ hyperdrive db -> hd-db-t-acme-prod\n * ~ origin.host: pg-old.example.com -> pg-new.example.com\n * ~ origin.database: appdb -> appdb_v2\n *\n * `logical` is the user's `tamer.config.ts` logical name; `derived` is\n * the Cloudflare-side name/id Tamer chose. `changes` is optional —\n * `create` rows print just the header line.\n *\n * Side effect: increments the module-scoped {@link applySummary} so\n * `runApply` can print a terraform-style \"Applied: N created, N updated,\n * N replaced.\" summary at the end. The counter is process-global; call\n * {@link resetApplySummary} at the start of each apply run.\n */\nexport function logApplyChange(args: {\n kind: PlanResourceKind;\n action: PlanAction;\n logical: string;\n derived: string;\n changes?: readonly PlanFieldChange[];\n}): void {\n const { kind, action, logical, derived, changes } = args;\n const sign = signFor(action);\n console.log(` ${sign} ${kind} ${logical} -> ${derived}`);\n for (const line of formatPlanFieldChanges(changes)) {\n console.log(line);\n }\n recordApplyAction(action);\n}\n\n/**\n * Counter for what `tamer apply` actually did this run, mirroring\n * `planSummary` so the final summary line at apply end matches the\n * `Summary: …` line `tamer plan` prints. `delete` is included so the\n * same counter survives the future `apply --plan <destroy.json>` path\n * (currently `runDestroy` owns deletes; the slot is reserved for the\n * unified-apply future).\n */\nconst applySummary: Record<PlanAction, number> = {\n create: 0,\n update: 0,\n replace: 0,\n delete: 0,\n no_change: 0,\n};\n\nfunction recordApplyAction(action: PlanAction): void {\n applySummary[action]++;\n}\n\n/** Reset before each `runApply` so summaries don't leak between invocations. */\nexport function resetApplySummary(): void {\n applySummary.create = 0;\n applySummary.update = 0;\n applySummary.replace = 0;\n applySummary.delete = 0;\n applySummary.no_change = 0;\n}\n\n/** Snapshot the counter for printing / testing. */\nexport function getApplySummary(): Readonly<Record<PlanAction, number>> {\n return { ...applySummary };\n}\n\n/**\n * Print the final summary line at the bottom of `tamer apply` output —\n * matches the \"Summary: N to create, N to update, N to replace.\" line\n * `tamer plan` prints, but in past tense and with whatever apply\n * actually did.\n */\nexport function printApplySummary(): void {\n const s = applySummary;\n console.log(\n `Summary: ${s.create} created, ${s.update} updated, ${s.replace} replaced.`,\n );\n}\n"],"mappings":";;;;;;;AA+BA,SAAgB,gBAAgB,GAAoB;AAClD,KAAI,MAAM,OAAW,QAAO;AAC5B,KAAI,MAAM,KAAM,QAAO;AACvB,KAAI,OAAO,MAAM,SACf,QAAO,EAAE,SAAS,KAAK,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,OAAO;AAElD,KAAI,OAAO,MAAM,SACf,KAAI;EACF,MAAM,OAAO,KAAK,UAAU,EAAE;AAC9B,SAAO,KAAK,SAAS,KAAK,GAAG,KAAK,MAAM,GAAG,GAAG,CAAC,OAAO;SAChD;AACN,SAAO,OAAO,EAAE;;AAGpB,QAAO,OAAO,EAAE;;;;;;;;;;;AAYlB,SAAgB,sBAAsB,GAA4B;AAEhE,QAAO,GADM,EAAE,SAAS,cAAc,MAAM,IAC7B,GAAG,EAAE,MAAM,IAAI,gBAAgB,EAAE,KAAK,CAAC,MAAM,gBAC1D,EAAE,GACH;;;;;;;;AASH,SAAgB,uBACd,SACA,SAAiB,UACP;AACV,KAAI,CAAC,WAAW,QAAQ,WAAW,EAAG,QAAO,EAAE;AAC/C,QAAO,QAAQ,KAAK,MAAM,GAAG,SAAS,sBAAsB,EAAE,GAAG;;;;;;;AAQnE,SAAgB,QAAQ,QAA4B;AAClD,SAAQ,QAAR;EACE,KAAK,SACH,QAAO;EACT,KAAK,SACH,QAAO;EACT,KAAK,UACH,QAAO;EACT,KAAK,SACH,QAAO;EACT,KAAK,YACH,QAAO;;;;;;;;;;;;;;;;;;;;AAqBb,SAAgB,eAAe,MAMtB;CACP,MAAM,EAAE,MAAM,QAAQ,SAAS,SAAS,YAAY;CACpD,MAAM,OAAO,QAAQ,OAAO;AAC5B,SAAQ,IAAI,KAAK,KAAK,GAAG,KAAK,GAAG,QAAQ,MAAM,UAAU;AACzD,MAAK,MAAM,QAAQ,uBAAuB,QAAQ,CAChD,SAAQ,IAAI,KAAK;AAEnB,mBAAkB,OAAO;;;;;;;;;;AAW3B,MAAMA,eAA2C;CAC/C,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,WAAW;CACZ;AAED,SAAS,kBAAkB,QAA0B;AACnD,cAAa;;;AAIf,SAAgB,oBAA0B;AACxC,cAAa,SAAS;AACtB,cAAa,SAAS;AACtB,cAAa,UAAU;AACvB,cAAa,SAAS;AACtB,cAAa,YAAY;;;;;;;;AAc3B,SAAgB,oBAA0B;CACxC,MAAM,IAAI;AACV,SAAQ,IACN,YAAY,EAAE,OAAO,YAAY,EAAE,OAAO,YAAY,EAAE,QAAQ,YACjE"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { f as getDispatchNamespaces } from "./normalize-DVSTRZhO.mjs";
|
|
2
|
-
import { $ as
|
|
3
|
-
import "./r2S3EmptyBucket-
|
|
4
|
-
import { n as logApplyChange } from "./planFormat-
|
|
5
|
-
import { i as tamerArtifactsKeyPrefix, r as tamerArtifactsBucketName } from "./tamerArtifactsR2
|
|
6
|
-
import { resolveTenantBindings } from "./resolveTenantBindings-
|
|
2
|
+
import { $ as cloudflareAccountIdFromEnv, C as tenantStateKey, O as stackNameForConfig, Q as CFApiClient, R as effectiveDispatchNamespaceName, S as tenantShardDatabaseName, b as parseTenantShardRoles, m as namingFromConfig, rt as loadConfig, x as tenantDispatchScriptName, y as StateManager } from "./tamer.mjs";
|
|
3
|
+
import "./r2S3EmptyBucket-OKIQdlms.mjs";
|
|
4
|
+
import { n as logApplyChange } from "./planFormat-DHjPdYzr.mjs";
|
|
5
|
+
import { i as tamerArtifactsKeyPrefix, r as tamerArtifactsBucketName } from "./tamerArtifactsR2--lL7OBaD.mjs";
|
|
6
|
+
import { resolveTenantBindings } from "./resolveTenantBindings-C-3w6e22.mjs";
|
|
7
7
|
import { basename, resolve } from "path";
|
|
8
8
|
|
|
9
9
|
//#region src/cli/commands/provision-tenant.ts
|
|
@@ -133,8 +133,8 @@ async function provisionTenantInner(options) {
|
|
|
133
133
|
if (options.migrate !== false && shards.length > 0) await migrateTenantShards(config, shards, env, naming);
|
|
134
134
|
const { writeFileSync, unlinkSync, mkdirSync } = await import("fs");
|
|
135
135
|
const { join: joinPath, dirname: dirnamePath } = await import("path");
|
|
136
|
-
const { spawnWranglerSync } = await import("./wranglerSpawn-
|
|
137
|
-
const { wranglerConfigCliArgs } = await import("./wranglerOutFile-
|
|
136
|
+
const { spawnWranglerSync } = await import("./wranglerSpawn-PEDw4vkF.mjs");
|
|
137
|
+
const { wranglerConfigCliArgs } = await import("./wranglerOutFile-BwTiiBhi.mjs");
|
|
138
138
|
let workerDir;
|
|
139
139
|
let tempWranglerPath;
|
|
140
140
|
const tempWranglerFile = "wrangler.tenant.json";
|
|
@@ -197,26 +197,23 @@ async function provisionTenantInner(options) {
|
|
|
197
197
|
if (deployResult.status !== 0) throw new Error(`wrangler deploy failed for tenant ${scriptName} (exit ${deployResult.status})`);
|
|
198
198
|
const dispatchSecretNames = config.tenant.dispatchSecrets ?? [];
|
|
199
199
|
if (dispatchSecretNames.length > 0) {
|
|
200
|
-
const { ensureTamerSecretsDatabase } = await import("./secretsDb-
|
|
201
|
-
const { SecretsVault } = await import("./SecretsVault-
|
|
202
|
-
const { readMasterKeyFromEnv } = await import("./masterKey-
|
|
203
|
-
const {
|
|
204
|
-
const { encryptSecretValue } = await import("./crypto-5HwDmS4B.mjs");
|
|
200
|
+
const { ensureTamerSecretsDatabase } = await import("./secretsDb-B_xO1fwY.mjs");
|
|
201
|
+
const { SecretsVault } = await import("./SecretsVault-DfizRmnN.mjs");
|
|
202
|
+
const { readMasterKeyFromEnv } = await import("./masterKey-DjpTmsn9.mjs");
|
|
203
|
+
const { decryptSecretValue } = await import("./crypto-Dsq43xcZ.mjs");
|
|
205
204
|
const vault = new SecretsVault(api, env, await ensureTamerSecretsDatabase(api));
|
|
206
205
|
const masterKey = readMasterKeyFromEnv(env);
|
|
207
206
|
if (!masterKey) throw new Error(`dispatchSecrets: master key not found. Set TAMER_SECRETS_KEY_${env} in the environment.`);
|
|
208
|
-
console.log(`Pushing ${dispatchSecretNames.length} dispatch secret(s)…`);
|
|
207
|
+
console.log(`Pushing ${dispatchSecretNames.length} dispatch secret(s) to ${scriptName}…`);
|
|
209
208
|
for (const secretName of dispatchSecretNames) {
|
|
210
209
|
const record = await vault.get(secretName);
|
|
211
210
|
if (!record) throw new Error(`dispatchSecrets: secret "${secretName}" not found in vault for env ${env}. Run: tamer secrets set ${secretName} --env ${env}`);
|
|
212
|
-
await encryptSecretValue("", masterKey);
|
|
213
|
-
const { decryptSecretValue } = await import("./crypto-5HwDmS4B.mjs");
|
|
214
211
|
const plaintext = await decryptSecretValue(record, masterKey);
|
|
215
212
|
await api.dispatchNamespaceScriptSecretPut(dispatchNamespaceName, scriptName, {
|
|
216
213
|
name: secretName,
|
|
217
214
|
text: plaintext
|
|
218
215
|
});
|
|
219
|
-
console.log(`
|
|
216
|
+
console.log(` ✓ ${secretName} → ${scriptName}`);
|
|
220
217
|
}
|
|
221
218
|
}
|
|
222
219
|
state.setTenant(buildTenantEntry(entryOpts, "ready", existing, { d1Shards: shards }));
|
|
@@ -249,12 +246,12 @@ async function migrateTenantShards(config, shards, env, naming) {
|
|
|
249
246
|
const { writeFileSync, unlinkSync, mkdirSync } = await import("fs");
|
|
250
247
|
const { join: join$1 } = await import("path");
|
|
251
248
|
const { tmpdir } = await import("os");
|
|
252
|
-
const { spawnWranglerSync } = await import("./wranglerSpawn-
|
|
253
|
-
const { wranglerConfigCliArgs } = await import("./wranglerOutFile-
|
|
254
|
-
const { resolveTenantD1Bindings } = await import("./resolveTenantBindings-
|
|
249
|
+
const { spawnWranglerSync } = await import("./wranglerSpawn-PEDw4vkF.mjs");
|
|
250
|
+
const { wranglerConfigCliArgs } = await import("./wranglerOutFile-BwTiiBhi.mjs");
|
|
251
|
+
const { resolveTenantD1Bindings } = await import("./resolveTenantBindings-C-3w6e22.mjs");
|
|
255
252
|
const shardBindings = config.tenant.shardBindings ?? {};
|
|
256
253
|
const d1Bindings = resolveTenantD1Bindings(config, shards, naming);
|
|
257
|
-
const { getConfigBaseDir } = await import("./loader-
|
|
254
|
+
const { getConfigBaseDir } = await import("./loader-O14iKXe5.mjs");
|
|
258
255
|
const baseDir = getConfigBaseDir();
|
|
259
256
|
for (let i = 0; i < shards.length; i++) {
|
|
260
257
|
const shard = shards[i];
|
|
@@ -301,4 +298,4 @@ async function migrateTenantShards(config, shards, env, naming) {
|
|
|
301
298
|
|
|
302
299
|
//#endregion
|
|
303
300
|
export { runProvisionTenant };
|
|
304
|
-
//# sourceMappingURL=provision-tenant-
|
|
301
|
+
//# sourceMappingURL=provision-tenant-wGPK8MRJ.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provision-tenant-DEscl-jo.mjs","names":["result: ProvisionTenantResult","allD1: Awaited<ReturnType<typeof api.d1ListAll>> | null","shards: TenantD1ShardRef[]","workerDir: string","tempWranglerPath: string","wranglerConfig: Record<string, unknown>","d1Databases: Array<Record<string, string>>","vars: Record<string, string>","services: Array<Record<string, string>>","join"],"sources":["../src/cli/commands/provision-tenant.ts"],"sourcesContent":["import { basename, resolve } from \"path\";\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 { getDispatchNamespaces } from \"../../types.js\";\nimport { effectiveDispatchNamespaceName } from \"../../features/dispatch-namespace/dispatch-namespace.resolve.js\";\nimport {\n parseTenantShardRoles,\n tenantDispatchScriptName,\n tenantShardDatabaseName,\n tenantStateKey,\n} from \"../../core/tenant/tenantKeys.js\";\nimport {\n buildSingleModuleDispatchForm,\n buildSingleModuleDispatchFormFromBuffer,\n} from \"../../core/wfp/buildDispatchUploadForm.js\";\nimport { resolveTenantBindings } from \"../../core/tenant/resolveTenantBindings.js\";\nimport { namingFromConfig } from \"../../core/config/namingFromConfig.js\";\nimport { tamerArtifactsBucketName, tamerArtifactsKeyPrefix } from \"../../core/state/tamerArtifactsR2.js\";\nimport { logApplyChange } from \"../../core/plan/planFormat.js\";\nimport type { TenantD1ShardRef, TenantStateEntry } from \"../../types.js\";\n\nconst now = (): string => new Date().toISOString();\n\nfunction buildTenantEntry(\n options: {\n product: string;\n workspace: string;\n dispatchNamespaceName: string;\n scriptName: string;\n },\n status: TenantStateEntry[\"provisioningStatus\"],\n existing: TenantStateEntry | undefined,\n patch?: Partial<Pick<TenantStateEntry, \"d1Shards\">>,\n): TenantStateEntry {\n return {\n product: options.product,\n workspace: options.workspace,\n provisioningStatus: status,\n dispatchNamespaceName: options.dispatchNamespaceName,\n scriptName: options.scriptName,\n d1Shards: patch?.d1Shards ?? existing?.d1Shards,\n createdAt: existing?.createdAt ?? now(),\n updatedAt: now(),\n };\n}\n\n/**\n * Resolve the effective shard set for this provision call.\n *\n * 1. The configured layout (`tenant.d1Shards` in `tamer.config.ts`)\n * is the source of truth — any role used by `--shards` must\n * appear here, and `apply` / `drift` / other operators read the\n * same shape.\n * 2. When `--shards a,b` is passed, trim to that subset (preserving\n * configured order so plan/provision logs stay deterministic).\n * Extending the layout is an edit to `tamer.config.ts`, not a\n * CLI flag — that's why `parseTenantShardRoles` rejects roles\n * outside the configured set.\n * 3. When the config has no `tenant.d1Shards`, the resolved set is\n * empty: `provision-tenant` only uploads the dispatch script\n * (truly generic single-Worker tenant case).\n */\nfunction resolveShardRoles(\n configured: readonly string[],\n shardsRaw: string | undefined,\n): string[] {\n if (shardsRaw == null || shardsRaw.trim() === \"\") return [...configured];\n return parseTenantShardRoles(shardsRaw, configured);\n}\n\n/**\n * Idempotent tenant provisioning. Each call:\n *\n * 1. Marks the tenant `pending` if there's no record yet.\n * 2. For every shard role declared in `tenant.d1Shards` in\n * `tamer.config.ts` (or a `--shards` subset): looks up an\n * existing D1 by derived name (`tenantShardDatabaseName(role,\n * …)`), creating it via `d1Create` only when missing. Persists\n * the `(role, derivedName, cfId)` triple to\n * `state.tenants[…].d1Shards[]`.\n * 3. Marks the tenant `d1_created` once *all* requested shards\n * exist in state.\n * 4. Uploads the dispatch script (from `--main` or `--artifact-key`)\n * via the WFP dispatch namespace.\n * 5. Marks the tenant `ready`.\n *\n * When `tenant.d1Shards` is omitted, step 2/3 collapse to a no-op\n * (truly generic single-Worker tenant — only the dispatch script is\n * provisioned). Re-running with the same shard set is a no-op (state\n * already has those rows). Editing `tamer.config.ts` to **extend**\n * the layout (e.g. adding a new role) and re-running picks up the\n * missing shards without disturbing existing ones, so a tenant\n * created with `[\"app\"]` can be migrated to `[\"system\",\"app\",\n * \"history\"]` (or `[\"primary\",\"audit\"]`, or any other set) with one\n * config edit + one `provision-tenant` invocation. Re-running after\n * a config that **shrinks** the layout never deletes shards (use\n * `destroy-tenant` for that).\n */\n/**\n * Machine-readable result envelope emitted on the final stdout line\n * when `--json` is passed. Designed for the Cloudflare Container caller\n * (`provision-workflow`, see `docs/handoff.md` §7) so the Worker can\n * parse the result without scraping human logs. On success the Workflow\n * sees `status: \"ready\"` plus the materialized tenant shape; on\n * failure it sees `status: \"failed\"` with `error` + best-effort\n * `partial` state, and the process exits non-zero.\n */\nexport interface ProvisionTenantResult {\n status: \"ready\" | \"failed\" | \"noop\";\n tenantKey: string;\n product: string;\n workspace: string;\n env: string;\n scriptName: string;\n dispatchNamespaceName: string;\n shards: { role: string; derivedName: string; cfId: string }[];\n error?: string;\n}\n\nexport async function runProvisionTenant(options: {\n env: string;\n product: string;\n workspace: string;\n main?: string;\n artifactKey?: string;\n moduleName?: string;\n configPath?: string;\n compatibilityDate?: string;\n compatibilityFlags?: string[];\n /**\n * Raw `--shards` value (`\"system,app\"` etc.) — validated against\n * `tenant.d1Shards` from the loaded config. Omit to provision every\n * configured shard.\n */\n shardsRaw?: string;\n json?: boolean;\n migrate?: boolean;\n}): Promise<void> {\n const env = options.env;\n if (env === \"local\") {\n throw new Error(\n \"provision-tenant requires a non-local --env (Cloudflare-backed state).\",\n );\n }\n\n try {\n await provisionTenantInner(options);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (options.json) {\n // Failure envelope: we may have died before loadConfig, so we\n // can't ask `tenant.ephemeralEnvPattern` whether `env` would\n // get the `-{env}` suffix. Emit the non-ephemeral form as a\n // best-effort hint — `error` carries the real signal.\n const result: ProvisionTenantResult = {\n status: \"failed\",\n tenantKey: tenantStateKey(options.product, options.workspace),\n product: options.product,\n workspace: options.workspace,\n env,\n scriptName: `${options.product}-${options.workspace}`,\n dispatchNamespaceName: \"\",\n shards: [],\n error: msg,\n };\n process.stdout.write(JSON.stringify(result) + \"\\n\");\n }\n // Container caller (provision-workflow) reads exit code as\n // primary success/failure signal; JSON envelope on stdout is\n // secondary detail. Re-throw so the CLI surfaces the original\n // error in human mode.\n throw err;\n }\n}\n\nasync function provisionTenantInner(options: {\n env: string;\n product: string;\n workspace: string;\n main?: string;\n artifactKey?: string;\n moduleName?: string;\n configPath?: string;\n compatibilityDate?: string;\n compatibilityFlags?: string[];\n shardsRaw?: string;\n json?: boolean;\n migrate?: boolean;\n}): Promise<void> {\n const env = options.env;\n const config = await loadConfig(options.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 dns = getDispatchNamespaces(config);\n if (dns.length === 0) {\n throw new Error(\n \"provision-tenant requires at least one dispatch namespace in the Tamer project config (dispatchNamespaces).\",\n );\n }\n const nsConfig = dns[0]!;\n const dispatchNamespaceName = effectiveDispatchNamespaceName(\n nsConfig,\n env,\n config.tenant,\n );\n const scriptName = tenantDispatchScriptName(\n options.product,\n options.workspace,\n env,\n config.tenant,\n );\n const desiredRoles = resolveShardRoles(\n config.tenant.d1Shards ?? [],\n options.shardsRaw,\n );\n\n const api = new CFApiClient(accountId);\n const state = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n await state.hydrate(api);\n\n let existing = state.getTenant(options.product, options.workspace);\n // \"ready\" is a no-op only when the existing layout already covers\n // every requested shard role. Adding a new role re-opens the tenant\n // for incremental provisioning without forcing a full re-deploy.\n if (existing?.provisioningStatus === \"ready\") {\n const have = new Set((existing.d1Shards ?? []).map((s) => s.role));\n const missing = desiredRoles.filter((r) => !have.has(r));\n if (missing.length === 0) {\n console.log(\n `Tenant ${tenantStateKey(options.product, options.workspace)} already ready — shards up to date, redeploying script…`,\n );\n // Don't return — fall through to always redeploy the script.\n // Code may have changed since the last provision.\n // The shard creation loop below will skip existing shards.\n } else {\n console.log(\n `Tenant ${tenantStateKey(options.product, options.workspace)} ready but missing shards: ${missing.join(\", \")}; extending layout.`,\n );\n }\n }\n\n const compatDate =\n options.compatibilityDate ??\n config.compatibility_date ??\n new Date().toISOString().slice(0, 10);\n\n const entryOpts = {\n product: options.product,\n workspace: options.workspace,\n dispatchNamespaceName,\n scriptName,\n };\n\n if (!existing) {\n state.setTenant(buildTenantEntry(entryOpts, \"pending\", undefined));\n await state.persist(api);\n existing = state.getTenant(options.product, options.workspace)!;\n }\n\n // Reconcile each requested shard role. We list D1 once per call when\n // we need to (some role is missing from state) and reuse the result\n // for every subsequent role.\n let allD1: Awaited<ReturnType<typeof api.d1ListAll>> | null = null;\n const shards: TenantD1ShardRef[] = [...(existing.d1Shards ?? [])];\n\n for (const role of desiredRoles) {\n if (shards.find((s) => s.role === role)) continue;\n const derivedName = tenantShardDatabaseName(\n role,\n options.workspace,\n options.product,\n config.tenant.id,\n env,\n );\n if (allD1 === null) {\n allD1 = await api.d1ListAll();\n }\n let cfId = allD1.find((d) => d.name === derivedName)?.uuid;\n if (cfId) {\n // Adopt an existing D1 (e.g. partial-failure resume, or\n // operator created it out-of-band) without re-issuing POST.\n logApplyChange({\n kind: \"d1\",\n action: \"no_change\",\n logical: `${role}-shard`,\n derived: derivedName,\n });\n } else {\n logApplyChange({\n kind: \"d1\",\n action: \"create\",\n logical: `${role}-shard`,\n derived: derivedName,\n });\n const created = await api.d1Create(derivedName);\n cfId = created.uuid;\n }\n shards.push({ role, derivedName, cfId });\n // Persist after each shard so a network failure mid-loop leaves\n // the already-created shards recorded — `provision-tenant` is\n // resumable role-by-role.\n state.setTenant(\n buildTenantEntry(entryOpts, \"d1_created\", existing, { d1Shards: shards }),\n );\n await state.persist(api);\n existing = state.getTenant(options.product, options.workspace)!;\n }\n\n // Resolve dispatch script bindings from shards + config.\n const naming = namingFromConfig(config);\n const bindings = resolveTenantBindings(\n config,\n shards,\n naming,\n state,\n env,\n accountId,\n );\n\n // Optionally run migrations on each shard before uploading the script.\n if (options.migrate !== false && shards.length > 0) {\n await migrateTenantShards(config, shards, env, naming);\n }\n\n // Deploy the tenant worker via wrangler — handles TS compilation, WASM\n // packaging, node: polyfills, and binding resolution. We write a temporary\n // wrangler.json with the resolved tenant bindings, then run\n // `wrangler deploy --dispatch-namespace`.\n const { writeFileSync, unlinkSync, mkdirSync } = await import(\"fs\");\n const { join: joinPath, dirname: dirnamePath } = await import(\"path\");\n const { spawnWranglerSync } = await import(\"../../core/wrangler/wranglerSpawn.js\");\n const { wranglerConfigCliArgs } = await import(\"../../core/wrangler/wranglerOutFile.js\");\n\n let workerDir: string;\n let tempWranglerPath: string;\n const tempWranglerFile = \"wrangler.tenant.json\";\n\n // Build the temporary wrangler config for the tenant dispatch script\n const wranglerConfig: Record<string, unknown> = {\n name: scriptName,\n main: options.main ? basename(resolve(process.cwd(), options.main)) : (options.moduleName ?? \"worker.js\"),\n compatibility_date: compatDate,\n compatibility_flags: options.compatibilityFlags ?? [],\n };\n\n // Add resolved bindings to wrangler config\n if (bindings.bindings?.length) {\n // Convert unified bindings format to wrangler config format\n const d1Databases: Array<Record<string, string>> = [];\n const vars: Record<string, string> = {};\n const services: Array<Record<string, string>> = [];\n for (const b of bindings.bindings) {\n if (b.type === \"d1\") {\n d1Databases.push({ binding: b.name, database_id: b.id });\n } else if (b.type === \"plain_text\") {\n vars[b.name] = b.text;\n } else if (b.type === \"service\") {\n services.push({ name: b.name, service: b.service, ...(b.environment ? { environment: b.environment } : {}) });\n }\n }\n if (d1Databases.length) wranglerConfig.d1_databases = d1Databases;\n if (Object.keys(vars).length) wranglerConfig.vars = vars;\n if (services.length) wranglerConfig.services = services;\n }\n\n if (options.main) {\n const mainPath = resolve(process.cwd(), options.main);\n workerDir = dirnamePath(mainPath);\n wranglerConfig.main = basename(mainPath);\n tempWranglerPath = joinPath(workerDir, tempWranglerFile);\n } else if (options.artifactKey) {\n // Download pre-built bundle from R2 to a temp directory\n const { tmpdir } = await import(\"os\");\n const bucket = tamerArtifactsBucketName();\n const fullKey = `${tamerArtifactsKeyPrefix(env)}${options.artifactKey}`;\n const body = await api.r2GetObject(bucket, fullKey);\n const mod =\n options.moduleName?.trim() ||\n basename(options.artifactKey).replace(/\\.(zip|tar|gz)$/i, \"\") ||\n \"worker.js\";\n workerDir = joinPath(tmpdir(), `tamer-tenant-${Date.now()}`);\n mkdirSync(workerDir, { recursive: true });\n writeFileSync(joinPath(workerDir, mod), new Uint8Array(body));\n wranglerConfig.main = mod;\n tempWranglerPath = joinPath(workerDir, tempWranglerFile);\n } else {\n throw new Error(\"provision-tenant: provide --main or --artifact-key\");\n }\n\n writeFileSync(tempWranglerPath, JSON.stringify(wranglerConfig, null, 2));\n\n console.log(`Deploying tenant script ${scriptName} to ${dispatchNamespaceName} via wrangler…`);\n const deployResult = spawnWranglerSync(\n [\n \"wrangler\",\n ...wranglerConfigCliArgs(tempWranglerFile),\n \"deploy\",\n \"--dispatch-namespace\",\n dispatchNamespaceName,\n ],\n {\n cwd: workerDir,\n stdio: \"inherit\",\n },\n );\n\n // Clean up temp config\n try {\n unlinkSync(tempWranglerPath);\n } catch {\n /* best-effort */\n }\n\n if (deployResult.status !== 0) {\n throw new Error(\n `wrangler deploy failed for tenant ${scriptName} (exit ${deployResult.status})`,\n );\n }\n\n // Push dispatch secrets from the vault (dispatchSecrets on TenantMeta).\n // Same vault as regular worker secrets — just delivered to the dispatch\n // namespace script instead of a regular Worker.\n const dispatchSecretNames = config.tenant.dispatchSecrets ?? [];\n if (dispatchSecretNames.length > 0) {\n const { ensureTamerSecretsDatabase } = await import(\"../../core/secrets/secretsDb.js\");\n const { SecretsVault } = await import(\"../../core/secrets/SecretsVault.js\");\n const { readMasterKeyFromEnv } = await import(\"../../core/secrets/masterKey.js\");\n const { secretValueFingerprint } = await import(\"../../core/secrets/fingerprint.js\");\n const { encryptSecretValue } = await import(\"../../core/secrets/crypto.js\");\n\n const secretsUuid = await ensureTamerSecretsDatabase(api);\n const vault = new SecretsVault(api, env, secretsUuid);\n const masterKey = readMasterKeyFromEnv(env);\n if (!masterKey) {\n throw new Error(\n `dispatchSecrets: master key not found. Set TAMER_SECRETS_KEY_${env} in the environment.`,\n );\n }\n\n console.log(`Pushing ${dispatchSecretNames.length} dispatch secret(s)…`);\n for (const secretName of dispatchSecretNames) {\n const record = await vault.get(secretName);\n if (!record) {\n throw new Error(\n `dispatchSecrets: secret \"${secretName}\" not found in vault for env ${env}. Run: tamer secrets set ${secretName} --env ${env}`,\n );\n }\n // Decrypt the stored ciphertext\n const decrypted = await encryptSecretValue(\"\", masterKey); // dummy to get crypto ready\n // Actually use the vault's stored ciphertext + decrypt with master key\n const { decryptSecretValue } = await import(\"../../core/secrets/crypto.js\");\n const plaintext = await decryptSecretValue(record, masterKey);\n\n await api.dispatchNamespaceScriptSecretPut(\n dispatchNamespaceName,\n scriptName,\n { name: secretName, text: plaintext },\n );\n console.log(` dispatch secret: ${secretName} → ${scriptName}`);\n }\n }\n\n state.setTenant(\n buildTenantEntry(entryOpts, \"ready\", existing, { d1Shards: shards }),\n );\n await state.persist(api);\n\n const shardsSummary =\n shards.length > 0\n ? `shards: ${shards.map((s) => s.role).join(\", \")}`\n : \"no shards declared\";\n console.log(\n `Provisioned tenant ${tenantStateKey(options.product, options.workspace)} → script ${scriptName} in ${dispatchNamespaceName} (${shardsSummary})`,\n );\n\n if (options.json) {\n const result: ProvisionTenantResult = {\n status: \"ready\",\n tenantKey: tenantStateKey(options.product, options.workspace),\n product: options.product,\n workspace: options.workspace,\n env,\n scriptName,\n dispatchNamespaceName,\n shards: shards.map((s) => ({\n role: s.role,\n derivedName: s.derivedName,\n cfId: s.cfId,\n })),\n };\n process.stdout.write(JSON.stringify(result) + \"\\n\");\n }\n}\n\n/**\n * Run D1 migrations on each tenant shard by writing a temporary wrangler.json\n * with the shard binding and invoking `wrangler d1 migrations apply`.\n */\nasync function migrateTenantShards(\n config: import(\"../../types.js\").CfiConfig,\n shards: TenantD1ShardRef[],\n env: string,\n naming: import(\"../../core/naming/NamingEngine.js\").NamingEngine,\n): Promise<void> {\n const { writeFileSync, unlinkSync, mkdirSync } = await import(\"fs\");\n const { join } = await import(\"path\");\n const { tmpdir } = await import(\"os\");\n const { spawnWranglerSync } = await import(\n \"../../core/wrangler/wranglerSpawn.js\"\n );\n const { wranglerConfigCliArgs } = await import(\n \"../../core/wrangler/wranglerOutFile.js\"\n );\n const { resolveTenantD1Bindings } = await import(\n \"../../core/tenant/resolveTenantBindings.js\"\n );\n\n const shardBindings = config.tenant.shardBindings ?? {};\n const d1Bindings = resolveTenantD1Bindings(config, shards, naming);\n\n // Resolve base dir for relative migrationsDir paths\n const { getConfigBaseDir } = await import(\"../../core/config/loader.js\");\n const baseDir = getConfigBaseDir();\n\n for (let i = 0; i < shards.length; i++) {\n const shard = shards[i]!;\n const binding = d1Bindings[i]!;\n const info = shardBindings[shard.role];\n if (!info?.migrationsDir) {\n console.log(` migrate: skipping ${shard.role} (no migrationsDir in shardBindings)`);\n continue;\n }\n\n const migDir = resolve(baseDir, info.migrationsDir);\n const tempWranglerPath = join(migDir, `wrangler.${shard.role}.json`);\n const tempConfig = {\n name: `tenant-migrate-${shard.role}`,\n d1_databases: [{ binding: binding.name, database_id: shard.cfId, database_name: shard.derivedName, migrations_dir: \".\" }],\n compatibility_date: config.compatibility_date ?? \"2024-01-01\",\n };\n writeFileSync(tempWranglerPath, JSON.stringify(tempConfig, null, 2));\n\n const migrateArgs = [\n \"wrangler\",\n ...wranglerConfigCliArgs(`wrangler.${shard.role}.json`),\n \"d1\",\n \"migrations\",\n \"apply\",\n binding.name,\n \"--remote\",\n ];\n if (info.migrationsTable) {\n migrateArgs.push(\"--migrations-table\", info.migrationsTable);\n }\n\n console.log(` migrate: applying migrations for ${shard.role} (${shard.derivedName})...`);\n const result = spawnWranglerSync(migrateArgs, {\n cwd: migDir,\n stdio: \"inherit\",\n });\n if (result.status !== 0) {\n console.warn(` migrate: failed for ${shard.role} (exit ${result.status})`);\n }\n try {\n unlinkSync(tempWranglerPath);\n } catch {\n /* best-effort cleanup */\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"provision-tenant-wGPK8MRJ.mjs","names":["result: ProvisionTenantResult","allD1: Awaited<ReturnType<typeof api.d1ListAll>> | null","shards: TenantD1ShardRef[]","workerDir: string","tempWranglerPath: string","wranglerConfig: Record<string, unknown>","d1Databases: Array<Record<string, string>>","vars: Record<string, string>","services: Array<Record<string, string>>","join"],"sources":["../src/cli/commands/provision-tenant.ts"],"sourcesContent":["import { basename, resolve } from \"path\";\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 { getDispatchNamespaces } from \"../../types.js\";\nimport { effectiveDispatchNamespaceName } from \"../../features/dispatch-namespace/dispatch-namespace.resolve.js\";\nimport {\n parseTenantShardRoles,\n tenantDispatchScriptName,\n tenantShardDatabaseName,\n tenantStateKey,\n} from \"../../core/tenant/tenantKeys.js\";\nimport {\n buildSingleModuleDispatchForm,\n buildSingleModuleDispatchFormFromBuffer,\n} from \"../../core/wfp/buildDispatchUploadForm.js\";\nimport { resolveTenantBindings } from \"../../core/tenant/resolveTenantBindings.js\";\nimport { namingFromConfig } from \"../../core/config/namingFromConfig.js\";\nimport { tamerArtifactsBucketName, tamerArtifactsKeyPrefix } from \"../../core/state/tamerArtifactsR2.js\";\nimport { logApplyChange } from \"../../core/plan/planFormat.js\";\nimport type { TenantD1ShardRef, TenantStateEntry } from \"../../types.js\";\n\nconst now = (): string => new Date().toISOString();\n\nfunction buildTenantEntry(\n options: {\n product: string;\n workspace: string;\n dispatchNamespaceName: string;\n scriptName: string;\n },\n status: TenantStateEntry[\"provisioningStatus\"],\n existing: TenantStateEntry | undefined,\n patch?: Partial<Pick<TenantStateEntry, \"d1Shards\">>,\n): TenantStateEntry {\n return {\n product: options.product,\n workspace: options.workspace,\n provisioningStatus: status,\n dispatchNamespaceName: options.dispatchNamespaceName,\n scriptName: options.scriptName,\n d1Shards: patch?.d1Shards ?? existing?.d1Shards,\n createdAt: existing?.createdAt ?? now(),\n updatedAt: now(),\n };\n}\n\n/**\n * Resolve the effective shard set for this provision call.\n *\n * 1. The configured layout (`tenant.d1Shards` in `tamer.config.ts`)\n * is the source of truth — any role used by `--shards` must\n * appear here, and `apply` / `drift` / other operators read the\n * same shape.\n * 2. When `--shards a,b` is passed, trim to that subset (preserving\n * configured order so plan/provision logs stay deterministic).\n * Extending the layout is an edit to `tamer.config.ts`, not a\n * CLI flag — that's why `parseTenantShardRoles` rejects roles\n * outside the configured set.\n * 3. When the config has no `tenant.d1Shards`, the resolved set is\n * empty: `provision-tenant` only uploads the dispatch script\n * (truly generic single-Worker tenant case).\n */\nfunction resolveShardRoles(\n configured: readonly string[],\n shardsRaw: string | undefined,\n): string[] {\n if (shardsRaw == null || shardsRaw.trim() === \"\") return [...configured];\n return parseTenantShardRoles(shardsRaw, configured);\n}\n\n/**\n * Idempotent tenant provisioning. Each call:\n *\n * 1. Marks the tenant `pending` if there's no record yet.\n * 2. For every shard role declared in `tenant.d1Shards` in\n * `tamer.config.ts` (or a `--shards` subset): looks up an\n * existing D1 by derived name (`tenantShardDatabaseName(role,\n * …)`), creating it via `d1Create` only when missing. Persists\n * the `(role, derivedName, cfId)` triple to\n * `state.tenants[…].d1Shards[]`.\n * 3. Marks the tenant `d1_created` once *all* requested shards\n * exist in state.\n * 4. Uploads the dispatch script (from `--main` or `--artifact-key`)\n * via the WFP dispatch namespace.\n * 5. Marks the tenant `ready`.\n *\n * When `tenant.d1Shards` is omitted, step 2/3 collapse to a no-op\n * (truly generic single-Worker tenant — only the dispatch script is\n * provisioned). Re-running with the same shard set is a no-op (state\n * already has those rows). Editing `tamer.config.ts` to **extend**\n * the layout (e.g. adding a new role) and re-running picks up the\n * missing shards without disturbing existing ones, so a tenant\n * created with `[\"app\"]` can be migrated to `[\"system\",\"app\",\n * \"history\"]` (or `[\"primary\",\"audit\"]`, or any other set) with one\n * config edit + one `provision-tenant` invocation. Re-running after\n * a config that **shrinks** the layout never deletes shards (use\n * `destroy-tenant` for that).\n */\n/**\n * Machine-readable result envelope emitted on the final stdout line\n * when `--json` is passed. Designed for the Cloudflare Container caller\n * (`provision-workflow`, see `docs/handoff.md` §7) so the Worker can\n * parse the result without scraping human logs. On success the Workflow\n * sees `status: \"ready\"` plus the materialized tenant shape; on\n * failure it sees `status: \"failed\"` with `error` + best-effort\n * `partial` state, and the process exits non-zero.\n */\nexport interface ProvisionTenantResult {\n status: \"ready\" | \"failed\" | \"noop\";\n tenantKey: string;\n product: string;\n workspace: string;\n env: string;\n scriptName: string;\n dispatchNamespaceName: string;\n shards: { role: string; derivedName: string; cfId: string }[];\n error?: string;\n}\n\nexport async function runProvisionTenant(options: {\n env: string;\n product: string;\n workspace: string;\n main?: string;\n artifactKey?: string;\n moduleName?: string;\n configPath?: string;\n compatibilityDate?: string;\n compatibilityFlags?: string[];\n /**\n * Raw `--shards` value (`\"system,app\"` etc.) — validated against\n * `tenant.d1Shards` from the loaded config. Omit to provision every\n * configured shard.\n */\n shardsRaw?: string;\n json?: boolean;\n migrate?: boolean;\n}): Promise<void> {\n const env = options.env;\n if (env === \"local\") {\n throw new Error(\n \"provision-tenant requires a non-local --env (Cloudflare-backed state).\",\n );\n }\n\n try {\n await provisionTenantInner(options);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (options.json) {\n // Failure envelope: we may have died before loadConfig, so we\n // can't ask `tenant.ephemeralEnvPattern` whether `env` would\n // get the `-{env}` suffix. Emit the non-ephemeral form as a\n // best-effort hint — `error` carries the real signal.\n const result: ProvisionTenantResult = {\n status: \"failed\",\n tenantKey: tenantStateKey(options.product, options.workspace),\n product: options.product,\n workspace: options.workspace,\n env,\n scriptName: `${options.product}-${options.workspace}`,\n dispatchNamespaceName: \"\",\n shards: [],\n error: msg,\n };\n process.stdout.write(JSON.stringify(result) + \"\\n\");\n }\n // Container caller (provision-workflow) reads exit code as\n // primary success/failure signal; JSON envelope on stdout is\n // secondary detail. Re-throw so the CLI surfaces the original\n // error in human mode.\n throw err;\n }\n}\n\nasync function provisionTenantInner(options: {\n env: string;\n product: string;\n workspace: string;\n main?: string;\n artifactKey?: string;\n moduleName?: string;\n configPath?: string;\n compatibilityDate?: string;\n compatibilityFlags?: string[];\n shardsRaw?: string;\n json?: boolean;\n migrate?: boolean;\n}): Promise<void> {\n const env = options.env;\n const config = await loadConfig(options.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 dns = getDispatchNamespaces(config);\n if (dns.length === 0) {\n throw new Error(\n \"provision-tenant requires at least one dispatch namespace in the Tamer project config (dispatchNamespaces).\",\n );\n }\n const nsConfig = dns[0]!;\n const dispatchNamespaceName = effectiveDispatchNamespaceName(\n nsConfig,\n env,\n config.tenant,\n );\n const scriptName = tenantDispatchScriptName(\n options.product,\n options.workspace,\n env,\n config.tenant,\n );\n const desiredRoles = resolveShardRoles(\n config.tenant.d1Shards ?? [],\n options.shardsRaw,\n );\n\n const api = new CFApiClient(accountId);\n const state = new StateManager(\n config.tenant.id,\n env,\n stackNameForConfig(config),\n );\n await state.hydrate(api);\n\n let existing = state.getTenant(options.product, options.workspace);\n // \"ready\" is a no-op only when the existing layout already covers\n // every requested shard role. Adding a new role re-opens the tenant\n // for incremental provisioning without forcing a full re-deploy.\n if (existing?.provisioningStatus === \"ready\") {\n const have = new Set((existing.d1Shards ?? []).map((s) => s.role));\n const missing = desiredRoles.filter((r) => !have.has(r));\n if (missing.length === 0) {\n console.log(\n `Tenant ${tenantStateKey(options.product, options.workspace)} already ready — shards up to date, redeploying script…`,\n );\n // Don't return — fall through to always redeploy the script.\n // Code may have changed since the last provision.\n // The shard creation loop below will skip existing shards.\n } else {\n console.log(\n `Tenant ${tenantStateKey(options.product, options.workspace)} ready but missing shards: ${missing.join(\", \")}; extending layout.`,\n );\n }\n }\n\n const compatDate =\n options.compatibilityDate ??\n config.compatibility_date ??\n new Date().toISOString().slice(0, 10);\n\n const entryOpts = {\n product: options.product,\n workspace: options.workspace,\n dispatchNamespaceName,\n scriptName,\n };\n\n if (!existing) {\n state.setTenant(buildTenantEntry(entryOpts, \"pending\", undefined));\n await state.persist(api);\n existing = state.getTenant(options.product, options.workspace)!;\n }\n\n // Reconcile each requested shard role. We list D1 once per call when\n // we need to (some role is missing from state) and reuse the result\n // for every subsequent role.\n let allD1: Awaited<ReturnType<typeof api.d1ListAll>> | null = null;\n const shards: TenantD1ShardRef[] = [...(existing.d1Shards ?? [])];\n\n for (const role of desiredRoles) {\n if (shards.find((s) => s.role === role)) continue;\n const derivedName = tenantShardDatabaseName(\n role,\n options.workspace,\n options.product,\n config.tenant.id,\n env,\n );\n if (allD1 === null) {\n allD1 = await api.d1ListAll();\n }\n let cfId = allD1.find((d) => d.name === derivedName)?.uuid;\n if (cfId) {\n // Adopt an existing D1 (e.g. partial-failure resume, or\n // operator created it out-of-band) without re-issuing POST.\n logApplyChange({\n kind: \"d1\",\n action: \"no_change\",\n logical: `${role}-shard`,\n derived: derivedName,\n });\n } else {\n logApplyChange({\n kind: \"d1\",\n action: \"create\",\n logical: `${role}-shard`,\n derived: derivedName,\n });\n const created = await api.d1Create(derivedName);\n cfId = created.uuid;\n }\n shards.push({ role, derivedName, cfId });\n // Persist after each shard so a network failure mid-loop leaves\n // the already-created shards recorded — `provision-tenant` is\n // resumable role-by-role.\n state.setTenant(\n buildTenantEntry(entryOpts, \"d1_created\", existing, { d1Shards: shards }),\n );\n await state.persist(api);\n existing = state.getTenant(options.product, options.workspace)!;\n }\n\n // Resolve dispatch script bindings from shards + config.\n const naming = namingFromConfig(config);\n const bindings = resolveTenantBindings(\n config,\n shards,\n naming,\n state,\n env,\n accountId,\n );\n\n // Optionally run migrations on each shard before uploading the script.\n if (options.migrate !== false && shards.length > 0) {\n await migrateTenantShards(config, shards, env, naming);\n }\n\n // Deploy the tenant worker via wrangler — handles TS compilation, WASM\n // packaging, node: polyfills, and binding resolution. We write a temporary\n // wrangler.json with the resolved tenant bindings, then run\n // `wrangler deploy --dispatch-namespace`.\n const { writeFileSync, unlinkSync, mkdirSync } = await import(\"fs\");\n const { join: joinPath, dirname: dirnamePath } = await import(\"path\");\n const { spawnWranglerSync } = await import(\"../../core/wrangler/wranglerSpawn.js\");\n const { wranglerConfigCliArgs } = await import(\"../../core/wrangler/wranglerOutFile.js\");\n\n let workerDir: string;\n let tempWranglerPath: string;\n const tempWranglerFile = \"wrangler.tenant.json\";\n\n // Build the temporary wrangler config for the tenant dispatch script\n const wranglerConfig: Record<string, unknown> = {\n name: scriptName,\n main: options.main ? basename(resolve(process.cwd(), options.main)) : (options.moduleName ?? \"worker.js\"),\n compatibility_date: compatDate,\n compatibility_flags: options.compatibilityFlags ?? [],\n };\n\n // Add resolved bindings to wrangler config\n if (bindings.bindings?.length) {\n // Convert unified bindings format to wrangler config format\n const d1Databases: Array<Record<string, string>> = [];\n const vars: Record<string, string> = {};\n const services: Array<Record<string, string>> = [];\n for (const b of bindings.bindings) {\n if (b.type === \"d1\") {\n d1Databases.push({ binding: b.name, database_id: b.id });\n } else if (b.type === \"plain_text\") {\n vars[b.name] = b.text;\n } else if (b.type === \"service\") {\n services.push({ name: b.name, service: b.service, ...(b.environment ? { environment: b.environment } : {}) });\n }\n }\n if (d1Databases.length) wranglerConfig.d1_databases = d1Databases;\n if (Object.keys(vars).length) wranglerConfig.vars = vars;\n if (services.length) wranglerConfig.services = services;\n }\n\n if (options.main) {\n const mainPath = resolve(process.cwd(), options.main);\n workerDir = dirnamePath(mainPath);\n wranglerConfig.main = basename(mainPath);\n tempWranglerPath = joinPath(workerDir, tempWranglerFile);\n } else if (options.artifactKey) {\n // Download pre-built bundle from R2 to a temp directory\n const { tmpdir } = await import(\"os\");\n const bucket = tamerArtifactsBucketName();\n const fullKey = `${tamerArtifactsKeyPrefix(env)}${options.artifactKey}`;\n const body = await api.r2GetObject(bucket, fullKey);\n const mod =\n options.moduleName?.trim() ||\n basename(options.artifactKey).replace(/\\.(zip|tar|gz)$/i, \"\") ||\n \"worker.js\";\n workerDir = joinPath(tmpdir(), `tamer-tenant-${Date.now()}`);\n mkdirSync(workerDir, { recursive: true });\n writeFileSync(joinPath(workerDir, mod), new Uint8Array(body));\n wranglerConfig.main = mod;\n tempWranglerPath = joinPath(workerDir, tempWranglerFile);\n } else {\n throw new Error(\"provision-tenant: provide --main or --artifact-key\");\n }\n\n writeFileSync(tempWranglerPath, JSON.stringify(wranglerConfig, null, 2));\n\n console.log(`Deploying tenant script ${scriptName} to ${dispatchNamespaceName} via wrangler…`);\n const deployResult = spawnWranglerSync(\n [\n \"wrangler\",\n ...wranglerConfigCliArgs(tempWranglerFile),\n \"deploy\",\n \"--dispatch-namespace\",\n dispatchNamespaceName,\n ],\n {\n cwd: workerDir,\n stdio: \"inherit\",\n },\n );\n\n // Clean up temp config\n try {\n unlinkSync(tempWranglerPath);\n } catch {\n /* best-effort */\n }\n\n if (deployResult.status !== 0) {\n throw new Error(\n `wrangler deploy failed for tenant ${scriptName} (exit ${deployResult.status})`,\n );\n }\n\n // Push dispatch secrets from the vault (dispatchSecrets on TenantMeta).\n const dispatchSecretNames = config.tenant.dispatchSecrets ?? [];\n if (dispatchSecretNames.length > 0) {\n const { ensureTamerSecretsDatabase } = await import(\"../../core/secrets/secretsDb.js\");\n const { SecretsVault } = await import(\"../../core/secrets/SecretsVault.js\");\n const { readMasterKeyFromEnv } = await import(\"../../core/secrets/masterKey.js\");\n const { decryptSecretValue } = await import(\"../../core/secrets/crypto.js\");\n\n const secretsUuid = await ensureTamerSecretsDatabase(api);\n const vault = new SecretsVault(api, env, secretsUuid);\n const masterKey = readMasterKeyFromEnv(env);\n if (!masterKey) {\n throw new Error(\n `dispatchSecrets: master key not found. Set TAMER_SECRETS_KEY_${env} in the environment.`,\n );\n }\n\n console.log(`Pushing ${dispatchSecretNames.length} dispatch secret(s) to ${scriptName}…`);\n for (const secretName of dispatchSecretNames) {\n const record = await vault.get(secretName);\n if (!record) {\n throw new Error(\n `dispatchSecrets: secret \"${secretName}\" not found in vault for env ${env}. ` +\n `Run: tamer secrets set ${secretName} --env ${env}`,\n );\n }\n const plaintext = await decryptSecretValue(record, masterKey);\n await api.dispatchNamespaceScriptSecretPut(\n dispatchNamespaceName,\n scriptName,\n { name: secretName, text: plaintext },\n );\n console.log(` ✓ ${secretName} → ${scriptName}`);\n }\n }\n\n state.setTenant(\n buildTenantEntry(entryOpts, \"ready\", existing, { d1Shards: shards }),\n );\n await state.persist(api);\n\n const shardsSummary =\n shards.length > 0\n ? `shards: ${shards.map((s) => s.role).join(\", \")}`\n : \"no shards declared\";\n console.log(\n `Provisioned tenant ${tenantStateKey(options.product, options.workspace)} → script ${scriptName} in ${dispatchNamespaceName} (${shardsSummary})`,\n );\n\n if (options.json) {\n const result: ProvisionTenantResult = {\n status: \"ready\",\n tenantKey: tenantStateKey(options.product, options.workspace),\n product: options.product,\n workspace: options.workspace,\n env,\n scriptName,\n dispatchNamespaceName,\n shards: shards.map((s) => ({\n role: s.role,\n derivedName: s.derivedName,\n cfId: s.cfId,\n })),\n };\n process.stdout.write(JSON.stringify(result) + \"\\n\");\n }\n}\n\n/**\n * Run D1 migrations on each tenant shard by writing a temporary wrangler.json\n * with the shard binding and invoking `wrangler d1 migrations apply`.\n */\nasync function migrateTenantShards(\n config: import(\"../../types.js\").CfiConfig,\n shards: TenantD1ShardRef[],\n env: string,\n naming: import(\"../../core/naming/NamingEngine.js\").NamingEngine,\n): Promise<void> {\n const { writeFileSync, unlinkSync, mkdirSync } = await import(\"fs\");\n const { join } = await import(\"path\");\n const { tmpdir } = await import(\"os\");\n const { spawnWranglerSync } = await import(\n \"../../core/wrangler/wranglerSpawn.js\"\n );\n const { wranglerConfigCliArgs } = await import(\n \"../../core/wrangler/wranglerOutFile.js\"\n );\n const { resolveTenantD1Bindings } = await import(\n \"../../core/tenant/resolveTenantBindings.js\"\n );\n\n const shardBindings = config.tenant.shardBindings ?? {};\n const d1Bindings = resolveTenantD1Bindings(config, shards, naming);\n\n // Resolve base dir for relative migrationsDir paths\n const { getConfigBaseDir } = await import(\"../../core/config/loader.js\");\n const baseDir = getConfigBaseDir();\n\n for (let i = 0; i < shards.length; i++) {\n const shard = shards[i]!;\n const binding = d1Bindings[i]!;\n const info = shardBindings[shard.role];\n if (!info?.migrationsDir) {\n console.log(` migrate: skipping ${shard.role} (no migrationsDir in shardBindings)`);\n continue;\n }\n\n const migDir = resolve(baseDir, info.migrationsDir);\n const tempWranglerPath = join(migDir, `wrangler.${shard.role}.json`);\n const tempConfig = {\n name: `tenant-migrate-${shard.role}`,\n d1_databases: [{ binding: binding.name, database_id: shard.cfId, database_name: shard.derivedName, migrations_dir: \".\" }],\n compatibility_date: config.compatibility_date ?? \"2024-01-01\",\n };\n writeFileSync(tempWranglerPath, JSON.stringify(tempConfig, null, 2));\n\n const migrateArgs = [\n \"wrangler\",\n ...wranglerConfigCliArgs(`wrangler.${shard.role}.json`),\n \"d1\",\n \"migrations\",\n \"apply\",\n binding.name,\n \"--remote\",\n ];\n if (info.migrationsTable) {\n migrateArgs.push(\"--migrations-table\", info.migrationsTable);\n }\n\n console.log(` migrate: applying migrations for ${shard.role} (${shard.derivedName})...`);\n const result = spawnWranglerSync(migrateArgs, {\n cwd: migDir,\n stdio: \"inherit\",\n });\n if (result.status !== 0) {\n console.warn(` migrate: failed for ${shard.role} (exit ${result.status})`);\n }\n try {\n unlinkSync(tempWranglerPath);\n } catch {\n /* best-effort cleanup */\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"r2S3EmptyBucket-
|
|
1
|
+
{"version":3,"file":"r2S3EmptyBucket-OKIQdlms.mjs","names":["keyMarker: string | undefined","uploadIdMarker: string | undefined","continuationToken: string | undefined"],"sources":["../src/features/r2/r2S3EmptyBucket.ts"],"sourcesContent":["/**\n * Empty an R2 bucket using the S3-compatible API (list/delete objects, abort\n * incomplete multipart uploads). The Cloudflare v4 \"delete bucket\" call fails\n * while any objects or in-flight MPU remain.\n *\n * Uses {@link https://developers.cloudflare.com/r2/api/s3/tokens/ | R2 S3 API tokens}:\n * set **R2_ACCESS_KEY_ID** and **R2_SECRET_ACCESS_KEY** (not CLOUDFLARE_API_TOKEN).\n */\nimport {\n S3Client,\n ListObjectsV2Command,\n DeleteObjectsCommand,\n ListMultipartUploadsCommand,\n AbortMultipartUploadCommand,\n} from \"@aws-sdk/client-s3\";\n\nconst DELETE_BATCH = 1000;\n\nexport function r2S3CredentialsFromEnv():\n | { accessKeyId: string; secretAccessKey: string }\n | undefined {\n const accessKeyId = process.env.R2_ACCESS_KEY_ID?.trim();\n const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY?.trim();\n if (!accessKeyId || !secretAccessKey) return undefined;\n return { accessKeyId, secretAccessKey };\n}\n\nfunction s3ForR2(\n accountId: string,\n creds: { accessKeyId: string; secretAccessKey: string },\n): S3Client {\n return new S3Client({\n region: \"auto\",\n endpoint: `https://${accountId}.r2.cloudflarestorage.com`,\n credentials: creds,\n });\n}\n\n/**\n * Aborts all incomplete multipart uploads, then deletes all object keys\n * (batched). Idempotent for an already-empty bucket.\n */\nexport async function emptyR2BucketViaS3(\n accountId: string,\n bucketName: string,\n creds: { accessKeyId: string; secretAccessKey: string },\n): Promise<{ uploadsAborted: number; objectsDeleted: number }> {\n const s3 = s3ForR2(accountId, creds);\n let uploadsAborted = 0;\n let keyMarker: string | undefined;\n let uploadIdMarker: string | undefined;\n\n for (;;) {\n const mpu = await s3.send(\n new ListMultipartUploadsCommand({\n Bucket: bucketName,\n KeyMarker: keyMarker,\n UploadIdMarker: uploadIdMarker,\n }),\n );\n for (const u of mpu.Uploads ?? []) {\n if (u.Key == null || u.UploadId == null) continue;\n await s3.send(\n new AbortMultipartUploadCommand({\n Bucket: bucketName,\n Key: u.Key,\n UploadId: u.UploadId,\n }),\n );\n uploadsAborted += 1;\n }\n if (!mpu.IsTruncated) break;\n keyMarker = mpu.NextKeyMarker;\n uploadIdMarker = mpu.NextUploadIdMarker;\n }\n\n let objectsDeleted = 0;\n let continuationToken: string | undefined;\n\n for (;;) {\n const listed = await s3.send(\n new ListObjectsV2Command({\n Bucket: bucketName,\n ContinuationToken: continuationToken,\n MaxKeys: DELETE_BATCH,\n }),\n );\n const keys = (listed.Contents ?? [])\n .map((o) => o.Key)\n .filter((k): k is string => Boolean(k));\n if (keys.length > 0) {\n const del = await s3.send(\n new DeleteObjectsCommand({\n Bucket: bucketName,\n Delete: { Objects: keys.map((Key) => ({ Key })), Quiet: true },\n }),\n );\n const errs = del.Errors ?? [];\n if (errs.length > 0) {\n const first = errs[0];\n throw new Error(\n `S3 DeleteObjects: ${first.Key ?? \"?\"} — ${first.Code ?? \"\"} ${first.Message ?? \"\"}`.trim(),\n );\n }\n objectsDeleted += del.Deleted?.length ?? keys.length;\n }\n if (!listed.IsTruncated) break;\n continuationToken = listed.NextContinuationToken;\n }\n\n return { uploadsAborted, objectsDeleted };\n}\n"],"mappings":";;;;;;;;;;;AAgBA,MAAM,eAAe;AAErB,SAAgB,yBAEF;CACZ,MAAM,cAAc,QAAQ,IAAI,kBAAkB,MAAM;CACxD,MAAM,kBAAkB,QAAQ,IAAI,sBAAsB,MAAM;AAChE,KAAI,CAAC,eAAe,CAAC,gBAAiB,QAAO;AAC7C,QAAO;EAAE;EAAa;EAAiB;;AAGzC,SAAS,QACP,WACA,OACU;AACV,QAAO,IAAI,SAAS;EAClB,QAAQ;EACR,UAAU,WAAW,UAAU;EAC/B,aAAa;EACd,CAAC;;;;;;AAOJ,eAAsB,mBACpB,WACA,YACA,OAC6D;CAC7D,MAAM,KAAK,QAAQ,WAAW,MAAM;CACpC,IAAI,iBAAiB;CACrB,IAAIA;CACJ,IAAIC;AAEJ,UAAS;EACP,MAAM,MAAM,MAAM,GAAG,KACnB,IAAI,4BAA4B;GAC9B,QAAQ;GACR,WAAW;GACX,gBAAgB;GACjB,CAAC,CACH;AACD,OAAK,MAAM,KAAK,IAAI,WAAW,EAAE,EAAE;AACjC,OAAI,EAAE,OAAO,QAAQ,EAAE,YAAY,KAAM;AACzC,SAAM,GAAG,KACP,IAAI,4BAA4B;IAC9B,QAAQ;IACR,KAAK,EAAE;IACP,UAAU,EAAE;IACb,CAAC,CACH;AACD,qBAAkB;;AAEpB,MAAI,CAAC,IAAI,YAAa;AACtB,cAAY,IAAI;AAChB,mBAAiB,IAAI;;CAGvB,IAAI,iBAAiB;CACrB,IAAIC;AAEJ,UAAS;EACP,MAAM,SAAS,MAAM,GAAG,KACtB,IAAI,qBAAqB;GACvB,QAAQ;GACR,mBAAmB;GACnB,SAAS;GACV,CAAC,CACH;EACD,MAAM,QAAQ,OAAO,YAAY,EAAE,EAChC,KAAK,MAAM,EAAE,IAAI,CACjB,QAAQ,MAAmB,QAAQ,EAAE,CAAC;AACzC,MAAI,KAAK,SAAS,GAAG;GACnB,MAAM,MAAM,MAAM,GAAG,KACnB,IAAI,qBAAqB;IACvB,QAAQ;IACR,QAAQ;KAAE,SAAS,KAAK,KAAK,SAAS,EAAE,KAAK,EAAE;KAAE,OAAO;KAAM;IAC/D,CAAC,CACH;GACD,MAAM,OAAO,IAAI,UAAU,EAAE;AAC7B,OAAI,KAAK,SAAS,GAAG;IACnB,MAAM,QAAQ,KAAK;AACnB,UAAM,IAAI,MACR,qBAAqB,MAAM,OAAO,IAAI,KAAK,MAAM,QAAQ,GAAG,GAAG,MAAM,WAAW,KAAK,MAAM,CAC5F;;AAEH,qBAAkB,IAAI,SAAS,UAAU,KAAK;;AAEhD,MAAI,CAAC,OAAO,YAAa;AACzB,sBAAoB,OAAO;;AAG7B,QAAO;EAAE;EAAgB;EAAgB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as emptyR2BucketViaS3 } from "./r2S3EmptyBucket-
|
|
3
|
-
import { n as logApplyChange } from "./planFormat-
|
|
1
|
+
import { nt as getWorkers } from "./tamer.mjs";
|
|
2
|
+
import { t as emptyR2BucketViaS3 } from "./r2S3EmptyBucket-OKIQdlms.mjs";
|
|
3
|
+
import { n as logApplyChange } from "./planFormat-DHjPdYzr.mjs";
|
|
4
4
|
import { createHash } from "crypto";
|
|
5
5
|
|
|
6
6
|
//#region src/core/naming/resolveCloudflareName.ts
|
|
@@ -3157,4 +3157,4 @@ function getResourceModule(kind) {
|
|
|
3157
3157
|
|
|
3158
3158
|
//#endregion
|
|
3159
3159
|
export { logicalNamesForResourceKind as a, workflowsApply as i, resourceModules as n, d1CloudflareDatabaseName as o, secretsStoreDeriveName as r, d1SkipsProvisionAndMigrate as s, getResourceModule as t };
|
|
3160
|
-
//# sourceMappingURL=registry-
|
|
3160
|
+
//# sourceMappingURL=registry-D3heJge_.mjs.map
|