@edge-base/cli 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/README.md +182 -0
  2. package/dist/commands/admin.d.ts +10 -0
  3. package/dist/commands/admin.d.ts.map +1 -0
  4. package/dist/commands/admin.js +307 -0
  5. package/dist/commands/admin.js.map +1 -0
  6. package/dist/commands/backup.d.ts +148 -0
  7. package/dist/commands/backup.d.ts.map +1 -0
  8. package/dist/commands/backup.js +1247 -0
  9. package/dist/commands/backup.js.map +1 -0
  10. package/dist/commands/completion.d.ts +3 -0
  11. package/dist/commands/completion.d.ts.map +1 -0
  12. package/dist/commands/completion.js +168 -0
  13. package/dist/commands/completion.js.map +1 -0
  14. package/dist/commands/create-plugin.d.ts +3 -0
  15. package/dist/commands/create-plugin.d.ts.map +1 -0
  16. package/dist/commands/create-plugin.js +208 -0
  17. package/dist/commands/create-plugin.js.map +1 -0
  18. package/dist/commands/deploy.d.ts +146 -0
  19. package/dist/commands/deploy.d.ts.map +1 -0
  20. package/dist/commands/deploy.js +1823 -0
  21. package/dist/commands/deploy.js.map +1 -0
  22. package/dist/commands/describe.d.ts +45 -0
  23. package/dist/commands/describe.d.ts.map +1 -0
  24. package/dist/commands/describe.js +114 -0
  25. package/dist/commands/describe.js.map +1 -0
  26. package/dist/commands/destroy.d.ts +13 -0
  27. package/dist/commands/destroy.d.ts.map +1 -0
  28. package/dist/commands/destroy.js +642 -0
  29. package/dist/commands/destroy.js.map +1 -0
  30. package/dist/commands/dev.d.ts +80 -0
  31. package/dist/commands/dev.d.ts.map +1 -0
  32. package/dist/commands/dev.js +1131 -0
  33. package/dist/commands/dev.js.map +1 -0
  34. package/dist/commands/docker.d.ts +22 -0
  35. package/dist/commands/docker.d.ts.map +1 -0
  36. package/dist/commands/docker.js +373 -0
  37. package/dist/commands/docker.js.map +1 -0
  38. package/dist/commands/export.d.ts +15 -0
  39. package/dist/commands/export.d.ts.map +1 -0
  40. package/dist/commands/export.js +142 -0
  41. package/dist/commands/export.js.map +1 -0
  42. package/dist/commands/init.d.ts +7 -0
  43. package/dist/commands/init.d.ts.map +1 -0
  44. package/dist/commands/init.js +506 -0
  45. package/dist/commands/init.js.map +1 -0
  46. package/dist/commands/keys.d.ts +23 -0
  47. package/dist/commands/keys.d.ts.map +1 -0
  48. package/dist/commands/keys.js +347 -0
  49. package/dist/commands/keys.js.map +1 -0
  50. package/dist/commands/logs.d.ts +17 -0
  51. package/dist/commands/logs.d.ts.map +1 -0
  52. package/dist/commands/logs.js +104 -0
  53. package/dist/commands/logs.js.map +1 -0
  54. package/dist/commands/migrate.d.ts +29 -0
  55. package/dist/commands/migrate.d.ts.map +1 -0
  56. package/dist/commands/migrate.js +302 -0
  57. package/dist/commands/migrate.js.map +1 -0
  58. package/dist/commands/migration.d.ts +18 -0
  59. package/dist/commands/migration.d.ts.map +1 -0
  60. package/dist/commands/migration.js +114 -0
  61. package/dist/commands/migration.js.map +1 -0
  62. package/dist/commands/neon.d.ts +66 -0
  63. package/dist/commands/neon.d.ts.map +1 -0
  64. package/dist/commands/neon.js +600 -0
  65. package/dist/commands/neon.js.map +1 -0
  66. package/dist/commands/plugins.d.ts +9 -0
  67. package/dist/commands/plugins.d.ts.map +1 -0
  68. package/dist/commands/plugins.js +295 -0
  69. package/dist/commands/plugins.js.map +1 -0
  70. package/dist/commands/realtime.d.ts +3 -0
  71. package/dist/commands/realtime.d.ts.map +1 -0
  72. package/dist/commands/realtime.js +71 -0
  73. package/dist/commands/realtime.js.map +1 -0
  74. package/dist/commands/secret.d.ts +7 -0
  75. package/dist/commands/secret.d.ts.map +1 -0
  76. package/dist/commands/secret.js +180 -0
  77. package/dist/commands/secret.js.map +1 -0
  78. package/dist/commands/seed.d.ts +21 -0
  79. package/dist/commands/seed.d.ts.map +1 -0
  80. package/dist/commands/seed.js +325 -0
  81. package/dist/commands/seed.js.map +1 -0
  82. package/dist/commands/telemetry.d.ts +12 -0
  83. package/dist/commands/telemetry.d.ts.map +1 -0
  84. package/dist/commands/telemetry.js +57 -0
  85. package/dist/commands/telemetry.js.map +1 -0
  86. package/dist/commands/typegen.d.ts +26 -0
  87. package/dist/commands/typegen.d.ts.map +1 -0
  88. package/dist/commands/typegen.js +212 -0
  89. package/dist/commands/typegen.js.map +1 -0
  90. package/dist/commands/upgrade.d.ts +29 -0
  91. package/dist/commands/upgrade.d.ts.map +1 -0
  92. package/dist/commands/upgrade.js +265 -0
  93. package/dist/commands/upgrade.js.map +1 -0
  94. package/dist/commands/webhook-test.d.ts +3 -0
  95. package/dist/commands/webhook-test.d.ts.map +1 -0
  96. package/dist/commands/webhook-test.js +133 -0
  97. package/dist/commands/webhook-test.js.map +1 -0
  98. package/dist/index.d.ts +3 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +183 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/lib/agent-contract.d.ts +36 -0
  103. package/dist/lib/agent-contract.d.ts.map +1 -0
  104. package/dist/lib/agent-contract.js +78 -0
  105. package/dist/lib/agent-contract.js.map +1 -0
  106. package/dist/lib/cf-auth.d.ts +76 -0
  107. package/dist/lib/cf-auth.d.ts.map +1 -0
  108. package/dist/lib/cf-auth.js +321 -0
  109. package/dist/lib/cf-auth.js.map +1 -0
  110. package/dist/lib/cli-context.d.ts +23 -0
  111. package/dist/lib/cli-context.d.ts.map +1 -0
  112. package/dist/lib/cli-context.js +40 -0
  113. package/dist/lib/cli-context.js.map +1 -0
  114. package/dist/lib/cloudflare-deploy-manifest.d.ts +26 -0
  115. package/dist/lib/cloudflare-deploy-manifest.d.ts.map +1 -0
  116. package/dist/lib/cloudflare-deploy-manifest.js +107 -0
  117. package/dist/lib/cloudflare-deploy-manifest.js.map +1 -0
  118. package/dist/lib/cloudflare-wrangler-resources.d.ts +32 -0
  119. package/dist/lib/cloudflare-wrangler-resources.d.ts.map +1 -0
  120. package/dist/lib/cloudflare-wrangler-resources.js +59 -0
  121. package/dist/lib/cloudflare-wrangler-resources.js.map +1 -0
  122. package/dist/lib/config-editor.d.ts +139 -0
  123. package/dist/lib/config-editor.d.ts.map +1 -0
  124. package/dist/lib/config-editor.js +1188 -0
  125. package/dist/lib/config-editor.js.map +1 -0
  126. package/dist/lib/deploy-shared.d.ts +55 -0
  127. package/dist/lib/deploy-shared.d.ts.map +1 -0
  128. package/dist/lib/deploy-shared.js +183 -0
  129. package/dist/lib/deploy-shared.js.map +1 -0
  130. package/dist/lib/dev-sidecar.d.ts +31 -0
  131. package/dist/lib/dev-sidecar.d.ts.map +1 -0
  132. package/dist/lib/dev-sidecar.js +1058 -0
  133. package/dist/lib/dev-sidecar.js.map +1 -0
  134. package/dist/lib/fetch-with-timeout.d.ts +14 -0
  135. package/dist/lib/fetch-with-timeout.d.ts.map +1 -0
  136. package/dist/lib/fetch-with-timeout.js +29 -0
  137. package/dist/lib/fetch-with-timeout.js.map +1 -0
  138. package/dist/lib/function-registry.d.ts +56 -0
  139. package/dist/lib/function-registry.d.ts.map +1 -0
  140. package/dist/lib/function-registry.js +210 -0
  141. package/dist/lib/function-registry.js.map +1 -0
  142. package/dist/lib/load-config.d.ts +24 -0
  143. package/dist/lib/load-config.d.ts.map +1 -0
  144. package/dist/lib/load-config.js +263 -0
  145. package/dist/lib/load-config.js.map +1 -0
  146. package/dist/lib/local-secrets.d.ts +2 -0
  147. package/dist/lib/local-secrets.d.ts.map +1 -0
  148. package/dist/lib/local-secrets.js +60 -0
  149. package/dist/lib/local-secrets.js.map +1 -0
  150. package/dist/lib/managed-resource-names.d.ts +4 -0
  151. package/dist/lib/managed-resource-names.d.ts.map +1 -0
  152. package/dist/lib/managed-resource-names.js +19 -0
  153. package/dist/lib/managed-resource-names.js.map +1 -0
  154. package/dist/lib/migrator.d.ts +57 -0
  155. package/dist/lib/migrator.d.ts.map +1 -0
  156. package/dist/lib/migrator.js +321 -0
  157. package/dist/lib/migrator.js.map +1 -0
  158. package/dist/lib/neon.d.ts +41 -0
  159. package/dist/lib/neon.d.ts.map +1 -0
  160. package/dist/lib/neon.js +325 -0
  161. package/dist/lib/neon.js.map +1 -0
  162. package/dist/lib/node-tools.d.ts +10 -0
  163. package/dist/lib/node-tools.d.ts.map +1 -0
  164. package/dist/lib/node-tools.js +32 -0
  165. package/dist/lib/node-tools.js.map +1 -0
  166. package/dist/lib/npm.d.ts +8 -0
  167. package/dist/lib/npm.d.ts.map +1 -0
  168. package/dist/lib/npm.js +10 -0
  169. package/dist/lib/npm.js.map +1 -0
  170. package/dist/lib/npx.d.ts +9 -0
  171. package/dist/lib/npx.d.ts.map +1 -0
  172. package/dist/lib/npx.js +11 -0
  173. package/dist/lib/npx.js.map +1 -0
  174. package/dist/lib/project-runtime.d.ts +38 -0
  175. package/dist/lib/project-runtime.d.ts.map +1 -0
  176. package/dist/lib/project-runtime.js +122 -0
  177. package/dist/lib/project-runtime.js.map +1 -0
  178. package/dist/lib/prompts.d.ts +28 -0
  179. package/dist/lib/prompts.d.ts.map +1 -0
  180. package/dist/lib/prompts.js +85 -0
  181. package/dist/lib/prompts.js.map +1 -0
  182. package/dist/lib/rate-limit-bindings.d.ts +11 -0
  183. package/dist/lib/rate-limit-bindings.d.ts.map +1 -0
  184. package/dist/lib/rate-limit-bindings.js +52 -0
  185. package/dist/lib/rate-limit-bindings.js.map +1 -0
  186. package/dist/lib/realtime-provision.d.ts +22 -0
  187. package/dist/lib/realtime-provision.d.ts.map +1 -0
  188. package/dist/lib/realtime-provision.js +246 -0
  189. package/dist/lib/realtime-provision.js.map +1 -0
  190. package/dist/lib/resolve-options.d.ts +42 -0
  191. package/dist/lib/resolve-options.d.ts.map +1 -0
  192. package/dist/lib/resolve-options.js +98 -0
  193. package/dist/lib/resolve-options.js.map +1 -0
  194. package/dist/lib/runtime-scaffold.d.ts +17 -0
  195. package/dist/lib/runtime-scaffold.d.ts.map +1 -0
  196. package/dist/lib/runtime-scaffold.js +366 -0
  197. package/dist/lib/runtime-scaffold.js.map +1 -0
  198. package/dist/lib/schema-check.d.ts +79 -0
  199. package/dist/lib/schema-check.d.ts.map +1 -0
  200. package/dist/lib/schema-check.js +347 -0
  201. package/dist/lib/schema-check.js.map +1 -0
  202. package/dist/lib/spinner.d.ts +20 -0
  203. package/dist/lib/spinner.d.ts.map +1 -0
  204. package/dist/lib/spinner.js +42 -0
  205. package/dist/lib/spinner.js.map +1 -0
  206. package/dist/lib/telemetry.d.ts +37 -0
  207. package/dist/lib/telemetry.d.ts.map +1 -0
  208. package/dist/lib/telemetry.js +98 -0
  209. package/dist/lib/telemetry.js.map +1 -0
  210. package/dist/lib/turnstile-provision.d.ts +27 -0
  211. package/dist/lib/turnstile-provision.d.ts.map +1 -0
  212. package/dist/lib/turnstile-provision.js +144 -0
  213. package/dist/lib/turnstile-provision.js.map +1 -0
  214. package/dist/lib/update-check.d.ts +13 -0
  215. package/dist/lib/update-check.d.ts.map +1 -0
  216. package/dist/lib/update-check.js +110 -0
  217. package/dist/lib/update-check.js.map +1 -0
  218. package/dist/lib/wrangler-secrets.d.ts +3 -0
  219. package/dist/lib/wrangler-secrets.d.ts.map +1 -0
  220. package/dist/lib/wrangler-secrets.js +32 -0
  221. package/dist/lib/wrangler-secrets.js.map +1 -0
  222. package/dist/lib/wrangler.d.ts +9 -0
  223. package/dist/lib/wrangler.d.ts.map +1 -0
  224. package/dist/lib/wrangler.js +84 -0
  225. package/dist/lib/wrangler.js.map +1 -0
  226. package/dist/templates/plugin/README.md.tmpl +91 -0
  227. package/dist/templates/plugin/client/js/package.json.tmpl +23 -0
  228. package/dist/templates/plugin/client/js/src/index.ts.tmpl +68 -0
  229. package/dist/templates/plugin/client/js/tsconfig.json.tmpl +14 -0
  230. package/dist/templates/plugin/server/package.json.tmpl +19 -0
  231. package/dist/templates/plugin/server/src/index.ts.tmpl +59 -0
  232. package/dist/templates/plugin/server/tsconfig.json.tmpl +14 -0
  233. package/llms.txt +94 -0
  234. package/package.json +60 -0
