dsh-edge 0.2.0 → 0.3.0-alpha.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.
- package/README.i18n.yaml +2 -2
- package/README.md +13 -10
- package/README.zh.md +13 -10
- package/THIRD_PARTY_NOTICES.md +447 -348
- package/dist/assets/{index-BNMwCG9c.css → index-C6eRlFa6.css} +1 -1
- package/dist/assets/{index-CA9Bpko5.js → index-ClqxG24t.js} +35 -35
- package/dist/index.html +3 -3
- package/dist/plugins/@deepseek-ai/dsh-api-gateway/client.js +84 -55
- package/dist/plugins/@deepseek-ai/dsh-client-connection/client.js +10 -6
- package/dist/plugins/@deepseek-ai/dsh-client-runtime/client.js +15 -6
- package/dist/plugins/@deepseek-ai/dsh-client-ui-attachment/client.js +782 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-conversation/client.js +188 -72
- package/dist/plugins/@deepseek-ai/dsh-client-ui-permission-presets/client.js +46 -13
- package/dist/plugins/@deepseek-ai/dsh-client-ui-sidebar/client.js +1 -1
- package/dist/plugins/@deepseek-ai/dsh-client-ui-subagent/client.js +287 -128
- package/dist/plugins/@deepseek-ai/dsh-client-ui-user-questions/client.js +46 -9
- package/dist/plugins/@deepseek-ai/dsh-client-ui-workspace/client.js +26 -0
- package/package.json +25 -22
- package/scripts/cli.mjs +56 -1
- package/scripts/install.d.mts +31 -0
- package/scripts/install.mjs +304 -16
- package/scripts/legal-files.mjs +5 -1
- package/scripts/wrangler-config-core.mjs +28 -0
- package/scripts/wrangler-config.d.mts +1 -0
- package/worker/direct/index.js +1021 -991
- package/worker/isolated/index.js +677 -647
package/scripts/install.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto'
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto'
|
|
2
2
|
import { execFile } from 'node:child_process'
|
|
3
3
|
import { chmod, mkdtemp, open, rm, writeFile } from 'node:fs/promises'
|
|
4
4
|
import { createRequire } from 'node:module'
|
|
@@ -93,6 +93,7 @@ const SENSITIVE_ENV_KEY = /(KEY|PASSWORD|SECRET|TOKEN)/iu
|
|
|
93
93
|
const WORKER_NAME = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/u
|
|
94
94
|
const CLAIM_URL = /https:\/\/dash\.cloudflare\.com\/claim-preview\?[^\s\u001b]+/u
|
|
95
95
|
const MAX_CAPTURE_BYTES = 2 * 1024 * 1024
|
|
96
|
+
const ATTACHMENT_STORAGE_BINDING = 'DSH_EDGE_ATTACHMENT_STORAGE'
|
|
96
97
|
const WINDOWS_ACL_TIMEOUT_MS = 30_000
|
|
97
98
|
const WINDOWS_PRIVATE_DIRECTORY_SCRIPT = String.raw`
|
|
98
99
|
$ErrorActionPreference = 'Stop'
|
|
@@ -151,6 +152,13 @@ export class InstallerOutputError extends Error {
|
|
|
151
152
|
}
|
|
152
153
|
}
|
|
153
154
|
|
|
155
|
+
class R2SubscriptionUnavailableError extends Error {
|
|
156
|
+
constructor(message) {
|
|
157
|
+
super(message)
|
|
158
|
+
this.name = 'R2SubscriptionUnavailableError'
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
154
162
|
/** Return account choices permitted by the selected runtime. */
|
|
155
163
|
export function accountChoices(mode, accounts, command = 'install') {
|
|
156
164
|
requireRuntimeMode(mode)
|
|
@@ -232,6 +240,82 @@ export function generateOwnerSecret() {
|
|
|
232
240
|
return randomBytes(32).toString('base64url')
|
|
233
241
|
}
|
|
234
242
|
|
|
243
|
+
/** Derive one stable account-local R2 bucket name from the Worker service name. */
|
|
244
|
+
export function attachmentBucketName(workerName) {
|
|
245
|
+
if (validateWorkerName(workerName) !== undefined) throw new Error('Worker name is invalid.')
|
|
246
|
+
const suffix = '-attachments'
|
|
247
|
+
if (workerName.length + suffix.length <= 63) return `${workerName}${suffix}`
|
|
248
|
+
const digest = createHash('sha256').update(workerName).digest('hex').slice(0, 8)
|
|
249
|
+
const prefix = workerName.slice(0, 42).replace(/-+$/u, '')
|
|
250
|
+
return `${prefix}-${digest}${suffix}`
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function r2ActivationUrl(accountId) {
|
|
254
|
+
return `https://dash.cloudflare.com/${encodeURIComponent(accountId)}/r2/overview`
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Create or reuse the permanent deployment's private R2 attachment bucket. */
|
|
258
|
+
export async function ensureR2Bucket({
|
|
259
|
+
bucketName,
|
|
260
|
+
runWrangler,
|
|
261
|
+
environment,
|
|
262
|
+
profile,
|
|
263
|
+
signal,
|
|
264
|
+
}) {
|
|
265
|
+
const infoArgs = ['r2', 'bucket', 'info', bucketName, '--json', ...profileArgs(profile)]
|
|
266
|
+
const initial = await runWrangler(infoArgs, { environment, signal })
|
|
267
|
+
throwIfR2SubscriptionUnavailable(initial)
|
|
268
|
+
if (initial.status === 0) {
|
|
269
|
+
requireR2BucketInfo(initial.stdout, bucketName)
|
|
270
|
+
return { bucketName, created: false }
|
|
271
|
+
}
|
|
272
|
+
const created = await runWrangler([
|
|
273
|
+
'r2', 'bucket', 'create', bucketName, ...profileArgs(profile),
|
|
274
|
+
], { environment, signal })
|
|
275
|
+
throwIfR2SubscriptionUnavailable(created)
|
|
276
|
+
if (created.status === 0) return { bucketName, created: true }
|
|
277
|
+
// A concurrent installer may have won the create race; prove exact existence.
|
|
278
|
+
const recovered = await runWrangler(infoArgs, { environment, signal })
|
|
279
|
+
throwIfR2SubscriptionUnavailable(recovered)
|
|
280
|
+
if (recovered.status === 0) {
|
|
281
|
+
requireR2BucketInfo(recovered.stdout, bucketName)
|
|
282
|
+
return { bucketName, created: false }
|
|
283
|
+
}
|
|
284
|
+
throw new Error(commandFailure(
|
|
285
|
+
`Could not create or access private R2 bucket "${bucketName}". Check this account's R2 access and permissions, then retry`,
|
|
286
|
+
created,
|
|
287
|
+
))
|
|
288
|
+
}
|
|
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
|
+
|
|
307
|
+
function requireR2BucketInfo(source, expectedName) {
|
|
308
|
+
let info
|
|
309
|
+
try {
|
|
310
|
+
info = JSON.parse(source)
|
|
311
|
+
} catch {
|
|
312
|
+
throw new Error('Wrangler returned malformed R2 bucket information.')
|
|
313
|
+
}
|
|
314
|
+
if (info === null || typeof info !== 'object' || info.name !== expectedName) {
|
|
315
|
+
throw new Error('Wrangler returned unexpected R2 bucket information.')
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
235
319
|
/** Build the least-privilege environment used by authenticated Wrangler commands. */
|
|
236
320
|
export function wranglerEnvironment(environment = process.env) {
|
|
237
321
|
return pickEnvironment(environment, [
|
|
@@ -319,6 +403,96 @@ export function parseWorkerExistence(result) {
|
|
|
319
403
|
throw new Error(commandFailure('Could not check whether the Worker already exists', result))
|
|
320
404
|
}
|
|
321
405
|
|
|
406
|
+
/** Inspect active Worker versions so upgrades preserve the existing attachment backend. */
|
|
407
|
+
export async function detectExistingAttachmentStorage({
|
|
408
|
+
workerName,
|
|
409
|
+
mode,
|
|
410
|
+
runWrangler,
|
|
411
|
+
environment,
|
|
412
|
+
profile,
|
|
413
|
+
signal,
|
|
414
|
+
}) {
|
|
415
|
+
const args = [
|
|
416
|
+
'--name', workerName, '--json',
|
|
417
|
+
...runtimeEnvironmentArgs(mode),
|
|
418
|
+
...profileArgs(profile),
|
|
419
|
+
]
|
|
420
|
+
const status = await runWrangler(['deployments', 'status', ...args], {
|
|
421
|
+
environment,
|
|
422
|
+
signal,
|
|
423
|
+
})
|
|
424
|
+
requireSuccess(status, 'Could not inspect the existing Worker deployment')
|
|
425
|
+
const versionIds = deploymentVersionIds(status.stdout)
|
|
426
|
+
const backends = new Set()
|
|
427
|
+
for (const versionId of versionIds) {
|
|
428
|
+
const version = await runWrangler([
|
|
429
|
+
'versions', 'view', versionId, ...args,
|
|
430
|
+
], { environment, signal })
|
|
431
|
+
requireSuccess(version, `Could not inspect existing Worker version ${versionId}`)
|
|
432
|
+
backends.add(versionAttachmentStorage(version.stdout))
|
|
433
|
+
}
|
|
434
|
+
if (backends.size !== 1) {
|
|
435
|
+
throw new Error('The active Worker versions use different attachment backends. Finish the existing rollout before upgrading.')
|
|
436
|
+
}
|
|
437
|
+
return backends.values().next().value
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function deploymentVersionIds(source) {
|
|
441
|
+
let deployment
|
|
442
|
+
try {
|
|
443
|
+
deployment = JSON.parse(source)
|
|
444
|
+
} catch {
|
|
445
|
+
throw new Error('Wrangler returned malformed deployment status.')
|
|
446
|
+
}
|
|
447
|
+
if (!isRecord(deployment) || !Array.isArray(deployment.versions)) {
|
|
448
|
+
throw new Error('Wrangler returned unexpected deployment status.')
|
|
449
|
+
}
|
|
450
|
+
const ids = deployment.versions
|
|
451
|
+
.filter(version => isRecord(version) && typeof version.percentage === 'number'
|
|
452
|
+
&& version.percentage > 0)
|
|
453
|
+
.map(version => version.version_id)
|
|
454
|
+
if (ids.length === 0 || ids.some(id => typeof id !== 'string' || id.length === 0)) {
|
|
455
|
+
throw new Error('Wrangler deployment status has no active Worker version.')
|
|
456
|
+
}
|
|
457
|
+
return [...new Set(ids)]
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function versionAttachmentStorage(source) {
|
|
461
|
+
let version
|
|
462
|
+
try {
|
|
463
|
+
version = JSON.parse(source)
|
|
464
|
+
} catch {
|
|
465
|
+
throw new Error('Wrangler returned malformed Worker version details.')
|
|
466
|
+
}
|
|
467
|
+
const bindings = isRecord(version) && isRecord(version.resources)
|
|
468
|
+
? version.resources.bindings
|
|
469
|
+
: undefined
|
|
470
|
+
if (!Array.isArray(bindings) || bindings.some(binding => !isRecord(binding))) {
|
|
471
|
+
throw new Error('Wrangler returned unexpected Worker version details.')
|
|
472
|
+
}
|
|
473
|
+
const attachment = bindings.filter(binding => binding.name === 'DSH_EDGE_ATTACHMENTS')
|
|
474
|
+
if (attachment.length > 1
|
|
475
|
+
|| (attachment.length === 1 && attachment[0].type !== 'r2_bucket')) {
|
|
476
|
+
throw new Error('The existing Worker has an invalid attachment binding.')
|
|
477
|
+
}
|
|
478
|
+
const markers = bindings.filter(binding => binding.name === ATTACHMENT_STORAGE_BINDING)
|
|
479
|
+
if (markers.length > 1 || (markers.length === 1
|
|
480
|
+
&& (markers[0].type !== 'plain_text'
|
|
481
|
+
|| (markers[0].text !== 'temporary-do' && markers[0].text !== 'private-r2')))) {
|
|
482
|
+
throw new Error('The existing Worker has an invalid attachment storage marker.')
|
|
483
|
+
}
|
|
484
|
+
const marker = markers[0]?.text
|
|
485
|
+
if ((marker === 'temporary-do' && attachment.length !== 0)
|
|
486
|
+
|| (marker === 'private-r2' && attachment.length !== 1)) {
|
|
487
|
+
throw new Error('The existing Worker attachment marker does not match its binding.')
|
|
488
|
+
}
|
|
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
|
|
494
|
+
}
|
|
495
|
+
|
|
322
496
|
/** Run the complete guided install with UI and Wrangler supplied as replaceable boundaries. */
|
|
323
497
|
export async function installEdge({
|
|
324
498
|
command = 'install',
|
|
@@ -380,6 +554,7 @@ export async function installEdge({
|
|
|
380
554
|
: requireSelectedAccount(accountSelection, accounts)
|
|
381
555
|
|
|
382
556
|
let workerName = DEFAULT_WORKER_NAME
|
|
557
|
+
let updatingExisting = false
|
|
383
558
|
while (true) {
|
|
384
559
|
workerName = await ui.workerName(workerName, validateWorkerName)
|
|
385
560
|
if (temporary) break
|
|
@@ -394,24 +569,59 @@ export async function installEdge({
|
|
|
394
569
|
}))
|
|
395
570
|
if (command === 'upgrade') {
|
|
396
571
|
if (!exists) throw new Error(`${workerName} does not exist in this account and runtime. Run dsh-edge install first.`)
|
|
572
|
+
updatingExisting = true
|
|
397
573
|
break
|
|
398
574
|
}
|
|
399
575
|
if (!exists) break
|
|
400
576
|
const action = await ui.workerConflict(workerName)
|
|
401
|
-
if (action === 'update')
|
|
577
|
+
if (action === 'update') {
|
|
578
|
+
updatingExisting = true
|
|
579
|
+
break
|
|
580
|
+
}
|
|
402
581
|
if (action === 'cancel') throw new InstallCancelledError()
|
|
403
582
|
workerName = `${workerName}-2`
|
|
404
583
|
}
|
|
405
584
|
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
585
|
+
const commandEnvironment = temporary
|
|
586
|
+
? unauthenticatedEnvironment(environment)
|
|
587
|
+
: accountEnvironment(profileEnvironment ?? environment, account.id)
|
|
588
|
+
const existingAttachmentStorage = updatingExisting
|
|
589
|
+
? await detectExistingAttachmentStorage({
|
|
590
|
+
workerName,
|
|
591
|
+
mode,
|
|
592
|
+
runWrangler,
|
|
593
|
+
environment: commandEnvironment,
|
|
594
|
+
profile,
|
|
595
|
+
signal,
|
|
596
|
+
})
|
|
597
|
+
: undefined
|
|
598
|
+
let attachmentStorage = updatingExisting
|
|
599
|
+
? existingAttachmentStorage ?? await ui.selectInitialAttachmentStorage()
|
|
600
|
+
: temporary ? 'temporary-do' : 'private-r2'
|
|
601
|
+
requireAttachmentStorage(attachmentStorage)
|
|
602
|
+
|
|
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
|
+
}
|
|
415
625
|
|
|
416
626
|
const confirmed = await ui.confirm({
|
|
417
627
|
mode,
|
|
@@ -419,17 +629,61 @@ export async function installEdge({
|
|
|
419
629
|
accountLabel: temporary ? 'Temporary account' : account.name,
|
|
420
630
|
workerName,
|
|
421
631
|
paid: mode === 'isolated',
|
|
632
|
+
temporary,
|
|
633
|
+
attachmentStorage,
|
|
422
634
|
})
|
|
423
635
|
if (!confirmed) throw new InstallCancelledError()
|
|
424
636
|
if (temporary && !await ui.acceptTemporaryTerms()) {
|
|
425
637
|
throw new InstallCancelledError()
|
|
426
638
|
}
|
|
427
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
|
+
|
|
650
|
+
let bucketName
|
|
651
|
+
if (attachmentStorage === 'private-r2') {
|
|
652
|
+
bucketName = attachmentBucketName(workerName)
|
|
653
|
+
ui.step(`Preparing private image storage (${bucketName})…`)
|
|
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
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
428
680
|
temporaryDirectory = await createTemporaryDirectory()
|
|
429
681
|
const secretsFile = join(temporaryDirectory, 'secrets.json')
|
|
430
682
|
const configFile = join(temporaryDirectory, 'wrangler.json')
|
|
431
683
|
const outputFile = join(temporaryDirectory, 'wrangler-output.ndjson')
|
|
432
|
-
await writePrebuiltModeWranglerConfig(mode, configFile
|
|
684
|
+
await writePrebuiltModeWranglerConfig(mode, configFile, {
|
|
685
|
+
...bucketName === undefined ? {} : { r2BucketName: bucketName },
|
|
686
|
+
})
|
|
433
687
|
await writeFile(secretsFile, JSON.stringify({
|
|
434
688
|
DEEPSEEK_API_KEY: deepSeekKey,
|
|
435
689
|
DSH_EDGE_ACCESS_KEY: ownerSecret,
|
|
@@ -439,9 +693,6 @@ export async function installEdge({
|
|
|
439
693
|
? 'Uploading the tested Worker release…'
|
|
440
694
|
: 'Installing the tested Worker release…'
|
|
441
695
|
ui.deploymentStart?.(deploymentMessage)
|
|
442
|
-
const commandEnvironment = temporary
|
|
443
|
-
? unauthenticatedEnvironment(environment)
|
|
444
|
-
: accountEnvironment(profileEnvironment ?? environment, account.id)
|
|
445
696
|
if (temporary) commandEnvironment.XDG_CONFIG_HOME = temporaryDirectory
|
|
446
697
|
commandEnvironment.WRANGLER_LOG_SANITIZE = 'true'
|
|
447
698
|
commandEnvironment.WRANGLER_OUTPUT_FILE_PATH = outputFile
|
|
@@ -527,6 +778,7 @@ export async function installEdge({
|
|
|
527
778
|
let result = {
|
|
528
779
|
...deployment,
|
|
529
780
|
account,
|
|
781
|
+
attachmentStorage,
|
|
530
782
|
claimUrl,
|
|
531
783
|
mode,
|
|
532
784
|
ownerSecret,
|
|
@@ -843,6 +1095,10 @@ function isTerminalSafe(value) {
|
|
|
843
1095
|
return !CONTROL_CHARACTER.test(value) && !BIDI_CONTROL.test(value)
|
|
844
1096
|
}
|
|
845
1097
|
|
|
1098
|
+
function isRecord(value) {
|
|
1099
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
1100
|
+
}
|
|
1101
|
+
|
|
846
1102
|
/** Create a chunk-safe terminal filter that retains plain text only. */
|
|
847
1103
|
export function createTerminalSanitizer() {
|
|
848
1104
|
let state = 'text'
|
|
@@ -983,16 +1239,48 @@ function requireRuntimeMode(mode) {
|
|
|
983
1239
|
}
|
|
984
1240
|
}
|
|
985
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
|
+
|
|
986
1265
|
function requireSuccess(result, prefix) {
|
|
987
1266
|
if (result.outputFailure !== undefined) throw result.outputFailure
|
|
988
1267
|
if (result.status !== 0) throw new Error(commandFailure(prefix, result))
|
|
989
1268
|
}
|
|
990
1269
|
|
|
991
1270
|
function commandFailure(prefix, result) {
|
|
992
|
-
const detail =
|
|
1271
|
+
const detail = conciseDiagnostic(result.stderr || result.stdout)
|
|
993
1272
|
return detail === '' ? `${prefix}.` : `${prefix}: ${detail}`
|
|
994
1273
|
}
|
|
995
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
|
+
|
|
996
1284
|
function describeError(error) {
|
|
997
1285
|
return error instanceof Error ? error.message : String(error)
|
|
998
1286
|
}
|
package/scripts/legal-files.mjs
CHANGED
|
@@ -41,6 +41,10 @@ const repositoryRoot = fileURLToPath(new URL('../../../', import.meta.url))
|
|
|
41
41
|
const packageRoot = fileURLToPath(new URL('../', import.meta.url))
|
|
42
42
|
const standaloneRoot = join(packageRoot, 'standalone')
|
|
43
43
|
const edgeManifest = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'))
|
|
44
|
+
const patchAudit = JSON.parse(readFileSync(join(standaloneRoot, 'patches', 'audit.json'), 'utf8'))
|
|
45
|
+
if (!Array.isArray(patchAudit) || patchAudit.length === 0) {
|
|
46
|
+
throw new Error('The retained patch audit must be a non-empty array.')
|
|
47
|
+
}
|
|
44
48
|
|
|
45
49
|
function bundledComponents() {
|
|
46
50
|
const invocation = resolvePnpmInvocation(process.env.npm_execpath ?? 'pnpm', [
|
|
@@ -212,7 +216,7 @@ function renderNotices(components) {
|
|
|
212
216
|
|
|
213
217
|
## DeepSeek Harness
|
|
214
218
|
|
|
215
|
-
\`dsh-edge\` assembles published DeepSeek Harness packages and applies
|
|
219
|
+
\`dsh-edge\` assembles published DeepSeek Harness packages and applies ${String(patchAudit.length)} version-bound adaptations to the pinned \`${edgeManifest.dshEdge.upstreamVersion}\` release. DeepSeek Harness remains under its upstream MIT license:
|
|
216
220
|
|
|
217
221
|
\`\`\`text
|
|
218
222
|
${upstreamLicense}
|
|
@@ -11,6 +11,8 @@ const WORKER_ARTIFACTS = Object.freeze({
|
|
|
11
11
|
direct: 'worker/direct/index.js',
|
|
12
12
|
isolated: 'worker/isolated/index.js',
|
|
13
13
|
})
|
|
14
|
+
const ATTACHMENT_BINDING = 'DSH_EDGE_ATTACHMENTS'
|
|
15
|
+
const ATTACHMENT_STORAGE_BINDING = 'DSH_EDGE_ATTACHMENT_STORAGE'
|
|
14
16
|
|
|
15
17
|
/** Render one mode-specific build configuration from an already parsed source object. */
|
|
16
18
|
export function renderParsedSourceModeWranglerConfig(mode, parsed, options = {}) {
|
|
@@ -34,6 +36,7 @@ export function renderParsedSourceModeWranglerConfig(mode, parsed, options = {})
|
|
|
34
36
|
config.main = resolve(root, parsed.main)
|
|
35
37
|
config.assets.directory = options.assetsDirectory ?? resolve(root, parsed.assets.directory)
|
|
36
38
|
config.minify = true
|
|
39
|
+
applyAttachmentStorage(config, mode, options.r2BucketName)
|
|
37
40
|
if (mode === 'direct') {
|
|
38
41
|
config.alias = {
|
|
39
42
|
...config.alias,
|
|
@@ -63,9 +66,34 @@ export function renderParsedPrebuiltModeWranglerConfig(mode, parsed, options = {
|
|
|
63
66
|
config.assets.directory = resolve(root, parsed.assets.directory)
|
|
64
67
|
config.no_bundle = true
|
|
65
68
|
config.find_additional_modules = false
|
|
69
|
+
applyAttachmentStorage(config, mode, options.r2BucketName)
|
|
66
70
|
return `${JSON.stringify(config, undefined, 2)}\n`
|
|
67
71
|
}
|
|
68
72
|
|
|
73
|
+
function applyAttachmentStorage(config, mode, bucketName) {
|
|
74
|
+
if (bucketName !== undefined && (typeof bucketName !== 'string' || bucketName.length === 0)) {
|
|
75
|
+
throw new Error('R2 attachment bucket name must be a non-empty string.')
|
|
76
|
+
}
|
|
77
|
+
const target = mode === 'direct' ? config : config.env?.isolated
|
|
78
|
+
if (!isRecord(target)) {
|
|
79
|
+
throw new Error('wrangler.jsonc must declare the isolated environment.')
|
|
80
|
+
}
|
|
81
|
+
if (target.vars !== undefined && !isRecord(target.vars)) {
|
|
82
|
+
throw new Error('wrangler.jsonc vars must be an object.')
|
|
83
|
+
}
|
|
84
|
+
target.vars = {
|
|
85
|
+
...target.vars,
|
|
86
|
+
[ATTACHMENT_STORAGE_BINDING]: bucketName === undefined ? 'temporary-do' : 'private-r2',
|
|
87
|
+
}
|
|
88
|
+
if (bucketName === undefined) return
|
|
89
|
+
const binding = [{ binding: ATTACHMENT_BINDING, bucket_name: bucketName }]
|
|
90
|
+
if (mode === 'direct') {
|
|
91
|
+
config.r2_buckets = binding
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
target.r2_buckets = binding
|
|
95
|
+
}
|
|
96
|
+
|
|
69
97
|
/** Return the released entrypoint for a runtime mode. */
|
|
70
98
|
export function workerArtifactPath(mode, options = {}) {
|
|
71
99
|
requireRuntimeMode(mode)
|