@grapecity-software/dsh-spreadjs-editor 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh plugin contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,229 @@
1
+ # @grapecity-software/dsh-spreadjs-editor
2
+
3
+ [GitHub](https://github.com/GrapeCityXA/dsh-plugin-spreadjs-editor)
4
+
5
+ 在 DeepSeek Harness Web UI 里,直接在右侧文件树中打开、查看和编辑 Excel / SpreadJS 文件。
6
+
7
+ A DeepSeek Harness Web UI plugin that opens, views, and edits Excel / SpreadJS files directly from the right-side file tree.
8
+
9
+ ---
10
+
11
+ ## 中文说明
12
+
13
+ ### 功能
14
+
15
+ - 支持 `.xlsx`、`.xlsm`、`.csv`、`.sjs`、`.ssjson` 文件。
16
+ - 基于 GrapeCity 官方 SpreadJS 和 SpreadJS Designer,提供完整的表格编辑能力。
17
+ - 与 ui-all 文件树集成,打开文件后立即进入编辑界面,不需要单独的编辑器页面。
18
+ - Designer 界面自动跟随系统 light/dark 偏好。
19
+
20
+ ### 快速开始
21
+
22
+ DSH 的 Web profile 使用 pnpm 10,而 pnpm 10 默认会拦截依赖包的生命周期脚本。安装前先把本包加入放行列表。
23
+
24
+ 编辑 Web profile 的 `pnpm-workspace.yaml`,通常位于:
25
+
26
+ ```text
27
+ ~/.dsh/profiles/web/pnpm-workspace.yaml
28
+ ```
29
+
30
+ 加入以下内容:
31
+
32
+ ```yaml
33
+ onlyBuiltDependencies:
34
+ - @grapecity-software/dsh-spreadjs-editor
35
+ ```
36
+
37
+ 然后安装插件并启动:
38
+
39
+ ```sh
40
+ dsh plugin --profile web add @grapecity-software/dsh-spreadjs-editor
41
+ dsh web
42
+ ```
43
+
44
+ 启动后,在 Web UI 右侧文件树中打开 `.xlsx`、`.xlsm`、`.csv`、`.sjs` 或 `.ssjson` 文件即可。
45
+
46
+ 安装阶段会自动生成 SpreadJS 客户端,不需要再执行 `npm install` 或 `npm run build`。
47
+
48
+ 如果使用 npx 运行 DSH,使用同样的命令:
49
+
50
+ ```sh
51
+ npx --yes @deepseek-ai/dsh@latest plugin --profile web add @grapecity-software/dsh-spreadjs-editor
52
+ npx --yes @deepseek-ai/dsh@latest web
53
+ ```
54
+
55
+ DeepSeek Harness CLI 是 `@deepseek-ai/dsh`,不要使用同名 npm 包 `dsh`。
56
+
57
+ 如果插件已经装好、之后才补放行配置,补一次构建即可:
58
+
59
+ ```sh
60
+ pnpm --dir ~/.dsh/profiles/web rebuild @grapecity-software/dsh-spreadjs-editor
61
+ ```
62
+
63
+ ### 常见问题
64
+
65
+ **我已经有 ui-all,还需要额外安装什么吗?**
66
+
67
+ 不需要。ui-all 自带 `dsh-better-sidebar`,本插件会直接注册到它。
68
+
69
+ **安装完还需要手动构建吗?**
70
+
71
+ 不需要。只要安装前放行过 `onlyBuiltDependencies`,`dsh plugin add` 会同时完成安装和构建。
72
+
73
+ **SpreadJS 会跟随每次安装自动升级吗?**
74
+
75
+ 不会自动跨版本升级。所有 GrapeCity 官方包在 `package.json` 中锁定为同一版本;维护者会在发布新版时统一同步。
76
+
77
+ ### 配置
78
+
79
+ 正式部署时,在 profile 的 `cordis.patch.yml` 中配置 license key:
80
+
81
+ | Key | 默认值 | 说明 |
82
+ | --- | --- | --- |
83
+ | `licenseKey` | `''` | SpreadJS + Designer 部署 license key。留空时使用 evaluation 构建。 |
84
+
85
+ ### 支持的文件
86
+
87
+ | 文件 | 说明 |
88
+ | --- | --- |
89
+ | `.xlsx` / `.xlsm` | Excel 工作簿 |
90
+ | `.csv` | CSV 文本 |
91
+ | `.sjs` | SpreadJS 原生工作簿格式 |
92
+ | `.ssjson` | SpreadJS JSON 工作簿 |
93
+
94
+ ### 从源码安装
95
+
96
+ 如果你需要从源码运行或参与开发:
97
+
98
+ ```sh
99
+ git clone https://github.com/GrapeCityXA/dsh-plugin-spreadjs-editor.git
100
+
101
+ cd dsh-plugin-spreadjs-editor
102
+ npm install
103
+ npm run build
104
+
105
+ dsh plugin --profile web add ../dsh-plugin-spreadjs-editor
106
+ ```
107
+
108
+ ### 许可
109
+
110
+ 插件代码使用 MIT。SpreadJS 是 GrapeCity 的商业产品,正式部署时请配置真实 license key。
111
+
112
+ ### 维护者
113
+
114
+ 发布新版前,统一把所有 GrapeCity 依赖同步到最新版本:
115
+
116
+ ```sh
117
+ npm run grapecity:update -- --latest
118
+ ```
119
+
120
+ ---
121
+
122
+ ## English
123
+
124
+ ### Features
125
+
126
+ - Opens `.xlsx`, `.xlsm`, `.csv`, `.sjs`, and `.ssjson` files.
127
+ - Built on GrapeCity's official SpreadJS and SpreadJS Designer for full spreadsheet editing.
128
+ - Integrated with the ui-all file tree: open a file and start editing immediately, with no separate editor page.
129
+ - The Designer follows the OS light/dark preference.
130
+
131
+ ### Quick Start
132
+
133
+ DSH's Web profile uses pnpm 10, which blocks dependency lifecycle scripts by default. Allow this package before installing.
134
+
135
+ Edit the Web profile's `pnpm-workspace.yaml`, usually:
136
+
137
+ ```text
138
+ ~/.dsh/profiles/web/pnpm-workspace.yaml
139
+ ```
140
+
141
+ Add:
142
+
143
+ ```yaml
144
+ onlyBuiltDependencies:
145
+ - @grapecity-software/dsh-spreadjs-editor
146
+ ```
147
+
148
+ Then install the plugin and start DSH:
149
+
150
+ ```sh
151
+ dsh plugin --profile web add @grapecity-software/dsh-spreadjs-editor
152
+ dsh web
153
+ ```
154
+
155
+ After startup, open `.xlsx`, `.xlsm`, `.csv`, `.sjs`, or `.ssjson` from the right-side file tree.
156
+
157
+ The SpreadJS client is generated automatically during install. There is no need to run `npm install` or `npm run build` separately.
158
+
159
+ If you run DSH through npx, use the same commands:
160
+
161
+ ```sh
162
+ npx --yes @deepseek-ai/dsh@latest plugin --profile web add @grapecity-software/dsh-spreadjs-editor
163
+ npx --yes @deepseek-ai/dsh@latest web
164
+ ```
165
+
166
+ Use `@deepseek-ai/dsh`; the unrelated npm package `dsh` is not the DeepSeek Harness CLI.
167
+
168
+ If the plugin was already installed before the allow-list entry was added, run the build once:
169
+
170
+ ```sh
171
+ pnpm --dir ~/.dsh/profiles/web rebuild @grapecity-software/dsh-spreadjs-editor
172
+ ```
173
+
174
+ ### FAQ
175
+
176
+ **I already have ui-all. Do I need to install anything else?**
177
+
178
+ No. ui-all bundles `dsh-better-sidebar`, and this plugin registers directly with it.
179
+
180
+ **Do I need to build manually after installation?**
181
+
182
+ No. If `onlyBuiltDependencies` is configured before installing, `dsh plugin add` installs and builds in one step.
183
+
184
+ **Will SpreadJS upgrade automatically with every install?**
185
+
186
+ No. All GrapeCity packages are pinned to the same version in `package.json`; maintainers sync them together before a new release.
187
+
188
+ ### Configuration
189
+
190
+ For production use, set the license key in the profile's `cordis.patch.yml`:
191
+
192
+ | Key | Default | Description |
193
+ | --- | --- | --- |
194
+ | `licenseKey` | `''` | SpreadJS + Designer deployment license key. Empty runs the evaluation build. |
195
+
196
+ ### Supported files
197
+
198
+ | File | Description |
199
+ | --- | --- |
200
+ | `.xlsx` / `.xlsm` | Excel workbook |
201
+ | `.csv` | CSV text |
202
+ | `.sjs` | SpreadJS native workbook format |
203
+ | `.ssjson` | SpreadJS JSON workbook |
204
+
205
+ ### Install from source
206
+
207
+ To run from source or contribute:
208
+
209
+ ```sh
210
+ git clone https://github.com/GrapeCityXA/dsh-plugin-spreadjs-editor.git
211
+
212
+ cd dsh-plugin-spreadjs-editor
213
+ npm install
214
+ npm run build
215
+
216
+ dsh plugin --profile web add ../dsh-plugin-spreadjs-editor
217
+ ```
218
+
219
+ ### License
220
+
221
+ MIT for the plugin code. SpreadJS is a commercial product by GrapeCity; configure a real license key for production use.
222
+
223
+ ### Maintainers
224
+
225
+ Before a new release, sync all GrapeCity dependencies to the latest version:
226
+
227
+ ```sh
228
+ npm run grapecity:update -- --latest
229
+ ```
@@ -0,0 +1,22 @@
1
+ # dsh-spreadjs-editor bundle patch
2
+ #
3
+ # One row activates the whole plugin: the Loader imports the package's node
4
+ # half (lib/index.js, the /spreadjs license/config endpoint) and the
5
+ # client-modules scan picks up the `dsh.client` declaration for the browser
6
+ # half (lib/client.js, which registers the SpreadJS viewer into
7
+ # dsh-better-sidebar).
8
+ #
9
+ # The row must run only under the Web composition (it injects the webServer
10
+ # service); a headless profile should not mount this bundle. The browser half
11
+ # has no hard client dependency: it waits for `dsh-better-sidebar`, which is
12
+ # bundled by @linxin666/dsh-web-all (ui-all).
13
+
14
+ - insert:
15
+ - id: spreadjs-editor
16
+ name: @grapecity-software/dsh-spreadjs-editor
17
+ inject: [webServer]
18
+ config:
19
+ # Optional: your SpreadJS license key. Empty (default) runs the
20
+ # evaluation watermark build. Keep it out of the patch if you prefer
21
+ # to configure it per-deployment.
22
+ licenseKey: ''
package/lib/index.js ADDED
@@ -0,0 +1,45 @@
1
+ //#region src/index.ts
2
+ const name = "dsh-spreadjs-editor";
3
+ /** Required service: the web server whose route table this plugin extends. */
4
+ const inject = ["webServer"];
5
+ function sendJson(res, status, body) {
6
+ const data = JSON.stringify(body);
7
+ res.writeHead(status, {
8
+ "Content-Type": "application/json; charset=utf-8",
9
+ "Content-Length": Buffer.byteLength(data),
10
+ "Cache-Control": "no-store"
11
+ });
12
+ res.end(data);
13
+ }
14
+ function readPathname(req) {
15
+ return new URL(req.url ?? "/", "http://localhost").pathname;
16
+ }
17
+ function apply(ctx, config = {}) {
18
+ const disposer = ctx.webServer.register({
19
+ kind: "prefix",
20
+ path: "/spreadjs",
21
+ handler: (req, res) => {
22
+ try {
23
+ const pathname = readPathname(req);
24
+ if (pathname === "/spreadjs/api/health") {
25
+ sendJson(res, 200, { ok: true });
26
+ return;
27
+ }
28
+ if (pathname === "/spreadjs/api/config") {
29
+ sendJson(res, 200, { licenseKey: config.licenseKey ?? "" });
30
+ return;
31
+ }
32
+ sendJson(res, 404, { error: "unknown endpoint" });
33
+ } catch (error) {
34
+ const message = error instanceof Error ? error.message : String(error);
35
+ if (!res.headersSent) sendJson(res, 500, { error: message });
36
+ else res.destroy();
37
+ }
38
+ }
39
+ });
40
+ ctx.effect(() => disposer, "dsh-spreadjs-editor: /spreadjs routes");
41
+ }
42
+ //#endregion
43
+ export { apply, inject, name };
44
+
45
+ //# sourceMappingURL=index.js.map
package/package.json ADDED
@@ -0,0 +1,111 @@
1
+ {
2
+ "name": "@grapecity-software/dsh-spreadjs-editor",
3
+ "version": "0.1.0",
4
+ "description": "DeepSeek Harness Web UI plugin: SpreadJS viewer registered through dsh-better-sidebar (ui-all).",
5
+ "keywords": [
6
+ "deepseek-harness",
7
+ "dsh",
8
+ "webui",
9
+ "plugin",
10
+ "spreadjs",
11
+ "excel"
12
+ ],
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "exports": {
16
+ ".": {
17
+ "default": "./lib/index.js"
18
+ },
19
+ "./client": {
20
+ "default": "./lib/client.js"
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "files": [
25
+ "cordis.patch.yml",
26
+ "README.md",
27
+ "LICENSE",
28
+ "src",
29
+ "scripts",
30
+ "tsdown.config.ts",
31
+ "tsconfig.json"
32
+ ],
33
+ "scripts": {
34
+ "build": "node scripts/build.mjs",
35
+ "grapecity:update": "node scripts/update-grapecity.mjs",
36
+ "postinstall": "node scripts/build.mjs",
37
+ "prepare": "node scripts/build.mjs",
38
+ "prepublishOnly": "npm run typecheck && npm test && npm run build",
39
+ "rebuild": "node scripts/build.mjs",
40
+ "test": "vitest run",
41
+ "typecheck": "tsc --noEmit"
42
+ },
43
+ "engines": {
44
+ "node": ">=20"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "https://github.com/GrapeCityXA/dsh-plugin-spreadjs-editor.git"
52
+ },
53
+ "dsh": {
54
+ "bundle": {
55
+ "patch": "./cordis.patch.yml"
56
+ },
57
+ "client": {
58
+ "platform": "web",
59
+ "inject": []
60
+ }
61
+ },
62
+ "peerDependencies": {
63
+ "@deepseek-ai/cordis": ">=4.0.0",
64
+ "@deepseek-ai/dsh-client-runtime": ">=0.1.1-rc.2",
65
+ "dsh-better-sidebar": ">=0.6.0",
66
+ "react": "^18.0.0"
67
+ },
68
+ "peerDependenciesMeta": {
69
+ "dsh-better-sidebar": {
70
+ "optional": true
71
+ }
72
+ },
73
+ "dependencies": {
74
+ "@grapecity-software/spread-excelio": "19.1.4",
75
+ "@grapecity-software/spread-sheets": "19.1.4",
76
+ "@grapecity-software/spread-sheets-io": "19.1.4",
77
+ "@grapecity-software/spread-sheets-resources-zh": "19.1.4",
78
+ "@grapecity-software/spread-sheets-designer": "19.1.4",
79
+ "@grapecity-software/spread-sheets-designer-resources-cn": "19.1.4",
80
+ "@grapecity-software/spread-sheets-shapes": "19.1.4",
81
+ "@grapecity-software/spread-sheets-charts": "19.1.4",
82
+ "@grapecity-software/spread-sheets-slicers": "19.1.4",
83
+ "@grapecity-software/spread-sheets-sparklines": "19.1.4",
84
+ "@grapecity-software/spread-sheets-print": "19.1.4",
85
+ "@grapecity-software/spread-sheets-pdf": "19.1.4",
86
+ "@grapecity-software/spread-sheets-barcode": "19.1.4",
87
+ "@grapecity-software/spread-sheets-formula-panel": "19.1.4",
88
+ "@grapecity-software/spread-sheets-pivot-addon": "19.1.4",
89
+ "@grapecity-software/spread-sheets-tablesheet": "19.1.4",
90
+ "@grapecity-software/spread-sheets-datacharts-addon": "19.1.4",
91
+ "@grapecity-software/spread-sheets-ganttsheet": "19.1.4",
92
+ "@grapecity-software/spread-sheets-reportsheet-addon": "19.1.4",
93
+ "@grapecity-software/spread-sheets-languagepackages": "19.1.4",
94
+ "tsdown": "^0.22.0",
95
+ "typescript": "^5.9.0",
96
+ "unrun": "^0.3.1"
97
+ },
98
+ "devDependencies": {
99
+ "@deepseek-ai/cordis": "^4.0.1",
100
+ "@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
101
+ "@deepseek-ai/dsh-client-ui-layout": "0.1.1-rc.2",
102
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.1.1-rc.2",
103
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.1-rc.2",
104
+ "@deepseek-ai/dsh-host-webserver": "0.1.1-rc.2",
105
+ "@types/node": "^22.0.0",
106
+ "@types/react": "^18.3.0",
107
+ "react": "^18.2.0",
108
+ "vitest": "^3.0.0"
109
+ },
110
+ "license": "MIT"
111
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Build entry. tsdown 0.22 uses `Promise.withResolvers` (ES2024), which is
3
+ * missing on Node 20; polyfill it here, then run the real tsdown CLI.
4
+ */
5
+ if (typeof Promise.withResolvers !== 'function') {
6
+ Promise.withResolvers = function withResolvers() {
7
+ let resolve
8
+ let reject
9
+ const promise = new Promise((res, rej) => {
10
+ resolve = res
11
+ reject = rej
12
+ })
13
+ return { promise, resolve, reject }
14
+ }
15
+ }
16
+
17
+ // Resolve tsdown by package location so pnpm's isolated store layout works:
18
+ // the dependency may live in a parent node_modules layer, and tsdown does not
19
+ // export its dist/run.mjs entry through package exports.
20
+ import { createRequire } from 'node:module'
21
+ import { dirname, join } from 'node:path'
22
+ import { pathToFileURL } from 'node:url'
23
+
24
+ const require = createRequire(import.meta.url)
25
+ const tsdownPackage = require.resolve('tsdown/package.json')
26
+
27
+ // tsdown's default TS config loader hits a known Node.js bug when the config
28
+ // lives under node_modules. unrun handles that path on Node 20-24.
29
+ process.argv.push('--config-loader', 'unrun')
30
+
31
+ await import(pathToFileURL(join(dirname(tsdownPackage), 'dist/run.mjs')).href)
@@ -0,0 +1,47 @@
1
+ // Structural smoke check for the BUILT browser half (lib/client.js).
2
+ //
3
+ // A full runtime load (factory → apply → betterSidebar registration) requires a real
4
+ // browser: SpreadJS touches DOM + canvas at module init, which jsdom cannot
5
+ // satisfy without the native `canvas` package. That end-to-end load is left to
6
+ // the harness browser (see README "Verification"). What we CAN assert here
7
+ // without a browser is every structural contract of the artifact:
8
+ // - the __ModuleLoader__.load closure factory wrapper with the plugin id
9
+ // - the module-table externals stay require()'d (not inlined)
10
+ // - SpreadJS + its stylesheets are inlined (no @grapecity require remains)
11
+ // - the injected style strings are embedded
12
+ import { readFileSync } from 'node:fs'
13
+
14
+ const code = readFileSync(new URL('../lib/client.js', import.meta.url), 'utf8')
15
+
16
+ let pass = 0
17
+ let fail = 0
18
+ function check(label, cond, detail = '') {
19
+ if (cond) {
20
+ pass++
21
+ console.log(` ok ${label}`)
22
+ } else {
23
+ fail++
24
+ console.log(`FAIL ${label}${detail ? ` — ${detail}` : ''}`)
25
+ }
26
+ }
27
+
28
+ // The only non-SpreadJS runtime externals are React and its jsx-runtime.
29
+ // @deepseek-ai/dsh-client-runtime/client is consumed type-only and erased.
30
+ const EXTERNALS = [
31
+ 'react',
32
+ 'react/jsx-runtime',
33
+ ]
34
+
35
+ check('loader wrapper opens with plugin id', /window\.__ModuleLoader__\.load\(\{\s*id: "dsh-spreadjs-editor"/.test(code))
36
+ check('loader factory signature', /factory: \(require\) =>/.test(code))
37
+ check('closure return (module.exports)', /return module\.exports;/.test(code))
38
+ for (const ext of EXTERNALS) {
39
+ check(`external require("${ext}") present`, new RegExp(`require\\("${ext.replace(/[/.]/g, '\\$&')}"\\)`).test(code))
40
+ }
41
+ check('no @grapecity require remains (inlined)', !/require\("@grapecity/.test(code))
42
+ check('spread-sheets css string embedded', code.includes('gc-spread-sheets'))
43
+ check('designer css string embedded', /SpreadJS Designer Library \d+\.\d+\.\d+/.test(code))
44
+ check('editor css string embedded', code.includes('.dsh-spreadjs-panel'))
45
+
46
+ console.log(`\n${pass} passed, ${fail} failed`)
47
+ process.exit(fail === 0 ? 0 : 1)
@@ -0,0 +1,88 @@
1
+ // Smoke test for the BUILT node half (lib/index.js): load it exactly as the
2
+ // harness loader would and drive the /spreadjs config/health handler with mock
3
+ // req/res.
4
+ import { Writable } from 'node:stream'
5
+ import { createRequire } from 'node:module'
6
+
7
+ const require = createRequire(import.meta.url)
8
+ const pkg = require('../lib/index.js')
9
+
10
+ let pass = 0
11
+ let fail = 0
12
+ function check(label, cond, detail = '') {
13
+ if (cond) {
14
+ pass++
15
+ console.log(` ok ${label}`)
16
+ } else {
17
+ fail++
18
+ console.log(`FAIL ${label}${detail ? ` — ${detail}` : ''}`)
19
+ }
20
+ }
21
+
22
+ // --- capture the handler like the harness would ---------------------------
23
+ let handler
24
+ const disposers = []
25
+ const ctx = {
26
+ webServer: {
27
+ register(route) {
28
+ handler = route.handler
29
+ const d = () => {}
30
+ disposers.push(d)
31
+ return d
32
+ },
33
+ },
34
+ effect(fn) {
35
+ disposers.push(fn)
36
+ },
37
+ }
38
+
39
+ pkg.apply(ctx, { licenseKey: 'SMOKE-KEY' })
40
+ check('registers /spreadjs route', handler !== undefined)
41
+ check('registers effect disposers', disposers.length === 2)
42
+
43
+ function makeRes() {
44
+ const chunks = []
45
+ const state = { status: 200, headers: {}, headersSent: false, body: '' }
46
+ const res = new Writable({
47
+ write(chunk, _e, cb) {
48
+ chunks.push(Buffer.from(chunk))
49
+ cb()
50
+ },
51
+ })
52
+ res.writeHead = (s, h) => {
53
+ state.status = s
54
+ if (h) state.headers = h
55
+ state.headersSent = true
56
+ }
57
+ Object.defineProperty(res, 'headersSent', { get: () => state.headersSent })
58
+ res.destroy = () => {}
59
+ return {
60
+ res,
61
+ finish: new Promise(r => res.on('finish', r)),
62
+ get state() {
63
+ state.body = Buffer.concat(chunks).toString()
64
+ return state
65
+ },
66
+ }
67
+ }
68
+
69
+ async function req(url) {
70
+ const r = makeRes()
71
+ await handler({ url }, r.res)
72
+ await r.finish
73
+ return r.state
74
+ }
75
+
76
+ // --- drive the endpoints ---------------------------------------------------
77
+ let r = await req('/spreadjs/api/health')
78
+ check('health -> 200', r.status === 200, `got ${r.status}`)
79
+ check('health body', r.body === JSON.stringify({ ok: true }), r.body)
80
+
81
+ r = await req('/spreadjs/api/config')
82
+ check('config -> licenseKey', JSON.parse(r.body).licenseKey === 'SMOKE-KEY', r.body)
83
+
84
+ r = await req('/spreadjs/api/unknown')
85
+ check('unknown -> 404', r.status === 404, `got ${r.status}`)
86
+
87
+ console.log(`\n${pass} passed, ${fail} failed`)
88
+ process.exit(fail === 0 ? 0 : 1)
@@ -0,0 +1,34 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs'
2
+ import { fileURLToPath } from 'node:url'
3
+
4
+ const pkgPath = fileURLToPath(new URL('../package.json', import.meta.url))
5
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
6
+
7
+ let version = process.argv[2]
8
+ if (version === '--latest') {
9
+ const res = await fetch(
10
+ 'https://registry.npmjs.org/@grapecity-software/spread-sheets/latest',
11
+ )
12
+ if (!res.ok) throw new Error(`npm registry returned ${res.status}`)
13
+ version = (await res.json()).version
14
+ }
15
+
16
+ if (!version) {
17
+ console.error('Usage: node scripts/update-grapecity.mjs <version> | --latest')
18
+ process.exit(1)
19
+ }
20
+
21
+ let changed = 0
22
+ for (const [name, spec] of Object.entries(pkg.dependencies ?? {})) {
23
+ if (name.startsWith('@grapecity-software/') && spec !== version) {
24
+ pkg.dependencies[name] = version
25
+ changed += 1
26
+ }
27
+ }
28
+
29
+ if (changed === 0) {
30
+ console.log(`GrapeCity dependencies already at ${version}`)
31
+ } else {
32
+ writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`)
33
+ console.log(`Updated ${changed} GrapeCity dependencies to ${version}`)
34
+ }
package/src/augment.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Pull the harness packages' declaration merges into the TS program.
3
+ *
4
+ * The node half reads `ctx.webServer` from @deepseek-ai/dsh-host-webserver;
5
+ * the browser half imports `ClientContext` directly from
6
+ * @deepseek-ai/dsh-client-runtime/client. This module exists for the
7
+ * typechecker only and is never part of a bundle.
8
+ */
9
+ import type {} from '@deepseek-ai/dsh-host-webserver'
10
+
11
+ export {}