@mearl/setup 2.2.2 → 2.4.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 +12 -0
- package/dist/aliSkills.js +6 -22
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +29 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/lifecycle.d.ts +2 -0
- package/dist/lifecycle.js +6 -2
- package/dist/packageManager.d.ts +6 -2
- package/dist/packageManager.js +45 -10
- package/dist/process.d.ts +8 -0
- package/dist/process.js +34 -3
- package/dist/version.d.ts +21 -0
- package/dist/version.js +77 -0
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -18,6 +18,18 @@ npx @mearl/setup <install|update|uninstall> [--local|--cloud|--mcp] [--yes]
|
|
|
18
18
|
- 卸载一个环境但另一个仍依赖全局 mearl skill 时会保留 skill。
|
|
19
19
|
- `--mcp` 的客户端配置和清理由 `@mearl/mcp-server` 自己的命令完成。
|
|
20
20
|
|
|
21
|
+
## 跟随浏览器扩展自动更新
|
|
22
|
+
|
|
23
|
+
浏览器扩展与 Native Host 首次建联时会交换版本。每次扩展后台启动只检查一次;当扩展
|
|
24
|
+
进入更高的 `major.minor` release line 时,旧 Host 会在保持当前连接可用的前提下,调用
|
|
25
|
+
对应 `major.minor.x` 范围内最新的 `@mearl/setup`,把 `@mearl/native-host` 与
|
|
26
|
+
`@mearl/client` 一起更新到该范围内的最新版本。成功后 Host 退出,扩展通过已有重连机制
|
|
27
|
+
启动新版本。只有两个本地 CLI 版本一致且都属于扩展的 release line 时,更新才会被视为成功。
|
|
28
|
+
|
|
29
|
+
自动更新不会降级较新的 Host,也不会改动 cloud、MCP 或独立安装的 Mearl Skill。
|
|
30
|
+
目标 npm 版本尚未发布、网络不可用或更新失败时,旧 Host 会继续运行;下一次扩展后台
|
|
31
|
+
生命周期可重新尝试,也可手动执行 `npx @mearl/setup update --local`。
|
|
32
|
+
|
|
21
33
|
## 人工与 Agent 使用
|
|
22
34
|
|
|
23
35
|
- 人工在终端执行时不加 `--yes`,需要选择 Skill 安装目标或 MCP 客户端时会显示交互提示。
|
package/dist/aliSkills.js
CHANGED
|
@@ -2,33 +2,13 @@ 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, } from './process.js';
|
|
5
|
+
import { executableNames, findExecutable, isExecutable, prependCommandPath, prependNodePath, resolveSpawnCommand, runCommand, versionManagerExecutableCandidates, } from './process.js';
|
|
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');
|
|
9
9
|
function existingExecutable(candidates, existsSync) {
|
|
10
10
|
return candidates.find(candidate => candidate && existsSync(candidate)) ?? null;
|
|
11
11
|
}
|
|
12
|
-
function versionManagerCandidates(home, executableNames, readdirSync) {
|
|
13
|
-
const roots = [
|
|
14
|
-
path.join(home, '.nvm', 'versions', 'node'),
|
|
15
|
-
path.join(home, '.fnm', 'node-versions'),
|
|
16
|
-
];
|
|
17
|
-
return roots.flatMap(root => {
|
|
18
|
-
try {
|
|
19
|
-
return readdirSync(root, { withFileTypes: true })
|
|
20
|
-
.filter(entry => entry.isDirectory())
|
|
21
|
-
.sort((left, right) => right.name.localeCompare(left.name, undefined, { numeric: true }))
|
|
22
|
-
.flatMap(entry => executableNames.flatMap(name => [
|
|
23
|
-
path.join(root, entry.name, 'bin', name),
|
|
24
|
-
path.join(root, entry.name, 'installation', 'bin', name),
|
|
25
|
-
]));
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
return [];
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
12
|
export function stripAnsi(value) {
|
|
33
13
|
return value.replace(ANSI_ESCAPE_PATTERN, '');
|
|
34
14
|
}
|
|
@@ -53,7 +33,11 @@ export function resolveAliSkillsExecutable(options = {}) {
|
|
|
53
33
|
...names.map(name => path.join(home, '.volta', 'bin', name)),
|
|
54
34
|
...names.map(name => path.join(home, '.local', 'share', 'pnpm', name)),
|
|
55
35
|
...names.map(name => path.join(home, 'Library', 'pnpm', name)),
|
|
56
|
-
...
|
|
36
|
+
...versionManagerExecutableCandidates(home, names, {
|
|
37
|
+
env,
|
|
38
|
+
platform,
|
|
39
|
+
readdirSync,
|
|
40
|
+
}),
|
|
57
41
|
...(platform === 'win32'
|
|
58
42
|
? []
|
|
59
43
|
: ['/opt/homebrew/bin/ali-skills', '/usr/local/bin/ali-skills', '/usr/bin/ali-skills']),
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -2,21 +2,38 @@
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { runMearlLifecycle } from './lifecycle.js';
|
|
5
|
+
import { isMearlReleaseLine } from './version.js';
|
|
5
6
|
export function parseSetupArgs(argv) {
|
|
6
7
|
if (argv.length === 0 || argv.includes('--help') || argv.includes('-h')) {
|
|
7
|
-
return { help: true, yes: false };
|
|
8
|
+
return { help: true, skipSkill: false, yes: false };
|
|
8
9
|
}
|
|
9
10
|
const operation = argv[0];
|
|
10
11
|
if (operation !== 'install' && operation !== 'update' && operation !== 'uninstall') {
|
|
11
12
|
throw new Error(`Unknown operation: ${operation}`);
|
|
12
13
|
}
|
|
13
14
|
let profile;
|
|
15
|
+
let skipSkill = false;
|
|
16
|
+
let targetLine;
|
|
14
17
|
let yes = false;
|
|
15
|
-
for (
|
|
18
|
+
for (let index = 1; index < argv.length; index += 1) {
|
|
19
|
+
const arg = argv[index];
|
|
16
20
|
if (arg === '--yes' || arg === '-y' || arg === '--non-interactive') {
|
|
17
21
|
yes = true;
|
|
18
22
|
continue;
|
|
19
23
|
}
|
|
24
|
+
if (arg === '--skip-skill') {
|
|
25
|
+
skipSkill = true;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (arg === '--target-line') {
|
|
29
|
+
const value = argv[index + 1];
|
|
30
|
+
if (!isMearlReleaseLine(value)) {
|
|
31
|
+
throw new Error('--target-line requires a valid Mearl release line such as 2.3.x');
|
|
32
|
+
}
|
|
33
|
+
targetLine = value;
|
|
34
|
+
index += 1;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
20
37
|
const candidate = arg === '--local'
|
|
21
38
|
? 'local'
|
|
22
39
|
: arg === '--cloud'
|
|
@@ -31,7 +48,10 @@ export function parseSetupArgs(argv) {
|
|
|
31
48
|
}
|
|
32
49
|
profile = candidate;
|
|
33
50
|
}
|
|
34
|
-
|
|
51
|
+
if (operation === 'uninstall' && targetLine) {
|
|
52
|
+
throw new Error('--target-line is not available for uninstall');
|
|
53
|
+
}
|
|
54
|
+
return { help: false, operation, profile, skipSkill, targetLine, yes };
|
|
35
55
|
}
|
|
36
56
|
function printHelp() {
|
|
37
57
|
console.error(`Mearl Setup
|
|
@@ -48,6 +68,9 @@ Options:
|
|
|
48
68
|
--local Manage @mearl/native-host and @mearl/client
|
|
49
69
|
--cloud Manage @mearl/cloud-client
|
|
50
70
|
--mcp Manage @mearl/mcp-server and its configuration
|
|
71
|
+
--target-line <major.minor.x>
|
|
72
|
+
Install the latest packages from one release line, such as 2.3.x
|
|
73
|
+
--skip-skill Keep the independently managed Mearl Skill unchanged
|
|
51
74
|
--yes, -y Run without prompts and accept detected defaults
|
|
52
75
|
--non-interactive
|
|
53
76
|
Alias for --yes, intended for Agents and CI
|
|
@@ -58,6 +81,7 @@ Examples:
|
|
|
58
81
|
npx @mearl/setup install --yes
|
|
59
82
|
npx @mearl/setup install --cloud
|
|
60
83
|
npx @mearl/setup update
|
|
84
|
+
npx @mearl/setup update --local --target-line 2.3.x --skip-skill
|
|
61
85
|
npx @mearl/setup uninstall --local`);
|
|
62
86
|
}
|
|
63
87
|
export async function main(argv = process.argv.slice(2), interactiveTerminal = process.stdin.isTTY === true && process.stdout.isTTY === true) {
|
|
@@ -82,6 +106,8 @@ export async function main(argv = process.argv.slice(2), interactiveTerminal = p
|
|
|
82
106
|
const ok = await runMearlLifecycle({
|
|
83
107
|
operation: parsed.operation,
|
|
84
108
|
profile: parsed.profile,
|
|
109
|
+
skipSkill: parsed.skipSkill,
|
|
110
|
+
targetLine: parsed.targetLine,
|
|
85
111
|
yes: parsed.yes,
|
|
86
112
|
});
|
|
87
113
|
return ok ? 0 : 1;
|
package/dist/index.d.ts
CHANGED
|
@@ -5,4 +5,6 @@ export type { MearlLifecycleDependencies, MearlLifecycleOperation, MearlLifecycl
|
|
|
5
5
|
export { MEARL_PROFILES, detectMearlProfiles } from './profiles.js';
|
|
6
6
|
export type { DetectedMearlProfiles, MearlProfile, MearlProfileConfig } from './profiles.js';
|
|
7
7
|
export { MEARL_NATIVE_HOST_NAMES, resolveNativeHostDirectory, uninstallNativeHostArtifacts, } from './nativeHost.js';
|
|
8
|
+
export { compareMearlReleaseLines, isMearlReleaseLine, isMearlReleaseVersion, isMearlReleaseVersionInLine, mearlReleaseLineForVersion, parseMearlReleaseLine, parseMearlReleaseVersion, } from './version.js';
|
|
9
|
+
export type { MearlReleaseLine, MearlReleaseVersion } from './version.js';
|
|
8
10
|
export type { NativeHostCleanupDependencies, NativeHostCleanupOptions, NativeHostIntegrationMode, } from './nativeHost.js';
|
package/dist/index.js
CHANGED
|
@@ -2,3 +2,4 @@ export { buildGlobalPackageCommand, detectGlobalPackage, resolveInvokingPackageM
|
|
|
2
2
|
export { runMearlLifecycle } from './lifecycle.js';
|
|
3
3
|
export { MEARL_PROFILES, detectMearlProfiles } from './profiles.js';
|
|
4
4
|
export { MEARL_NATIVE_HOST_NAMES, resolveNativeHostDirectory, uninstallNativeHostArtifacts, } from './nativeHost.js';
|
|
5
|
+
export { compareMearlReleaseLines, isMearlReleaseLine, isMearlReleaseVersion, isMearlReleaseVersionInLine, mearlReleaseLineForVersion, parseMearlReleaseLine, parseMearlReleaseVersion, } from './version.js';
|
package/dist/lifecycle.d.ts
CHANGED
package/dist/lifecycle.js
CHANGED
|
@@ -7,6 +7,7 @@ import { buildGlobalPackageCommand, detectGlobalPackage, resolveInvokingPackageM
|
|
|
7
7
|
import { MEARL_PROFILES, detectMearlProfiles, } from './profiles.js';
|
|
8
8
|
import { uninstallNativeHostArtifacts } from './nativeHost.js';
|
|
9
9
|
import { executableNames, prependCommandPath, runCommand, } from './process.js';
|
|
10
|
+
import { isMearlReleaseLine } from './version.js';
|
|
10
11
|
async function confirmInTerminal(message) {
|
|
11
12
|
if (!input.isTTY || !output.isTTY)
|
|
12
13
|
return false;
|
|
@@ -85,6 +86,9 @@ function operationVerb(operation) {
|
|
|
85
86
|
return operation === 'uninstall' ? 'uninstall' : operation === 'update' ? 'update' : 'install';
|
|
86
87
|
}
|
|
87
88
|
export async function runMearlLifecycle(options, dependencies = {}) {
|
|
89
|
+
if (options.targetLine && !isMearlReleaseLine(options.targetLine)) {
|
|
90
|
+
throw new Error('targetLine must be a valid Mearl release line such as 2.3.x');
|
|
91
|
+
}
|
|
88
92
|
const env = options.env ?? process.env;
|
|
89
93
|
const writeLine = dependencies.writeLine ?? (line => console.error(line));
|
|
90
94
|
const existsSync = dependencies.existsSync ?? fs.existsSync;
|
|
@@ -158,7 +162,7 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
158
162
|
continue;
|
|
159
163
|
}
|
|
160
164
|
const packageOperation = options.operation === 'uninstall' ? 'uninstall' : options.operation;
|
|
161
|
-
const command = buildGlobalPackageCommand(installation, packageOperation, config.packages);
|
|
165
|
+
const command = buildGlobalPackageCommand(installation, packageOperation, config.packages, options.targetLine);
|
|
162
166
|
writeLine(`Packages (${profile}): ${command.manager}`);
|
|
163
167
|
const result = await runLifecycleCommand(runner, command.command, command.args, env, `Packages (${profile})`, writeLine);
|
|
164
168
|
if (!result) {
|
|
@@ -182,7 +186,7 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
182
186
|
}
|
|
183
187
|
let skillOk = true;
|
|
184
188
|
const selectedUsesSkill = selected.some(profile => MEARL_PROFILES[profile].usesSkill);
|
|
185
|
-
if (packagesOk && selectedUsesSkill) {
|
|
189
|
+
if (packagesOk && selectedUsesSkill && options.skipSkill !== true) {
|
|
186
190
|
try {
|
|
187
191
|
if (options.operation === 'uninstall') {
|
|
188
192
|
const remainingSkillProfiles = ['local', 'cloud'].filter(profile => detected.has(profile) && !selected.includes(profile));
|
package/dist/packageManager.d.ts
CHANGED
|
@@ -3,8 +3,12 @@ export type PackageOperation = 'install' | 'update' | 'uninstall';
|
|
|
3
3
|
export interface PackageManagerInstallation {
|
|
4
4
|
manager: GlobalPackageManager;
|
|
5
5
|
command: string;
|
|
6
|
+
/** Directory containing globally installed package executables. */
|
|
7
|
+
binDirectory?: string;
|
|
6
8
|
}
|
|
7
|
-
export interface GlobalPackageCommand
|
|
9
|
+
export interface GlobalPackageCommand {
|
|
10
|
+
manager: GlobalPackageManager;
|
|
11
|
+
command: string;
|
|
8
12
|
args: string[];
|
|
9
13
|
}
|
|
10
14
|
export interface DetectedGlobalPackage extends PackageManagerInstallation {
|
|
@@ -21,7 +25,7 @@ export interface PackageDetectionOptions {
|
|
|
21
25
|
realpathSync?: (filePath: string) => string;
|
|
22
26
|
}
|
|
23
27
|
export declare function resolvePackageManagerInstallation(packageRoot: string, packageName: string, options?: PackageDetectionOptions): PackageManagerInstallation | null;
|
|
24
|
-
export declare function buildGlobalPackageCommand(installation: PackageManagerInstallation, operation: PackageOperation, packages: string[]): GlobalPackageCommand;
|
|
28
|
+
export declare function buildGlobalPackageCommand(installation: PackageManagerInstallation, operation: PackageOperation, packages: string[], targetSelector?: string): GlobalPackageCommand;
|
|
25
29
|
export declare function resolveInvokingPackageManager(options?: {
|
|
26
30
|
env?: NodeJS.ProcessEnv;
|
|
27
31
|
existsSync?: (filePath: string) => boolean;
|
package/dist/packageManager.js
CHANGED
|
@@ -29,26 +29,48 @@ export function resolvePackageManagerInstallation(packageRoot, packageName, opti
|
|
|
29
29
|
...executableNames('pnpm', platform).map(name => path.join(pnpmHome, name)),
|
|
30
30
|
...executableNames('pnpm', platform).map(name => path.join(path.dirname(processExecPath), name)),
|
|
31
31
|
], existsSync) ?? fallbackCommand('pnpm', platform);
|
|
32
|
-
return { manager: 'pnpm', command };
|
|
32
|
+
return { manager: 'pnpm', command, binDirectory: pnpmHome };
|
|
33
33
|
}
|
|
34
34
|
const bunMarker = `/.bun/install/global/node_modules/${lowerPackageName}`;
|
|
35
35
|
if (lowerRoot.endsWith(bunMarker)) {
|
|
36
36
|
const bunHome = normalizedRoot.slice(0, lowerRoot.indexOf('/.bun/'));
|
|
37
37
|
const command = firstExisting(executableNames('bun', platform).map(name => path.join(bunHome, '.bun', 'bin', name)), existsSync) ?? fallbackCommand('bun', platform);
|
|
38
|
-
return {
|
|
38
|
+
return {
|
|
39
|
+
manager: 'bun',
|
|
40
|
+
command,
|
|
41
|
+
binDirectory: path.join(bunHome, '.bun', 'bin'),
|
|
42
|
+
};
|
|
39
43
|
}
|
|
40
44
|
if ((lowerRoot.includes('/.config/yarn/global/node_modules/') ||
|
|
41
45
|
lowerRoot.includes('/.yarn/global/node_modules/') ||
|
|
42
46
|
lowerRoot.includes('/yarn/global/node_modules/') ||
|
|
43
47
|
lowerRoot.includes('/yarn/data/global/node_modules/')) &&
|
|
44
48
|
lowerRoot.endsWith(`/node_modules/${lowerPackageName}`)) {
|
|
45
|
-
|
|
49
|
+
const layout = [
|
|
50
|
+
{ marker: '/.config/yarn/global/', binSegments: ['.yarn', 'bin'] },
|
|
51
|
+
{ marker: '/.yarn/global/', binSegments: ['.yarn', 'bin'] },
|
|
52
|
+
{ marker: '/yarn/data/global/', binSegments: ['yarn', 'bin'] },
|
|
53
|
+
{ marker: '/yarn/global/', binSegments: ['yarn', 'bin'] },
|
|
54
|
+
].find(candidate => lowerRoot.includes(candidate.marker));
|
|
55
|
+
const prefix = normalizedRoot.slice(0, lowerRoot.indexOf(layout.marker));
|
|
56
|
+
const binDirectory = path.join(prefix, ...layout.binSegments);
|
|
57
|
+
const command = firstExisting([
|
|
58
|
+
...executableNames('yarn', platform).map(name => path.join(binDirectory, name)),
|
|
59
|
+
...executableNames('yarn', platform).map(name => path.join(path.dirname(processExecPath), name)),
|
|
60
|
+
], existsSync) ??
|
|
61
|
+
findExecutable('yarn', { env, existsSync, platform }) ??
|
|
62
|
+
fallbackCommand('yarn', platform);
|
|
63
|
+
return { manager: 'yarn', command, binDirectory };
|
|
46
64
|
}
|
|
47
65
|
if (lowerRoot.includes('/.volta/tools/image/packages/') &&
|
|
48
66
|
lowerRoot.endsWith(`/lib/node_modules/${lowerPackageName}`)) {
|
|
49
67
|
const voltaHome = normalizedRoot.slice(0, lowerRoot.indexOf('/.volta/'));
|
|
50
68
|
const command = firstExisting(executableNames('volta', platform).map(name => path.join(voltaHome, '.volta', 'bin', name)), existsSync) ?? fallbackCommand('volta', platform);
|
|
51
|
-
return {
|
|
69
|
+
return {
|
|
70
|
+
manager: 'volta',
|
|
71
|
+
command,
|
|
72
|
+
binDirectory: path.join(voltaHome, '.volta', 'bin'),
|
|
73
|
+
};
|
|
52
74
|
}
|
|
53
75
|
const npmMarker = `/lib/node_modules/${lowerPackageName}`;
|
|
54
76
|
if (lowerRoot.endsWith(npmMarker)) {
|
|
@@ -57,18 +79,25 @@ export function resolvePackageManagerInstallation(packageRoot, packageName, opti
|
|
|
57
79
|
...executableNames('npm', platform).map(name => path.join(prefix, 'bin', name)),
|
|
58
80
|
...executableNames('npm', platform).map(name => path.join(path.dirname(processExecPath), name)),
|
|
59
81
|
], existsSync) ?? fallbackCommand('npm', platform);
|
|
60
|
-
return { manager: 'npm', command };
|
|
82
|
+
return { manager: 'npm', command, binDirectory: path.join(prefix, 'bin') };
|
|
61
83
|
}
|
|
62
84
|
if (platform === 'win32' && env.APPDATA) {
|
|
63
85
|
const npmRoot = normalizePath(path.join(env.APPDATA, 'npm', 'node_modules', packageName));
|
|
64
86
|
if (lowerRoot === npmRoot.toLowerCase()) {
|
|
65
|
-
|
|
87
|
+
const binDirectory = path.join(env.APPDATA, 'npm');
|
|
88
|
+
const command = firstExisting([
|
|
89
|
+
...executableNames('npm', platform).map(name => path.join(binDirectory, name)),
|
|
90
|
+
...executableNames('npm', platform).map(name => path.join(path.dirname(processExecPath), name)),
|
|
91
|
+
], existsSync) ?? fallbackCommand('npm', platform);
|
|
92
|
+
return { manager: 'npm', command, binDirectory };
|
|
66
93
|
}
|
|
67
94
|
}
|
|
68
95
|
return null;
|
|
69
96
|
}
|
|
70
|
-
export function buildGlobalPackageCommand(installation, operation, packages) {
|
|
71
|
-
const targets = operation === 'uninstall'
|
|
97
|
+
export function buildGlobalPackageCommand(installation, operation, packages, targetSelector = 'latest') {
|
|
98
|
+
const targets = operation === 'uninstall'
|
|
99
|
+
? packages
|
|
100
|
+
: packages.map(packageName => `${packageName}@${targetSelector}`);
|
|
72
101
|
let args;
|
|
73
102
|
switch (installation.manager) {
|
|
74
103
|
case 'npm':
|
|
@@ -91,7 +120,7 @@ export function buildGlobalPackageCommand(installation, operation, packages) {
|
|
|
91
120
|
args = operation === 'uninstall' ? ['uninstall', ...targets] : ['install', ...targets];
|
|
92
121
|
break;
|
|
93
122
|
}
|
|
94
|
-
return {
|
|
123
|
+
return { manager: installation.manager, command: installation.command, args };
|
|
95
124
|
}
|
|
96
125
|
export function resolveInvokingPackageManager(options = {}) {
|
|
97
126
|
const env = options.env ?? process.env;
|
|
@@ -194,7 +223,13 @@ export function detectGlobalPackage(packageName, binNames, options = {}) {
|
|
|
194
223
|
});
|
|
195
224
|
if (!installation)
|
|
196
225
|
continue;
|
|
197
|
-
return {
|
|
226
|
+
return {
|
|
227
|
+
packageName,
|
|
228
|
+
packageRoot,
|
|
229
|
+
executable,
|
|
230
|
+
...installation,
|
|
231
|
+
binDirectory: path.dirname(executable),
|
|
232
|
+
};
|
|
198
233
|
}
|
|
199
234
|
return null;
|
|
200
235
|
}
|
package/dist/process.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
1
2
|
export interface SpawnCommand {
|
|
2
3
|
command: string;
|
|
3
4
|
args: string[];
|
|
@@ -16,9 +17,16 @@ export interface RunCommandOptions {
|
|
|
16
17
|
export type CommandRunner = (command: string, args: string[], options?: RunCommandOptions) => Promise<CommandResult>;
|
|
17
18
|
export declare function isExecutable(filePath: string, platform?: NodeJS.Platform): boolean;
|
|
18
19
|
export declare function executableNames(name: string, platform?: NodeJS.Platform): string[];
|
|
20
|
+
export interface VersionManagerExecutableCandidateOptions {
|
|
21
|
+
env?: NodeJS.ProcessEnv;
|
|
22
|
+
platform?: NodeJS.Platform;
|
|
23
|
+
readdirSync?: typeof fs.readdirSync;
|
|
24
|
+
}
|
|
25
|
+
export declare function versionManagerExecutableCandidates(home: string, names: string[], options?: VersionManagerExecutableCandidateOptions): string[];
|
|
19
26
|
export declare function resolveSpawnCommand(executable: string, args: string[], platform?: NodeJS.Platform, commandInterpreter?: string | undefined): SpawnCommand;
|
|
20
27
|
export declare function prependCommandPath(command: string, env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
21
28
|
export declare function prependNodePath(env?: NodeJS.ProcessEnv, nodeExecutable?: string): NodeJS.ProcessEnv;
|
|
29
|
+
export declare function prependPathDirectory(directory: string, env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
22
30
|
export declare function findExecutable(name: string, options?: {
|
|
23
31
|
env?: NodeJS.ProcessEnv;
|
|
24
32
|
existsSync?: (filePath: string) => boolean;
|
package/dist/process.js
CHANGED
|
@@ -14,6 +14,37 @@ export function isExecutable(filePath, platform = process.platform) {
|
|
|
14
14
|
export function executableNames(name, platform = process.platform) {
|
|
15
15
|
return platform === 'win32' ? [`${name}.cmd`, `${name}.exe`, name] : [name];
|
|
16
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
|
+
}
|
|
17
48
|
export function resolveSpawnCommand(executable, args, platform = process.platform, commandInterpreter = process.env.ComSpec) {
|
|
18
49
|
if (platform === 'win32' && /\.(?:cmd|bat)$/i.test(executable)) {
|
|
19
50
|
return {
|
|
@@ -26,12 +57,12 @@ export function resolveSpawnCommand(executable, args, platform = process.platfor
|
|
|
26
57
|
export function prependCommandPath(command, env = process.env) {
|
|
27
58
|
if (!path.isAbsolute(command))
|
|
28
59
|
return env;
|
|
29
|
-
return
|
|
60
|
+
return prependPathDirectory(path.dirname(command), env);
|
|
30
61
|
}
|
|
31
62
|
export function prependNodePath(env = process.env, nodeExecutable = process.execPath) {
|
|
32
|
-
return
|
|
63
|
+
return prependPathDirectory(path.dirname(nodeExecutable), env);
|
|
33
64
|
}
|
|
34
|
-
function
|
|
65
|
+
export function prependPathDirectory(directory, env = process.env) {
|
|
35
66
|
const pathKey = Object.keys(env).find(key => key.toLowerCase() === 'path') || 'PATH';
|
|
36
67
|
const inherited = env[pathKey] || '';
|
|
37
68
|
const entries = inherited.split(path.delimiter).filter(Boolean);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const MEARL_RELEASE_VERSION_MAX_LENGTH = 64;
|
|
2
|
+
export interface MearlReleaseVersion {
|
|
3
|
+
raw: string;
|
|
4
|
+
major: number;
|
|
5
|
+
minor: number;
|
|
6
|
+
patch: number;
|
|
7
|
+
channel: 'stable' | 'beta';
|
|
8
|
+
beta?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface MearlReleaseLine {
|
|
11
|
+
raw: string;
|
|
12
|
+
major: number;
|
|
13
|
+
minor: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function parseMearlReleaseVersion(value: unknown): MearlReleaseVersion | null;
|
|
16
|
+
export declare function isMearlReleaseVersion(value: unknown): value is string;
|
|
17
|
+
export declare function parseMearlReleaseLine(value: unknown): MearlReleaseLine | null;
|
|
18
|
+
export declare function isMearlReleaseLine(value: unknown): value is string;
|
|
19
|
+
export declare function mearlReleaseLineForVersion(version: string): string;
|
|
20
|
+
export declare function isMearlReleaseVersionInLine(version: string, releaseLine: string): boolean;
|
|
21
|
+
export declare function compareMearlReleaseLines(leftVersion: string, rightVersion: string): -1 | 0 | 1;
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export const MEARL_RELEASE_VERSION_MAX_LENGTH = 64;
|
|
2
|
+
const RELEASE_VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-beta\.(0|[1-9]\d*))?$/;
|
|
3
|
+
const RELEASE_LINE_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.x$/;
|
|
4
|
+
const RELEASE_COMPONENT_MAXIMUM = 65_535;
|
|
5
|
+
function parseComponent(value) {
|
|
6
|
+
const component = Number(value);
|
|
7
|
+
return Number.isSafeInteger(component) && component <= RELEASE_COMPONENT_MAXIMUM
|
|
8
|
+
? component
|
|
9
|
+
: null;
|
|
10
|
+
}
|
|
11
|
+
export function parseMearlReleaseVersion(value) {
|
|
12
|
+
if (typeof value !== 'string' || value.length > MEARL_RELEASE_VERSION_MAX_LENGTH)
|
|
13
|
+
return null;
|
|
14
|
+
const match = RELEASE_VERSION_PATTERN.exec(value);
|
|
15
|
+
if (!match)
|
|
16
|
+
return null;
|
|
17
|
+
const major = parseComponent(match[1]);
|
|
18
|
+
const minor = parseComponent(match[2]);
|
|
19
|
+
const patch = parseComponent(match[3]);
|
|
20
|
+
const beta = match[4] === undefined ? undefined : parseComponent(match[4]);
|
|
21
|
+
if (major === null || minor === null || patch === null || beta === null)
|
|
22
|
+
return null;
|
|
23
|
+
return {
|
|
24
|
+
raw: value,
|
|
25
|
+
major,
|
|
26
|
+
minor,
|
|
27
|
+
patch,
|
|
28
|
+
channel: beta === undefined ? 'stable' : 'beta',
|
|
29
|
+
...(beta === undefined ? {} : { beta }),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function isMearlReleaseVersion(value) {
|
|
33
|
+
return parseMearlReleaseVersion(value) !== null;
|
|
34
|
+
}
|
|
35
|
+
export function parseMearlReleaseLine(value) {
|
|
36
|
+
if (typeof value !== 'string' || value.length > MEARL_RELEASE_VERSION_MAX_LENGTH)
|
|
37
|
+
return null;
|
|
38
|
+
const match = RELEASE_LINE_PATTERN.exec(value);
|
|
39
|
+
if (!match)
|
|
40
|
+
return null;
|
|
41
|
+
const major = parseComponent(match[1]);
|
|
42
|
+
const minor = parseComponent(match[2]);
|
|
43
|
+
if (major === null || minor === null)
|
|
44
|
+
return null;
|
|
45
|
+
return { raw: value, major, minor };
|
|
46
|
+
}
|
|
47
|
+
export function isMearlReleaseLine(value) {
|
|
48
|
+
return parseMearlReleaseLine(value) !== null;
|
|
49
|
+
}
|
|
50
|
+
export function mearlReleaseLineForVersion(version) {
|
|
51
|
+
const parsed = parseMearlReleaseVersion(version);
|
|
52
|
+
if (!parsed)
|
|
53
|
+
throw new Error('A valid Mearl release version is required');
|
|
54
|
+
return `${parsed.major}.${parsed.minor}.x`;
|
|
55
|
+
}
|
|
56
|
+
export function isMearlReleaseVersionInLine(version, releaseLine) {
|
|
57
|
+
const parsedVersion = parseMearlReleaseVersion(version);
|
|
58
|
+
const parsedLine = parseMearlReleaseLine(releaseLine);
|
|
59
|
+
return (parsedVersion !== null &&
|
|
60
|
+
parsedLine !== null &&
|
|
61
|
+
parsedVersion.major === parsedLine.major &&
|
|
62
|
+
parsedVersion.minor === parsedLine.minor);
|
|
63
|
+
}
|
|
64
|
+
export function compareMearlReleaseLines(leftVersion, rightVersion) {
|
|
65
|
+
const left = parseMearlReleaseVersion(leftVersion);
|
|
66
|
+
const right = parseMearlReleaseVersion(rightVersion);
|
|
67
|
+
if (!left || !right) {
|
|
68
|
+
throw new Error('Mearl release-line comparison requires valid release versions');
|
|
69
|
+
}
|
|
70
|
+
for (const key of ['major', 'minor']) {
|
|
71
|
+
if (left[key] < right[key])
|
|
72
|
+
return -1;
|
|
73
|
+
if (left[key] > right[key])
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mearl/setup",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "One-shot installer, updater, and uninstaller for Mearl environments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -18,9 +18,17 @@
|
|
|
18
18
|
"types": "./dist/process.d.ts",
|
|
19
19
|
"import": "./dist/process.js"
|
|
20
20
|
},
|
|
21
|
+
"./package-manager": {
|
|
22
|
+
"types": "./dist/packageManager.d.ts",
|
|
23
|
+
"import": "./dist/packageManager.js"
|
|
24
|
+
},
|
|
21
25
|
"./native-host": {
|
|
22
26
|
"types": "./dist/nativeHost.d.ts",
|
|
23
27
|
"import": "./dist/nativeHost.js"
|
|
28
|
+
},
|
|
29
|
+
"./version": {
|
|
30
|
+
"types": "./dist/version.d.ts",
|
|
31
|
+
"import": "./dist/version.js"
|
|
24
32
|
}
|
|
25
33
|
},
|
|
26
34
|
"bin": {
|