@package-broker/cli 0.19.8 → 2.0.0-rc.2

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 (193) 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 +828 -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 +132 -0
  40. package/dist/commands/cloudflare/api.d.ts.map +1 -0
  41. package/dist/commands/cloudflare/api.js +502 -0
  42. package/dist/commands/cloudflare/api.js.map +1 -0
  43. package/dist/commands/cloudflare/deploy.d.ts +111 -0
  44. package/dist/commands/cloudflare/deploy.d.ts.map +1 -0
  45. package/dist/commands/cloudflare/deploy.js +2170 -0
  46. package/dist/commands/cloudflare/deploy.js.map +1 -0
  47. package/dist/commands/cloudflare/inventory.d.ts +257 -0
  48. package/dist/commands/cloudflare/inventory.d.ts.map +1 -0
  49. package/dist/commands/cloudflare/inventory.js +842 -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/providers-file.d.ts +17 -0
  60. package/dist/commands/cloudflare/providers-file.d.ts.map +1 -0
  61. package/dist/commands/cloudflare/providers-file.js +155 -0
  62. package/dist/commands/cloudflare/providers-file.js.map +1 -0
  63. package/dist/commands/cloudflare/provision.d.ts +79 -0
  64. package/dist/commands/cloudflare/provision.d.ts.map +1 -0
  65. package/dist/commands/cloudflare/provision.js +961 -0
  66. package/dist/commands/cloudflare/provision.js.map +1 -0
  67. package/dist/commands/cloudflare/queue-consumer.d.ts +4 -0
  68. package/dist/commands/cloudflare/queue-consumer.d.ts.map +1 -0
  69. package/dist/commands/cloudflare/queue-consumer.js +57 -0
  70. package/dist/commands/cloudflare/queue-consumer.js.map +1 -0
  71. package/dist/commands/cloudflare/routes.d.ts +3 -0
  72. package/dist/commands/cloudflare/routes.d.ts.map +1 -0
  73. package/dist/commands/cloudflare/routes.js +26 -0
  74. package/dist/commands/cloudflare/routes.js.map +1 -0
  75. package/dist/commands/cloudflare/setup.d.ts +50 -0
  76. package/dist/commands/cloudflare/setup.d.ts.map +1 -0
  77. package/dist/commands/cloudflare/setup.js +836 -0
  78. package/dist/commands/cloudflare/setup.js.map +1 -0
  79. package/dist/commands/cloudflare/smoke.d.ts +29 -0
  80. package/dist/commands/cloudflare/smoke.d.ts.map +1 -0
  81. package/dist/commands/cloudflare/smoke.js +701 -0
  82. package/dist/commands/cloudflare/smoke.js.map +1 -0
  83. package/dist/commands/cloudflare/teardown.d.ts +65 -0
  84. package/dist/commands/cloudflare/teardown.d.ts.map +1 -0
  85. package/dist/commands/cloudflare/teardown.js +984 -0
  86. package/dist/commands/cloudflare/teardown.js.map +1 -0
  87. package/dist/commands/cloudflare/worker-settings.d.ts +4 -0
  88. package/dist/commands/cloudflare/worker-settings.d.ts.map +1 -0
  89. package/dist/commands/cloudflare/worker-settings.js +93 -0
  90. package/dist/commands/cloudflare/worker-settings.js.map +1 -0
  91. package/dist/commands/cloudflare/zone.d.ts +3 -0
  92. package/dist/commands/cloudflare/zone.d.ts.map +1 -0
  93. package/dist/commands/cloudflare/zone.js +73 -0
  94. package/dist/commands/cloudflare/zone.js.map +1 -0
  95. package/dist/commands/cloudflare.d.ts +55 -0
  96. package/dist/commands/cloudflare.d.ts.map +1 -0
  97. package/dist/commands/cloudflare.js +393 -0
  98. package/dist/commands/cloudflare.js.map +1 -0
  99. package/dist/commands/import-cutover.d.ts +5 -0
  100. package/dist/commands/import-cutover.d.ts.map +1 -0
  101. package/dist/commands/import-cutover.js +25 -0
  102. package/dist/commands/import-cutover.js.map +1 -0
  103. package/dist/commands/import-private-packagist.d.ts +38 -0
  104. package/dist/commands/import-private-packagist.d.ts.map +1 -0
  105. package/dist/commands/import-private-packagist.js +232 -0
  106. package/dist/commands/import-private-packagist.js.map +1 -0
  107. package/dist/commands/import-satis.d.ts +11 -0
  108. package/dist/commands/import-satis.d.ts.map +1 -0
  109. package/dist/commands/import-satis.js +112 -0
  110. package/dist/commands/import-satis.js.map +1 -0
  111. package/dist/commands/import.d.ts +22 -0
  112. package/dist/commands/import.d.ts.map +1 -0
  113. package/dist/commands/import.js +343 -0
  114. package/dist/commands/import.js.map +1 -0
  115. package/dist/commands/openapi-specs.d.ts +4 -0
  116. package/dist/commands/openapi-specs.d.ts.map +1 -0
  117. package/dist/commands/openapi-specs.js +94 -0
  118. package/dist/commands/openapi-specs.js.map +1 -0
  119. package/dist/commands/openapi.d.ts +12 -0
  120. package/dist/commands/openapi.d.ts.map +1 -0
  121. package/dist/commands/openapi.js +44 -0
  122. package/dist/commands/openapi.js.map +1 -0
  123. package/dist/commands/source.d.ts +8 -0
  124. package/dist/commands/source.d.ts.map +1 -0
  125. package/dist/commands/source.js +61 -0
  126. package/dist/commands/source.js.map +1 -0
  127. package/dist/commands/sqlite.d.ts +11 -0
  128. package/dist/commands/sqlite.d.ts.map +1 -0
  129. package/dist/commands/sqlite.js +76 -0
  130. package/dist/commands/sqlite.js.map +1 -0
  131. package/dist/import/client.d.ts +71 -0
  132. package/dist/import/client.d.ts.map +1 -0
  133. package/dist/import/client.js +348 -0
  134. package/dist/import/client.js.map +1 -0
  135. package/dist/import/credential-refs.d.ts +2 -0
  136. package/dist/import/credential-refs.d.ts.map +1 -0
  137. package/dist/import/credential-refs.js +22 -0
  138. package/dist/import/credential-refs.js.map +1 -0
  139. package/dist/import/cutover.d.ts +64 -0
  140. package/dist/import/cutover.d.ts.map +1 -0
  141. package/dist/import/cutover.js +544 -0
  142. package/dist/import/cutover.js.map +1 -0
  143. package/dist/import/private-packagist/api.d.ts +57 -0
  144. package/dist/import/private-packagist/api.d.ts.map +1 -0
  145. package/dist/import/private-packagist/api.js +246 -0
  146. package/dist/import/private-packagist/api.js.map +1 -0
  147. package/dist/import/private-packagist/dates.d.ts +30 -0
  148. package/dist/import/private-packagist/dates.d.ts.map +1 -0
  149. package/dist/import/private-packagist/dates.js +189 -0
  150. package/dist/import/private-packagist/dates.js.map +1 -0
  151. package/dist/import/private-packagist/map.d.ts +48 -0
  152. package/dist/import/private-packagist/map.d.ts.map +1 -0
  153. package/dist/import/private-packagist/map.js +162 -0
  154. package/dist/import/private-packagist/map.js.map +1 -0
  155. package/dist/import/private-packagist/plan.d.ts +61 -0
  156. package/dist/import/private-packagist/plan.d.ts.map +1 -0
  157. package/dist/import/private-packagist/plan.js +936 -0
  158. package/dist/import/private-packagist/plan.js.map +1 -0
  159. package/dist/import/private-packagist/snapshot.d.ts +31 -0
  160. package/dist/import/private-packagist/snapshot.d.ts.map +1 -0
  161. package/dist/import/private-packagist/snapshot.js +406 -0
  162. package/dist/import/private-packagist/snapshot.js.map +1 -0
  163. package/dist/import/satis.d.ts +93 -0
  164. package/dist/import/satis.d.ts.map +1 -0
  165. package/dist/import/satis.js +873 -0
  166. package/dist/import/satis.js.map +1 -0
  167. package/dist/index.d.ts +3 -2
  168. package/dist/index.d.ts.map +1 -1
  169. package/dist/index.js +3 -96
  170. package/dist/index.js.map +1 -1
  171. package/dist/manifest.d.ts +56 -0
  172. package/dist/manifest.d.ts.map +1 -0
  173. package/dist/manifest.js +137 -0
  174. package/dist/manifest.js.map +1 -0
  175. package/dist/migrations-collect.d.ts +6 -0
  176. package/dist/migrations-collect.d.ts.map +1 -0
  177. package/dist/migrations-collect.js +39 -0
  178. package/dist/migrations-collect.js.map +1 -0
  179. package/dist/prebuilt-expected.json +7 -0
  180. package/dist/prebuilt.d.ts +27 -0
  181. package/dist/prebuilt.d.ts.map +1 -0
  182. package/dist/prebuilt.js +391 -0
  183. package/dist/prebuilt.js.map +1 -0
  184. package/package.json +22 -25
  185. package/README.md +0 -29
  186. package/dist/__tests__/migrate.test.d.ts +0 -2
  187. package/dist/__tests__/migrate.test.d.ts.map +0 -1
  188. package/dist/__tests__/migrate.test.js +0 -116
  189. package/dist/__tests__/migrate.test.js.map +0 -1
  190. package/dist/migrate.d.ts +0 -40
  191. package/dist/migrate.d.ts.map +0 -1
  192. package/dist/migrate.js +0 -335
  193. package/dist/migrate.js.map +0 -1
