@changfenhuang/dsh-genui 0.9.1

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 (106) hide show
  1. package/CHANGELOG.md +367 -0
  2. package/LICENSE +21 -0
  3. package/README.md +261 -0
  4. package/README.zh-CN.md +261 -0
  5. package/SKILL.md +115 -0
  6. package/cordis.patch.yml +9 -0
  7. package/demo-prompts.md +118 -0
  8. package/lib/assets/echarts.js +64 -0
  9. package/lib/assets/mermaid.js +3591 -0
  10. package/lib/assets/three.js +3844 -0
  11. package/lib/client.js +5 -0
  12. package/lib/index.js +1895 -0
  13. package/lib/invariant.js +19 -0
  14. package/lib/types/client/EChartNode.d.ts +4 -0
  15. package/lib/types/client/ErrorBoundary.d.ts +30 -0
  16. package/lib/types/client/GenuiBlock.d.ts +7 -0
  17. package/lib/types/client/PlotBlock.d.ts +44 -0
  18. package/lib/types/client/action-context.d.ts +7 -0
  19. package/lib/types/client/asset-echarts.d.ts +15 -0
  20. package/lib/types/client/asset-loader.d.ts +31 -0
  21. package/lib/types/client/asset-mermaid.d.ts +1 -0
  22. package/lib/types/client/asset-three.d.ts +1 -0
  23. package/lib/types/client/blocks/advanced.d.ts +104 -0
  24. package/lib/types/client/blocks/basic.d.ts +27 -0
  25. package/lib/types/client/blocks/charts.d.ts +38 -0
  26. package/lib/types/client/blocks/diagram/geometry.d.ts +73 -0
  27. package/lib/types/client/blocks/diagram/index.d.ts +5 -0
  28. package/lib/types/client/blocks/diagram/layout.d.ts +41 -0
  29. package/lib/types/client/blocks/diagram/theme.d.ts +35 -0
  30. package/lib/types/client/blocks/forms.d.ts +83 -0
  31. package/lib/types/client/blocks/render-node.d.ts +10 -0
  32. package/lib/types/client/blocks/state.d.ts +58 -0
  33. package/lib/types/client/dom-fence.d.ts +14 -0
  34. package/lib/types/client/echarts-lazy.d.ts +17 -0
  35. package/lib/types/client/fence-render.d.ts +58 -0
  36. package/lib/types/client/gallery.d.ts +9 -0
  37. package/lib/types/client/guard.d.ts +126 -0
  38. package/lib/types/client/index.d.ts +36 -0
  39. package/lib/types/client/interaction-store.d.ts +43 -0
  40. package/lib/types/client/mermaid-core.d.ts +8 -0
  41. package/lib/types/client/mermaid-lazy.d.ts +9 -0
  42. package/lib/types/client/mermaid-safe.d.ts +52 -0
  43. package/lib/types/client/panel-command.d.ts +29 -0
  44. package/lib/types/client/panel-store.d.ts +94 -0
  45. package/lib/types/client/panel.d.ts +15 -0
  46. package/lib/types/client/parse-partial.d.ts +37 -0
  47. package/lib/types/client/safe-math.d.ts +43 -0
  48. package/lib/types/client/scene3d-core.d.ts +21 -0
  49. package/lib/types/client/scene3d-lazy.d.ts +8 -0
  50. package/lib/types/client/spec.d.ts +610 -0
  51. package/lib/types/client/toolview.d.ts +7 -0
  52. package/lib/types/index.d.ts +1 -0
  53. package/lib/types/plugin/index.d.ts +25 -0
  54. package/lib/types/plugin/invariant.d.ts +15 -0
  55. package/lib/types/plugin/tool.d.ts +27 -0
  56. package/lib/types/shared/fence-repair.d.ts +67 -0
  57. package/package.json +121 -0
  58. package/src/client/EChartNode.tsx +242 -0
  59. package/src/client/ErrorBoundary.tsx +66 -0
  60. package/src/client/GenuiBlock.module.css +1263 -0
  61. package/src/client/GenuiBlock.tsx +166 -0
  62. package/src/client/PlotBlock.module.css +155 -0
  63. package/src/client/PlotBlock.tsx +431 -0
  64. package/src/client/action-context.ts +32 -0
  65. package/src/client/asset-echarts.ts +25 -0
  66. package/src/client/asset-loader.ts +79 -0
  67. package/src/client/asset-mermaid.ts +13 -0
  68. package/src/client/asset-three.ts +12 -0
  69. package/src/client/blocks/advanced.tsx +435 -0
  70. package/src/client/blocks/basic.tsx +116 -0
  71. package/src/client/blocks/charts.tsx +319 -0
  72. package/src/client/blocks/diagram/geometry.ts +151 -0
  73. package/src/client/blocks/diagram/index.tsx +284 -0
  74. package/src/client/blocks/diagram/layout.ts +199 -0
  75. package/src/client/blocks/diagram/theme.ts +108 -0
  76. package/src/client/blocks/forms.tsx +523 -0
  77. package/src/client/blocks/render-node.tsx +260 -0
  78. package/src/client/blocks/state.ts +61 -0
  79. package/src/client/dom-fence.tsx +641 -0
  80. package/src/client/echarts-lazy.ts +35 -0
  81. package/src/client/fence-render.tsx +230 -0
  82. package/src/client/gallery.ts +128 -0
  83. package/src/client/guard.ts +1450 -0
  84. package/src/client/index.tsx +195 -0
  85. package/src/client/interaction-store.ts +122 -0
  86. package/src/client/mermaid-core.ts +121 -0
  87. package/src/client/mermaid-lazy.ts +31 -0
  88. package/src/client/mermaid-safe.ts +122 -0
  89. package/src/client/panel-command.ts +119 -0
  90. package/src/client/panel-store.ts +471 -0
  91. package/src/client/panel.tsx +185 -0
  92. package/src/client/parse-partial.ts +162 -0
  93. package/src/client/safe-math.ts +270 -0
  94. package/src/client/scene3d-core.ts +192 -0
  95. package/src/client/scene3d-lazy.ts +26 -0
  96. package/src/client/spec.ts +767 -0
  97. package/src/client/toolview.tsx +66 -0
  98. package/src/css-modules.d.ts +5 -0
  99. package/src/index.ts +12 -0
  100. package/src/plugin/index.ts +173 -0
  101. package/src/plugin/invariant.ts +30 -0
  102. package/src/plugin/node-shims.d.ts +31 -0
  103. package/src/plugin/tool.ts +330 -0
  104. package/src/shared/fence-repair.ts +244 -0
  105. package/tsconfig.json +35 -0
  106. package/tsdown.config.ts +193 -0
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Shared fence-body JSON repair — pure string functions, no DOM, no I/O.
3
+ * Used by BOTH the client fence renderer (tier-1/tier-2 auto-repair before
4
+ * rendering) and the node-side validate_dsh_ui tool (which returns the
5
+ * repaired JSON to the model instead of making it re-author the fix).
6
+ *
7
+ * Two tiers, deliberately gated differently by the callers:
8
+ * - Tier-1 (`repairFenceJson`): heals the most common model JSON typos that
9
+ * do NOT change the body's structure — unescaped half-width quotes inside
10
+ * string values and trailing commas. Safe at any time (streaming included),
11
+ * adopted only when the WHOLE body parses afterwards.
12
+ * - Tier-2 (`completeFenceJson`): heals structural incompleteness — missing
13
+ * closing quotes/brackets — by appending the missing terminators, and
14
+ * skips mismatched closers (a `]` mistyped as `}`, duplicated terminators).
15
+ * SETTLED MESSAGES ONLY: a streaming half must never be adopted as a
16
+ * finished prefix.
17
+ * @module @changfenhuang/dsh-genui/shared/fence-repair
18
+ */
19
+
20
+ /** A fence body counts as complete when it parses as a whole JSON value. */
21
+ export function isCompleteJson(raw: string): boolean {
22
+ try {
23
+ JSON.parse(raw)
24
+ return true
25
+ } catch {
26
+ return false
27
+ }
28
+ }
29
+
30
+ /** Short human-readable reason for a body that fails whole-JSON parsing, or
31
+ * null when it parses. Positions come from the host's JSON.parse error. */
32
+ export function describeJsonFailure(raw: string): string | null {
33
+ try {
34
+ JSON.parse(raw)
35
+ return null
36
+ } catch (err) {
37
+ const msg = err instanceof Error ? err.message : String(err)
38
+ const pos = msg.match(/position (\d+)/i)
39
+ const where = pos !== null ? `(字符 ${pos[1]} 附近)` : ''
40
+ return `${where}${msg.slice(0, 140)}`
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Tier-1 repair — SAFE AT ANY TIME (streaming included): heals the most
46
+ * common model JSON typos that do NOT change the body's structure, and only
47
+ * when the whole body parses afterwards (so a still-growing streaming half
48
+ * can never be adopted):
49
+ *
50
+ * 1. Unescaped half-width `"` inside a string value — Chinese text quoted
51
+ * with ASCII quotes (e.g. `对"别名路径"判定失败`), which makes JSON.parse
52
+ * fail near that quote with "Expected ',' or ']'...".
53
+ * 2. Trailing commas before `}` / `]` or at end of input.
54
+ *
55
+ * The state-machine scan walks the raw body tracking string-open state:
56
+ * - inside a string, a quote whose next non-space char is NOT one of `, ] } :`
57
+ * (or end of input) cannot legally close the string → escape it as `\"`;
58
+ * - a `,` whose next non-space char is `}` / `]` / end of input is a trailing
59
+ * comma → drop it.
60
+ *
61
+ * Returns `{ text, repairs }` on success, or null when nothing needed fixing
62
+ * or the body still does not parse (callers fall through to tier-2 / banner).
63
+ */
64
+ export function repairFenceJson(raw: string): { text: string; repairs: number } | null {
65
+ try {
66
+ JSON.parse(raw)
67
+ return null
68
+ } catch {
69
+ // fall through to the repair scan
70
+ }
71
+ let out = ''
72
+ let inString = false
73
+ let escaped = false
74
+ let repairs = 0
75
+ for (let i = 0; i < raw.length; i++) {
76
+ const ch = raw[i]
77
+ if (escaped) {
78
+ out += ch
79
+ escaped = false
80
+ continue
81
+ }
82
+ if (inString && ch === '\\') {
83
+ out += ch
84
+ escaped = true
85
+ continue
86
+ }
87
+ if (ch === '"') {
88
+ if (!inString) {
89
+ inString = true
90
+ out += ch
91
+ continue
92
+ }
93
+ // Inside a string: is this quote the terminator? Look past whitespace.
94
+ let j = i + 1
95
+ while (j < raw.length && (raw[j] === ' ' || raw[j] === '\t' || raw[j] === '\n' || raw[j] === '\r')) j++
96
+ const next = j < raw.length ? raw[j] : ''
97
+ if (next === ',' || next === ']' || next === '}' || next === ':' || next === '') {
98
+ inString = false
99
+ out += ch
100
+ } else {
101
+ // Free-standing quote inside a value → escape it.
102
+ out += '\\"'
103
+ repairs++
104
+ }
105
+ continue
106
+ }
107
+ if (ch === ',') {
108
+ // Trailing comma before `}` / `]` / end of input → drop it.
109
+ let j = i + 1
110
+ while (j < raw.length && (raw[j] === ' ' || raw[j] === '\t' || raw[j] === '\n' || raw[j] === '\r')) j++
111
+ const next = j < raw.length ? raw[j] : ''
112
+ if (next === '}' || next === ']' || next === '') {
113
+ repairs++
114
+ continue
115
+ }
116
+ }
117
+ out += ch
118
+ }
119
+ if (repairs === 0) return null
120
+ try {
121
+ JSON.parse(out)
122
+ return { text: out, repairs }
123
+ } catch {
124
+ return null
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Tier-2 repair — SETTLED MESSAGES ONLY (never while streaming): heals
130
+ * structural incompleteness — missing closing quotes/brackets — by appending
131
+ * the missing terminators, and heals stray closers — a `]` mistyped as `}` or
132
+ * a duplicated terminator — by skipping closers that do not match the open
133
+ * stack (they cannot be legal JSON). Callers gate it on settled messages (the
134
+ * client uses the host-provided fence source; the validate tool is by
135
+ * definition pre-emission), so a streaming half can never flash premature UI.
136
+ *
137
+ * ONE unified scan: the tier-1 fixes (quote escaping + trailing-comma drops)
138
+ * are folded into the same pass, so bodies that combine BOTH defect classes
139
+ * (a trailing comma AND a missing closer) heal in one shot — the old
140
+ * two-phase chain lost tier-1's partial work when its whole-body parse
141
+ * failed, and re-scanning the raw text could not compose the repairs.
142
+ * Adopted only when the completed body parses as whole JSON.
143
+ */
144
+ export function completeFenceJson(raw: string): { text: string; repairs: number } | null {
145
+ try {
146
+ JSON.parse(raw)
147
+ return null
148
+ } catch {
149
+ // fall through to the unified repair scan
150
+ }
151
+ let out = ''
152
+ const stack: Array<'}' | ']'> = []
153
+ let inString = false
154
+ let escaped = false
155
+ let repairs = 0
156
+ for (let i = 0; i < raw.length; i++) {
157
+ const ch = raw[i]
158
+ if (escaped) {
159
+ out += ch
160
+ escaped = false
161
+ continue
162
+ }
163
+ if (inString) {
164
+ if (ch === '\\') {
165
+ out += ch
166
+ escaped = true
167
+ continue
168
+ }
169
+ if (ch !== '"') {
170
+ out += ch
171
+ continue
172
+ }
173
+ // Inside a string: is this quote the terminator? Look past whitespace.
174
+ let j = i + 1
175
+ while (j < raw.length && (raw[j] === ' ' || raw[j] === '\t' || raw[j] === '\n' || raw[j] === '\r')) j++
176
+ const next = j < raw.length ? raw[j] : ''
177
+ if (next === ',' || next === ']' || next === '}' || next === ':' || next === '') {
178
+ inString = false
179
+ out += ch
180
+ } else {
181
+ // Free-standing quote inside a value → escape it (tier-1 fix).
182
+ out += '\\"'
183
+ repairs++
184
+ }
185
+ continue
186
+ }
187
+ if (ch === '"') {
188
+ inString = true
189
+ out += ch
190
+ continue
191
+ }
192
+ if (ch === '{') {
193
+ stack.push('}')
194
+ out += ch
195
+ continue
196
+ }
197
+ if (ch === '[') {
198
+ stack.push(']')
199
+ out += ch
200
+ continue
201
+ }
202
+ if (ch === '}' || ch === ']') {
203
+ if (stack[stack.length - 1] === ch) {
204
+ stack.pop()
205
+ out += ch
206
+ } else {
207
+ // Mismatched closer (e.g. a `]` mistyped as `}`, or a duplicated
208
+ // terminator): no legal JSON can contain it here, so skip it and let
209
+ // the remaining closers pair up again. The whole-body parse below is
210
+ // the final arbiter — if skipping made things worse, nothing is
211
+ // adopted and the diagnostic banner stays.
212
+ repairs++
213
+ }
214
+ continue
215
+ }
216
+ if (ch === ',') {
217
+ // Trailing comma before `}` / `]` / end of input → drop it (tier-1 fix).
218
+ let j = i + 1
219
+ while (j < raw.length && (raw[j] === ' ' || raw[j] === '\t' || raw[j] === '\n' || raw[j] === '\r')) j++
220
+ const next = j < raw.length ? raw[j] : ''
221
+ if (next === '}' || next === ']' || next === '') {
222
+ repairs++
223
+ continue
224
+ }
225
+ }
226
+ out += ch
227
+ }
228
+ if (inString) {
229
+ // Unterminated string value → close it.
230
+ out += '"'
231
+ repairs++
232
+ }
233
+ while (stack.length > 0) {
234
+ out += stack.pop()
235
+ repairs++
236
+ }
237
+ if (repairs === 0) return null
238
+ try {
239
+ JSON.parse(out)
240
+ return { text: out, repairs }
241
+ } catch {
242
+ return null
243
+ }
244
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2024",
4
+ "module": "esnext",
5
+ "moduleResolution": "bundler",
6
+ "jsx": "react-jsx",
7
+ "lib": [
8
+ "ES2024",
9
+ "DOM",
10
+ "DOM.Iterable"
11
+ ],
12
+ "types": [
13
+ "node"
14
+ ],
15
+ "declaration": true,
16
+ "skipLibCheck": true,
17
+ "esModuleInterop": true,
18
+ "allowImportingTsExtensions": true,
19
+ "rewriteRelativeImportExtensions": true,
20
+ "strict": true,
21
+ "noUncheckedIndexedAccess": true,
22
+ "exactOptionalPropertyTypes": true,
23
+ "noImplicitOverride": true,
24
+ "noFallthroughCasesInSwitch": true,
25
+ "noUnusedLocals": true,
26
+ "noUnusedParameters": true,
27
+ "rootDir": "src",
28
+ "outDir": "lib",
29
+ "emitDeclarationOnly": true,
30
+ "declarationDir": "lib/types"
31
+ },
32
+ "include": [
33
+ "src"
34
+ ]
35
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * dsh-genui build: node-half lib (host plugin, prompt injection) + browser
3
+ * client bundle (the dsh-ui renderer) speaking the dsh module-loader
4
+ * protocol (`window.__ModuleLoader__.load`). Mirrors the dsh repo's
5
+ * packages/client/tsdown.client.ts preset, simplified for one package.
6
+ *
7
+ * Deterministic output: the CSS Modules class map is emitted in fixed
8
+ * UTF-16 local-name order (never localeCompare — system-locale drift), and
9
+ * the production browser bundle carries no sourcemap, so the same source
10
+ * builds byte-identical client.js every time.
11
+ */
12
+ import { readFile } from 'node:fs/promises'
13
+ import { basename, dirname, relative, resolve as resolvePath } from 'node:path'
14
+ import { fileURLToPath } from 'node:url'
15
+ import type { UserConfig } from 'tsdown'
16
+ import { transform } from 'lightningcss'
17
+
18
+ const ID = '@changfenhuang/dsh-genui'
19
+ const PROJECT_ROOT = dirname(fileURLToPath(import.meta.url))
20
+
21
+ /** Module-table entries this bundle may leave external: platform seed rows
22
+ * (react family, cordis, ui-primitives) answered by the loader's require.
23
+ * `react-dom/client` joins the list for the DOM render channel (pure-plugin
24
+ * fence rendering on pristine hosts mounts its own React roots). */
25
+ const EXTERNALS = [
26
+ 'react', 'react/jsx-runtime', 'react-dom/client', '@deepseek-ai/cordis', '@deepseek-ai/dsh-client-ui-primitives',
27
+ ]
28
+
29
+ const CSS_VIRTUAL_PREFIX = '\0dsh-css:'
30
+ const CSS_VIRTUAL_SUFFIX = '.mjs'
31
+
32
+
33
+ function cssModulesPlugin(): NonNullable<UserConfig['plugins']>[number] {
34
+ return {
35
+ name: 'dsh-css-modules-inline',
36
+ resolveId(source: string, importer: string | undefined) {
37
+ if (!source.endsWith('.module.css')) return null
38
+ // The bundle builds straight from src, so the importer's directory is
39
+ // always the source tree — no lib/types backtracking needed.
40
+ const abs = importer !== undefined ? resolvePath(dirname(importer), source) : source
41
+ const stableId = relative(PROJECT_ROOT, abs).replaceAll('\\', '/')
42
+ return CSS_VIRTUAL_PREFIX + stableId + CSS_VIRTUAL_SUFFIX
43
+ },
44
+ async load(virtualId: string) {
45
+ if (!virtualId.startsWith(CSS_VIRTUAL_PREFIX)) return null
46
+ const stableId = virtualId.slice(CSS_VIRTUAL_PREFIX.length, -CSS_VIRTUAL_SUFFIX.length)
47
+ const fileId = resolvePath(PROJECT_ROOT, stableId)
48
+ this.addWatchFile(fileId)
49
+ const source = await readFile(fileId)
50
+ const { code, exports: cssExports } = transform({
51
+ filename: stableId,
52
+ code: source,
53
+ cssModules: { pattern: '[hash]_[local]' },
54
+ minify: true,
55
+ })
56
+ // Deterministic key order: fixed UTF-16 comparison on the LOCAL class
57
+ // names (localeCompare depends on the system locale). Values unchanged.
58
+ const entries = Object.entries(cssExports ?? {})
59
+ .map(([local, exp]) => [local, exp.name] as const)
60
+ .sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
61
+ const classMap = Object.fromEntries(entries)
62
+ const tagId = `${ID}/${basename(fileId)}`
63
+ return [
64
+ `const css = ${JSON.stringify(code.toString())};`,
65
+ `const tagId = ${JSON.stringify(tagId)};`,
66
+ `if (typeof document !== 'undefined' && document.querySelector('style[data-plugin-css=' + JSON.stringify(tagId) + ']') === null) {`,
67
+ ` const tag = document.createElement('style');`,
68
+ ` tag.dataset.plugin = ${JSON.stringify(ID)};`,
69
+ ' tag.dataset.pluginCss = tagId;',
70
+ ' tag.textContent = css;',
71
+ ' document.head.appendChild(tag);',
72
+ '}',
73
+ `export default ${JSON.stringify(classMap)};`,
74
+ ].join('\n')
75
+ },
76
+ }
77
+ }
78
+
79
+ function purityGate(): NonNullable<UserConfig['plugins']>[number] {
80
+ return {
81
+ name: 'dsh-client-bundle-purity',
82
+ resolveId(source: string) {
83
+ if (!source.startsWith('@deepseek-ai/')) return null
84
+ if (EXTERNALS.includes(source)) return null
85
+ throw new Error(
86
+ `client bundle purity: "${source}" is not in the module table (EXTERNALS) — `
87
+ + 'cross-plugin value imports are forbidden; collaborate through cordis services',
88
+ )
89
+ },
90
+ }
91
+ }
92
+
93
+ const clientConfig: UserConfig = {
94
+ name: `${ID}/client`,
95
+ entry: { client: 'src/client/index.tsx' },
96
+ outDir: 'lib',
97
+ format: 'cjs',
98
+ platform: 'browser',
99
+ dts: false,
100
+ // Minified production bundles: the host serves plugin bytes uncompressed,
101
+ // so every byte counts at first-load time.
102
+ minify: true,
103
+ // No sourcemap in the production bundle: nothing ships the map, nothing
104
+ // rewrites paths, builds are byte-identical.
105
+ sourcemap: false,
106
+ clean: false,
107
+ deps: {
108
+ neverBundle: [...EXTERNALS],
109
+ alwaysBundle: (id: string) => !EXTERNALS.includes(id),
110
+ },
111
+ define: {
112
+ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV ?? 'production'),
113
+ 'import.meta.env.MODE': JSON.stringify(process.env.NODE_ENV ?? 'production'),
114
+ 'import.meta.env': JSON.stringify({ MODE: process.env.NODE_ENV ?? 'production' }),
115
+ },
116
+ plugins: [purityGate(), cssModulesPlugin()],
117
+ outputOptions: {
118
+ entryFileNames: 'client.js',
119
+ // The loader fetches one script per plugin; the lazy mermaid/three
120
+ // engines are NOT part of this bundle anymore — they ship as standalone
121
+ // IIFE assets under lib/assets/, fetched on demand by the runtime loaders
122
+ // (see asset-loader.ts). Nothing heavy may re-enter this bundle.
123
+ codeSplitting: false,
124
+ banner: `window.__ModuleLoader__.load({ id: ${JSON.stringify(ID)}, factory: (require) => {`,
125
+ footer: 'return module.exports; } });',
126
+ intro: 'var module = { exports: {} }; var exports = module.exports;',
127
+ },
128
+ }
129
+
130
+ /**
131
+ * Lazy engine assets: mermaid and three ship as standalone single-entry
132
+ * IIFEs that register themselves on `window.__GenuiAssets__` (no
133
+ * module-loader protocol — the loaders read the global directly after script
134
+ * injection). Two configs because rolldown refuses IIFE with multiple
135
+ * entries; each bundle is fully independent (no cross-entry chunks). The
136
+ * main client bundle never contains either engine, so the eager download
137
+ * drops from ~9 MB to the small renderer core.
138
+ */
139
+ function assetConfig(name: 'mermaid' | 'three' | 'echarts', entry: string): UserConfig {
140
+ return {
141
+ name: `${ID}/assets/${name}`,
142
+ entry: { [`assets/${name}`]: entry },
143
+ outDir: 'lib',
144
+ format: 'iife',
145
+ platform: 'browser',
146
+ dts: false,
147
+ // Minified: these engines are huge unminified (6.7 MB / 1.8 MB) and the
148
+ // host serves uncompressed bytes; on-demand loads still want them small.
149
+ minify: true,
150
+ sourcemap: false,
151
+ clean: false,
152
+ deps: {
153
+ neverBundle: [...EXTERNALS],
154
+ alwaysBundle: (id: string) => !EXTERNALS.includes(id),
155
+ },
156
+ define: {
157
+ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV ?? 'production'),
158
+ },
159
+ plugins: [purityGate()],
160
+ outputOptions: {
161
+ entryFileNames: '[name].js',
162
+ },
163
+ }
164
+ }
165
+
166
+ const libConfig: UserConfig = {
167
+ name: ID,
168
+ // Named entries keep the published layout stable (lib/index.js +
169
+ // lib/invariant.js) while src/index.ts becomes the canonical bundle
170
+ // source entry that plugin_check's tool-bundle contract expects.
171
+ entry: { index: 'src/index.ts', invariant: 'src/plugin/invariant.ts' },
172
+ outDir: 'lib',
173
+ format: ['esm'],
174
+ platform: 'node',
175
+ target: 'es2024',
176
+ fixedExtension: false,
177
+ dts: false,
178
+ // Flatten entry outputs to lib/<basename>.js so package exports keep the
179
+ // stable paths lib/index.js + lib/invariant.js (a directory-mirroring
180
+ // default would emit lib/plugin/invariant.js).
181
+ outputOptions: { entryFileNames: '[name].js' },
182
+ // Cleanup happens in the build script (`rm -rf lib` before tsc): tsdown
183
+ // must never wipe lib/types (tsc's declaration output) mid-pipeline.
184
+ clean: false,
185
+ }
186
+
187
+ export default [
188
+ libConfig,
189
+ clientConfig,
190
+ assetConfig('mermaid', 'src/client/asset-mermaid.ts'),
191
+ assetConfig('three', 'src/client/asset-three.ts'),
192
+ assetConfig('echarts', 'src/client/asset-echarts.ts'),
193
+ ]