@lorepack/cli 0.0.0-stage → 0.1.0-alpha.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.
Files changed (229) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/commands/activate.d.ts +20 -0
  3. package/dist/commands/activate.d.ts.map +1 -0
  4. package/dist/commands/activate.js +171 -0
  5. package/dist/commands/activate.js.map +1 -0
  6. package/dist/commands/build.d.ts +3 -0
  7. package/dist/commands/build.d.ts.map +1 -0
  8. package/dist/commands/build.js +53 -0
  9. package/dist/commands/build.js.map +1 -0
  10. package/dist/commands/config.d.ts +14 -0
  11. package/dist/commands/config.d.ts.map +1 -0
  12. package/dist/commands/config.js +109 -0
  13. package/dist/commands/config.js.map +1 -0
  14. package/dist/commands/connect.d.ts +5 -0
  15. package/dist/commands/connect.d.ts.map +1 -0
  16. package/dist/commands/connect.js +196 -0
  17. package/dist/commands/connect.js.map +1 -0
  18. package/dist/commands/deploy.d.ts +14 -0
  19. package/dist/commands/deploy.d.ts.map +1 -0
  20. package/dist/commands/deploy.js +248 -0
  21. package/dist/commands/deploy.js.map +1 -0
  22. package/dist/commands/dev.d.ts +25 -0
  23. package/dist/commands/dev.d.ts.map +1 -0
  24. package/dist/commands/dev.js +227 -0
  25. package/dist/commands/dev.js.map +1 -0
  26. package/dist/commands/diff.d.ts +3 -0
  27. package/dist/commands/diff.d.ts.map +1 -0
  28. package/dist/commands/diff.js +75 -0
  29. package/dist/commands/diff.js.map +1 -0
  30. package/dist/commands/doctor.d.ts +14 -0
  31. package/dist/commands/doctor.d.ts.map +1 -0
  32. package/dist/commands/doctor.js +96 -0
  33. package/dist/commands/doctor.js.map +1 -0
  34. package/dist/commands/export.d.ts +3 -0
  35. package/dist/commands/export.d.ts.map +1 -0
  36. package/dist/commands/export.js +126 -0
  37. package/dist/commands/export.js.map +1 -0
  38. package/dist/commands/index.d.ts +8 -0
  39. package/dist/commands/index.d.ts.map +1 -0
  40. package/dist/commands/index.js +51 -0
  41. package/dist/commands/index.js.map +1 -0
  42. package/dist/commands/init.d.ts +3 -0
  43. package/dist/commands/init.d.ts.map +1 -0
  44. package/dist/commands/init.js +95 -0
  45. package/dist/commands/init.js.map +1 -0
  46. package/dist/commands/inspect.d.ts +15 -0
  47. package/dist/commands/inspect.d.ts.map +1 -0
  48. package/dist/commands/inspect.js +527 -0
  49. package/dist/commands/inspect.js.map +1 -0
  50. package/dist/commands/mcp.d.ts +12 -0
  51. package/dist/commands/mcp.d.ts.map +1 -0
  52. package/dist/commands/mcp.js +219 -0
  53. package/dist/commands/mcp.js.map +1 -0
  54. package/dist/commands/pack.d.ts +9 -0
  55. package/dist/commands/pack.d.ts.map +1 -0
  56. package/dist/commands/pack.js +57 -0
  57. package/dist/commands/pack.js.map +1 -0
  58. package/dist/commands/plan.d.ts +3 -0
  59. package/dist/commands/plan.d.ts.map +1 -0
  60. package/dist/commands/plan.js +44 -0
  61. package/dist/commands/plan.js.map +1 -0
  62. package/dist/commands/prune.d.ts +7 -0
  63. package/dist/commands/prune.d.ts.map +1 -0
  64. package/dist/commands/prune.js +341 -0
  65. package/dist/commands/prune.js.map +1 -0
  66. package/dist/commands/search.d.ts +3 -0
  67. package/dist/commands/search.d.ts.map +1 -0
  68. package/dist/commands/search.js +84 -0
  69. package/dist/commands/search.js.map +1 -0
  70. package/dist/commands/serve.d.ts +20 -0
  71. package/dist/commands/serve.d.ts.map +1 -0
  72. package/dist/commands/serve.js +66 -0
  73. package/dist/commands/serve.js.map +1 -0
  74. package/dist/commands/status.d.ts +3 -0
  75. package/dist/commands/status.d.ts.map +1 -0
  76. package/dist/commands/status.js +27 -0
  77. package/dist/commands/status.js.map +1 -0
  78. package/dist/commands/target.d.ts +71 -0
  79. package/dist/commands/target.d.ts.map +1 -0
  80. package/dist/commands/target.js +945 -0
  81. package/dist/commands/target.js.map +1 -0
  82. package/dist/entry.d.ts +3 -0
  83. package/dist/entry.d.ts.map +1 -0
  84. package/dist/entry.js +19 -0
  85. package/dist/entry.js.map +1 -0
  86. package/dist/framework/context.d.ts +49 -0
  87. package/dist/framework/context.d.ts.map +1 -0
  88. package/dist/framework/context.js +47 -0
  89. package/dist/framework/context.js.map +1 -0
  90. package/dist/framework/exit.d.ts +36 -0
  91. package/dist/framework/exit.d.ts.map +1 -0
  92. package/dist/framework/exit.js +57 -0
  93. package/dist/framework/exit.js.map +1 -0
  94. package/dist/framework/metadata.d.ts +38 -0
  95. package/dist/framework/metadata.d.ts.map +1 -0
  96. package/dist/framework/metadata.js +47 -0
  97. package/dist/framework/metadata.js.map +1 -0
  98. package/dist/framework/program.d.ts +47 -0
  99. package/dist/framework/program.d.ts.map +1 -0
  100. package/dist/framework/program.js +189 -0
  101. package/dist/framework/program.js.map +1 -0
  102. package/dist/index.d.ts +7 -0
  103. package/dist/index.d.ts.map +1 -0
  104. package/dist/index.js +7 -0
  105. package/dist/index.js.map +1 -0
  106. package/dist/migrations/README.md +7 -0
  107. package/dist/migrations/build/0001_catalog.sql +85 -0
  108. package/dist/migrations/build/0002_tables.sql +43 -0
  109. package/dist/migrations/build/0003_table-cell-range.sql +13 -0
  110. package/dist/migrations/build/0004_chunk-page.sql +11 -0
  111. package/dist/migrations/state/0001_state.sql +40 -0
  112. package/dist/public-entry.js +38563 -0
  113. package/dist/public-entry.js.map +7 -0
  114. package/dist/public-index.js +36428 -0
  115. package/dist/public-index.js.map +7 -0
  116. package/dist/services/build.d.ts +48 -0
  117. package/dist/services/build.d.ts.map +1 -0
  118. package/dist/services/build.js +499 -0
  119. package/dist/services/build.js.map +1 -0
  120. package/dist/services/builds.d.ts +40 -0
  121. package/dist/services/builds.d.ts.map +1 -0
  122. package/dist/services/builds.js +184 -0
  123. package/dist/services/builds.js.map +1 -0
  124. package/dist/services/cancellation.d.ts +27 -0
  125. package/dist/services/cancellation.d.ts.map +1 -0
  126. package/dist/services/cancellation.js +41 -0
  127. package/dist/services/cancellation.js.map +1 -0
  128. package/dist/services/cloudflare-target.d.ts +38 -0
  129. package/dist/services/cloudflare-target.d.ts.map +1 -0
  130. package/dist/services/cloudflare-target.js +471 -0
  131. package/dist/services/cloudflare-target.js.map +1 -0
  132. package/dist/services/comparer.d.ts +15 -0
  133. package/dist/services/comparer.d.ts.map +1 -0
  134. package/dist/services/comparer.js +43 -0
  135. package/dist/services/comparer.js.map +1 -0
  136. package/dist/services/config-resolve.d.ts +60 -0
  137. package/dist/services/config-resolve.d.ts.map +1 -0
  138. package/dist/services/config-resolve.js +178 -0
  139. package/dist/services/config-resolve.js.map +1 -0
  140. package/dist/services/deploy.d.ts +70 -0
  141. package/dist/services/deploy.d.ts.map +1 -0
  142. package/dist/services/deploy.js +260 -0
  143. package/dist/services/deploy.js.map +1 -0
  144. package/dist/services/dev-session.d.ts +52 -0
  145. package/dist/services/dev-session.d.ts.map +1 -0
  146. package/dist/services/dev-session.js +102 -0
  147. package/dist/services/dev-session.js.map +1 -0
  148. package/dist/services/doctor.d.ts +35 -0
  149. package/dist/services/doctor.d.ts.map +1 -0
  150. package/dist/services/doctor.js +389 -0
  151. package/dist/services/doctor.js.map +1 -0
  152. package/dist/services/export.d.ts +20 -0
  153. package/dist/services/export.d.ts.map +1 -0
  154. package/dist/services/export.js +129 -0
  155. package/dist/services/export.js.map +1 -0
  156. package/dist/services/init.d.ts +46 -0
  157. package/dist/services/init.d.ts.map +1 -0
  158. package/dist/services/init.js +231 -0
  159. package/dist/services/init.js.map +1 -0
  160. package/dist/services/packing.d.ts +25 -0
  161. package/dist/services/packing.d.ts.map +1 -0
  162. package/dist/services/packing.js +69 -0
  163. package/dist/services/packing.js.map +1 -0
  164. package/dist/services/parse-cache.d.ts +47 -0
  165. package/dist/services/parse-cache.d.ts.map +1 -0
  166. package/dist/services/parse-cache.js +65 -0
  167. package/dist/services/parse-cache.js.map +1 -0
  168. package/dist/services/project.d.ts +49 -0
  169. package/dist/services/project.d.ts.map +1 -0
  170. package/dist/services/project.js +119 -0
  171. package/dist/services/project.js.map +1 -0
  172. package/dist/services/remote-prune.d.ts +7 -0
  173. package/dist/services/remote-prune.d.ts.map +1 -0
  174. package/dist/services/remote-prune.js +34 -0
  175. package/dist/services/remote-prune.js.map +1 -0
  176. package/dist/services/retention.d.ts +20 -0
  177. package/dist/services/retention.d.ts.map +1 -0
  178. package/dist/services/retention.js +106 -0
  179. package/dist/services/retention.js.map +1 -0
  180. package/dist/services/revalidate.d.ts +36 -0
  181. package/dist/services/revalidate.d.ts.map +1 -0
  182. package/dist/services/revalidate.js +73 -0
  183. package/dist/services/revalidate.js.map +1 -0
  184. package/dist/services/search.d.ts +25 -0
  185. package/dist/services/search.d.ts.map +1 -0
  186. package/dist/services/search.js +88 -0
  187. package/dist/services/search.js.map +1 -0
  188. package/dist/services/serving.d.ts +70 -0
  189. package/dist/services/serving.d.ts.map +1 -0
  190. package/dist/services/serving.js +240 -0
  191. package/dist/services/serving.js.map +1 -0
  192. package/dist/services/status.d.ts +54 -0
  193. package/dist/services/status.d.ts.map +1 -0
  194. package/dist/services/status.js +203 -0
  195. package/dist/services/status.js.map +1 -0
  196. package/dist/services/studio-assets.d.ts +13 -0
  197. package/dist/services/studio-assets.d.ts.map +1 -0
  198. package/dist/services/studio-assets.js +94 -0
  199. package/dist/services/studio-assets.js.map +1 -0
  200. package/dist/services/studio-diagnostics.d.ts +28 -0
  201. package/dist/services/studio-diagnostics.d.ts.map +1 -0
  202. package/dist/services/studio-diagnostics.js +88 -0
  203. package/dist/services/studio-diagnostics.js.map +1 -0
  204. package/dist/services/studio-endpoints.d.ts +61 -0
  205. package/dist/services/studio-endpoints.d.ts.map +1 -0
  206. package/dist/services/studio-endpoints.js +88 -0
  207. package/dist/services/studio-endpoints.js.map +1 -0
  208. package/dist/services/studio-mutations.d.ts +32 -0
  209. package/dist/services/studio-mutations.d.ts.map +1 -0
  210. package/dist/services/studio-mutations.js +149 -0
  211. package/dist/services/studio-mutations.js.map +1 -0
  212. package/dist/services/versions.d.ts +38 -0
  213. package/dist/services/versions.d.ts.map +1 -0
  214. package/dist/services/versions.js +46 -0
  215. package/dist/services/versions.js.map +1 -0
  216. package/dist/services/watch.d.ts +103 -0
  217. package/dist/services/watch.d.ts.map +1 -0
  218. package/dist/services/watch.js +420 -0
  219. package/dist/services/watch.js.map +1 -0
  220. package/package.json +73 -4
  221. package/studio-dist/assets/ibm-plex-mono-latin-400-normal-Dm_PoFIZ.woff2 +0 -0
  222. package/studio-dist/assets/ibm-plex-mono-latin-500-normal-C_OblDzq.woff2 +0 -0
  223. package/studio-dist/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  224. package/studio-dist/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  225. package/studio-dist/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  226. package/studio-dist/assets/index-BuAYbQVX.js +66 -0
  227. package/studio-dist/assets/index-C_3XEUt4.css +1 -0
  228. package/studio-dist/index.html +30 -0
  229. package/README.md +0 -3