@@ -0,0 +1,642 @@
1
+ import { Command } from 'commander';
2
+ import { execFileSync } from 'node:child_process';
3
+ import { existsSync, readFileSync, rmSync } from 'node:fs';
4
+ import { join, resolve } from 'node:path';
5
+ import { createInterface } from 'node:readline';
6
+ import chalk from 'chalk';
7
+ import { ensureCloudflareAuth, ensureWranglerToml, resolveApiToken } from '../lib/cf-auth.js';
8
+ import { getCloudflareDeployManifestPath, readCloudflareDeployManifest, } from '../lib/cloudflare-deploy-manifest.js';
9
+ import { parseWranglerResourceConfig } from '../lib/cloudflare-wrangler-resources.js';
10
+ import { fetchWithTimeout } from '../lib/fetch-with-timeout.js';
11
+ import { raiseCliError, raiseNeedsInput } from '../lib/agent-contract.js';
12
+ import { isJson, isNonInteractive } from '../lib/cli-context.js';
13
+ import { buildLegacyManagedD1DatabaseName, buildManagedD1DatabaseName, } from '../lib/managed-resource-names.js';
14
+ import { resolveProjectWorkerName, resolveProjectWorkerUrl } from '../lib/project-runtime.js';
15
+ import { resolveOptionalServiceKey } from '../lib/resolve-options.js';
16
+ import { wranglerArgs, wranglerCommand } from '../lib/wrangler.js';
17
+ function resolveWorkerNameFromProject(projectDir) {
18
+ return resolveProjectWorkerName(projectDir);
19
+ }
20
+ function resolveWorkerUrlFromProject(projectDir) {
21
+ return resolveProjectWorkerUrl(projectDir);
22
+ }
23
+ function resourceKey(resource) {
24
+ return [resource.type, resource.id ?? '', resource.binding ?? '', resource.name].join(':');
25
+ }
26
+ function mergeDestroyResources(manifest, wranglerContent) {
27
+ const merged = new Map();
28
+ for (const resource of manifest?.resources ?? []) {
29
+ merged.set(resourceKey(resource), resource);
30
+ }
31
+ if (wranglerContent) {
32
+ const wranglerConfig = parseWranglerResourceConfig(wranglerContent);
33
+ for (const bucket of wranglerConfig.r2Buckets) {
34
+ const key = resourceKey({
35
+ type: 'r2_bucket',
36
+ name: bucket.bucketName,
37
+ binding: bucket.binding,
38
+ id: bucket.bucketName,
39
+ });
40
+ const existing = merged.get(key);
41
+ merged.set(key, {
42
+ type: 'r2_bucket',
43
+ name: bucket.bucketName,
44
+ binding: bucket.binding,
45
+ id: bucket.bucketName,
46
+ managed: existing?.managed ?? true,
47
+ source: existing?.source ?? 'wrangler',
48
+ metadata: {
49
+ ...(existing?.metadata ?? {}),
50
+ ...(bucket.jurisdiction ? { jurisdiction: bucket.jurisdiction } : {}),
51
+ },
52
+ });
53
+ }
54
+ const existingBindings = new Set([...merged.values()].map((r) => `${r.type}:${r.binding ?? ''}`));
55
+ for (const db of wranglerConfig.d1Databases) {
56
+ if (existingBindings.has(`d1_database:${db.binding}`))
57
+ continue;
58
+ const normalizedId = normalizeWranglerResourceId(db.databaseId);
59
+ const key = resourceKey({
60
+ type: 'd1_database',
61
+ name: deriveWranglerD1ResourceName(db.binding, db.databaseName),
62
+ binding: db.binding,
63
+ id: normalizedId,
64
+ });
65
+ if (!merged.has(key)) {
66
+ merged.set(key, {
67
+ type: 'd1_database',
68
+ name: deriveWranglerD1ResourceName(db.binding, db.databaseName),
69
+ binding: db.binding,
70
+ id: normalizedId,
71
+ managed: true,
72
+ source: 'wrangler',
73
+ });
74
+ }
75
+ }
76
+ for (const kv of wranglerConfig.kvNamespaces) {
77
+ if (existingBindings.has(`kv_namespace:${kv.binding}`))
78
+ continue;
79
+ const normalizedId = normalizeWranglerResourceId(kv.id);
80
+ const key = resourceKey({
81
+ type: 'kv_namespace',
82
+ name: deriveWranglerKvName(kv.binding),
83
+ binding: kv.binding,
84
+ id: normalizedId,
85
+ });
86
+ if (!merged.has(key)) {
87
+ merged.set(key, {
88
+ type: 'kv_namespace',
89
+ name: deriveWranglerKvName(kv.binding),
90
+ binding: kv.binding,
91
+ id: normalizedId,
92
+ managed: true,
93
+ source: 'wrangler',
94
+ });
95
+ }
96
+ }
97
+ for (const vec of wranglerConfig.vectorizeIndexes) {
98
+ if (existingBindings.has(`vectorize:${vec.binding}`))
99
+ continue;
100
+ const key = resourceKey({
101
+ type: 'vectorize',
102
+ name: vec.indexName,
103
+ binding: vec.binding,
104
+ id: vec.indexName,
105
+ });
106
+ if (!merged.has(key)) {
107
+ merged.set(key, {
108
+ type: 'vectorize',
109
+ name: vec.indexName,
110
+ binding: vec.binding,
111
+ id: vec.indexName,
112
+ managed: true,
113
+ source: 'wrangler',
114
+ });
115
+ }
116
+ }
117
+ for (const hd of wranglerConfig.hyperdriveConfigs) {
118
+ if (existingBindings.has(`hyperdrive:${hd.binding}`))
119
+ continue;
120
+ const key = resourceKey({
121
+ type: 'hyperdrive',
122
+ name: hd.binding,
123
+ binding: hd.binding,
124
+ id: hd.id,
125
+ });
126
+ if (!merged.has(key)) {
127
+ merged.set(key, {
128
+ type: 'hyperdrive',
129
+ name: hd.binding,
130
+ binding: hd.binding,
131
+ id: hd.id,
132
+ managed: true,
133
+ source: 'wrangler',
134
+ });
135
+ }
136
+ }
137
+ }
138
+ return Array.from(merged.values());
139
+ }
140
+ function extractExecErrorFull(error) {
141
+ if (error && typeof error === 'object') {
142
+ const stderr = 'stderr' in error ? String(error.stderr ?? '').trim() : '';
143
+ if (stderr)
144
+ return stderr;
145
+ const stdout = 'stdout' in error ? String(error.stdout ?? '').trim() : '';
146
+ if (stdout)
147
+ return stdout;
148
+ }
149
+ return error instanceof Error ? error.message : String(error);
150
+ }
151
+ function extractExecErrorMessage(error) {
152
+ const full = extractExecErrorFull(error);
153
+ // Wrangler may append a log-file path as the last line — prefer the last meaningful line.
154
+ const lines = full.split('\n').filter(Boolean);
155
+ return lines.at(-1) ?? full;
156
+ }
157
+ function isPlaceholderCloudflareId(value) {
158
+ if (!value)
159
+ return false;
160
+ return /^(local|placeholder)$/i.test(value) || /^YOUR_[A-Z0-9_]+$/i.test(value);
161
+ }
162
+ function normalizeWranglerResourceId(value) {
163
+ return value && !isPlaceholderCloudflareId(value) ? value : undefined;
164
+ }
165
+ function deriveWranglerD1ResourceName(binding, databaseName) {
166
+ if (binding === 'AUTH_DB')
167
+ return 'auth';
168
+ if (binding === 'CONTROL_DB')
169
+ return 'control';
170
+ if (binding.startsWith('DB_D1_')) {
171
+ return binding.slice('DB_D1_'.length).toLowerCase().replace(/_/g, '-');
172
+ }
173
+ return databaseName
174
+ .replace(/^edgebase-db-/, '')
175
+ .replace(/^edgebase-/, '');
176
+ }
177
+ function deriveWranglerKvName(binding) {
178
+ if (binding === 'KV')
179
+ return 'internal';
180
+ return binding.toLowerCase().replace(/^kv[_-]?/i, '').replace(/_/g, '-') || binding;
181
+ }
182
+ function runWrangler(projectDir, args, options) {
183
+ return execFileSync(wranglerCommand(), wranglerArgs(args), {
184
+ cwd: projectDir,
185
+ encoding: 'utf-8',
186
+ stdio: ['pipe', 'pipe', 'pipe'],
187
+ input: options?.input,
188
+ });
189
+ }
190
+ async function confirmDestroy(message) {
191
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
192
+ const answer = await new Promise((resolveAnswer) => {
193
+ rl.question(message, (value) => resolveAnswer(value));
194
+ });
195
+ rl.close();
196
+ return /^y(es)?$/i.test(answer.trim());
197
+ }
198
+ async function wipeManagedStorageBucket(workerUrl, serviceKey) {
199
+ const response = await fetchWithTimeout(`${workerUrl.replace(/\/$/, '')}/admin/api/backup/restore-storage?action=wipe`, {
200
+ method: 'POST',
201
+ headers: { 'X-EdgeBase-Service-Key': serviceKey },
202
+ });
203
+ if (!response.ok) {
204
+ throw new Error(`storage wipe failed (${response.status}): ${await response.text()}`);
205
+ }
206
+ const payload = await response.json();
207
+ return typeof payload.deleted === 'number' ? payload.deleted : 0;
208
+ }
209
+ async function listStorageBucketObjects(workerUrl, serviceKey, bucketName, cursor) {
210
+ const query = new URLSearchParams({ limit: '1000' });
211
+ if (cursor) {
212
+ query.set('cursor', cursor);
213
+ }
214
+ const response = await fetchWithTimeout(`${workerUrl.replace(/\/$/, '')}/admin/api/data/storage/buckets/${encodeURIComponent(bucketName)}/objects?${query.toString()}`, {
215
+ headers: { 'X-EdgeBase-Service-Key': serviceKey },
216
+ });
217
+ if (!response.ok) {
218
+ throw new Error(`storage list failed (${response.status}): ${await response.text()}`);
219
+ }
220
+ const payload = await response.json();
221
+ return {
222
+ keys: (payload.objects ?? [])
223
+ .map((entry) => (typeof entry.key === 'string' ? entry.key : null))
224
+ .filter((entry) => Boolean(entry)),
225
+ cursor: typeof payload.cursor === 'string' && payload.cursor.length > 0 ? payload.cursor : null,
226
+ };
227
+ }
228
+ async function wipeStorageBucketViaAdmin(workerUrl, serviceKey, bucketName) {
229
+ let deleted = 0;
230
+ let cursor = null;
231
+ do {
232
+ const page = await listStorageBucketObjects(workerUrl, serviceKey, bucketName, cursor ?? undefined);
233
+ for (const key of page.keys) {
234
+ const response = await fetchWithTimeout(`${workerUrl.replace(/\/$/, '')}/admin/api/data/storage/buckets/${encodeURIComponent(bucketName)}/objects/${encodeURIComponent(key)}`, {
235
+ method: 'DELETE',
236
+ headers: { 'X-EdgeBase-Service-Key': serviceKey },
237
+ });
238
+ if (!response.ok && response.status !== 404) {
239
+ throw new Error(`storage object delete failed (${response.status}): ${await response.text()}`);
240
+ }
241
+ if (response.ok) {
242
+ deleted += 1;
243
+ }
244
+ }
245
+ cursor = page.cursor;
246
+ } while (cursor);
247
+ return deleted;
248
+ }
249
+ async function listTurnstileWidgets(accountId, apiToken) {
250
+ const response = await fetchWithTimeout(`https://api.cloudflare.com/client/v4/accounts/${accountId}/challenges/widgets`, {
251
+ headers: { Authorization: `Bearer ${apiToken}` },
252
+ });
253
+ if (!response.ok) {
254
+ throw new Error(`Turnstile list failed (${response.status}): ${await response.text()}`);
255
+ }
256
+ const payload = await response.json();
257
+ return (payload.result ?? [])
258
+ .filter((entry) => typeof entry.name === 'string' && typeof entry.sitekey === 'string')
259
+ .map((entry) => ({ name: entry.name, sitekey: entry.sitekey }));
260
+ }
261
+ async function deleteTurnstileWidget(accountId, apiToken, resource) {
262
+ const configuredSiteKey = typeof resource.metadata?.siteKey === 'string' ? resource.metadata.siteKey : undefined;
263
+ const siteKey = configuredSiteKey ?? (await listTurnstileWidgets(accountId, apiToken)).find((widget) => widget.name === resource.name)?.sitekey;
264
+ if (!siteKey) {
265
+ throw new Error(`Turnstile widget "${resource.name}" not found (no siteKey in manifest and no matching widget name on account).`);
266
+ }
267
+ const response = await fetchWithTimeout(`https://api.cloudflare.com/client/v4/accounts/${accountId}/challenges/widgets/${siteKey}`, {
268
+ method: 'DELETE',
269
+ headers: { Authorization: `Bearer ${apiToken}` },
270
+ });
271
+ if (!response.ok) {
272
+ throw new Error(`Turnstile delete failed (${response.status}): ${await response.text()}`);
273
+ }
274
+ }
275
+ async function deleteD1Database(accountId, apiToken, databaseId) {
276
+ const response = await fetchWithTimeout(`https://api.cloudflare.com/client/v4/accounts/${accountId}/d1/database/${databaseId}`, {
277
+ method: 'DELETE',
278
+ headers: { Authorization: `Bearer ${apiToken}` },
279
+ });
280
+ if (!response.ok) {
281
+ throw new Error(`D1 delete failed (${response.status}): ${await response.text()}`);
282
+ }
283
+ }
284
+ function fetchD1DatabaseList(projectDir) {
285
+ const uuidToName = new Map();
286
+ try {
287
+ const output = runWrangler(projectDir, ['wrangler', 'd1', 'list', '--json']);
288
+ const databases = JSON.parse(output);
289
+ for (const entry of databases) {
290
+ if (typeof entry.uuid === 'string' && typeof entry.name === 'string') {
291
+ uuidToName.set(entry.uuid, entry.name);
292
+ }
293
+ }
294
+ }
295
+ catch {
296
+ // Fall back to deterministic naming.
297
+ }
298
+ return uuidToName;
299
+ }
300
+ function resolveManagedD1DeleteName(workerName, resource, d1List) {
301
+ if (resource.id) {
302
+ const name = d1List.get(resource.id);
303
+ if (name)
304
+ return name;
305
+ }
306
+ if (workerName) {
307
+ return buildManagedD1DatabaseName(workerName, resource.name);
308
+ }
309
+ return buildLegacyManagedD1DatabaseName(resource.name);
310
+ }
311
+ function formatDestroyPlan(workerName, resources) {
312
+ const lines = [];
313
+ if (workerName) {
314
+ lines.push(`Worker: ${workerName}`);
315
+ }
316
+ for (const resource of resources) {
317
+ if (resource.managed === false)
318
+ continue;
319
+ const detail = resource.binding ? `${resource.name} (${resource.binding})` : resource.name;
320
+ lines.push(`${resource.type}: ${detail}`);
321
+ }
322
+ return lines;
323
+ }
324
+ function isAlreadyDeletedError(message) {
325
+ return /not.?found|does not exist|couldn't find|could not find|could not be found|no such/i.test(message);
326
+ }
327
+ function isR2BucketNotEmptyError(message) {
328
+ return /bucket.+not empty|bucket you tried to delete.+not empty|\[code:\s*10008\]/i.test(message);
329
+ }
330
+ async function runDeleteStep(label, dryRun, result, action) {
331
+ if (dryRun) {
332
+ result.skipped.push(`[dry-run] ${label}`);
333
+ return;
334
+ }
335
+ try {
336
+ await action();
337
+ result.deleted.push(label);
338
+ }
339
+ catch (error) {
340
+ const full = extractExecErrorFull(error);
341
+ if (isAlreadyDeletedError(full)) {
342
+ result.deleted.push(`${label} (already removed)`);
343
+ }
344
+ else {
345
+ result.failures.push({ label, message: extractExecErrorMessage(error) });
346
+ }
347
+ }
348
+ }
349
+ function isManagedResource(resource) {
350
+ return resource.managed !== false;
351
+ }
352
+ export const _internals = {
353
+ mergeDestroyResources,
354
+ isAlreadyDeletedError,
355
+ isR2BucketNotEmptyError,
356
+ };
357
+ function resolveProjectDir() {
358
+ const cwd = resolve('.');
359
+ if (existsSync(getCloudflareDeployManifestPath(cwd)) || existsSync(join(cwd, 'wrangler.toml'))) {
360
+ return cwd;
361
+ }
362
+ const edgebaseSubdir = join(cwd, 'edgebase');
363
+ if (existsSync(getCloudflareDeployManifestPath(edgebaseSubdir)) || existsSync(join(edgebaseSubdir, 'wrangler.toml'))) {
364
+ return edgebaseSubdir;
365
+ }
366
+ return cwd;
367
+ }
368
+ export const destroyCommand = new Command('destroy')
369
+ .description('Destroy project-scoped Cloudflare resources for this project')
370
+ .option('--dry-run', 'Show the deletion plan without removing resources')
371
+ .option('--service-key <key>', 'Service Key used to wipe the managed STORAGE bucket')
372
+ .option('--url <url>', 'Worker URL used to wipe the managed STORAGE bucket')
373
+ .option('-y, --yes', 'Skip confirmation prompt')
374
+ .action(async (options) => {
375
+ const projectDir = resolveProjectDir();
376
+ const wranglerPath = join(projectDir, 'wrangler.toml');
377
+ const manifestPath = getCloudflareDeployManifestPath(projectDir);
378
+ const manifest = readCloudflareDeployManifest(projectDir);
379
+ const wranglerContent = existsSync(wranglerPath) ? readFileSync(wranglerPath, 'utf-8') : null;
380
+ const resources = mergeDestroyResources(manifest, wranglerContent).filter(isManagedResource);
381
+ const workerName = manifest?.worker.name || resolveWorkerNameFromProject(projectDir);
382
+ const workerUrl = (options.url
383
+ || process.env.EDGEBASE_URL
384
+ || manifest?.worker.url
385
+ || resolveWorkerUrlFromProject(projectDir)).replace(/\/$/, '');
386
+ const isTTY = !!process.stdin.isTTY;
387
+ if (!workerName && resources.length === 0) {
388
+ const message = `No Cloudflare resources found for this project. (${manifestPath})`;
389
+ if (isJson()) {
390
+ console.log(JSON.stringify({ status: 'noop', message }));
391
+ }
392
+ else {
393
+ console.log(chalk.yellow('⚠'), message);
394
+ }
395
+ return;
396
+ }
397
+ const planLines = formatDestroyPlan(workerName, resources);
398
+ if (!isJson()) {
399
+ console.log(chalk.blue(options.dryRun ? '🧹 Previewing Cloudflare destroy...' : '🧹 Destroying Cloudflare resources...'));
400
+ console.log();
401
+ for (const line of planLines) {
402
+ console.log(chalk.dim(` • ${line}`));
403
+ }
404
+ console.log();
405
+ }
406
+ if (!options.dryRun && !options.yes) {
407
+ if (isJson() || !isTTY || isNonInteractive()) {
408
+ raiseNeedsInput({
409
+ code: 'destroy_confirmation_required',
410
+ field: 'yes',
411
+ message: 'Destroy requires explicit confirmation before deleting Cloudflare resources.',
412
+ hint: 'Review the deletion plan, then rerun with --yes.',
413
+ choices: [{
414
+ label: 'Approve deletion',
415
+ value: 'yes',
416
+ args: ['--yes'],
417
+ hint: 'Use only after confirming the destroy plan.',
418
+ }],
419
+ });
420
+ }
421
+ const confirmed = await confirmDestroy(` ${chalk.cyan('Proceed with Cloudflare resource deletion?')} (y/N): `);
422
+ if (!confirmed) {
423
+ console.log(chalk.yellow('⚠ Destroy cancelled.'));
424
+ return;
425
+ }
426
+ }
427
+ const cfAuth = options.dryRun ? null : await ensureCloudflareAuth(projectDir, isTTY);
428
+ if (!options.dryRun && cfAuth?.accountId) {
429
+ ensureWranglerToml(projectDir, cfAuth.accountId);
430
+ }
431
+ const accountId = !isPlaceholderCloudflareId(manifest?.accountId)
432
+ ? (manifest?.accountId ?? '')
433
+ : (cfAuth?.accountId ?? '');
434
+ const apiToken = options.dryRun
435
+ ? undefined
436
+ : (() => {
437
+ try {
438
+ return resolveApiToken().token;
439
+ }
440
+ catch {
441
+ return process.env.CLOUDFLARE_API_TOKEN;
442
+ }
443
+ })();
444
+ const serviceKey = resolveOptionalServiceKey({ serviceKey: options.serviceKey });
445
+ const result = { deleted: [], skipped: [], failures: [] };
446
+ const turnstileResources = resources.filter((resource) => resource.type === 'turnstile_widget');
447
+ for (const resource of turnstileResources) {
448
+ await runDeleteStep(`Turnstile ${resource.name}`, !!options.dryRun, result, async () => {
449
+ if (!accountId) {
450
+ throw new Error('Cloudflare account id is required for Turnstile deletion.');
451
+ }
452
+ if (!apiToken) {
453
+ throw new Error('Set CLOUDFLARE_API_TOKEN to delete managed Turnstile widgets.');
454
+ }
455
+ await deleteTurnstileWidget(accountId, apiToken, resource);
456
+ });
457
+ }
458
+ const r2Resources = resources.filter((resource) => resource.type === 'r2_bucket');
459
+ for (const resource of r2Resources) {
460
+ await runDeleteStep(`R2 ${resource.name}`, !!options.dryRun, result, async () => {
461
+ const jurisdiction = typeof resource.metadata?.jurisdiction === 'string'
462
+ ? resource.metadata.jurisdiction
463
+ : undefined;
464
+ if (resource.binding === 'STORAGE' && workerUrl && serviceKey) {
465
+ try {
466
+ await wipeManagedStorageBucket(workerUrl, serviceKey);
467
+ }
468
+ catch {
469
+ // Storage wipe may fail if Worker is already deleted — continue with bucket deletion.
470
+ }
471
+ }
472
+ const args = ['wrangler', 'r2', 'bucket', 'delete', resource.name];
473
+ if (jurisdiction) {
474
+ args.push(`--jurisdiction=${jurisdiction}`);
475
+ }
476
+ try {
477
+ runWrangler(projectDir, args, { input: 'y\n' });
478
+ }
479
+ catch (error) {
480
+ const full = extractExecErrorFull(error);
481
+ if (isAlreadyDeletedError(full))
482
+ return;
483
+ if (resource.binding === 'STORAGE'
484
+ && workerUrl
485
+ && serviceKey
486
+ && isR2BucketNotEmptyError(full)) {
487
+ await wipeStorageBucketViaAdmin(workerUrl, serviceKey, resource.name);
488
+ try {
489
+ runWrangler(projectDir, args, { input: 'y\n' });
490
+ return;
491
+ }
492
+ catch (retryError) {
493
+ const retryFull = extractExecErrorFull(retryError);
494
+ if (isAlreadyDeletedError(retryFull))
495
+ return;
496
+ throw retryError;
497
+ }
498
+ }
499
+ if (resource.binding === 'STORAGE' && (!workerUrl || !serviceKey)) {
500
+ throw new Error(`${extractExecErrorMessage(error)}. Set --url/EDGEBASE_URL and --service-key/EDGEBASE_SERVICE_KEY if the bucket is not empty.`);
501
+ }
502
+ throw error;
503
+ }
504
+ });
505
+ }
506
+ const storageDeleteFailed = r2Resources.some((r) => r.binding === 'STORAGE' && result.failures.some((f) => f.label === `R2 ${r.name}`));
507
+ if (workerName && storageDeleteFailed) {
508
+ result.skipped.push(`Worker ${workerName} (skipped: STORAGE bucket deletion failed — deleting the Worker would prevent future bucket cleanup)`);
509
+ }
510
+ else if (workerName) {
511
+ await runDeleteStep(`Worker ${workerName}`, !!options.dryRun, result, () => {
512
+ runWrangler(projectDir, ['wrangler', 'delete', workerName, '--force'], { input: 'y\n' });
513
+ });
514
+ }
515
+ const orderedTypes = [
516
+ 'kv_namespace',
517
+ 'd1_database',
518
+ 'vectorize',
519
+ 'hyperdrive',
520
+ ];
521
+ const hasD1 = resources.some((r) => r.type === 'd1_database');
522
+ const d1List = hasD1 && !options.dryRun ? fetchD1DatabaseList(projectDir) : new Map();
523
+ for (const type of orderedTypes) {
524
+ for (const resource of resources.filter((entry) => entry.type === type)) {
525
+ const label = `${type} ${resource.binding ?? resource.name}`;
526
+ await runDeleteStep(label, !!options.dryRun, result, async () => {
527
+ if (resource.type === 'kv_namespace') {
528
+ if (resource.id) {
529
+ runWrangler(projectDir, ['wrangler', 'kv', 'namespace', 'delete', '--namespace-id', resource.id, '-y']);
530
+ return;
531
+ }
532
+ const candidateNames = [
533
+ resource.binding,
534
+ resource.name,
535
+ workerName && resource.binding ? `${workerName}-${resource.binding}` : undefined,
536
+ ].filter((value, index, all) => !!value && all.indexOf(value) === index);
537
+ let lastError = null;
538
+ for (const candidate of candidateNames) {
539
+ try {
540
+ runWrangler(projectDir, ['wrangler', 'kv', 'namespace', 'delete', candidate, '-y']);
541
+ return;
542
+ }
543
+ catch (error) {
544
+ const full = extractExecErrorFull(error);
545
+ if (isAlreadyDeletedError(full))
546
+ return;
547
+ lastError = error;
548
+ }
549
+ }
550
+ throw lastError ?? new Error(`Could not delete KV namespace "${resource.binding ?? resource.name}".`);
551
+ return;
552
+ }
553
+ if (resource.type === 'd1_database') {
554
+ if (resource.id && accountId && apiToken && !isPlaceholderCloudflareId(resource.id)) {
555
+ await deleteD1Database(accountId, apiToken, resource.id);
556
+ return;
557
+ }
558
+ const deleteName = resolveManagedD1DeleteName(workerName, resource, d1List);
559
+ try {
560
+ runWrangler(projectDir, ['wrangler', 'd1', 'delete', deleteName, '-y']);
561
+ }
562
+ catch (error) {
563
+ if (deleteName !== buildLegacyManagedD1DatabaseName(resource.name)) {
564
+ runWrangler(projectDir, ['wrangler', 'd1', 'delete', buildLegacyManagedD1DatabaseName(resource.name), '-y']);
565
+ return;
566
+ }
567
+ throw error;
568
+ }
569
+ return;
570
+ }
571
+ if (resource.type === 'vectorize') {
572
+ runWrangler(projectDir, ['wrangler', 'vectorize', 'delete', resource.id || `edgebase-${resource.name}`, '-y']);
573
+ return;
574
+ }
575
+ if (resource.type === 'hyperdrive') {
576
+ let hyperdriveId = resource.id;
577
+ if (!hyperdriveId) {
578
+ // Attempt to resolve ID by name from wrangler hyperdrive list
579
+ try {
580
+ const listOutput = runWrangler(projectDir, ['wrangler', 'hyperdrive', 'list']);
581
+ const namePattern = resource.name === 'auth' ? 'edgebase-auth' : `edgebase-db-${resource.name}`;
582
+ const match = listOutput.match(new RegExp(`(${namePattern})\\s.*?([a-f0-9-]{36})`, 'i'))
583
+ ?? listOutput.match(new RegExp(`([a-f0-9-]{36}).*?${namePattern}`, 'i'));
584
+ hyperdriveId = match?.[2] ?? match?.[1];
585
+ }
586
+ catch {
587
+ // list failed
588
+ }
589
+ }
590
+ if (!hyperdriveId) {
591
+ throw new Error(`Hyperdrive id is missing and could not be resolved for "${resource.name}".`);
592
+ }
593
+ runWrangler(projectDir, ['wrangler', 'hyperdrive', 'delete', hyperdriveId], { input: 'y\n' });
594
+ }
595
+ });
596
+ }
597
+ }
598
+ if (!options.dryRun && result.failures.length === 0) {
599
+ rmSync(manifestPath, { force: true });
600
+ rmSync(join(projectDir, '.edgebase', 'secrets.json'), { force: true });
601
+ rmSync(join(projectDir, 'edgebase-schema.lock.json'), { force: true });
602
+ }
603
+ if (isJson()) {
604
+ if (result.failures.length > 0) {
605
+ raiseCliError({
606
+ code: 'destroy_partial_failure',
607
+ message: `Destroy completed with ${result.failures.length} failure(s).`,
608
+ details: {
609
+ worker: workerName,
610
+ deleted: result.deleted,
611
+ skipped: result.skipped,
612
+ failures: result.failures,
613
+ },
614
+ });
615
+ }
616
+ console.log(JSON.stringify({
617
+ status: options.dryRun ? 'dry-run' : 'success',
618
+ worker: workerName,
619
+ deleted: result.deleted,
620
+ skipped: result.skipped,
621
+ }));
622
+ }
623
+ else if (result.failures.length === 0) {
624
+ console.log(chalk.green(`✓ ${options.dryRun ? 'Destroy preview complete.' : 'Cloudflare resources removed.'}`));
625
+ if (options.dryRun) {
626
+ console.log(chalk.dim(' Run npx edgebase destroy --yes to execute.'));
627
+ }
628
+ }
629
+ else {
630
+ raiseCliError({
631
+ code: 'destroy_partial_failure',
632
+ message: `Destroy completed with ${result.failures.length} failure(s).`,
633
+ details: {
634
+ worker: workerName,
635
+ deleted: result.deleted,
636
+ skipped: result.skipped,
637
+ failures: result.failures,
638
+ },
639
+ });
640
+ }
641
+ });
642
+ //# sourceMappingURL=destroy.js.map