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.
Files changed (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +183 -0
  4. package/README.zh.md +183 -0
  5. package/THIRD_PARTY_NOTICES.md +199 -0
  6. package/dist/_headers +3 -0
  7. package/dist/assets/fonts/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  8. package/dist/assets/fonts/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  9. package/dist/assets/fonts/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  10. package/dist/assets/fonts/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  11. package/dist/assets/fonts/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  12. package/dist/assets/fonts/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  13. package/dist/assets/fonts/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  14. package/dist/assets/fonts/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  15. package/dist/assets/fonts/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  16. package/dist/assets/fonts/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  17. package/dist/assets/fonts/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  18. package/dist/assets/fonts/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  19. package/dist/assets/fonts/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  20. package/dist/assets/fonts/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  21. package/dist/assets/fonts/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  22. package/dist/assets/fonts/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  23. package/dist/assets/fonts/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  24. package/dist/assets/fonts/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  25. package/dist/assets/fonts/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  26. package/dist/assets/fonts/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  27. package/dist/assets/fonts/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  28. package/dist/assets/fonts/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  29. package/dist/assets/fonts/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  30. package/dist/assets/fonts/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  31. package/dist/assets/fonts/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  32. package/dist/assets/fonts/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  33. package/dist/assets/fonts/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  34. package/dist/assets/fonts/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  35. package/dist/assets/fonts/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  36. package/dist/assets/fonts/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  37. package/dist/assets/fonts/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  38. package/dist/assets/fonts/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  39. package/dist/assets/fonts/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  40. package/dist/assets/fonts/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  41. package/dist/assets/fonts/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  42. package/dist/assets/fonts/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  43. package/dist/assets/fonts/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  44. package/dist/assets/fonts/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  45. package/dist/assets/fonts/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  46. package/dist/assets/fonts/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  47. package/dist/assets/fonts/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  48. package/dist/assets/fonts/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  49. package/dist/assets/fonts/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  50. package/dist/assets/fonts/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  51. package/dist/assets/fonts/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  52. package/dist/assets/fonts/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  53. package/dist/assets/fonts/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  54. package/dist/assets/fonts/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  55. package/dist/assets/fonts/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  56. package/dist/assets/fonts/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  57. package/dist/assets/fonts/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  58. package/dist/assets/fonts/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  59. package/dist/assets/fonts/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  60. package/dist/assets/fonts/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  61. package/dist/assets/fonts/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  62. package/dist/assets/fonts/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  63. package/dist/assets/fonts/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  64. package/dist/assets/fonts/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  65. package/dist/assets/fonts/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  66. package/dist/assets/index-C-1AiF3k.js +112 -0
  67. package/dist/assets/index-CSGf6Qzd.css +1 -0
  68. package/dist/assets/langs/c-BIGW1oBm.js +2 -0
  69. package/dist/assets/langs/cpp-DIPi6g--.js +2 -0
  70. package/dist/assets/langs/csharp-DSvCPggb.js +2 -0
  71. package/dist/assets/langs/css-CLj8gQPS.js +2 -0
  72. package/dist/assets/langs/go-C27-OAKa.js +2 -0
  73. package/dist/assets/langs/html-CfGypltT.js +2 -0
  74. package/dist/assets/langs/ini-BEwlwnbL.js +2 -0
  75. package/dist/assets/langs/java-CylS5w8V.js +2 -0
  76. package/dist/assets/langs/kotlin-BdnUsdx6.js +2 -0
  77. package/dist/assets/langs/less-B1dDrJ26.js +2 -0
  78. package/dist/assets/langs/lua-BaeVxFsk.js +2 -0
  79. package/dist/assets/langs/markdown-Cvjx9yec.js +2 -0
  80. package/dist/assets/langs/mdx-Cmh6b_Ma.js +2 -0
  81. package/dist/assets/langs/php-D-uCvoST.js +2 -0
  82. package/dist/assets/langs/python-B6aJPvgy.js +2 -0
  83. package/dist/assets/langs/ruby-DEItuUFs.js +2 -0
  84. package/dist/assets/langs/rust-B1yitclQ.js +2 -0
  85. package/dist/assets/langs/scss-D5BDwBP9.js +2 -0
  86. package/dist/assets/langs/sql-CRqJ_cUM.js +2 -0
  87. package/dist/assets/langs/swift-D82vCrfD.js +2 -0
  88. package/dist/assets/langs/toml-vGWfd6FD.js +2 -0
  89. package/dist/assets/langs/xml-sdJ4AIDG.js +2 -0
  90. package/dist/assets/langs/yaml-Buea-lGh.js +2 -0
  91. package/dist/assets/vendor-Cjbwl5VI.js +411 -0
  92. package/dist/assets/vendor-CjyC-hUb.css +1 -0
  93. package/dist/favicon.svg +8 -0
  94. package/dist/index.html +35 -0
  95. package/dist/manifest.webmanifest +16 -0
  96. package/dist/plugins/@deepseek-ai/dsh-api-gateway/client.js +420 -0
  97. package/dist/plugins/@deepseek-ai/dsh-api-remotes/client.js +5937 -0
  98. package/dist/plugins/@deepseek-ai/dsh-client-connection/client.js +10207 -0
  99. package/dist/plugins/@deepseek-ai/dsh-client-locale/client.js +1231 -0
  100. package/dist/plugins/@deepseek-ai/dsh-client-modules/client.js +259 -0
  101. package/dist/plugins/@deepseek-ai/dsh-client-runtime/client.js +10539 -0
  102. package/dist/plugins/@deepseek-ai/dsh-client-ui-agent-preset/client.js +1725 -0
  103. package/dist/plugins/@deepseek-ai/dsh-client-ui-commands/client.js +1116 -0
  104. package/dist/plugins/@deepseek-ai/dsh-client-ui-conversation/client.js +9832 -0
  105. package/dist/plugins/@deepseek-ai/dsh-client-ui-deliverables/client.js +373 -0
  106. package/dist/plugins/@deepseek-ai/dsh-client-ui-edge/client.js +549 -0
  107. package/dist/plugins/@deepseek-ai/dsh-client-ui-input-trigger/client.js +859 -0
  108. package/dist/plugins/@deepseek-ai/dsh-client-ui-jobs/client.js +280 -0
  109. package/dist/plugins/@deepseek-ai/dsh-client-ui-layout/client.js +456 -0
  110. package/dist/plugins/@deepseek-ai/dsh-client-ui-model-selection/client.js +804 -0
  111. package/dist/plugins/@deepseek-ai/dsh-client-ui-permission-presets/client.js +461 -0
  112. package/dist/plugins/@deepseek-ai/dsh-client-ui-settings/client.js +254 -0
  113. package/dist/plugins/@deepseek-ai/dsh-client-ui-settings-general/client.js +609 -0
  114. package/dist/plugins/@deepseek-ai/dsh-client-ui-sidebar/client.js +292 -0
  115. package/dist/plugins/@deepseek-ai/dsh-client-ui-skill/client.js +327 -0
  116. package/dist/plugins/@deepseek-ai/dsh-client-ui-subagent/client.js +710 -0
  117. package/dist/plugins/@deepseek-ai/dsh-client-ui-theme/client.js +1312 -0
  118. package/dist/plugins/@deepseek-ai/dsh-client-ui-tool/client.js +1630 -0
  119. package/dist/plugins/@deepseek-ai/dsh-client-ui-trajectory/client.js +7370 -0
  120. package/dist/plugins/@deepseek-ai/dsh-client-ui-user-questions/client.js +700 -0
  121. package/dist/plugins/@deepseek-ai/dsh-client-ui-workflow-run/client.js +474 -0
  122. package/dist/plugins/@deepseek-ai/dsh-client-ui-workspace/client.js +2426 -0
  123. package/dist/plugins/@deepseek-ai/dsh-typert-registry/client.js +1372 -0
  124. package/package.json +97 -0
  125. package/scripts/assemble-web.mjs +230 -0
  126. package/scripts/bundle-size.d.mts +2 -0
  127. package/scripts/bundle-size.mjs +26 -0
  128. package/scripts/bundle.mjs +53 -0
  129. package/scripts/cli.d.mts +39 -0
  130. package/scripts/cli.mjs +496 -0
  131. package/scripts/install.d.mts +149 -0
  132. package/scripts/install.mjs +989 -0
  133. package/scripts/legal-files.mjs +24 -0
  134. package/scripts/verify-packed.mjs +32 -0
  135. package/scripts/wrangler-config.d.mts +18 -0
  136. package/scripts/wrangler-config.mjs +84 -0
  137. package/src/agent.ts +119 -0
  138. package/src/auth.ts +352 -0
  139. package/src/deepseek.ts +231 -0
  140. package/src/deployment.ts +147 -0
  141. package/src/direct-shell-core-empty.ts +10 -0
  142. package/src/direct-shell-protocol.ts +5 -0
  143. package/src/direct-shell.ts +341 -0
  144. package/src/do-session-persistence.ts +1064 -0
  145. package/src/edge-api.ts +796 -0
  146. package/src/edge-credentials.ts +56 -0
  147. package/src/edge-remotes.ts +41 -0
  148. package/src/edge-workspace-store.ts +339 -0
  149. package/src/http.ts +212 -0
  150. package/src/index.ts +196 -0
  151. package/src/instance.ts +1007 -0
  152. package/src/isolated-direct-shell-unavailable.ts +11 -0
  153. package/src/protocol.ts +39 -0
  154. package/src/release.ts +9 -0
  155. package/src/session-store.ts +1110 -0
  156. package/src/sse.ts +86 -0
  157. package/src/web-search.ts +38 -0
  158. package/src/workspace.ts +270 -0
  159. 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,2 @@
1
+ export function parseWranglerGzipBytes(output: string): number
2
+ export function requireGzipBudget(output: string, maxBytes: number): number
@@ -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>