@@ -0,0 +1,842 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { execFile } from 'node:child_process';
3
+ import { constants } from 'node:fs';
4
+ import { chmod, lstat, mkdir, open, readFile, realpath, rename, unlink } from 'node:fs/promises';
5
+ import { hostname } from 'node:os';
6
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
7
+ import { promisify } from 'node:util';
8
+ import { CloudflareError, GitWorktreeRequiredError } from './api.js';
9
+ export function abortOnlyRule(rule) {
10
+ return {
11
+ id: rule.id,
12
+ enabled: rule.enabled,
13
+ conditions: { prefix: rule.conditions.prefix ?? '' },
14
+ abortMultipartUploadsTransition: rule.abortMultipartUploadsTransition,
15
+ };
16
+ }
17
+ export function lifecycleRule(raw) {
18
+ const record = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
19
+ const only = (v, keys) => Object.keys(v).length === keys.length && keys.every((k) => Object.hasOwn(v, k));
20
+ if (!record(raw))
21
+ return false;
22
+ const emptyTransitions = Array.isArray(raw.storageClassTransitions) && raw.storageClassTransitions.length === 0;
23
+ if (!only(raw, emptyTransitions
24
+ ? [
25
+ 'id',
26
+ 'enabled',
27
+ 'conditions',
28
+ 'abortMultipartUploadsTransition',
29
+ 'storageClassTransitions',
30
+ ]
31
+ : ['id', 'enabled', 'conditions', 'abortMultipartUploadsTransition']))
32
+ return false;
33
+ const { conditions, abortMultipartUploadsTransition: abort } = raw;
34
+ if (!record(conditions) ||
35
+ !(only(conditions, []) ||
36
+ (only(conditions, ['prefix']) && typeof conditions.prefix === 'string')))
37
+ return false;
38
+ if (!record(abort) || !only(abort, ['condition']) || !record(abort.condition))
39
+ return false;
40
+ const c = abort.condition;
41
+ return (only(c, ['type', 'maxAge']) &&
42
+ c.type === 'Age' &&
43
+ typeof c.maxAge === 'number' &&
44
+ Number.isSafeInteger(c.maxAge) &&
45
+ c.maxAge > 0 &&
46
+ typeof raw.id === 'string' &&
47
+ raw.id.length > 0 &&
48
+ raw.id.length <= 255 &&
49
+ typeof raw.enabled === 'boolean');
50
+ }
51
+ export const hashedBindingNames = new Set(['PB_IDP_PROVIDERS']);
52
+ export const deploymentHistoryLimits = {
53
+ versions: 10_000,
54
+ plainValues: 64,
55
+ hashedValues: 64,
56
+ };
57
+ export class DeploymentHistoryCapacityError extends CloudflareError {
58
+ constructor() {
59
+ super('capacity_exceeded', 2, false, 'deploy');
60
+ this.message =
61
+ 'Deployment history capacity reached (10000 versions or 64 distinct plain binding values). Retain the inventory and use a new installation before deploying again.';
62
+ }
63
+ }
64
+ export function recordDeploymentHistory(deployment, receipt) {
65
+ const history = structuredClone(deployment.history ?? { version_ids: [], plain_bindings: [] });
66
+ for (const id of [
67
+ receipt.versionId,
68
+ deployment.attempt.version_id,
69
+ deployment.last_verified?.version_id,
70
+ ])
71
+ if (id && !history.version_ids.includes(id))
72
+ history.version_ids.push(id);
73
+ const bindings = [
74
+ ...[deployment.attempt, deployment.last_verified].flatMap((attempt) => attempt?.version_id && attempt.worker_url
75
+ ? [{ name: 'PB_PUBLIC_URL', text: attempt.worker_url }]
76
+ : []),
77
+ ...(receipt.plainBinding ? [receipt.plainBinding] : []),
78
+ ];
79
+ for (const binding of bindings) {
80
+ if (hashedBindingNames.has(binding.name)) {
81
+ const hashes = (history.hashed_bindings ??= []);
82
+ const existing = hashes.find((b) => b.name === binding.name);
83
+ const sha256 = digest(binding.text);
84
+ if (!existing)
85
+ hashes.push({ name: binding.name, sha256: [sha256] });
86
+ else {
87
+ const index = existing.sha256.indexOf(sha256);
88
+ if (index !== -1)
89
+ existing.sha256.splice(index, 1);
90
+ existing.sha256.push(sha256);
91
+ if (existing.sha256.length > deploymentHistoryLimits.hashedValues)
92
+ existing.sha256.shift();
93
+ }
94
+ continue;
95
+ }
96
+ const existing = history.plain_bindings.find((b) => b.name === binding.name);
97
+ if (!existing)
98
+ history.plain_bindings.push({ name: binding.name, texts: [binding.text] });
99
+ else if (!existing.texts.includes(binding.text))
100
+ existing.texts.push(binding.text);
101
+ }
102
+ if (history.version_ids.length + Number(Boolean(receipt.reserveVersion)) >
103
+ deploymentHistoryLimits.versions ||
104
+ history.plain_bindings.reduce((count, b) => count + b.texts.length, 0) >
105
+ deploymentHistoryLimits.plainValues)
106
+ throw new DeploymentHistoryCapacityError();
107
+ deployment.history = history;
108
+ }
109
+ export function safeBuildInfo(raw) {
110
+ const bad = () => {
111
+ throw new CloudflareError('invalid_input', 2);
112
+ };
113
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
114
+ return bad();
115
+ const v = raw;
116
+ if (Object.keys(v).some((k) => ![
117
+ 'schemaVersion',
118
+ 'target',
119
+ 'core',
120
+ 'plugins',
121
+ 'lockfileSha256',
122
+ 'gitSha',
123
+ 'dirty',
124
+ ].includes(k)) ||
125
+ v.schemaVersion !== 1 ||
126
+ v.target !== 'cloudflare' ||
127
+ typeof v.core !== 'string' ||
128
+ !/^[a-zA-Z0-9.+-]{1,64}$/.test(v.core) ||
129
+ !v.plugins ||
130
+ typeof v.plugins !== 'object' ||
131
+ Array.isArray(v.plugins))
132
+ return bad();
133
+ if (Object.entries(v.plugins).some(([k, value]) => !/^[@a-zA-Z0-9/_.-]{1,128}$/.test(k) ||
134
+ typeof value !== 'string' ||
135
+ !/^[a-zA-Z0-9.+-]{1,64}$/.test(value)))
136
+ return bad();
137
+ if (v.lockfileSha256 !== null &&
138
+ (typeof v.lockfileSha256 !== 'string' || !/^[a-f0-9]{64}$/.test(v.lockfileSha256)))
139
+ return bad();
140
+ if (v.gitSha !== null && (typeof v.gitSha !== 'string' || !/^[a-f0-9]{40,64}$/.test(v.gitSha)))
141
+ return bad();
142
+ if (v.dirty !== undefined && v.dirty !== null && typeof v.dirty !== 'boolean')
143
+ return bad();
144
+ return raw;
145
+ }
146
+ function deploymentValid(raw) {
147
+ const record = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
148
+ const exact = (v, keys) => record(v) &&
149
+ Object.keys(v).length === keys.length &&
150
+ Object.keys(v).every((k) => keys.includes(k));
151
+ const hash = (v) => typeof v === 'string' && /^[a-f0-9]{64}$/.test(v);
152
+ const id = (v) => typeof v === 'string' && /^[a-zA-Z0-9_-]{1,128}$/.test(v);
153
+ const origin = (v) => {
154
+ try {
155
+ const u = new URL(String(v));
156
+ return u.protocol === 'https:' && u.origin === v && !u.username && !u.password;
157
+ }
158
+ catch {
159
+ return false;
160
+ }
161
+ };
162
+ const settings = (v) => {
163
+ if (!exact(v, ['endpoints', 'schedules', 'consumer', 'routes', 'domain']) ||
164
+ !exact(v.endpoints, ['enabled', 'previews_enabled']) ||
165
+ typeof v.endpoints.enabled !== 'boolean' ||
166
+ typeof v.endpoints.previews_enabled !== 'boolean' ||
167
+ !Array.isArray(v.schedules) ||
168
+ !v.schedules.every((s) => s === '* * * * *') ||
169
+ !Array.isArray(v.routes))
170
+ return false;
171
+ if (v.consumer !== null) {
172
+ const c = v.consumer;
173
+ if (!exact(c, [
174
+ 'consumer_id',
175
+ 'script_name',
176
+ 'batch_size',
177
+ 'max_wait_time_ms',
178
+ 'max_retries',
179
+ 'max_concurrency',
180
+ 'dead_letter_queue',
181
+ ]) ||
182
+ (c.consumer_id !== null && !id(c.consumer_id)) ||
183
+ !id(c.script_name) ||
184
+ !id(c.dead_letter_queue) ||
185
+ !['batch_size', 'max_wait_time_ms', 'max_retries', 'max_concurrency'].every((k) => typeof c[k] === 'number' && Number.isSafeInteger(c[k]) && c[k] >= 0))
186
+ return false;
187
+ }
188
+ if (!v.routes.every((r) => exact(r, ['id', 'zone_id', 'pattern', 'script']) &&
189
+ (r.id === null || id(r.id)) &&
190
+ typeof r.zone_id === 'string' &&
191
+ /^[a-f0-9]{32}$/.test(r.zone_id) &&
192
+ typeof r.pattern === 'string' &&
193
+ /^[a-zA-Z0-9*./:-]{1,256}$/.test(r.pattern) &&
194
+ id(r.script)))
195
+ return false;
196
+ if (v.domain !== null) {
197
+ const d = v.domain;
198
+ if (!exact(d, ['id', 'zone_id', 'hostname', 'service', 'environment']) ||
199
+ (d.id !== null && !id(d.id)) ||
200
+ typeof d.zone_id !== 'string' ||
201
+ !/^[a-f0-9]{32}$/.test(d.zone_id) ||
202
+ typeof d.hostname !== 'string' ||
203
+ !/^[a-z0-9.-]{1,253}$/.test(d.hostname) ||
204
+ !id(d.service) ||
205
+ d.environment !== 'production')
206
+ return false;
207
+ }
208
+ return true;
209
+ };
210
+ const attempt = (v) => {
211
+ if (!record(v) ||
212
+ !exact(v, [
213
+ 'operation_id',
214
+ 'artifact_sha256',
215
+ 'assets_sha256',
216
+ 'settings_sha256',
217
+ ...(Object.hasOwn(v, 'providers_sha256') ? ['providers_sha256'] : []),
218
+ ...(Object.hasOwn(v, 'identity_sha256') ? ['identity_sha256'] : []),
219
+ ...(Object.hasOwn(v, 'providers_text') ? ['providers_text'] : []),
220
+ 'secret_names',
221
+ 'build_info',
222
+ 'version_id',
223
+ 'previous_version_id',
224
+ 'previous_deployment_id',
225
+ 'previous_distribution',
226
+ ...(Object.hasOwn(v, 'script_creation') ? ['script_creation'] : []),
227
+ 'requested_rollout',
228
+ 'worker_url',
229
+ 'candidate_probe_url',
230
+ 'migration_ledger_sha256',
231
+ 'rollback_compatible',
232
+ 'contract_ids',
233
+ 'phase',
234
+ 'activated_at',
235
+ 'smoke',
236
+ ...(Object.hasOwn(v, 'smoke_checks') ? ['smoke_checks'] : []),
237
+ 'rollback',
238
+ 'preview',
239
+ 'prior_settings',
240
+ 'intended_settings',
241
+ 'changes',
242
+ ]))
243
+ return false;
244
+ try {
245
+ safeBuildInfo(v.build_info);
246
+ }
247
+ catch {
248
+ return false;
249
+ }
250
+ return (id(v.operation_id) &&
251
+ hash(v.artifact_sha256) &&
252
+ (v.assets_sha256 === null || hash(v.assets_sha256)) &&
253
+ hash(v.settings_sha256) &&
254
+ (v.identity_sha256 === undefined || v.identity_sha256 === null || hash(v.identity_sha256)) &&
255
+ (v.providers_sha256 === undefined ||
256
+ v.providers_sha256 === null ||
257
+ hash(v.providers_sha256)) &&
258
+ (v.providers_text === undefined ||
259
+ (typeof v.providers_text === 'string' &&
260
+ Buffer.byteLength(v.providers_text, 'utf8') <= 5120 &&
261
+ !['verified', 'failed', 'rolled_back'].includes(String(v.phase)))) &&
262
+ Array.isArray(v.secret_names) &&
263
+ v.secret_names.every((n) => typeof n === 'string' && /^[A-Z][A-Z0-9_]{0,127}$/.test(n) && n !== 'PB_TOKEN_PEPPER') &&
264
+ new Set(v.secret_names).size === v.secret_names.length &&
265
+ hash(v.migration_ledger_sha256) &&
266
+ [v.version_id, v.previous_version_id, v.previous_deployment_id].every((x) => x === null || id(x)) &&
267
+ (v.script_creation === undefined ||
268
+ (record(v.script_creation) &&
269
+ exact(v.script_creation, ['state', 'deployment_id', 'version_id']) &&
270
+ v.previous_version_id === null &&
271
+ v.previous_deployment_id === null &&
272
+ v.requested_rollout === 100 &&
273
+ (v.script_creation.state === 'pending'
274
+ ? v.script_creation.deployment_id === null &&
275
+ v.script_creation.version_id === null &&
276
+ v.phase === 'preparing_upload'
277
+ : v.script_creation.state === 'complete' &&
278
+ id(v.script_creation.deployment_id) &&
279
+ id(v.script_creation.version_id)))) &&
280
+ Array.isArray(v.previous_distribution) &&
281
+ v.previous_distribution.every((d) => exact(d, ['version_id', 'percentage']) && id(d.version_id) && d.percentage === 100) &&
282
+ v.previous_distribution.length <= 1 &&
283
+ (v.previous_version_id === null
284
+ ? v.previous_distribution.length === 0
285
+ : v.previous_distribution[0]?.version_id === v.previous_version_id) &&
286
+ typeof v.requested_rollout === 'number' &&
287
+ Number.isInteger(v.requested_rollout) &&
288
+ v.requested_rollout >= 1 &&
289
+ v.requested_rollout <= 100 &&
290
+ origin(v.worker_url) &&
291
+ (v.candidate_probe_url === null || origin(v.candidate_probe_url)) &&
292
+ typeof v.rollback_compatible === 'boolean' &&
293
+ Array.isArray(v.contract_ids) &&
294
+ v.contract_ids.every((x) => typeof x === 'string' && /^[a-z0-9-]+:\d{4}_[a-z0-9_]+$/.test(x)) &&
295
+ [
296
+ 'preparing_upload',
297
+ 'upload_pending',
298
+ 'uploaded',
299
+ 'configuring',
300
+ 'activation_pending',
301
+ 'active',
302
+ 'verified',
303
+ 'failed',
304
+ 'rollback_pending',
305
+ 'rolled_back',
306
+ ].includes(String(v.phase)) &&
307
+ (v.activated_at === null || iso(v.activated_at)) &&
308
+ ['pending', 'passed', 'failed'].includes(String(v.smoke)) &&
309
+ (v.smoke_checks === undefined ||
310
+ (Array.isArray(v.smoke_checks) &&
311
+ v.smoke_checks.length <= 4000 &&
312
+ v.smoke_checks.every((c) => exact(c, ['name', 'status', 'attempt', 'elapsed_ms']) &&
313
+ /^(?:(?:canonical|candidate)_(?:ready|(?:composer|npm)_(?:un)?authorized)|rollback_ready)$/.test(String(c.name)) &&
314
+ ['passed', 'failed', 'connection', 'not_ready', 'rate_limited', 'timeout'].includes(String(c.status)) &&
315
+ typeof c.attempt === 'number' &&
316
+ Number.isSafeInteger(c.attempt) &&
317
+ c.attempt > 0 &&
318
+ typeof c.elapsed_ms === 'number' &&
319
+ Number.isSafeInteger(c.elapsed_ms) &&
320
+ c.elapsed_ms >= 0))) &&
321
+ ['not_needed', 'pending', 'succeeded', 'failed', 'unknown'].includes(String(v.rollback)) &&
322
+ ['disabled', 'enable_pending', 'enabled', 'cleanup_pending'].includes(String(v.preview)) &&
323
+ settings(v.prior_settings) &&
324
+ settings(v.intended_settings) &&
325
+ Array.isArray(v.changes) &&
326
+ v.changes.every((c) => exact(c, ['kind', 'state', 'restoration']) &&
327
+ ['endpoints', 'schedules', 'consumer', 'routes', 'domain'].includes(String(c.kind)) &&
328
+ ['pending', 'applied', 'unknown'].includes(String(c.state)) &&
329
+ ['not_needed', 'restored', 'retained_first_install', 'failed', 'unknown'].includes(String(c.restoration))));
330
+ };
331
+ const history = (v) => record(v) &&
332
+ exact(v, [
333
+ 'version_ids',
334
+ 'plain_bindings',
335
+ ...(Object.hasOwn(v, 'hashed_bindings') ? ['hashed_bindings'] : []),
336
+ ]) &&
337
+ (!Object.hasOwn(v, 'hashed_bindings') ||
338
+ (Array.isArray(v.hashed_bindings) &&
339
+ v.hashed_bindings.length > 0 &&
340
+ new Set(v.hashed_bindings.map((b) => record(b) && b.name)).size ===
341
+ v.hashed_bindings.length &&
342
+ v.hashed_bindings.every((b) => exact(b, ['name', 'sha256']) &&
343
+ typeof b.name === 'string' &&
344
+ hashedBindingNames.has(b.name) &&
345
+ Array.isArray(b.sha256) &&
346
+ b.sha256.length > 0 &&
347
+ b.sha256.length <= deploymentHistoryLimits.hashedValues &&
348
+ b.sha256.every(hash) &&
349
+ new Set(b.sha256).size === b.sha256.length))) &&
350
+ Array.isArray(v.version_ids) &&
351
+ v.version_ids.length <= deploymentHistoryLimits.versions &&
352
+ v.version_ids.every(id) &&
353
+ new Set(v.version_ids).size === v.version_ids.length &&
354
+ Array.isArray(v.plain_bindings) &&
355
+ v.plain_bindings.length <= deploymentHistoryLimits.plainValues &&
356
+ new Set(v.plain_bindings.map((b) => record(b) && b.name)).size === v.plain_bindings.length &&
357
+ v.plain_bindings.every((b) => exact(b, ['name', 'texts']) &&
358
+ typeof b.name === 'string' &&
359
+ !hashedBindingNames.has(b.name) &&
360
+ /^[A-Z][A-Z0-9_]{0,127}$/.test(b.name) &&
361
+ Array.isArray(b.texts) &&
362
+ b.texts.length > 0 &&
363
+ b.texts.every((text) => typeof text === 'string' && text.length <= 4096) &&
364
+ new Set(b.texts).size === b.texts.length) &&
365
+ v.plain_bindings.reduce((count, b) => count + b.texts.length, 0) <=
366
+ deploymentHistoryLimits.plainValues;
367
+ return (record(raw) &&
368
+ (Object.hasOwn(raw, 'history')
369
+ ? exact(raw, ['attempt', 'last_verified', 'history']) && history(raw.history)
370
+ : exact(raw, ['attempt', 'last_verified'])) &&
371
+ attempt(raw.attempt) &&
372
+ (raw.last_verified === null ||
373
+ (record(raw.last_verified) &&
374
+ attempt(raw.last_verified) &&
375
+ raw.last_verified.phase === 'verified' &&
376
+ raw.last_verified.smoke === 'passed')));
377
+ }
378
+ export const digest = (value) => createHash('sha256').update(value).digest('hex');
379
+ const component = (value, max) => value.length <= max && /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(value);
380
+ export function installIdentity(o) {
381
+ const environment = o.environment ?? 'production';
382
+ const script = `${o.workerName}-${environment}`;
383
+ const deployment = o.deploymentId ?? script;
384
+ if (!/^[a-f0-9]{32}$/.test(o.accountId) ||
385
+ !component(o.workerName, 24) ||
386
+ !component(environment, 12) ||
387
+ !component(deployment, 64) ||
388
+ (o.jurisdiction !== undefined && !['eu', 'default'].includes(o.jurisdiction)) ||
389
+ (o.locationHint !== undefined && !/^[a-z]{2,8}$/.test(o.locationHint)) ||
390
+ (o.runIdentity !== undefined && !/^[a-zA-Z0-9_.-]{1,128}$/.test(o.runIdentity)))
391
+ throw new CloudflareError('invalid_input', 2);
392
+ // Until the provider contract is verified, accept no guessed hint/EU combination.
393
+ if ((o.jurisdiction ?? 'eu') === 'eu' && o.locationHint !== undefined)
394
+ throw new CloudflareError('invalid_input', 2);
395
+ return {
396
+ account_id: o.accountId,
397
+ worker_name: o.workerName,
398
+ environment,
399
+ script_name: script,
400
+ deployment_id: deployment,
401
+ owner_digest: digest([o.accountId, o.workerName, environment, deployment].join('\0')),
402
+ run_identity: o.runIdentity ?? null,
403
+ jurisdiction: o.jurisdiction ?? 'eu',
404
+ location_hint: o.locationHint ?? null,
405
+ };
406
+ }
407
+ export function emptyInventory(identity, now) {
408
+ return {
409
+ schema_version: 2,
410
+ revision: 0,
411
+ identity,
412
+ resources: {},
413
+ operations: [],
414
+ secret_delivery: { master_key: 'not_started', secret_name: 'PB_MASTER_KEY' },
415
+ bootstrap: { state: 'not_started', expires_at: null },
416
+ migrations: {
417
+ applied_this_transaction: [],
418
+ ledger: [],
419
+ rollback_compatible: true,
420
+ contract_ids: [],
421
+ },
422
+ deployment: null,
423
+ updated_at: new Date(now).toISOString(),
424
+ };
425
+ }
426
+ function inside(parent, child) {
427
+ const part = relative(parent, child);
428
+ return part === '' || (!isAbsolute(part) && part !== '..' && !part.startsWith(`..${sep}`));
429
+ }
430
+ async function stat(path) {
431
+ try {
432
+ return await lstat(path);
433
+ }
434
+ catch (error) {
435
+ if (error.code === 'ENOENT')
436
+ return null;
437
+ throw error;
438
+ }
439
+ }
440
+ async function noLinks(root, path) {
441
+ if (!inside(root, path))
442
+ throw new CloudflareError('unsafe_path', 2);
443
+ let current = root;
444
+ for (const part of relative(root, path).split(sep).filter(Boolean)) {
445
+ current = join(current, part);
446
+ const entry = await stat(current);
447
+ if (entry?.isSymbolicLink())
448
+ throw new CloudflareError('unsafe_path', 2);
449
+ if (entry && current !== path && !entry.isDirectory())
450
+ throw new CloudflareError('unsafe_path', 2);
451
+ }
452
+ }
453
+ async function canonical(path) {
454
+ const entry = await stat(path);
455
+ if (entry !== null)
456
+ return realpath(path);
457
+ const parent = dirname(path);
458
+ if (parent === path)
459
+ throw new CloudflareError('unsafe_path', 2);
460
+ return join(await canonical(parent), basename(path));
461
+ }
462
+ const iso = (v) => typeof v === 'string' && Number.isFinite(Date.parse(v));
463
+ const safeId = (v) => typeof v === 'string' && /^[a-zA-Z0-9_-]{1,128}$/.test(v);
464
+ export function validResourceId(kind, id) {
465
+ return (typeof id === 'string' &&
466
+ (kind === 'd1'
467
+ ? /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(id)
468
+ : kind === 'r2'
469
+ ? /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/.test(id)
470
+ : kind === 'queue'
471
+ ? /^[a-f0-9]{32}$/.test(id)
472
+ : kind === 'worker'
473
+ ? component(id, 37)
474
+ : false));
475
+ }
476
+ export function resourceKind(key) {
477
+ return key === 'jobs' || key === 'dlq' ? 'queue' : key;
478
+ }
479
+ export function validateInventory(raw, expected) {
480
+ const bad = () => {
481
+ throw new CloudflareError('invalid_inventory', 2);
482
+ };
483
+ if (typeof raw !== 'object' || raw === null)
484
+ return bad();
485
+ const data = raw;
486
+ if (data.schema_version !== 2 ||
487
+ !Number.isSafeInteger(data.revision) ||
488
+ data.revision < 0 ||
489
+ !iso(data.updated_at) ||
490
+ !data.identity)
491
+ return bad();
492
+ for (const key of Object.keys(expected)) {
493
+ // Location hints are creation-only; an altered hint never changes ownership.
494
+ if (key !== 'location_hint' && data.identity[key] !== expected[key])
495
+ throw new CloudflareError('ownership_conflict', 8);
496
+ }
497
+ if (!data.resources ||
498
+ typeof data.resources !== 'object' ||
499
+ Array.isArray(data.resources) ||
500
+ !Array.isArray(data.operations) ||
501
+ !data.migrations ||
502
+ !data.bootstrap ||
503
+ !data.secret_delivery ||
504
+ (data.deployment !== null && !deploymentValid(data.deployment)))
505
+ return bad();
506
+ const names = {
507
+ d1: `${expected.script_name}-db`,
508
+ r2: `${expected.script_name}-blobs`,
509
+ jobs: `${expected.script_name}-jobs`,
510
+ dlq: `${expected.script_name}-jobs-dlq`,
511
+ analytics: `${expected.script_name.replaceAll('-', '_')}_metrics`,
512
+ worker: expected.script_name,
513
+ };
514
+ for (const [key, r] of Object.entries(data.resources)) {
515
+ if (!Object.hasOwn(names, key) ||
516
+ !r ||
517
+ r.name !== names[key] ||
518
+ !['planned', 'creating', 'complete', 'binding_pending', 'bound'].includes(r.state))
519
+ return bad();
520
+ const kind = resourceKind(key);
521
+ if (r.id !== null &&
522
+ (!validResourceId(kind, r.id) || ((kind === 'worker' || kind === 'r2') && r.id !== r.name)))
523
+ return bad();
524
+ if ((kind === 'd1' || kind === 'r2') &&
525
+ (r.jurisdiction !== null || ['complete', 'binding_pending', 'bound'].includes(r.state)) &&
526
+ r.jurisdiction !== expected.jurisdiction)
527
+ return bad();
528
+ if (r.state === 'complete' && (r.id === null || r.evidence === null))
529
+ return bad();
530
+ if (r.evidence !== null &&
531
+ (!['created', 'pending_create', 'adopted', 'worker_binding'].includes(r.evidence.source) ||
532
+ !iso(r.evidence.verified_at) ||
533
+ (r.evidence.operation_id !== null && !safeId(r.evidence.operation_id))))
534
+ return bad();
535
+ }
536
+ const ids = new Set();
537
+ for (const op of data.operations) {
538
+ if (!op ||
539
+ !safeId(op.id) ||
540
+ ids.has(op.id) ||
541
+ !['d1', 'r2', 'queue'].includes(op.kind) ||
542
+ !/^[a-f0-9]{64}$/.test(op.spec_sha256) ||
543
+ !iso(op.absence_observed_at) ||
544
+ !iso(op.started_at) ||
545
+ Date.parse(op.started_at) < Date.parse(op.absence_observed_at) ||
546
+ !['pending', 'confirmed', 'unknown', 'failed'].includes(op.state) ||
547
+ (op.resolved_id !== null && !validResourceId(op.kind, op.resolved_id)) ||
548
+ !Object.entries(data.resources).some(([key, r]) => r?.name === op.name && resourceKind(key) === op.kind))
549
+ return bad();
550
+ ids.add(op.id);
551
+ }
552
+ const allowed = [
553
+ 'schema_version',
554
+ 'revision',
555
+ 'identity',
556
+ 'resources',
557
+ 'operations',
558
+ 'secret_delivery',
559
+ 'bootstrap',
560
+ 'migrations',
561
+ 'deployment',
562
+ 'updated_at',
563
+ ];
564
+ if (Object.keys(data).some((key) => !allowed.includes(key)) ||
565
+ Object.keys(data.identity).some((key) => !Object.hasOwn(expected, key)))
566
+ return bad();
567
+ if (![
568
+ 'not_started',
569
+ 'export_pending',
570
+ 'exported',
571
+ 'upload_pending',
572
+ 'installed',
573
+ 'unknown',
574
+ ].includes(data.secret_delivery.master_key) ||
575
+ data.secret_delivery.secret_name !== 'PB_MASTER_KEY' ||
576
+ !['not_started', 'issuing', 'issued', 'unknown', 'already_set_up'].includes(data.bootstrap.state) ||
577
+ (data.bootstrap.expires_at !== null && !iso(data.bootstrap.expires_at)))
578
+ return bad();
579
+ if (Object.keys(data.secret_delivery).some((key) => !['master_key', 'secret_name'].includes(key)) ||
580
+ Object.keys(data.bootstrap).some((key) => !['state', 'expires_at'].includes(key)) ||
581
+ Object.keys(data.migrations).some((key) => !['applied_this_transaction', 'ledger', 'rollback_compatible', 'contract_ids'].includes(key)))
582
+ return bad();
583
+ const migrations = data.migrations;
584
+ if (typeof migrations.rollback_compatible !== 'boolean' ||
585
+ !Array.isArray(migrations.applied_this_transaction) ||
586
+ !Array.isArray(migrations.contract_ids) ||
587
+ !Array.isArray(migrations.ledger))
588
+ return bad();
589
+ const migrationId = (id) => typeof id === 'string' && /^[a-z0-9-]+:\d{4}_[a-z0-9_]+$/.test(id);
590
+ if (![...migrations.applied_this_transaction, ...migrations.contract_ids].every(migrationId) ||
591
+ migrations.ledger.some((entry) => !entry ||
592
+ !migrationId(entry.id) ||
593
+ !/^[a-f0-9]{64}$/.test(entry.checksum) ||
594
+ !['expand', 'contract'].includes(entry.phase) ||
595
+ Object.keys(entry).some((key) => !['id', 'checksum', 'phase'].includes(key))))
596
+ return bad();
597
+ for (const [key, r] of Object.entries(data.resources)) {
598
+ if (Object.keys(r).some((key) => !['name', 'id', 'jurisdiction', 'state', 'evidence', 'lifecycle_baseline'].includes(key)))
599
+ return bad();
600
+ if (r.lifecycle_baseline !== undefined &&
601
+ (key !== 'r2' ||
602
+ !Array.isArray(r.lifecycle_baseline) ||
603
+ r.lifecycle_baseline.length > 16 ||
604
+ !r.lifecycle_baseline.every((rule) => lifecycleRule(rule) && typeof rule.conditions.prefix === 'string') ||
605
+ r.evidence === null ||
606
+ !['created', 'pending_create'].includes(r.evidence.source)))
607
+ return bad();
608
+ if (r.evidence &&
609
+ Object.keys(r.evidence).some((key) => !['source', 'operation_id', 'verified_at'].includes(key)))
610
+ return bad();
611
+ const unresolved = data.operations.filter((op) => op.name === r.name && ['pending', 'unknown'].includes(op.state));
612
+ if (unresolved.length > 1)
613
+ return bad();
614
+ if (r.evidence && ['created', 'pending_create'].includes(r.evidence.source)) {
615
+ if (!data.operations.some((op) => op.id === r.evidence?.operation_id &&
616
+ op.kind === resourceKind(key) &&
617
+ op.name === r.name &&
618
+ op.resolved_id === r.id))
619
+ return bad();
620
+ }
621
+ }
622
+ for (const op of data.operations) {
623
+ if (Object.keys(op).some((key) => ![
624
+ 'id',
625
+ 'kind',
626
+ 'name',
627
+ 'spec_sha256',
628
+ 'absence_observed_at',
629
+ 'started_at',
630
+ 'state',
631
+ 'resolved_id',
632
+ ].includes(key)) ||
633
+ (op.state === 'confirmed' && op.resolved_id === null))
634
+ return bad();
635
+ }
636
+ return data;
637
+ }
638
+ export class InventoryStore {
639
+ project;
640
+ directory;
641
+ path;
642
+ lockPath;
643
+ identity;
644
+ stateRoot;
645
+ constructor(project, directory, path, lockPath, identity, stateRoot) {
646
+ this.project = project;
647
+ this.directory = directory;
648
+ this.path = path;
649
+ this.lockPath = lockPath;
650
+ this.identity = identity;
651
+ this.stateRoot = stateRoot;
652
+ }
653
+ static async create(o) {
654
+ const project = await realpath(resolve(o.project));
655
+ const stateRoot = join(project, '.pb/state/cloudflare');
656
+ const path = resolve(project, o.inventory ??
657
+ join(stateRoot, `inventory.${o.identity.worker_name}.${o.identity.environment}.json`));
658
+ if (!inside(stateRoot, path) ||
659
+ !/^[a-zA-Z0-9][a-zA-Z0-9_.-]*\.json$/.test(basename(path)) ||
660
+ basename(path).startsWith('lock.'))
661
+ throw new CloudflareError('unsafe_path', 2);
662
+ const directory = dirname(path);
663
+ await noLinks(project, directory);
664
+ await noLinks(project, path);
665
+ let output;
666
+ try {
667
+ output = await canonical(resolve(project, o.outDir));
668
+ }
669
+ catch {
670
+ throw new CloudflareError('unsafe_path', 2);
671
+ }
672
+ if (inside(output, stateRoot) || inside(stateRoot, output))
673
+ throw new CloudflareError('unsafe_path', 2);
674
+ const lockPath = join(stateRoot, `lock.${o.identity.worker_name}.${o.identity.environment}.json`);
675
+ await noLinks(project, lockPath);
676
+ const existingLock = await stat(lockPath);
677
+ if (existingLock && !existingLock.isFile())
678
+ throw new CloudflareError('unsafe_path', 2);
679
+ const file = await stat(path);
680
+ if (file && !file.isFile())
681
+ throw new CloudflareError('unsafe_path', 2);
682
+ try {
683
+ for (const candidate of [path, lockPath, join(directory, '.inventory-probe.tmp')])
684
+ await promisify(execFile)('git', ['check-ignore', '--quiet', candidate], { cwd: project });
685
+ }
686
+ catch {
687
+ try {
688
+ await promisify(execFile)('git', ['rev-parse', '--is-inside-work-tree'], { cwd: project });
689
+ }
690
+ catch (probe) {
691
+ if (probe.code === 128)
692
+ throw new GitWorktreeRequiredError();
693
+ }
694
+ throw new CloudflareError('unsafe_path', 2);
695
+ }
696
+ return new InventoryStore(project, directory, path, lockPath, o.identity, stateRoot);
697
+ }
698
+ async read() {
699
+ await noLinks(this.project, this.path);
700
+ if (!(await stat(this.path)))
701
+ return null;
702
+ if (!(await stat(this.path))?.isFile() || (await stat(this.path)).size > 4_194_304)
703
+ throw new CloudflareError('unsafe_path', 2);
704
+ const file = await open(this.path, constants.O_RDONLY | constants.O_NOFOLLOW);
705
+ try {
706
+ const data = JSON.parse(await file.readFile('utf8'));
707
+ return validateInventory(data, this.identity);
708
+ }
709
+ catch (error) {
710
+ if (error instanceof CloudflareError)
711
+ throw error;
712
+ throw new CloudflareError('invalid_inventory', 2);
713
+ }
714
+ finally {
715
+ await file.close();
716
+ }
717
+ }
718
+ async prepare() {
719
+ await noLinks(this.project, this.directory);
720
+ await mkdir(this.directory, { recursive: true, mode: 0o700 });
721
+ await chmod(this.stateRoot, 0o700);
722
+ await chmod(this.directory, 0o700);
723
+ }
724
+ async write(data) {
725
+ for (const attempt of [data.deployment?.attempt, data.deployment?.last_verified])
726
+ if (attempt && ['verified', 'failed', 'rolled_back'].includes(attempt.phase))
727
+ delete attempt.providers_text;
728
+ validateInventory(data, this.identity);
729
+ await this.prepare();
730
+ await noLinks(this.project, this.path);
731
+ const existing = await stat(this.path);
732
+ if (existing && !existing.isFile())
733
+ throw new CloudflareError('unsafe_path', 2);
734
+ const temporary = join(this.directory, `.inventory-${randomUUID()}.tmp`);
735
+ try {
736
+ await promisify(execFile)('git', ['check-ignore', '--quiet', temporary], {
737
+ cwd: this.project,
738
+ });
739
+ }
740
+ catch {
741
+ throw new CloudflareError('unsafe_path', 2);
742
+ }
743
+ const file = await open(temporary, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 0o600);
744
+ try {
745
+ try {
746
+ await file.writeFile(`${JSON.stringify(data, null, 2)}\n`);
747
+ await file.sync();
748
+ }
749
+ finally {
750
+ await file.close();
751
+ }
752
+ await rename(temporary, this.path);
753
+ const dir = await open(this.directory, constants.O_RDONLY);
754
+ try {
755
+ await dir.sync();
756
+ }
757
+ finally {
758
+ await dir.close();
759
+ }
760
+ }
761
+ finally {
762
+ await unlink(temporary).catch(() => undefined);
763
+ }
764
+ }
765
+ async lock(now) {
766
+ await this.prepare();
767
+ await noLinks(this.project, this.lockPath);
768
+ const marker = {
769
+ pid: process.pid,
770
+ host: hostname(),
771
+ started_at: new Date(now).toISOString(),
772
+ nonce: randomUUID(),
773
+ };
774
+ try {
775
+ const file = await open(this.lockPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 0o600);
776
+ try {
777
+ await file.writeFile(JSON.stringify(marker));
778
+ await file.sync();
779
+ }
780
+ finally {
781
+ await file.close();
782
+ }
783
+ return async () => {
784
+ await noLinks(this.project, this.lockPath);
785
+ if (JSON.parse(await readFile(this.lockPath, 'utf8')).nonce !== marker.nonce)
786
+ throw new CloudflareError('install_locked', 4);
787
+ await unlink(this.lockPath);
788
+ };
789
+ }
790
+ catch (error) {
791
+ if (error.code !== 'EEXIST')
792
+ throw error;
793
+ const entry = await stat(this.lockPath);
794
+ if (!entry?.isFile())
795
+ throw new CloudflareError('unsafe_path', 2);
796
+ // A check followed by unlink can remove a concurrent recovery's replacement lock.
797
+ // Even a dead owner requires explicit recovery with all writers stopped.
798
+ throw new CloudflareError('install_locked', 4);
799
+ }
800
+ }
801
+ }
802
+ // Raw journals contain sensitive infrastructure metadata even on failed runs.
803
+ export function inventoryTransportPolicy(visibility) {
804
+ return visibility === 'private'
805
+ ? { raw_inventory: true, retention_days: 30, scope: 'exact_inventory_only' }
806
+ : { raw_inventory: false, retention_days: 0, scope: 'id_free_summary_only' };
807
+ }
808
+ /** A setup caller owns this lease across provision, migration and later stages. */
809
+ export class InventoryTransaction {
810
+ store;
811
+ release;
812
+ active = true;
813
+ appliedMigrations = new Set();
814
+ constructor(store, release) {
815
+ this.store = store;
816
+ this.release = release;
817
+ }
818
+ static async begin(store, now) {
819
+ return new InventoryTransaction(store, await store.lock(now));
820
+ }
821
+ assertStore(store) {
822
+ if (!this.active)
823
+ throw new CloudflareError('install_locked', 4);
824
+ if (this.store.path !== store.path)
825
+ throw new CloudflareError('ownership_conflict', 8);
826
+ validateInventory(emptyInventory(this.store.identity, 0), store.identity);
827
+ }
828
+ recordAppliedMigrations(ids) {
829
+ if (!this.active)
830
+ throw new CloudflareError('install_locked', 4);
831
+ for (const id of ids)
832
+ this.appliedMigrations.add(id);
833
+ return [...this.appliedMigrations];
834
+ }
835
+ async close() {
836
+ if (!this.active)
837
+ return;
838
+ this.active = false;
839
+ await this.release();
840
+ }
841
+ }
842
+ //# sourceMappingURL=inventory.js.map