@dsh-workbench/adapter-probe 0.0.0-stage → 1.0.0-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/LICENSE +21 -0
- package/README.md +26 -2
- package/dist/browser-wake.d.ts +21 -0
- package/dist/browser-wake.js +263 -0
- package/dist/diy-preset-health.d.ts +13 -0
- package/dist/diy-preset-health.js +45 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +14 -0
- package/dist/install.d.ts +2 -0
- package/dist/install.js +7 -0
- package/dist/platform.d.ts +1 -0
- package/dist/platform.js +1 -0
- package/dist/preflight-route.d.ts +12 -0
- package/dist/preflight-route.js +93 -0
- package/dist/preflight.d.ts +81 -0
- package/dist/preflight.js +372 -0
- package/dist/probe-cache.d.ts +6 -0
- package/dist/probe-cache.js +19 -0
- package/dist/probe-libs.d.ts +10 -0
- package/dist/probe-libs.js +67 -0
- package/dist/probe-units.d.ts +18 -0
- package/dist/probe-units.js +190 -0
- package/dist/probe-verdict.d.ts +16 -0
- package/dist/probe-verdict.js +126 -0
- package/dist/runtime-seed.d.ts +43 -0
- package/dist/runtime-seed.js +178 -0
- package/dist/self-update.d.ts +63 -0
- package/dist/self-update.js +277 -0
- package/dist/toolchain.d.ts +83 -0
- package/dist/toolchain.js +197 -0
- package/dist/version-range.d.ts +1 -0
- package/dist/version-range.js +1 -0
- package/package.json +40 -3
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import writeFileAtomic from 'write-file-atomic';
|
|
2
|
+
import { quoteShellArg } from '@dsh-workbench/shared/host';
|
|
3
|
+
import { hostSurface } from './platform.js';
|
|
4
|
+
import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, statSync, } from 'node:fs';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { runDetached } from './runtime-seed.js';
|
|
7
|
+
import { SELF_UPDATE_STATES, } from '@dsh-workbench/shared/contract/protocol/bundle-install';
|
|
8
|
+
export { SELF_UPDATE_STATES };
|
|
9
|
+
const PATH_DSH = { file: 'dsh', args: [], env: {} };
|
|
10
|
+
function isPathDsh(cli) {
|
|
11
|
+
return cli.file === PATH_DSH.file && cli.args.length === 0 && Object.keys(cli.env).length === 0;
|
|
12
|
+
}
|
|
13
|
+
function quoted(value) {
|
|
14
|
+
return quoteShellArg(value);
|
|
15
|
+
}
|
|
16
|
+
function cliCommand(cli) {
|
|
17
|
+
const env = Object.entries(cli.env)
|
|
18
|
+
.map(([key, value]) => `${key}=${quoted(value)}`)
|
|
19
|
+
.join(' ');
|
|
20
|
+
const command = [cli.file, ...cli.args].map(quoted).join(' ');
|
|
21
|
+
return env === '' ? command : `env ${env} ${command}`;
|
|
22
|
+
}
|
|
23
|
+
export function desktopSelfUpdateCli(proc = process, exists = existsSync) {
|
|
24
|
+
if (hostSurface({ proc }).deployment !== 'desktop')
|
|
25
|
+
return undefined;
|
|
26
|
+
const entry = proc.argv[1];
|
|
27
|
+
if (entry === undefined || !/dsh-desktop-host[\\/]lib[\\/]index\.js$/.test(entry))
|
|
28
|
+
return undefined;
|
|
29
|
+
const cli = join(dirname(entry), 'cli.js');
|
|
30
|
+
if (!exists(cli))
|
|
31
|
+
return undefined;
|
|
32
|
+
return {
|
|
33
|
+
file: proc.execPath,
|
|
34
|
+
args: ['--expose-internals', cli],
|
|
35
|
+
env: { ELECTRON_RUN_AS_NODE: '1' },
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function selfUpdateCliFor(profile, proc = process, exists = existsSync) {
|
|
39
|
+
const surface = hostSurface({ proc, profile });
|
|
40
|
+
if (surface.capabilities.selfUpdateCli !== 'desktop-cli')
|
|
41
|
+
return PATH_DSH;
|
|
42
|
+
return desktopSelfUpdateCli(proc, exists) ?? PATH_DSH;
|
|
43
|
+
}
|
|
44
|
+
export function selfUpdatePaths(dshHome, platform = process.platform) {
|
|
45
|
+
const configured = process.env.DSH_WORKBENCH_HOME?.trim();
|
|
46
|
+
const dir = configured === undefined || configured === '' ? join(dshHome, 'workbench') : configured;
|
|
47
|
+
return {
|
|
48
|
+
dir,
|
|
49
|
+
script: join(dir, platform === 'win32' ? 'self-update.mjs' : 'self-update.sh'),
|
|
50
|
+
marker: join(dir, 'self-update.json'),
|
|
51
|
+
log: join(dir, 'self-update.log'),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function recoveryCommand(input) {
|
|
55
|
+
const cli = input.cli ?? PATH_DSH;
|
|
56
|
+
const command = isPathDsh(cli) ? 'dsh' : cliCommand(cli);
|
|
57
|
+
return `${command} plugin --profile ${input.profile} add ${input.pkg}@${input.version}`;
|
|
58
|
+
}
|
|
59
|
+
export function selfUpdateMarker(request, state, version, at) {
|
|
60
|
+
return { ...request, state, version, at };
|
|
61
|
+
}
|
|
62
|
+
export function selfUpdateScript(request, paths, cli = selfUpdateCliFor(request.profile)) {
|
|
63
|
+
const rescue = recoveryCommand({
|
|
64
|
+
profile: request.profile,
|
|
65
|
+
pkg: request.pkg,
|
|
66
|
+
version: request.from,
|
|
67
|
+
cli,
|
|
68
|
+
});
|
|
69
|
+
const lastsOne = recoveryCommand({
|
|
70
|
+
profile: request.profile,
|
|
71
|
+
pkg: request.pkg,
|
|
72
|
+
version: request.to === '' ? 'latest' : request.to,
|
|
73
|
+
cli,
|
|
74
|
+
});
|
|
75
|
+
const target = request.to === '' ? 'latest' : request.to;
|
|
76
|
+
const landed = request.to === '' ? 'is_other' : 'is_to';
|
|
77
|
+
const restart = isPathDsh(cli) ? '重启一次 dsh' : '退出并重开 Desktop';
|
|
78
|
+
const install = isPathDsh(cli)
|
|
79
|
+
? 'install() { dsh plugin --profile "$PROFILE" add "$PKG@$1" >>"$LOG" 2>&1; }'
|
|
80
|
+
: `install() { ${cliCommand(cli)} plugin --profile "$PROFILE" add "$PKG@$1" >>"$LOG" 2>&1; }`;
|
|
81
|
+
const guard = isPathDsh(cli)
|
|
82
|
+
? 'if ! command -v dsh >/dev/null 2>&1; then'
|
|
83
|
+
: `if ! command -v ${quoted(cli.file)} >/dev/null 2>&1; then`;
|
|
84
|
+
const missing = isPathDsh(cli)
|
|
85
|
+
? `say "PATH 上没有 dsh —— 这一跳没跑。救法:${rescue}"`
|
|
86
|
+
: `say ${quoted(`找不到 Desktop 的 dsh(${cli.file})—— 这一跳没跑。救法:${rescue}`)}`;
|
|
87
|
+
return `#!/usr/bin/env bash
|
|
88
|
+
set -uo pipefail
|
|
89
|
+
DSH_HOME=${quoted(request.dshHome)}
|
|
90
|
+
export DSH_HOME
|
|
91
|
+
PKG=${quoted(request.pkg)}
|
|
92
|
+
PROFILE=${quoted(request.profile)}
|
|
93
|
+
FROM=${quoted(request.from)}
|
|
94
|
+
TO=${quoted(target)}
|
|
95
|
+
MARKER=${quoted(paths.marker)}
|
|
96
|
+
LOG=${quoted(paths.log)}
|
|
97
|
+
PROFILE_DIR="$DSH_HOME/profiles/$PROFILE"
|
|
98
|
+
say() { printf '%s %s\\n' "$(date '+%F %T')" "$*" >>"$LOG"; }
|
|
99
|
+
on_disk() {
|
|
100
|
+
node -e 'const p=process.argv[1];try{process.stdout.write(require(p).version)}catch(e){}' \\
|
|
101
|
+
"$PROFILE_DIR/node_modules/$PKG/package.json" 2>/dev/null
|
|
102
|
+
}
|
|
103
|
+
write_marker() {
|
|
104
|
+
printf '{"pkg":"%s","profile":"%s","from":"%s","to":"%s","state":"%s","version":"%s","at":"%s"}\\n' \\
|
|
105
|
+
"$PKG" "$PROFILE" "$FROM" "$TO" "$1" "$2" "$(date -u '+%FT%TZ')" >"$MARKER"
|
|
106
|
+
}
|
|
107
|
+
${install}
|
|
108
|
+
is_to() { [ "$(on_disk)" = "$TO" ]; }
|
|
109
|
+
is_other() { [ -n "$(on_disk)" ] && [ "$(on_disk)" != "$FROM" ]; }
|
|
110
|
+
${guard}
|
|
111
|
+
${missing}
|
|
112
|
+
write_marker failed "$FROM"
|
|
113
|
+
exit 1
|
|
114
|
+
fi
|
|
115
|
+
say "开始 \${FROM} -> \${TO}(不卸任何一行)"
|
|
116
|
+
write_marker running "$FROM"
|
|
117
|
+
if install "$TO" && ${landed}; then
|
|
118
|
+
say "换好了:$(on_disk) 在盘上(目标 \${TO}),${restart} 才用得上"
|
|
119
|
+
write_marker ok "$(on_disk)"
|
|
120
|
+
exit 0
|
|
121
|
+
fi
|
|
122
|
+
say "换版没成(盘上 $(on_disk))—— 把 \${FROM} 装回去"
|
|
123
|
+
if install "$FROM" && [ "$(on_disk)" = "$FROM" ]; then
|
|
124
|
+
say "已回滚:\${FROM} 还在盘上,工作台这一行没被动过"
|
|
125
|
+
write_marker rolled-back "$FROM"
|
|
126
|
+
exit 0
|
|
127
|
+
fi
|
|
128
|
+
say "回滚也没成。救法:${rescue}(或 ${lastsOne})"
|
|
129
|
+
write_marker failed "$(on_disk)"
|
|
130
|
+
exit 1
|
|
131
|
+
`;
|
|
132
|
+
}
|
|
133
|
+
export function selfUpdateNodeScript(request, paths, cli = selfUpdateCliFor(request.profile)) {
|
|
134
|
+
const target = request.to === '' ? 'latest' : request.to;
|
|
135
|
+
const restart = isPathDsh(cli) ? '重启一次 dsh' : '退出并重开 Desktop';
|
|
136
|
+
const rescue = recoveryCommand({ profile: request.profile, pkg: request.pkg, version: request.from, cli });
|
|
137
|
+
const last = recoveryCommand({ profile: request.profile, pkg: request.pkg, version: target, cli });
|
|
138
|
+
const missing = isPathDsh(cli)
|
|
139
|
+
? `PATH 上没有 dsh —— 这一跳没跑。救法:${rescue}`
|
|
140
|
+
: `找不到 Desktop 的 dsh(${cli.file})—— 这一跳没跑。救法:${rescue}`;
|
|
141
|
+
return [
|
|
142
|
+
'#!/usr/bin/env node',
|
|
143
|
+
"import { spawnSync } from 'node:child_process';",
|
|
144
|
+
"import { appendFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs';",
|
|
145
|
+
"import { delimiter, join } from 'node:path';",
|
|
146
|
+
`const DSH_HOME = ${JSON.stringify(request.dshHome)};`,
|
|
147
|
+
`const PKG = ${JSON.stringify(request.pkg)};`,
|
|
148
|
+
`const PROFILE = ${JSON.stringify(request.profile)};`,
|
|
149
|
+
`const FROM = ${JSON.stringify(request.from)};`,
|
|
150
|
+
`const TO = ${JSON.stringify(target)};`,
|
|
151
|
+
`const MARKER = ${JSON.stringify(paths.marker)};`,
|
|
152
|
+
`const LOG = ${JSON.stringify(paths.log)};`,
|
|
153
|
+
`const CLI = ${JSON.stringify(cli)};`,
|
|
154
|
+
'const say = (message) => appendFileSync(LOG, `${new Date().toISOString()} ${message}\\n`);',
|
|
155
|
+
"const onDisk = () => { try { const read = JSON.parse(readFileSync(join(DSH_HOME, 'profiles', PROFILE, 'node_modules', PKG, 'package.json'), 'utf8')); return typeof read.version === 'string' ? read.version : ''; } catch { return ''; } };",
|
|
156
|
+
'const writeMarker = (state, version) => writeFileSync(MARKER, JSON.stringify({ pkg: PKG, profile: PROFILE, from: FROM, to: TO, state, version, at: new Date().toISOString() }) + \'\\n\');',
|
|
157
|
+
"const install = (version) => { const result = spawnSync(CLI.file, [...CLI.args, 'plugin', '--profile', PROFILE, 'add', `${PKG}@${version}`], { env: { ...process.env, ...CLI.env }, encoding: 'utf8' }); if (result.stdout) say(result.stdout.trim()); if (result.stderr) say(result.stderr.trim()); if (result.error) say(String(result.error)); return result.status === 0; };",
|
|
158
|
+
"const canRun = () => { if (CLI.file.includes('/') || (CLI.file.length > 2 && CLI.file[1] === ':')) return existsSync(CLI.file); const exts = process.platform === 'win32' ? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT').split(';') : ['']; return (process.env.PATH ?? '').split(delimiter).some((dir) => exts.some((ext) => existsSync(join(dir, CLI.file + ext)))); };",
|
|
159
|
+
'const isTo = () => onDisk() === TO;',
|
|
160
|
+
"const isOther = () => { const version = onDisk(); return version !== '' && version !== FROM; };",
|
|
161
|
+
`say(\`开始 \${FROM} -> \${TO}(不卸任何一行)\`);`,
|
|
162
|
+
"writeMarker('running', FROM);",
|
|
163
|
+
`if (!canRun()) { say(${JSON.stringify(missing)}); writeMarker('failed', FROM); process.exit(1); }`,
|
|
164
|
+
"if (install(TO) && (TO === 'latest' ? isOther() : isTo())) {",
|
|
165
|
+
' say(`换好了:${onDisk()} 在盘上(目标 ${TO}),' + restart + ' 才用得上`);',
|
|
166
|
+
" writeMarker('ok', onDisk());",
|
|
167
|
+
' process.exit(0);',
|
|
168
|
+
'}',
|
|
169
|
+
'say(`换版没成(盘上 ${onDisk()})—— 把 ${FROM} 装回去`);',
|
|
170
|
+
"if (install(FROM) && onDisk() === FROM) {",
|
|
171
|
+
' say(`已回滚:${FROM} 还在盘上,工作台这一行没被动过`);',
|
|
172
|
+
" writeMarker('rolled-back', FROM);",
|
|
173
|
+
' process.exit(0);',
|
|
174
|
+
'}',
|
|
175
|
+
`say(${JSON.stringify(`回滚也没成。救法:${rescue}(或 ${last})`)});`,
|
|
176
|
+
"writeMarker('failed', onDisk());",
|
|
177
|
+
'process.exit(1);',
|
|
178
|
+
'',
|
|
179
|
+
].join('\n');
|
|
180
|
+
}
|
|
181
|
+
export function planSelfUpdate(request, now = new Date(), cli = selfUpdateCliFor(request.profile), platform = process.platform) {
|
|
182
|
+
const paths = selfUpdatePaths(request.dshHome, platform);
|
|
183
|
+
const windows = platform === 'win32';
|
|
184
|
+
return {
|
|
185
|
+
paths,
|
|
186
|
+
script: windows ? selfUpdateNodeScript(request, paths, cli) : selfUpdateScript(request, paths, cli),
|
|
187
|
+
marker: selfUpdateMarker(request, 'running', request.from, now.toISOString()),
|
|
188
|
+
command: windows
|
|
189
|
+
? {
|
|
190
|
+
file: process.execPath,
|
|
191
|
+
args: [paths.script],
|
|
192
|
+
env: hostSurface().runtime === 'electron' ? { ELECTRON_RUN_AS_NODE: '1' } : {},
|
|
193
|
+
log: paths.log,
|
|
194
|
+
}
|
|
195
|
+
: { file: 'bash', args: [paths.script], env: {}, log: paths.log },
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
export function startSelfUpdate(request, onError, spawnImpl, cli = selfUpdateCliFor(request.profile)) {
|
|
199
|
+
const plan = planSelfUpdate(request, new Date(), cli);
|
|
200
|
+
mkdirSync(plan.paths.dir, { recursive: true });
|
|
201
|
+
writeFileAtomic.sync(plan.paths.script, plan.script, { mode: 0o755 });
|
|
202
|
+
chmodSync(plan.paths.script, 0o755);
|
|
203
|
+
writeFileAtomic.sync(plan.paths.marker, `${JSON.stringify(plan.marker)}\n`);
|
|
204
|
+
runDetached(plan.command, onError, spawnImpl);
|
|
205
|
+
const forward = recoveryCommand({
|
|
206
|
+
profile: request.profile,
|
|
207
|
+
pkg: request.pkg,
|
|
208
|
+
version: request.to === '' ? 'latest' : request.to,
|
|
209
|
+
cli,
|
|
210
|
+
});
|
|
211
|
+
return { started: true, paths: plan.paths, forward };
|
|
212
|
+
}
|
|
213
|
+
function asText(value) {
|
|
214
|
+
return typeof value === 'string' && value !== '' ? value : undefined;
|
|
215
|
+
}
|
|
216
|
+
function readTail(file) {
|
|
217
|
+
try {
|
|
218
|
+
const info = statSync(file);
|
|
219
|
+
if (!info.isFile() || info.size === 0)
|
|
220
|
+
return undefined;
|
|
221
|
+
const start = Math.max(0, info.size - 4096);
|
|
222
|
+
const length = info.size - start;
|
|
223
|
+
const buffer = Buffer.alloc(length);
|
|
224
|
+
const fd = openSync(file, 'r');
|
|
225
|
+
try {
|
|
226
|
+
readSync(fd, buffer, 0, length, start);
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
closeSync(fd);
|
|
230
|
+
}
|
|
231
|
+
const lines = buffer
|
|
232
|
+
.toString('utf8')
|
|
233
|
+
.split('\n')
|
|
234
|
+
.map((line) => line.trim())
|
|
235
|
+
.filter((line) => line !== '');
|
|
236
|
+
if (lines.length === 0)
|
|
237
|
+
return undefined;
|
|
238
|
+
return lines.slice(-4).join(' / ').slice(0, 800);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
export function readSelfUpdateView(home, profile) {
|
|
245
|
+
const paths = selfUpdatePaths(home);
|
|
246
|
+
let raw;
|
|
247
|
+
try {
|
|
248
|
+
raw = JSON.parse(readFileSync(paths.marker, 'utf8'));
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
if (typeof raw !== 'object' || raw === null)
|
|
254
|
+
return undefined;
|
|
255
|
+
const record = raw;
|
|
256
|
+
const state = asText(record.state);
|
|
257
|
+
if (state === undefined || !SELF_UPDATE_STATES.includes(state))
|
|
258
|
+
return undefined;
|
|
259
|
+
const owner = asText(record.profile);
|
|
260
|
+
if (profile !== undefined && owner !== undefined && owner !== profile)
|
|
261
|
+
return undefined;
|
|
262
|
+
const from = asText(record.from);
|
|
263
|
+
const to = asText(record.to);
|
|
264
|
+
const version = asText(record.version);
|
|
265
|
+
const at = asText(record.at);
|
|
266
|
+
const tail = readTail(paths.log);
|
|
267
|
+
return {
|
|
268
|
+
state: state,
|
|
269
|
+
...(owner === undefined ? {} : { profile: owner }),
|
|
270
|
+
...(from === undefined ? {} : { from }),
|
|
271
|
+
...(to === undefined ? {} : { to }),
|
|
272
|
+
...(version === undefined ? {} : { version }),
|
|
273
|
+
...(at === undefined ? {} : { at }),
|
|
274
|
+
...(tail === undefined ? {} : { tail }),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
export { runningVersionOf } from '@dsh-workbench/shared/host';
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export interface ToolchainProcess {
|
|
2
|
+
readonly versions: {
|
|
3
|
+
readonly electron?: string | undefined;
|
|
4
|
+
readonly node?: string | undefined;
|
|
5
|
+
};
|
|
6
|
+
readonly execPath: string;
|
|
7
|
+
readonly resourcesPath?: string | undefined;
|
|
8
|
+
}
|
|
9
|
+
export interface PnpmProcess {
|
|
10
|
+
readonly versions: {
|
|
11
|
+
readonly electron?: string | undefined;
|
|
12
|
+
readonly node?: string | undefined;
|
|
13
|
+
};
|
|
14
|
+
readonly resourcesPath?: string | undefined;
|
|
15
|
+
}
|
|
16
|
+
export declare function desktopPnpmPath(proc?: PnpmProcess, exists?: (path: string) => boolean): string | undefined;
|
|
17
|
+
export interface ToolchainOptions {
|
|
18
|
+
readonly root: string;
|
|
19
|
+
readonly proc?: ToolchainProcess | undefined;
|
|
20
|
+
readonly platform?: NodeJS.Platform | undefined;
|
|
21
|
+
readonly log?: ((message: string) => void) | undefined;
|
|
22
|
+
}
|
|
23
|
+
export interface ToolchainResult {
|
|
24
|
+
readonly pathDir?: string | undefined;
|
|
25
|
+
readonly npmDir?: string | undefined;
|
|
26
|
+
readonly pnpmDir?: string | undefined;
|
|
27
|
+
readonly files: readonly string[];
|
|
28
|
+
}
|
|
29
|
+
export declare const LOCAL_NPM_VERSION: string;
|
|
30
|
+
export declare const LOCAL_PNPM_VERSION: string;
|
|
31
|
+
export declare function localNpmRoot(root: string): string;
|
|
32
|
+
export declare function localNpmBin(root: string): string;
|
|
33
|
+
export declare function localNpmCache(root: string): string;
|
|
34
|
+
export declare function localNpmCommand(root: string, platform?: NodeJS.Platform): string;
|
|
35
|
+
export declare function localNpmInstallCommand(root: string, proc?: PnpmProcess, exists?: (path: string) => boolean, platform?: NodeJS.Platform): string | undefined;
|
|
36
|
+
export interface LocalNpmProvision {
|
|
37
|
+
readonly ok: boolean;
|
|
38
|
+
readonly detail: string;
|
|
39
|
+
readonly binDir?: string | undefined;
|
|
40
|
+
}
|
|
41
|
+
export declare function provisionLocalNpm(options: {
|
|
42
|
+
readonly root: string;
|
|
43
|
+
readonly run: (command: string) => Promise<{
|
|
44
|
+
ok: boolean;
|
|
45
|
+
detail: string;
|
|
46
|
+
}>;
|
|
47
|
+
readonly installed?: (() => boolean) | undefined;
|
|
48
|
+
readonly platform?: NodeJS.Platform | undefined;
|
|
49
|
+
readonly proc?: PnpmProcess | undefined;
|
|
50
|
+
readonly exists?: ((path: string) => boolean) | undefined;
|
|
51
|
+
}): Promise<LocalNpmProvision>;
|
|
52
|
+
export declare function localPnpmRoot(root: string): string;
|
|
53
|
+
export declare function localPnpmBin(root: string): string;
|
|
54
|
+
export declare function localPnpmCommand(root: string, platform?: NodeJS.Platform): string;
|
|
55
|
+
export declare function localPnpmInstallCommand(root: string, proc?: PnpmProcess, exists?: (path: string) => boolean, platform?: NodeJS.Platform): string | undefined;
|
|
56
|
+
export interface LocalPnpmProvision {
|
|
57
|
+
readonly ok: boolean;
|
|
58
|
+
readonly detail: string;
|
|
59
|
+
readonly binDir?: string | undefined;
|
|
60
|
+
}
|
|
61
|
+
export declare function provisionLocalPnpm(options: {
|
|
62
|
+
readonly root: string;
|
|
63
|
+
readonly run: (command: string) => Promise<{
|
|
64
|
+
ok: boolean;
|
|
65
|
+
detail: string;
|
|
66
|
+
}>;
|
|
67
|
+
readonly installed?: (() => boolean) | undefined;
|
|
68
|
+
readonly platform?: NodeJS.Platform | undefined;
|
|
69
|
+
readonly proc?: PnpmProcess | undefined;
|
|
70
|
+
readonly exists?: ((path: string) => boolean) | undefined;
|
|
71
|
+
}): Promise<LocalPnpmProvision>;
|
|
72
|
+
export declare function provisionLocalPnpmSync(options: {
|
|
73
|
+
readonly root: string;
|
|
74
|
+
readonly run?: ((command: string) => void) | undefined;
|
|
75
|
+
readonly installed?: (() => boolean) | undefined;
|
|
76
|
+
readonly platform?: NodeJS.Platform | undefined;
|
|
77
|
+
readonly proc?: PnpmProcess | undefined;
|
|
78
|
+
readonly exists?: ((path: string) => boolean) | undefined;
|
|
79
|
+
}): LocalPnpmProvision;
|
|
80
|
+
export declare function desktopNodeBinary(proc?: ToolchainProcess, platform?: NodeJS.Platform, exists?: (path: string) => boolean): string | undefined;
|
|
81
|
+
export declare function desktopNodeShim(proc?: ToolchainProcess, platform?: NodeJS.Platform, exists?: (path: string) => boolean): string | undefined;
|
|
82
|
+
export declare function toolchainBinDir(root: string): string;
|
|
83
|
+
export declare function ensureToolchain(options: ToolchainOptions): ToolchainResult;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { execaSync } from 'execa';
|
|
2
|
+
import lockfile from 'proper-lockfile';
|
|
3
|
+
import writeFileAtomic from 'write-file-atomic';
|
|
4
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { quoteShellArg, quoteShellArgFor } from '@dsh-workbench/shared/host';
|
|
7
|
+
import { hostSurface } from './platform.js';
|
|
8
|
+
export function desktopPnpmPath(proc = process, exists = existsSync) {
|
|
9
|
+
if (hostSurface({ proc }).deployment !== 'desktop')
|
|
10
|
+
return undefined;
|
|
11
|
+
const resources = proc.resourcesPath;
|
|
12
|
+
if (resources === undefined || resources === '')
|
|
13
|
+
return undefined;
|
|
14
|
+
const path = join(resources, 'runtime', 'primary-runtime', 'dependencies', 'pnpm', 'bin', 'pnpm.cjs');
|
|
15
|
+
return exists(path) ? path : undefined;
|
|
16
|
+
}
|
|
17
|
+
export const LOCAL_NPM_VERSION = [10, 9, 4].join('.');
|
|
18
|
+
export const LOCAL_PNPM_VERSION = [10, 32, 1].join('.');
|
|
19
|
+
export function localNpmRoot(root) {
|
|
20
|
+
return join(root, 'npm');
|
|
21
|
+
}
|
|
22
|
+
export function localNpmBin(root) {
|
|
23
|
+
return join(localNpmRoot(root), 'node_modules', '.bin');
|
|
24
|
+
}
|
|
25
|
+
export function localNpmCache(root) {
|
|
26
|
+
return join(root, 'npm-cache');
|
|
27
|
+
}
|
|
28
|
+
export function localNpmCommand(root, platform = process.platform) {
|
|
29
|
+
return join(localNpmBin(root), platform === 'win32' ? 'npm.cmd' : 'npm');
|
|
30
|
+
}
|
|
31
|
+
export function localNpmInstallCommand(root, proc = process, exists = existsSync, platform = process.platform) {
|
|
32
|
+
const pnpm = desktopPnpmPath(proc, exists);
|
|
33
|
+
if (pnpm === undefined)
|
|
34
|
+
return undefined;
|
|
35
|
+
return (`node ${quoteShellArgFor(pnpm, platform)} add --dir ${quoteShellArgFor(localNpmRoot(root), platform)} ` +
|
|
36
|
+
`npm@${LOCAL_NPM_VERSION} --store-dir ${quoteShellArgFor(join(root, 'npm-store'), platform)}`);
|
|
37
|
+
}
|
|
38
|
+
export async function provisionLocalNpm(options) {
|
|
39
|
+
const command = localNpmInstallCommand(options.root, options.proc ?? process, options.exists ?? existsSync);
|
|
40
|
+
if (command === undefined)
|
|
41
|
+
return { ok: false, detail: '找不到 Desktop 自带 pnpm,装不了本地 npm' };
|
|
42
|
+
try {
|
|
43
|
+
mkdirSync(localNpmRoot(options.root), { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
return { ok: false, detail: `建不了 ${localNpmRoot(options.root)}:${String(error)}` };
|
|
47
|
+
}
|
|
48
|
+
const result = await options.run(command);
|
|
49
|
+
if (!result.ok)
|
|
50
|
+
return { ok: false, detail: result.detail };
|
|
51
|
+
const platform = options.platform ?? process.platform;
|
|
52
|
+
const installed = options.installed?.() ?? existsSync(localNpmCommand(options.root, platform));
|
|
53
|
+
if (!installed)
|
|
54
|
+
return { ok: false, detail: 'pnpm 跑完但本地 npm 不在' };
|
|
55
|
+
return {
|
|
56
|
+
ok: true,
|
|
57
|
+
binDir: localNpmBin(options.root),
|
|
58
|
+
detail: `npm@${LOCAL_NPM_VERSION} 装到 ${localNpmRoot(options.root)}`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function localPnpmRoot(root) {
|
|
62
|
+
return join(root, 'pnpm-10');
|
|
63
|
+
}
|
|
64
|
+
export function localPnpmBin(root) {
|
|
65
|
+
return join(localPnpmRoot(root), 'node_modules', '.bin');
|
|
66
|
+
}
|
|
67
|
+
export function localPnpmCommand(root, platform = process.platform) {
|
|
68
|
+
return join(localPnpmBin(root), platform === 'win32' ? 'pnpm.cmd' : 'pnpm');
|
|
69
|
+
}
|
|
70
|
+
export function localPnpmInstallCommand(root, proc = process, exists = existsSync, platform = process.platform) {
|
|
71
|
+
const pnpm = desktopPnpmPath(proc, exists);
|
|
72
|
+
if (pnpm === undefined)
|
|
73
|
+
return undefined;
|
|
74
|
+
return (`node ${quoteShellArgFor(pnpm, platform)} add --dir ${quoteShellArgFor(localPnpmRoot(root), platform)} ` +
|
|
75
|
+
`pnpm@${LOCAL_PNPM_VERSION} --store-dir ${quoteShellArgFor(join(root, 'pnpm-store'), platform)}`);
|
|
76
|
+
}
|
|
77
|
+
export async function provisionLocalPnpm(options) {
|
|
78
|
+
const command = localPnpmInstallCommand(options.root, options.proc ?? process, options.exists ?? existsSync);
|
|
79
|
+
if (command === undefined)
|
|
80
|
+
return { ok: false, detail: '找不到 Desktop 自带 pnpm,装不了本地 pnpm 10' };
|
|
81
|
+
try {
|
|
82
|
+
mkdirSync(localPnpmRoot(options.root), { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
return { ok: false, detail: `建不了 ${localPnpmRoot(options.root)}:${String(error)}` };
|
|
86
|
+
}
|
|
87
|
+
const result = await options.run(command);
|
|
88
|
+
if (!result.ok)
|
|
89
|
+
return { ok: false, detail: result.detail };
|
|
90
|
+
const platform = options.platform ?? process.platform;
|
|
91
|
+
const installed = options.installed?.() ?? existsSync(localPnpmCommand(options.root, platform));
|
|
92
|
+
if (!installed)
|
|
93
|
+
return { ok: false, detail: 'pnpm 跑完但本地 pnpm 10 不在' };
|
|
94
|
+
return {
|
|
95
|
+
ok: true,
|
|
96
|
+
binDir: localPnpmBin(options.root),
|
|
97
|
+
detail: `pnpm@${LOCAL_PNPM_VERSION} 装到 ${localPnpmRoot(options.root)}`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function provisionLocalPnpmSync(options) {
|
|
101
|
+
const command = localPnpmInstallCommand(options.root, options.proc ?? process, options.exists ?? existsSync);
|
|
102
|
+
if (command === undefined)
|
|
103
|
+
return { ok: false, detail: '找不到 Desktop 自带 pnpm,装不了本地 pnpm 10' };
|
|
104
|
+
try {
|
|
105
|
+
mkdirSync(localPnpmRoot(options.root), { recursive: true });
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
return { ok: false, detail: `建不了 ${localPnpmRoot(options.root)}:${String(error)}` };
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const run = options.run ??
|
|
112
|
+
((line) => {
|
|
113
|
+
execaSync(line, { stdio: 'ignore', timeout: 180_000, env: process.env, shell: true });
|
|
114
|
+
});
|
|
115
|
+
run(command);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
return { ok: false, detail: String(error) };
|
|
119
|
+
}
|
|
120
|
+
const platform = options.platform ?? process.platform;
|
|
121
|
+
const installed = options.installed?.() ?? existsSync(localPnpmCommand(options.root, platform));
|
|
122
|
+
if (!installed)
|
|
123
|
+
return { ok: false, detail: 'pnpm 跑完但本地 pnpm 10 不在' };
|
|
124
|
+
return {
|
|
125
|
+
ok: true,
|
|
126
|
+
binDir: localPnpmBin(options.root),
|
|
127
|
+
detail: `pnpm@${LOCAL_PNPM_VERSION} 装到 ${localPnpmRoot(options.root)}`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function readText(file) {
|
|
131
|
+
try {
|
|
132
|
+
return readFileSync(file, 'utf8');
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
export function desktopNodeBinary(proc = process, platform = process.platform, exists = existsSync) {
|
|
139
|
+
if (hostSurface({ proc }).deployment !== 'desktop')
|
|
140
|
+
return undefined;
|
|
141
|
+
const resources = proc.resourcesPath;
|
|
142
|
+
if (resources === undefined || resources === '')
|
|
143
|
+
return undefined;
|
|
144
|
+
const path = join(resources, 'runtime', 'primary-runtime', 'dependencies', 'node', 'bin', platform === 'win32' ? 'node.exe' : 'node');
|
|
145
|
+
return exists(path) ? path : undefined;
|
|
146
|
+
}
|
|
147
|
+
export function desktopNodeShim(proc = process, platform = process.platform, exists = existsSync) {
|
|
148
|
+
if (proc.versions.electron === undefined)
|
|
149
|
+
return undefined;
|
|
150
|
+
const node = desktopNodeBinary(proc, platform, exists);
|
|
151
|
+
if (platform === 'win32') {
|
|
152
|
+
return node === undefined
|
|
153
|
+
? `@echo off\r\nset ELECTRON_RUN_AS_NODE=1\r\n"${proc.execPath}" %*\r\n`
|
|
154
|
+
: `@echo off\r\n"${node}" %*\r\n`;
|
|
155
|
+
}
|
|
156
|
+
return node === undefined
|
|
157
|
+
? `#!/bin/sh\nexec env ELECTRON_RUN_AS_NODE=1 ${quoteShellArg(proc.execPath)} "$@"\n`
|
|
158
|
+
: `#!/bin/sh\nexec ${quoteShellArg(node)} "$@"\n`;
|
|
159
|
+
}
|
|
160
|
+
export function toolchainBinDir(root) {
|
|
161
|
+
return join(root, 'bin', 'desktop');
|
|
162
|
+
}
|
|
163
|
+
export function ensureToolchain(options) {
|
|
164
|
+
const platform = options.platform ?? process.platform;
|
|
165
|
+
const shim = desktopNodeShim(options.proc ?? process, platform);
|
|
166
|
+
if (shim === undefined)
|
|
167
|
+
return { files: [] };
|
|
168
|
+
const pathDir = toolchainBinDir(options.root);
|
|
169
|
+
const file = join(pathDir, platform === 'win32' ? 'node.cmd' : 'node');
|
|
170
|
+
const files = [];
|
|
171
|
+
let release;
|
|
172
|
+
try {
|
|
173
|
+
mkdirSync(pathDir, { recursive: true });
|
|
174
|
+
release = lockfile.lockSync(pathDir, { realpath: false });
|
|
175
|
+
if (readText(file) !== shim) {
|
|
176
|
+
rmSync(file, { force: true });
|
|
177
|
+
writeFileAtomic.sync(file, shim, 'utf8');
|
|
178
|
+
chmodSync(file, 0o755);
|
|
179
|
+
files.push(file);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
options.log?.(`工具链 shim 没写成:${String(error)}`);
|
|
184
|
+
return { files };
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
release?.();
|
|
188
|
+
}
|
|
189
|
+
const npmDir = existsSync(localNpmCommand(options.root, platform)) ? localNpmBin(options.root) : undefined;
|
|
190
|
+
const pnpmDir = existsSync(localPnpmCommand(options.root, platform)) ? localPnpmBin(options.root) : undefined;
|
|
191
|
+
return {
|
|
192
|
+
pathDir,
|
|
193
|
+
...(npmDir === undefined ? {} : { npmDir }),
|
|
194
|
+
...(pnpmDir === undefined ? {} : { pnpmDir }),
|
|
195
|
+
files,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { checkVersionRange, type VersionVerdict } from '@dsh-workbench/shared/host';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { checkVersionRange } from '@dsh-workbench/shared/host';
|
package/package.json
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-workbench/adapter-probe",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
|
+
"description": "Workbench adapter-probe adapter.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"execa": "^10.2.0",
|
|
17
|
+
"proper-lockfile": "^4.1.2",
|
|
18
|
+
"write-file-atomic": "^8.0.0",
|
|
19
|
+
"@dsh-workbench/adapter-profile": "1.0.0-beta.1",
|
|
20
|
+
"@dsh-workbench/adapter-runtime": "1.0.0-beta.1",
|
|
21
|
+
"@dsh-workbench/shared": "1.0.0-beta.1",
|
|
22
|
+
"@dsh-workbench/adapter-supply": "1.0.0-beta.1"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^24.19.0",
|
|
26
|
+
"@types/proper-lockfile": "^4.1.4",
|
|
27
|
+
"@types/write-file-atomic": "^4.0.3",
|
|
28
|
+
"vitest": "^5.0.2",
|
|
29
|
+
"dayjs": "^1.11.23",
|
|
30
|
+
"@vitest/coverage-v8": "^5.0.2"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc -p tsconfig.json",
|
|
38
|
+
"test": "vitest run --testTimeout=20000",
|
|
39
|
+
"arch": "arch-guard",
|
|
40
|
+
"check:runtimes": "node ../../scripts/check-adapter-package.mjs --runtime",
|
|
41
|
+
"coverage": "vitest run --coverage --coverage.reporter=text-summary --testTimeout=20000"
|
|
42
|
+
}
|
|
6
43
|
}
|