@dragonmastery/tamer 0.40.0 → 0.41.0
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/{apply-CV4_3Jv4.mjs → apply-Mk9usR-U.mjs} +14 -14
- package/dist/{apply-CV4_3Jv4.mjs.map → apply-Mk9usR-U.mjs.map} +1 -1
- package/dist/{applyTarget-B1YPgkb3.mjs → applyTarget-CJSn9DeJ.mjs} +3 -3
- package/dist/{applyTarget-B1YPgkb3.mjs.map → applyTarget-CJSn9DeJ.mjs.map} +1 -1
- package/dist/{bootstrap-ilkixdmD.mjs → bootstrap-BCy6B-B6.mjs} +3 -3
- package/dist/{bootstrap-ilkixdmD.mjs.map → bootstrap-BCy6B-B6.mjs.map} +1 -1
- package/dist/{buildDispatchUploadForm-D_fM8JaL.mjs → buildDispatchUploadForm-XO8AxhXJ.mjs} +2 -6
- package/dist/buildDispatchUploadForm-XO8AxhXJ.mjs.map +1 -0
- package/dist/{cloudflareSnapshot-BAeNVohz.mjs → cloudflareSnapshot-DqP8v_xG.mjs} +4 -4
- package/dist/{cloudflareSnapshot-BAeNVohz.mjs.map → cloudflareSnapshot-DqP8v_xG.mjs.map} +1 -1
- package/dist/{deploy-BEaNADU6.mjs → deploy-DbXBHpCG.mjs} +9 -9
- package/dist/{deploy-BEaNADU6.mjs.map → deploy-DbXBHpCG.mjs.map} +1 -1
- package/dist/{destroy-Krf35oqE.mjs → destroy-CesVkyxf.mjs} +10 -10
- package/dist/{destroy-Krf35oqE.mjs.map → destroy-CesVkyxf.mjs.map} +1 -1
- package/dist/{destroy-tenant-C95ljuon.mjs → destroy-tenant-DMynv_u-.mjs} +1 -1
- package/dist/{destroy-tenant-C95ljuon.mjs.map → destroy-tenant-DMynv_u-.mjs.map} +1 -1
- package/dist/{dev-C__1rLos.mjs → dev-sk6JGKe4.mjs} +7 -7
- package/dist/{dev-C__1rLos.mjs.map → dev-sk6JGKe4.mjs.map} +1 -1
- package/dist/{dns-records.resolve-DwBR_1WI.mjs → dns-records.resolve-SPYGYNHa.mjs} +1 -1
- package/dist/{dns-records.resolve-DwBR_1WI.mjs.map → dns-records.resolve-SPYGYNHa.mjs.map} +1 -1
- package/dist/{dns-records.resolve-C2T0m4NG.mjs → dns-records.resolve-jECsH6N-.mjs} +1 -1
- package/dist/{dns-records.sync-Dfwk76J_.mjs → dns-records.sync-BORRvLky.mjs} +2 -2
- package/dist/{dns-records.sync-Dfwk76J_.mjs.map → dns-records.sync-BORRvLky.mjs.map} +1 -1
- package/dist/{doctor-BIaLEVFR.mjs → doctor-BgqnscIE.mjs} +1 -1
- package/dist/{doctor-BIaLEVFR.mjs.map → doctor-BgqnscIE.mjs.map} +1 -1
- package/dist/{drift-CLsSBorO.mjs → drift-C5r1cKxV.mjs} +6 -6
- package/dist/{drift-CLsSBorO.mjs.map → drift-C5r1cKxV.mjs.map} +1 -1
- package/dist/drift-oOUlh0u8.mjs +8 -0
- package/dist/{emit-Dh68dvo5.mjs → emit-C7KXAVP0.mjs} +2 -2
- package/dist/{emit-Dh68dvo5.mjs.map → emit-C7KXAVP0.mjs.map} +1 -1
- package/dist/{env-gc-0vX5Av4i.mjs → env-gc-DE4EV7j7.mjs} +10 -10
- package/dist/{env-gc-0vX5Av4i.mjs.map → env-gc-DE4EV7j7.mjs.map} +1 -1
- package/dist/{env-list-DYCprcLb.mjs → env-list-DKseThiA.mjs} +1 -1
- package/dist/{env-list-DYCprcLb.mjs.map → env-list-DKseThiA.mjs.map} +1 -1
- package/dist/{events-CnWvxyX_.mjs → events-CqbN9sbT.mjs} +1 -1
- package/dist/{events-CnWvxyX_.mjs.map → events-CqbN9sbT.mjs.map} +1 -1
- package/dist/{generator-DAU5K77L.mjs → generator-CbH3UZ3K.mjs} +2 -2
- package/dist/{generator-DAU5K77L.mjs.map → generator-CbH3UZ3K.mjs.map} +1 -1
- package/dist/{import-BNbHjR9t.mjs → import-leVD9Ryg.mjs} +5 -5
- package/dist/{import-BNbHjR9t.mjs.map → import-leVD9Ryg.mjs.map} +1 -1
- package/dist/index.d.mts +20 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/loader-B5iVsP6t.mjs +3 -0
- package/dist/{logpush-job-C_6uzGUC.mjs → logpush-job-Dqlt-wEw.mjs} +2 -2
- package/dist/{logpush-job-C_6uzGUC.mjs.map → logpush-job-Dqlt-wEw.mjs.map} +1 -1
- package/dist/{migrate-EVfFlJOM.mjs → migrate-DyrTw9ep.mjs} +5 -5
- package/dist/{migrate-EVfFlJOM.mjs.map → migrate-DyrTw9ep.mjs.map} +1 -1
- package/dist/normalize-DVSTRZhO.mjs.map +1 -1
- package/dist/{plan-tnUWkiM1.mjs → plan-D7UyLznb.mjs} +11 -11
- package/dist/{plan-tnUWkiM1.mjs.map → plan-D7UyLznb.mjs.map} +1 -1
- package/dist/{planFormat-DpA8XhzX.mjs → planFormat-CJw8Kq2s.mjs} +1 -1
- package/dist/{planFormat-DpA8XhzX.mjs.map → planFormat-CJw8Kq2s.mjs.map} +1 -1
- package/dist/{provision-tenant-DW4eg-7P.mjs → provision-tenant-z4DLxYAM.mjs} +25 -37
- package/dist/provision-tenant-z4DLxYAM.mjs.map +1 -0
- package/dist/{r2S3EmptyBucket-CXLmOrYF.mjs → r2S3EmptyBucket-DD81ZWQ7.mjs} +1 -1
- package/dist/{r2S3EmptyBucket-CXLmOrYF.mjs.map → r2S3EmptyBucket-DD81ZWQ7.mjs.map} +1 -1
- package/dist/{registry-X9dlQxG3.mjs → registry-CTerXUza.mjs} +81 -33
- package/dist/registry-CTerXUza.mjs.map +1 -0
- package/dist/resolveTenantBindings-BypqzcSn.mjs +65 -0
- package/dist/resolveTenantBindings-BypqzcSn.mjs.map +1 -0
- package/dist/{stackOutputs-CU2oxjpU.mjs → stackOutputs-BLp-dyzl.mjs} +1 -1
- package/dist/{stackOutputs-CU2oxjpU.mjs.map → stackOutputs-BLp-dyzl.mjs.map} +1 -1
- package/dist/{status-srUxsBIB.mjs → status-WMG5abrN.mjs} +6 -6
- package/dist/{status-srUxsBIB.mjs.map → status-WMG5abrN.mjs.map} +1 -1
- package/dist/sync-DSgJGQh1.mjs +7 -0
- package/dist/{sync-DfJGkOME.mjs → sync-Dii9n2nJ.mjs} +5 -5
- package/dist/{sync-DfJGkOME.mjs.map → sync-Dii9n2nJ.mjs.map} +1 -1
- package/dist/tamer.mjs +66 -25
- package/dist/tamer.mjs.map +1 -1
- package/dist/{tamerArtifactsR2-B9myb-IA.mjs → tamerArtifactsR2-B3X21TGV.mjs} +2 -2
- package/dist/{tamerArtifactsR2-B9myb-IA.mjs.map → tamerArtifactsR2-B3X21TGV.mjs.map} +1 -1
- package/dist/{tenant-MWIs0esz.mjs → tenant-WNYMWmIe.mjs} +1 -1
- package/dist/{tenant-MWIs0esz.mjs.map → tenant-WNYMWmIe.mjs.map} +1 -1
- package/dist/{tenant-migrate-CT-qyTUD.mjs → tenant-migrate-DBxTsvJ8.mjs} +20 -34
- package/dist/tenant-migrate-DBxTsvJ8.mjs.map +1 -0
- package/dist/{types-BPxuutXk.mjs → types-HPpAxgub.mjs} +5 -5
- package/dist/{types-BPxuutXk.mjs.map → types-HPpAxgub.mjs.map} +1 -1
- package/dist/{verifyPlanFile-CoAOsD3W.mjs → verifyPlanFile-B9VCcFIJ.mjs} +2 -2
- package/dist/{verifyPlanFile-CoAOsD3W.mjs.map → verifyPlanFile-B9VCcFIJ.mjs.map} +1 -1
- package/dist/{wfp-delete-CwWQFxxj.mjs → wfp-delete-TSVBw9XF.mjs} +1 -1
- package/dist/{wfp-delete-CwWQFxxj.mjs.map → wfp-delete-TSVBw9XF.mjs.map} +1 -1
- package/dist/{wfp-put-BBitXJep.mjs → wfp-put-5XZ-KC3g.mjs} +2 -2
- package/dist/{wfp-put-BBitXJep.mjs.map → wfp-put-5XZ-KC3g.mjs.map} +1 -1
- package/dist/{worker-route-CvuUPq1k.mjs → worker-route-BapxsQyX.mjs} +2 -2
- package/dist/{worker-route-CvuUPq1k.mjs.map → worker-route-BapxsQyX.mjs.map} +1 -1
- package/dist/{workers-DSlrKeNL.mjs → workers-D7ow_joN.mjs} +7 -13
- package/dist/{workers-DSlrKeNL.mjs.map → workers-D7ow_joN.mjs.map} +1 -1
- package/dist/wranglerSpawn-CfPkFLP3.mjs +3 -0
- package/dist/{wranglerSpawn-DWdgrsmQ.mjs → wranglerSpawn-VkSL0gZd.mjs} +1 -1
- package/dist/{wranglerSpawn-DWdgrsmQ.mjs.map → wranglerSpawn-VkSL0gZd.mjs.map} +1 -1
- package/dist/{zoneResolver-VoxLHM4N.mjs → zoneResolver-CamXJpSB.mjs} +1 -1
- package/dist/{zoneResolver-VoxLHM4N.mjs.map → zoneResolver-CamXJpSB.mjs.map} +1 -1
- package/package.json +5 -2
- package/dist/buildDispatchUploadForm-D_fM8JaL.mjs.map +0 -1
- package/dist/drift-08k11FV6.mjs +0 -8
- package/dist/provision-tenant-DW4eg-7P.mjs.map +0 -1
- package/dist/registry-X9dlQxG3.mjs.map +0 -1
- package/dist/resolveTenantBindings-DJDPhILD.mjs +0 -80
- package/dist/resolveTenantBindings-DJDPhILD.mjs.map +0 -1
- package/dist/sync-CfNyelDN.mjs +0 -7
- package/dist/tenant-migrate-CT-qyTUD.mjs.map +0 -1
- package/dist/wranglerSpawn-aARBLHpA.mjs +0 -3
- /package/dist/{secrets-2Hy5LMHs.mjs → secrets-DJ1yUy01.mjs} +0 -0
- /package/dist/{wranglerOutFile-f08VsoAj.mjs → wranglerOutFile-DDFKeKwO.mjs} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planFormat-
|
|
1
|
+
{"version":3,"file":"planFormat-CJw8Kq2s.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,10 +1,10 @@
|
|
|
1
1
|
import { f as getDispatchNamespaces } from "./normalize-DVSTRZhO.mjs";
|
|
2
2
|
import { P as effectiveDispatchNamespaceName, R as CFApiClient, U as loadConfig, _ as tenantDispatchScriptName, g as parseTenantShardRoles, h as StateManager, u as namingFromConfig, v as tenantShardDatabaseName, w as stackNameForConfig, y as tenantStateKey, z as cloudflareAccountIdFromEnv } from "./tamer.mjs";
|
|
3
|
-
import "./r2S3EmptyBucket-
|
|
4
|
-
import { n as logApplyChange } from "./planFormat-
|
|
5
|
-
import { i as tamerArtifactsKeyPrefix, r as tamerArtifactsBucketName } from "./tamerArtifactsR2-
|
|
6
|
-
import { n as buildSingleModuleDispatchFormFromBuffer, t as buildSingleModuleDispatchForm } from "./buildDispatchUploadForm-
|
|
7
|
-
import { resolveTenantBindings } from "./resolveTenantBindings-
|
|
3
|
+
import "./r2S3EmptyBucket-DD81ZWQ7.mjs";
|
|
4
|
+
import { n as logApplyChange } from "./planFormat-CJw8Kq2s.mjs";
|
|
5
|
+
import { i as tamerArtifactsKeyPrefix, r as tamerArtifactsBucketName } from "./tamerArtifactsR2-B3X21TGV.mjs";
|
|
6
|
+
import { n as buildSingleModuleDispatchFormFromBuffer, t as buildSingleModuleDispatchForm } from "./buildDispatchUploadForm-XO8AxhXJ.mjs";
|
|
7
|
+
import { resolveTenantBindings } from "./resolveTenantBindings-BypqzcSn.mjs";
|
|
8
8
|
import { basename, resolve } from "path";
|
|
9
9
|
|
|
10
10
|
//#region src/cli/commands/provision-tenant.ts
|
|
@@ -196,43 +196,34 @@ async function migrateTenantShards(config, shards, env, naming) {
|
|
|
196
196
|
const { writeFileSync, unlinkSync, mkdirSync } = await import("fs");
|
|
197
197
|
const { join: join$1 } = await import("path");
|
|
198
198
|
const { tmpdir } = await import("os");
|
|
199
|
-
const { spawnWranglerSync } = await import("./wranglerSpawn-
|
|
200
|
-
const { wranglerConfigCliArgs } = await import("./wranglerOutFile-
|
|
201
|
-
const { resolveTenantD1Bindings } = await import("./resolveTenantBindings-
|
|
202
|
-
const
|
|
203
|
-
const roleToMigrations = /* @__PURE__ */ new Map();
|
|
204
|
-
for (const wc of workers) {
|
|
205
|
-
const d1s = wc.resources?.d1;
|
|
206
|
-
if (!d1s) continue;
|
|
207
|
-
for (const d1 of d1s) {
|
|
208
|
-
const role = d1.registryRole ?? "";
|
|
209
|
-
if (role) roleToMigrations.set(role, {
|
|
210
|
-
dir: d1.migrationsDir,
|
|
211
|
-
table: d1.migrationsTable
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
}
|
|
199
|
+
const { spawnWranglerSync } = await import("./wranglerSpawn-CfPkFLP3.mjs");
|
|
200
|
+
const { wranglerConfigCliArgs } = await import("./wranglerOutFile-DDFKeKwO.mjs");
|
|
201
|
+
const { resolveTenantD1Bindings } = await import("./resolveTenantBindings-BypqzcSn.mjs");
|
|
202
|
+
const shardBindings = config.tenant.shardBindings ?? {};
|
|
215
203
|
const d1Bindings = resolveTenantD1Bindings(config, shards, naming);
|
|
216
|
-
const
|
|
217
|
-
|
|
204
|
+
const { getConfigBaseDir } = await import("./loader-B5iVsP6t.mjs");
|
|
205
|
+
const baseDir = getConfigBaseDir();
|
|
218
206
|
for (let i = 0; i < shards.length; i++) {
|
|
219
207
|
const shard = shards[i];
|
|
220
208
|
const binding = d1Bindings[i];
|
|
221
|
-
const
|
|
222
|
-
if (!
|
|
223
|
-
console.log(` migrate: skipping ${shard.role} (no migrationsDir
|
|
209
|
+
const info = shardBindings[shard.role];
|
|
210
|
+
if (!info?.migrationsDir) {
|
|
211
|
+
console.log(` migrate: skipping ${shard.role} (no migrationsDir in shardBindings)`);
|
|
224
212
|
continue;
|
|
225
213
|
}
|
|
226
|
-
const
|
|
214
|
+
const migDir = resolve(baseDir, info.migrationsDir);
|
|
215
|
+
const tempWranglerPath = join$1(migDir, `wrangler.${shard.role}.json`);
|
|
227
216
|
const tempConfig = {
|
|
228
217
|
name: `tenant-migrate-${shard.role}`,
|
|
229
218
|
d1_databases: [{
|
|
230
|
-
|
|
231
|
-
database_id: shard.cfId
|
|
219
|
+
binding: binding.name,
|
|
220
|
+
database_id: shard.cfId,
|
|
221
|
+
database_name: shard.derivedName,
|
|
222
|
+
migrations_dir: "."
|
|
232
223
|
}],
|
|
233
224
|
compatibility_date: config.compatibility_date ?? "2024-01-01"
|
|
234
225
|
};
|
|
235
|
-
writeFileSync(
|
|
226
|
+
writeFileSync(tempWranglerPath, JSON.stringify(tempConfig, null, 2));
|
|
236
227
|
const migrateArgs = [
|
|
237
228
|
"wrangler",
|
|
238
229
|
...wranglerConfigCliArgs(`wrangler.${shard.role}.json`),
|
|
@@ -242,22 +233,19 @@ async function migrateTenantShards(config, shards, env, naming) {
|
|
|
242
233
|
binding.name,
|
|
243
234
|
"--remote"
|
|
244
235
|
];
|
|
245
|
-
if (
|
|
236
|
+
if (info.migrationsTable) migrateArgs.push("--migrations-table", info.migrationsTable);
|
|
246
237
|
console.log(` migrate: applying migrations for ${shard.role} (${shard.derivedName})...`);
|
|
247
238
|
const result = spawnWranglerSync(migrateArgs, {
|
|
248
|
-
cwd:
|
|
239
|
+
cwd: migDir,
|
|
249
240
|
stdio: "inherit"
|
|
250
241
|
});
|
|
251
242
|
if (result.status !== 0) console.warn(` migrate: failed for ${shard.role} (exit ${result.status})`);
|
|
252
243
|
try {
|
|
253
|
-
unlinkSync(
|
|
244
|
+
unlinkSync(tempWranglerPath);
|
|
254
245
|
} catch {}
|
|
255
246
|
}
|
|
256
|
-
try {
|
|
257
|
-
unlinkSync(tempDir);
|
|
258
|
-
} catch {}
|
|
259
247
|
}
|
|
260
248
|
|
|
261
249
|
//#endregion
|
|
262
250
|
export { runProvisionTenant };
|
|
263
|
-
//# sourceMappingURL=provision-tenant-
|
|
251
|
+
//# sourceMappingURL=provision-tenant-z4DLxYAM.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provision-tenant-z4DLxYAM.mjs","names":["result: ProvisionTenantResult","allD1: Awaited<ReturnType<typeof api.d1ListAll>> | null","shards: TenantD1ShardRef[]","form: FormData","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; nothing to do.`,\n );\n if (options.json) {\n const result: ProvisionTenantResult = {\n status: \"noop\",\n tenantKey: tenantStateKey(options.product, options.workspace),\n product: options.product,\n workspace: options.workspace,\n env,\n scriptName,\n dispatchNamespaceName,\n shards: (existing.d1Shards ?? []).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 return;\n }\n console.log(\n `Tenant ${tenantStateKey(options.product, options.workspace)} ready but missing shards: ${missing.join(\", \")}; extending layout.`,\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 let form: FormData;\n const formOpts = {\n compatibility_date: compatDate,\n compatibility_flags: options.compatibilityFlags,\n bindings,\n };\n if (options.main) {\n const mainPath = resolve(process.cwd(), options.main);\n form = buildSingleModuleDispatchForm(mainPath, formOpts);\n } else if (options.artifactKey) {\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 form = buildSingleModuleDispatchFormFromBuffer(mod, body, formOpts);\n } else {\n throw new Error(\"provision-tenant: provide --main or --artifact-key\");\n }\n\n await api.dispatchNamespaceScriptPut(dispatchNamespaceName, scriptName, form);\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-DD81ZWQ7.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,7 @@
|
|
|
1
1
|
import { H as getWorkers } from "./tamer.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { n as logApplyChange } from "./planFormat-
|
|
2
|
+
import { t as emptyR2BucketViaS3 } from "./r2S3EmptyBucket-DD81ZWQ7.mjs";
|
|
3
|
+
import { n as logApplyChange } from "./planFormat-CJw8Kq2s.mjs";
|
|
4
|
+
import { createHash } from "crypto";
|
|
4
5
|
|
|
5
6
|
//#region src/core/naming/resolveCloudflareName.ts
|
|
6
7
|
function resolveOverride(fn, tenantId, env, ctx) {
|
|
@@ -667,21 +668,71 @@ function r2Drift(allR2, resources, env, state, naming) {
|
|
|
667
668
|
}
|
|
668
669
|
|
|
669
670
|
//#endregion
|
|
670
|
-
//#region src/features/r2/
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
}
|
|
681
|
-
if (!nextCursor) break;
|
|
682
|
-
cursor = nextCursor;
|
|
671
|
+
//#region src/features/r2/r2TempS3Creds.ts
|
|
672
|
+
/**
|
|
673
|
+
* Find the "Workers R2 Storage Bucket Item Write" permission group ID.
|
|
674
|
+
* This is the bucket-scoped permission that maps to S3 operations.
|
|
675
|
+
*/
|
|
676
|
+
async function findR2BucketItemWriteGroupId(api) {
|
|
677
|
+
const groups = await api.accountTokenPermissionGroupsListAll();
|
|
678
|
+
for (const g of groups) {
|
|
679
|
+
const name = g.name.toLowerCase();
|
|
680
|
+
if (/workers r2 storage/i.test(name) && /bucket item/i.test(name) && /write|edit/i.test(name)) return g.id;
|
|
683
681
|
}
|
|
684
|
-
|
|
682
|
+
throw new Error("Could not find 'Workers R2 Storage Bucket Item Write' permission group. Available: " + groups.slice(0, 12).map((g) => g.name).join(", "));
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Mint a temporary R2 S3 credential for a specific bucket.
|
|
686
|
+
* Returns S3-compatible credentials + the token ID (for cleanup).
|
|
687
|
+
*
|
|
688
|
+
* Uses the same permission group and resource format as the Cloudflare
|
|
689
|
+
* dashboard when creating R2 API tokens:
|
|
690
|
+
* group: "Workers R2 Storage Bucket Item Write"
|
|
691
|
+
* resource: "com.cloudflare.edge.r2.bucket.{accountId}_default_{bucketName}"
|
|
692
|
+
*/
|
|
693
|
+
async function mintR2S3Credentials(api, accountId, bucketName) {
|
|
694
|
+
const groupId = await findR2BucketItemWriteGroupId(api);
|
|
695
|
+
const bucketResource = `com.cloudflare.edge.r2.bucket.${accountId}_default_${bucketName}`;
|
|
696
|
+
const tokenName = `Tamer R2 cleanup ${bucketName} ${Date.now()}`;
|
|
697
|
+
console.log(`R2: minting S3 token — group=${groupId}, resource=${bucketResource}`);
|
|
698
|
+
const result = await api.accountTokenCreate({
|
|
699
|
+
name: tokenName,
|
|
700
|
+
policies: [{
|
|
701
|
+
effect: "allow",
|
|
702
|
+
permission_groups: [{ id: groupId }],
|
|
703
|
+
resources: { [bucketResource]: "*" }
|
|
704
|
+
}],
|
|
705
|
+
condition: {}
|
|
706
|
+
});
|
|
707
|
+
if (!result.value) {
|
|
708
|
+
try {
|
|
709
|
+
await api.accountTokenDelete(result.id);
|
|
710
|
+
} catch {}
|
|
711
|
+
throw new Error("R2 S3 cleanup: token create succeeded but no value returned. Check that the CF API token has 'API Tokens: Edit' permission.");
|
|
712
|
+
}
|
|
713
|
+
if (!result.policies?.[0]?.id) {
|
|
714
|
+
try {
|
|
715
|
+
await api.accountTokenDelete(result.id);
|
|
716
|
+
} catch {}
|
|
717
|
+
throw new Error("R2 S3 cleanup: token create succeeded but no policy id returned.");
|
|
718
|
+
}
|
|
719
|
+
const policyId = result.policies?.[0]?.id;
|
|
720
|
+
console.log(`R2: token created — id=${result.id}, policyId=${policyId}, valuePrefix=${result.value?.slice(0, 12)}…`);
|
|
721
|
+
const secretAccessKey = createHash("sha256").update(result.value).digest("hex");
|
|
722
|
+
console.log(`R2: derived secret — prefix=${secretAccessKey.slice(0, 16)}…`);
|
|
723
|
+
return {
|
|
724
|
+
accessKeyId: policyId,
|
|
725
|
+
secretAccessKey,
|
|
726
|
+
tokenId: result.id
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Delete a previously minted temporary R2 S3 token.
|
|
731
|
+
*/
|
|
732
|
+
async function deleteR2S3Credentials(api, tokenId) {
|
|
733
|
+
try {
|
|
734
|
+
await api.accountTokenDelete(tokenId);
|
|
735
|
+
} catch {}
|
|
685
736
|
}
|
|
686
737
|
|
|
687
738
|
//#endregion
|
|
@@ -691,38 +742,35 @@ async function r2Destroy(_env, state, api, config, baseDir, _force) {
|
|
|
691
742
|
const resources = state.getAll();
|
|
692
743
|
const r2Entries = Object.values(resources).filter((e) => e.type === "r2_bucket");
|
|
693
744
|
const accountId = api.getAccountId();
|
|
694
|
-
const s3creds = r2S3CredentialsFromEnv();
|
|
695
745
|
for (const entry of r2Entries) {
|
|
696
746
|
if (!owned.has(entry.logicalName)) continue;
|
|
697
747
|
const name = entry.derivedName;
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
748
|
+
let creds = null;
|
|
749
|
+
try {
|
|
750
|
+
creds = await mintR2S3Credentials(api, accountId, name);
|
|
751
|
+
} catch {}
|
|
752
|
+
if (creds) try {
|
|
753
|
+
const { uploadsAborted, objectsDeleted } = await emptyR2BucketViaS3(accountId, name, {
|
|
754
|
+
accessKeyId: creds.accessKeyId,
|
|
755
|
+
secretAccessKey: creds.secretAccessKey
|
|
756
|
+
});
|
|
701
757
|
console.log(`R2: bucket "${name}" — aborted ${uploadsAborted} multipart upload(s), deleted ${objectsDeleted} object(s).`);
|
|
702
758
|
} catch (err) {
|
|
703
|
-
console.warn(`R2: S3 empty failed for "${name}"
|
|
704
|
-
await tryEmptyViaCfApi(api, name);
|
|
759
|
+
console.warn(`R2: S3 empty failed for "${name}":`, err instanceof Error ? err.message : err);
|
|
705
760
|
}
|
|
706
|
-
else await tryEmptyViaCfApi(api, name);
|
|
707
761
|
try {
|
|
708
762
|
await api.r2Delete(name);
|
|
709
763
|
state.delete(name);
|
|
764
|
+
console.log(`R2: deleted bucket "${name}".`);
|
|
710
765
|
} catch (err) {
|
|
711
766
|
const msg = err instanceof Error ? err.message : String(err);
|
|
712
767
|
if (/404|not found/i.test(msg)) state.delete(name);
|
|
713
768
|
else throw err;
|
|
769
|
+
} finally {
|
|
770
|
+
if (creds) await deleteR2S3Credentials(api, creds.tokenId);
|
|
714
771
|
}
|
|
715
772
|
}
|
|
716
773
|
}
|
|
717
|
-
async function tryEmptyViaCfApi(api, bucketName) {
|
|
718
|
-
try {
|
|
719
|
-
console.log(`R2: emptying bucket "${bucketName}" via CF API…`);
|
|
720
|
-
const { objectsDeleted } = await emptyR2BucketViaCfApi(api, bucketName);
|
|
721
|
-
console.log(`R2: bucket "${bucketName}" — deleted ${objectsDeleted} object(s) via CF API.`);
|
|
722
|
-
} catch (err) {
|
|
723
|
-
console.warn(`R2: CF API empty failed for "${bucketName}" (bucket may have objects that prevent deletion):`, err instanceof Error ? err.message : err);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
774
|
|
|
727
775
|
//#endregion
|
|
728
776
|
//#region src/features/r2/r2.generate.ts
|
|
@@ -3109,4 +3157,4 @@ function getResourceModule(kind) {
|
|
|
3109
3157
|
|
|
3110
3158
|
//#endregion
|
|
3111
3159
|
export { logicalNamesForResourceKind as a, workflowsApply as i, resourceModules as n, d1CloudflareDatabaseName as o, secretsStoreDeriveName as r, d1SkipsProvisionAndMigrate as s, getResourceModule as t };
|
|
3112
|
-
//# sourceMappingURL=registry-
|
|
3160
|
+
//# sourceMappingURL=registry-CTerXUza.mjs.map
|