@mearl/setup 2.20.6 → 2.22.0

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/README.md CHANGED
@@ -121,6 +121,12 @@ npx @mearl/setup provider uninstall brow
121
121
  Ali Skills 安装的插件会同时移除 Skill;手工注册的外部插件只清除注册、保留源码;Provider
122
122
  只有在包管理器成功卸载 npm 包后才清除注册。单项失败不阻止其他项继续清理,命令最终返回非零。
123
123
  该选项不删除 Chrome 扩展;`extension --full` 仍表示安装扩展及完整本地环境。
124
+ - `uninstall --full` 在所有环境、插件与 Provider 成功移除后统一收尾:停止插件执行服务,
125
+ 确认没有活跃或退出状态未明的任务,再删除任务历史、源码快照、服务日志、AuthX SDK 缓存、
126
+ 旧设计稿缓存和已结束宿主的页面会话文件。只卸载部分环境、单项失败或服务重新启动时保留共享缓存。
127
+ 即使包和注册已移除,也可以再次运行该命令完成缓存收尾。自定义 `MEARL_PLUGIN_DIR` 只清理该注册表的执行缓存。
128
+ 记事本、迭代记录、配置与身份、插件持久状态、Provider 私有数据、浏览器配置及用户导出的设计稿均保留;
129
+ CDP 标签 ID 计数也保留,防止旧请求命中新标签。
124
130
  - `--mcp` 的客户端配置和清理由 `@mearl/mcp-server` 自己的命令完成。
125
131
  - 默认的 `install` / `update` 不处理浏览器扩展。`extension` 是独立命令:从
126
132
  `https://mearl.alibaba-inc.com/` 识别并下载最新的 `mearl-chrome-v*.zip`,原子解压到
@@ -1,5 +1,5 @@
1
1
  import fs from 'node:fs';
