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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,32 @@
1
+ import type { ShellKind } from '@dsh-workbench/shared/host';
2
+ export interface BootstrapEnvOptions {
3
+ readonly opencliRoot?: string | undefined;
4
+ readonly realHome?: string | undefined;
5
+ readonly parent?: NodeJS.ProcessEnv | undefined;
6
+ readonly shellKind?: ShellKind | undefined;
7
+ }
8
+ export interface BootstrapSpawnOptions {
9
+ readonly env: Readonly<Record<string, string>>;
10
+ readonly timeout: number;
11
+ readonly reject: false;
12
+ readonly stripFinalNewline: false;
13
+ }
14
+ export interface BootstrapSpawnResult {
15
+ readonly exitCode: number | null;
16
+ readonly timedOut?: boolean | undefined;
17
+ readonly stdout: string;
18
+ readonly stderr: string;
19
+ }
20
+ export type BootstrapSpawn = (file: string, args: readonly string[], options: BootstrapSpawnOptions) => Promise<BootstrapSpawnResult>;
21
+ export interface BootstrapRunOptions {
22
+ readonly timeoutMs: number;
23
+ readonly env?: Readonly<Record<string, string>> | undefined;
24
+ readonly shellKind?: ShellKind | undefined;
25
+ readonly spawn?: BootstrapSpawn | undefined;
26
+ }
27
+ export interface BootstrapRunResult {
28
+ readonly ok: boolean;
29
+ readonly detail: string;
30
+ }
31
+ export declare function bootstrapEnv(options?: BootstrapEnvOptions): Record<string, string>;
32
+ export declare function runBootstrapCommand(line: string, options: BootstrapRunOptions): Promise<BootstrapRunResult>;
@@ -0,0 +1,76 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { execa } from 'execa';
5
+ import { isolatedRoot } from '@dsh-workbench/adapter-runtime';
6
+ import { tailLines } from '@dsh-workbench/shared/host';
7
+ import { currentShellKind } from './platform.js';
8
+ export function bootstrapEnv(options = {}) {
9
+ const shellKind = options.shellKind ?? currentShellKind();
10
+ const opencliRoot = options.opencliRoot ?? isolatedRoot();
11
+ const realHome = options.realHome ?? homedir();
12
+ const parent = options.parent ?? process.env;
13
+ const env = {};
14
+ for (const [key, value] of Object.entries(parent)) {
15
+ if (typeof value === 'string')
16
+ env[key] = value;
17
+ }
18
+ const home = join(opencliRoot, 'home');
19
+ env['HOME'] = home;
20
+ if (shellKind === 'pwsh')
21
+ env['USERPROFILE'] = home;
22
+ const userConfig = join(realHome, '.npmrc');
23
+ if (existsSync(userConfig))
24
+ env['NPM_CONFIG_USERCONFIG'] = userConfig;
25
+ const cache = join(opencliRoot, 'npm-cache');
26
+ env['npm_config_cache'] = cache;
27
+ env['NPM_CONFIG_CACHE'] = cache;
28
+ env['NO_COLOR'] = '1';
29
+ env['TERM'] = 'dumb';
30
+ return env;
31
+ }
32
+ function shellOf(line, shellKind) {
33
+ return shellKind === 'pwsh'
34
+ ? { file: 'pwsh', args: ['-NoProfile', '-NonInteractive', '-Command', line] }
35
+ : { file: 'bash', args: ['-c', line] };
36
+ }
37
+ const defaultSpawn = async (file, args, options) => {
38
+ const result = await execa(file, [...args], {
39
+ env: { ...options.env },
40
+ timeout: options.timeout,
41
+ reject: options.reject,
42
+ stripFinalNewline: options.stripFinalNewline,
43
+ });
44
+ return {
45
+ exitCode: result.exitCode ?? null,
46
+ timedOut: result.timedOut === true,
47
+ stdout: result.stdout,
48
+ stderr: result.stderr,
49
+ };
50
+ };
51
+ export async function runBootstrapCommand(line, options) {
52
+ const shellKind = options.shellKind ?? currentShellKind();
53
+ const spawn = options.spawn ?? defaultSpawn;
54
+ const env = { ...bootstrapEnv({ shellKind }), ...(options.env ?? {}) };
55
+ const command = shellOf(line, shellKind);
56
+ try {
57
+ const result = await spawn(command.file, command.args, {
58
+ env,
59
+ timeout: options.timeoutMs,
60
+ reject: false,
61
+ stripFinalNewline: false,
62
+ });
63
+ if (result.timedOut === true)
64
+ return { ok: false, detail: `超时(${String(options.timeoutMs)} ms):${line}` };
65
+ if (result.exitCode === 0)
66
+ return { ok: true, detail: tailLines(result.stdout, 1) };
67
+ const detail = tailLines(result.stderr || result.stdout, 4);
68
+ return {
69
+ ok: false,
70
+ detail: detail === '' ? `没跑通(退出码 ${String(result.exitCode)}):${line}` : detail,
71
+ };
72
+ }
73
+ catch (error) {
74
+ return { ok: false, detail: `${String(error)}:${line}` };
75
+ }
76
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './bootstrap-run.js';
1
2
  export * from './browser-wake.js';
2
3
  export * from './diy-preset-health.js';
3
4
  export * from './platform.js';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './bootstrap-run.js';
1
2
  export * from './browser-wake.js';
2
3
  export * from './diy-preset-health.js';
3
4
  export * from './platform.js';
package/dist/install.js CHANGED
@@ -1,10 +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
5
  const override = process.env.DSH_WB_PNPM?.trim();
6
6
  if (override !== undefined && override !== '')
7
7
  return isolatedInstallCommand();
8
8
  const pnpm = desktopPnpmPath(proc, exists);
9
- return pnpm === undefined ? isolatedInstallCommand() : isolatedPnpmInstallCommand(pnpm);
9
+ const major = Number(desktopPnpmVersion(proc, exists)?.split('.')[0] ?? '0');
10
+ return pnpm === undefined || (major !== 0 && major < 10)
11
+ ? isolatedInstallCommand()
12
+ : isolatedPnpmInstallCommand(pnpm);
10
13
  }
