dsh-edge 0.3.0-alpha.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +174 -34
- package/README.zh.md +174 -34
- package/package.json +1 -1
- package/scripts/cli.mjs +53 -1
- package/scripts/install.d.mts +9 -3
- package/scripts/install.mjs +131 -22
- package/worker/direct/index.js +81 -81
- package/worker/isolated/index.js +43 -43
package/scripts/install.mjs
CHANGED
|
@@ -152,6 +152,13 @@ export class InstallerOutputError extends Error {
|
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
class R2SubscriptionUnavailableError extends Error {
|
|
156
|
+
constructor(message) {
|
|
157
|
+
super(message)
|
|
158
|
+
this.name = 'R2SubscriptionUnavailableError'
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
155
162
|
/** Return account choices permitted by the selected runtime. */
|
|
156
163
|
export function accountChoices(mode, accounts, command = 'install') {
|
|
157
164
|
requireRuntimeMode(mode)
|
|
@@ -243,6 +250,10 @@ export function attachmentBucketName(workerName) {
|
|
|
243
250
|
return `${prefix}-${digest}${suffix}`
|
|
244
251
|
}
|
|
245
252
|
|
|
253
|
+
function r2ActivationUrl(accountId) {
|
|
254
|
+
return `https://dash.cloudflare.com/${encodeURIComponent(accountId)}/r2/overview`
|
|
255
|
+
}
|
|
256
|
+
|
|
246
257
|
/** Create or reuse the permanent deployment's private R2 attachment bucket. */
|
|
247
258
|
export async function ensureR2Bucket({
|
|
248
259
|
bucketName,
|
|
@@ -253,6 +264,7 @@ export async function ensureR2Bucket({
|
|
|
253
264
|
}) {
|
|
254
265
|
const infoArgs = ['r2', 'bucket', 'info', bucketName, '--json', ...profileArgs(profile)]
|
|
255
266
|
const initial = await runWrangler(infoArgs, { environment, signal })
|
|
267
|
+
throwIfR2SubscriptionUnavailable(initial)
|
|
256
268
|
if (initial.status === 0) {
|
|
257
269
|
requireR2BucketInfo(initial.stdout, bucketName)
|
|
258
270
|
return { bucketName, created: false }
|
|
@@ -260,19 +272,38 @@ export async function ensureR2Bucket({
|
|
|
260
272
|
const created = await runWrangler([
|
|
261
273
|
'r2', 'bucket', 'create', bucketName, ...profileArgs(profile),
|
|
262
274
|
], { environment, signal })
|
|
275
|
+
throwIfR2SubscriptionUnavailable(created)
|
|
263
276
|
if (created.status === 0) return { bucketName, created: true }
|
|
264
277
|
// A concurrent installer may have won the create race; prove exact existence.
|
|
265
278
|
const recovered = await runWrangler(infoArgs, { environment, signal })
|
|
279
|
+
throwIfR2SubscriptionUnavailable(recovered)
|
|
266
280
|
if (recovered.status === 0) {
|
|
267
281
|
requireR2BucketInfo(recovered.stdout, bucketName)
|
|
268
282
|
return { bucketName, created: false }
|
|
269
283
|
}
|
|
270
284
|
throw new Error(commandFailure(
|
|
271
|
-
`Could not create or access private R2 bucket "${bucketName}".
|
|
285
|
+
`Could not create or access private R2 bucket "${bucketName}". Check this account's R2 access and permissions, then retry`,
|
|
272
286
|
created,
|
|
273
287
|
))
|
|
274
288
|
}
|
|
275
289
|
|
|
290
|
+
/** Verify R2 availability without creating a bucket or collecting deployment credentials. */
|
|
291
|
+
async function verifyR2Subscription({ runWrangler, environment, profile, signal }) {
|
|
292
|
+
const result = await runWrangler([
|
|
293
|
+
'r2', 'bucket', 'list', ...profileArgs(profile),
|
|
294
|
+
], { environment, signal })
|
|
295
|
+
throwIfR2SubscriptionUnavailable(result)
|
|
296
|
+
requireSuccess(result, 'Could not check Cloudflare R2 availability')
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function throwIfR2SubscriptionUnavailable(result) {
|
|
300
|
+
if (!/\[code:\s*10042\]/u.test(`${result.stdout}\n${result.stderr}`)) return
|
|
301
|
+
throw new R2SubscriptionUnavailableError(commandFailure(
|
|
302
|
+
'Cloudflare R2 is not enabled for this account',
|
|
303
|
+
result,
|
|
304
|
+
))
|
|
305
|
+
}
|
|
306
|
+
|
|
276
307
|
function requireR2BucketInfo(source, expectedName) {
|
|
277
308
|
let info
|
|
278
309
|
try {
|
|
@@ -455,9 +486,11 @@ function versionAttachmentStorage(source) {
|
|
|
455
486
|
|| (marker === 'private-r2' && attachment.length !== 1)) {
|
|
456
487
|
throw new Error('The existing Worker attachment marker does not match its binding.')
|
|
457
488
|
}
|
|
458
|
-
|
|
459
|
-
//
|
|
460
|
-
|
|
489
|
+
if (marker !== undefined) return marker
|
|
490
|
+
// An R2 binding predating the explicit marker is still authoritative. A
|
|
491
|
+
// release with neither binding nor marker predates image attachments, so it
|
|
492
|
+
// has no image references to strand and can ask the owner to choose once.
|
|
493
|
+
return attachment.length === 1 ? 'private-r2' : undefined
|
|
461
494
|
}
|
|
462
495
|
|
|
463
496
|
/** Run the complete guided install with UI and Wrangler supplied as replaceable boundaries. */
|
|
@@ -552,7 +585,7 @@ export async function installEdge({
|
|
|
552
585
|
const commandEnvironment = temporary
|
|
553
586
|
? unauthenticatedEnvironment(environment)
|
|
554
587
|
: accountEnvironment(profileEnvironment ?? environment, account.id)
|
|
555
|
-
const
|
|
588
|
+
const existingAttachmentStorage = updatingExisting
|
|
556
589
|
? await detectExistingAttachmentStorage({
|
|
557
590
|
workerName,
|
|
558
591
|
mode,
|
|
@@ -561,17 +594,34 @@ export async function installEdge({
|
|
|
561
594
|
profile,
|
|
562
595
|
signal,
|
|
563
596
|
})
|
|
597
|
+
: undefined
|
|
598
|
+
let attachmentStorage = updatingExisting
|
|
599
|
+
? existingAttachmentStorage ?? await ui.selectInitialAttachmentStorage()
|
|
564
600
|
: temporary ? 'temporary-do' : 'private-r2'
|
|
601
|
+
requireAttachmentStorage(attachmentStorage)
|
|
565
602
|
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
603
|
+
const canSwitchToDurableObject = updatingExisting
|
|
604
|
+
&& existingAttachmentStorage === undefined
|
|
605
|
+
while (attachmentStorage === 'private-r2') {
|
|
606
|
+
ui.step('Checking Cloudflare R2 availability…')
|
|
607
|
+
try {
|
|
608
|
+
await verifyR2Subscription({
|
|
609
|
+
runWrangler,
|
|
610
|
+
environment: commandEnvironment,
|
|
611
|
+
profile,
|
|
612
|
+
signal,
|
|
613
|
+
})
|
|
614
|
+
break
|
|
615
|
+
} catch (error) {
|
|
616
|
+
if (!(error instanceof R2SubscriptionUnavailableError)) throw error
|
|
617
|
+
const resolution = await promptR2Recovery(
|
|
618
|
+
ui,
|
|
619
|
+
account.id,
|
|
620
|
+
canSwitchToDurableObject,
|
|
621
|
+
)
|
|
622
|
+
if (resolution === 'temporary-do') attachmentStorage = 'temporary-do'
|
|
623
|
+
}
|
|
624
|
+
}
|
|
575
625
|
|
|
576
626
|
const confirmed = await ui.confirm({
|
|
577
627
|
mode,
|
|
@@ -587,17 +637,44 @@ export async function installEdge({
|
|
|
587
637
|
throw new InstallCancelledError()
|
|
588
638
|
}
|
|
589
639
|
|
|
640
|
+
const secretMode = await ui.selectOwnerSecretMode()
|
|
641
|
+
const ownerSecret = secretMode === 'generate'
|
|
642
|
+
? generateOwnerSecret()
|
|
643
|
+
: await ui.ownerSecret(validateOwnerSecret)
|
|
644
|
+
const secretError = validateOwnerSecret(ownerSecret)
|
|
645
|
+
if (secretError !== undefined) throw new Error(secretError)
|
|
646
|
+
const deepSeekKey = await ui.deepSeekKey(validateDeepSeekKey)
|
|
647
|
+
const deepSeekError = validateDeepSeekKey(deepSeekKey)
|
|
648
|
+
if (deepSeekError !== undefined) throw new Error(deepSeekError)
|
|
649
|
+
|
|
590
650
|
let bucketName
|
|
591
651
|
if (attachmentStorage === 'private-r2') {
|
|
592
652
|
bucketName = attachmentBucketName(workerName)
|
|
593
653
|
ui.step(`Preparing private image storage (${bucketName})…`)
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
654
|
+
while (true) {
|
|
655
|
+
try {
|
|
656
|
+
await ensureR2Bucket({
|
|
657
|
+
bucketName,
|
|
658
|
+
runWrangler,
|
|
659
|
+
environment: commandEnvironment,
|
|
660
|
+
profile,
|
|
661
|
+
signal,
|
|
662
|
+
})
|
|
663
|
+
break
|
|
664
|
+
} catch (error) {
|
|
665
|
+
if (!(error instanceof R2SubscriptionUnavailableError)) throw error
|
|
666
|
+
const resolution = await promptR2Recovery(
|
|
667
|
+
ui,
|
|
668
|
+
account.id,
|
|
669
|
+
canSwitchToDurableObject,
|
|
670
|
+
)
|
|
671
|
+
if (resolution === 'temporary-do') {
|
|
672
|
+
attachmentStorage = 'temporary-do'
|
|
673
|
+
bucketName = undefined
|
|
674
|
+
break
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
601
678
|
}
|
|
602
679
|
|
|
603
680
|
temporaryDirectory = await createTemporaryDirectory()
|
|
@@ -1162,16 +1239,48 @@ function requireRuntimeMode(mode) {
|
|
|
1162
1239
|
}
|
|
1163
1240
|
}
|
|
1164
1241
|
|
|
1242
|
+
function requireAttachmentStorage(storage) {
|
|
1243
|
+
if (storage !== 'temporary-do' && storage !== 'private-r2') {
|
|
1244
|
+
throw new Error(`Unsupported attachment storage: ${String(storage)}`)
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
function requireR2RecoveryAction(action, canSwitchToDurableObject) {
|
|
1249
|
+
if (action === 'cancel') throw new InstallCancelledError()
|
|
1250
|
+
if (action === 'retry') return action
|
|
1251
|
+
if (action === 'temporary-do' && canSwitchToDurableObject) return action
|
|
1252
|
+
throw new Error(`Unsupported R2 recovery action: ${String(action)}`)
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
async function promptR2Recovery(ui, accountId, canSwitchToDurableObject) {
|
|
1256
|
+
return requireR2RecoveryAction(
|
|
1257
|
+
await ui.r2SubscriptionUnavailable({
|
|
1258
|
+
activationUrl: r2ActivationUrl(accountId),
|
|
1259
|
+
canSwitchToDurableObject,
|
|
1260
|
+
}),
|
|
1261
|
+
canSwitchToDurableObject,
|
|
1262
|
+
)
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1165
1265
|
function requireSuccess(result, prefix) {
|
|
1166
1266
|
if (result.outputFailure !== undefined) throw result.outputFailure
|
|
1167
1267
|
if (result.status !== 0) throw new Error(commandFailure(prefix, result))
|
|
1168
1268
|
}
|
|
1169
1269
|
|
|
1170
1270
|
function commandFailure(prefix, result) {
|
|
1171
|
-
const detail =
|
|
1271
|
+
const detail = conciseDiagnostic(result.stderr || result.stdout)
|
|
1172
1272
|
return detail === '' ? `${prefix}.` : `${prefix}: ${detail}`
|
|
1173
1273
|
}
|
|
1174
1274
|
|
|
1275
|
+
/** Remove known Node dependency noise from concise errors while verbose output stays unchanged. */
|
|
1276
|
+
function conciseDiagnostic(value) {
|
|
1277
|
+
return stripAnsi(value).split(/\r?\n/u)
|
|
1278
|
+
.filter(line => !/^\(node:\d+\) \[DEP0040\] DeprecationWarning: The `punycode` module is deprecated\./u.test(line.trim()))
|
|
1279
|
+
.filter(line => !/^\(Use `node --trace-deprecation \.\.\.` to show where the warning was created\)$/u.test(line.trim()))
|
|
1280
|
+
.join('\n')
|
|
1281
|
+
.trim()
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1175
1284
|
function describeError(error) {
|
|
1176
1285
|
return error instanceof Error ? error.message : String(error)
|
|
1177
1286
|
}
|