dsh-edge 0.1.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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +183 -0
- package/README.zh.md +183 -0
- package/THIRD_PARTY_NOTICES.md +199 -0
- package/dist/_headers +3 -0
- package/dist/assets/fonts/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/fonts/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/fonts/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/fonts/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/fonts/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/fonts/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/fonts/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/fonts/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/fonts/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/fonts/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/fonts/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/fonts/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/fonts/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/fonts/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/fonts/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/fonts/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/fonts/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/fonts/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/index-C-1AiF3k.js +112 -0
- package/dist/assets/index-CSGf6Qzd.css +1 -0
- package/dist/assets/langs/c-BIGW1oBm.js +2 -0
- package/dist/assets/langs/cpp-DIPi6g--.js +2 -0
- package/dist/assets/langs/csharp-DSvCPggb.js +2 -0
- package/dist/assets/langs/css-CLj8gQPS.js +2 -0
- package/dist/assets/langs/go-C27-OAKa.js +2 -0
- package/dist/assets/langs/html-CfGypltT.js +2 -0
- package/dist/assets/langs/ini-BEwlwnbL.js +2 -0
- package/dist/assets/langs/java-CylS5w8V.js +2 -0
- package/dist/assets/langs/kotlin-BdnUsdx6.js +2 -0
- package/dist/assets/langs/less-B1dDrJ26.js +2 -0
- package/dist/assets/langs/lua-BaeVxFsk.js +2 -0
- package/dist/assets/langs/markdown-Cvjx9yec.js +2 -0
- package/dist/assets/langs/mdx-Cmh6b_Ma.js +2 -0
- package/dist/assets/langs/php-D-uCvoST.js +2 -0
- package/dist/assets/langs/python-B6aJPvgy.js +2 -0
- package/dist/assets/langs/ruby-DEItuUFs.js +2 -0
- package/dist/assets/langs/rust-B1yitclQ.js +2 -0
- package/dist/assets/langs/scss-D5BDwBP9.js +2 -0
- package/dist/assets/langs/sql-CRqJ_cUM.js +2 -0
- package/dist/assets/langs/swift-D82vCrfD.js +2 -0
- package/dist/assets/langs/toml-vGWfd6FD.js +2 -0
- package/dist/assets/langs/xml-sdJ4AIDG.js +2 -0
- package/dist/assets/langs/yaml-Buea-lGh.js +2 -0
- package/dist/assets/vendor-Cjbwl5VI.js +411 -0
- package/dist/assets/vendor-CjyC-hUb.css +1 -0
- package/dist/favicon.svg +8 -0
- package/dist/index.html +35 -0
- package/dist/manifest.webmanifest +16 -0
- package/dist/plugins/@deepseek-ai/dsh-api-gateway/client.js +420 -0
- package/dist/plugins/@deepseek-ai/dsh-api-remotes/client.js +5937 -0
- package/dist/plugins/@deepseek-ai/dsh-client-connection/client.js +10207 -0
- package/dist/plugins/@deepseek-ai/dsh-client-locale/client.js +1231 -0
- package/dist/plugins/@deepseek-ai/dsh-client-modules/client.js +259 -0
- package/dist/plugins/@deepseek-ai/dsh-client-runtime/client.js +10539 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-agent-preset/client.js +1725 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-commands/client.js +1116 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-conversation/client.js +9832 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-deliverables/client.js +373 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-edge/client.js +549 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-input-trigger/client.js +859 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-jobs/client.js +280 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-layout/client.js +456 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-model-selection/client.js +804 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-permission-presets/client.js +461 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-settings/client.js +254 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-settings-general/client.js +609 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-sidebar/client.js +292 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-skill/client.js +327 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-subagent/client.js +710 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-theme/client.js +1312 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-tool/client.js +1630 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-trajectory/client.js +7370 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-user-questions/client.js +700 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-workflow-run/client.js +474 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-workspace/client.js +2426 -0
- package/dist/plugins/@deepseek-ai/dsh-typert-registry/client.js +1372 -0
- package/package.json +97 -0
- package/scripts/assemble-web.mjs +230 -0
- package/scripts/bundle-size.d.mts +2 -0
- package/scripts/bundle-size.mjs +26 -0
- package/scripts/bundle.mjs +53 -0
- package/scripts/cli.d.mts +39 -0
- package/scripts/cli.mjs +496 -0
- package/scripts/install.d.mts +149 -0
- package/scripts/install.mjs +989 -0
- package/scripts/legal-files.mjs +24 -0
- package/scripts/verify-packed.mjs +32 -0
- package/scripts/wrangler-config.d.mts +18 -0
- package/scripts/wrangler-config.mjs +84 -0
- package/src/agent.ts +119 -0
- package/src/auth.ts +352 -0
- package/src/deepseek.ts +231 -0
- package/src/deployment.ts +147 -0
- package/src/direct-shell-core-empty.ts +10 -0
- package/src/direct-shell-protocol.ts +5 -0
- package/src/direct-shell.ts +341 -0
- package/src/do-session-persistence.ts +1064 -0
- package/src/edge-api.ts +796 -0
- package/src/edge-credentials.ts +56 -0
- package/src/edge-remotes.ts +41 -0
- package/src/edge-workspace-store.ts +339 -0
- package/src/http.ts +212 -0
- package/src/index.ts +196 -0
- package/src/instance.ts +1007 -0
- package/src/isolated-direct-shell-unavailable.ts +11 -0
- package/src/protocol.ts +39 -0
- package/src/release.ts +9 -0
- package/src/session-store.ts +1110 -0
- package/src/sse.ts +86 -0
- package/src/web-search.ts +38 -0
- package/src/workspace.ts +270 -0
- package/wrangler.jsonc +56 -0
package/package.json
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-edge",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Install and run DeepSeek Harness on Cloudflare Workers",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"dsh-edge": "scripts/cli.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"src",
|
|
13
|
+
"scripts/*.d.mts",
|
|
14
|
+
"scripts/*.mjs",
|
|
15
|
+
"wrangler.jsonc",
|
|
16
|
+
"README.md",
|
|
17
|
+
"README.zh.md",
|
|
18
|
+
"LICENSE",
|
|
19
|
+
"THIRD_PARTY_NOTICES.md"
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/pawaca/dsh-edge.git",
|
|
24
|
+
"directory": "apps/dsh-edge"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/pawaca/dsh-edge/tree/master/apps/dsh-edge#readme",
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/pawaca/dsh-edge/issues"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"deepseek",
|
|
32
|
+
"agent",
|
|
33
|
+
"cloudflare-workers",
|
|
34
|
+
"durable-objects"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=22.14.0"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"dshEdge": {
|
|
43
|
+
"upstreamVersion": "0.1.0-rc.7"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@clack/prompts": "1.7.0",
|
|
47
|
+
"@cloudflare/computer": "0.2.0",
|
|
48
|
+
"just-bash": "3.3.0",
|
|
49
|
+
"execa": "^10.0.0",
|
|
50
|
+
"jsonc-parser": "^3.3.1",
|
|
51
|
+
"wrangler": "4.123.0",
|
|
52
|
+
"@deepseek-ai/dsh-brand": "0.1.0-rc.7",
|
|
53
|
+
"@deepseek-ai/cordis": "4.0.1",
|
|
54
|
+
"@deepseek-ai/dsh-anonymous-user-id": "0.1.0-rc.7",
|
|
55
|
+
"@deepseek-ai/dsh-host-apiproxy": "0.1.0-rc.7",
|
|
56
|
+
"@deepseek-ai/dsh-credentials": "0.1.0-rc.7",
|
|
57
|
+
"@deepseek-ai/dsh-launch-environment": "0.1.0-rc.7",
|
|
58
|
+
"@deepseek-ai/dsh-agent-loop": "0.1.0-rc.7",
|
|
59
|
+
"@deepseek-ai/dsh-llm": "0.1.0-rc.7",
|
|
60
|
+
"@deepseek-ai/dsh-agent": "0.1.0-rc.7",
|
|
61
|
+
"@deepseek-ai/dsh-llm-deepseek": "0.1.0-rc.7",
|
|
62
|
+
"@deepseek-ai/dsh-session": "0.1.0-rc.7",
|
|
63
|
+
"@deepseek-ai/dsh-session-persistence": "0.1.0-rc.7",
|
|
64
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.0-rc.7",
|
|
65
|
+
"@deepseek-ai/dsh-session-query": "0.1.0-rc.7",
|
|
66
|
+
"@deepseek-ai/dsh-tool-call-timeout-policy": "0.1.0-rc.7",
|
|
67
|
+
"@deepseek-ai/dsh-settings": "0.1.0-rc.7",
|
|
68
|
+
"@deepseek-ai/dsh-tools": "0.1.0-rc.7",
|
|
69
|
+
"@deepseek-ai/dsh-tool-web": "0.1.0-rc.7",
|
|
70
|
+
"@deepseek-ai/dsh-session-title": "0.1.0-rc.7",
|
|
71
|
+
"@deepseek-ai/dsh-web": "0.1.0-rc.7",
|
|
72
|
+
"@deepseek-ai/dsh-web-search-deepseek": "0.1.0-rc.7"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@cloudflare/workers-types": "5.20260812.1",
|
|
76
|
+
"playwright": "^1.49.0",
|
|
77
|
+
"typescript": "^6.0.3",
|
|
78
|
+
"vitest": "^4.1.8",
|
|
79
|
+
"ws": "^8.21.0"
|
|
80
|
+
},
|
|
81
|
+
"scripts": {
|
|
82
|
+
"bundle:direct": "pnpm run web:build && node scripts/bundle.mjs direct",
|
|
83
|
+
"bundle:isolated": "pnpm run web:build && node scripts/bundle.mjs isolated",
|
|
84
|
+
"dev": "pnpm run web:build && wrangler dev --env=\"\"",
|
|
85
|
+
"dev:isolated": "pnpm run web:build && wrangler dev --env isolated",
|
|
86
|
+
"dsh-edge": "node scripts/cli.mjs",
|
|
87
|
+
"example:install": "node examples/install-keyless.mjs",
|
|
88
|
+
"install:cloudflare": "pnpm run web:build && node scripts/cli.mjs install",
|
|
89
|
+
"pack:verify": "node scripts/verify-packed.mjs",
|
|
90
|
+
"web:assemble": "node scripts/assemble-web.mjs",
|
|
91
|
+
"web:build": "pnpm --workspace-root run build:lib:host && pnpm --workspace-root run build:lib:client && pnpm --workspace-root run build:web && pnpm run web:assemble",
|
|
92
|
+
"test": "vitest run --root ../.. apps/dsh-edge/tests/agent.spec.ts apps/dsh-edge/tests/auth.spec.ts apps/dsh-edge/tests/cli.spec.ts apps/dsh-edge/tests/deployment-health.spec.ts apps/dsh-edge/tests/deployment.spec.ts apps/dsh-edge/tests/direct-shell.spec.ts apps/dsh-edge/tests/do-session-persistence.spec.ts apps/dsh-edge/tests/edge-api.spec.ts apps/dsh-edge/tests/edge-credentials.spec.ts apps/dsh-edge/tests/edge-remotes.spec.ts apps/dsh-edge/tests/installer.spec.ts apps/dsh-edge/tests/sse.spec.ts apps/dsh-edge/tests/web-search.spec.ts apps/dsh-edge/tests/workspace.spec.ts",
|
|
93
|
+
"test:integration": "pnpm run web:build && node tests/session.integration.mjs",
|
|
94
|
+
"test:snapshot": "vitest run --config vitest.snapshot.config.ts",
|
|
95
|
+
"typecheck": "tsc --noEmit"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/** Assemble the upstream Web shell and client-plugin graph as Worker assets. */
|
|
2
|
+
|
|
3
|
+
import { createHash } from 'node:crypto'
|
|
4
|
+
import {
|
|
5
|
+
cp,
|
|
6
|
+
mkdir,
|
|
7
|
+
readFile,
|
|
8
|
+
readdir,
|
|
9
|
+
rm,
|
|
10
|
+
stat,
|
|
11
|
+
writeFile,
|
|
12
|
+
} from 'node:fs/promises'
|
|
13
|
+
import { dirname, join, relative, resolve } from 'node:path'
|
|
14
|
+
import { fileURLToPath } from 'node:url'
|
|
15
|
+
|
|
16
|
+
const appRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
17
|
+
const repoRoot = resolve(appRoot, '../..')
|
|
18
|
+
const shellDist = join(repoRoot, 'apps/web/dist')
|
|
19
|
+
const outputRoot = join(appRoot, 'dist')
|
|
20
|
+
const deploymentPatches = [
|
|
21
|
+
join(repoRoot, 'packages/bundle/base/cordis.patch.yml'),
|
|
22
|
+
join(repoRoot, 'packages/bundle/web-app/cordis.patch.yml'),
|
|
23
|
+
]
|
|
24
|
+
// Deployment-owned browser features use the same client-plugin contract but
|
|
25
|
+
// intentionally stay out of upstream's generic Web composition.
|
|
26
|
+
const edgeClientPackages = ['@deepseek-ai/dsh-client-ui-edge']
|
|
27
|
+
|
|
28
|
+
// HMR is useful only when the upstream Node host also runs its bundle watcher.
|
|
29
|
+
// Edge deploys immutable, content-addressed static assets instead.
|
|
30
|
+
const edgeExcludedPackages = new Set([
|
|
31
|
+
// Requires the upstream Node-side bundle watcher.
|
|
32
|
+
'@deepseek-ai/dsh-client-hmr',
|
|
33
|
+
// These surfaces depend on generated Host remotes or writable Host settings
|
|
34
|
+
// that the minimal Edge ApiProxy does not advertise yet. Keeping them out of
|
|
35
|
+
// the graph is capability negotiation at assembly time; their upstream code
|
|
36
|
+
// remains unchanged and can be restored with the matching server domain.
|
|
37
|
+
'@deepseek-ai/dsh-cordis-client-runner',
|
|
38
|
+
'@deepseek-ai/dsh-client-ui-cordis',
|
|
39
|
+
'@deepseek-ai/dsh-client-ui-goal',
|
|
40
|
+
'@deepseek-ai/dsh-client-ui-message-feedback',
|
|
41
|
+
'@deepseek-ai/dsh-client-ui-plan',
|
|
42
|
+
'@deepseek-ai/dsh-client-ui-settings-models',
|
|
43
|
+
'@deepseek-ai/dsh-client-ui-settings-plugin-inventory',
|
|
44
|
+
'@deepseek-ai/dsh-client-ui-settings-plugins',
|
|
45
|
+
// Its header action calls the session.export endpoint, which Edge does not
|
|
46
|
+
// implement. Keep the unavailable action out of the assembled browser.
|
|
47
|
+
'@deepseek-ai/dsh-session-log-export',
|
|
48
|
+
])
|
|
49
|
+
const shellStaticPackages = new Set(['@deepseek-ai/dsh-client-ui-primitives'])
|
|
50
|
+
const assetSecurityHeaders = `/*
|
|
51
|
+
Content-Security-Policy: frame-ancestors 'none'
|
|
52
|
+
X-Frame-Options: DENY
|
|
53
|
+
`
|
|
54
|
+
|
|
55
|
+
function shortHash(input) {
|
|
56
|
+
return createHash('sha1').update(input).digest('hex').slice(0, 12)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function isDirectory(path) {
|
|
60
|
+
try {
|
|
61
|
+
return (await stat(path)).isDirectory()
|
|
62
|
+
} catch {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function findPackageJsonFiles(root) {
|
|
68
|
+
const found = []
|
|
69
|
+
const visit = async (directory) => {
|
|
70
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
71
|
+
if (entry.name === 'node_modules' || entry.name === 'lib') continue
|
|
72
|
+
const path = join(directory, entry.name)
|
|
73
|
+
if (entry.isDirectory()) {
|
|
74
|
+
await visit(path)
|
|
75
|
+
} else if (entry.name === 'package.json') {
|
|
76
|
+
found.push(path)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
await visit(root)
|
|
81
|
+
return found
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function removeSourceMaps(root) {
|
|
85
|
+
for (const entry of await readdir(root, { withFileTypes: true })) {
|
|
86
|
+
const path = join(root, entry.name)
|
|
87
|
+
if (entry.isDirectory()) {
|
|
88
|
+
await removeSourceMaps(path)
|
|
89
|
+
} else if (entry.name.endsWith('.map')) {
|
|
90
|
+
await rm(path)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function deploymentPackageNames(patch) {
|
|
96
|
+
const names = []
|
|
97
|
+
for (const match of patch.matchAll(/^\s+name:\s+'([^']+)'\s*$/gm)) {
|
|
98
|
+
if (!edgeExcludedPackages.has(match[1])) names.push(match[1])
|
|
99
|
+
}
|
|
100
|
+
return names
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function clientBundleRelativePath(pkg) {
|
|
104
|
+
const client = pkg.exports?.['./client']
|
|
105
|
+
if (typeof client === 'string') return client
|
|
106
|
+
if (client !== null && typeof client === 'object' && typeof client.default === 'string') {
|
|
107
|
+
return client.default
|
|
108
|
+
}
|
|
109
|
+
throw new Error(`${pkg.name} is in the Web roster but exports no ./client bundle.`)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function injectBootManifest(html, graph) {
|
|
113
|
+
const json = JSON.stringify(graph).replaceAll('<', '\\u003c')
|
|
114
|
+
const script = `<script>window.__DSH_BOOT__ = ${json}</script>`
|
|
115
|
+
const head = html.indexOf('<head>')
|
|
116
|
+
return head === -1
|
|
117
|
+
? `${script}${html}`
|
|
118
|
+
: `${html.slice(0, head + 6)}${script}${html.slice(head + 6)}`
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function injectOwnerSessionGuard(html) {
|
|
122
|
+
const script = `<script>(() => {
|
|
123
|
+
const originalFetch = window.fetch.bind(window)
|
|
124
|
+
let redirecting = false
|
|
125
|
+
window.fetch = async (...args) => {
|
|
126
|
+
const input = args[0]
|
|
127
|
+
const href = input instanceof Request ? input.url : String(input)
|
|
128
|
+
const target = new URL(href, window.location.href)
|
|
129
|
+
const response = await originalFetch(...args)
|
|
130
|
+
if (!redirecting
|
|
131
|
+
&& response.status === 401
|
|
132
|
+
&& response.headers.get('www-authenticate') === 'DshEdgeOwner'
|
|
133
|
+
&& target.origin === window.location.origin
|
|
134
|
+
&& target.pathname.startsWith('/api/')) {
|
|
135
|
+
redirecting = true
|
|
136
|
+
window.location.replace('/login')
|
|
137
|
+
}
|
|
138
|
+
return response
|
|
139
|
+
}
|
|
140
|
+
})()</script>`
|
|
141
|
+
const head = html.indexOf('<head>')
|
|
142
|
+
return head === -1
|
|
143
|
+
? `${script}${html}`
|
|
144
|
+
: `${html.slice(0, head + 6)}${script}${html.slice(head + 6)}`
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function copyBundle(pkgPath, pkg, entries) {
|
|
148
|
+
const declaration = pkg.dsh?.client
|
|
149
|
+
if (declaration?.platform !== 'web') {
|
|
150
|
+
throw new Error(`${pkg.name} is in the upstream Web roster but has no web dsh.client declaration.`)
|
|
151
|
+
}
|
|
152
|
+
const source = resolve(dirname(pkgPath), clientBundleRelativePath(pkg))
|
|
153
|
+
const contents = await readFile(source)
|
|
154
|
+
const rev = shortHash(contents)
|
|
155
|
+
const destination = join(outputRoot, 'plugins', pkg.name, 'client.js')
|
|
156
|
+
await mkdir(dirname(destination), { recursive: true })
|
|
157
|
+
await writeFile(destination, contents)
|
|
158
|
+
|
|
159
|
+
entries.push({
|
|
160
|
+
id: pkg.name,
|
|
161
|
+
url: `/plugins/${pkg.name}/client.js?rev=${rev}`,
|
|
162
|
+
rev,
|
|
163
|
+
...(Array.isArray(declaration.inject) ? { inject: declaration.inject } : {}),
|
|
164
|
+
...(declaration.immediately === true ? { immediately: true } : {}),
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function main() {
|
|
169
|
+
if (!await isDirectory(shellDist)) {
|
|
170
|
+
throw new Error('Upstream Web shell is not built. Run `pnpm --workspace-root run build:web` first.')
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const packagesByName = new Map()
|
|
174
|
+
for (const pkgPath of await findPackageJsonFiles(join(repoRoot, 'packages'))) {
|
|
175
|
+
const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
|
|
176
|
+
if (typeof pkg.name === 'string') packagesByName.set(pkg.name, { path: pkgPath, pkg })
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const declared = []
|
|
180
|
+
for (const patchPath of deploymentPatches) {
|
|
181
|
+
declared.push(...deploymentPackageNames(await readFile(patchPath, 'utf8')))
|
|
182
|
+
}
|
|
183
|
+
declared.push(...edgeClientPackages)
|
|
184
|
+
const roster = []
|
|
185
|
+
const seen = new Set()
|
|
186
|
+
for (const name of declared) {
|
|
187
|
+
if (seen.has(name)) continue
|
|
188
|
+
const record = packagesByName.get(name)
|
|
189
|
+
// Vendor entries and package subpaths have no workspace package root; only
|
|
190
|
+
// workspace packages can declare the client bundles assembled here.
|
|
191
|
+
if (record === undefined) continue
|
|
192
|
+
if (record.pkg.dsh?.client?.platform !== 'web') continue
|
|
193
|
+
seen.add(name)
|
|
194
|
+
roster.push(name)
|
|
195
|
+
}
|
|
196
|
+
await rm(outputRoot, { recursive: true, force: true })
|
|
197
|
+
await cp(shellDist, outputRoot, { recursive: true })
|
|
198
|
+
// Match the published upstream Web package: production assets do not ship
|
|
199
|
+
// source maps, keeping the Worker upload compact without changing runtime JS.
|
|
200
|
+
await removeSourceMaps(outputRoot)
|
|
201
|
+
|
|
202
|
+
const entries = []
|
|
203
|
+
for (const name of roster) {
|
|
204
|
+
const record = packagesByName.get(name)
|
|
205
|
+
if (record === undefined) throw new Error(`Cannot resolve upstream Web package ${name}.`)
|
|
206
|
+
await copyBundle(record.path, record.pkg, entries)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const selected = new Set(entries.map(entry => entry.id))
|
|
210
|
+
for (const entry of entries) {
|
|
211
|
+
for (const dependency of entry.inject ?? []) {
|
|
212
|
+
if (!selected.has(dependency) && !shellStaticPackages.has(dependency)) {
|
|
213
|
+
throw new Error(`${entry.id} injects missing Edge Web package ${dependency}.`)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const graph = { rev: shortHash(JSON.stringify(entries)), entries }
|
|
219
|
+
const indexPath = join(outputRoot, 'index.html')
|
|
220
|
+
const index = await readFile(indexPath, 'utf8')
|
|
221
|
+
await writeFile(indexPath, injectOwnerSessionGuard(injectBootManifest(index, graph)))
|
|
222
|
+
// Cloudflare may serve index.html and SPA fallbacks without invoking the
|
|
223
|
+
// Worker. Apply the framing boundary at the asset layer so every alias of
|
|
224
|
+
// the executable shell receives the same policy while immutable assets can
|
|
225
|
+
// still bypass Worker execution.
|
|
226
|
+
await writeFile(join(outputRoot, '_headers'), assetSecurityHeaders)
|
|
227
|
+
console.log(`Assembled ${entries.length} upstream client plugins in ${relative(repoRoot, outputRoot)} (rev ${graph.rev}).`)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
await main()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const SIZE_UNITS = Object.freeze({
|
|
2
|
+
B: 1,
|
|
3
|
+
KiB: 1024,
|
|
4
|
+
MiB: 1024 * 1024,
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
/** Extract Wrangler's total compressed upload size in bytes. */
|
|
8
|
+
export function parseWranglerGzipBytes(output) {
|
|
9
|
+
const match = /Total Upload:[^\n/]+\/ gzip:\s*([0-9]+(?:\.[0-9]+)?)\s*(B|KiB|MiB)/u.exec(output)
|
|
10
|
+
if (match === null) throw new Error('Wrangler did not report a compressed upload size.')
|
|
11
|
+
return Math.ceil(Number(match[1]) * SIZE_UNITS[match[2]])
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Reject a compressed Worker artifact above the repository budget. */
|
|
15
|
+
export function requireGzipBudget(output, maxBytes) {
|
|
16
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
|
|
17
|
+
throw new TypeError('maxBytes must be a positive safe integer.')
|
|
18
|
+
}
|
|
19
|
+
const actualBytes = parseWranglerGzipBytes(output)
|
|
20
|
+
if (actualBytes > maxBytes) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Direct Worker gzip size ${actualBytes} bytes exceeds the ${maxBytes}-byte budget.`,
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
return actualBytes
|
|
26
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import { mkdtemp, rm } from 'node:fs/promises'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import { execa } from 'execa'
|
|
7
|
+
import { requireGzipBudget } from './bundle-size.mjs'
|
|
8
|
+
import { writeModeWranglerConfig } from './wrangler-config.mjs'
|
|
9
|
+
|
|
10
|
+
const DIRECT_GZIP_BUDGET_BYTES = 900 * 1024
|
|
11
|
+
const appDirectory = fileURLToPath(new URL('..', import.meta.url))
|
|
12
|
+
const require = createRequire(import.meta.url)
|
|
13
|
+
const wranglerCli = require.resolve('wrangler')
|
|
14
|
+
const mode = process.argv[2]
|
|
15
|
+
|
|
16
|
+
if (mode !== 'direct' && mode !== 'isolated') {
|
|
17
|
+
process.stderr.write('Usage: node scripts/bundle.mjs <direct|isolated>\n')
|
|
18
|
+
process.exitCode = 2
|
|
19
|
+
} else {
|
|
20
|
+
const directory = await mkdtemp(join(tmpdir(), `dsh-edge-${mode}-bundle-`))
|
|
21
|
+
try {
|
|
22
|
+
const configFile = join(directory, 'wrangler.json')
|
|
23
|
+
const outputDirectory = join(directory, 'worker')
|
|
24
|
+
await writeModeWranglerConfig(mode, configFile)
|
|
25
|
+
const result = await execa(process.execPath, [
|
|
26
|
+
wranglerCli,
|
|
27
|
+
'deploy',
|
|
28
|
+
'--config', configFile,
|
|
29
|
+
'--env', mode === 'direct' ? '' : 'isolated',
|
|
30
|
+
'--dry-run',
|
|
31
|
+
'--outdir', outputDirectory,
|
|
32
|
+
], {
|
|
33
|
+
cwd: appDirectory,
|
|
34
|
+
reject: false,
|
|
35
|
+
all: true,
|
|
36
|
+
})
|
|
37
|
+
const output = result.all ?? `${result.stdout}\n${result.stderr}`
|
|
38
|
+
process.stdout.write(output.endsWith('\n') ? output : `${output}\n`)
|
|
39
|
+
if (result.exitCode !== 0) {
|
|
40
|
+
process.exitCode = result.exitCode
|
|
41
|
+
} else if (mode === 'direct') {
|
|
42
|
+
const bytes = requireGzipBudget(output, DIRECT_GZIP_BUDGET_BYTES)
|
|
43
|
+
process.stdout.write(
|
|
44
|
+
`Direct Worker gzip budget: ${bytes}/${DIRECT_GZIP_BUDGET_BYTES} bytes.\n`,
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
49
|
+
process.exitCode = 1
|
|
50
|
+
} finally {
|
|
51
|
+
await rm(directory, { recursive: true, force: true })
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { InstallerUi } from './install.mjs'
|
|
2
|
+
|
|
3
|
+
export type InstallerInterruptSignal = 'SIGHUP' | 'SIGINT' | 'SIGTERM'
|
|
4
|
+
|
|
5
|
+
export class InstallInterruptedError extends Error {
|
|
6
|
+
readonly exitCode: number
|
|
7
|
+
readonly signal: InstallerInterruptSignal
|
|
8
|
+
outputFailureStream?: 'stderr' | 'stdout'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type InstallerCommand = 'install' | 'upgrade'
|
|
12
|
+
export function parseCommand(args: string[]): InstallerCommand | 'help' | 'version'
|
|
13
|
+
export function createInstallerUi(
|
|
14
|
+
clack?: typeof import('@clack/prompts'),
|
|
15
|
+
signal?: AbortSignal,
|
|
16
|
+
writeDescriptor?: (fd: number, value: string) => number,
|
|
17
|
+
output?: import('node:stream').Writable,
|
|
18
|
+
writeRecovery?: (failedStream: 'stderr' | 'stdout', value: string) => Promise<boolean>,
|
|
19
|
+
command?: InstallerCommand,
|
|
20
|
+
): InstallerUi
|
|
21
|
+
export function runInstaller(options?: {
|
|
22
|
+
command?: InstallerCommand
|
|
23
|
+
install?: (options: { command: InstallerCommand; ui: InstallerUi; signal: AbortSignal }) => Promise<unknown>
|
|
24
|
+
installEdgeImpl?: typeof import('./install.mjs').installEdge
|
|
25
|
+
installerUi?: InstallerUi
|
|
26
|
+
outputDrainTimeoutMs?: number
|
|
27
|
+
uiFactory?: (
|
|
28
|
+
signal: AbortSignal,
|
|
29
|
+
output: import('node:stream').Writable,
|
|
30
|
+
writeRecovery: (failedStream: 'stderr' | 'stdout', value: string) => Promise<boolean>,
|
|
31
|
+
) => InstallerUi
|
|
32
|
+
runtimeProcess?: {
|
|
33
|
+
on(signal: InstallerInterruptSignal, handler: () => void): unknown
|
|
34
|
+
removeListener(signal: InstallerInterruptSignal, handler: () => void): unknown
|
|
35
|
+
}
|
|
36
|
+
stderr?: import('node:stream').Writable
|
|
37
|
+
stdout?: import('node:stream').Writable
|
|
38
|
+
wranglerRunner?: typeof import('./install.mjs').executeWrangler
|
|
39
|
+
}): Promise<unknown>
|