@package-broker/cli 0.19.7 → 2.0.0-rc.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 (181) hide show
  1. package/LICENSE +661 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/bin.d.ts +3 -0
  4. package/dist/bin.d.ts.map +1 -0
  5. package/dist/bin.js +12 -0
  6. package/dist/bin.js.map +1 -0
  7. package/dist/build-artifacts.d.ts +27 -0
  8. package/dist/build-artifacts.d.ts.map +1 -0
  9. package/dist/build-artifacts.js +169 -0
  10. package/dist/build-artifacts.js.map +1 -0
  11. package/dist/build-assets.d.ts +36 -0
  12. package/dist/build-assets.d.ts.map +1 -0
  13. package/dist/build-assets.js +224 -0
  14. package/dist/build-assets.js.map +1 -0
  15. package/dist/build.d.ts +31 -0
  16. package/dist/build.d.ts.map +1 -0
  17. package/dist/build.js +277 -0
  18. package/dist/build.js.map +1 -0
  19. package/dist/cli.d.ts +2 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +801 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/cloudflare/d1-http-executor.d.ts +30 -0
  24. package/dist/cloudflare/d1-http-executor.d.ts.map +1 -0
  25. package/dist/cloudflare/d1-http-executor.js +230 -0
  26. package/dist/cloudflare/d1-http-executor.js.map +1 -0
  27. package/dist/cloudflare/d1-http-response.d.ts +4 -0
  28. package/dist/cloudflare/d1-http-response.d.ts.map +1 -0
  29. package/dist/cloudflare/d1-http-response.js +113 -0
  30. package/dist/cloudflare/d1-http-response.js.map +1 -0
  31. package/dist/commands/admin.d.ts +47 -0
  32. package/dist/commands/admin.d.ts.map +1 -0
  33. package/dist/commands/admin.js +447 -0
  34. package/dist/commands/admin.js.map +1 -0
  35. package/dist/commands/built-manifest.d.ts +13 -0
  36. package/dist/commands/built-manifest.d.ts.map +1 -0
  37. package/dist/commands/built-manifest.js +197 -0
  38. package/dist/commands/built-manifest.js.map +1 -0
  39. package/dist/commands/cloudflare/api.d.ts +129 -0
  40. package/dist/commands/cloudflare/api.d.ts.map +1 -0
  41. package/dist/commands/cloudflare/api.js +480 -0
  42. package/dist/commands/cloudflare/api.js.map +1 -0
  43. package/dist/commands/cloudflare/deploy.d.ts +81 -0
  44. package/dist/commands/cloudflare/deploy.d.ts.map +1 -0
  45. package/dist/commands/cloudflare/deploy.js +1848 -0
  46. package/dist/commands/cloudflare/deploy.js.map +1 -0
  47. package/dist/commands/cloudflare/inventory.d.ts +243 -0
  48. package/dist/commands/cloudflare/inventory.d.ts.map +1 -0
  49. package/dist/commands/cloudflare/inventory.js +786 -0
  50. package/dist/commands/cloudflare/inventory.js.map +1 -0
  51. package/dist/commands/cloudflare/janitor.d.ts +10 -0
  52. package/dist/commands/cloudflare/janitor.d.ts.map +1 -0
  53. package/dist/commands/cloudflare/janitor.js +84 -0
  54. package/dist/commands/cloudflare/janitor.js.map +1 -0
  55. package/dist/commands/cloudflare/migrations-check.d.ts +5 -0
  56. package/dist/commands/cloudflare/migrations-check.d.ts.map +1 -0
  57. package/dist/commands/cloudflare/migrations-check.js +10 -0
  58. package/dist/commands/cloudflare/migrations-check.js.map +1 -0
  59. package/dist/commands/cloudflare/provision.d.ts +79 -0
  60. package/dist/commands/cloudflare/provision.d.ts.map +1 -0
  61. package/dist/commands/cloudflare/provision.js +961 -0
  62. package/dist/commands/cloudflare/provision.js.map +1 -0
  63. package/dist/commands/cloudflare/queue-consumer.d.ts +4 -0
  64. package/dist/commands/cloudflare/queue-consumer.d.ts.map +1 -0
  65. package/dist/commands/cloudflare/queue-consumer.js +57 -0
  66. package/dist/commands/cloudflare/queue-consumer.js.map +1 -0
  67. package/dist/commands/cloudflare/setup.d.ts +48 -0
  68. package/dist/commands/cloudflare/setup.d.ts.map +1 -0
  69. package/dist/commands/cloudflare/setup.js +836 -0
  70. package/dist/commands/cloudflare/setup.js.map +1 -0
  71. package/dist/commands/cloudflare/smoke.d.ts +29 -0
  72. package/dist/commands/cloudflare/smoke.d.ts.map +1 -0
  73. package/dist/commands/cloudflare/smoke.js +715 -0
  74. package/dist/commands/cloudflare/smoke.js.map +1 -0
  75. package/dist/commands/cloudflare/teardown.d.ts +62 -0
  76. package/dist/commands/cloudflare/teardown.d.ts.map +1 -0
  77. package/dist/commands/cloudflare/teardown.js +906 -0
  78. package/dist/commands/cloudflare/teardown.js.map +1 -0
  79. package/dist/commands/cloudflare/worker-settings.d.ts +4 -0
  80. package/dist/commands/cloudflare/worker-settings.d.ts.map +1 -0
  81. package/dist/commands/cloudflare/worker-settings.js +93 -0
  82. package/dist/commands/cloudflare/worker-settings.js.map +1 -0
  83. package/dist/commands/cloudflare.d.ts +55 -0
  84. package/dist/commands/cloudflare.d.ts.map +1 -0
  85. package/dist/commands/cloudflare.js +393 -0
  86. package/dist/commands/cloudflare.js.map +1 -0
  87. package/dist/commands/import-cutover.d.ts +5 -0
  88. package/dist/commands/import-cutover.d.ts.map +1 -0
  89. package/dist/commands/import-cutover.js +25 -0
  90. package/dist/commands/import-cutover.js.map +1 -0
  91. package/dist/commands/import-private-packagist.d.ts +38 -0
  92. package/dist/commands/import-private-packagist.d.ts.map +1 -0
  93. package/dist/commands/import-private-packagist.js +232 -0
  94. package/dist/commands/import-private-packagist.js.map +1 -0
  95. package/dist/commands/import-satis.d.ts +11 -0
  96. package/dist/commands/import-satis.d.ts.map +1 -0
  97. package/dist/commands/import-satis.js +112 -0
  98. package/dist/commands/import-satis.js.map +1 -0
  99. package/dist/commands/import.d.ts +22 -0
  100. package/dist/commands/import.d.ts.map +1 -0
  101. package/dist/commands/import.js +343 -0
  102. package/dist/commands/import.js.map +1 -0
  103. package/dist/commands/openapi-specs.d.ts +4 -0
  104. package/dist/commands/openapi-specs.d.ts.map +1 -0
  105. package/dist/commands/openapi-specs.js +94 -0
  106. package/dist/commands/openapi-specs.js.map +1 -0
  107. package/dist/commands/openapi.d.ts +12 -0
  108. package/dist/commands/openapi.d.ts.map +1 -0
  109. package/dist/commands/openapi.js +44 -0
  110. package/dist/commands/openapi.js.map +1 -0
  111. package/dist/commands/source.d.ts +8 -0
  112. package/dist/commands/source.d.ts.map +1 -0
  113. package/dist/commands/source.js +61 -0
  114. package/dist/commands/source.js.map +1 -0
  115. package/dist/commands/sqlite.d.ts +11 -0
  116. package/dist/commands/sqlite.d.ts.map +1 -0
  117. package/dist/commands/sqlite.js +76 -0
  118. package/dist/commands/sqlite.js.map +1 -0
  119. package/dist/import/client.d.ts +71 -0
  120. package/dist/import/client.d.ts.map +1 -0
  121. package/dist/import/client.js +348 -0
  122. package/dist/import/client.js.map +1 -0
  123. package/dist/import/credential-refs.d.ts +2 -0
  124. package/dist/import/credential-refs.d.ts.map +1 -0
  125. package/dist/import/credential-refs.js +22 -0
  126. package/dist/import/credential-refs.js.map +1 -0
  127. package/dist/import/cutover.d.ts +64 -0
  128. package/dist/import/cutover.d.ts.map +1 -0
  129. package/dist/import/cutover.js +544 -0
  130. package/dist/import/cutover.js.map +1 -0
  131. package/dist/import/private-packagist/api.d.ts +57 -0
  132. package/dist/import/private-packagist/api.d.ts.map +1 -0
  133. package/dist/import/private-packagist/api.js +246 -0
  134. package/dist/import/private-packagist/api.js.map +1 -0
  135. package/dist/import/private-packagist/dates.d.ts +30 -0
  136. package/dist/import/private-packagist/dates.d.ts.map +1 -0
  137. package/dist/import/private-packagist/dates.js +189 -0
  138. package/dist/import/private-packagist/dates.js.map +1 -0
  139. package/dist/import/private-packagist/map.d.ts +48 -0
  140. package/dist/import/private-packagist/map.d.ts.map +1 -0
  141. package/dist/import/private-packagist/map.js +162 -0
  142. package/dist/import/private-packagist/map.js.map +1 -0
  143. package/dist/import/private-packagist/plan.d.ts +61 -0
  144. package/dist/import/private-packagist/plan.d.ts.map +1 -0
  145. package/dist/import/private-packagist/plan.js +936 -0
  146. package/dist/import/private-packagist/plan.js.map +1 -0
  147. package/dist/import/private-packagist/snapshot.d.ts +31 -0
  148. package/dist/import/private-packagist/snapshot.d.ts.map +1 -0
  149. package/dist/import/private-packagist/snapshot.js +406 -0
  150. package/dist/import/private-packagist/snapshot.js.map +1 -0
  151. package/dist/import/satis.d.ts +93 -0
  152. package/dist/import/satis.d.ts.map +1 -0
  153. package/dist/import/satis.js +873 -0
  154. package/dist/import/satis.js.map +1 -0
  155. package/dist/index.d.ts +3 -2
  156. package/dist/index.d.ts.map +1 -1
  157. package/dist/index.js +3 -96
  158. package/dist/index.js.map +1 -1
  159. package/dist/manifest.d.ts +55 -0
  160. package/dist/manifest.d.ts.map +1 -0
  161. package/dist/manifest.js +125 -0
  162. package/dist/manifest.js.map +1 -0
  163. package/dist/migrations-collect.d.ts +6 -0
  164. package/dist/migrations-collect.d.ts.map +1 -0
  165. package/dist/migrations-collect.js +39 -0
  166. package/dist/migrations-collect.js.map +1 -0
  167. package/dist/prebuilt-expected.json +7 -0
  168. package/dist/prebuilt.d.ts +27 -0
  169. package/dist/prebuilt.d.ts.map +1 -0
  170. package/dist/prebuilt.js +391 -0
  171. package/dist/prebuilt.js.map +1 -0
  172. package/package.json +21 -25
  173. package/README.md +0 -29
  174. package/dist/__tests__/migrate.test.d.ts +0 -2
  175. package/dist/__tests__/migrate.test.d.ts.map +0 -1
  176. package/dist/__tests__/migrate.test.js +0 -116
  177. package/dist/__tests__/migrate.test.js.map +0 -1
  178. package/dist/migrate.d.ts +0 -40
  179. package/dist/migrate.d.ts.map +0 -1
  180. package/dist/migrate.js +0 -335
  181. package/dist/migrate.js.map +0 -1