2
- import { type CommandRunner } from './process.js';
2
+ import { type CommandRunner } from '@mearl/daemon-core';
3
3
  export interface AliSkillsCliOutput {
4
4
  stdout: string;
5
5
  stderr: string;
package/dist/aliSkills.js CHANGED
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { spawn } from 'node:child_process';
5
- import { executableNames, findExecutable, isExecutable, prependCommandPath, prependNodePath, resolveSpawnCommand, runCommand, versionManagerExecutableCandidates, } from './process.js';
5
+ import { executableNames, findExecutable, isExecutable, prependCommandPath, prependNodePath, resolveSpawnCommand, runCommand, versionManagerExecutableCandidates, } from '@mearl/daemon-core';
6
6
  const COMMAND_TIMEOUT_MS = 120_000;
7
7
  const MAX_OUTPUT_LENGTH = 1_000_000;
8
8
  const ANSI_ESCAPE_PATTERN = new RegExp(String.raw `\u001B\[[0-?]*[ -/]*[@-~]`, 'g');
@@ -1,7 +1,7 @@
1
1
  import { findPluginRegistration, listPluginNames, unregisterPlugin } from '@mearl/client/plugin';
2
2
  import { resolveAliSkillsInvocation } from './aliSkills.js';
3
3
  import { installPlugins } from './plugin.js';
4
- import { findExecutable, type CommandRunner } from './process.js';
4
+ import { findExecutable, type CommandRunner } from '@mearl/daemon-core';
5
5
  export { ALIBABA_NPM_REGISTRY } from './npmRegistry.js';
6
6
  export declare const ALIBABA_INTERNAL_PLUGIN_NAMES: string[];
7
7
  export interface InternalPluginOptions {
@@ -3,7 +3,7 @@ import { findOfficialPlugin, findPluginRegistration, listPluginNames, listOffici
3
3
  import { resolveAliSkillsInvocation } from './aliSkills.js';
4
4
  import { ALIBABA_NPM_REGISTRY } from './npmRegistry.js';
5
5
  import { installPlugins } from './plugin.js';
6
- import { findExecutable, prependCommandPath, runCommand } from './process.js';
6
+ import { findExecutable, prependCommandPath, runCommand, } from '@mearl/daemon-core';
7
7
  export { ALIBABA_NPM_REGISTRY } from './npmRegistry.js';
8
8
  export const ALIBABA_INTERNAL_PLUGIN_NAMES = listOfficialPluginNames('alibaba');
9
9
  export async function canAccessAlibabaNpmRegistry(options = {}, dependencies = {}) {
@@ -4,10 +4,11 @@ import { runGlobalMearlSkillOperation } from './aliSkills.js';
4
4
  import { detectGlobalPackage, type PackageManagerInstallation, type PackageDetectionOptions } from './packageManager.js';
5
5
  import { type DetectedMearlProfiles, type MearlProfile } from './profiles.js';
6
6
  import { uninstallNativeHostArtifacts } from './nativeHost.js';
7
- import { type CommandRunner } from './process.js';
7
+ import { type CommandRunner } from '@mearl/daemon-core';
8
8
  import { installAlibabaInternalPlugins, uninstallAlibabaInternalPlugins } from './internalPlugins.js';
9
9
  import { uninstallPlugin } from './plugin.js';
10
10
  import { uninstallProviderPackage } from './provider.js';
11
+ import { cleanupUninstalledRuntime } from './runtimeCleanup.js';
11
12
  export type MearlLifecycleOperation = 'install' | 'update' | 'uninstall';
12
13
  export interface MearlLifecycleOptions {
13
14
  operation: MearlLifecycleOperation;
@@ -23,6 +24,7 @@ export interface MearlLifecycleOptions {
23
24
  env?: NodeJS.ProcessEnv;
24
25
  }
25
26
  export interface MearlLifecycleDependencies {
27
+ cleanupRuntime?: typeof cleanupUninstalledRuntime;
26
28
  cleanupNativeHost?: typeof uninstallNativeHostArtifacts;
27
29
  confirm?: (message: string) => Promise<boolean>;
28
30
  detectPackage?: typeof detectGlobalPackage;
package/dist/lifecycle.js CHANGED
@@ -8,12 +8,13 @@ import { runGlobalMearlSkillOperation } from './aliSkills.js';
8
8
  import { buildGlobalPackageCommand, detectGlobalPackage, resolveInvokingPackageManager, resolvePackageManagerInstallation, } from './packageManager.js';
9
9
  import { MEARL_PROFILES, detectMearlProfiles, } from './profiles.js';
10
10
  import { uninstallNativeHostArtifacts } from './nativeHost.js';
11
- import { executableNames, prependCommandPath, runCommand, } from './process.js';
11
+ import { executableNames, prependCommandPath, runCommand, } from '@mearl/daemon-core';
12
12
  import { isMearlReleaseLine } from './version.js';
13
13
  import { installAlibabaInternalPlugins, uninstallAlibabaInternalPlugins, } from './internalPlugins.js';
14
14
  import { uninstallPlugin } from './plugin.js';
15
15
  import { uninstallProviderPackage } from './provider.js';
16
16
  import { packageRegistryEnvironment } from './npmRegistry.js';
17
+ import { cleanupUninstalledRuntime } from './runtimeCleanup.js';
17
18
  const LEGACY_CLOUD_CLIENT = '@mearl/cloud-client';
18
19
  const UNIFIED_CLIENT = '@mearl/client';
19
20
  const PROFILE_ORDER = ['local', 'cloud', 'mcp'];
@@ -227,17 +228,9 @@ export async function runMearlLifecycle(options, dependencies = {}) {
227
228
  return false;
228
229
  }
229
230
  }
230
- const hasFullIntegrationTargets = fullPluginNames.length > 0 || fullProviders.length > 0;
231
- if (options.operation === 'uninstall' &&
232
- options.full &&
233
- lifecycleSelected.length === 0 &&
234
- !hasFullIntegrationTargets) {
235
- writeLine('No installed Mearl environments, CLI plugins, or Providers were detected.');
236
- return true;
237
- }
238
231
  if (selected.length === 0 &&
239
232
  !autoUpdateClient &&
240
- !(options.operation === 'uninstall' && options.full && hasFullIntegrationTargets)) {
233
+ !(options.operation === 'uninstall' && options.full)) {
241
234
  writeLine('No installed Mearl environment was detected on the current PATH.');
242
235
  if (options.operation === 'uninstall')
243
236
  return true;
@@ -488,6 +481,18 @@ export async function runMearlLifecycle(options, dependencies = {}) {
488
481
  }
489
482
  }
490
483
  if (packagesOk && skillOk && fullOk) {
484
+ if (options.operation === 'uninstall' &&
485
+ options.full &&
486
+ PROFILE_ORDER.every(profile => !detected.has(profile) || lifecycleSelected.includes(profile))) {
487
+ try {
488
+ await (dependencies.cleanupRuntime ?? cleanupUninstalledRuntime)();
489
+ writeLine('Runtime caches: cleaned; user data and browser profiles were retained.');
490
+ }
491
+ catch (error) {
492
+ writeLine(`Runtime caches: ${error instanceof Error ? error.message : String(error)}`);
493
+ return false;
494
+ }
495
+ }
491
496
  writeLine('Mearl setup complete.');
492
497
  return true;
493
498
  }
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { executableNames, findExecutable, isExecutable } from './process.js';
3
+ import { executableNames, findExecutable, isExecutable } from '@mearl/daemon-core';
4
4
  function normalizePath(filePath) {
5
5
  return filePath.replaceAll('\\', '/').replace(/\/+$/, '');
6
6
  }
package/dist/plugin.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type CommandRunner } from './process.js';
1
+ import { type CommandRunner } from '@mearl/daemon-core';
2
2
  export interface PluginSetupResult {
3
3
  entry: string;
4
4
  name: string;
package/dist/plugin.js CHANGED
@@ -4,7 +4,7 @@ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'nod
4
4
  import { assertPluginName, findPluginDirectoryEntry, findOfficialPlugin, findPluginEntry, findPluginRegistration, listPluginNames, loadPlugin, OFFICIAL_PLUGIN_MANAGER, registerPlugin, resolvePluginEntry, unregisterPlugin, } from '@mearl/client/plugin';
5
5
  import { ensurePluginRuntime } from './pluginRuntime.js';
6
6
  import { resolveAliSkillsInvocation, stripAnsi } from './aliSkills.js';
7
- import { findExecutable, prependNodePath, runCommand } from './process.js';
7
+ import { findExecutable, prependNodePath, runCommand, } from '@mearl/daemon-core';
8
8
  const registrationContext = {
9
9
  options: {},
10
10
  invoke: async () => {
@@ -38,6 +38,7 @@ export async function setupPlugin(target, requestedName) {
38
38
  await ensurePluginRuntime();
39
39
  return {
40
40
  entry: registerPlugin(name, entry, plugin.description, {
41
+ hasPublicCommands: Boolean(plugin.commandHelp(name)),
41
42
  toolbox: plugin.toolbox,
42
43
  appProfiles: plugin.appProfiles,
43
44
  profileSources: plugin.profileSources,
@@ -266,6 +267,7 @@ export async function installPlugins(args, runner = runCommand, options = {}) {
266
267
  throw new Error(`多个 skill 使用插件名称 ${name},请调整入口的 name metadata。`);
267
268
  plugins.set(name, {
268
269
  entry,
270
+ hasPublicCommands: Boolean(plugin.commandHelp(name)),
269
271
  description: plugin.description,
270
272
  ...(plugin.toolbox ? { toolbox: plugin.toolbox } : {}),
271
273
  appProfiles: plugin.appProfiles,
@@ -277,14 +279,15 @@ export async function installPlugins(args, runner = runCommand, options = {}) {
277
279
  return 0;
278
280
  }
279
281
  await ensurePluginRuntime(runner);
280
- for (const [name, { entry, description, toolbox, appProfiles, profileSources }] of plugins) {
282
+ for (const [name, { entry, description, toolbox, appProfiles, profileSources, hasPublicCommands },] of plugins) {
281
283
  registerPlugin(name, entry, description, {
284
+ hasPublicCommands,
282
285
  managedBy: options.registrationManagedBy,
283
286
  ...(toolbox ? { toolbox } : {}),
284
287
  appProfiles,
285
288
  profileSources,
286
289
  });
287
- console.error(`Plugin ${name} registered: ${entry}\nRun: mearl ${name} --help`);
290
+ console.error(`Plugin ${name} registered: ${entry}${hasPublicCommands ? `\nRun: mearl ${name} --help` : toolbox ? '\nOpen in Mearl toolbox.' : ''}`);
288
291
  }
289
292
  return 0;
290
293
  }
@@ -1,4 +1,4 @@
1
- import { type CommandRunner } from './process.js';
1
+ import { type CommandRunner } from '@mearl/daemon-core';
2
2
  export declare const PLUGIN_RUNTIME_VERSION: string;
3
3
  /** 插件使用当前 setup 同版本的运行时;已安装的更高版本保持不动。 */
4
4
  export declare function ensurePluginRuntime(runner?: CommandRunner): Promise<void>;
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'node:fs';
2
2
  import { createRequire } from 'node:module';
3
3
  import { join } from 'node:path';
4
4
  import { buildGlobalPackageCommand, detectCliPackage, detectGlobalPackage, resolveInvokingPackageManager, } from './packageManager.js';
5
- import { prependCommandPath, runCommand } from './process.js';
5
+ import { prependCommandPath, runCommand } from '@mearl/daemon-core';
6
6
  import { compareMearlReleaseVersions, isMearlReleaseVersion } from './version.js';
7
7
  import { packageRegistryEnvironment } from './npmRegistry.js';
8
8
  const require = createRequire(import.meta.url);
@@ -1,6 +1,6 @@
1
- import { listInstalledProviders, registerProviderPackage, stopProvider, uninstallProvider, type InstalledProviderManifest } from '@mearl/provider/host';
1
+ import { listInstalledProviders, registerProviderPackage, uninstallProvider, type InstalledProviderManifest } from '@mearl/provider/host';
2
2
  import { resolvePackageManagerInstallation, type PackageManagerInstallation } from './packageManager.js';
3
- import { type CommandRunner } from './process.js';
3
+ import { type CommandRunner } from '@mearl/daemon-core';
4
4
  interface ProviderPackageSpec {
5
5
  packageName: string;
6
6
  target: string;
@@ -13,7 +13,7 @@ export interface ProviderSetupDependencies {
13
13
  resolvePackageManager?: typeof resolvePackageManagerInstallation;
14
14
  resolvePackageRoot?: (installation: PackageManagerInstallation, packageName: string, runner: CommandRunner, env: NodeJS.ProcessEnv) => Promise<string>;
15
15
  runCommand?: CommandRunner;
16
- stopProvider?: typeof stopProvider;
16
+ stopProvider?: (providerId: string) => void | boolean | Promise<void | boolean>;
17
17
  unregisterProvider?: typeof uninstallProvider;
18
18
  }
19
19
  export interface ProviderSetupResult {
package/dist/provider.js CHANGED
@@ -1,9 +1,26 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { join, resolve } from 'node:path';
3
- import { listInstalledProviders, registerProviderPackage, stopProvider, uninstallProvider, } from '@mearl/provider/host';
3
+ import { listInstalledProviders, registerProviderPackage, stopProvider, readLiveProvider, readProviderStart, uninstallProvider, } from '@mearl/provider/host';
4
4
  import { buildGlobalPackageCommand, resolveInvokingPackageManager, resolvePackageManagerInstallation, } from './packageManager.js';
5
5
  import { packageRegistryEnvironment } from './npmRegistry.js';
6
- import { prependCommandPath, runCommand } from './process.js';
6
+ import { isProcessOwnerAlive, processIdentity, prependCommandPath, runCommand, } from '@mearl/daemon-core';
7
+ async function stopProviderBeforePackageChange(providerId) {
8
+ if (readProviderStart(providerId))
9
+ throw new Error(`Provider ${providerId} is still starting`);
10
+ const live = readLiveProvider(providerId);
11
+ if (!live)
12
+ return;
13
+ const owner = { pid: live.pid, processIdentity: processIdentity(live.pid) };
14
+ stopProvider(providerId);
15
+ const deadline = Date.now() + 5000;
16
+ while (isProcessOwnerAlive(owner)) {
17
+ if (Date.now() >= deadline)
18
+ throw new Error(`Provider ${providerId} did not finish stopping`);
19
+ await new Promise(resolve => setTimeout(resolve, 50));
20
+ }
21
+ if (readLiveProvider(providerId) || readProviderStart(providerId))
22
+ throw new Error(`Provider ${providerId} restarted during shutdown`);
23
+ }
7
24
  export function parseProviderPackageSpec(value) {
8
25
  const input = value.trim();
9
26
  const nameMatch = input.match(/^(@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*|[a-z0-9][a-z0-9._-]*)(?:@(.+))?$/i);
@@ -94,7 +111,7 @@ export async function updateProviderPackage(selector, dependencies = {}) {
94
111
  const runner = dependencies.runCommand ?? runCommand;
95
112
  const detectedManager = (dependencies.resolvePackageManager ?? resolvePackageManagerInstallation)(current.packageRoot, current.packageName) ?? undefined;
96
113
  const installation = providerManager(env, dependencies, detectedManager);
97
- (dependencies.stopProvider ?? stopProvider)(current.providerId);
114
+ await (dependencies.stopProvider ?? stopProviderBeforePackageChange)(current.providerId);
98
115
  await runPackageCommand(installation, 'update', current.packageName, 'latest', runner, env);
99
116
  const manifest = (dependencies.registerPackage ?? registerProviderPackage)(current.packageRoot);
100
117
  return { manifest, manager: installation.manager };
@@ -104,7 +121,7 @@ export async function uninstallProviderPackage(selector, dependencies = {}) {
104
121
  const manifest = findRegisteredProvider(selector, listProviders);
105
122
  const env = process.env;
106
123
  const installation = (dependencies.resolvePackageManager ?? resolvePackageManagerInstallation)(manifest.packageRoot, manifest.packageName) ?? providerManager(env, dependencies);
107
- (dependencies.stopProvider ?? stopProvider)(manifest.providerId);
124
+ await (dependencies.stopProvider ?? stopProviderBeforePackageChange)(manifest.providerId);
108
125
  await runPackageCommand(installation, 'uninstall', manifest.packageName, 'latest', dependencies.runCommand ?? runCommand, env);
109
126
  (dependencies.unregisterProvider ?? uninstallProvider)(manifest.providerId);
110
127
  return { manifest, manager: installation.manager };
@@ -0,0 +1,2 @@
1
+ /** User state, browser profiles and CDP tab-ID counters survive uninstall. */
2
+ export declare function cleanupUninstalledRuntime(): Promise<void>;
@@ -0,0 +1,50 @@
1
+ import { readdirSync, rmSync } from 'node:fs';
2
+ import { basename, join, resolve } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { cleanupPluginRuntime, pluginDirectory } from '@mearl/client/plugin';
5
+ import { isProcessOwnerAlive, listRecordFiles, pruneOrphanLogs, readRecord, removeRecordIfPid, } from '@mearl/daemon-core';
6
+ /** User state, browser profiles and CDP tab-ID counters survive uninstall. */
7
+ export async function cleanupUninstalledRuntime() {
8
+ await cleanupPluginRuntime();
9
+ const root = join(homedir(), '.mearl');
10
+ if (resolve(pluginDirectory()) !== resolve(join(root, 'plugins')))
11
+ return;
12
+ if (listRecordFiles(join(root, 'providers/installed')).length)
13
+ throw new Error('Registered Providers still use runtime caches; caches were retained');
14
+ for (const directory of ['providers/live', 'providers/starting']) {
15
+ if (listRecordFiles(join(root, directory)).some(file => {
16
+ const record = readRecord(file);
17
+ return record && isProcessOwnerAlive(record);
18
+ }))
19
+ throw new Error('Providers still use runtime caches; caches were retained');
20
+ }
21
+ for (const directory of [
22
+ 'authx-sdk',
23
+ 'design-overlay/objects',
24
+ 'design-overlay/uploads',
25
+ 'design-overlay/pages',
26
+ ])
27
+ rmSync(join(root, directory), { recursive: true, force: true });
28
+ pruneOrphanLogs(join(root, 'managed-browsers'), 0);
29
+ pruneOrphanLogs(join(root, 'providers/logs'), 0);
30
+ let entries;
31
+ try {
32
+ entries = readdirSync(join(root, 'plugin-pages'), { withFileTypes: true });
33
+ }
34
+ catch (error) {
35
+ if (error.code === 'ENOENT')
36
+ return;
37
+ throw error;
38
+ }
39
+ for (const entry of entries) {
40
+ if (!entry.isDirectory() || !/^[a-f0-9]{16}\.json\.d$/.test(entry.name))
41
+ continue;
42
+ for (const file of listRecordFiles(join(root, 'plugin-pages', entry.name))) {
43
+ if (!/^session-[a-f0-9]{16}\.json$/.test(basename(file)))
44
+ continue;
45
+ const record = readRecord(file);
46
+ if (record && !isProcessOwnerAlive(record))
47
+ removeRecordIfPid(file, record.pid);
48
+ }
49
+ }
50
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/setup",
3
- "version": "2.20.6",
3
+ "version": "2.22.0",
4
4
  "description": "One-shot environment setup and configuration for Mearl",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -18,10 +18,6 @@
18
18
  "types": "./dist/aliSkills.d.ts",
19
19
  "import": "./dist/aliSkills.js"
20
20
  },
21
- "./process": {
22
- "types": "./dist/process.d.ts",
23
- "import": "./dist/process.js"
24
- },
25
21
  "./package-manager": {
26
22
  "types": "./dist/packageManager.d.ts",
27
23
  "import": "./dist/packageManager.js"
@@ -69,8 +65,9 @@
69
65
  "commander": "^15.0.0",
70
66
  "extract-zip": "^2.0.1",
71
67
  "ws": "^8.18.3",
72
- "@mearl/client": "2.20.6",
73
- "@mearl/provider": "2.20.6"
68
+ "@mearl/client": "2.22.0",
69
+ "@mearl/provider": "2.22.0",
70
+ "@mearl/daemon-core": "2.22.0"
74
71
  },
75
72
  "devDependencies": {
76
73
  "@types/node": "^24.9.1",
package/dist/process.d.ts DELETED
@@ -1,38 +0,0 @@
1
- import fs from 'node:fs';
2
- export interface SpawnCommand {
3
- command: string;
4
- args: string[];
5
- }
6
- export interface CommandResult {
7
- code: number;
8
- stdout: string;
9
- stderr: string;
10
- }
11
- export interface RunCommandOptions {
12
- capture?: boolean;
13
- /** 显示并捕获输出,同时继承 stdin 以保留安装器交互。 */
14
- tee?: boolean;
15
- cwd?: string;
16
- env?: NodeJS.ProcessEnv;
17
- timeoutMs?: number;
18
- }
19
- export type CommandRunner = (command: string, args: string[], options?: RunCommandOptions) => Promise<CommandResult>;
20
- export declare function isExecutable(filePath: string, platform?: NodeJS.Platform): boolean;
21
- export declare function executableNames(name: string, platform?: NodeJS.Platform): string[];
22
- export interface VersionManagerExecutableCandidateOptions {
23
- env?: NodeJS.ProcessEnv;
24
- platform?: NodeJS.Platform;
25
- readdirSync?: typeof fs.readdirSync;
26
- }
27
- export declare function versionManagerExecutableCandidates(home: string, names: string[], options?: VersionManagerExecutableCandidateOptions): string[];
28
- export declare function resolveSpawnCommand(executable: string, args: string[], platform?: NodeJS.Platform, commandInterpreter?: string | undefined): SpawnCommand;
29
- export declare function prependCommandPath(command: string, env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
30
- export declare function prependNodePath(env?: NodeJS.ProcessEnv, nodeExecutable?: string): NodeJS.ProcessEnv;
31
- export declare function prependPathDirectory(directory: string, env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
32
- export declare function findExecutable(name: string, options?: {
33
- env?: NodeJS.ProcessEnv;
34
- existsSync?: (filePath: string) => boolean;
35
- platform?: NodeJS.Platform;
36
- extraDirectories?: string[];
37
- }): string | null;
38
- export declare const runCommand: CommandRunner;
package/dist/process.js DELETED
@@ -1,139 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { spawn } from 'node:child_process';
4
- const MAX_CAPTURE_LENGTH = 1_000_000;
5
- export function isExecutable(filePath, platform = process.platform) {
6
- try {
7
- fs.accessSync(filePath, platform === 'win32' ? fs.constants.F_OK : fs.constants.X_OK);
8
- return true;
9
- }
10
- catch {
11
- return false;
12
- }
13
- }
14
- export function executableNames(name, platform = process.platform) {
15
- return platform === 'win32' ? [`${name}.cmd`, `${name}.exe`, name] : [name];
16
- }
17
- export function versionManagerExecutableCandidates(home, names, options = {}) {
18
- const env = options.env ?? process.env;
19
- const platform = options.platform ?? process.platform;
20
- const readdirSync = options.readdirSync ?? fs.readdirSync;
21
- const fnmDefaultRoot = env.XDG_DATA_HOME
22
- ? path.join(env.XDG_DATA_HOME, 'fnm')
23
- : platform === 'darwin'
24
- ? path.join(home, 'Library', 'Application Support', 'fnm')
25
- : platform === 'win32' && env.APPDATA
26
- ? path.join(env.APPDATA, 'fnm')
27
- : path.join(home, '.local', 'share', 'fnm');
28
- const roots = [
29
- path.join(env.NVM_DIR || path.join(home, '.nvm'), 'versions', 'node'),
30
- path.join(env.FNM_DIR || fnmDefaultRoot, 'node-versions'),
31
- path.join(home, '.fnm', 'node-versions'),
32
- ].filter((root, index, all) => all.indexOf(root) === index);
33
- return roots.flatMap(root => {
34
- try {
35
- return readdirSync(root, { withFileTypes: true })
36
- .filter(entry => entry.isDirectory())
37
- .sort((left, right) => right.name.localeCompare(left.name, undefined, { numeric: true }))
38
- .flatMap(entry => names.flatMap(name => [
39
- path.join(root, entry.name, 'bin', name),
40
- path.join(root, entry.name, 'installation', 'bin', name),
41
- ]));
42
- }
43
- catch {
44
- return [];
45
- }
46
- });
47
- }
48
- export function resolveSpawnCommand(executable, args, platform = process.platform, commandInterpreter = process.env.ComSpec) {
49
- if (platform === 'win32' && /\.(?:cmd|bat)$/i.test(executable)) {
50
- return {
51
- command: commandInterpreter || 'cmd.exe',
52
- args: ['/d', '/s', '/c', executable, ...args],
53
- };
54
- }
55
- return { command: executable, args };
56
- }
57
- export function prependCommandPath(command, env = process.env) {
58
- if (!path.isAbsolute(command))
59
- return env;
60
- return prependPathDirectory(path.dirname(command), env);
61
- }
62
- export function prependNodePath(env = process.env, nodeExecutable = process.execPath) {
63
- return prependPathDirectory(path.dirname(nodeExecutable), env);
64
- }
65
- export function prependPathDirectory(directory, env = process.env) {
66
- const pathKey = Object.keys(env).find(key => key.toLowerCase() === 'path') || 'PATH';
67
- const inherited = env[pathKey] || '';
68
- const entries = inherited.split(path.delimiter).filter(Boolean);
69
- if (entries.includes(directory))
70
- return env;
71
- return { ...env, [pathKey]: [directory, ...entries].join(path.delimiter) };
72
- }
73
- export function findExecutable(name, options = {}) {
74
- const env = options.env ?? process.env;
75
- const platform = options.platform ?? process.platform;
76
- const existsSync = options.existsSync ?? (candidate => isExecutable(candidate, platform));
77
- const pathKey = Object.keys(env).find(key => key.toLowerCase() === 'path') || 'PATH';
78
- const directories = [
79
- ...(options.extraDirectories ?? []),
80
- ...(env[pathKey] ?? '').split(path.delimiter).filter(Boolean),
81
- ];
82
- for (const directory of directories) {
83
- for (const executableName of executableNames(name, platform)) {
84
- const candidate = path.join(directory, executableName);
85
- if (existsSync(candidate))
86
- return candidate;
87
- }
88
- }
89
- return null;
90
- }
91
- export const runCommand = (command, args, options = {}) => new Promise((resolve, reject) => {
92
- const capture = options.capture === true || options.tee === true;
93
- const launch = resolveSpawnCommand(command, args);
94
- const child = spawn(launch.command, launch.args, {
95
- cwd: options.cwd,
96
- env: options.env,
97
- stdio: capture ? [options.tee ? 'inherit' : 'ignore', 'pipe', 'pipe'] : 'inherit',
98
- windowsHide: true,
99
- });
100
- let stdout = '';
101
- let stderr = '';
102
- let settled = false;
103
- const append = (current, chunk) => options.tee
104
- ? (current + chunk).slice(-MAX_CAPTURE_LENGTH)
105
- : current.length >= MAX_CAPTURE_LENGTH
106
- ? current
107
- : (current + chunk).slice(0, MAX_CAPTURE_LENGTH);
108
- const finish = (error, code = 1) => {
109
- if (settled)
110
- return;
111
- settled = true;
112
- if (timer)
113
- clearTimeout(timer);
114
- if (error)
115
- reject(error);
116
- else
117
- resolve({ code, stdout, stderr });
118
- };
119
- const timer = options.timeoutMs === undefined
120
- ? undefined
121
- : setTimeout(() => {
122
- child.kill('SIGTERM');
123
- finish(new Error(`Command timed out after ${options.timeoutMs}ms`));
124
- }, options.timeoutMs);
125
- if (capture) {
126
- child.stdout?.setEncoding('utf8').on('data', (chunk) => {
127
- stdout = append(stdout, chunk);
128
- if (options.tee)
129
- process.stdout.write(chunk);
130
- });
131
- child.stderr?.setEncoding('utf8').on('data', (chunk) => {
132
- stderr = append(stderr, chunk);
133
- if (options.tee)
134
- process.stderr.write(chunk);
135
- });
136
- }
137
- child.once('error', error => finish(error));
138
- child.once('close', code => finish(undefined, code ?? 1));
139
- });