@dsh-plugin/dsh-loader 1.0.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/README.md ADDED
@@ -0,0 +1,226 @@
1
+ # dshloader
2
+
3
+ [English](#english) | [中文](README.zh-CN.md)
4
+
5
+ ---
6
+
7
+ ## English
8
+
9
+ A runtime compatibility shim for **dsh** (DeepSeek Harness) cordis bundle
10
+ plugins. dshloader decouples third-party plugins from dsh's internal service
11
+ names, module paths, package names, and RPC details through a version-aware
12
+ **adapter registry**, so that when dsh upgrades and breaks internal APIs, you
13
+ only upgrade dshloader — plugins keep working unchanged.
14
+
15
+ ### Why
16
+
17
+ dsh is moving fast and its internal surface changes between releases:
18
+
19
+ - `httpServer` was renamed to `webServer` — old plugins that inject
20
+ `httpServer` hang forever.
21
+ - Deep source imports like
22
+ `@deepseek-ai/dsh-client-runtime/src/client/sessions/context-provenance.ts`
23
+ break when dsh ships no `src/`.
24
+ - Client UI packages like `@deepseek-ai/dsh-client-ui-primitives` could be
25
+ renamed in future dsh versions, breaking every plugin that imports them
26
+ directly.
27
+ - The official `dsh-host-apiproxy` hardcodes a settings namespace whitelist,
28
+ so third-party settings cards never appear in the Web UI.
29
+
30
+ dshloader absorbs these (and future) breaks behind a **stable API**:
31
+ `ctx.dshLoader` on the host, `window.__dshLoader__` in the browser, and
32
+ `@dsh-plugin/dsh-loader/*` stable subpaths for package imports.
33
+
34
+ ### Quick start
35
+
36
+ #### 1. Install dshloader into a profile
37
+
38
+ ```sh
39
+ dsh plugin --profile <name> add /path/to/dshloader
40
+ # or
41
+ DSH_HOME=~/.dsh npx dshloader setup <name>
42
+ ```
43
+
44
+ #### 2. Plugin `package.json` — only depend on dshloader
45
+
46
+ ```json
47
+ {
48
+ "dependencies": {
49
+ "@dsh-plugin/dsh-loader": "link:..."
50
+ }
51
+ }
52
+ ```
53
+
54
+ > **Plugins must NOT declare any `@deepseek-ai/*` dependency.** All dsh
55
+ > packages are accessed through dshloader's stable subpaths.
56
+
57
+ #### 3. Host side — use `ctx.dshLoader`
58
+
59
+ ```js
60
+ export const inject = ['dshLoader'];
61
+
62
+ export async function apply(ctx) {
63
+ // Settings: register a namespace
64
+ const scope = ctx.dshLoader.settings.register('my-plugin', schema);
65
+
66
+ // Web: register routes and WebSocket upgrades
67
+ ctx.dshLoader.web.get('/api/my-plugin/status', (req, res) => res.json({ ok: true }));
68
+ ctx.dshLoader.web.registerUpgrade({ path: '/ws/my-plugin', handler: fn });
69
+
70
+ // Services: read cordis services
71
+ const sessions = ctx.dshLoader.services.get('sessions');
72
+ }
73
+ ```
74
+
75
+ #### 4. Import dsh packages via stable subpaths
76
+
77
+ ```js
78
+ // Host packages
79
+ const { defineTool } = require('@dsh-plugin/dsh-loader/tools');
80
+
81
+ // Client UI packages (in client bundle source)
82
+ import { IconCloseFill14 } from '@dsh-plugin/dsh-loader/ui-primitives';
83
+ ```
84
+
85
+ **Stable subpath → real dsh package mapping (dsh 1.x):**
86
+
87
+ | Stable subpath | Real dsh package |
88
+ |---|---|
89
+ | `@dsh-plugin/dsh-loader/tools` | `@deepseek-ai/dsh-tools` |
90
+ | `@dsh-plugin/dsh-loader/llm` | `@deepseek-ai/dsh-llm` |
91
+ | `@dsh-plugin/dsh-loader/agent` | `@deepseek-ai/dsh-agent` |
92
+ | `@dsh-plugin/dsh-loader/settings` | `@deepseek-ai/dsh-settings` |
93
+ | `@dsh-plugin/dsh-loader/ui-primitives` | `@deepseek-ai/dsh-client-ui-primitives` |
94
+ | `@dsh-plugin/dsh-loader/ui-slots` | `@deepseek-ai/dsh-client-ui-slots` |
95
+ | `@dsh-plugin/dsh-loader/ui-settings` | `@deepseek-ai/dsh-client-ui-settings/client` |
96
+ | `@dsh-plugin/dsh-loader/web-react` | `@deepseek-ai/dsh-client-web-react` |
97
+ | `@dsh-plugin/dsh-loader/schema-form` | `@deepseek-ai/dsh-client-schema-form` |
98
+ | `@dsh-plugin/dsh-loader/runtime` | `@deepseek-ai/dsh-client-runtime/client` |
99
+
100
+ When dsh renames a package, only the dshloader adapter changes — plugin
101
+ source and bundle stay the same.
102
+
103
+ #### 5. Client side — use `window.__dshLoader__`
104
+
105
+ ```js
106
+ // Read cordis client services
107
+ const conv = window.__dshLoader__.services.get('conversation');
108
+
109
+ // Register a package alias at runtime (fallback)
110
+ window.__dshLoader__.registerPackageAlias('@old/pkg', '@new/pkg');
111
+ ```
112
+
113
+ #### 6. Build config — mark stable subpaths as external
114
+
115
+ ```ts
116
+ const CLIENT_EXTERNALS = [
117
+ 'react', 'react/jsx-runtime', 'react-dom', 'react-dom/client', 'cordis',
118
+ '@dsh-plugin/dsh-loader/ui-primitives',
119
+ '@dsh-plugin/dsh-loader/ui-slots',
120
+ '@dsh-plugin/dsh-loader/ui-settings',
121
+ '@dsh-plugin/dsh-loader/web-react',
122
+ '@dsh-plugin/dsh-loader/schema-form',
123
+ '@dsh-plugin/dsh-loader/runtime',
124
+ ]
125
+ ```
126
+
127
+ ### How it works
128
+
129
+ ```
130
+ plugin ──▶ ctx.dshLoader.{settings,web,services} ──▶ dshloader adapter
131
+
132
+
133
+ real dsh (current version)
134
+
135
+ plugin bundle ──▶ require('@dsh-plugin/dsh-loader/ui-primitives')
136
+
137
+ ▼ (__ModuleLoader__ wrapper maps stable name)
138
+ require('@deepseek-ai/dsh-client-ui-primitives')
139
+
140
+
141
+ dsh module table
142
+ ```
143
+
144
+ 1. **Version detection** reads `node_modules/@deepseek-ai/dsh/package.json`
145
+ (or `DSHLOADER_DSH_VERSION` for tests/override).
146
+ 2. **AdapterRegistry** selects the best adapter for the detected version
147
+ (exact → range → nearest-low fallback → clear error).
148
+ 3. The selected **adapter** registers service aliases, installs package-name
149
+ mapping hooks (host: `Module._resolveFilename`; client:
150
+ `__ModuleLoader__.load` wrapper), and (only when opted in) the settings
151
+ whitelist bypass bridge. All registrations use `ctx.reflect.provide` /
152
+ `ctx.effect`, so cordis auto-recycles them on fiber unload.
153
+
154
+ > **Load order does not matter.** cordis is reactive dependency injection:
155
+ > plugins declaring `inject: [...]` stay `PENDING` until the alias is
156
+ > provided, regardless of where dshloader sits in `cordis.patch.yml`.
157
+
158
+ ### Settings whitelist bypass (`exposeAllNamespaces`)
159
+
160
+ By default dshloader **does not** bypass the official settings namespace
161
+ whitelist. Opt in explicitly:
162
+
163
+ - env: `DSHLOADER_EXPOSE_ALL_SETTINGS=1`
164
+ - profile `package.json`: `dsh.dshloader.exposeAllNamespaces: true`
165
+
166
+ > **Security trade-off**: enabling this removes the official default-deny
167
+ > boundary for browser settings access. Only enable it in profiles where you
168
+ > trust every installed plugin.
169
+
170
+ ### CLI
171
+
172
+ ```
173
+ dshloader setup <profile> Inject dshloader into a profile (dep + patch).
174
+ dshloader dump-config <profile> Run `dsh --profile <name> --dump-config`.
175
+ dshloader info [profile] Print loader version, detected dsh version,
176
+ selected adapter.
177
+ ```
178
+
179
+ ### Rollback / disable
180
+
181
+ - Disable per launch: `DSHLOADER_DISABLE=1 dsh web`
182
+ - Remove: `dsh plugin --profile <name> rm @dsh-plugin/dsh-loader`
183
+
184
+ ### Project layout
185
+
186
+ ```
187
+ src/
188
+ index.js host bundle entry (name / inject / apply)
189
+ client.js client bundle entry (immediately tier)
190
+ api.js DshLoaderHostAPI construction
191
+ registry.js AdapterRegistry + version detection
192
+ version.js loader version + log prefix
193
+ stable/ stable subpath re-exports (ui-primitives, tools, ...)
194
+ services/
195
+ settings.js settings stable API
196
+ web.js web stable API
197
+ services.js services stable API (get / alias)
198
+ adapters/
199
+ dsh-1-x.js dsh 1.x adapter
200
+ index.js adapter registration
201
+ setup.mjs profile injection + dump-config + info
202
+ bin/dshloader.mjs CLI entry
203
+ docs/
204
+ api.md full API reference (Chinese)
205
+ design.md design document (Chinese)
206
+ tests/ L1 (unit) / module (L2) / integration (L3)
207
+ examples/
208
+ sample-plugin/ minimal example plugin
209
+ dsh-aux-state/ example using ctx.dshLoader only
210
+ ```
211
+
212
+ ### Develop
213
+
214
+ ```sh
215
+ pnpm install
216
+ npm test # all tests
217
+ npm run test:l1 # unit
218
+ npm run test:l2 # module
219
+ npm run test:l3 # integration
220
+ ```
221
+
222
+ Node.js >= 18, `node --test`, no extra test framework.
223
+
224
+ ### License
225
+
226
+ BSD-3-Clause
@@ -0,0 +1,202 @@
1
+ # dshloader
2
+
3
+ [English](README.md) | [中文](#中文)
4
+
5
+ ---
6
+
7
+ ## 中文
8
+
9
+ **dsh**(DeepSeek Harness)cordis bundle 插件的运行时兼容层。dshloader 通过版本感知的**适配器注册表**,把第三方插件与 dsh 的内部服务名、模块路径、包名、RPC 细节解耦——dsh 升级改了内部 API 时,只需升级 dshloader,插件不用改。
10
+
11
+ ### 为什么需要
12
+
13
+ dsh 迭代很快,内部 API 在版本间会变:
14
+
15
+ - `httpServer` 被重命名为 `webServer`——旧插件注入 `httpServer` 会永远挂起。
16
+ - 深层源码导入如
17
+ `@deepseek-ai/dsh-client-runtime/src/client/sessions/context-provenance.ts`
18
+ 在 dsh 不再发布 `src/` 时直接报错。
19
+ - 客户端 UI 包如 `@deepseek-ai/dsh-client-ui-primitives` 在未来 dsh 版本中可能改名,直接 import 的插件全部会坏。
20
+ - 官方 `dsh-host-apiproxy` 硬编码了 settings namespace 白名单,第三方设置卡片无法出现在 Web UI 中。
21
+
22
+ dshloader 把这些(以及未来的)破坏性变更吸收到**稳定 API** 后面:host 侧的 `ctx.dshLoader`、浏览器侧的 `window.__dshLoader__`、以及包导入的 `@dsh-plugin/dsh-loader/*` 稳定 subpath。
23
+
24
+ ### 快速上手
25
+
26
+ #### 1. 安装 dshloader 到 profile
27
+
28
+ ```sh
29
+ dsh plugin --profile <name> add /path/to/dshloader
30
+ # 或
31
+ DSH_HOME=~/.dsh npx dshloader setup <name>
32
+ ```
33
+
34
+ #### 2. 插件 `package.json`——只依赖 dshloader
35
+
36
+ ```json
37
+ {
38
+ "dependencies": {
39
+ "@dsh-plugin/dsh-loader": "link:..."
40
+ }
41
+ }
42
+ ```
43
+
44
+ > **插件不允许声明任何 `@deepseek-ai/*` 依赖。** 所有 dsh 包都通过 dshloader 的稳定 subpath 访问。
45
+
46
+ #### 3. Host 侧——用 `ctx.dshLoader`
47
+
48
+ ```js
49
+ export const inject = ['dshLoader'];
50
+
51
+ export async function apply(ctx) {
52
+ // Settings:注册 namespace
53
+ const scope = ctx.dshLoader.settings.register('my-plugin', schema);
54
+
55
+ // Web:注册路由和 WebSocket upgrade
56
+ ctx.dshLoader.web.get('/api/my-plugin/status', (req, res) => res.json({ ok: true }));
57
+ ctx.dshLoader.web.registerUpgrade({ path: '/ws/my-plugin', handler: fn });
58
+
59
+ // Services:读取 cordis 服务
60
+ const sessions = ctx.dshLoader.services.get('sessions');
61
+ }
62
+ ```
63
+
64
+ #### 4. 通过稳定 subpath 导入 dsh 包
65
+
66
+ ```js
67
+ // Host 包
68
+ const { defineTool } = require('@dsh-plugin/dsh-loader/tools');
69
+
70
+ // Client UI 包(在 client bundle 源码中)
71
+ import { IconCloseFill14 } from '@dsh-plugin/dsh-loader/ui-primitives';
72
+ ```
73
+
74
+ **稳定 subpath → dsh 真实包名映射(dsh 1.x):**
75
+
76
+ | 稳定 subpath | dsh 真实包名 |
77
+ |---|---|
78
+ | `@dsh-plugin/dsh-loader/tools` | `@deepseek-ai/dsh-tools` |
79
+ | `@dsh-plugin/dsh-loader/llm` | `@deepseek-ai/dsh-llm` |
80
+ | `@dsh-plugin/dsh-loader/agent` | `@deepseek-ai/dsh-agent` |
81
+ | `@dsh-plugin/dsh-loader/settings` | `@deepseek-ai/dsh-settings` |
82
+ | `@dsh-plugin/dsh-loader/ui-primitives` | `@deepseek-ai/dsh-client-ui-primitives` |
83
+ | `@dsh-plugin/dsh-loader/ui-slots` | `@deepseek-ai/dsh-client-ui-slots` |
84
+ | `@dsh-plugin/dsh-loader/ui-settings` | `@deepseek-ai/dsh-client-ui-settings/client` |
85
+ | `@dsh-plugin/dsh-loader/web-react` | `@deepseek-ai/dsh-client-web-react` |
86
+ | `@dsh-plugin/dsh-loader/schema-form` | `@deepseek-ai/dsh-client-schema-form` |
87
+ | `@dsh-plugin/dsh-loader/runtime` | `@deepseek-ai/dsh-client-runtime/client` |
88
+
89
+ dsh 改包名时,只需改 dshloader 适配器——插件源码和 bundle 不用动。
90
+
91
+ #### 5. Client 侧——用 `window.__dshLoader__`
92
+
93
+ ```js
94
+ // 读取 cordis client 服务
95
+ const conv = window.__dshLoader__.services.get('conversation');
96
+
97
+ // 运行时注册包名别名(兜底用)
98
+ window.__dshLoader__.registerPackageAlias('@old/pkg', '@new/pkg');
99
+ ```
100
+
101
+ #### 6. 构建配置——把稳定 subpath 加入 external
102
+
103
+ ```ts
104
+ const CLIENT_EXTERNALS = [
105
+ 'react', 'react/jsx-runtime', 'react-dom', 'react-dom/client', 'cordis',
106
+ '@dsh-plugin/dsh-loader/ui-primitives',
107
+ '@dsh-plugin/dsh-loader/ui-slots',
108
+ '@dsh-plugin/dsh-loader/ui-settings',
109
+ '@dsh-plugin/dsh-loader/web-react',
110
+ '@dsh-plugin/dsh-loader/schema-form',
111
+ '@dsh-plugin/dsh-loader/runtime',
112
+ ]
113
+ ```
114
+
115
+ ### 工作原理
116
+
117
+ ```
118
+ plugin ──▶ ctx.dshLoader.{settings,web,services} ──▶ dshloader 适配器
119
+
120
+
121
+ 真实 dsh(当前版本)
122
+
123
+ plugin bundle ──▶ require('@dsh-plugin/dsh-loader/ui-primitives')
124
+
125
+ ▼(__ModuleLoader__ wrapper 映射稳定名)
126
+ require('@deepseek-ai/dsh-client-ui-primitives')
127
+
128
+
129
+ dsh 模块表
130
+ ```
131
+
132
+ 1. **版本探测** 读取 `node_modules/@deepseek-ai/dsh/package.json`(或 `DSHLOADER_DSH_VERSION`)。
133
+ 2. **适配器注册表** 选择最适合当前版本的适配器(精确 → 范围 → 最近低版本回退 → 报错)。
134
+ 3. 选中的**适配器** 注册服务别名、安装包名映射钩子(host: `Module._resolveFilename`;client: `__ModuleLoader__.load` wrapper)、以及(仅在开启时)settings 白名单绕过桥接。所有注册通过 `ctx.reflect.provide` / `ctx.effect`,cordis 在 fiber 卸载时自动回收。
135
+
136
+ > **加载顺序无关。** cordis 是响应式依赖注入:声明了 `inject: [...]` 的插件会停在 `PENDING` 状态,直到依赖的服务出现,与 dshloader 在 `cordis.patch.yml` 中的位置无关。
137
+
138
+ ### Settings 白名单绕过(`exposeAllNamespaces`)
139
+
140
+ 默认**不绕过**官方 settings namespace 白名单。需要时显式开启:
141
+
142
+ - 环境变量:`DSHLOADER_EXPOSE_ALL_SETTINGS=1`
143
+ - profile `package.json`:`dsh.dshloader.exposeAllNamespaces: true`
144
+
145
+ > **安全权衡**:开启后会移除官方对浏览器 settings 访问的默认拒绝边界。仅在信任所有已安装插件的 profile 中开启。
146
+
147
+ ### CLI
148
+
149
+ ```
150
+ dshloader setup <profile> 注入 dshloader 到 profile(依赖 + patch)。
151
+ dshloader dump-config <profile> 运行 `dsh --profile <name> --dump-config`。
152
+ dshloader info [profile] 打印 loader 版本、探测到的 dsh 版本、选中的适配器。
153
+ ```
154
+
155
+ ### 回滚 / 禁用
156
+
157
+ - 单次启动禁用:`DSHLOADER_DISABLE=1 dsh web`
158
+ - 移除:`dsh plugin --profile <name> rm @dsh-plugin/dsh-loader`
159
+
160
+ ### 项目结构
161
+
162
+ ```
163
+ src/
164
+ index.js host bundle 入口(name / inject / apply)
165
+ client.js client bundle 入口(immediately tier)
166
+ api.js DshLoaderHostAPI 构造
167
+ registry.js 适配器注册表 + 版本探测
168
+ version.js loader 版本 + 日志前缀
169
+ stable/ 稳定 subpath re-export(ui-primitives、tools 等)
170
+ services/
171
+ settings.js settings 稳定 API
172
+ web.js web 稳定 API
173
+ services.js services 稳定 API(get / alias)
174
+ adapters/
175
+ dsh-1-x.js dsh 1.x 适配器
176
+ index.js 适配器注册
177
+ setup.mjs profile 注入 + dump-config + info
178
+ bin/dshloader.mjs CLI 入口
179
+ docs/
180
+ api.md 完整 API 参考
181
+ design.md 设计文档
182
+ tests/ L1(单元)/ module(L2)/ integration(L3)
183
+ examples/
184
+ sample-plugin/ 最小示例插件
185
+ dsh-aux-state/ 仅用 ctx.dshLoader 的示例
186
+ ```
187
+
188
+ ### 开发
189
+
190
+ ```sh
191
+ pnpm install
192
+ npm test # 全部测试
193
+ npm run test:l1 # 单元测试
194
+ npm run test:l2 # 模块测试
195
+ npm run test:l3 # 集成测试
196
+ ```
197
+
198
+ Node.js >= 18,`node --test`,无额外测试框架。
199
+
200
+ ### 许可证
201
+
202
+ BSD-3-Clause
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ // dshloader CLI entry (M6). Subcommands: setup, dump-config, info.
3
+ import { setupProfile, dumpConfig, info } from '../src/setup.mjs';
4
+
5
+ const [cmd, ...rest] = process.argv.slice(2);
6
+
7
+ function usage() {
8
+ console.log(`dshloader <command> [args]
9
+
10
+ Commands:
11
+ setup <profile> Inject dshloader into a profile (dependency + patch).
12
+ dump-config <profile> Run \`dsh --profile <name> --dump-config\` to validate.
13
+ info [profile] Print dshloader version, detected dsh version, adapter.`);
14
+ }
15
+
16
+ try {
17
+ switch (cmd) {
18
+ case 'setup': {
19
+ const profile = rest[0];
20
+ if (!profile) throw new Error('setup requires a profile name');
21
+ setupProfile(profile);
22
+ break;
23
+ }
24
+ case 'dump-config': {
25
+ const profile = rest[0];
26
+ if (!profile) throw new Error('dump-config requires a profile name');
27
+ const { ok, output } = dumpConfig(profile);
28
+ process.stdout.write(output);
29
+ process.exit(ok ? 0 : 1);
30
+ }
31
+ case 'info': {
32
+ info(rest[0]);
33
+ break;
34
+ }
35
+ default:
36
+ usage();
37
+ process.exit(cmd ? 1 : 0);
38
+ }
39
+ } catch (error) {
40
+ console.error(error.message);
41
+ process.exit(1);
42
+ }
@@ -0,0 +1,8 @@
1
+ # dsh bundle patch: mounts dshloader into a profile's cordis layer stack.
2
+ # Position in the `insert` list does NOT affect whether service aliases /
3
+ # module redirects take effect — cordis is reactive dependency injection,
4
+ # so downstream plugins declaring `inject: [...]` stay PENDING until the
5
+ # alias is provided, regardless of file order. See docs/design.md §1.2/§6.2.
6
+ - insert:
7
+ - id: dsh-loader
8
+ name: '@dsh-plugin/dsh-loader'
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@dsh-plugin/dsh-loader",
3
+ "version": "1.0.0",
4
+ "description": "Runtime compatibility shim for dsh (DeepSeek Harness) cordis bundle plugins: decouples third-party plugins from real dsh internal service names, module paths, and RPC details via a version-aware adapter registry.",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./client": "./lib/client.js",
10
+ "./registry": "./src/registry.js",
11
+ "./cordis.patch.yml": "./cordis.patch.yml",
12
+ "./package.json": "./package.json",
13
+ "./ui-primitives": "./src/stable/ui-primitives.js",
14
+ "./ui-settings": "./src/stable/ui-settings.js",
15
+ "./ui-slots": "./src/stable/ui-slots.js",
16
+ "./web-react": "./src/stable/web-react.js",
17
+ "./schema-form": "./src/stable/schema-form.js",
18
+ "./runtime": "./src/stable/runtime.js",
19
+ "./tools": "./src/stable/tools.js",
20
+ "./llm": "./src/stable/llm.js",
21
+ "./agent": "./src/stable/agent.js",
22
+ "./settings": "./src/stable/settings.js"
23
+ },
24
+ "bin": {
25
+ "dshloader": "./bin/dshloader.mjs"
26
+ },
27
+ "files": [
28
+ "src",
29
+ "bin",
30
+ "lib/client.js",
31
+ "lib/client.js.map",
32
+ "cordis.patch.yml",
33
+ "README.md"
34
+ ],
35
+ "scripts": {
36
+ "build:client": "tsdown --config tsdown.client.config.mjs",
37
+ "test": "node --test \"tests/**/*.test.mjs\"",
38
+ "test:l1": "node --test \"tests/*.test.mjs\"",
39
+ "test:l2": "node --test \"tests/module/*.test.mjs\"",
40
+ "test:l3": "node --test \"tests/integration/*.test.mjs\"",
41
+ "setup": "node bin/dshloader.mjs setup"
42
+ },
43
+ "engines": {
44
+ "node": ">=18"
45
+ },
46
+ "dependencies": {
47
+ "semver": "^7.8.5"
48
+ },
49
+ "dsh": {
50
+ "bundle": {
51
+ "patch": "./cordis.patch.yml"
52
+ },
53
+ "client": {
54
+ "platform": "web",
55
+ "immediately": true
56
+ }
57
+ },
58
+ "license": "BSD-3-Clause"
59
+ }