@dsh-workbench/adapter-probe 1.1.13 → 1.1.14-beta.1
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/bridge-report.d.ts +7 -0
- package/dist/bridge-report.js +32 -0
- package/dist/install.js +8 -2
- package/dist/preflight-route.js +22 -46
- package/dist/preflight.d.ts +5 -7
- package/dist/preflight.js +61 -63
- package/dist/runtime-seed.js +16 -3
- package/dist/toolchain-probe.js +25 -13
- package/dist/toolchain.d.ts +3 -0
- package/dist/toolchain.js +48 -15
- package/package.json +5 -5
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type PreflightBridgeIssue = 'daemon' | 'extension' | 'profile' | 'stale' | 'unknown';
|
|
2
|
+
export declare const BRIDGE_ISSUE_ORDER: readonly PreflightBridgeIssue[];
|
|
3
|
+
export declare function readBridgeReport(stdout: string): {
|
|
4
|
+
readonly ok: boolean;
|
|
5
|
+
readonly issues: readonly PreflightBridgeIssue[];
|
|
6
|
+
readonly detail: string;
|
|
7
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { tailLines } from '@dsh-workbench/shared/host';
|
|
2
|
+
export const BRIDGE_ISSUE_ORDER = ['profile', 'stale', 'daemon', 'extension', 'unknown'];
|
|
3
|
+
export function readBridgeReport(stdout) {
|
|
4
|
+
const text = String(stdout ?? '');
|
|
5
|
+
const ok = /^\[OK\] Daemon:/m.test(text) && /^\[OK\] Extension:/m.test(text) && /^\[OK\] Connectivity:/m.test(text);
|
|
6
|
+
if (ok) {
|
|
7
|
+
const good = text.split('\n').find((line) => line.trim() === 'Everything looks good!');
|
|
8
|
+
return { ok: true, issues: [], detail: good?.trim() ?? tailLines(text, 1) };
|
|
9
|
+
}
|
|
10
|
+
const found = new Set();
|
|
11
|
+
if (!/^\[OK\] Daemon:/m.test(text))
|
|
12
|
+
found.add('daemon');
|
|
13
|
+
if (!/^\[OK\] Extension:/m.test(text))
|
|
14
|
+
found.add('extension');
|
|
15
|
+
if (/Multiple Chrome profiles are connected/i.test(text))
|
|
16
|
+
found.add('profile');
|
|
17
|
+
if (/Default browser profile is stale|Selected browser profile is not connected/i.test(text)) {
|
|
18
|
+
found.add('stale');
|
|
19
|
+
}
|
|
20
|
+
if (found.has('profile') || found.has('stale'))
|
|
21
|
+
found.delete('extension');
|
|
22
|
+
if (found.size === 0)
|
|
23
|
+
found.add('unknown');
|
|
24
|
+
return { ok: false, issues: BRIDGE_ISSUE_ORDER.filter((issue) => found.has(issue)), detail: issuesSection(text) };
|
|
25
|
+
}
|
|
26
|
+
function issuesSection(text) {
|
|
27
|
+
const lines = text.split('\n');
|
|
28
|
+
const start = lines.findIndex((line) => /^Issues:/.test(line.trim()));
|
|
29
|
+
if (start < 0)
|
|
30
|
+
return tailLines(text, 6);
|
|
31
|
+
return lines.slice(start).join('\n').trim();
|
|
32
|
+
}
|
package/dist/install.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { isolatedInstallCommand, isolatedPnpmInstallCommand } from '@dsh-workbench/adapter-runtime';
|
|
3
|
-
import { desktopPnpmPath } from './toolchain.js';
|
|
3
|
+
import { desktopPnpmPath, desktopPnpmVersion } from './toolchain.js';
|
|
4
4
|
export function opencliInstallCommand(proc = process, exists = existsSync) {
|
|
5
|
+
const override = process.env.DSH_WB_PNPM?.trim();
|
|
6
|
+
if (override !== undefined && override !== '')
|
|
7
|
+
return isolatedInstallCommand();
|
|
5
8
|
const pnpm = desktopPnpmPath(proc, exists);
|
|
6
|
-
|
|
9
|
+
const major = Number(desktopPnpmVersion(proc, exists)?.split('.')[0] ?? '0');
|
|
10
|
+
return pnpm === undefined || (major !== 0 && major < 10)
|
|
11
|
+
? isolatedInstallCommand()
|
|
12
|
+
: isolatedPnpmInstallCommand(pnpm);
|
|
7
13
|
}
|
package/dist/preflight-route.js
CHANGED
|
@@ -8,7 +8,7 @@ import { probeDesktopToolchain } from './toolchain-probe.js';
|
|
|
8
8
|
import { createDesktopBrowser } from './browser-wake.js';
|
|
9
9
|
import { profileDirOf } from '@dsh-workbench/adapter-supply';
|
|
10
10
|
import { prependPath, runtimeSeedRootOf } from './runtime-seed.js';
|
|
11
|
-
import {
|
|
11
|
+
import { localPnpmCommand, provisionLocalPnpm } from './toolchain.js';
|
|
12
12
|
function desktopBrowserOf(input) {
|
|
13
13
|
const dir = profileDirOf(input.ctx);
|
|
14
14
|
const surface = hostSurface({ profile: dir === null ? undefined : basename(dir) });
|
|
@@ -30,8 +30,6 @@ export function registerPreflight(input) {
|
|
|
30
30
|
shell: input.shell,
|
|
31
31
|
command,
|
|
32
32
|
});
|
|
33
|
-
const profileDir = profileDirOf(input.ctx);
|
|
34
|
-
const surface = hostSurface({ profile: profileDir === null ? undefined : basename(profileDir) });
|
|
35
33
|
const preflight = createPreflight({
|
|
36
34
|
shell: input.shell,
|
|
37
35
|
command,
|
|
@@ -42,51 +40,29 @@ export function registerPreflight(input) {
|
|
|
42
40
|
...(input.runtimeCheck === undefined ? {} : { runtimeCheck: input.runtimeCheck }),
|
|
43
41
|
...(input.runtimeLabel === undefined ? {} : { runtimeLabel: input.runtimeLabel }),
|
|
44
42
|
probeDiy: createDiyPresetProbe(input.ctx),
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
if (result.ok && result.binDir !== undefined) {
|
|
63
|
-
prependPath(result.binDir);
|
|
64
|
-
if (process.env.npm_config_cache === undefined) {
|
|
65
|
-
process.env.npm_config_cache = localNpmCache(root);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return result;
|
|
69
|
-
},
|
|
70
|
-
provisionPnpm: async () => {
|
|
71
|
-
const root = runtimeSeedRootOf(input.ctx);
|
|
72
|
-
if (root === null)
|
|
73
|
-
return { ok: false, detail: '找不到工作台根' };
|
|
74
|
-
const result = await provisionLocalPnpm({
|
|
75
|
-
root,
|
|
76
|
-
run: async (command) => {
|
|
77
|
-
const ran = await runShell(input.shell, command, { timeoutMs: INSTALL_TIMEOUT_MS });
|
|
78
|
-
return {
|
|
79
|
-
ok: ran.exitCode === 0 && !ran.timedOut,
|
|
80
|
-
detail: tailLines(ran.stderr || ran.stdout, 3),
|
|
81
|
-
};
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
if (result.ok && result.binDir !== undefined)
|
|
85
|
-
prependPath(result.binDir);
|
|
86
|
-
return result;
|
|
43
|
+
probeToolchain: () => probeDesktopToolchain(input.shell),
|
|
44
|
+
provisionPnpm: async () => {
|
|
45
|
+
const root = runtimeSeedRootOf(input.ctx);
|
|
46
|
+
if (root === null)
|
|
47
|
+
return { ok: false, detail: '找不到工作台根' };
|
|
48
|
+
const result = await provisionLocalPnpm({
|
|
49
|
+
root,
|
|
50
|
+
pnpm: process.env.DSH_WB_PNPM?.trim() || 'pnpm',
|
|
51
|
+
npm: 'npm',
|
|
52
|
+
run: async (command) => {
|
|
53
|
+
const ran = await runShell(input.shell, command, { timeoutMs: INSTALL_TIMEOUT_MS });
|
|
54
|
+
return {
|
|
55
|
+
ok: ran.exitCode === 0 && !ran.timedOut,
|
|
56
|
+
detail: tailLines(ran.stderr || ran.stdout, 3),
|
|
57
|
+
};
|
|
87
58
|
},
|
|
59
|
+
});
|
|
60
|
+
if (result.ok && result.binDir !== undefined) {
|
|
61
|
+
prependPath(result.binDir);
|
|
62
|
+
process.env.DSH_WB_PNPM = localPnpmCommand(root);
|
|
88
63
|
}
|
|
89
|
-
|
|
64
|
+
return result;
|
|
65
|
+
},
|
|
90
66
|
...(desktopBrowser === undefined ? {} : { desktopBrowser }),
|
|
91
67
|
});
|
|
92
68
|
input.routes.preflight = preflightHandler(preflight);
|
package/dist/preflight.d.ts
CHANGED
|
@@ -14,14 +14,11 @@ export interface PreflightStep {
|
|
|
14
14
|
readonly detail?: string;
|
|
15
15
|
readonly action?: string;
|
|
16
16
|
readonly command?: string;
|
|
17
|
+
readonly log?: readonly string[];
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
-
export
|
|
20
|
-
export
|
|
21
|
-
readonly ok: boolean;
|
|
22
|
-
readonly issues: readonly PreflightBridgeIssue[];
|
|
23
|
-
readonly detail: string;
|
|
24
|
-
};
|
|
19
|
+
import { type PreflightBridgeIssue } from './bridge-report.js';
|
|
20
|
+
export { BRIDGE_ISSUE_ORDER, readBridgeReport } from './bridge-report.js';
|
|
21
|
+
export type { PreflightBridgeIssue } from './bridge-report.js';
|
|
25
22
|
export interface PreflightDiyGap {
|
|
26
23
|
readonly rows: readonly string[];
|
|
27
24
|
readonly detail: string;
|
|
@@ -37,6 +34,7 @@ export interface PreflightSnapshot {
|
|
|
37
34
|
readonly bridgeIssues?: readonly PreflightBridgeIssue[];
|
|
38
35
|
readonly diyGap?: PreflightDiyGap;
|
|
39
36
|
readonly canWakeBrowser?: boolean;
|
|
37
|
+
readonly log?: readonly string[];
|
|
40
38
|
}
|
|
41
39
|
export interface Preflight {
|
|
42
40
|
run(options?: {
|
package/dist/preflight.js
CHANGED
|
@@ -4,7 +4,7 @@ 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 {
|
|
7
|
+
import { 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,37 +16,8 @@ export function classifyFailure(text) {
|
|
|
16
16
|
return 'missing';
|
|
17
17
|
return 'unknown';
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
export
|
|
21
|
-
const text = String(stdout ?? '');
|
|
22
|
-
const ok = /^\[OK\] Daemon:/m.test(text) && /^\[OK\] Extension:/m.test(text) && /^\[OK\] Connectivity:/m.test(text);
|
|
23
|
-
if (ok) {
|
|
24
|
-
const good = text.split('\n').find((line) => line.trim() === 'Everything looks good!');
|
|
25
|
-
return { ok: true, issues: [], detail: good?.trim() ?? tailLines(text, 1) };
|
|
26
|
-
}
|
|
27
|
-
const found = new Set();
|
|
28
|
-
if (!/^\[OK\] Daemon:/m.test(text))
|
|
29
|
-
found.add('daemon');
|
|
30
|
-
if (!/^\[OK\] Extension:/m.test(text))
|
|
31
|
-
found.add('extension');
|
|
32
|
-
if (/Multiple Chrome profiles are connected/i.test(text))
|
|
33
|
-
found.add('profile');
|
|
34
|
-
if (/Default browser profile is stale|Selected browser profile is not connected/i.test(text)) {
|
|
35
|
-
found.add('stale');
|
|
36
|
-
}
|
|
37
|
-
if (found.has('profile') || found.has('stale'))
|
|
38
|
-
found.delete('extension');
|
|
39
|
-
if (found.size === 0)
|
|
40
|
-
found.add('unknown');
|
|
41
|
-
return { ok: false, issues: BRIDGE_ISSUE_ORDER.filter((issue) => found.has(issue)), detail: issuesSection(text) };
|
|
42
|
-
}
|
|
43
|
-
function issuesSection(text) {
|
|
44
|
-
const lines = text.split('\n');
|
|
45
|
-
const start = lines.findIndex((line) => /^Issues:/.test(line.trim()));
|
|
46
|
-
if (start < 0)
|
|
47
|
-
return tailLines(text, 6);
|
|
48
|
-
return lines.slice(start).join('\n').trim();
|
|
49
|
-
}
|
|
19
|
+
import { readBridgeReport } from './bridge-report.js';
|
|
20
|
+
export { BRIDGE_ISSUE_ORDER, readBridgeReport } from './bridge-report.js';
|
|
50
21
|
export function opencliInstallCommandDefault() {
|
|
51
22
|
return opencliInstallCommand();
|
|
52
23
|
}
|
|
@@ -72,16 +43,37 @@ export function createPreflight(options) {
|
|
|
72
43
|
...(options.probeDiy === undefined ? [] : [step('diy')]),
|
|
73
44
|
...(warmReads().length === 0 ? [] : [step('warm')]),
|
|
74
45
|
];
|
|
46
|
+
let logLines = [];
|
|
47
|
+
const stepLogs = new Map();
|
|
48
|
+
function appendLog(stepId, text) {
|
|
49
|
+
const stepLines = stepLogs.get(stepId) ?? [];
|
|
50
|
+
for (const line of String(text).split('\n')) {
|
|
51
|
+
if (line.trim() === '')
|
|
52
|
+
continue;
|
|
53
|
+
const clipped = line.length > 400 ? `${line.slice(0, 400)}…` : line;
|
|
54
|
+
stepLines.push(clipped);
|
|
55
|
+
logLines.push(clipped);
|
|
56
|
+
}
|
|
57
|
+
if (stepLines.length > 200)
|
|
58
|
+
stepLines.splice(0, stepLines.length - 200);
|
|
59
|
+
if (logLines.length > 200)
|
|
60
|
+
logLines = logLines.slice(-200);
|
|
61
|
+
stepLogs.set(stepId, stepLines);
|
|
62
|
+
set({
|
|
63
|
+
steps: current.steps.map((item) => item.id === stepId ? { ...item, log: [...stepLines] } : item),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
75
66
|
let current = {
|
|
76
67
|
phase: 'idle',
|
|
77
68
|
steps: steps(),
|
|
78
69
|
at: Date.now(),
|
|
70
|
+
log: [],
|
|
79
71
|
...(options.desktopBrowser === undefined ? {} : { canWakeBrowser: true }),
|
|
80
72
|
};
|
|
81
73
|
let running = null;
|
|
82
74
|
let shellStalled = false;
|
|
83
75
|
function set(next) {
|
|
84
|
-
current = { ...current, ...next, at: Date.now() };
|
|
76
|
+
current = { ...current, ...next, log: [...logLines], at: Date.now() };
|
|
85
77
|
return current;
|
|
86
78
|
}
|
|
87
79
|
function patchStep(id, patch) {
|
|
@@ -90,30 +82,37 @@ export function createPreflight(options) {
|
|
|
90
82
|
function hostFailure(detail) {
|
|
91
83
|
return set({ phase: 'failed', error: detail, failure: 'host', guidance: 'host' });
|
|
92
84
|
}
|
|
93
|
-
async function exec(commandLine, timeoutMs) {
|
|
85
|
+
async function exec(stepId, commandLine, timeoutMs) {
|
|
94
86
|
if (shellStalled) {
|
|
95
87
|
return { kind: 'stalled', detail: `前一条命令就没交回结果,这一步没跑:${commandLine}` };
|
|
96
88
|
}
|
|
89
|
+
appendLog(stepId, `> ${commandLine}`);
|
|
97
90
|
try {
|
|
98
91
|
const result = await runShell(shell, commandLine, { timeoutMs });
|
|
99
92
|
if (result.stalled) {
|
|
100
93
|
shellStalled = true;
|
|
94
|
+
appendLog(stepId, `没返回:${commandLine}`);
|
|
101
95
|
return { kind: 'stalled', detail: `没有返回(宿主没把结果交回来):${commandLine}` };
|
|
102
96
|
}
|
|
103
97
|
if (result.timedOut) {
|
|
98
|
+
appendLog(stepId, `超时(${String(timeoutMs)} ms):${commandLine}`);
|
|
104
99
|
return { kind: 'error', detail: `超时(${String(timeoutMs)} ms):${commandLine}` };
|
|
105
100
|
}
|
|
101
|
+
const output = result.stdout || result.stderr;
|
|
102
|
+
if (output.trim() !== '')
|
|
103
|
+
appendLog(stepId, tailLines(output, 12));
|
|
106
104
|
if (result.exitCode !== 0) {
|
|
107
105
|
return { kind: 'nonzero', detail: tailLines(result.stderr || result.stdout, 4) };
|
|
108
106
|
}
|
|
109
107
|
return { kind: 'ok', stdout: result.stdout };
|
|
110
108
|
}
|
|
111
109
|
catch (error) {
|
|
110
|
+
appendLog(stepId, String(error));
|
|
112
111
|
return { kind: 'error', detail: String(error) };
|
|
113
112
|
}
|
|
114
113
|
}
|
|
115
114
|
async function probeVersion() {
|
|
116
|
-
const result = await exec(`${command} --version`, PREFLIGHT_VERSION_TIMEOUT_MS);
|
|
115
|
+
const result = await exec('cli', `${command} --version`, PREFLIGHT_VERSION_TIMEOUT_MS);
|
|
117
116
|
if (result.kind === 'ok') {
|
|
118
117
|
return { ok: true, missing: false, detail: result.stdout.trim().split('\n')[0] ?? '', stalled: false };
|
|
119
118
|
}
|
|
@@ -125,7 +124,7 @@ export function createPreflight(options) {
|
|
|
125
124
|
};
|
|
126
125
|
}
|
|
127
126
|
async function probeBridge() {
|
|
128
|
-
const result = await exec(`${command} doctor`, OPENCLI_TIMEOUT_MS);
|
|
127
|
+
const result = await exec('bridge', `${command} doctor`, OPENCLI_TIMEOUT_MS);
|
|
129
128
|
if (result.kind === 'stalled') {
|
|
130
129
|
return { ok: false, detail: result.detail, issues: ['unknown'], stalled: true };
|
|
131
130
|
}
|
|
@@ -137,9 +136,14 @@ export function createPreflight(options) {
|
|
|
137
136
|
return { ...report, command: bridgeFixCommand(command, report.issues) };
|
|
138
137
|
}
|
|
139
138
|
async function installOpencli() {
|
|
140
|
-
if (options.install !== undefined)
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
if (options.install !== undefined) {
|
|
140
|
+
appendLog('cli', '> 安装 opencli');
|
|
141
|
+
const installed = await options.install();
|
|
142
|
+
if (installed.detail.trim() !== '')
|
|
143
|
+
appendLog('cli', installed.detail);
|
|
144
|
+
return installed;
|
|
145
|
+
}
|
|
146
|
+
const result = await exec('cli', installCommand, INSTALL_TIMEOUT_MS);
|
|
143
147
|
if (result.kind !== 'ok')
|
|
144
148
|
return { ok: false, detail: result.detail };
|
|
145
149
|
return { ok: true, detail: tailLines(result.stdout, 1) };
|
|
@@ -154,9 +158,9 @@ export function createPreflight(options) {
|
|
|
154
158
|
}
|
|
155
159
|
patchStep('warm', {
|
|
156
160
|
status: 'running',
|
|
157
|
-
action:
|
|
161
|
+
action: '预热浏览器站点',
|
|
158
162
|
});
|
|
159
|
-
const results = await Promise.all(reads.map((read) => exec(opencliCommandLine(command, read), WARM_TIMEOUT_MS)));
|
|
163
|
+
const results = await Promise.all(reads.map((read) => exec('warm', opencliCommandLine(command, read), WARM_TIMEOUT_MS)));
|
|
160
164
|
const failed = reads.filter((_, index) => results[index]?.kind !== 'ok');
|
|
161
165
|
patchStep('warm', {
|
|
162
166
|
status: failed.length === 0 ? 'ok' : 'warn',
|
|
@@ -191,6 +195,8 @@ export function createPreflight(options) {
|
|
|
191
195
|
}
|
|
192
196
|
async function doRun() {
|
|
193
197
|
shellStalled = false;
|
|
198
|
+
logLines = [];
|
|
199
|
+
stepLogs.clear();
|
|
194
200
|
set({
|
|
195
201
|
phase: 'checking',
|
|
196
202
|
error: undefined,
|
|
@@ -214,29 +220,18 @@ export function createPreflight(options) {
|
|
|
214
220
|
pnpmProblem: true,
|
|
215
221
|
stalled: false,
|
|
216
222
|
}));
|
|
217
|
-
if (toolchain !== undefined && toolchain.
|
|
218
|
-
|
|
219
|
-
patchStep('toolchain', { status: 'running', action: `装本地 npm@${LOCAL_NPM_VERSION}` });
|
|
220
|
-
const provisioned = await options.provisionNpm().catch((error) => ({
|
|
221
|
-
ok: false,
|
|
222
|
-
detail: String(error),
|
|
223
|
-
}));
|
|
224
|
-
if (provisioned.ok) {
|
|
225
|
-
const reprobed = await options.probeToolchain?.();
|
|
226
|
-
if (reprobed !== undefined)
|
|
227
|
-
toolchain = reprobed;
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
toolchain = { ...toolchain, detail: `${toolchain.detail};本地 npm 没装成:${provisioned.detail}` };
|
|
231
|
-
}
|
|
232
|
-
}
|
|
223
|
+
if (toolchain !== undefined && toolchain.detail.trim() !== '')
|
|
224
|
+
appendLog('toolchain', toolchain.detail);
|
|
233
225
|
if (toolchain !== undefined && toolchain.ok && toolchain.pnpmProblem && options.provisionPnpm !== undefined) {
|
|
234
226
|
set({ phase: 'installing' });
|
|
235
227
|
patchStep('toolchain', { status: 'running', action: `装本地 pnpm@${LOCAL_PNPM_VERSION}` });
|
|
228
|
+
appendLog('toolchain', '> 安装本地 pnpm');
|
|
236
229
|
const provisioned = await options.provisionPnpm().catch((error) => ({
|
|
237
230
|
ok: false,
|
|
238
231
|
detail: String(error),
|
|
239
232
|
}));
|
|
233
|
+
if (provisioned.detail.trim() !== '')
|
|
234
|
+
appendLog('toolchain', provisioned.detail);
|
|
240
235
|
if (provisioned.ok) {
|
|
241
236
|
const reprobed = await options.probeToolchain?.();
|
|
242
237
|
if (reprobed !== undefined)
|
|
@@ -249,29 +244,32 @@ export function createPreflight(options) {
|
|
|
249
244
|
if (toolchain !== undefined) {
|
|
250
245
|
set({ phase: 'checking' });
|
|
251
246
|
patchStep('toolchain', {
|
|
252
|
-
status: toolchain.ok
|
|
247
|
+
status: toolchain.ok && !toolchain.pnpmProblem ? 'ok' : 'failed',
|
|
253
248
|
detail: toolchain.detail,
|
|
254
249
|
action: undefined,
|
|
255
250
|
});
|
|
256
|
-
if (!toolchain.ok) {
|
|
251
|
+
if (!toolchain.ok || toolchain.pnpmProblem) {
|
|
257
252
|
return toolchain.stalled === true
|
|
258
253
|
? hostFailure(toolchain.detail)
|
|
259
254
|
: set({ phase: 'failed', error: toolchain.detail, failure: 'missing', guidance: 'install' });
|
|
260
255
|
}
|
|
261
256
|
}
|
|
262
257
|
if (options.runtimeCheck !== undefined) {
|
|
258
|
+
appendLog('runtime', '> 检查运行环境');
|
|
263
259
|
patchStep('runtime', { status: 'running' });
|
|
264
260
|
const verdict = await options.runtimeCheck().catch((error) => ({
|
|
265
261
|
ok: false,
|
|
266
262
|
detail: error instanceof Error ? error.message : String(error),
|
|
267
263
|
}));
|
|
264
|
+
if (verdict.detail.trim() !== '')
|
|
265
|
+
appendLog('runtime', verdict.detail);
|
|
268
266
|
patchStep('runtime', { status: verdict.ok ? 'ok' : 'failed', detail: verdict.detail, action: undefined });
|
|
269
267
|
if (!verdict.ok)
|
|
270
268
|
return set({ phase: 'failed', error: verdict.detail, failure: 'missing', guidance: 'install' });
|
|
271
269
|
}
|
|
272
270
|
const diyStarted = options.probeDiy?.();
|
|
273
271
|
if (withConsole) {
|
|
274
|
-
patchStep('cli', { status: 'running', action:
|
|
272
|
+
patchStep('cli', { status: 'running', action: '检查 opencli 版本' });
|
|
275
273
|
if (diyStarted !== undefined)
|
|
276
274
|
patchStep('diy', { status: 'running' });
|
|
277
275
|
let version = await probeVersion();
|
|
@@ -299,7 +297,7 @@ export function createPreflight(options) {
|
|
|
299
297
|
});
|
|
300
298
|
}
|
|
301
299
|
set({ phase: 'installing' });
|
|
302
|
-
patchStep('cli', { status: 'running', action:
|
|
300
|
+
patchStep('cli', { status: 'running', action: '安装 opencli', command: installCommand });
|
|
303
301
|
const installed = await installOpencli();
|
|
304
302
|
if (!installed.ok) {
|
|
305
303
|
patchStep('cli', {
|
|
@@ -314,7 +312,7 @@ export function createPreflight(options) {
|
|
|
314
312
|
guidance: 'install',
|
|
315
313
|
});
|
|
316
314
|
}
|
|
317
|
-
patchStep('cli', { status: 'running', action:
|
|
315
|
+
patchStep('cli', { status: 'running', action: '检查 opencli 版本' });
|
|
318
316
|
version = await probeVersion();
|
|
319
317
|
if (version.stalled) {
|
|
320
318
|
patchStep('cli', { status: 'failed', detail: version.detail, action: undefined });
|
|
@@ -332,7 +330,7 @@ export function createPreflight(options) {
|
|
|
332
330
|
}
|
|
333
331
|
patchStep('cli', { status: 'ok', detail: version.detail, action: undefined });
|
|
334
332
|
}
|
|
335
|
-
patchStep('bridge', { status: 'running', action:
|
|
333
|
+
patchStep('bridge', { status: 'running', action: '检查浏览器通道' });
|
|
336
334
|
const bridge = withBridge ? await probeBridge() : { ok: true, detail: '', issues: [] };
|
|
337
335
|
if (bridge.stalled === true) {
|
|
338
336
|
patchStep('bridge', { status: 'failed', detail: bridge.detail, action: undefined });
|
package/dist/runtime-seed.js
CHANGED
|
@@ -4,8 +4,8 @@ import { createRequire } from 'node:module';
|
|
|
4
4
|
import writeFileAtomic from 'write-file-atomic';
|
|
5
5
|
import { chmodSync, closeSync, mkdirSync, openSync, readFileSync, rmSync, } from 'node:fs';
|
|
6
6
|
import { delimiter, dirname, join, sep } from 'node:path';
|
|
7
|
-
import { quoteShellArg } from '@dsh-workbench/shared/host';
|
|
8
|
-
import { ensureToolchain, localNpmCache, provisionLocalPnpmSync } from './toolchain.js';
|
|
7
|
+
import { quoteShellArg, quoteShellArgFor } from '@dsh-workbench/shared/host';
|
|
8
|
+
import { desktopPnpmPath, desktopPnpmVersion, ensureToolchain, localNpmCache, localPnpmCommand, provisionLocalPnpmSync, } from './toolchain.js';
|
|
9
9
|
export function opencliBinName(platform = process.platform) {
|
|
10
10
|
return platform === 'win32' ? 'opencli.cmd' : 'opencli';
|
|
11
11
|
}
|
|
@@ -125,6 +125,16 @@ export function prependPath(entryDir, env = process.env) {
|
|
|
125
125
|
return true;
|
|
126
126
|
}
|
|
127
127
|
export function seedWorkbenchToolchain(root, log) {
|
|
128
|
+
const bundledPnpm = desktopPnpmPath();
|
|
129
|
+
const bundledMajor = Number(desktopPnpmVersion()?.split('.')[0] ?? '0');
|
|
130
|
+
const bundledUsable = bundledPnpm !== undefined && bundledMajor >= 10;
|
|
131
|
+
if (bundledPnpm !== undefined && bundledUsable) {
|
|
132
|
+
process.env.DSH_WB_PNPM = `node ${quoteShellArgFor(bundledPnpm, process.platform)}`;
|
|
133
|
+
log(`pnpm 用 Desktop 自带的:${bundledPnpm}`);
|
|
134
|
+
}
|
|
135
|
+
else if (bundledPnpm !== undefined) {
|
|
136
|
+
log(`Desktop 自带 pnpm 版本低于 10,优先用 PATH 上的 pnpm`);
|
|
137
|
+
}
|
|
128
138
|
const toolchain = ensureToolchain({ root, log });
|
|
129
139
|
for (const file of toolchain.files)
|
|
130
140
|
log(`工具链写下 ${file}`);
|
|
@@ -141,12 +151,15 @@ export function seedWorkbenchToolchain(root, log) {
|
|
|
141
151
|
if (toolchain.pnpmDir !== undefined) {
|
|
142
152
|
if (prependPath(toolchain.pnpmDir))
|
|
143
153
|
log(`PATH 前插 ${toolchain.pnpmDir}`);
|
|
154
|
+
if (!bundledUsable)
|
|
155
|
+
process.env.DSH_WB_PNPM = localPnpmCommand(root);
|
|
144
156
|
}
|
|
145
|
-
else if (toolchain.pathDir !== undefined) {
|
|
157
|
+
else if (!bundledUsable && toolchain.pathDir !== undefined) {
|
|
146
158
|
const installed = provisionLocalPnpmSync({ root });
|
|
147
159
|
if (installed.ok && installed.binDir !== undefined) {
|
|
148
160
|
if (prependPath(installed.binDir))
|
|
149
161
|
log(`PATH 前插 ${installed.binDir}`);
|
|
162
|
+
process.env.DSH_WB_PNPM = localPnpmCommand(root);
|
|
150
163
|
log(`工具链 provision ${installed.detail}`);
|
|
151
164
|
}
|
|
152
165
|
else {
|
package/dist/toolchain-probe.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { runShell } from '@dsh-workbench/shared/host';
|
|
2
|
-
import {
|
|
2
|
+
import { quoteShellArg } from '@dsh-workbench/shared/host';
|
|
3
|
+
import { desktopPnpmPath, desktopPnpmVersion } from './toolchain.js';
|
|
4
|
+
import { hostSurface } from './platform.js';
|
|
3
5
|
async function toolVersion(shell, line) {
|
|
4
6
|
try {
|
|
5
7
|
const result = await runShell(shell, line, { timeoutMs: 10_000 });
|
|
@@ -11,10 +13,16 @@ async function toolVersion(shell, line) {
|
|
|
11
13
|
}
|
|
12
14
|
}
|
|
13
15
|
export async function probeDesktopToolchain(shell) {
|
|
16
|
+
const bundledPnpm = desktopPnpmPath();
|
|
17
|
+
const bundledMajor = desktopPnpmVersion()?.split('.')[0];
|
|
18
|
+
const preferBundled = bundledPnpm !== undefined && Number(bundledMajor ?? '0') >= 10;
|
|
19
|
+
const pnpmLine = preferBundled
|
|
20
|
+
? `node ${quoteShellArg(bundledPnpm ?? '')} --version`
|
|
21
|
+
: 'pnpm --version';
|
|
14
22
|
const [node, npm, pnpm] = await Promise.all([
|
|
15
23
|
toolVersion(shell, 'node --version'),
|
|
16
24
|
toolVersion(shell, 'npm --version'),
|
|
17
|
-
toolVersion(shell,
|
|
25
|
+
toolVersion(shell, pnpmLine),
|
|
18
26
|
]);
|
|
19
27
|
if (node.stalled || npm.stalled || pnpm.stalled) {
|
|
20
28
|
return {
|
|
@@ -28,21 +36,25 @@ export async function probeDesktopToolchain(shell) {
|
|
|
28
36
|
}
|
|
29
37
|
const nodeMissing = !node.ok;
|
|
30
38
|
const npmMissing = !npm.ok;
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
39
|
+
const pnpmMajor = Number(/^v?(\d+)/.exec(pnpm.text)?.[1] ?? '0');
|
|
40
|
+
const pnpmProblem = !pnpm.ok || pnpmMajor < 10;
|
|
41
|
+
const desktop = hostSurface({ proc: process }).deployment === 'desktop';
|
|
34
42
|
const parts = [
|
|
35
|
-
nodeMissing
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
nodeMissing
|
|
44
|
+
? desktop
|
|
45
|
+
? 'node 不在 PATH(Desktop 自带 Node 的 shim 没生效)'
|
|
46
|
+
: 'node 不在 PATH'
|
|
47
|
+
: desktop
|
|
48
|
+
? `node ${node.text}(Desktop 自带)`
|
|
49
|
+
: `node ${node.text}`,
|
|
50
|
+
npmMissing ? 'npm 缺(已改用 pnpm)' : `npm ${npm.text}`,
|
|
41
51
|
pnpmProblem
|
|
42
52
|
? pnpm.ok
|
|
43
53
|
? `pnpm ${pnpm.text}(工坊按 10 方言)`
|
|
44
|
-
: 'pnpm
|
|
45
|
-
:
|
|
54
|
+
: 'pnpm 缺(装不了 opencli/插件/模块库)'
|
|
55
|
+
: preferBundled
|
|
56
|
+
? `pnpm ${pnpm.text}(Desktop 自带)`
|
|
57
|
+
: `pnpm ${pnpm.text}`,
|
|
46
58
|
];
|
|
47
59
|
return { ok: !nodeMissing, detail: parts.join(' · '), nodeMissing, npmMissing, pnpmProblem };
|
|
48
60
|
}
|
package/dist/toolchain.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface PnpmProcess {
|
|
|
14
14
|
readonly resourcesPath?: string | undefined;
|
|
15
15
|
}
|
|
16
16
|
export declare function desktopPnpmPath(proc?: PnpmProcess, exists?: (path: string) => boolean): string | undefined;
|
|
17
|
+
export declare function desktopPnpmVersion(proc?: PnpmProcess, exists?: (path: string) => boolean): string | undefined;
|
|
17
18
|
export interface ToolchainOptions {
|
|
18
19
|
readonly root: string;
|
|
19
20
|
readonly proc?: ToolchainProcess | undefined;
|
|
@@ -68,6 +69,8 @@ export declare function provisionLocalPnpm(options: {
|
|
|
68
69
|
readonly platform?: NodeJS.Platform | undefined;
|
|
69
70
|
readonly proc?: PnpmProcess | undefined;
|
|
70
71
|
readonly exists?: ((path: string) => boolean) | undefined;
|
|
72
|
+
readonly pnpm?: string | undefined;
|
|
73
|
+
readonly npm?: string | undefined;
|
|
71
74
|
}): Promise<LocalPnpmProvision>;
|
|
72
75
|
export declare function provisionLocalPnpmSync(options: {
|
|
73
76
|
readonly root: string;
|
package/dist/toolchain.js
CHANGED
|
@@ -14,6 +14,23 @@ export function desktopPnpmPath(proc = process, exists = existsSync) {
|
|
|
14
14
|
const path = join(resources, 'runtime', 'primary-runtime', 'dependencies', 'pnpm', 'bin', 'pnpm.cjs');
|
|
15
15
|
return exists(path) ? path : undefined;
|
|
16
16
|
}
|
|
17
|
+
export function desktopPnpmVersion(proc = process, exists = existsSync) {
|
|
18
|
+
if (hostSurface({ proc }).deployment !== 'desktop')
|
|
19
|
+
return undefined;
|
|
20
|
+
const resources = proc.resourcesPath;
|
|
21
|
+
if (resources === undefined || resources === '')
|
|
22
|
+
return undefined;
|
|
23
|
+
const manifest = join(resources, 'runtime', 'primary-runtime', 'dependencies', 'pnpm', 'package.json');
|
|
24
|
+
if (!exists(manifest))
|
|
25
|
+
return undefined;
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(readFileSync(manifest, 'utf8'));
|
|
28
|
+
return typeof parsed.version === 'string' ? parsed.version : undefined;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
17
34
|
export const LOCAL_NPM_VERSION = [10, 9, 4].join('.');
|
|
18
35
|
export const LOCAL_PNPM_VERSION = [10, 32, 1].join('.');
|
|
19
36
|
export function localNpmRoot(root) {
|
|
@@ -75,27 +92,43 @@ export function localPnpmInstallCommand(root, proc = process, exists = existsSyn
|
|
|
75
92
|
`pnpm@${LOCAL_PNPM_VERSION} --store-dir ${quoteShellArgFor(join(root, 'pnpm-store'), platform)}`);
|
|
76
93
|
}
|
|
77
94
|
export async function provisionLocalPnpm(options) {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
95
|
+
const platform = options.platform ?? process.platform;
|
|
96
|
+
const bootstrap = options.pnpm?.trim();
|
|
97
|
+
const pnpmCommand = bootstrap === undefined || bootstrap === ''
|
|
98
|
+
? localPnpmInstallCommand(options.root, options.proc ?? process, options.exists ?? existsSync)
|
|
99
|
+
: `${bootstrap} add --dir ${quoteShellArgFor(localPnpmRoot(options.root), platform)} pnpm@${LOCAL_PNPM_VERSION} --store-dir ${quoteShellArgFor(join(options.root, 'pnpm-store'), platform)}`;
|
|
100
|
+
const npm = options.npm?.trim();
|
|
101
|
+
const npmCommand = npm === undefined || npm === ''
|
|
102
|
+
? undefined
|
|
103
|
+
: `${npm} install --prefix ${quoteShellArgFor(localPnpmRoot(options.root), platform)} pnpm@${LOCAL_PNPM_VERSION} --cache ${quoteShellArgFor(join(options.root, 'npm-cache'), platform)} --no-audit --no-fund`;
|
|
104
|
+
const commands = [pnpmCommand, npmCommand].filter((item) => item !== undefined);
|
|
105
|
+
if (commands.length === 0)
|
|
106
|
+
return { ok: false, detail: '找不到可用的 pnpm 或 npm,装不了本地 pnpm 10' };
|
|
81
107
|
try {
|
|
82
108
|
mkdirSync(localPnpmRoot(options.root), { recursive: true });
|
|
83
109
|
}
|
|
84
110
|
catch (error) {
|
|
85
111
|
return { ok: false, detail: `建不了 ${localPnpmRoot(options.root)}:${String(error)}` };
|
|
86
112
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
let detail = '';
|
|
114
|
+
for (const command of commands) {
|
|
115
|
+
const result = await options.run(command);
|
|
116
|
+
if (result.ok) {
|
|
117
|
+
const installed = options.installed?.() ?? existsSync(localPnpmCommand(options.root, platform));
|
|
118
|
+
if (installed) {
|
|
119
|
+
return {
|
|
120
|
+
ok: true,
|
|
121
|
+
binDir: localPnpmBin(options.root),
|
|
122
|
+
detail: `pnpm@${LOCAL_PNPM_VERSION} 装到 ${localPnpmRoot(options.root)}`,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
detail = 'pnpm 跑完但本地 pnpm 10 不在';
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
detail = result.detail;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return { ok: false, detail };
|
|
99
132
|
}
|
|
100
133
|
export function provisionLocalPnpmSync(options) {
|
|
101
134
|
const command = localPnpmInstallCommand(options.root, options.proc ?? process, options.exists ?? existsSync);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-workbench/adapter-probe",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.14-beta.1",
|
|
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/
|
|
20
|
-
"@dsh-workbench/adapter-runtime": "1.1.
|
|
21
|
-
"@dsh-workbench/
|
|
22
|
-
"@dsh-workbench/adapter-
|
|
19
|
+
"@dsh-workbench/adapter-supply": "1.1.14-beta.1",
|
|
20
|
+
"@dsh-workbench/adapter-runtime": "1.1.14-beta.1",
|
|
21
|
+
"@dsh-workbench/shared": "1.1.14-beta.1",
|
|
22
|
+
"@dsh-workbench/adapter-profile": "1.1.14-beta.1"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^24.19.0",
|