dsh-wsl-tool 1.10.1 → 1.10.3
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/index.js +41 -14
- package/lib/client.js +68 -8
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -64,26 +64,37 @@ if (Schema === null) {
|
|
|
64
64
|
* documents the precedence: this configuration wins, the environment is the
|
|
65
65
|
* deployment default, the built-in defaults are last.
|
|
66
66
|
*
|
|
67
|
-
* `distro` and `timeoutMs` are
|
|
68
|
-
*
|
|
69
|
-
*
|
|
67
|
+
* `distro` and `timeoutMs` are values rather than switches, but they are here so a
|
|
68
|
+
* patch (or a future row in the panel) can set them without a schema change.
|
|
69
|
+
*
|
|
70
|
+
* EVERY field is `.volatile()`, and that is not decoration: DSH's settings service
|
|
71
|
+
* projects a plugin's config into a form only through `volatileForm()`, which keeps
|
|
72
|
+
* a field only when its nearest ancestor is marked volatile — a schema without one
|
|
73
|
+
* contributes no form at all, so the entry never reaches `ctx.settings.describe()`,
|
|
74
|
+
* its namespace is never served to the browser, and the sidebar panel waits for a
|
|
75
|
+
* scope that will never arrive. Volatile is also what the platform means by "can be
|
|
76
|
+
* edited without remounting", which is what the update hook below relies on.
|
|
77
|
+
*
|
|
78
|
+
* A volatile field must not sit inside another volatile one, so `tools` is a plain
|
|
79
|
+
* object whose leaf switches are volatile, not a volatile object.
|
|
70
80
|
*/
|
|
71
81
|
export const Config = Schema?.object({
|
|
72
82
|
tools: Schema.object({
|
|
73
|
-
wsl: Schema.boolean().default(true).description('注册 `wsl` 工具:在 WSL 里执行 Linux 命令。'),
|
|
74
|
-
path: Schema.boolean().default(true).description('注册 `wsl-path` 工具:Windows 路径与 /mnt/... 互转。'),
|
|
75
|
-
env: Schema.boolean().default(true).description('注册 `wsl-env` 工具:汇总 WSL 环境能力。'),
|
|
76
|
-
}).description('
|
|
77
|
-
backgroundJobs: Schema.boolean().default(true).description('允许 `runInBackground`,由内置 job 工具读回结果。'),
|
|
78
|
-
translatePaths: Schema.boolean().default(true).description('默认把命令里的 Windows 路径转成 /mnt/...。'),
|
|
79
|
-
startInSessionWorkspace: Schema.boolean().default(false).description('未传 `workdir` 时从会话工作区开始,而不是 Linux 家目录。'),
|
|
80
|
-
dangerGuard: Schema.boolean().default(true).description('危险命令必须显式 `allowDangerous` 才放行。关掉后模型可直接删除/分区。'),
|
|
81
|
-
distro: Schema.string().default('').description('要固定使用的发行版;留空则用系统默认(也可用 DSH_WSL_DISTRO)。'),
|
|
82
|
-
timeoutMs: Schema.number().default(0).description('默认命令超时毫秒数;0 表示用内置默认(也可用 DSH_WSL_TIMEOUT_MS)。'),
|
|
83
|
+
wsl: Schema.boolean().default(true).description('注册 `wsl` 工具:在 WSL 里执行 Linux 命令。').volatile(),
|
|
84
|
+
path: Schema.boolean().default(true).description('注册 `wsl-path` 工具:Windows 路径与 /mnt/... 互转。').volatile(),
|
|
85
|
+
env: Schema.boolean().default(true).description('注册 `wsl-env` 工具:汇总 WSL 环境能力。').volatile(),
|
|
86
|
+
}).description('要注册哪几个工具;这三个开关在下次启动 DSH 后生效。'),
|
|
87
|
+
backgroundJobs: Schema.boolean().default(true).description('允许 `runInBackground`,由内置 job 工具读回结果。').volatile(),
|
|
88
|
+
translatePaths: Schema.boolean().default(true).description('默认把命令里的 Windows 路径转成 /mnt/...。').volatile(),
|
|
89
|
+
startInSessionWorkspace: Schema.boolean().default(false).description('未传 `workdir` 时从会话工作区开始,而不是 Linux 家目录。').volatile(),
|
|
90
|
+
dangerGuard: Schema.boolean().default(true).description('危险命令必须显式 `allowDangerous` 才放行。关掉后模型可直接删除/分区。').volatile(),
|
|
91
|
+
distro: Schema.string().default('').description('要固定使用的发行版;留空则用系统默认(也可用 DSH_WSL_DISTRO)。').volatile(),
|
|
92
|
+
timeoutMs: Schema.number().default(0).description('默认命令超时毫秒数;0 表示用内置默认(也可用 DSH_WSL_TIMEOUT_MS)。').volatile(),
|
|
83
93
|
}).description('dsh-wsl 的功能开关与默认值。')
|
|
84
94
|
|
|
85
95
|
export function apply(ctx, settings = {}) {
|
|
86
|
-
// Resolved once per mount
|
|
96
|
+
// Resolved once per mount. Behaviour reads this object at call time, so the
|
|
97
|
+
// volatile-update hook below can change it in place without remounting.
|
|
87
98
|
const config = resolveConfig(process.env, settings)
|
|
88
99
|
const runner = createRunner(ctx, config)
|
|
89
100
|
// `ctx` is passed to the `wsl` tool for the optional `jobs` service only
|
|
@@ -92,4 +103,20 @@ export function apply(ctx, settings = {}) {
|
|
|
92
103
|
if (config.tools.wsl) ctx.tools.register(createWslTool({ ctx, config, runner }))
|
|
93
104
|
if (config.tools.path) ctx.tools.register(createWslPathTool({ config, runner }))
|
|
94
105
|
if (config.tools.env) ctx.tools.register(createWslEnvTool({ config, runner }))
|
|
106
|
+
|
|
107
|
+
// A volatile edit is written into the same object the loader handed us and then
|
|
108
|
+
// announced, so re-resolving in place is what makes the behaviour switches
|
|
109
|
+
// (background jobs, path translation, the guard, workdir, distro, timeout) take
|
|
110
|
+
// effect without a restart. The three `tools.*` switches are the exception: they
|
|
111
|
+
// decide which tools were registered, so they need the next start, and the panel
|
|
112
|
+
// says so on those rows.
|
|
113
|
+
if (typeof ctx.on === 'function') {
|
|
114
|
+
ctx.on('loader/volatile-update', () => {
|
|
115
|
+
try {
|
|
116
|
+
Object.assign(config, resolveConfig(process.env, settings))
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error('dsh-wsl-tool: applying a live settings change failed', error)
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
}
|
|
95
122
|
}
|
package/lib/client.js
CHANGED
|
@@ -163,6 +163,25 @@ window.__ModuleLoader__.load({
|
|
|
163
163
|
revision: undefined,
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
/**
|
|
167
|
+
* No scope yet — the Host has not listed this namespace, or its `describe()`
|
|
168
|
+
* round trip is still in flight.
|
|
169
|
+
*
|
|
170
|
+
* This is deliberately NOT the unavailable snapshot: a panel that reads "this
|
|
171
|
+
* DSH does not provide the plugin's settings scope" while it is merely still
|
|
172
|
+
* waiting sends whoever is debugging it after the wrong thing (measured: it
|
|
173
|
+
* said exactly that for a namespace the Host was already serving, because the
|
|
174
|
+
* form had been taken before the round trip settled).
|
|
175
|
+
*/
|
|
176
|
+
const PENDING_SNAPSHOT = {
|
|
177
|
+
status: 'loading',
|
|
178
|
+
value: undefined,
|
|
179
|
+
base: undefined,
|
|
180
|
+
user: undefined,
|
|
181
|
+
writable: false,
|
|
182
|
+
revision: undefined,
|
|
183
|
+
}
|
|
184
|
+
|
|
166
185
|
// ---------------------------------------------------------------- helpers
|
|
167
186
|
|
|
168
187
|
/** Read one path out of a possibly absent object, never throwing. */
|
|
@@ -218,7 +237,7 @@ window.__ModuleLoader__.load({
|
|
|
218
237
|
|
|
219
238
|
const read = () => {
|
|
220
239
|
if (scope === undefined || scope === null || typeof scope.getSnapshot !== 'function') {
|
|
221
|
-
return
|
|
240
|
+
return PENDING_SNAPSHOT
|
|
222
241
|
}
|
|
223
242
|
try {
|
|
224
243
|
const snapshot = scope.getSnapshot()
|
|
@@ -231,8 +250,15 @@ window.__ModuleLoader__.load({
|
|
|
231
250
|
|
|
232
251
|
return {
|
|
233
252
|
/** Whether the `configForms` service was there at all. A service this DSH
|
|
234
|
-
* does not ship reads differently from a row it does not serve
|
|
253
|
+
* does not ship reads differently from a row it does not serve — and both
|
|
254
|
+
* read differently from a namespace that simply has not been listed yet. */
|
|
235
255
|
hasService: () => served,
|
|
256
|
+
/** Record that the service exists, before any scope is available. Without
|
|
257
|
+
* this, a namespace that has not been served yet reads as "this DSH has no
|
|
258
|
+
* configuration forms", which is a different (and wrong) diagnosis. */
|
|
259
|
+
noteService: () => {
|
|
260
|
+
served = true
|
|
261
|
+
},
|
|
236
262
|
/** Bind the scope once the service appears. */
|
|
237
263
|
attach: (next) => {
|
|
238
264
|
if (next === undefined || next === null) return
|
|
@@ -528,7 +554,7 @@ window.__ModuleLoader__.load({
|
|
|
528
554
|
{ key: 'header', style: HEADER_STYLE },
|
|
529
555
|
React.createElement('h2', { style: TITLE_STYLE }, 'WSL'),
|
|
530
556
|
React.createElement('p', { style: DESCRIPTION_STYLE }, '通过 WSL 在 Windows 上执行 Linux 命令的开关与说明'),
|
|
531
|
-
React.createElement('p', { style: NOTE_STYLE }, '
|
|
557
|
+
React.createElement('p', { style: NOTE_STYLE }, '工具开关在下次启动 DSH 后生效;其余开关即时生效。'),
|
|
532
558
|
),
|
|
533
559
|
)
|
|
534
560
|
|
|
@@ -542,7 +568,8 @@ window.__ModuleLoader__.load({
|
|
|
542
568
|
),
|
|
543
569
|
)
|
|
544
570
|
} else if (snapshot.status === 'loading') {
|
|
545
|
-
children.push(React.createElement('p', { key: 'note-loading', style: NOTE_STYLE },
|
|
571
|
+
children.push(React.createElement('p', { key: 'note-loading', style: NOTE_STYLE },
|
|
572
|
+
'正在等待 Host 提供该插件的配置作用域(' + CONFIG_ROW_ID + ')…'))
|
|
546
573
|
} else if (snapshot.status !== 'ready') {
|
|
547
574
|
children.push(
|
|
548
575
|
React.createElement('p', { key: 'note-unavailable', style: NOTE_STYLE }, '此 DSH 未提供该插件的配置作用域'),
|
|
@@ -632,18 +659,51 @@ window.__ModuleLoader__.load({
|
|
|
632
659
|
try {
|
|
633
660
|
if (typeof ctx.inject === 'function') {
|
|
634
661
|
ctx.inject(['configForms'], (inner) => {
|
|
635
|
-
|
|
662
|
+
const configForms = inner.configForms
|
|
663
|
+
if (configForms === undefined || configForms === null) return
|
|
664
|
+
// The service is here; whether it lists our namespace is a separate
|
|
665
|
+
// question that `whileServed` answers below.
|
|
666
|
+
model.noteService()
|
|
667
|
+
/**
|
|
668
|
+
* Take the form once the Host actually lists the namespace.
|
|
669
|
+
*
|
|
670
|
+
* A form fetched before the `describe()` round trip settles is born
|
|
671
|
+
* with its persistence already fixed to "not host" and never upgrades
|
|
672
|
+
* — it stays `unavailable` forever. That is exactly how this panel
|
|
673
|
+
* lost every switch while the tools kept working, so the namespace is
|
|
674
|
+
* awaited through the platform's own gate instead of being requested
|
|
675
|
+
* eagerly. The shipped settings pages use this same call.
|
|
676
|
+
*/
|
|
677
|
+
const attachWhenServed = () => {
|
|
678
|
+
if (typeof configForms.whileServed !== 'function') {
|
|
679
|
+
// Older host face: no gate to wait on, so take whatever it gives
|
|
680
|
+
// and let the model report `unavailable` honestly.
|
|
681
|
+
attach(configForms.get(CONFIG_ROW_ID))
|
|
682
|
+
return () => model.detach()
|
|
683
|
+
}
|
|
684
|
+
return configForms.whileServed([CONFIG_ROW_ID], () => {
|
|
685
|
+
attach(configForms.get(CONFIG_ROW_ID))
|
|
686
|
+
return () => model.detach()
|
|
687
|
+
})
|
|
688
|
+
}
|
|
689
|
+
const attach = (scope) => {
|
|
690
|
+
try {
|
|
691
|
+
model.attach(scope)
|
|
692
|
+
} catch (error) {
|
|
693
|
+
console.error('dsh-wsl-tool: reading the "' + CONFIG_ROW_ID + '" configuration scope failed', error)
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
let stop
|
|
636
697
|
try {
|
|
637
|
-
|
|
698
|
+
stop = attachWhenServed()
|
|
638
699
|
} catch (error) {
|
|
639
700
|
console.error('dsh-wsl-tool: this DSH serves no "' + CONFIG_ROW_ID + '" configuration row', error)
|
|
640
701
|
return
|
|
641
702
|
}
|
|
642
|
-
model.attach(scope)
|
|
643
703
|
// The subscription lives exactly as long as this fiber: `effect` runs
|
|
644
704
|
// the returned disposer on unload or recomposition.
|
|
645
705
|
if (typeof inner.effect === 'function') {
|
|
646
|
-
inner.effect(() =>
|
|
706
|
+
inner.effect(() => stop, 'dsh-wsl-tool: configuration scope')
|
|
647
707
|
}
|
|
648
708
|
})
|
|
649
709
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-wsl-tool",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.3",
|
|
4
4
|
"description": "Run Linux commands from Windows through WSL, essentially matching a native Linux DSH for command execution: background jobs, stdin, path translation, a destructive-command guard and a WSL capability report. A left-sidebar panel switches each capability on or off, each with a one-line explanation, and an opt-in patch gives the desktop app's sidebar terminal a WSL shell. WSL calls run below the DSH sandbox, and a project on a Windows drive keeps Windows filesystem semantics.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|