@@ -0,0 +1,945 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { randomBytes } from 'node:crypto';
3
+ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { promisify } from 'node:util';
7
+ import { LoreError, loadConfig, writeFileAtomic } from '@lorepack/core';
8
+ import { hashRuntimeToken, hasRuntimeToken, listRuntimeTokens, RUNTIME_TOKEN_OVERLAP_MS, RUNTIME_TOKEN_PREFIX, revokeRuntimeTokens, rotateRuntimeTokenHash, runProjectionMigrations, storeRuntimeTokenHash, } from '@lorepack/deploy-cloudflare';
9
+ import { targetsDirectory } from '../services/config-resolve.js';
10
+ const execFileAsync = promisify(execFile);
11
+ const CLOUDFLARE_CAPABILITIES = ['lexical-search', 'structured-context', 'table-query'];
12
+ const CLOUDFLARE_SETUP_DOC = 'docs/integrations/cloudflare-target-setup.md';
13
+ const WRANGLER_REMOTE_D1_RETRY_LIMIT = 3;
14
+ const WRANGLER_BIN = join(import.meta.dirname, '..', '..', '..', 'deploy-cloudflare', 'node_modules', 'wrangler', 'bin', 'wrangler.js');
15
+ const WRANGLER_D1_TRANSACTION_CONTROL = new Set(['begin immediate', 'commit', 'rollback']);
16
+ export function targetCommand(options = {}) {
17
+ return {
18
+ name: 'target',
19
+ description: 'Prepare and inspect deployment targets.',
20
+ arguments: [
21
+ { name: 'subject', description: 'add, status, or token', required: true },
22
+ { name: 'target', description: 'cloudflare', required: true },
23
+ ],
24
+ flags: [
25
+ { flags: '--dry-run', description: 'show the setup plan and change nothing' },
26
+ { flags: '--yes', description: 'apply without asking' },
27
+ { flags: '--rotate', description: 'replace the current runtime bearer token' },
28
+ { flags: '--revoke', description: 'remove every runtime bearer token' },
29
+ { flags: '--account-id <id>', description: 'connect to an existing Cloudflare account id' },
30
+ { flags: '--worker <name>', description: 'connect to an existing Worker name' },
31
+ { flags: '--catalog-db <name>', description: 'connect to an existing D1 database name' },
32
+ {
33
+ flags: '--objects-bucket <name>',
34
+ description: 'connect to an existing R2 bucket name',
35
+ },
36
+ ],
37
+ handler: async (args, flags, context) => {
38
+ const subject = args[0];
39
+ const target = args[1];
40
+ if (target !== 'cloudflare' ||
41
+ (subject !== 'add' && subject !== 'status' && subject !== 'token')) {
42
+ throw new LoreError('LORE_E_INVALID_ARGUMENT', `Unknown target command: ${[subject, target].filter(Boolean).join(' ')}.`, {
43
+ remediation: 'Use `lorepack target add cloudflare`, `lorepack target status cloudflare`, or `lorepack target token cloudflare`.',
44
+ subject: [subject, target].filter(Boolean).join(' '),
45
+ });
46
+ }
47
+ if (subject === 'token')
48
+ return await handleTokenCommand(flags, context, options);
49
+ if (subject === 'status')
50
+ return await handleStatusCommand(context, options);
51
+ const config = loadConfig({ cwd: context.options.cwd });
52
+ const adapter = options.adapter ?? createWranglerAdapter();
53
+ const detection = await adapter.detect();
54
+ if (!detection.installed) {
55
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', 'The pinned Cloudflare Wrangler dependency is not available.', {
56
+ remediation: 'Run `pnpm install` so the pinned Wrangler dependency is available.',
57
+ subject: 'cloudflare',
58
+ });
59
+ }
60
+ const identity = await adapter.whoami();
61
+ if (!identity.authenticated) {
62
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', 'Wrangler is installed, but no Cloudflare login is available for this machine.', {
63
+ remediation: `Log in first with \`${process.execPath} ${detection.path ?? WRANGLER_BIN} login --device\`.`,
64
+ subject: 'cloudflare',
65
+ });
66
+ }
67
+ const defaults = defaultNames(config.config.name);
68
+ const existing = readCloudflareTargetReceiptIfPresent(config.projectRoot);
69
+ const usesExplicitResources = typeof flags.accountId === 'string' ||
70
+ typeof flags.worker === 'string' ||
71
+ typeof flags.catalogDb === 'string' ||
72
+ typeof flags.objectsBucket === 'string';
73
+ const planned = {
74
+ accountId: typeof flags.accountId === 'string'
75
+ ? flags.accountId
76
+ : (existing?.accountId ?? identity.accountId ?? '(unavailable from wrangler whoami)'),
77
+ workerName: typeof flags.worker === 'string'
78
+ ? flags.worker
79
+ : (existing?.workerName ?? defaults.workerName),
80
+ catalogDatabaseName: typeof flags.catalogDb === 'string'
81
+ ? flags.catalogDb
82
+ : (existing?.catalogDatabaseName ?? defaults.catalogDatabaseName),
83
+ objectsBucketName: typeof flags.objectsBucket === 'string'
84
+ ? flags.objectsBucket
85
+ : (existing?.objectsBucketName ?? defaults.objectsBucketName),
86
+ };
87
+ if (planned.accountId.startsWith('(')) {
88
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', 'Wrangler did not report a Cloudflare account id for this login.', {
89
+ remediation: 'Log into the intended Cloudflare account again, or pass --account-id explicitly.',
90
+ subject: 'cloudflare',
91
+ });
92
+ }
93
+ const adapterWithProvisioning = requireProvisioningAdapter(adapter);
94
+ const remote = await adapterWithProvisioning.inspectResources({
95
+ workerName: planned.workerName,
96
+ catalogDatabaseName: planned.catalogDatabaseName,
97
+ objectsBucketName: planned.objectsBucketName,
98
+ });
99
+ const mode = existing === null ? (usesExplicitResources ? 'connect-existing' : 'provision') : 'reuse';
100
+ const plan = renderCloudflarePlan({
101
+ project: config.config.name,
102
+ wranglerVersion: detection.version ?? 'unknown',
103
+ identity,
104
+ planned,
105
+ existing,
106
+ remote,
107
+ mode,
108
+ });
109
+ if (flags.dryRun === true) {
110
+ return {
111
+ human: `${plan}\n\n(dry run, nothing was changed)`,
112
+ json: {
113
+ target: 'cloudflare',
114
+ project: config.config.name,
115
+ wranglerVersion: detection.version ?? null,
116
+ identity,
117
+ planned,
118
+ capabilities: CLOUDFLARE_CAPABILITIES,
119
+ },
120
+ };
121
+ }
122
+ if (flags.yes !== true) {
123
+ const confirmed = await (options.confirm ?? confirmTargetSetup)(plan, context);
124
+ if (!confirmed)
125
+ return { human: `${plan}\n\nCancelled. Nothing was changed.` };
126
+ }
127
+ const drift = existing === null ? [] : compareReceipt(existing, planned);
128
+ if (drift.length > 0) {
129
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', `The cloudflare target receipt does not match the requested resources: ${drift
130
+ .map((entry) => entry.field)
131
+ .join(', ')}.`, {
132
+ remediation: `Fix the drift or remove ${cloudflareTargetPath(config.projectRoot)} and set the target up again.\n${drift
133
+ .map((entry) => ` ${entry.field}: receipt=${entry.current} requested=${entry.requested}`)
134
+ .join('\n')}`,
135
+ subject: 'cloudflare',
136
+ });
137
+ }
138
+ if (existing !== null && usesExplicitResources === false) {
139
+ const missing = describeMissingRemoteResources(remote);
140
+ if (missing.length > 0) {
141
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', `The cloudflare target receipt points at remote resources that are missing: ${missing.join(', ')}.`, {
142
+ remediation: `Recreate the missing resources or remove ${cloudflareTargetPath(config.projectRoot)} and run \`lorepack target add cloudflare\` again.`,
143
+ subject: 'cloudflare',
144
+ });
145
+ }
146
+ return {
147
+ human: `${plan}\n\ncloudflare already matches ${cloudflareTargetPath(config.projectRoot)}.`,
148
+ json: existing,
149
+ };
150
+ }
151
+ if (usesExplicitResources &&
152
+ (typeof flags.worker !== 'string' ||
153
+ typeof flags.catalogDb !== 'string' ||
154
+ typeof flags.objectsBucket !== 'string')) {
155
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', 'Connecting existing Cloudflare resources requires explicit resource identifiers together.', {
156
+ remediation: 'Pass --worker, --catalog-db, and --objects-bucket together (and --account-id when Wrangler cannot infer it), or omit them all to provision the deterministic names automatically.',
157
+ subject: 'cloudflare',
158
+ });
159
+ }
160
+ if (usesExplicitResources) {
161
+ const missing = describeMissingExistingResources(remote);
162
+ if (missing.length > 0) {
163
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', `The requested existing Cloudflare resources are not visible: ${missing.join(', ')}.`, {
164
+ remediation: 'Create the named resources first, or omit the explicit identifiers so Lorepack provisions the deterministic names.',
165
+ subject: 'cloudflare',
166
+ });
167
+ }
168
+ }
169
+ else {
170
+ const conflicts = describeProvisioningConflicts(remote, planned);
171
+ if (conflicts.length > 0) {
172
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', 'The deterministic Cloudflare resource names are already in use.', {
173
+ remediation: `Connect those resources explicitly with --worker, --catalog-db, and --objects-bucket, or rename the project before rerunning.\n${conflicts
174
+ .map((line) => ` ${line}`)
175
+ .join('\n')}`,
176
+ subject: 'cloudflare',
177
+ });
178
+ }
179
+ await createCloudflareProvisionedResources(adapterWithProvisioning, planned);
180
+ }
181
+ const receipt = {
182
+ formatVersion: 1,
183
+ target: 'cloudflare',
184
+ project: config.config.name,
185
+ configuredAt: (options.now ?? (() => new Date()))().toISOString(),
186
+ wranglerVersion: detection.version ?? 'unknown',
187
+ accountId: planned.accountId,
188
+ workerName: planned.workerName,
189
+ catalogDatabaseName: planned.catalogDatabaseName,
190
+ objectsBucketName: planned.objectsBucketName,
191
+ capabilities: [...CLOUDFLARE_CAPABILITIES],
192
+ };
193
+ const path = cloudflareTargetPath(config.projectRoot);
194
+ writeFileAtomic(path, `${JSON.stringify(receipt, null, 2)}\n`);
195
+ return {
196
+ human: `${plan}\n\nConfigured cloudflare for ${config.config.name}.\nReceipt: ${path}`,
197
+ json: receipt,
198
+ };
199
+ },
200
+ };
201
+ }
202
+ async function handleStatusCommand(context, options) {
203
+ const config = loadConfig({ cwd: context.options.cwd });
204
+ const adapter = options.adapter ?? createWranglerAdapter();
205
+ if (!isCloudflareTargetTokenAdapter(adapter)) {
206
+ throw new LoreError('LORE_E_INTERNAL', 'The Cloudflare target adapter cannot open the catalog database for target status.', {
207
+ remediation: 'Use the default Wrangler-backed adapter, or provide an adapter with openCatalogDatabase().',
208
+ });
209
+ }
210
+ const detection = await adapter.detect();
211
+ if (!detection.installed) {
212
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', 'The pinned Cloudflare Wrangler dependency is not available.', {
213
+ remediation: 'Run `pnpm install` so the pinned Wrangler dependency is available.',
214
+ subject: 'cloudflare',
215
+ });
216
+ }
217
+ const identity = await adapter.whoami();
218
+ if (!identity.authenticated) {
219
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', 'Wrangler is installed, but no Cloudflare login is available for this machine.', {
220
+ remediation: `Log in first with \`${process.execPath} ${detection.path ?? WRANGLER_BIN} login --device\`.`,
221
+ subject: 'cloudflare',
222
+ });
223
+ }
224
+ const receipt = readCloudflareTargetReceipt(config.projectRoot);
225
+ if (identity.accountId !== undefined && identity.accountId !== receipt.accountId) {
226
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', `The cloudflare target receipt belongs to account ${receipt.accountId}, but Wrangler is logged into ${identity.accountId}.`, {
227
+ remediation: 'Log into the matching Cloudflare account or rewrite the receipt with `lorepack target add cloudflare`.',
228
+ subject: receipt.accountId,
229
+ });
230
+ }
231
+ const db = adapter.openCatalogDatabase(receipt.catalogDatabaseName);
232
+ const status = await readCloudflareTargetStatus(db, receipt.project);
233
+ return {
234
+ human: renderCloudflareTargetStatus(receipt.workerName, status),
235
+ json: {
236
+ target: 'cloudflare',
237
+ worker: receipt.workerName,
238
+ activeBuildId: status.activeBuildId,
239
+ builds: status.builds,
240
+ },
241
+ };
242
+ }
243
+ async function readCloudflareTargetStatus(db, projectId) {
244
+ const hasProjectedBuilds = await db
245
+ .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'projected_builds'")
246
+ .run();
247
+ if ((hasProjectedBuilds.results?.length ?? 0) === 0) {
248
+ return { activeBuildId: null, builds: [] };
249
+ }
250
+ const active = await db
251
+ .prepare('SELECT build_id AS buildId FROM active_build WHERE id = 1')
252
+ .run();
253
+ const activeBuildId = active.results?.[0]?.buildId ?? null;
254
+ const projected = await db
255
+ .prepare(`SELECT build_id AS buildId, projected_at AS projectedAt, verified_at AS verifiedAt
256
+ FROM projected_builds
257
+ WHERE project_id = ?
258
+ ORDER BY projected_at DESC, build_id DESC`)
259
+ .bind(projectId)
260
+ .run();
261
+ return {
262
+ activeBuildId,
263
+ builds: (projected.results ?? []).map((row) => ({
264
+ buildId: row.buildId,
265
+ deployedAt: row.projectedAt,
266
+ state: row.buildId === activeBuildId
267
+ ? 'active'
268
+ : row.verifiedAt === null
269
+ ? 'projected'
270
+ : 'verified',
271
+ active: row.buildId === activeBuildId,
272
+ })),
273
+ };
274
+ }
275
+ function renderCloudflareTargetStatus(workerName, status) {
276
+ const lines = [`Cloudflare target status for ${workerName}`, ''];
277
+ if (status.builds.length === 0) {
278
+ lines.push('No remote projected builds yet.');
279
+ return lines.join('\n');
280
+ }
281
+ lines.push(' BUILD DEPLOYED STATE');
282
+ for (const build of status.builds) {
283
+ lines.push(`${build.active ? '*' : ' '} ${build.buildId.slice(0, 17)} ${build.deployedAt.slice(0, 19)} ${build.state}`);
284
+ }
285
+ lines.push('', '* active');
286
+ return lines.join('\n');
287
+ }
288
+ async function handleTokenCommand(flags, context, options) {
289
+ if (flags.rotate === true && flags.revoke === true) {
290
+ throw new LoreError('LORE_E_INVALID_ARGUMENT', 'Choose either --rotate or --revoke, not both.', {
291
+ remediation: 'Use `lorepack target token cloudflare --rotate` or `--revoke`.',
292
+ });
293
+ }
294
+ const config = loadConfig({ cwd: context.options.cwd });
295
+ const adapter = options.adapter ?? createWranglerAdapter();
296
+ if (!isCloudflareTargetTokenAdapter(adapter)) {
297
+ throw new LoreError('LORE_E_INTERNAL', 'The Cloudflare target adapter cannot open the catalog database for token management.', {
298
+ remediation: 'Use the default Wrangler-backed adapter, or provide an adapter with openCatalogDatabase().',
299
+ });
300
+ }
301
+ const detection = await adapter.detect();
302
+ if (!detection.installed) {
303
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', 'The pinned Cloudflare Wrangler dependency is not available.', {
304
+ remediation: 'Run `pnpm install` so the pinned Wrangler dependency is available.',
305
+ subject: 'cloudflare',
306
+ });
307
+ }
308
+ const identity = await adapter.whoami();
309
+ if (!identity.authenticated) {
310
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', 'Wrangler is installed, but no Cloudflare login is available for this machine.', {
311
+ remediation: `Log in first with \`${process.execPath} ${detection.path ?? WRANGLER_BIN} login --device\`.`,
312
+ subject: 'cloudflare',
313
+ });
314
+ }
315
+ const receipt = readCloudflareTargetReceipt(config.projectRoot);
316
+ if (identity.accountId !== undefined && identity.accountId !== receipt.accountId) {
317
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', `The cloudflare target receipt belongs to account ${receipt.accountId}, but Wrangler is logged into ${identity.accountId}.`, {
318
+ remediation: 'Log into the matching Cloudflare account or rewrite the receipt with `lorepack target add cloudflare`.',
319
+ subject: receipt.accountId,
320
+ });
321
+ }
322
+ const db = adapter.openCatalogDatabase(receipt.catalogDatabaseName);
323
+ await runProjectionMigrations(db);
324
+ if (flags.revoke === true) {
325
+ const revoked = await revokeRuntimeTokens(db);
326
+ return {
327
+ human: revoked === 0
328
+ ? `No runtime token was configured for ${receipt.workerName}.`
329
+ : `Revoked ${revoked} runtime token${revoked === 1 ? '' : 's'} for ${receipt.workerName}.`,
330
+ json: { target: 'cloudflare', worker: receipt.workerName, revoked },
331
+ };
332
+ }
333
+ const alreadyConfigured = await hasRuntimeToken(db);
334
+ if (alreadyConfigured && flags.rotate !== true) {
335
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', `A runtime token already exists for ${receipt.workerName}.`, {
336
+ remediation: 'Use `lorepack target token cloudflare --rotate` to replace it, or `--revoke` to remove it first.',
337
+ subject: receipt.workerName,
338
+ });
339
+ }
340
+ const token = issueRuntimeToken();
341
+ const issuedAt = (options.now ?? (() => new Date()))();
342
+ const issuedAtIso = issuedAt.toISOString();
343
+ const overlapEndsAt = alreadyConfigured === true
344
+ ? new Date(issuedAt.getTime() + RUNTIME_TOKEN_OVERLAP_MS).toISOString()
345
+ : null;
346
+ if (overlapEndsAt === null) {
347
+ await storeRuntimeTokenHash(db, await hashRuntimeToken(token), issuedAtIso);
348
+ }
349
+ else {
350
+ await rotateRuntimeTokenHash(db, await hashRuntimeToken(token), issuedAtIso, overlapEndsAt);
351
+ }
352
+ const action = alreadyConfigured ? 'Rotated' : 'Generated';
353
+ const activeTokens = await listRuntimeTokens(db);
354
+ return {
355
+ human: [
356
+ `${action} the runtime bearer token for ${receipt.workerName}.`,
357
+ ...(overlapEndsAt === null
358
+ ? []
359
+ : [`The previous token remains valid until ${overlapEndsAt}.`]),
360
+ 'Token (shown once):',
361
+ token,
362
+ '',
363
+ 'Set it in your shell before running remote verification or client requests:',
364
+ ` export LORE_REMOTE_BEARER_TOKEN=${token}`,
365
+ ].join('\n'),
366
+ json: {
367
+ target: 'cloudflare',
368
+ worker: receipt.workerName,
369
+ rotated: alreadyConfigured,
370
+ activeTokens,
371
+ ...(overlapEndsAt === null ? {} : { overlapEndsAt }),
372
+ token,
373
+ },
374
+ };
375
+ }
376
+ export function cloudflareTargetPath(projectRoot) {
377
+ return join(targetsDirectory(projectRoot), 'cloudflare.json');
378
+ }
379
+ function renderCloudflarePlan(input) {
380
+ const lines = [`Cloudflare target plan for ${input.project}`, ''];
381
+ lines.push('Authentication');
382
+ lines.push(` Wrangler ${input.wranglerVersion}`);
383
+ lines.push(` ${input.identity.email ?? 'Cloudflare user'}${input.identity.accountName === undefined ? '' : ` on ${input.identity.accountName}`}`);
384
+ lines.push('');
385
+ lines.push('Resources');
386
+ lines.push(` Worker: ${describeWorkerPlan(input.mode, input.planned.workerName, input.remote)}`);
387
+ lines.push(` D1 catalog: ${describeCatalogPlan(input.mode, input.planned.catalogDatabaseName, input.remote)}`);
388
+ lines.push(` R2 objects: ${describeBucketPlan(input.mode, input.planned.objectsBucketName, input.remote)}`);
389
+ lines.push(` Account: ${input.planned.accountId}`);
390
+ lines.push('');
391
+ lines.push('Capabilities');
392
+ lines.push(` ${CLOUDFLARE_CAPABILITIES.join(', ')}`);
393
+ lines.push('');
394
+ lines.push('Receipt');
395
+ lines.push(' Write non-secret Cloudflare identifiers under .lore/targets/cloudflare.json');
396
+ if (input.existing !== null) {
397
+ lines.push(' Existing receipt found and will be checked for drift before any rewrite');
398
+ }
399
+ lines.push('');
400
+ lines.push('Permissions');
401
+ lines.push(` ${CLOUDFLARE_SETUP_DOC}`);
402
+ return lines.join('\n');
403
+ }
404
+ function describeWorkerPlan(mode, name, remote) {
405
+ if (mode === 'provision') {
406
+ return remote.workerExists ? `conflict ${name}` : `reserve ${name} for the first deploy`;
407
+ }
408
+ if (mode === 'connect-existing') {
409
+ return remote.workerExists ? `connect ${name}` : `record ${name} for the first deploy`;
410
+ }
411
+ return remote.workerExists ? `reuse ${name}` : `reuse ${name} (not deployed yet)`;
412
+ }
413
+ function describeCatalogPlan(mode, name, remote) {
414
+ if (mode === 'provision') {
415
+ return remote.catalogDatabaseExists ? `conflict ${name}` : `create ${name}`;
416
+ }
417
+ if (mode === 'connect-existing')
418
+ return `connect ${name}`;
419
+ return remote.catalogDatabaseExists ? `reuse ${name}` : `reuse ${name} (missing remotely)`;
420
+ }
421
+ function describeBucketPlan(mode, name, remote) {
422
+ if (mode === 'provision') {
423
+ return remote.objectsBucketExists ? `conflict ${name}` : `create ${name}`;
424
+ }
425
+ if (mode === 'connect-existing')
426
+ return `connect ${name}`;
427
+ return remote.objectsBucketExists ? `reuse ${name}` : `reuse ${name} (missing remotely)`;
428
+ }
429
+ function compareReceipt(existing, requested) {
430
+ const drift = [];
431
+ if (existing.accountId !== requested.accountId) {
432
+ drift.push({ field: 'accountId', current: existing.accountId, requested: requested.accountId });
433
+ }
434
+ if (existing.workerName !== requested.workerName) {
435
+ drift.push({
436
+ field: 'workerName',
437
+ current: existing.workerName,
438
+ requested: requested.workerName,
439
+ });
440
+ }
441
+ if (existing.catalogDatabaseName !== requested.catalogDatabaseName) {
442
+ drift.push({
443
+ field: 'catalogDatabaseName',
444
+ current: existing.catalogDatabaseName,
445
+ requested: requested.catalogDatabaseName,
446
+ });
447
+ }
448
+ if (existing.objectsBucketName !== requested.objectsBucketName) {
449
+ drift.push({
450
+ field: 'objectsBucketName',
451
+ current: existing.objectsBucketName,
452
+ requested: requested.objectsBucketName,
453
+ });
454
+ }
455
+ return drift;
456
+ }
457
+ function defaultNames(project) {
458
+ const slug = project
459
+ .toLowerCase()
460
+ .replace(/[^a-z0-9]+/g, '-')
461
+ .replace(/^-+|-+$/g, '')
462
+ .slice(0, 40);
463
+ return {
464
+ workerName: `${slug}-runtime`,
465
+ catalogDatabaseName: `${slug}-catalog`,
466
+ objectsBucketName: `${slug}-objects`,
467
+ };
468
+ }
469
+ export function createWranglerAdapter() {
470
+ return {
471
+ async detect() {
472
+ if (!existsSync(WRANGLER_BIN))
473
+ return { installed: false };
474
+ const { stdout } = await execFileAsync(process.execPath, [WRANGLER_BIN, '--version'], {
475
+ cwd: join(import.meta.dirname, '..', '..', '..', 'deploy-cloudflare'),
476
+ });
477
+ return { installed: true, version: stdout.trim(), path: WRANGLER_BIN };
478
+ },
479
+ async whoami() {
480
+ try {
481
+ const { stdout } = await execFileAsync(process.execPath, [WRANGLER_BIN, 'whoami', '--json'], {
482
+ cwd: join(import.meta.dirname, '..', '..', '..', 'deploy-cloudflare'),
483
+ env: { ...process.env, NO_D1_WARNING: 'true' },
484
+ });
485
+ const parsed = JSON.parse(stdout);
486
+ return {
487
+ authenticated: true,
488
+ ...(parsed.email === undefined ? {} : { email: parsed.email }),
489
+ ...(parsed.accounts?.[0]?.id === undefined ? {} : { accountId: parsed.accounts[0].id }),
490
+ ...(parsed.accounts?.[0]?.name === undefined
491
+ ? {}
492
+ : { accountName: parsed.accounts[0].name }),
493
+ };
494
+ }
495
+ catch {
496
+ return { authenticated: false };
497
+ }
498
+ },
499
+ async inspectResources({ workerName, catalogDatabaseName, objectsBucketName, }) {
500
+ const [worker, catalog, bucket] = await Promise.all([
501
+ wranglerWorkerExists(workerName),
502
+ wranglerD1Exists(catalogDatabaseName),
503
+ wranglerBucketExists(objectsBucketName),
504
+ ]);
505
+ return {
506
+ workerExists: worker,
507
+ catalogDatabaseExists: catalog,
508
+ objectsBucketExists: bucket,
509
+ };
510
+ },
511
+ async createCatalogDatabase(name) {
512
+ await execWrangler(['d1', 'create', name]);
513
+ },
514
+ async createObjectsBucket(name) {
515
+ await execWrangler(['r2', 'bucket', 'create', name]);
516
+ },
517
+ openCatalogDatabase(name) {
518
+ return new WranglerCatalogDatabase(name);
519
+ },
520
+ };
521
+ }
522
+ function requireProvisioningAdapter(adapter) {
523
+ if (isCloudflareTargetProvisioningAdapter(adapter))
524
+ return adapter;
525
+ throw new LoreError('LORE_E_INTERNAL', 'The Cloudflare target adapter cannot inspect or provision remote resources.', {
526
+ remediation: 'Use the default Wrangler-backed adapter, or provide one with inspectResources(), createCatalogDatabase(), and createObjectsBucket().',
527
+ });
528
+ }
529
+ function isCloudflareTargetProvisioningAdapter(adapter) {
530
+ return (typeof adapter.inspectResources ===
531
+ 'function' &&
532
+ typeof adapter.createCatalogDatabase ===
533
+ 'function' &&
534
+ typeof adapter.createObjectsBucket ===
535
+ 'function');
536
+ }
537
+ function isCloudflareTargetTokenAdapter(adapter) {
538
+ return (typeof adapter.openCatalogDatabase === 'function');
539
+ }
540
+ function describeMissingRemoteResources(remote) {
541
+ return [
542
+ ...(remote.catalogDatabaseExists ? [] : ['D1 catalog']),
543
+ ...(remote.objectsBucketExists ? [] : ['R2 objects']),
544
+ ];
545
+ }
546
+ function describeMissingExistingResources(remote) {
547
+ return describeMissingRemoteResources(remote);
548
+ }
549
+ function describeProvisioningConflicts(remote, planned) {
550
+ return [
551
+ ...(remote.workerExists
552
+ ? [`Worker name ${planned.workerName} already has deployments in this account.`]
553
+ : []),
554
+ ...(remote.catalogDatabaseExists
555
+ ? [`D1 database ${planned.catalogDatabaseName} already exists.`]
556
+ : []),
557
+ ...(remote.objectsBucketExists
558
+ ? [`R2 bucket ${planned.objectsBucketName} already exists.`]
559
+ : []),
560
+ ];
561
+ }
562
+ async function createCloudflareProvisionedResources(adapter, planned) {
563
+ try {
564
+ await adapter.createCatalogDatabase(planned.catalogDatabaseName);
565
+ await adapter.createObjectsBucket(planned.objectsBucketName);
566
+ }
567
+ catch (cause) {
568
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', 'Cloudflare resource provisioning failed during target setup.', {
569
+ remediation: `Check the token permissions in ${CLOUDFLARE_SETUP_DOC}, the account quota, and whether the resource names are available, then rerun \`lorepack target add cloudflare\`.`,
570
+ subject: 'cloudflare',
571
+ cause,
572
+ });
573
+ }
574
+ const confirmed = await adapter.inspectResources(planned);
575
+ const missing = describeMissingRemoteResources(confirmed);
576
+ if (missing.length > 0) {
577
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', `Cloudflare target setup did not leave the expected remote resources visible: ${missing.join(', ')}.`, {
578
+ remediation: 'Retry the setup, then inspect the Cloudflare account directly if the resources still do not appear.',
579
+ subject: 'cloudflare',
580
+ });
581
+ }
582
+ }
583
+ async function confirmTargetSetup(plan, context) {
584
+ if (!context.streams.isTty) {
585
+ throw new LoreError('LORE_E_INVALID_ARGUMENT', 'Target setup confirmation requires a terminal when --yes is not given.', {
586
+ remediation: 'Re-run with `--yes` in CI or another non-interactive environment.',
587
+ });
588
+ }
589
+ context.write(`${plan}\n\nWrite this target receipt? [y/N] `);
590
+ const answer = await new Promise((resolve) => {
591
+ process.stdin.setEncoding('utf8');
592
+ process.stdin.once('data', (chunk) => resolve(String(chunk)));
593
+ });
594
+ return /^y(es)?\s*$/i.test(answer.trim());
595
+ }
596
+ export function readCloudflareTargetReceipt(projectRoot) {
597
+ const path = cloudflareTargetPath(projectRoot);
598
+ if (!existsSync(path)) {
599
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', 'This project has no cloudflare target receipt.', {
600
+ remediation: 'Run `lorepack target add cloudflare` first.',
601
+ subject: 'cloudflare',
602
+ });
603
+ }
604
+ return readCloudflareTargetReceiptFile(path);
605
+ }
606
+ function readCloudflareTargetReceiptIfPresent(projectRoot) {
607
+ const path = cloudflareTargetPath(projectRoot);
608
+ if (!existsSync(path))
609
+ return null;
610
+ return readCloudflareTargetReceiptFile(path);
611
+ }
612
+ function readCloudflareTargetReceiptFile(path) {
613
+ try {
614
+ return parseCloudflareTargetReceipt(JSON.parse(readFileSync(path, 'utf8')), path);
615
+ }
616
+ catch (cause) {
617
+ if (cause instanceof LoreError)
618
+ throw cause;
619
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', `The cloudflare target receipt at ${path} could not be read.`, {
620
+ remediation: 'Fix or remove the unreadable receipt, then run `lorepack target add cloudflare` again.',
621
+ subject: path,
622
+ cause,
623
+ });
624
+ }
625
+ }
626
+ function parseCloudflareTargetReceipt(raw, path) {
627
+ if (!isCloudflareTargetReceipt(raw)) {
628
+ throw new LoreError('LORE_E_TARGET_NOT_CONFIGURED', `The cloudflare target receipt at ${path} is invalid.`, {
629
+ remediation: 'Remove the invalid receipt and run `lorepack target add cloudflare` again so it is rewritten.',
630
+ subject: path,
631
+ });
632
+ }
633
+ return raw;
634
+ }
635
+ function isCloudflareTargetReceipt(raw) {
636
+ if (typeof raw !== 'object' || raw === null)
637
+ return false;
638
+ const receipt = raw;
639
+ return (receipt.formatVersion === 1 &&
640
+ receipt.target === 'cloudflare' &&
641
+ typeof receipt.project === 'string' &&
642
+ typeof receipt.configuredAt === 'string' &&
643
+ typeof receipt.wranglerVersion === 'string' &&
644
+ typeof receipt.accountId === 'string' &&
645
+ typeof receipt.workerName === 'string' &&
646
+ typeof receipt.catalogDatabaseName === 'string' &&
647
+ typeof receipt.objectsBucketName === 'string' &&
648
+ Array.isArray(receipt.capabilities) &&
649
+ receipt.capabilities.every((value) => typeof value === 'string'));
650
+ }
651
+ class WranglerCatalogDatabase {
652
+ #name;
653
+ #transaction = new WranglerD1TransactionBatch();
654
+ constructor(name) {
655
+ this.#name = name;
656
+ }
657
+ prepare(query) {
658
+ return new WranglerStatement(this.#name, this.#transaction, query);
659
+ }
660
+ }
661
+ class WranglerStatement {
662
+ #databaseName;
663
+ #transaction;
664
+ #query;
665
+ #bindings = [];
666
+ constructor(databaseName, transaction, query) {
667
+ this.#databaseName = databaseName;
668
+ this.#transaction = transaction;
669
+ this.#query = query;
670
+ }
671
+ bind(...values) {
672
+ this.#bindings = values;
673
+ return this;
674
+ }
675
+ async run() {
676
+ const control = wranglerD1TransactionControl(this.#query);
677
+ if (control === 'begin') {
678
+ this.#transaction.begin();
679
+ return {};
680
+ }
681
+ if (control === 'rollback') {
682
+ this.#transaction.rollback();
683
+ return {};
684
+ }
685
+ if (control === 'commit') {
686
+ const flushed = this.#transaction.commit();
687
+ if (flushed === null)
688
+ return {};
689
+ const { stdout } = await execWranglerSqlFile(this.#databaseName, flushed);
690
+ return { results: readD1Results(parseWranglerJson(stdout)) };
691
+ }
692
+ const mode = wranglerD1ExecutionMode(this.#query);
693
+ const rendered = renderSql(this.#query, this.#bindings);
694
+ if (mode === 'file' && this.#transaction.active) {
695
+ this.#transaction.stage(rendered);
696
+ return {};
697
+ }
698
+ if (mode === 'command' && this.#transaction.active) {
699
+ const flushed = this.#transaction.flush();
700
+ if (flushed !== null) {
701
+ await execWranglerSqlFile(this.#databaseName, flushed);
702
+ }
703
+ }
704
+ const { stdout } = mode === 'file'
705
+ ? await execWranglerSqlFile(this.#databaseName, rendered)
706
+ : await execWrangler([
707
+ 'd1',
708
+ 'execute',
709
+ this.#databaseName,
710
+ '--remote',
711
+ '--json',
712
+ '--command',
713
+ rendered,
714
+ ]);
715
+ return { results: readD1Results(parseWranglerJson(stdout)) };
716
+ }
717
+ }
718
+ export function isWranglerD1TransactionControl(query) {
719
+ return wranglerD1TransactionControl(query) !== null;
720
+ }
721
+ export function wranglerD1ExecutionMode(query) {
722
+ if (wranglerD1TransactionControl(query) !== null)
723
+ return 'skip';
724
+ return isWranglerD1ReadQuery(query) ? 'command' : 'file';
725
+ }
726
+ export class WranglerD1TransactionBatch {
727
+ #statements = null;
728
+ get active() {
729
+ return this.#statements !== null;
730
+ }
731
+ begin() {
732
+ this.#statements = [];
733
+ }
734
+ stage(sql) {
735
+ if (this.#statements === null)
736
+ return;
737
+ this.#statements.push(sql);
738
+ }
739
+ flush() {
740
+ const statements = this.#statements;
741
+ if (statements === null || statements.length === 0)
742
+ return null;
743
+ this.#statements = [];
744
+ return renderWranglerSqlBatch(statements);
745
+ }
746
+ commit() {
747
+ const flushed = this.flush();
748
+ this.#statements = null;
749
+ return flushed;
750
+ }
751
+ rollback() {
752
+ this.#statements = null;
753
+ }
754
+ }
755
+ export function renderWranglerSqlBatch(statements) {
756
+ return statements.map(terminateSql).join('\n');
757
+ }
758
+ export function isRetryableWranglerRemoteD1Failure(text) {
759
+ return text.includes('fetch failed') || /\bcode\b[^\d]*7500\b/.test(text);
760
+ }
761
+ function wranglerD1TransactionControl(query) {
762
+ const normalized = query.trim().toLowerCase();
763
+ if (!WRANGLER_D1_TRANSACTION_CONTROL.has(normalized))
764
+ return null;
765
+ if (normalized === 'begin immediate')
766
+ return 'begin';
767
+ if (normalized === 'commit')
768
+ return 'commit';
769
+ return 'rollback';
770
+ }
771
+ function isWranglerD1ReadQuery(query) {
772
+ const normalized = query.trim().toLowerCase();
773
+ return (normalized.startsWith('select') ||
774
+ normalized.startsWith('pragma') ||
775
+ normalized.startsWith('with'));
776
+ }
777
+ function issueRuntimeToken() {
778
+ return `${RUNTIME_TOKEN_PREFIX}${randomBytes(24).toString('hex')}`;
779
+ }
780
+ async function execWrangler(args) {
781
+ const attempts = isWranglerRemoteD1Execute(args) ? WRANGLER_REMOTE_D1_RETRY_LIMIT : 1;
782
+ let lastFailure = null;
783
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
784
+ try {
785
+ const { stdout } = await execFileAsync(process.execPath, [WRANGLER_BIN, ...args], {
786
+ cwd: join(import.meta.dirname, '..', '..', '..', 'deploy-cloudflare'),
787
+ env: { ...process.env, NO_D1_WARNING: 'true' },
788
+ encoding: 'utf8',
789
+ maxBuffer: 10 * 1024 * 1024,
790
+ });
791
+ return { stdout };
792
+ }
793
+ catch (cause) {
794
+ const failure = wranglerExecFailure(cause);
795
+ lastFailure = failure;
796
+ if (attempt >= attempts || !isRetryableWranglerRemoteD1Failure(failure.message)) {
797
+ throw failure;
798
+ }
799
+ await sleep(250 * attempt);
800
+ }
801
+ }
802
+ throw lastFailure ?? new Error('Wrangler execution failed without an error message.');
803
+ }
804
+ async function execWranglerSqlFile(databaseName, sql) {
805
+ const path = tempPath('lore-target-d1-execute-', 'statement.sql');
806
+ try {
807
+ writeFileSync(path, sql, 'utf8');
808
+ return await execWrangler([
809
+ 'd1',
810
+ 'execute',
811
+ databaseName,
812
+ '--remote',
813
+ '--json',
814
+ '--file',
815
+ path,
816
+ ]);
817
+ }
818
+ finally {
819
+ rmSync(path, { force: true });
820
+ }
821
+ }
822
+ async function wranglerD1Exists(name) {
823
+ try {
824
+ await execWrangler(['d1', 'info', name, '--json']);
825
+ return true;
826
+ }
827
+ catch {
828
+ return false;
829
+ }
830
+ }
831
+ async function wranglerBucketExists(name) {
832
+ try {
833
+ await execWrangler(['r2', 'bucket', 'info', name, '--json']);
834
+ return true;
835
+ }
836
+ catch {
837
+ return false;
838
+ }
839
+ }
840
+ async function wranglerWorkerExists(name) {
841
+ try {
842
+ const { stdout } = await execWrangler(['deployments', 'list', '--name', name, '--json']);
843
+ const parsed = JSON.parse(stdout);
844
+ return Array.isArray(parsed) && parsed.length > 0;
845
+ }
846
+ catch {
847
+ return false;
848
+ }
849
+ }
850
+ function readD1Results(raw) {
851
+ if (Array.isArray(raw)) {
852
+ for (const item of raw) {
853
+ const results = readResultsArray(item);
854
+ if (results !== null)
855
+ return results;
856
+ }
857
+ return [];
858
+ }
859
+ return readResultsArray(raw) ?? [];
860
+ }
861
+ function isWranglerRemoteD1Execute(args) {
862
+ return args[0] === 'd1' && args[1] === 'execute' && args.includes('--remote');
863
+ }
864
+ export function parseWranglerJson(stdout) {
865
+ try {
866
+ return JSON.parse(stdout);
867
+ }
868
+ catch (error) {
869
+ const start = findWranglerJsonStart(stdout);
870
+ if (start === null)
871
+ throw error;
872
+ return JSON.parse(stdout.slice(start));
873
+ }
874
+ }
875
+ function readResultsArray(raw) {
876
+ if (typeof raw !== 'object' || raw === null)
877
+ return null;
878
+ const value = raw;
879
+ if (Array.isArray(value.results))
880
+ return value.results;
881
+ if (Array.isArray(value.result))
882
+ return value.result;
883
+ return null;
884
+ }
885
+ function renderSql(query, bindings) {
886
+ let index = 0;
887
+ const rendered = query.replace(/\?/g, () => {
888
+ if (index >= bindings.length) {
889
+ throw new LoreError('LORE_E_INTERNAL', 'A Cloudflare D1 statement was missing a bound value.', {
890
+ remediation: 'Fix the Cloudflare CLI adapter so every placeholder is bound exactly once.',
891
+ });
892
+ }
893
+ return literalFor(bindings[index++]);
894
+ });
895
+ if (index !== bindings.length) {
896
+ throw new LoreError('LORE_E_INTERNAL', 'A Cloudflare D1 statement bound too many values.', {
897
+ remediation: 'Fix the Cloudflare CLI adapter so every placeholder is bound exactly once.',
898
+ });
899
+ }
900
+ return rendered;
901
+ }
902
+ function findWranglerJsonStart(stdout) {
903
+ const objectStart = stdout.indexOf('{');
904
+ const arrayStart = stdout.indexOf('[');
905
+ if (objectStart === -1 && arrayStart === -1)
906
+ return null;
907
+ if (objectStart === -1)
908
+ return arrayStart;
909
+ if (arrayStart === -1)
910
+ return objectStart;
911
+ return Math.min(objectStart, arrayStart);
912
+ }
913
+ function terminateSql(sql) {
914
+ return sql.trimEnd().endsWith(';') ? sql : `${sql};`;
915
+ }
916
+ function tempPath(prefix, filename) {
917
+ const directory = mkdtempSync(join(tmpdir(), prefix));
918
+ return join(directory, filename);
919
+ }
920
+ function literalFor(value) {
921
+ if (value === null)
922
+ return 'NULL';
923
+ if (typeof value === 'string')
924
+ return `'${value.replaceAll("'", "''")}'`;
925
+ if (typeof value === 'number')
926
+ return String(value);
927
+ if (typeof value === 'boolean')
928
+ return value ? '1' : '0';
929
+ throw new LoreError('LORE_E_INTERNAL', `Unsupported Cloudflare D1 binding type ${typeof value}.`, {
930
+ remediation: 'Fix the Cloudflare CLI adapter so it writes only scalar SQLite values.',
931
+ });
932
+ }
933
+ function wranglerExecFailure(cause) {
934
+ const failure = cause;
935
+ const stdout = failure.stdout === undefined ? '' : String(failure.stdout);
936
+ const stderr = failure.stderr === undefined ? '' : String(failure.stderr);
937
+ const message = [failure.message ?? 'Wrangler execution failed.', stderr, stdout]
938
+ .filter((part) => part.trim() !== '')
939
+ .join('\n');
940
+ return new Error(message);
941
+ }
942
+ function sleep(ms) {
943
+ return new Promise((resolve) => setTimeout(resolve, ms));
944
+ }
945
+ //# sourceMappingURL=target.js.map