@@ -0,0 +1,961 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { createInterface } from 'node:readline/promises';
5
+ import { validatePlatformPorts } from '@package-broker/core/platform/port-config';
6
+ import { PLUGIN_BINDABLE_PORTS } from '@package-broker/core/plugin';
7
+ import { assertPrebuiltConfiguration, materializePrebuilt, prebuiltEnvelope, removePrebuilt, } from '../../prebuilt.js';
8
+ import { queueConsumerScript } from './queue-consumer.js';
9
+ import { CREATE_SETTLE_MS, CloudflareApi, CloudflareError, cloudflareCredentials, commandEnvelope, failCommand, object, } from './api.js';
10
+ import { InventoryStore, InventoryTransaction, digest, emptyInventory, installIdentity, abortOnlyRule, lifecycleRule, validResourceId, } from './inventory.js';
11
+ const QUEUE_ACCOUNT_LIMIT = 10_000;
12
+ export const cloudflareProviderContract = {
13
+ async capabilities(api, required, identity) {
14
+ const evidence = {};
15
+ const probe = async (check, read) => {
16
+ try {
17
+ await read();
18
+ return true;
19
+ }
20
+ catch (error) {
21
+ if (check !== 'analytics_engine' &&
22
+ error instanceof CloudflareError &&
23
+ (error.status === 403 || (check === 'queues' && error.providerCode === 10253)))
24
+ return false;
25
+ throw new CloudflareError('capability_unknown', 1, false, 'provision', null, check);
26
+ }
27
+ };
28
+ if (required.queues) {
29
+ let count = 0;
30
+ const supported = await probe('queues', async () => {
31
+ count = (await api.list('/queues')).length;
32
+ });
33
+ // https://developers.cloudflare.com/queues/platform/limits/
34
+ evidence.queues = {
35
+ supported,
36
+ remaining: supported ? Math.max(0, QUEUE_ACCOUNT_LIMIT - count) : 0,
37
+ };
38
+ }
39
+ if (required.r2)
40
+ evidence.r2 = {
41
+ supported: await probe('r2', async () => {
42
+ await api.list('/r2/buckets', 'buckets', {
43
+ 'cf-r2-jurisdiction': identity.jurisdiction,
44
+ });
45
+ }),
46
+ };
47
+ if (required.analytics) {
48
+ let paid = null;
49
+ try {
50
+ const subscriptions = await api.snapshot('/subscriptions');
51
+ // WORKERS_PAID is documented at /tenant/reference/subscriptions/.
52
+ paid = subscriptions.some((subscription) => {
53
+ const plan = object(subscription.rate_plan);
54
+ return (typeof plan.id === 'string' &&
55
+ plan.id.toUpperCase() === 'WORKERS_PAID' &&
56
+ plan.scope === 'account' &&
57
+ (subscription.state === 'Paid' || subscription.state === 'Provisioned'));
58
+ });
59
+ }
60
+ catch {
61
+ // Billing permission is optional; unreadable or incomplete subscriptions are no veto.
62
+ }
63
+ if (paid !== null)
64
+ evidence.analytics = { supported: paid, source: 'subscriptions' };
65
+ else {
66
+ try {
67
+ await api.analyticsEngineTables();
68
+ }
69
+ catch (error) {
70
+ const unknown = new CloudflareError('capability_unknown', 1, false, 'provision', null, 'analytics_engine', null, 'analytics_engine_sql');
71
+ if (error instanceof CloudflareError && (error.status === 401 || error.status === 403))
72
+ unknown.message =
73
+ 'Analytics Engine SQL access was denied. Verify the token has Account Analytics Read permission for the selected account, then rerun. Billing permission is optional.';
74
+ throw unknown;
75
+ }
76
+ evidence.analytics = { supported: true, source: 'analytics_engine_sql' };
77
+ }
78
+ }
79
+ return evidence;
80
+ },
81
+ // No bounded creation-visibility guarantee is documented. Journal timing alone is not ownership.
82
+ pendingCreation: () => false,
83
+ exactNameUnique: (kind) => ['d1', 'r2', 'queue', 'worker'].includes(kind),
84
+ };
85
+ const kindPath = (kind) => kind === 'd1' ? '/d1/database' : kind === 'r2' ? '/r2/buckets' : '/queues';
86
+ const jurisdictionHeaders = (spec) => spec.kind === 'r2' && spec.jurisdiction !== null
87
+ ? { 'cf-r2-jurisdiction': spec.jurisdiction }
88
+ : undefined;
89
+ function specsFor(identity, required) {
90
+ const base = identity.script_name;
91
+ return [
92
+ { key: 'd1', kind: 'd1', name: `${base}-db`, jurisdiction: identity.jurisdiction },
93
+ ...(required.r2
94
+ ? [
95
+ {
96
+ key: 'r2',
97
+ kind: 'r2',
98
+ name: `${base}-blobs`,
99
+ jurisdiction: identity.jurisdiction,
100
+ },
101
+ ]
102
+ : []),
103
+ ...(required.queues
104
+ ? [
105
+ {
106
+ key: 'dlq',
107
+ kind: 'queue',
108
+ name: `${base}-jobs-dlq`,
109
+ jurisdiction: null,
110
+ },
111
+ {
112
+ key: 'jobs',
113
+ kind: 'queue',
114
+ name: `${base}-jobs`,
115
+ jurisdiction: null,
116
+ },
117
+ ]
118
+ : []),
119
+ ];
120
+ }
121
+ class UnsupportedRequirementsError extends CloudflareError {
122
+ requirements;
123
+ constructor(requirements) {
124
+ super('invalid_input', 2);
125
+ this.requirements = requirements;
126
+ }
127
+ }
128
+ async function selectedBuild(outDir) {
129
+ try {
130
+ const manifest = object(JSON.parse(await readFile(resolve(outDir, 'generated/manifest.json'), 'utf8')));
131
+ const info = object(JSON.parse(await readFile(resolve(outDir, 'build-info.json'), 'utf8')));
132
+ if (manifest.schemaVersion !== 1 ||
133
+ manifest.platform !== 'cloudflare' ||
134
+ info.schemaVersion !== 1 ||
135
+ info.target !== 'cloudflare' ||
136
+ typeof info.core !== 'string' ||
137
+ !Array.isArray(manifest.bindings) ||
138
+ !Array.isArray(manifest.plugins))
139
+ throw new Error();
140
+ const unsupported = [];
141
+ for (const binding of manifest.bindings) {
142
+ const port = object(binding).port;
143
+ if (typeof port !== 'string' ||
144
+ !PLUGIN_BINDABLE_PORTS.some((supported) => supported === port))
145
+ unsupported.push(typeof port === 'string' && /^[a-z][a-z0-9_-]{0,63}$/.test(port)
146
+ ? port
147
+ : 'unrecognized_requirement');
148
+ }
149
+ const ports = manifest.platformPorts;
150
+ if (ports && typeof ports === 'object') {
151
+ for (const [key, adapter] of Object.entries({
152
+ blob: 'r2',
153
+ cache: 'cache-api',
154
+ jobs: 'queues',
155
+ observability: 'workers',
156
+ })) {
157
+ const section = object(ports)[key];
158
+ if (section && object(section).adapter !== adapter)
159
+ unsupported.push(key);
160
+ }
161
+ }
162
+ if (unsupported.length)
163
+ throw new UnsupportedRequirementsError(unsupported);
164
+ if (ports !== null &&
165
+ (ports === undefined || validatePlatformPorts({ cloudflare: ports }, 'cloudflare').length > 0))
166
+ throw new Error();
167
+ const selected = ports;
168
+ // Only allow build-info fields with their declared types; never echo arbitrary manifest data.
169
+ if (typeof info.core !== 'string' || !/^[a-zA-Z0-9.+-]{1,64}$/.test(info.core))
170
+ throw new Error();
171
+ const plugins = object(info.plugins);
172
+ for (const [key, value] of Object.entries(plugins))
173
+ if (!/^[@a-zA-Z0-9/_.-]{1,128}$/.test(key) ||
174
+ typeof value !== 'string' ||
175
+ !/^[a-zA-Z0-9.+-]{1,64}$/.test(value))
176
+ throw new Error();
177
+ if (info.lockfileSha256 !== null &&
178
+ (typeof info.lockfileSha256 !== 'string' || !/^[a-f0-9]{64}$/.test(info.lockfileSha256)))
179
+ throw new Error();
180
+ if (info.gitSha !== null &&
181
+ (typeof info.gitSha !== 'string' || !/^[a-f0-9]{40,64}$/.test(info.gitSha)))
182
+ throw new Error();
183
+ if (info.dirty !== undefined && info.dirty !== null && typeof info.dirty !== 'boolean')
184
+ throw new Error();
185
+ return {
186
+ required: {
187
+ r2: selected?.blob.adapter === 'r2',
188
+ queues: selected?.jobs.adapter === 'queues',
189
+ analytics: selected?.observability.analyticsBinding !== undefined,
190
+ },
191
+ ports: selected,
192
+ info: {
193
+ schemaVersion: 1,
194
+ target: 'cloudflare',
195
+ core: info.core,
196
+ plugins: plugins,
197
+ lockfileSha256: info.lockfileSha256,
198
+ gitSha: info.gitSha,
199
+ ...(info.dirty === undefined ? {} : { dirty: info.dirty }),
200
+ },
201
+ };
202
+ }
203
+ catch (error) {
204
+ if (error instanceof UnsupportedRequirementsError)
205
+ throw error;
206
+ throw new CloudflareError('invalid_input', 2);
207
+ }
208
+ }
209
+ function parseRemote(kind, raw) {
210
+ const record = object(raw);
211
+ const id = kind === 'd1' ? record.uuid : kind === 'r2' ? record.name : record.queue_id;
212
+ const name = kind === 'queue' ? record.queue_name : record.name;
213
+ if (!validResourceId(kind, id) ||
214
+ typeof name !== 'string' ||
215
+ !/^[a-z0-9][a-z0-9-]{0,62}$/.test(name))
216
+ throw new CloudflareError('provider_failure');
217
+ const jurisdiction = record.jurisdiction === 'eu' || record.jurisdiction === 'default' ? record.jurisdiction : null;
218
+ const timestamp = record.created_at ?? record.created_on;
219
+ return {
220
+ kind,
221
+ id,
222
+ name,
223
+ jurisdiction,
224
+ created_at: typeof timestamp === 'string' && Number.isFinite(Date.parse(timestamp)) ? timestamp : null,
225
+ };
226
+ }
227
+ async function readWorker(api, identity) {
228
+ try {
229
+ const raw = object((await api.request(`/workers/scripts/${identity.script_name}/settings`)).result);
230
+ if (!Array.isArray(raw.tags) ||
231
+ raw.tags.some((tag) => typeof tag !== 'string') ||
232
+ !Array.isArray(raw.bindings))
233
+ throw new CloudflareError('capability_unknown');
234
+ const names = raw.bindings.map((binding) => object(binding).name);
235
+ if (new Set(names).size !== names.length)
236
+ throw new CloudflareError('configuration_conflict', 8);
237
+ return {
238
+ tags: raw.tags,
239
+ bindings: raw.bindings.map((b) => {
240
+ const binding = object(b);
241
+ if (typeof binding.type !== 'string' || typeof binding.name !== 'string')
242
+ throw new CloudflareError('capability_unknown');
243
+ return {
244
+ type: binding.type,
245
+ name: binding.name,
246
+ id: typeof binding.id === 'string' ? binding.id : null,
247
+ text: typeof binding.text === 'string' ? binding.text : null,
248
+ resource_name: typeof (binding.bucket_name ?? binding.queue_name ?? binding.dataset) === 'string'
249
+ ? String(binding.bucket_name ?? binding.queue_name ?? binding.dataset)
250
+ : null,
251
+ };
252
+ }),
253
+ };
254
+ }
255
+ catch (error) {
256
+ if (error instanceof CloudflareError && error.status === 404)
257
+ return null;
258
+ throw error;
259
+ }
260
+ }
261
+ function ownedWorker(worker, identity) {
262
+ return (worker !== null &&
263
+ worker.tags.includes('pb-install-v1') &&
264
+ worker.tags.includes(`pb-owner-${identity.owner_digest}`));
265
+ }
266
+ function bindingOwns(worker, identity, spec, remote) {
267
+ if (!ownedWorker(worker, identity))
268
+ return false;
269
+ return worker.bindings.some((b) => spec.kind === 'd1'
270
+ ? b.type === 'd1' && b.id === remote.id
271
+ : spec.kind === 'r2'
272
+ ? b.type === 'r2_bucket' && b.resource_name === remote.name
273
+ : b.type === 'queue' && (b.id === remote.id || b.resource_name === remote.name));
274
+ }
275
+ function verify(spec, remote) {
276
+ if (spec.name !== remote.name || spec.kind !== remote.kind)
277
+ throw new CloudflareError('configuration_conflict', 8);
278
+ if (spec.jurisdiction !== null && spec.jurisdiction !== remote.jurisdiction)
279
+ throw new CloudflareError('jurisdiction_conflict', 8);
280
+ }
281
+ function pendingEvidence(contract, remote, op, now) {
282
+ return (remote.created_at !== null &&
283
+ Date.parse(remote.created_at) >= Date.parse(op.absence_observed_at) &&
284
+ Date.parse(remote.created_at) >= Date.parse(op.started_at) &&
285
+ Date.parse(remote.created_at) <= now &&
286
+ contract.pendingCreation(remote, op, now));
287
+ }
288
+ function specHash(spec) {
289
+ return digest(JSON.stringify({ kind: spec.kind, name: spec.name, jurisdiction: spec.jurisdiction }));
290
+ }
291
+ function receipt(remote, source, now, operationId) {
292
+ return {
293
+ name: remote.name,
294
+ id: remote.id,
295
+ ...(remote.kind === 'd1' || remote.kind === 'r2' ? { jurisdiction: remote.jurisdiction } : {}),
296
+ state: 'complete',
297
+ evidence: { source, operation_id: operationId, verified_at: new Date(now).toISOString() },
298
+ };
299
+ }
300
+ async function allResources(api, specs, queueConsumers = new Map()) {
301
+ const result = new Map();
302
+ const lists = new Map();
303
+ for (const spec of specs) {
304
+ if (!lists.has(spec.kind))
305
+ lists.set(spec.kind, await api.list(kindPath(spec.kind), spec.kind === 'r2' ? 'buckets' : undefined, jurisdictionHeaders(spec)));
306
+ const matches = lists
307
+ .get(spec.kind)
308
+ .filter((raw) => (spec.kind === 'queue' ? raw.queue_name : raw.name) === spec.name);
309
+ if (matches.length > 1)
310
+ throw new CloudflareError('ownership_conflict', 8);
311
+ const resources = [];
312
+ for (const raw of matches) {
313
+ const listed = parseRemote(spec.kind, raw);
314
+ const detail = parseRemote(spec.kind, (await api.request(`${kindPath(spec.kind)}/${encodeURIComponent(listed.id)}`, 'GET', undefined, jurisdictionHeaders(spec))).result);
315
+ if (detail.id !== listed.id)
316
+ throw new CloudflareError('ownership_conflict', 8);
317
+ verify(spec, detail);
318
+ if (spec.kind === 'queue') {
319
+ const consumers = await api.snapshot(`/queues/${detail.id}/consumers`);
320
+ queueConsumers.set(detail.id, consumers);
321
+ const script = spec.name.slice(0, spec.key === 'dlq' ? -9 : -5);
322
+ if ((spec.key === 'dlq' && consumers.length > 0) ||
323
+ consumers.length > 1 ||
324
+ consumers.some((consumer) => consumer.type !== 'worker' ||
325
+ queueConsumerScript(consumer) !== script ||
326
+ (spec.key === 'jobs' &&
327
+ consumer.dead_letter_queue !== undefined &&
328
+ consumer.dead_letter_queue !== null &&
329
+ consumer.dead_letter_queue !== `${script}-jobs-dlq`)))
330
+ throw new CloudflareError('configuration_conflict', 8);
331
+ }
332
+ resources.push(detail);
333
+ }
334
+ result.set(spec.name, resources);
335
+ }
336
+ return result;
337
+ }
338
+ async function preflight(api, o, required, missingQueues, contract, identity, checks) {
339
+ // Optional identification has no bearing on authoritative service checks.
340
+ let planStatus = 'readable_unverified';
341
+ try {
342
+ await api.request('/subscriptions');
343
+ }
344
+ catch {
345
+ planStatus = 'unavailable_optional';
346
+ }
347
+ const planCheck = checks.find((check) => check.name === 'plan_identification');
348
+ if (planCheck)
349
+ planCheck.status = planStatus;
350
+ else
351
+ checks.push({ name: 'plan_identification', status: planStatus });
352
+ if (o.domain === undefined || o.gradualProbe === true) {
353
+ let subdomain;
354
+ try {
355
+ subdomain = object((await api.request('/workers/subdomain')).result).subdomain;
356
+ }
357
+ catch (error) {
358
+ if (!(error instanceof CloudflareError && error.status === 404))
359
+ throw error;
360
+ }
361
+ if (typeof subdomain !== 'string' || !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(subdomain))
362
+ throw new CloudflareError('workers_dev_subdomain_missing');
363
+ }
364
+ const evidence = await contract.capabilities(api, required, identity);
365
+ if (required.queues) {
366
+ if (!evidence.queues || typeof evidence.queues.supported !== 'boolean')
367
+ throw new CloudflareError('capability_unknown', 1, false, 'provision', null, 'queues');
368
+ if (!evidence.queues.supported)
369
+ throw new CloudflareError('queues_unavailable', 1, false, 'provision', null, 'queues');
370
+ if (!Number.isSafeInteger(evidence.queues.remaining) || evidence.queues.remaining < 0)
371
+ throw new CloudflareError('capability_unknown', 1, false, 'provision', null, 'queues');
372
+ if (missingQueues > evidence.queues.remaining)
373
+ throw new CloudflareError('capacity_exceeded', 1, false, 'provision', null, 'queues');
374
+ }
375
+ if (required.r2) {
376
+ if (typeof evidence.r2?.supported !== 'boolean')
377
+ throw new CloudflareError('capability_unknown', 1, false, 'provision', null, 'r2');
378
+ if (!evidence.r2.supported)
379
+ throw new CloudflareError('r2_unavailable', 1, false, 'provision', null, 'r2');
380
+ }
381
+ if (required.analytics) {
382
+ if (typeof evidence.analytics?.supported !== 'boolean')
383
+ throw new CloudflareError('capability_unknown', 1, false, 'provision', null, 'analytics_engine');
384
+ if (evidence.analytics.source)
385
+ checks.push({
386
+ name: 'analytics_engine',
387
+ status: evidence.analytics.supported ? 'supported' : 'unsupported',
388
+ source: evidence.analytics.source,
389
+ });
390
+ if (!evidence.analytics.supported)
391
+ throw new CloudflareError('analytics_engine_unavailable', 1, false, 'provision', null, 'analytics_engine');
392
+ }
393
+ }
394
+ function checkAdoptions(values) {
395
+ const set = new Set();
396
+ for (const value of values) {
397
+ const [kind, id, extra] = value.split(':');
398
+ if (extra !== undefined ||
399
+ !kind ||
400
+ !id ||
401
+ (kind === 'worker'
402
+ ? !/^[a-z0-9-]{1,37}$/.test(id)
403
+ : !['d1', 'r2', 'queue'].includes(kind) || !validResourceId(kind, id)) ||
404
+ set.has(value))
405
+ throw new CloudflareError('invalid_input', 2);
406
+ set.add(value);
407
+ }
408
+ return set;
409
+ }
410
+ async function confirmedGone(api, spec, id) {
411
+ const paths = spec.kind === 'r2' ? ['eu', 'default'] : [null];
412
+ for (const jurisdiction of paths) {
413
+ try {
414
+ await api.request(`${kindPath(spec.kind)}/${encodeURIComponent(id)}`, 'GET', undefined, jurisdiction === null ? undefined : { 'cf-r2-jurisdiction': jurisdiction });
415
+ return false;
416
+ }
417
+ catch (error) {
418
+ if (!(error instanceof CloudflareError && error.status === 404))
419
+ return false;
420
+ }
421
+ }
422
+ return true;
423
+ }
424
+ function localFailure(error) {
425
+ const code = error?.code;
426
+ return !(error instanceof CloudflareError) &&
427
+ typeof code === 'string' &&
428
+ /^E[A-Z0-9]+$/.test(code)
429
+ ? new CloudflareError('state_io_failure')
430
+ : error;
431
+ }
432
+ async function defaultPrompt(message) {
433
+ const prompt = createInterface({ input: process.stdin, output: process.stderr });
434
+ try {
435
+ return await prompt.question(message);
436
+ }
437
+ finally {
438
+ prompt.close();
439
+ }
440
+ }
441
+ export async function provision(o, operations = {}) {
442
+ let prebuiltOut;
443
+ const output = commandEnvelope('provision', o.dryRun ?? false);
444
+ let release;
445
+ let inventory;
446
+ const setOutputs = () => {
447
+ output.database_id = inventory?.resources.d1?.id ?? null;
448
+ output.r2_bucket = inventory?.resources.r2?.id ?? null;
449
+ output.queue_names = [inventory?.resources.jobs, inventory?.resources.dlq]
450
+ .filter((r) => r?.id !== null && r !== undefined)
451
+ .map((r) => r.name);
452
+ };
453
+ try {
454
+ if (o.prebuilt)
455
+ await assertPrebuiltConfiguration(resolve(o.project ?? dirname(resolve(o.configPath ?? 'broker.config.ts'))), o.configPath);
456
+ const auth = cloudflareCredentials(o.env);
457
+ const identity = installIdentity({
458
+ ...auth,
459
+ workerName: o.workerName,
460
+ ...(o.environment === undefined ? {} : { environment: o.environment }),
461
+ ...(o.deploymentId === undefined ? {} : { deploymentId: o.deploymentId }),
462
+ ...(o.runIdentity === undefined ? {} : { runIdentity: o.runIdentity }),
463
+ ...(o.jurisdiction === undefined ? {} : { jurisdiction: o.jurisdiction }),
464
+ ...(o.locationHint === undefined ? {} : { locationHint: o.locationHint }),
465
+ });
466
+ if (o.domain !== undefined &&
467
+ !/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,63}$/.test(o.domain))
468
+ throw new CloudflareError('invalid_input', 2);
469
+ const adoptions = checkAdoptions(o.adoptResource ?? []);
470
+ const project = resolve(o.project ?? dirname(resolve(o.configPath ?? 'broker.config.ts')));
471
+ let outDir = o.prebuilt
472
+ ? resolve(project, o.outDir ?? '.pb/prebuilt/cloudflare')
473
+ : resolve(o.outDir ?? '.pb/cloudflare');
474
+ if (o.prebuilt) {
475
+ const materialized = await (operations.materializePrebuilt ?? materializePrebuilt)({
476
+ project,
477
+ outDir,
478
+ });
479
+ outDir = prebuiltOut = materialized.outDir;
480
+ output.prebuilt = prebuiltEnvelope(materialized);
481
+ output.artifact_sha256 = output.prebuilt.artifact_sha256;
482
+ }
483
+ const store = await InventoryStore.create({
484
+ project,
485
+ outDir,
486
+ identity,
487
+ ...(o.inventory === undefined ? {} : { inventory: o.inventory }),
488
+ });
489
+ const build = await selectedBuild(outDir);
490
+ output.build_info = build.info;
491
+ const now = o.now ?? Date.now;
492
+ // After confirmation: inventory + capacity, then absence + read-back for each of two queues.
493
+ // Each scan can take 100 pages at the client's one-request-per-second rate.
494
+ const provisionBudgetMs = 600_000 + (build.required.queues ? 6 * Math.ceil(QUEUE_ACCOUNT_LIMIT / 100) * 1000 : 0);
495
+ const api = new CloudflareApi({
496
+ ...auth,
497
+ ...(o.fetch === undefined ? {} : { fetch: o.fetch }),
498
+ now,
499
+ ...(o.sleep === undefined ? {} : { sleep: o.sleep }),
500
+ });
501
+ api.restartDeadline(provisionBudgetMs);
502
+ const contract = o.contract ?? cloudflareProviderContract;
503
+ if (o.transaction)
504
+ o.transaction.assertStore(store);
505
+ else if (!o.dryRun)
506
+ release = await store.lock(now());
507
+ inventory = (await store.read()) ?? emptyInventory(identity, now());
508
+ const journal = inventory;
509
+ if (journal.identity.location_hint !== identity.location_hint &&
510
+ Object.keys(journal.resources).length)
511
+ output.checks.push({ name: 'location_hint', status: 'ignored_for_existing_resources' });
512
+ const save = async () => {
513
+ journal.revision++;
514
+ journal.updated_at = new Date(now()).toISOString();
515
+ await store.write(journal);
516
+ };
517
+ const specs = specsFor(identity, build.required);
518
+ for (const key of Object.keys(journal.resources)) {
519
+ if (key !== 'analytics' && key !== 'worker' && !specs.some((spec) => spec.key === key))
520
+ throw new CloudflareError('configuration_conflict', 8);
521
+ if (key === 'analytics' && !build.required.analytics)
522
+ throw new CloudflareError('configuration_conflict', 8);
523
+ }
524
+ const inspect = async () => {
525
+ const queueConsumers = new Map();
526
+ const resources = await allResources(api, specs, queueConsumers);
527
+ const worker = await readWorker(api, identity);
528
+ if (worker &&
529
+ !ownedWorker(worker, identity) &&
530
+ !(journal.resources.worker?.id === identity.script_name &&
531
+ journal.resources.worker.evidence?.source === 'adopted') &&
532
+ !adoptions.has(`worker:${identity.script_name}`))
533
+ throw new CloudflareError('ownership_conflict', 8);
534
+ if (worker &&
535
+ worker.tags.some((tag) => tag.startsWith('pb-owner-') && tag !== `pb-owner-${identity.owner_digest}`))
536
+ throw new CloudflareError('ownership_conflict', 8);
537
+ // A foreign binding is not overridden by adoption or a local receipt.
538
+ if (worker) {
539
+ const expectedBindings = [
540
+ { name: 'DB', type: 'd1' },
541
+ ...(build.ports
542
+ ? [
543
+ { name: build.ports.blob.binding, type: 'r2_bucket' },
544
+ { name: build.ports.jobs.binding, type: 'queue' },
545
+ ]
546
+ : []),
547
+ ];
548
+ for (const binding of expectedBindings) {
549
+ const b = worker.bindings.find((b) => b.name === binding.name);
550
+ if (b && b.type !== binding.type)
551
+ throw new CloudflareError('configuration_conflict', 8);
552
+ const spec = specs.find((s) => binding.type === 'd1'
553
+ ? s.key === 'd1'
554
+ : binding.type === 'r2_bucket'
555
+ ? s.key === 'r2'
556
+ : s.key === 'jobs');
557
+ const found = spec ? resources.get(spec.name)?.[0] : undefined;
558
+ if (b && !found)
559
+ throw new CloudflareError('configuration_conflict', 8);
560
+ if (b &&
561
+ found &&
562
+ (binding.type === 'd1' ? b.id !== found.id : b.resource_name !== found.name))
563
+ throw new CloudflareError('ownership_conflict', 8);
564
+ }
565
+ }
566
+ if (worker) {
567
+ const deployment = worker.bindings.find((binding) => binding.name === 'DEPLOYMENT_ID');
568
+ if (deployment &&
569
+ (deployment.type !== 'plain_text' || deployment.text !== identity.deployment_id))
570
+ throw new CloudflareError('configuration_conflict', 8);
571
+ if (build.required.analytics) {
572
+ const analytics = worker.bindings.find((binding) => binding.name === build.ports?.observability.analyticsBinding);
573
+ if (analytics &&
574
+ (analytics.type !== 'analytics_engine' ||
575
+ analytics.resource_name !== `${identity.script_name.replaceAll('-', '_')}_metrics`))
576
+ throw new CloudflareError('configuration_conflict', 8);
577
+ }
578
+ }
579
+ const resourceOwned = (spec, remote) => {
580
+ if (bindingOwns(worker, identity, spec, remote))
581
+ return true;
582
+ if (spec.key !== 'dlq' || !ownedWorker(worker, identity))
583
+ return false;
584
+ const jobs = resources.get(`${identity.script_name}-jobs`)?.[0];
585
+ if (!jobs)
586
+ return false;
587
+ const consumers = queueConsumers.get(jobs.id) ?? [];
588
+ return (worker.bindings.some((binding) => binding.name === build.ports?.jobs.binding &&
589
+ binding.type === 'queue' &&
590
+ (binding.id === jobs.id || binding.resource_name === jobs.name)) &&
591
+ consumers.length === 1 &&
592
+ consumers[0].type === 'worker' &&
593
+ queueConsumerScript(consumers[0]) === identity.script_name &&
594
+ consumers[0].dead_letter_queue === remote.name);
595
+ };
596
+ for (const spec of specs) {
597
+ const remote = resources.get(spec.name)?.[0];
598
+ const recorded = journal.resources[spec.key];
599
+ const pending = journal.operations.find((op) => op.name === spec.name && (op.state === 'pending' || op.state === 'unknown'));
600
+ if (pending && pending.spec_sha256 !== specHash(spec))
601
+ throw new CloudflareError('configuration_conflict', 8);
602
+ if (recorded?.state === 'complete' && (!remote || remote.id !== recorded.id))
603
+ throw new CloudflareError('ownership_conflict', 8);
604
+ if (!remote)
605
+ continue;
606
+ const adopted = adoptions.has(`${spec.kind}:${remote.id}`);
607
+ if (pending &&
608
+ !adopted &&
609
+ !(recorded?.id === remote.id &&
610
+ recorded.evidence?.source === 'created' &&
611
+ recorded.evidence.operation_id === pending.id) &&
612
+ !pendingEvidence(contract, remote, pending, now()) &&
613
+ !resourceOwned(spec, remote))
614
+ throw new CloudflareError('ownership_conflict', 8);
615
+ if (!pending && recorded?.state !== 'complete' && !adopted && !resourceOwned(spec, remote))
616
+ throw new CloudflareError('ownership_conflict', 8);
617
+ }
618
+ const missing = specs.filter((spec) => !resources.get(spec.name)?.length);
619
+ await preflight(api, o, build.required, missing.filter((spec) => spec.kind === 'queue').length, contract, identity, output.checks);
620
+ return { resources, worker, missing, queueConsumers };
621
+ };
622
+ let state = await inspect();
623
+ if (o.abandonIntent !== undefined) {
624
+ const target = journal.operations.find((op) => op.id === o.abandonIntent &&
625
+ (op.state === 'pending' || op.state === 'unknown') &&
626
+ specs.some((spec) => spec.name === op.name));
627
+ if (!target)
628
+ throw new CloudflareError('invalid_input', 2);
629
+ output.checks.push({ name: 'abandon_intent', status: target.id });
630
+ }
631
+ for (const flag of adoptions) {
632
+ const [kind, id] = flag.split(':');
633
+ if (kind === 'worker'
634
+ ? state.worker === null || id !== identity.script_name
635
+ : !specs.some((spec) => spec.kind === kind))
636
+ throw new CloudflareError('invalid_input', 2);
637
+ }
638
+ const abandoned = [];
639
+ // Resolve every old intent before planning additional creates. Read-only runs never repair the journal.
640
+ for (const spec of specs) {
641
+ const pending = journal.operations.find((op) => op.name === spec.name && ['pending', 'unknown'].includes(op.state));
642
+ if (!pending)
643
+ continue;
644
+ const remote = state.resources.get(spec.name)?.[0];
645
+ const abandoning = pending.id === o.abandonIntent;
646
+ if (abandoning && remote)
647
+ throw new CloudflareError('ownership_conflict', 8);
648
+ if (!remote) {
649
+ if (abandoning) {
650
+ if (now() - Date.parse(pending.started_at) <= CREATE_SETTLE_MS)
651
+ throw new CloudflareError('outcome_unknown', 6);
652
+ if (pending.resolved_id !== null &&
653
+ !(await confirmedGone(api, spec, pending.resolved_id)))
654
+ throw new CloudflareError('ownership_conflict', 8);
655
+ abandoned.push({ operation: pending, key: spec.key });
656
+ continue;
657
+ }
658
+ output.checks.push({ name: 'pending_intent', status: pending.id });
659
+ if (!o.dryRun) {
660
+ pending.state = 'unknown';
661
+ await save();
662
+ }
663
+ throw new CloudflareError('outcome_unknown', 6);
664
+ }
665
+ if (!o.dryRun) {
666
+ const recorded = journal.resources[spec.key];
667
+ pending.state = 'confirmed';
668
+ pending.resolved_id = remote.id;
669
+ journal.resources[spec.key] = receipt(remote, recorded?.state === 'complete' && recorded.id === remote.id && recorded.evidence
670
+ ? recorded.evidence.source
671
+ : adoptions.has(`${spec.kind}:${remote.id}`)
672
+ ? 'adopted'
673
+ : 'pending_create', now(), pending.id);
674
+ await save();
675
+ }
676
+ }
677
+ const plan = () => specs.map((spec) => ({
678
+ stage: 'provision',
679
+ action: state.missing.some((s) => s.key === spec.key) ? 'create' : 'reuse',
680
+ kind: spec.kind,
681
+ name: spec.name,
682
+ ...(spec.jurisdiction === null ? {} : { jurisdiction: spec.jurisdiction }),
683
+ }));
684
+ output.plan = plan();
685
+ if (build.required.analytics)
686
+ output.plan.push({
687
+ stage: 'provision',
688
+ action: 'binding_pending',
689
+ kind: 'analytics',
690
+ name: `${identity.script_name.replaceAll('-', '_')}_metrics`,
691
+ });
692
+ o.progress?.(`${JSON.stringify(output.plan)}\nAccount: [redacted]. Resources are retained; no deletes. Lost responses require durable private inventory or exact-ID adoption.`);
693
+ if (state.missing.length && !o.dryRun) {
694
+ if (!o.yes) {
695
+ if (o.json || !(o.interactive ?? (process.stdin.isTTY && process.stderr.isTTY)))
696
+ throw new CloudflareError('confirmation_required', 2);
697
+ if ((await (o.prompt ?? defaultPrompt)('Type yes to create these resources: ')) !== 'yes')
698
+ throw new CloudflareError('confirmation_declined', 2);
699
+ }
700
+ api.restartDeadline(provisionBudgetMs);
701
+ const signature = JSON.stringify({
702
+ resources: [...state.resources.entries()],
703
+ worker: state.worker,
704
+ queueConsumers: [...state.queueConsumers.entries()],
705
+ });
706
+ const confirmed = await inspect();
707
+ let confirmedBuild;
708
+ try {
709
+ confirmedBuild = await selectedBuild(outDir);
710
+ }
711
+ catch {
712
+ throw new CloudflareError('plan_changed', 8);
713
+ }
714
+ if (JSON.stringify(confirmedBuild) !== JSON.stringify(build) ||
715
+ JSON.stringify({
716
+ resources: [...confirmed.resources.entries()],
717
+ worker: confirmed.worker,
718
+ queueConsumers: [...confirmed.queueConsumers.entries()],
719
+ }) !== signature)
720
+ throw new CloudflareError('plan_changed', 8);
721
+ state = confirmed;
722
+ }
723
+ const resolvedIds = [...state.resources.values()].flat();
724
+ for (const flag of adoptions) {
725
+ const [kind, id] = flag.split(':');
726
+ const matched = kind === 'worker'
727
+ ? state.worker !== null && id === identity.script_name
728
+ : resolvedIds.some((remote) => remote.kind === kind && remote.id === id);
729
+ if (!matched)
730
+ throw new CloudflareError('invalid_input', 2);
731
+ }
732
+ if (!o.dryRun && abandoned.length) {
733
+ for (const { operation, key } of abandoned) {
734
+ operation.state = 'failed';
735
+ const intended = journal.resources[key];
736
+ if (intended && intended.state !== 'complete')
737
+ intended.state = 'planned';
738
+ }
739
+ await save();
740
+ }
741
+ if (!o.dryRun && state.worker && adoptions.has(`worker:${identity.script_name}`)) {
742
+ journal.resources.worker = {
743
+ name: identity.script_name,
744
+ id: identity.script_name,
745
+ state: 'complete',
746
+ evidence: {
747
+ source: 'adopted',
748
+ operation_id: null,
749
+ verified_at: new Date(now()).toISOString(),
750
+ },
751
+ };
752
+ await save();
753
+ }
754
+ for (const spec of specs) {
755
+ let remote = state.resources.get(spec.name)?.[0];
756
+ if (remote) {
757
+ if (!o.dryRun && journal.resources[spec.key]?.state !== 'complete') {
758
+ journal.resources[spec.key] = receipt(remote, adoptions.has(`${spec.kind}:${remote.id}`) ? 'adopted' : 'worker_binding', now(), null);
759
+ await save();
760
+ }
761
+ continue;
762
+ }
763
+ if (o.dryRun)
764
+ continue;
765
+ // Observe exact absence at this boundary, after the acknowledged plan was checked.
766
+ if ((await allResources(api, [spec])).get(spec.name)?.length)
767
+ throw new CloudflareError('plan_changed', 8);
768
+ api.assertCreateBudget();
769
+ const started = new Date(now()).toISOString();
770
+ const operation = {
771
+ id: randomUUID(),
772
+ kind: spec.kind,
773
+ name: spec.name,
774
+ spec_sha256: specHash(spec),
775
+ absence_observed_at: started,
776
+ started_at: started,
777
+ state: 'pending',
778
+ resolved_id: null,
779
+ };
780
+ journal.operations.push(operation);
781
+ journal.resources[spec.key] = {
782
+ name: spec.name,
783
+ id: null,
784
+ ...(spec.jurisdiction === null ? {} : { jurisdiction: spec.jurisdiction }),
785
+ state: 'creating',
786
+ evidence: null,
787
+ };
788
+ await save();
789
+ let returnedId;
790
+ let posted = false;
791
+ try {
792
+ const body = spec.kind === 'queue'
793
+ ? {
794
+ queue_name: spec.name,
795
+ ...(identity.jurisdiction === 'default'
796
+ ? {}
797
+ : { jurisdiction: identity.jurisdiction }),
798
+ }
799
+ : {
800
+ name: spec.name,
801
+ ...(spec.kind === 'd1' && spec.jurisdiction !== 'default'
802
+ ? { jurisdiction: spec.jurisdiction }
803
+ : {}),
804
+ ...(identity.location_hint === null
805
+ ? {}
806
+ : spec.kind === 'd1'
807
+ ? { primary_location_hint: identity.location_hint }
808
+ : { locationHint: identity.location_hint }),
809
+ };
810
+ const response = await api.request(kindPath(spec.kind), 'POST', body, jurisdictionHeaders(spec));
811
+ posted = true;
812
+ const created = parseRemote(spec.kind, response.result);
813
+ returnedId = created.id;
814
+ operation.resolved_id = created.id;
815
+ await save();
816
+ verify(spec, created);
817
+ journal.resources[spec.key] = receipt(created, 'created', now(), operation.id);
818
+ await save();
819
+ remote = (await allResources(api, [spec])).get(spec.name)?.[0];
820
+ if (!remote || remote.id !== returnedId)
821
+ throw new CloudflareError('outcome_unknown', 6);
822
+ }
823
+ catch (error) {
824
+ if (!(error instanceof CloudflareError))
825
+ throw error;
826
+ if (error.code === 'deadline_exceeded' && !posted) {
827
+ operation.state = 'failed';
828
+ const intended = journal.resources[spec.key];
829
+ if (intended && intended.state !== 'complete')
830
+ intended.state = 'planned';
831
+ await save();
832
+ throw error;
833
+ }
834
+ if (error.code === 'jurisdiction_conflict' || error.code === 'configuration_conflict') {
835
+ operation.state = 'unknown';
836
+ await save();
837
+ throw error;
838
+ }
839
+ if (!posted &&
840
+ error.status !== null &&
841
+ error.status >= 400 &&
842
+ error.status < 500 &&
843
+ ![408, 409, 429].includes(error.status)) {
844
+ operation.state = 'failed';
845
+ await save();
846
+ throw error;
847
+ }
848
+ // Never resend a POST: even 409 and truncated successes require exact reconciliation.
849
+ for (let attempt = 0; attempt < 4; attempt++) {
850
+ try {
851
+ await api.pause(1000 * 2 ** attempt);
852
+ remote = (await allResources(api, [spec])).get(spec.name)?.[0];
853
+ }
854
+ catch (readError) {
855
+ if (readError instanceof CloudflareError &&
856
+ ['ownership_conflict', 'jurisdiction_conflict'].includes(readError.code))
857
+ throw readError;
858
+ continue;
859
+ }
860
+ if (remote)
861
+ break;
862
+ }
863
+ if (!remote ||
864
+ (returnedId !== undefined
865
+ ? returnedId !== remote.id
866
+ : !pendingEvidence(contract, remote, operation, now()))) {
867
+ operation.state = 'unknown';
868
+ await save();
869
+ throw new CloudflareError(remote ? 'ownership_conflict' : 'outcome_unknown', remote ? 8 : 6);
870
+ }
871
+ }
872
+ verify(spec, remote);
873
+ operation.state = 'confirmed';
874
+ operation.resolved_id = remote.id;
875
+ journal.resources[spec.key] = receipt(remote, returnedId === remote.id ? 'created' : 'pending_create', now(), operation.id);
876
+ await save();
877
+ }
878
+ const bucket = journal.resources.r2;
879
+ if (!o.dryRun &&
880
+ bucket?.id &&
881
+ bucket.state === 'complete' &&
882
+ bucket.lifecycle_baseline === undefined &&
883
+ bucket.evidence &&
884
+ ['created', 'pending_create'].includes(bucket.evidence.source)) {
885
+ let baseline;
886
+ try {
887
+ const live = object((await api.request(`/r2/buckets/${encodeURIComponent(bucket.id)}/lifecycle`, 'GET', undefined, { 'cf-r2-jurisdiction': identity.jurisdiction })).result).rules;
888
+ if (Array.isArray(live) && live.length <= 16 && live.every(lifecycleRule))
889
+ baseline = live.map(abortOnlyRule);
890
+ }
891
+ catch {
892
+ baseline = undefined;
893
+ }
894
+ if (baseline) {
895
+ bucket.lifecycle_baseline = baseline;
896
+ await save();
897
+ }
898
+ else
899
+ output.checks.push({ name: 'lifecycle_baseline', status: 'unrecorded' });
900
+ }
901
+ if (!o.dryRun && build.required.analytics && journal.resources.analytics === undefined) {
902
+ journal.resources.analytics = {
903
+ name: `${identity.script_name.replaceAll('-', '_')}_metrics`,
904
+ id: null,
905
+ state: 'binding_pending',
906
+ evidence: null,
907
+ };
908
+ await save();
909
+ }
910
+ // Dry-run exposes resolved non-secret IDs without writing adoption receipts.
911
+ if (o.dryRun)
912
+ for (const spec of specs) {
913
+ const remote = state.resources.get(spec.name)?.[0];
914
+ if (remote)
915
+ journal.resources[spec.key] = receipt(remote, 'worker_binding', now(), null);
916
+ }
917
+ if (o.domain)
918
+ output.worker_url = `https://${o.domain}`;
919
+ output.status = o.dryRun ? 'planned' : 'ok';
920
+ output.checks.push({
921
+ name: 'provider_semantics',
922
+ status: contract === cloudflareProviderContract ? 'documented' : 'custom',
923
+ });
924
+ setOutputs();
925
+ await release?.();
926
+ release = undefined;
927
+ return { exitCode: 0, output };
928
+ }
929
+ catch (error) {
930
+ if (error instanceof UnsupportedRequirementsError)
931
+ output.checks.push(...error.requirements.map((name) => ({ name, status: 'unsupported' })));
932
+ setOutputs();
933
+ if (release) {
934
+ try {
935
+ await release();
936
+ }
937
+ catch {
938
+ /* Preserve the original safe failure and journal. */
939
+ }
940
+ }
941
+ return failCommand(output, localFailure(error));
942
+ }
943
+ finally {
944
+ if (prebuiltOut)
945
+ await removePrebuilt(prebuiltOut).catch(() => undefined);
946
+ }
947
+ }
948
+ export async function runCloudflareProvision(o) {
949
+ const result = await provision({
950
+ ...o,
951
+ progress: o.progress ?? ((message) => console.error(message)),
952
+ });
953
+ if (o.json)
954
+ console.log(JSON.stringify(result.output));
955
+ else if (result.output.error)
956
+ console.error(`${result.output.error.code}: ${result.output.error.message}`);
957
+ else
958
+ console.log(JSON.stringify(result.output));
959
+ return result.exitCode;
960
+ }
961
+ //# sourceMappingURL=provision.js.map