@dsh-workbench/adapter-probe 1.1.11 → 1.1.13

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/dist/index.d.ts CHANGED
@@ -9,6 +9,7 @@ export * from './probe-units.js';
9
9
  export * from './probe-verdict.js';
10
10
  export * from './runtime-seed.js';
11
11
  export * from './toolchain.js';
12
+ export * from './toolchain-probe.js';
12
13
  export * from './version-range.js';
13
14
  export * from './install.js';
14
15
  export * from './self-update.js';
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ export * from './probe-units.js';
9
9
  export * from './probe-verdict.js';
10
10
  export * from './runtime-seed.js';
11
11
  export * from './toolchain.js';
12
+ export * from './toolchain-probe.js';
12
13
  export * from './version-range.js';
13
14
  export * from './install.js';
14
15
  export * from './self-update.js';
@@ -3,7 +3,8 @@ import { INSTALL_TIMEOUT_MS, preflightHandler, runShell, tailLines, workbenchRoo
3
3
  import { WAKE_BROWSER_ROUTE } from '@dsh-workbench/shared/contract/protocol/preflight-wake';
4
4
  import { createDiyPresetProbe } from './diy-preset-health.js';
5
5
  import { hostSurface } from './platform.js';
6
- import { createPreflight, probeDesktopToolchain } from './preflight.js';
6
+ import { createPreflight } from './preflight.js';
7
+ import { probeDesktopToolchain } from './toolchain-probe.js';
7
8
  import { createDesktopBrowser } from './browser-wake.js';
8
9
  import { profileDirOf } from '@dsh-workbench/adapter-supply';
9
10
  import { prependPath, runtimeSeedRootOf } from './runtime-seed.js';
@@ -1,7 +1,11 @@
1
1
  import type { DshShell, RuntimeCapabilities } from '@dsh-workbench/shared';
2
2
  import type { DiyPresetVerdict } from './diy-preset-health.js';
3
3
  import { type LocalNpmProvision, type LocalPnpmProvision } from './toolchain.js';
4
+ import type { ToolchainReport } from './toolchain-probe.js';
4
5
  import type { DesktopBrowser } from './browser-wake.js';
6
+ export type PreflightFailure = 'permission' | 'network' | 'missing' | 'host' | 'unknown';
7
+ export declare function classifyFailure(text: string): PreflightFailure;
8
+ export type PreflightGuidance = 'install' | 'path' | 'shell' | 'host';
5
9
  export type PreflightStepId = 'toolchain' | 'runtime' | 'cli' | 'bridge' | 'diy' | 'warm';
6
10
  export type PreflightStepStatus = 'pending' | 'running' | 'ok' | 'warn' | 'failed';
7
11
  export interface PreflightStep {
@@ -11,17 +15,6 @@ export interface PreflightStep {
11
15
  readonly action?: string;
12
16
  readonly command?: string;
13
17
  }
14
- export interface ToolchainReport {
15
- readonly ok: boolean;
16
- readonly detail: string;
17
- readonly nodeMissing: boolean;
18
- readonly npmMissing: boolean;
19
- readonly pnpmProblem: boolean;
20
- }
21
- export type PreflightFailure = 'permission' | 'network' | 'missing' | 'unknown';
22
- export declare function classifyFailure(text: string): PreflightFailure;
23
- export type PreflightGuidance = 'install' | 'path' | 'shell';
24
- export declare function probeDesktopToolchain(shell: DshShell): Promise<ToolchainReport>;
25
18
  export type PreflightBridgeIssue = 'daemon' | 'extension' | 'profile' | 'stale' | 'unknown';
26
19
  export declare const BRIDGE_ISSUE_ORDER: readonly PreflightBridgeIssue[];
27
20
  export declare function readBridgeReport(stdout: string): {
@@ -57,6 +50,7 @@ export interface BridgeVerdict {
57
50
  readonly detail: string;
58
51
  readonly issues: readonly PreflightBridgeIssue[];
59
52
  readonly command?: string | undefined;
53
+ readonly stalled?: boolean | undefined;
60
54
  }
61
55
  export declare function canWarm(bridge: {
62
56
  ok: boolean;
package/dist/preflight.js CHANGED
@@ -1,10 +1,10 @@
1
- import { INSTALL_TIMEOUT_MS, OPENCLI_TIMEOUT_MS, PREFLIGHT_FRESH_MS, WARM_TIMEOUT_MS } from '@dsh-workbench/shared/host';
1
+ import { INSTALL_TIMEOUT_MS, OPENCLI_TIMEOUT_MS, PREFLIGHT_FRESH_MS, PREFLIGHT_VERSION_TIMEOUT_MS, WARM_TIMEOUT_MS, } from '@dsh-workbench/shared/host';
2
2
  import { tailLines } from '@dsh-workbench/shared/host';
3
3
  import { opencliCommandLine } from '@dsh-workbench/adapter-runtime';
4
4
  import { runShell } from '@dsh-workbench/shared/host';
5
5
  import { DIY_UPGRADE_COMMAND } from './diy-preset-health.js';
6
6
  import { opencliInstallCommand } from './install.js';
7
- import { desktopPnpmPath, LOCAL_NPM_VERSION, LOCAL_PNPM_VERSION, } from './toolchain.js';
7
+ import { LOCAL_NPM_VERSION, LOCAL_PNPM_VERSION } from './toolchain.js';
8
8
  import { bridgeFixCommand } from './browser-wake.js';
9
9
  export function classifyFailure(text) {
10
10
  const lower = text.toLowerCase();
@@ -16,42 +16,6 @@ export function classifyFailure(text) {
16
16
  return 'missing';
17
17
  return 'unknown';
18
18
  }
19
- async function toolVersion(shell, line) {
20
- try {
21
- const result = await runShell(shell, line, { timeoutMs: 10_000 });
22
- const text = (result.stdout || result.stderr).trim().split('\n')[0] ?? '';
23
- return { ok: result.exitCode === 0 && !result.timedOut, text };
24
- }
25
- catch {
26
- return { ok: false, text: '' };
27
- }
28
- }
29
- export async function probeDesktopToolchain(shell) {
30
- const [node, npm, pnpm] = await Promise.all([
31
- toolVersion(shell, 'node --version'),
32
- toolVersion(shell, 'npm --version'),
33
- toolVersion(shell, 'pnpm --version'),
34
- ]);
35
- const nodeMissing = !node.ok;
36
- const npmMissing = !npm.ok;
37
- const hasBundledPnpm = desktopPnpmPath() !== undefined;
38
- const pnpmMajor = /^v?(\d+)/.exec(pnpm.text)?.[1];
39
- const pnpmProblem = !pnpm.ok || pnpmMajor !== '10';
40
- const parts = [
41
- nodeMissing ? 'node 不在 PATH(Desktop 自带 Node 的 shim 没生效)' : `node ${node.text}(Desktop 自带)`,
42
- npmMissing
43
- ? hasBundledPnpm
44
- ? 'npm 缺(opencli 安装走 pnpm;插件安装/模块库仍需 npm)'
45
- : 'npm 缺(装不了 opencli:bundled pnpm 也没找到)'
46
- : `npm ${npm.text}`,
47
- pnpmProblem
48
- ? pnpm.ok
49
- ? `pnpm ${pnpm.text}(工坊按 10 方言)`
50
- : 'pnpm 缺(只影响工坊 build/install)'
51
- : `pnpm ${pnpm.text}`,
52
- ];
53
- return { ok: !nodeMissing, detail: parts.join(' · '), nodeMissing, npmMissing, pnpmProblem };
54
- }
55
19
  export const BRIDGE_ISSUE_ORDER = ['profile', 'stale', 'daemon', 'extension', 'unknown'];
56
20
  export function readBridgeReport(stdout) {
57
21
  const text = String(stdout ?? '');
@@ -115,6 +79,7 @@ export function createPreflight(options) {
115
79
  ...(options.desktopBrowser === undefined ? {} : { canWakeBrowser: true }),
116
80
  };
117
81
  let running = null;
82
+ let shellStalled = false;
118
83
  function set(next) {
119
84
  current = { ...current, ...next, at: Date.now() };
120
85
  return current;
@@ -122,9 +87,19 @@ export function createPreflight(options) {
122
87
  function patchStep(id, patch) {
123
88
  return set({ steps: current.steps.map((item) => (item.id === id ? { ...item, ...patch } : item)) });
124
89
  }
90
+ function hostFailure(detail) {
91
+ return set({ phase: 'failed', error: detail, failure: 'host', guidance: 'host' });
92
+ }
125
93
  async function exec(commandLine, timeoutMs) {
94
+ if (shellStalled) {
95
+ return { kind: 'stalled', detail: `前一条命令就没交回结果,这一步没跑:${commandLine}` };
96
+ }
126
97
  try {
127
98
  const result = await runShell(shell, commandLine, { timeoutMs });
99
+ if (result.stalled) {
100
+ shellStalled = true;
101
+ return { kind: 'stalled', detail: `没有返回(宿主没把结果交回来):${commandLine}` };
102
+ }
128
103
  if (result.timedOut) {
129
104
  return { kind: 'error', detail: `超时(${String(timeoutMs)} ms):${commandLine}` };
130
105
  }
@@ -138,13 +113,22 @@ export function createPreflight(options) {
138
113
  }
139
114
  }
140
115
  async function probeVersion() {
141
- const result = await exec(`${command} --version`, OPENCLI_TIMEOUT_MS);
142
- if (result.kind === 'ok')
143
- return { ok: true, missing: false, detail: result.stdout.trim().split('\n')[0] ?? '' };
144
- return { ok: false, missing: result.kind === 'nonzero', detail: result.detail };
116
+ const result = await exec(`${command} --version`, PREFLIGHT_VERSION_TIMEOUT_MS);
117
+ if (result.kind === 'ok') {
118
+ return { ok: true, missing: false, detail: result.stdout.trim().split('\n')[0] ?? '', stalled: false };
119
+ }
120
+ return {
121
+ ok: false,
122
+ missing: result.kind === 'nonzero',
123
+ detail: result.detail,
124
+ stalled: result.kind === 'stalled',
125
+ };
145
126
  }
146
127
  async function probeBridge() {
147
128
  const result = await exec(`${command} doctor`, OPENCLI_TIMEOUT_MS);
129
+ if (result.kind === 'stalled') {
130
+ return { ok: false, detail: result.detail, issues: ['unknown'], stalled: true };
131
+ }
148
132
  if (result.kind !== 'ok')
149
133
  return { ok: false, detail: result.detail, issues: ['unknown'] };
150
134
  const report = readBridgeReport(result.stdout);
@@ -206,6 +190,7 @@ export function createPreflight(options) {
206
190
  return { rows: verdict.rows, detail: verdict.detail, command: DIY_UPGRADE_COMMAND };
207
191
  }
208
192
  async function doRun() {
193
+ shellStalled = false;
209
194
  set({
210
195
  phase: 'checking',
211
196
  error: undefined,
@@ -227,6 +212,7 @@ export function createPreflight(options) {
227
212
  nodeMissing: true,
228
213
  npmMissing: true,
229
214
  pnpmProblem: true,
215
+ stalled: false,
230
216
  }));
231
217
  if (toolchain !== undefined && toolchain.ok && toolchain.npmMissing && options.provisionNpm !== undefined) {
232
218
  set({ phase: 'installing' });
@@ -268,12 +254,9 @@ export function createPreflight(options) {
268
254
  action: undefined,
269
255
  });
270
256
  if (!toolchain.ok) {
271
- return set({
272
- phase: 'failed',
273
- error: toolchain.detail,
274
- failure: 'missing',
275
- guidance: 'install',
276
- });
257
+ return toolchain.stalled === true
258
+ ? hostFailure(toolchain.detail)
259
+ : set({ phase: 'failed', error: toolchain.detail, failure: 'missing', guidance: 'install' });
277
260
  }
278
261
  }
279
262
  if (options.runtimeCheck !== undefined) {
@@ -292,6 +275,10 @@ export function createPreflight(options) {
292
275
  if (diyStarted !== undefined)
293
276
  patchStep('diy', { status: 'running' });
294
277
  let version = await probeVersion();
278
+ if (version.stalled) {
279
+ patchStep('cli', { status: 'failed', detail: version.detail, action: undefined });
280
+ return hostFailure(version.detail);
281
+ }
295
282
  if (!version.ok && !version.missing) {
296
283
  patchStep('cli', { status: 'failed', detail: version.detail, action: undefined });
297
284
  return set({
@@ -329,6 +316,10 @@ export function createPreflight(options) {
329
316
  }
330
317
  patchStep('cli', { status: 'running', action: `${command} --version` });
331
318
  version = await probeVersion();
319
+ if (version.stalled) {
320
+ patchStep('cli', { status: 'failed', detail: version.detail, action: undefined });
321
+ return hostFailure(version.detail);
322
+ }
332
323
  if (!version.ok) {
333
324
  patchStep('cli', { status: 'failed', detail: version.detail, command: installCommand });
334
325
  return set({
@@ -343,6 +334,10 @@ export function createPreflight(options) {
343
334
  }
344
335
  patchStep('bridge', { status: 'running', action: `${command} doctor` });
345
336
  const bridge = withBridge ? await probeBridge() : { ok: true, detail: '', issues: [] };
337
+ if (bridge.stalled === true) {
338
+ patchStep('bridge', { status: 'failed', detail: bridge.detail, action: undefined });
339
+ return hostFailure(bridge.detail);
340
+ }
346
341
  if (withBridge && options.desktopBrowser !== undefined && bridge.ok) {
347
342
  await options.desktopBrowser.observe().catch(() => undefined);
348
343
  }
@@ -0,0 +1,10 @@
1
+ import type { DshShell } from '@dsh-workbench/shared';
2
+ export interface ToolchainReport {
3
+ readonly ok: boolean;
4
+ readonly detail: string;
5
+ readonly nodeMissing: boolean;
6
+ readonly npmMissing: boolean;
7
+ readonly pnpmProblem: boolean;
8
+ readonly stalled?: boolean | undefined;
9
+ }
10
+ export declare function probeDesktopToolchain(shell: DshShell): Promise<ToolchainReport>;
@@ -0,0 +1,48 @@
1
+ import { runShell } from '@dsh-workbench/shared/host';
2
+ import { desktopPnpmPath } from './toolchain.js';
3
+ async function toolVersion(shell, line) {
4
+ try {
5
+ const result = await runShell(shell, line, { timeoutMs: 10_000 });
6
+ const text = (result.stdout || result.stderr).trim().split('\n')[0] ?? '';
7
+ return { ok: result.exitCode === 0 && !result.timedOut, text, stalled: result.stalled };
8
+ }
9
+ catch {
10
+ return { ok: false, text: '', stalled: false };
11
+ }
12
+ }
13
+ export async function probeDesktopToolchain(shell) {
14
+ const [node, npm, pnpm] = await Promise.all([
15
+ toolVersion(shell, 'node --version'),
16
+ toolVersion(shell, 'npm --version'),
17
+ toolVersion(shell, 'pnpm --version'),
18
+ ]);
19
+ if (node.stalled || npm.stalled || pnpm.stalled) {
20
+ return {
21
+ ok: false,
22
+ detail: '版本探测没有返回:node / npm / pnpm 的命令都没把结果交回来(不是没装)',
23
+ nodeMissing: false,
24
+ npmMissing: false,
25
+ pnpmProblem: false,
26
+ stalled: true,
27
+ };
28
+ }
29
+ const nodeMissing = !node.ok;
30
+ const npmMissing = !npm.ok;
31
+ const hasBundledPnpm = desktopPnpmPath() !== undefined;
32
+ const pnpmMajor = /^v?(\d+)/.exec(pnpm.text)?.[1];
33
+ const pnpmProblem = !pnpm.ok || pnpmMajor !== '10';
34
+ const parts = [
35
+ nodeMissing ? 'node 不在 PATH(Desktop 自带 Node 的 shim 没生效)' : `node ${node.text}(Desktop 自带)`,
36
+ npmMissing
37
+ ? hasBundledPnpm
38
+ ? 'npm 缺(opencli 安装走 pnpm;插件安装/模块库仍需 npm)'
39
+ : 'npm 缺(装不了 opencli:bundled pnpm 也没找到)'
40
+ : `npm ${npm.text}`,
41
+ pnpmProblem
42
+ ? pnpm.ok
43
+ ? `pnpm ${pnpm.text}(工坊按 10 方言)`
44
+ : 'pnpm 缺(只影响工坊 build/install)'
45
+ : `pnpm ${pnpm.text}`,
46
+ ];
47
+ return { ok: !nodeMissing, detail: parts.join(' · '), nodeMissing, npmMissing, pnpmProblem };
48
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-workbench/adapter-probe",
3
- "version": "1.1.11",
3
+ "version": "1.1.13",
4
4
  "description": "Workbench adapter-probe adapter.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -16,10 +16,10 @@
16
16
  "execa": "^10.2.0",
17
17
  "proper-lockfile": "^4.1.2",
18
18
  "write-file-atomic": "^8.0.0",
19
- "@dsh-workbench/shared": "1.1.11",
20
- "@dsh-workbench/adapter-runtime": "1.1.11",
21
- "@dsh-workbench/adapter-supply": "1.1.11",
22
- "@dsh-workbench/adapter-profile": "1.1.11"
19
+ "@dsh-workbench/shared": "1.1.13",
20
+ "@dsh-workbench/adapter-runtime": "1.1.13",
21
+ "@dsh-workbench/adapter-profile": "1.1.13",
22
+ "@dsh-workbench/adapter-supply": "1.1.13"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^24.19.0",