dsh-wsl-tool 1.10.0 → 1.10.2
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/PUBLISHING.md +16 -0
- package/index.js +19 -7
- package/lib/client.js +67 -7
- package/lib/schema.js +35 -0
- package/package.json +1 -1
package/PUBLISHING.md
CHANGED
|
@@ -67,6 +67,22 @@ both that the entry stays relative and that it resolves.
|
|
|
67
67
|
once at load. A patch change (the tool row, or the sidebar terminal override)
|
|
68
68
|
is read at composition time, so it needs that restart too.
|
|
69
69
|
|
|
70
|
+
**Then prove the schema really builds from the installed copy**, because this
|
|
71
|
+
is the one failure the tools survive:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
cd ~/.dsh/profiles/desktop/node_modules/dsh-wsl
|
|
75
|
+
node -e "import('./index.js').then(m => console.log(typeof m.Config, JSON.stringify(m.Config({}))))"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
It must print `function` and the resolved defaults. If it prints `undefined`,
|
|
79
|
+
the plugin declares no `Config`, the platform has no schema to project, the
|
|
80
|
+
entry's config status stays `absent` and the sidebar panel renders without a
|
|
81
|
+
single switch while all three tools keep working. The fragile step is the
|
|
82
|
+
interop hop: `@deepseek-ai/schemastery` exports its builder as the **default**
|
|
83
|
+
export, so `const { Schema } = await import(...)` silently yields `undefined`
|
|
84
|
+
(see `lib/schema.js`, and the regression test that pins the picking).
|
|
85
|
+
|
|
70
86
|
3. The tag runs the pipeline. The order is deliberate — the release asset goes
|
|
71
87
|
**first** because it is the market's critical path, then npm, so a failing npm
|
|
72
88
|
publish fails the run loudly without withholding the release.
|
package/index.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
// every switch below is consulted once, in `apply`.
|
|
23
23
|
|
|
24
24
|
import { resolveConfig } from './lib/config.js'
|
|
25
|
+
import { pickSchemaBuilder } from './lib/schema.js'
|
|
25
26
|
import { createRunner } from './lib/runner.js'
|
|
26
27
|
import { createWslTool } from './lib/tools/wsl.js'
|
|
27
28
|
import { createWslPathTool } from './lib/tools/wsl-path.js'
|
|
@@ -31,18 +32,29 @@ export const name = 'tool-wsl'
|
|
|
31
32
|
export const inject = ['tools', 'subprocess']
|
|
32
33
|
|
|
33
34
|
// The schema is what gives this plugin a settings surface at all: the platform
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
35
|
+
// projects the config of every entry that declares one (`ctx.settings.describe()`
|
|
36
|
+
// is keyed by entry id), and the sidebar panel reads and writes that projection.
|
|
37
|
+
// It needs `@deepseek-ai/schemastery`, which a real DSH profile supplies (the
|
|
38
|
+
// plugin declares it as an optional peer dependency) but which is absent when this
|
|
39
|
+
// module is imported standalone, as `test/smoke.mjs` does. A soft import keeps the
|
|
40
|
+
// tools runnable there: without a schema every switch keeps its default and only
|
|
41
|
+
// the panel is missing — and `lib/schema.js` explains why the PICKING, not just
|
|
42
|
+
// the import, is the fragile part.
|
|
40
43
|
let Schema = null
|
|
41
44
|
try {
|
|
42
|
-
|
|
45
|
+
Schema = pickSchemaBuilder(await import('@deepseek-ai/schemastery'))
|
|
43
46
|
} catch {
|
|
44
47
|
Schema = null
|
|
45
48
|
}
|
|
49
|
+
if (Schema === null) {
|
|
50
|
+
// Not fatal — the tools run on their defaults — but it is exactly the difference
|
|
51
|
+
// between a panel with switches and one without, so say so instead of failing
|
|
52
|
+
// quietly.
|
|
53
|
+
console.warn(
|
|
54
|
+
'dsh-wsl-tool: no schema builder (@deepseek-ai/schemastery) — the tools run on ' +
|
|
55
|
+
'their defaults and the sidebar panel will not offer the switches',
|
|
56
|
+
)
|
|
57
|
+
}
|
|
46
58
|
|
|
47
59
|
/**
|
|
48
60
|
* The plugin's own configuration.
|
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
|
|
@@ -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/lib/schema.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// The schema builder behind `Config`, resolved through one interop hop.
|
|
2
|
+
//
|
|
3
|
+
// `@deepseek-ai/schemastery` exports the builder as its DEFAULT export: the module
|
|
4
|
+
// namespace of `import('@deepseek-ai/schemastery')` is `{ default: Schema }`, with
|
|
5
|
+
// no named `Schema`. Code that destructures `{ Schema }` from it gets `undefined`
|
|
6
|
+
// without any error, and `Schema?.object({...})` then yields `undefined` — so the
|
|
7
|
+
// plugin declares no `Config`, the platform has no schema to project, its settings
|
|
8
|
+
// namespace never appears, and the sidebar panel renders without switches. That is
|
|
9
|
+
// a quiet failure two layers away from its cause (measured: the Loader entry's
|
|
10
|
+
// config status stayed `absent` while the tools themselves worked), so the shape is
|
|
11
|
+
// pinned here, in one place, with a unit test.
|
|
12
|
+
//
|
|
13
|
+
// A named `Schema` export and a `Schema` property on the default are accepted too:
|
|
14
|
+
// other DSH packages re-export the builder in those shapes, and the check below is
|
|
15
|
+
// about what can actually build a schema, not about this one package.
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Pick the schema builder out of a module namespace.
|
|
19
|
+
*
|
|
20
|
+
* @param namespace - a module namespace (`await import(...)`), or anything else.
|
|
21
|
+
* @returns the builder, or null when the namespace carries none.
|
|
22
|
+
*/
|
|
23
|
+
export function pickSchemaBuilder(namespace) {
|
|
24
|
+
if (namespace === null || typeof namespace !== 'object') return null
|
|
25
|
+
const candidates = [namespace.Schema, namespace.default?.Schema, namespace.default]
|
|
26
|
+
for (const candidate of candidates) {
|
|
27
|
+
// `object` is the entry point every Config needs; requiring it here is what
|
|
28
|
+
// makes a wrong export shape look like a missing builder instead of a schema
|
|
29
|
+
// that throws later, while the composition is already being built.
|
|
30
|
+
if (candidate !== null && candidate !== undefined && typeof candidate.object === 'function') {
|
|
31
|
+
return candidate
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null
|
|
35
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-wsl-tool",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.2",
|
|
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",
|