@dsh-workbench/adapter-probe 1.1.14-beta.2 → 1.1.14

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.
@@ -1,18 +1,18 @@
1
1
  export declare const latestVersion: (options: {
2
- readonly shell: import("@dsh-workbench/shared").DshShell;
2
+ readonly seam: import("@dsh-workbench/shared/host").Seam;
3
3
  readonly name: string;
4
4
  readonly registry: string;
5
5
  readonly force?: boolean;
6
6
  }) => Promise<string | undefined>;
7
7
  export declare const missingReleaseParts: (options: {
8
- readonly shell: import("@dsh-workbench/shared").DshShell;
8
+ readonly seam: import("@dsh-workbench/shared/host").Seam;
9
9
  readonly name: string;
10
10
  readonly registry: string;
11
11
  readonly version: string;
12
12
  readonly force?: boolean;
13
13
  }) => Promise<readonly string[]>;
14
14
  export declare const probeRelease: (options: {
15
- readonly shell: import("@dsh-workbench/shared").DshShell;
15
+ readonly seam: import("@dsh-workbench/shared/host").Seam;
16
16
  readonly package: string;
17
17
  readonly registry?: string | undefined;
18
18
  readonly force?: boolean;
@@ -1,6 +1,6 @@
1
1
  import { createReleasePort } from '@dsh-workbench/adapter-profile';
2
- import { npmCacheDir, vendorPrefix } from '@dsh-workbench/adapter-runtime';
3
- const port = createReleasePort({ cacheDir: npmCacheDir(), prefix: vendorPrefix() });
2
+ import { vendorPrefix } from '@dsh-workbench/adapter-runtime';
3
+ const port = createReleasePort({ prefix: vendorPrefix() });
4
4
  export const latestVersion = port.latestVersion;
5
5
  export const missingReleaseParts = port.missingReleaseParts;
6
6
  export const probeRelease = port.probeRelease;
@@ -1,5 +1,5 @@
1
- import type { spawn } from 'node:child_process';
2
1
  import type { DshHostContext } from '@dsh-workbench/shared';
2
+ import type { Seam } from '@dsh-workbench/shared/host';
3
3
  export type SeedLog = (message: string) => void;
4
4
  export interface RuntimeSeedPaths {
5
5
  readonly root: string;
@@ -21,9 +21,7 @@ export interface RuntimeSeedOptions {
21
21
  }
22
22
  export declare function opencliBinName(platform?: NodeJS.Platform): string;
23
23
  export declare function runtimeSeedPaths(root: string, platform?: NodeJS.Platform): RuntimeSeedPaths;
24
- export declare function runtimeSeedRootOf(ctx: DshHostContext): string | null;
25
- export declare function coversWritableRoot(workspaceRoot: string, root: string): boolean;
26
- export declare function warnUnwritableRoot(ctx: DshHostContext): void;
24
+ export declare function runtimeSeedRootOf(_ctx: DshHostContext): string | null;
27
25
  export declare function wrapperRootOf(fallbackRoot: string): string;
28
26
  export declare function isInstalledCopy(packageRoot: string): boolean;
29
27
  export declare function opencliWrapperScript(packageRoot: string, home: string, platform?: NodeJS.Platform): string;
@@ -38,7 +36,7 @@ export declare function workshopSeedPlan(packageRoot: string, home: string, root
38
36
  readonly entry: SeedCommand;
39
37
  readonly full: SeedCommand;
40
38
  };
41
- export declare function runDetached(command: SeedCommand, onError: SeedLog, spawnImpl?: typeof spawn): void;
39
+ export declare function runDetached(command: SeedCommand, onError: SeedLog, seam?: Seam | undefined): void;
42
40
  export declare function prependPath(entryDir: string, env?: NodeJS.ProcessEnv): boolean;
43
- export declare function seedWorkbenchToolchain(root: string, log: SeedLog): void;
41
+ export declare function seedWorkbenchToolchain(root: string, log: SeedLog, seam?: Seam | undefined): Promise<void>;
44
42
  export declare function seedRuntimeFiles(options: RuntimeSeedOptions): readonly string[];
@@ -1,11 +1,9 @@
1
- import { execa } from 'execa';
2
1
  import { toolchainManifestOf } from '@dsh-workbench/adapter-runtime';
3
2
  import { createRequire } from 'node:module';
4
- import writeFileAtomic from 'write-file-atomic';
5
- import { chmodSync, closeSync, mkdirSync, openSync, readFileSync, rmSync, } from 'node:fs';
3
+ import { mkdirSync, readFileSync, rmSync } from 'node:fs';
6
4
  import { delimiter, dirname, join, sep } from 'node:path';
7
- import { quoteShellArg, quoteShellArgFor } from '@dsh-workbench/shared/host';
8
- import { desktopPnpmPath, desktopPnpmVersion, ensureToolchain, localNpmCache, localPnpmCommand, provisionLocalPnpmSync, } from './toolchain.js';
5
+ import { pathWithBinsOf, quoteShellArg, quoteShellArgFor, workbenchRoot, writeTextFile } from '@dsh-workbench/shared/host';
6
+ import { desktopBundledBins, desktopNodeExecutable, desktopPnpmPath, desktopPnpmVersion, ensureToolchain, localNpmCache, localPnpmCommand, provisionLocalPnpm, } from './toolchain.js';
9
7
  export function opencliBinName(platform = process.platform) {
10
8
  return platform === 'win32' ? 'opencli.cmd' : 'opencli';
11
9
  }
@@ -25,32 +23,8 @@ export function runtimeSeedPaths(root, platform = process.platform) {
25
23
  workshopManifest: join(workshop, 'package.json'),
26
24
  };
27
25
  }
28
- export function runtimeSeedRootOf(ctx) {
29
- const configured = process.env.DSH_WORKBENCH_HOME?.trim();
30
- if (configured !== undefined && configured !== '')
31
- return configured;
32
- const homePath = ctx.get('dshHomePath');
33
- if (typeof homePath !== 'function')
34
- return null;
35
- try {
36
- return homePath('workbench');
37
- }
38
- catch {
39
- return null;
40
- }
41
- }
42
- export function coversWritableRoot(workspaceRoot, root) {
43
- const prefix = workspaceRoot.endsWith(sep) ? workspaceRoot : `${workspaceRoot}${sep}`;
44
- return root === workspaceRoot || root.startsWith(prefix);
45
- }
46
- export function warnUnwritableRoot(ctx) {
47
- const root = runtimeSeedRootOf(ctx);
48
- if (root === null)
49
- return;
50
- const workspaceRoot = ctx.get('sandboxPolicy')?.workspaceRoot;
51
- if (workspaceRoot === undefined || coversWritableRoot(workspaceRoot, root))
52
- return;
53
- ctx.logger?.warn?.(`[dsh-workbench] 可写根 ${workspaceRoot} 盖不住 ${root}:市场装插件、自举命令可能被沙箱挡(把 profile/sandbox.patch.yml 那一行写进这个 profile 的 cordis.patch.yml 再重启)`);
26
+ export function runtimeSeedRootOf(_ctx) {
27
+ return workbenchRoot();
54
28
  }
55
29
  export function wrapperRootOf(fallbackRoot) {
56
30
  try {
@@ -88,49 +62,46 @@ export function workshopSeedPlan(packageRoot, home, root, platform = process.pla
88
62
  full: { file: 'bash', args: [script], env, log },
89
63
  };
90
64
  }
91
- const execaSpawn = ((file, args, options) => execa(file, [...args], { ...options, reject: false }));
92
- export function runDetached(command, onError, spawnImpl = execaSpawn) {
93
- let fd = null;
65
+ export function runDetached(command, onError, seam) {
66
+ if (seam === undefined) {
67
+ onError(`自举脚本没跑起来(${command.file}):宿主没给执行缝`);
68
+ return;
69
+ }
94
70
  try {
95
- fd = openSync(command.log, 'a');
96
- const child = spawnImpl(command.file, [...command.args], {
97
- env: { ...process.env, ...command.env },
98
- detached: true,
99
- stdio: ['ignore', fd, fd],
71
+ const handle = seam.spawn({
72
+ target: { argv: [command.file, ...command.args] },
73
+ intent: { kind: 'host' },
74
+ lifetime: 'host',
75
+ logFile: command.log,
76
+ env: command.env,
77
+ label: 'seed',
100
78
  });
101
- child.on('error', (error) => onError(`自举脚本没跑起来(${command.file}):${String(error)}`));
102
- child.unref();
79
+ handle.onError((error) => onError(`自举脚本没跑起来(${command.file}):${String(error)}`));
103
80
  }
104
81
  catch (error) {
105
82
  onError(`自举脚本没跑起来(${command.file}):${String(error)}`);
106
83
  }
107
- finally {
108
- if (fd !== null)
109
- closeQuietly(fd);
110
- }
111
- }
112
- function closeQuietly(fd) {
113
- try {
114
- closeSync(fd);
115
- }
116
- catch {
117
- return;
118
- }
119
84
  }
120
85
  export function prependPath(entryDir, env = process.env) {
121
86
  const parts = (env.PATH ?? '').split(delimiter).filter((part) => part !== '');
122
87
  if (parts.includes(entryDir))
123
88
  return false;
124
- env.PATH = [entryDir, ...parts].join(delimiter);
89
+ env.PATH = pathWithBinsOf(env.PATH, [entryDir]);
125
90
  return true;
126
91
  }
127
- export function seedWorkbenchToolchain(root, log) {
92
+ export async function seedWorkbenchToolchain(root, log, seam) {
93
+ for (const dir of desktopBundledBins()) {
94
+ if (prependPath(dir))
95
+ log(`PATH 前插 Desktop 自带 ${dir}`);
96
+ }
128
97
  const bundledPnpm = desktopPnpmPath();
129
98
  const bundledMajor = Number(desktopPnpmVersion()?.split('.')[0] ?? '0');
130
99
  const bundledUsable = bundledPnpm !== undefined && bundledMajor >= 10;
131
100
  if (bundledPnpm !== undefined && bundledUsable) {
132
- process.env.DSH_WB_PNPM = `node ${quoteShellArgFor(bundledPnpm, process.platform)}`;
133
- log(`pnpm 用 Desktop 自带的:${bundledPnpm}`);
101
+ const bundledNode = desktopNodeExecutable();
102
+ const nodePrefix = bundledNode === undefined ? 'node' : quoteShellArgFor(bundledNode, process.platform);
103
+ process.env.DSH_WB_PNPM = `${nodePrefix} ${quoteShellArgFor(bundledPnpm, process.platform)}`;
104
+ log(`pnpm 用 Desktop 自带的:${bundledPnpm}(node: ${nodePrefix})`);
134
105
  }
135
106
  else if (bundledPnpm !== undefined) {
136
107
  log(`Desktop 自带 pnpm 版本低于 10,优先用 PATH 上的 pnpm`);
@@ -155,7 +126,7 @@ export function seedWorkbenchToolchain(root, log) {
155
126
  process.env.DSH_WB_PNPM = localPnpmCommand(root);
156
127
  }
157
128
  else if (!bundledUsable && toolchain.pathDir !== undefined) {
158
- const installed = provisionLocalPnpmSync({ root });
129
+ const installed = await provisionLocalPnpm({ root, seam });
159
130
  if (installed.ok && installed.binDir !== undefined) {
160
131
  if (prependPath(installed.binDir))
161
132
  log(`PATH 前插 ${installed.binDir}`);
@@ -178,15 +149,14 @@ export function seedRuntimeFiles(options) {
178
149
  const manifest = join(paths.opencliRoot, 'toolchain', 'package.json');
179
150
  mkdirSync(dirname(manifest), { recursive: true });
180
151
  if (readText(manifest) !== toolchainManifestOf()) {
181
- writeFileAtomic.sync(manifest, toolchainManifestOf(), 'utf8');
152
+ writeTextFile(manifest, toolchainManifestOf());
182
153
  done.push(manifest);
183
154
  }
184
155
  rmSync(join(paths.opencliRoot, 'toolchain', 'pnpm-workspace.yaml'), { force: true });
185
156
  const wanted = opencliWrapperScript(options.packageRoot, options.home, platform);
186
157
  if (readText(paths.opencliBin) !== wanted) {
187
158
  rmSync(paths.opencliBin, { force: true });
188
- writeFileAtomic.sync(paths.opencliBin, wanted, 'utf8');
189
- chmodSync(paths.opencliBin, 0o755);
159
+ writeTextFile(paths.opencliBin, wanted, { mode: 0o755 });
190
160
  done.push(paths.opencliBin);
191
161
  }
192
162
  }
@@ -1,4 +1,4 @@
1
- import type { spawn } from 'node:child_process';
1
+ import type { Seam } from '@dsh-workbench/shared/host';
2
2
  import { type SeedCommand } from './runtime-seed.js';
3
3
  import { SELF_UPDATE_STATES, type SelfUpdateState, type WorkbenchSelfUpdate } from '@dsh-workbench/shared/contract/protocol/bundle-install';
4
4
  export { SELF_UPDATE_STATES, type SelfUpdateState };
@@ -47,7 +47,7 @@ export interface SelfUpdateStart {
47
47
  readonly paths: SelfUpdatePaths;
48
48
  readonly forward: string;
49
49
  }
50
- export declare function selfUpdatePaths(dshHome: string, platform?: NodeJS.Platform): SelfUpdatePaths;
50
+ export declare function selfUpdatePaths(_dshHome: string, platform?: NodeJS.Platform): SelfUpdatePaths;
51
51
  export declare function recoveryCommand(input: {
52
52
  readonly profile: string;
53
53
  readonly pkg: string;
@@ -58,6 +58,6 @@ export declare function selfUpdateMarker(request: SelfUpdateRequest, state: Self
58
58
  export declare function selfUpdateScript(request: SelfUpdateRequest, paths: SelfUpdatePaths, cli?: SelfUpdateCli): string;
59
59
  export declare function selfUpdateNodeScript(request: SelfUpdateRequest, paths: SelfUpdatePaths, cli?: SelfUpdateCli): string;
60
60
  export declare function planSelfUpdate(request: SelfUpdateRequest, now?: Date, cli?: SelfUpdateCli, platform?: NodeJS.Platform): SelfUpdatePlan;
61
- export declare function startSelfUpdate(request: SelfUpdateRequest, onError: (message: string) => void, spawnImpl?: typeof spawn, cli?: SelfUpdateCli): SelfUpdateStart;
61
+ export declare function startSelfUpdate(request: SelfUpdateRequest, onError: (message: string) => void, seam?: Seam | undefined, cli?: SelfUpdateCli): SelfUpdateStart;
62
62
  export declare function readSelfUpdateView(home: string, profile?: string): WorkbenchSelfUpdate | undefined;
63
63
  export { runningVersionOf } from '@dsh-workbench/shared/host';
@@ -1,7 +1,6 @@
1
- import writeFileAtomic from 'write-file-atomic';
2
- import { quoteShellArg } from '@dsh-workbench/shared/host';
1
+ import { now as clockNow, quoteShellArg, workbenchRoot, writeJsonFile, writeTextFile } from '@dsh-workbench/shared/host';
3
2
  import { hostSurface } from './platform.js';
4
- import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, statSync, } from 'node:fs';
3
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, statSync, } from 'node:fs';
5
4
  import { dirname, join } from 'node:path';
6
5
  import { runDetached } from './runtime-seed.js';
7
6
  import { SELF_UPDATE_STATES, } from '@dsh-workbench/shared/contract/protocol/bundle-install';
@@ -41,9 +40,8 @@ export function selfUpdateCliFor(profile, proc = process, exists = existsSync) {
41
40
  return PATH_DSH;
42
41
  return desktopSelfUpdateCli(proc, exists) ?? PATH_DSH;
43
42
  }
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;
43
+ export function selfUpdatePaths(_dshHome, platform = process.platform) {
44
+ const dir = workbenchRoot();
47
45
  return {
48
46
  dir,
49
47
  script: join(dir, platform === 'win32' ? 'self-update.mjs' : 'self-update.sh'),
@@ -178,7 +176,7 @@ export function selfUpdateNodeScript(request, paths, cli = selfUpdateCliFor(requ
178
176
  '',
179
177
  ].join('\n');
180
178
  }
181
- export function planSelfUpdate(request, now = new Date(), cli = selfUpdateCliFor(request.profile), platform = process.platform) {
179
+ export function planSelfUpdate(request, now = new Date(clockNow()), cli = selfUpdateCliFor(request.profile), platform = process.platform) {
182
180
  const paths = selfUpdatePaths(request.dshHome, platform);
183
181
  const windows = platform === 'win32';
184
182
  return {
@@ -195,13 +193,12 @@ export function planSelfUpdate(request, now = new Date(), cli = selfUpdateCliFor
195
193
  : { file: 'bash', args: [paths.script], env: {}, log: paths.log },
196
194
  };
197
195
  }
198
- export function startSelfUpdate(request, onError, spawnImpl, cli = selfUpdateCliFor(request.profile)) {
199
- const plan = planSelfUpdate(request, new Date(), cli);
196
+ export function startSelfUpdate(request, onError, seam, cli = selfUpdateCliFor(request.profile)) {
197
+ const plan = planSelfUpdate(request, new Date(clockNow()), cli);
200
198
  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);
199
+ writeTextFile(plan.paths.script, plan.script, { mode: 0o755 });
200
+ writeJsonFile(plan.paths.marker, plan.marker);
201
+ runDetached(plan.command, onError, seam);
205
202
  const forward = recoveryCommand({
206
203
  profile: request.profile,
207
204
  pkg: request.pkg,
@@ -1,2 +1,5 @@
1
- import type { SupplyPort } from '@dsh-workbench/shared/host';
2
- export declare function createSupplyPort(): SupplyPort;
1
+ import type { Seam, SupplyPort } from '@dsh-workbench/shared/host';
2
+ export interface ProbeSupplyOptions {
3
+ readonly seam?: Seam | undefined;
4
+ }
5
+ export declare function createSupplyPort(options?: ProbeSupplyOptions): SupplyPort;
@@ -7,7 +7,8 @@ import { startSelfUpdate } from './self-update.js';
7
7
  import { declarationGaps, declarationRefusalOf, moduleDeclarationOf, packageRouteOf, pathDeclarationOf, pluginInstallPlan, profileDirOf, profileModules, readInstalledModule, readProfilePackage, registryDeclarationOf, resolvePluginSource, splitNpmSpec, } from '@dsh-workbench/adapter-supply';
8
8
  import { readBundleManager, refusalReason } from '@dsh-workbench/shared/host';
9
9
  import { OPENCLI_MANIFEST_FILE } from '@dsh-workbench/adapter-runtime';
10
- export function createSupplyPort() {
10
+ export function createSupplyPort(options = {}) {
11
+ const seam = options.seam;
11
12
  const port = {
12
13
  isolatedRoot,
13
14
  isolatedHome,
@@ -42,7 +43,7 @@ export function createSupplyPort() {
42
43
  splitNpmSpec,
43
44
  libInstallCommand,
44
45
  libUninstallCommand,
45
- startSelfUpdate,
46
+ startSelfUpdate: (request, onError) => startSelfUpdate(request, onError, seam),
46
47
  };
47
48
  return { ...port, env: port, modules: port };
48
49
  }
@@ -1,4 +1,4 @@
1
- import type { DshShell } from '@dsh-workbench/shared';
1
+ import { type Seam } from '@dsh-workbench/shared/host';
2
2
  export interface ToolchainReport {
3
3
  readonly ok: boolean;
4
4
  readonly detail: string;
@@ -6,5 +6,7 @@ export interface ToolchainReport {
6
6
  readonly npmMissing: boolean;
7
7
  readonly pnpmProblem: boolean;
8
8
  readonly stalled?: boolean | undefined;
9
+ readonly diagnostics?: readonly string[] | undefined;
10
+ readonly hostBroken?: boolean | undefined;
9
11
  }
10
- export declare function probeDesktopToolchain(shell: DshShell): Promise<ToolchainReport>;
12
+ export declare function probeDesktopToolchain(seam: Seam): Promise<ToolchainReport>;
@@ -1,28 +1,49 @@
1
- import { runShell } from '@dsh-workbench/shared/host';
2
- import { quoteShellArg } from '@dsh-workbench/shared/host';
3
- import { desktopPnpmPath, desktopPnpmVersion } from './toolchain.js';
1
+ import { productWrite, quoteShellArg } from '@dsh-workbench/shared/host';
2
+ import { delimiter } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { desktopBundledBins, desktopNodeExecutable, desktopPnpmPath, desktopPnpmVersion, desktopResourcesDir, desktopRuntimeEnv, } from './desktop-runtime.js';
4
5
  import { hostSurface } from './platform.js';
5
- async function toolVersion(shell, line) {
6
+ async function toolVersion(seam, line, extra) {
6
7
  try {
7
- const result = await runShell(shell, line, { timeoutMs: 10_000 });
8
+ const result = await seam.run({
9
+ command: line,
10
+ intent: productWrite(),
11
+ retry: 'safe',
12
+ timeoutMs: 10_000,
13
+ ...(extra?.workdir === undefined ? {} : { workdir: extra.workdir }),
14
+ });
8
15
  const text = (result.stdout || result.stderr).trim().split('\n')[0] ?? '';
9
- return { ok: result.exitCode === 0 && !result.timedOut, text, stalled: result.stalled };
16
+ const fact = seam.facts().filter((entry) => entry.command === line).at(-1);
17
+ const sandbox = fact === undefined
18
+ ? '(无)'
19
+ : `mode=${fact.mode} root=${fact.workspaceRoot}${fact.denial === undefined ? '' : ` denial=${fact.denial.scope}`}`;
20
+ return {
21
+ ok: result.exitCode === 0 && !result.timedOut,
22
+ text,
23
+ stalled: result.stalled,
24
+ code: result.exitCode,
25
+ raw: `${result.stdout ?? ''}${result.stderr ?? ''}`.trim().split('\n').slice(0, 2).join(' / '),
26
+ sandbox,
27
+ };
10
28
  }
11
- catch {
12
- return { ok: false, text: '', stalled: false };
29
+ catch (error) {
30
+ return { ok: false, text: '', stalled: false, code: null, raw: String(error), sandbox: '(抛错)' };
13
31
  }
14
32
  }
15
- export async function probeDesktopToolchain(shell) {
33
+ export async function probeDesktopToolchain(seam) {
34
+ const bundledNode = desktopNodeExecutable();
16
35
  const bundledPnpm = desktopPnpmPath();
17
36
  const bundledMajor = desktopPnpmVersion()?.split('.')[0];
18
37
  const preferBundled = bundledPnpm !== undefined && Number(bundledMajor ?? '0') >= 10;
38
+ const nodeCommand = bundledNode === undefined ? 'node' : quoteShellArg(bundledNode);
19
39
  const pnpmLine = preferBundled
20
- ? `node ${quoteShellArg(bundledPnpm ?? '')} --version`
40
+ ? `${nodeCommand} ${quoteShellArg(bundledPnpm ?? '')} --version`
21
41
  : 'pnpm --version';
42
+ const path = desktopRuntimeEnv().PATH;
22
43
  const [node, npm, pnpm] = await Promise.all([
23
- toolVersion(shell, 'node --version'),
24
- toolVersion(shell, 'npm --version'),
25
- toolVersion(shell, pnpmLine),
44
+ toolVersion(seam, `${nodeCommand} --version`),
45
+ toolVersion(seam, 'npm --version'),
46
+ toolVersion(seam, pnpmLine),
26
47
  ]);
27
48
  if (node.stalled || npm.stalled || pnpm.stalled) {
28
49
  return {
@@ -56,5 +77,50 @@ export async function probeDesktopToolchain(shell) {
56
77
  ? `pnpm ${pnpm.text}(Desktop 自带)`
57
78
  : `pnpm ${pnpm.text}`,
58
79
  ];
59
- return { ok: !nodeMissing, detail: parts.join(' · '), nodeMissing, npmMissing, pnpmProblem };
80
+ const control = [];
81
+ if (nodeMissing) {
82
+ for (const [label, file] of [
83
+ ['direct bundled node', desktopNodeExecutable()],
84
+ ['direct host node', process.execPath],
85
+ ]) {
86
+ if (file === undefined) {
87
+ control.push(`control ${label}: (没有可执行文件)`);
88
+ continue;
89
+ }
90
+ try {
91
+ const out = await seam.run({
92
+ argv: [file, '--version'],
93
+ intent: { kind: 'host' },
94
+ timeoutMs: 10_000,
95
+ });
96
+ control.push(`control ${label}: exit=${String(out.exitCode)} out=${(out.stdout || out.stderr).slice(0, 80)}`);
97
+ }
98
+ catch (error) {
99
+ control.push(`control ${label}: 起不来 ${String(error).slice(0, 120)}`);
100
+ }
101
+ }
102
+ for (const [label, workdir] of [
103
+ ['default', undefined],
104
+ ['home', homedir()],
105
+ ['cwd', process.cwd()],
106
+ ]) {
107
+ const probe = await toolVersion(seam, 'echo workbench-probe', { workdir });
108
+ control.push(`control ${label}: exit=${String(probe.code)} out=${probe.raw.slice(0, 80) || '(空)'}`);
109
+ }
110
+ }
111
+ const hostBroken = control.some((line) => line.includes('exit=3221225794'));
112
+ const diagnostics = [
113
+ `deployment=${desktop ? 'desktop' : 'host'} · resources=${desktopResourcesDir() ?? '(无)'} · execPath=${process.execPath}`,
114
+ `bundled node=${bundledNode ?? '(找不到)'} · bundled pnpm=${bundledPnpm ?? '(找不到)'} · bins=${desktopBundledBins().join('|') || '(无)'}`,
115
+ `PATH 前两段=${(path ?? '').split(delimiter).slice(0, 2).join('|') || '(空)'}`,
116
+ `node: exit=${String(node.code)} sandbox=${node.sandbox} out=${node.raw.slice(0, 160) || '(空)'} cmd=${nodeCommand}`,
117
+ `pnpm: exit=${String(pnpm.code)} sandbox=${pnpm.sandbox} out=${pnpm.raw.slice(0, 160) || '(空)'} cmd=${pnpmLine}`,
118
+ `npm: exit=${String(npm.code)} sandbox=${npm.sandbox} out=${npm.raw.slice(0, 120) || '(空)'}`,
119
+ ...control,
120
+ ];
121
+ const base = parts.join(' · ');
122
+ const detail = hostBroken
123
+ ? `宿主执行通道起不了子进程(0xC0000142 · DLL 初始化失败)——不是没装 node:${base}`
124
+ : base;
125
+ return { ok: !nodeMissing, detail, nodeMissing, npmMissing, pnpmProblem, diagnostics, hostBroken };
60
126
  }
@@ -1,3 +1,7 @@
1
+ import type { Seam } from '@dsh-workbench/shared/host';
2
+ import type { PnpmProcess } from './desktop-runtime.js';
3
+ export { desktopBundledBins, desktopNodeExecutable, desktopPnpmPath, desktopPnpmVersion, desktopResourcesDir, desktopRuntimeEnv, desktopRuntimeEnvPath } from './desktop-runtime.js';
4
+ export type { PnpmProcess } from './desktop-runtime.js';
1
5
  export interface ToolchainProcess {
2
6
  readonly versions: {
3
7
  readonly electron?: string | undefined;
@@ -6,15 +10,6 @@ export interface ToolchainProcess {
6
10
  readonly execPath: string;
7
11
  readonly resourcesPath?: string | undefined;
8
12
  }
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 declare function desktopPnpmVersion(proc?: PnpmProcess, exists?: (path: string) => boolean): string | undefined;
18
13
  export interface ToolchainOptions {
19
14
  readonly root: string;
20
15
  readonly proc?: ToolchainProcess | undefined;
@@ -61,10 +56,11 @@ export interface LocalPnpmProvision {
61
56
  }
62
57
  export declare function provisionLocalPnpm(options: {
63
58
  readonly root: string;
64
- readonly run: (command: string) => Promise<{
59
+ readonly seam?: Seam | undefined;
60
+ readonly run?: ((command: string) => Promise<{
65
61
  ok: boolean;
66
62
  detail: string;
67
- }>;
63
+ }>) | undefined;
68
64
  readonly installed?: (() => boolean) | undefined;
69
65
  readonly platform?: NodeJS.Platform | undefined;
70
66
  readonly proc?: PnpmProcess | undefined;
@@ -72,14 +68,6 @@ export declare function provisionLocalPnpm(options: {
72
68
  readonly pnpm?: string | undefined;
73
69
  readonly npm?: string | undefined;
74
70
  }): Promise<LocalPnpmProvision>;
75
- export declare function provisionLocalPnpmSync(options: {
76
- readonly root: string;
77
- readonly run?: ((command: string) => void) | undefined;
78
- readonly installed?: (() => boolean) | undefined;
79
- readonly platform?: NodeJS.Platform | undefined;
80
- readonly proc?: PnpmProcess | undefined;
81
- readonly exists?: ((path: string) => boolean) | undefined;
82
- }): LocalPnpmProvision;
83
71
  export declare function desktopNodeBinary(proc?: ToolchainProcess, platform?: NodeJS.Platform, exists?: (path: string) => boolean): string | undefined;
84
72
  export declare function desktopNodeShim(proc?: ToolchainProcess, platform?: NodeJS.Platform, exists?: (path: string) => boolean): string | undefined;
85
73
  export declare function toolchainBinDir(root: string): string;
package/dist/toolchain.js CHANGED
@@ -1,37 +1,11 @@
1
- import { execaSync } from 'execa';
2
1
  import lockfile from 'proper-lockfile';
3
- import writeFileAtomic from 'write-file-atomic';
4
- import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
2
+ import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
5
3
  import { join } from 'node:path';
6
- import { quoteShellArg, quoteShellArgFor } from '@dsh-workbench/shared/host';
7
- import { bootstrapEnv } from './bootstrap-run.js';
4
+ import { quoteShellArg, quoteShellArgFor, writeTextFile } from '@dsh-workbench/shared/host';
5
+ import { bootstrapEnv, runBootstrapCommand } from './bootstrap-run.js';
8
6
  import { hostSurface } from './platform.js';
9
- export function desktopPnpmPath(proc = process, exists = existsSync) {
10
- if (hostSurface({ proc }).deployment !== 'desktop')
11
- return undefined;
12
- const resources = proc.resourcesPath;
13
- if (resources === undefined || resources === '')
14
- return undefined;
15
- const path = join(resources, 'runtime', 'primary-runtime', 'dependencies', 'pnpm', 'bin', 'pnpm.cjs');
16
- return exists(path) ? path : undefined;
17
- }
18
- export function desktopPnpmVersion(proc = process, exists = existsSync) {
19
- if (hostSurface({ proc }).deployment !== 'desktop')
20
- return undefined;
21
- const resources = proc.resourcesPath;
22
- if (resources === undefined || resources === '')
23
- return undefined;
24
- const manifest = join(resources, 'runtime', 'primary-runtime', 'dependencies', 'pnpm', 'package.json');
25
- if (!exists(manifest))
26
- return undefined;
27
- try {
28
- const parsed = JSON.parse(readFileSync(manifest, 'utf8'));
29
- return typeof parsed.version === 'string' ? parsed.version : undefined;
30
- }
31
- catch {
32
- return undefined;
33
- }
34
- }
7
+ import { desktopPnpmPath } from './desktop-runtime.js';
8
+ export { desktopBundledBins, desktopNodeExecutable, desktopPnpmPath, desktopPnpmVersion, desktopResourcesDir, desktopRuntimeEnv, desktopRuntimeEnvPath } from './desktop-runtime.js';
35
9
  export const LOCAL_NPM_VERSION = [10, 9, 4].join('.');
36
10
  export const LOCAL_PNPM_VERSION = [10, 32, 1].join('.');
37
11
  export function localNpmRoot(root) {
@@ -92,6 +66,17 @@ export function localPnpmInstallCommand(root, proc = process, exists = existsSyn
92
66
  return (`node ${quoteShellArgFor(pnpm, platform)} add --dir ${quoteShellArgFor(localPnpmRoot(root), platform)} ` +
93
67
  `pnpm@${LOCAL_PNPM_VERSION} --store-dir ${quoteShellArgFor(join(root, 'pnpm-store'), platform)}`);
94
68
  }
69
+ const LOCAL_PNPM_PROVISION_TIMEOUT_MS = 180_000;
70
+ function hostProvisionRun(root, seam) {
71
+ return async (command) => {
72
+ const result = await runBootstrapCommand(command, {
73
+ timeoutMs: LOCAL_PNPM_PROVISION_TIMEOUT_MS,
74
+ seam,
75
+ env: bootstrapEnv({ opencliRoot: join(root, 'opencli') }),
76
+ });
77
+ return { ok: result.ok, detail: result.detail };
78
+ };
79
+ }
95
80
  export async function provisionLocalPnpm(options) {
96
81
  const platform = options.platform ?? process.platform;
97
82
  const bootstrap = options.pnpm?.trim();
@@ -112,8 +97,9 @@ export async function provisionLocalPnpm(options) {
112
97
  return { ok: false, detail: `建不了 ${localPnpmRoot(options.root)}:${String(error)}` };
113
98
  }
114
99
  let detail = '';
100
+ const run = options.run ?? hostProvisionRun(options.root, options.seam);
115
101
  for (const command of commands) {
116
- const result = await options.run(command);
102
+ const result = await run(command);
117
103
  if (result.ok) {
118
104
  const installed = options.installed?.() ?? existsSync(localPnpmCommand(options.root, platform));
119
105
  if (installed) {
@@ -131,41 +117,6 @@ export async function provisionLocalPnpm(options) {
131
117
  }
132
118
  return { ok: false, detail };
133
119
  }
134
- export function provisionLocalPnpmSync(options) {
135
- const command = localPnpmInstallCommand(options.root, options.proc ?? process, options.exists ?? existsSync);
136
- if (command === undefined)
137
- return { ok: false, detail: '找不到 Desktop 自带 pnpm,装不了本地 pnpm 10' };
138
- try {
139
- mkdirSync(localPnpmRoot(options.root), { recursive: true });
140
- }
141
- catch (error) {
142
- return { ok: false, detail: `建不了 ${localPnpmRoot(options.root)}:${String(error)}` };
143
- }
144
- try {
145
- const run = options.run ??
146
- ((line) => {
147
- execaSync(line, {
148
- stdio: 'ignore',
149
- timeout: 180_000,
150
- env: bootstrapEnv({ opencliRoot: join(options.root, 'opencli') }),
151
- shell: true,
152
- });
153
- });
154
- run(command);
155
- }
156
- catch (error) {
157
- return { ok: false, detail: String(error) };
158
- }
159
- const platform = options.platform ?? process.platform;
160
- const installed = options.installed?.() ?? existsSync(localPnpmCommand(options.root, platform));
161
- if (!installed)
162
- return { ok: false, detail: 'pnpm 跑完但本地 pnpm 10 不在' };
163
- return {
164
- ok: true,
165
- binDir: localPnpmBin(options.root),
166
- detail: `pnpm@${LOCAL_PNPM_VERSION} 装到 ${localPnpmRoot(options.root)}`,
167
- };
168
- }
169
120
  function readText(file) {
170
121
  try {
171
122
  return readFileSync(file, 'utf8');
@@ -213,8 +164,7 @@ export function ensureToolchain(options) {
213
164
  release = lockfile.lockSync(pathDir, { realpath: false });
214
165
  if (readText(file) !== shim) {
215
166
  rmSync(file, { force: true });
216
- writeFileAtomic.sync(file, shim, 'utf8');
217
- chmodSync(file, 0o755);
167
+ writeTextFile(file, shim, { mode: 0o755 });
218
168
  files.push(file);
219
169
  }
220
170
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-workbench/adapter-probe",
3
- "version": "1.1.14-beta.2",
3
+ "version": "1.1.14",
4
4
  "description": "Workbench adapter-probe adapter.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -13,18 +13,15 @@
13
13
  "dist"
14
14
  ],
15
15
  "dependencies": {
16
- "execa": "^10.2.0",
17
16
  "proper-lockfile": "^4.1.2",
18
- "write-file-atomic": "^8.0.0",
19
- "@dsh-workbench/adapter-runtime": "1.1.14-beta.2",
20
- "@dsh-workbench/shared": "1.1.14-beta.2",
21
- "@dsh-workbench/adapter-profile": "1.1.14-beta.2",
22
- "@dsh-workbench/adapter-supply": "1.1.14-beta.2"
17
+ "@dsh-workbench/adapter-profile": "1.1.14",
18
+ "@dsh-workbench/shared": "1.1.14",
19
+ "@dsh-workbench/adapter-runtime": "1.1.14",
20
+ "@dsh-workbench/adapter-supply": "1.1.14"
23
21
  },
24
22
  "devDependencies": {
25
23
  "@types/node": "^24.19.0",
26
24
  "@types/proper-lockfile": "^4.1.4",
27
- "@types/write-file-atomic": "^4.0.3",
28
25
  "vitest": "^5.0.2",
29
26
  "dayjs": "^1.11.23",
30
27
  "@vitest/coverage-v8": "^5.0.2"