@@ -1,3 +1,5 @@
1
+ import type { ShellKind } from '@dsh-workbench/shared/host';
1
2
  import type { HostOs } from '@dsh-workbench/shared/contract/protocol/platform';
2
- export { hostOsOf, hostSurface, profileNameOf, type HostSurfaceInput } from '@dsh-workbench/shared/host';
3
+ export { hostOsOf, hostSurface, profileNameOf, shellKindOf, type HostSurfaceInput } from '@dsh-workbench/shared/host';
3
4
  export declare function currentHostOs(): HostOs;
5
+ export declare function currentShellKind(): ShellKind;
package/dist/platform.js CHANGED
@@ -1,5 +1,8 @@
1
- import { hostOsOf } from '@dsh-workbench/shared/host';
2
- export { hostOsOf, hostSurface, profileNameOf } from '@dsh-workbench/shared/host';
1
+ import { hostOsOf, shellKindOf } from '@dsh-workbench/shared/host';
2
+ export { hostOsOf, hostSurface, profileNameOf, shellKindOf } from '@dsh-workbench/shared/host';
3
3
  export function currentHostOs() {
4
4
  return hostOsOf(process.platform);
5
5
  }
6
+ export function currentShellKind() {
7
+ return shellKindOf(process.platform);
8
+ }
@@ -1,7 +1,9 @@
1
1
  import { basename, join } from 'node:path';
2
- import { INSTALL_TIMEOUT_MS, preflightHandler, runShell, tailLines, workbenchRoot } from '@dsh-workbench/shared/host';
2
+ import { PREFLIGHT_INSTALL_TIMEOUT_MS, preflightHandler, workbenchRoot } from '@dsh-workbench/shared/host';
3
3
  import { WAKE_BROWSER_ROUTE } from '@dsh-workbench/shared/contract/protocol/preflight-wake';
4
+ import { runBootstrapCommand } from './bootstrap-run.js';
4
5
  import { createDiyPresetProbe } from './diy-preset-health.js';
6
+ import { opencliInstallCommand } from './install.js';
5
7
  import { hostSurface } from './platform.js';
6
8
  import { createPreflight } from './preflight.js';
7
9
  import { probeDesktopToolchain } from './toolchain-probe.js';
@@ -23,7 +25,10 @@ function desktopBrowserOf(input) {
23
25
  }
24
26
  export function registerPreflight(input) {
25
27
  const command = input.runtimeCommands['opencli'] ?? Object.values(input.runtimeCommands)[0] ?? 'opencli';
26
- const installCommand = process.env.DSH_WORKBENCH_OPENCLI_INSTALL;
28
+ const configuredInstall = process.env.DSH_WORKBENCH_OPENCLI_INSTALL;
29
+ const installCommand = configuredInstall === undefined || configuredInstall === ''
30
+ ? opencliInstallCommand()
31
+ : configuredInstall;
27
32
  const desktopBrowser = input.desktopBrowser ??
28
33
  desktopBrowserOf({
29
34
  ctx: input.ctx,
@@ -33,7 +38,8 @@ export function registerPreflight(input) {
33
38
  const preflight = createPreflight({
34
39
  shell: input.shell,
35
40
  command,
36
- ...(installCommand === undefined || installCommand === '' ? {} : { installCommand }),
41
+ installCommand,
42
+ install: () => runBootstrapCommand(installCommand, { timeoutMs: PREFLIGHT_INSTALL_TIMEOUT_MS }),
37
43
  logger: input.ctx.logger,
38
44
  warmReads: input.warmReads,
39
45
  ...(input.runtimeCapabilities === undefined ? {} : { capabilities: input.runtimeCapabilities }),
@@ -49,13 +55,7 @@ export function registerPreflight(input) {
49
55
  root,
50
56
  pnpm: process.env.DSH_WB_PNPM?.trim() || 'pnpm',
51
57
  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
- };
58
- },
58
+ run: (command) => runBootstrapCommand(command, { timeoutMs: PREFLIGHT_INSTALL_TIMEOUT_MS }),
59
59
  });
60
60
  if (result.ok && result.binDir !== undefined) {
61
61
  prependPath(result.binDir);
package/dist/preflight.js CHANGED
@@ -1,4 +1,4 @@
1
- import { INSTALL_TIMEOUT_MS, OPENCLI_TIMEOUT_MS, PREFLIGHT_FRESH_MS, PREFLIGHT_VERSION_TIMEOUT_MS, WARM_TIMEOUT_MS, } from '@dsh-workbench/shared/host';
1
+ import { PREFLIGHT_DOCTOR_TIMEOUT_MS, PREFLIGHT_FRESH_MS, PREFLIGHT_INSTALL_TIMEOUT_MS, PREFLIGHT_VERSION_TIMEOUT_MS, WARM_TIMEOUT_MS, } from '@dsh-workbench/shared/host';
2
2
  import { tailLines } from '@dsh-workbench/shared/host';
3
3
  import { opencliCommandLine } from '@dsh-workbench/adapter-runtime';
4
4
  import { runShell } from '@dsh-workbench/shared/host';
@@ -124,7 +124,7 @@ export function createPreflight(options) {
124
124
  };
125
125
  }
126
126
  async function probeBridge() {
127
- const result = await exec('bridge', `${command} doctor`, OPENCLI_TIMEOUT_MS);
127
+ const result = await exec('bridge', `${command} doctor`, PREFLIGHT_DOCTOR_TIMEOUT_MS);
128
128
  if (result.kind === 'stalled') {
129
129
  return { ok: false, detail: result.detail, issues: ['unknown'], stalled: true };
130
130
  }
@@ -143,7 +143,7 @@ export function createPreflight(options) {
143
143
  appendLog('cli', installed.detail);
144
144
  return installed;
145
145
  }
146
- const result = await exec('cli', installCommand, INSTALL_TIMEOUT_MS);
146
+ const result = await exec('cli', installCommand, PREFLIGHT_INSTALL_TIMEOUT_MS);
147
147
  if (result.kind !== 'ok')
148
148
  return { ok: false, detail: result.detail };
149
149
  return { ok: true, detail: tailLines(result.stdout, 1) };
@@ -5,7 +5,7 @@ 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
7
  import { quoteShellArg, quoteShellArgFor } from '@dsh-workbench/shared/host';
8
- import { desktopPnpmPath, desktopPnpmVersion, ensureToolchain, localNpmCache, provisionLocalPnpmSync } from './toolchain.js';
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
  }
@@ -126,13 +126,14 @@ export function prependPath(entryDir, env = process.env) {
126
126
  }
127
127
  export function seedWorkbenchToolchain(root, log) {
128
128
  const bundledPnpm = desktopPnpmPath();
129
- const bundledVersion = desktopPnpmVersion()?.split('.')[0];
130
- if (bundledPnpm !== undefined && bundledVersion === '10') {
129
+ const bundledMajor = Number(desktopPnpmVersion()?.split('.')[0] ?? '0');
130
+ const bundledUsable = bundledPnpm !== undefined && bundledMajor >= 10;
131
+ if (bundledPnpm !== undefined && bundledUsable) {
131
132
  process.env.DSH_WB_PNPM = `node ${quoteShellArgFor(bundledPnpm, process.platform)}`;
132
133
  log(`pnpm 用 Desktop 自带的:${bundledPnpm}`);
133
134
  }
134
135
  else if (bundledPnpm !== undefined) {
135
- log(`Desktop 自带 pnpm 版本不是 10,优先用 PATH 上的 pnpm`);
136
+ log(`Desktop 自带 pnpm 版本低于 10,优先用 PATH 上的 pnpm`);
136
137
  }
137
138
  const toolchain = ensureToolchain({ root, log });
138
139
  for (const file of toolchain.files)
@@ -150,12 +151,15 @@ export function seedWorkbenchToolchain(root, log) {
150
151
  if (toolchain.pnpmDir !== undefined) {
151
152
  if (prependPath(toolchain.pnpmDir))
152
153
  log(`PATH 前插 ${toolchain.pnpmDir}`);
154
+ if (!bundledUsable)
155
+ process.env.DSH_WB_PNPM = localPnpmCommand(root);
153
156
  }
154
- else if (toolchain.pathDir !== undefined) {
157
+ else if (!bundledUsable && toolchain.pathDir !== undefined) {
155
158
  const installed = provisionLocalPnpmSync({ root });
156
159
  if (installed.ok && installed.binDir !== undefined) {
157
160
  if (prependPath(installed.binDir))
158
161
  log(`PATH 前插 ${installed.binDir}`);
162
+ process.env.DSH_WB_PNPM = localPnpmCommand(root);
159
163
  log(`工具链 provision ${installed.detail}`);
160
164
  }
161
165
  else {
@@ -15,7 +15,7 @@ async function toolVersion(shell, line) {
15
15
  export async function probeDesktopToolchain(shell) {
16
16
  const bundledPnpm = desktopPnpmPath();
17
17
  const bundledMajor = desktopPnpmVersion()?.split('.')[0];
18
- const preferBundled = bundledPnpm !== undefined && bundledMajor === '10';
18
+ const preferBundled = bundledPnpm !== undefined && Number(bundledMajor ?? '0') >= 10;
19
19
  const pnpmLine = preferBundled
20
20
  ? `node ${quoteShellArg(bundledPnpm ?? '')} --version`
21
21
  : 'pnpm --version';
@@ -36,8 +36,8 @@ export async function probeDesktopToolchain(shell) {
36
36
  }
37
37
  const nodeMissing = !node.ok;
38
38
  const npmMissing = !npm.ok;
39
- const pnpmMajor = /^v?(\d+)/.exec(pnpm.text)?.[1];
40
- const pnpmProblem = !pnpm.ok || pnpmMajor !== '10';
39
+ const pnpmMajor = Number(/^v?(\d+)/.exec(pnpm.text)?.[1] ?? '0');
40
+ const pnpmProblem = !pnpm.ok || pnpmMajor < 10;
41
41
  const desktop = hostSurface({ proc: process }).deployment === 'desktop';
42
42
  const parts = [
43
43
  nodeMissing
package/dist/toolchain.js CHANGED
@@ -4,6 +4,7 @@ import writeFileAtomic from 'write-file-atomic';
4
4
  import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
5
5
  import { join } from 'node:path';
6
6
  import { quoteShellArg, quoteShellArgFor } from '@dsh-workbench/shared/host';
7
+ import { bootstrapEnv } from './bootstrap-run.js';
7
8
  import { hostSurface } from './platform.js';
8
9
  export function desktopPnpmPath(proc = process, exists = existsSync) {
9
10
  if (hostSurface({ proc }).deployment !== 'desktop')
@@ -143,7 +144,12 @@ export function provisionLocalPnpmSync(options) {
143
144
  try {
144
145
  const run = options.run ??
145
146
  ((line) => {
146
- execaSync(line, { stdio: 'ignore', timeout: 180_000, env: process.env, shell: true });
147
+ execaSync(line, {
148
+ stdio: 'ignore',
149
+ timeout: 180_000,
150
+ env: bootstrapEnv({ opencliRoot: join(options.root, 'opencli') }),
151
+ shell: true,
152
+ });
147
153
  });
148
154
  run(command);
149
155
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-workbench/adapter-probe",
3
- "version": "1.1.14-beta.0",
3
+ "version": "1.1.14-beta.2",
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/adapter-profile": "1.1.14-beta.0",
20
- "@dsh-workbench/adapter-runtime": "1.1.14-beta.0",
21
- "@dsh-workbench/adapter-supply": "1.1.14-beta.0",
22
- "@dsh-workbench/shared": "1.1.14-beta.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"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^24.19.0",