@mearl/setup
Advanced tools
| export declare const MEARL_RELEASE_VERSION_MAX_LENGTH = 64; | ||
| export interface MearlReleaseVersion { | ||
| raw: string; | ||
| major: number; | ||
| minor: number; | ||
| patch: number; | ||
| channel: 'stable' | 'beta'; | ||
| beta?: number; | ||
| } | ||
| export interface MearlReleaseLine { | ||
| raw: string; | ||
| major: number; | ||
| minor: number; | ||
| } | ||
| export declare function parseMearlReleaseVersion(value: unknown): MearlReleaseVersion | null; | ||
| export declare function isMearlReleaseVersion(value: unknown): value is string; | ||
| export declare function parseMearlReleaseLine(value: unknown): MearlReleaseLine | null; | ||
| export declare function isMearlReleaseLine(value: unknown): value is string; | ||
| export declare function mearlReleaseLineForVersion(version: string): string; | ||
| export declare function isMearlReleaseVersionInLine(version: string, releaseLine: string): boolean; | ||
| export declare function compareMearlReleaseLines(leftVersion: string, rightVersion: string): -1 | 0 | 1; |
| export const MEARL_RELEASE_VERSION_MAX_LENGTH = 64; | ||
| const RELEASE_VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-beta\.(0|[1-9]\d*))?$/; | ||
| const RELEASE_LINE_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.x$/; | ||
| const RELEASE_COMPONENT_MAXIMUM = 65_535; | ||
| function parseComponent(value) { | ||
| const component = Number(value); | ||
| return Number.isSafeInteger(component) && component <= RELEASE_COMPONENT_MAXIMUM | ||
| ? component | ||
| : null; | ||
| } | ||
| export function parseMearlReleaseVersion(value) { | ||
| if (typeof value !== 'string' || value.length > MEARL_RELEASE_VERSION_MAX_LENGTH) | ||
| return null; | ||
| const match = RELEASE_VERSION_PATTERN.exec(value); | ||
| if (!match) | ||
| return null; | ||
| const major = parseComponent(match[1]); | ||
| const minor = parseComponent(match[2]); | ||
| const patch = parseComponent(match[3]); | ||
| const beta = match[4] === undefined ? undefined : parseComponent(match[4]); | ||
| if (major === null || minor === null || patch === null || beta === null) | ||
| return null; | ||
| return { | ||
| raw: value, | ||
| major, | ||
| minor, | ||
| patch, | ||
| channel: beta === undefined ? 'stable' : 'beta', | ||
| ...(beta === undefined ? {} : { beta }), | ||
| }; | ||
| } | ||
| export function isMearlReleaseVersion(value) { | ||
| return parseMearlReleaseVersion(value) !== null; | ||
| } | ||
| export function parseMearlReleaseLine(value) { | ||
| if (typeof value !== 'string' || value.length > MEARL_RELEASE_VERSION_MAX_LENGTH) | ||
| return null; | ||
| const match = RELEASE_LINE_PATTERN.exec(value); | ||
| if (!match) | ||
| return null; | ||
| const major = parseComponent(match[1]); | ||
| const minor = parseComponent(match[2]); | ||
| if (major === null || minor === null) | ||
| return null; | ||
| return { raw: value, major, minor }; | ||
| } | ||
| export function isMearlReleaseLine(value) { | ||
| return parseMearlReleaseLine(value) !== null; | ||
| } | ||
| export function mearlReleaseLineForVersion(version) { | ||
| const parsed = parseMearlReleaseVersion(version); | ||
| if (!parsed) | ||
| throw new Error('A valid Mearl release version is required'); | ||
| return `${parsed.major}.${parsed.minor}.x`; | ||
| } | ||
| export function isMearlReleaseVersionInLine(version, releaseLine) { | ||
| const parsedVersion = parseMearlReleaseVersion(version); | ||
| const parsedLine = parseMearlReleaseLine(releaseLine); | ||
| return (parsedVersion !== null && | ||
| parsedLine !== null && | ||
| parsedVersion.major === parsedLine.major && | ||
| parsedVersion.minor === parsedLine.minor); | ||
| } | ||
| export function compareMearlReleaseLines(leftVersion, rightVersion) { | ||
| const left = parseMearlReleaseVersion(leftVersion); | ||
| const right = parseMearlReleaseVersion(rightVersion); | ||
| if (!left || !right) { | ||
| throw new Error('Mearl release-line comparison requires valid release versions'); | ||
| } | ||
| for (const key of ['major', 'minor']) { | ||
| if (left[key] < right[key]) | ||
| return -1; | ||
| if (left[key] > right[key]) | ||
| return 1; | ||
| } | ||
| return 0; | ||
| } |
+2
-0
@@ -8,2 +8,4 @@ #!/usr/bin/env node | ||
| profile?: MearlProfile; | ||
| skipSkill: boolean; | ||
| targetLine?: string; | ||
| yes: boolean; | ||
@@ -10,0 +12,0 @@ } |
+29
-3
@@ -5,5 +5,6 @@ #!/usr/bin/env node | ||
| import { runMearlLifecycle } from './lifecycle.js'; | ||
| import { isMearlReleaseLine } from './version.js'; | ||
| export function parseSetupArgs(argv) { | ||
| if (argv.length === 0 || argv.includes('--help') || argv.includes('-h')) { | ||
| return { help: true, yes: false }; | ||
| return { help: true, skipSkill: false, yes: false }; | ||
| } | ||
@@ -15,4 +16,7 @@ const operation = argv[0]; | ||
| let profile; | ||
| let skipSkill = false; | ||
| let targetLine; | ||
| let yes = false; | ||
| for (const arg of argv.slice(1)) { | ||
| for (let index = 1; index < argv.length; index += 1) { | ||
| const arg = argv[index]; | ||
| if (arg === '--yes' || arg === '-y' || arg === '--non-interactive') { | ||
@@ -22,2 +26,15 @@ yes = true; | ||
| } | ||
| if (arg === '--skip-skill') { | ||
| skipSkill = true; | ||
| continue; | ||
| } | ||
| if (arg === '--target-line') { | ||
| const value = argv[index + 1]; | ||
| if (!isMearlReleaseLine(value)) { | ||
| throw new Error('--target-line requires a valid Mearl release line such as 2.3.x'); | ||
| } | ||
| targetLine = value; | ||
| index += 1; | ||
| continue; | ||
| } | ||
| const candidate = arg === '--local' | ||
@@ -37,3 +54,6 @@ ? 'local' | ||
| } | ||
| return { help: false, operation, profile, yes }; | ||
| if (operation === 'uninstall' && targetLine) { | ||
| throw new Error('--target-line is not available for uninstall'); | ||
| } | ||
| return { help: false, operation, profile, skipSkill, targetLine, yes }; | ||
| } | ||
@@ -55,2 +75,5 @@ function printHelp() { | ||
| --mcp Manage @mearl/mcp-server and its configuration | ||
| --target-line <major.minor.x> | ||
| Install the latest packages from one release line, such as 2.3.x | ||
| --skip-skill Keep the independently managed Mearl Skill unchanged | ||
| --yes, -y Run without prompts and accept detected defaults | ||
@@ -66,2 +89,3 @@ --non-interactive | ||
| npx @mearl/setup update | ||
| npx @mearl/setup update --local --target-line 2.3.x --skip-skill | ||
| npx @mearl/setup uninstall --local`); | ||
@@ -91,2 +115,4 @@ } | ||
| profile: parsed.profile, | ||
| skipSkill: parsed.skipSkill, | ||
| targetLine: parsed.targetLine, | ||
| yes: parsed.yes, | ||
@@ -93,0 +119,0 @@ }); |
+2
-0
@@ -8,2 +8,4 @@ export { buildGlobalPackageCommand, detectGlobalPackage, resolveInvokingPackageManager, resolvePackageManagerInstallation, } from './packageManager.js'; | ||
| export { MEARL_NATIVE_HOST_NAMES, resolveNativeHostDirectory, uninstallNativeHostArtifacts, } from './nativeHost.js'; | ||
| export { compareMearlReleaseLines, isMearlReleaseLine, isMearlReleaseVersion, isMearlReleaseVersionInLine, mearlReleaseLineForVersion, parseMearlReleaseLine, parseMearlReleaseVersion, } from './version.js'; | ||
| export type { MearlReleaseLine, MearlReleaseVersion } from './version.js'; | ||
| export type { NativeHostCleanupDependencies, NativeHostCleanupOptions, NativeHostIntegrationMode, } from './nativeHost.js'; |
+1
-0
@@ -5,1 +5,2 @@ export { buildGlobalPackageCommand, detectGlobalPackage, resolveInvokingPackageManager, resolvePackageManagerInstallation, } from './packageManager.js'; | ||
| export { MEARL_NATIVE_HOST_NAMES, resolveNativeHostDirectory, uninstallNativeHostArtifacts, } from './nativeHost.js'; | ||
| export { compareMearlReleaseLines, isMearlReleaseLine, isMearlReleaseVersion, isMearlReleaseVersionInLine, mearlReleaseLineForVersion, parseMearlReleaseLine, parseMearlReleaseVersion, } from './version.js'; |
@@ -11,2 +11,4 @@ import { runGlobalMearlSkillOperation } from './aliSkills.js'; | ||
| yes?: boolean; | ||
| targetLine?: string; | ||
| skipSkill?: boolean; | ||
| env?: NodeJS.ProcessEnv; | ||
@@ -13,0 +15,0 @@ } |
@@ -10,2 +10,3 @@ import fs from 'node:fs'; | ||
| import { executableNames, prependCommandPath, runCommand, } from './process.js'; | ||
| import { isMearlReleaseLine } from './version.js'; | ||
| async function confirmInTerminal(message) { | ||
@@ -89,2 +90,5 @@ if (!input.isTTY || !output.isTTY) | ||
| export async function runMearlLifecycle(options, dependencies = {}) { | ||
| if (options.targetLine && !isMearlReleaseLine(options.targetLine)) { | ||
| throw new Error('targetLine must be a valid Mearl release line such as 2.3.x'); | ||
| } | ||
| const env = options.env ?? process.env; | ||
@@ -163,3 +167,3 @@ const writeLine = dependencies.writeLine ?? (line => console.error(line)); | ||
| const packageOperation = options.operation === 'uninstall' ? 'uninstall' : options.operation; | ||
| const command = buildGlobalPackageCommand(installation, packageOperation, config.packages); | ||
| const command = buildGlobalPackageCommand(installation, packageOperation, config.packages, options.targetLine); | ||
| writeLine(`Packages (${profile}): ${command.manager}`); | ||
@@ -188,3 +192,3 @@ const result = await runLifecycleCommand(runner, command.command, command.args, env, `Packages (${profile})`, writeLine); | ||
| const selectedUsesSkill = selected.some(profile => MEARL_PROFILES[profile].usesSkill); | ||
| if (packagesOk && selectedUsesSkill) { | ||
| if (packagesOk && selectedUsesSkill && options.skipSkill !== true) { | ||
| try { | ||
@@ -191,0 +195,0 @@ if (options.operation === 'uninstall') { |
@@ -6,4 +6,8 @@ export type GlobalPackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'volta'; | ||
| command: string; | ||
| /** Directory containing globally installed package executables. */ | ||
| binDirectory?: string; | ||
| } | ||
| export interface GlobalPackageCommand extends PackageManagerInstallation { | ||
| export interface GlobalPackageCommand { | ||
| manager: GlobalPackageManager; | ||
| command: string; | ||
| args: string[]; | ||
@@ -25,3 +29,3 @@ } | ||
| export declare function resolvePackageManagerInstallation(packageRoot: string, packageName: string, options?: PackageDetectionOptions): PackageManagerInstallation | null; | ||
| export declare function buildGlobalPackageCommand(installation: PackageManagerInstallation, operation: PackageOperation, packages: string[]): GlobalPackageCommand; | ||
| export declare function buildGlobalPackageCommand(installation: PackageManagerInstallation, operation: PackageOperation, packages: string[], targetSelector?: string): GlobalPackageCommand; | ||
| export declare function resolveInvokingPackageManager(options?: { | ||
@@ -28,0 +32,0 @@ env?: NodeJS.ProcessEnv; |
+45
-10
@@ -32,3 +32,3 @@ import fs from 'node:fs'; | ||
| ], existsSync) ?? fallbackCommand('pnpm', platform); | ||
| return { manager: 'pnpm', command }; | ||
| return { manager: 'pnpm', command, binDirectory: pnpmHome }; | ||
| } | ||
@@ -39,3 +39,7 @@ const bunMarker = `/.bun/install/global/node_modules/${lowerPackageName}`; | ||
| const command = firstExisting(executableNames('bun', platform).map(name => path.join(bunHome, '.bun', 'bin', name)), existsSync) ?? fallbackCommand('bun', platform); | ||
| return { manager: 'bun', command }; | ||
| return { | ||
| manager: 'bun', | ||
| command, | ||
| binDirectory: path.join(bunHome, '.bun', 'bin'), | ||
| }; | ||
| } | ||
@@ -47,3 +51,17 @@ if ((lowerRoot.includes('/.config/yarn/global/node_modules/') || | ||
| lowerRoot.endsWith(`/node_modules/${lowerPackageName}`)) { | ||
| return { manager: 'yarn', command: fallbackCommand('yarn', platform) }; | ||
| const layout = [ | ||
| { marker: '/.config/yarn/global/', binSegments: ['.yarn', 'bin'] }, | ||
| { marker: '/.yarn/global/', binSegments: ['.yarn', 'bin'] }, | ||
| { marker: '/yarn/data/global/', binSegments: ['yarn', 'bin'] }, | ||
| { marker: '/yarn/global/', binSegments: ['yarn', 'bin'] }, | ||
| ].find(candidate => lowerRoot.includes(candidate.marker)); | ||
| const prefix = normalizedRoot.slice(0, lowerRoot.indexOf(layout.marker)); | ||
| const binDirectory = path.join(prefix, ...layout.binSegments); | ||
| const command = firstExisting([ | ||
| ...executableNames('yarn', platform).map(name => path.join(binDirectory, name)), | ||
| ...executableNames('yarn', platform).map(name => path.join(path.dirname(processExecPath), name)), | ||
| ], existsSync) ?? | ||
| findExecutable('yarn', { env, existsSync, platform }) ?? | ||
| fallbackCommand('yarn', platform); | ||
| return { manager: 'yarn', command, binDirectory }; | ||
| } | ||
@@ -54,3 +72,7 @@ if (lowerRoot.includes('/.volta/tools/image/packages/') && | ||
| const command = firstExisting(executableNames('volta', platform).map(name => path.join(voltaHome, '.volta', 'bin', name)), existsSync) ?? fallbackCommand('volta', platform); | ||
| return { manager: 'volta', command }; | ||
| return { | ||
| manager: 'volta', | ||
| command, | ||
| binDirectory: path.join(voltaHome, '.volta', 'bin'), | ||
| }; | ||
| } | ||
@@ -64,3 +86,3 @@ const npmMarker = `/lib/node_modules/${lowerPackageName}`; | ||
| ], existsSync) ?? fallbackCommand('npm', platform); | ||
| return { manager: 'npm', command }; | ||
| return { manager: 'npm', command, binDirectory: path.join(prefix, 'bin') }; | ||
| } | ||
@@ -70,3 +92,8 @@ if (platform === 'win32' && env.APPDATA) { | ||
| if (lowerRoot === npmRoot.toLowerCase()) { | ||
| return { manager: 'npm', command: fallbackCommand('npm', platform) }; | ||
| const binDirectory = path.join(env.APPDATA, 'npm'); | ||
| const command = firstExisting([ | ||
| ...executableNames('npm', platform).map(name => path.join(binDirectory, name)), | ||
| ...executableNames('npm', platform).map(name => path.join(path.dirname(processExecPath), name)), | ||
| ], existsSync) ?? fallbackCommand('npm', platform); | ||
| return { manager: 'npm', command, binDirectory }; | ||
| } | ||
@@ -76,4 +103,6 @@ } | ||
| } | ||
| export function buildGlobalPackageCommand(installation, operation, packages) { | ||
| const targets = operation === 'uninstall' ? packages : packages.map(packageName => `${packageName}@latest`); | ||
| export function buildGlobalPackageCommand(installation, operation, packages, targetSelector = 'latest') { | ||
| const targets = operation === 'uninstall' | ||
| ? packages | ||
| : packages.map(packageName => `${packageName}@${targetSelector}`); | ||
| let args; | ||
@@ -101,3 +130,3 @@ switch (installation.manager) { | ||
| } | ||
| return { ...installation, args }; | ||
| return { manager: installation.manager, command: installation.command, args }; | ||
| } | ||
@@ -205,5 +234,11 @@ export function resolveInvokingPackageManager(options = {}) { | ||
| continue; | ||
| return { packageName, packageRoot, executable, ...installation }; | ||
| return { | ||
| packageName, | ||
| packageRoot, | ||
| executable, | ||
| ...installation, | ||
| binDirectory: path.dirname(executable), | ||
| }; | ||
| } | ||
| return null; | ||
| } |
@@ -29,2 +29,3 @@ import fs from 'node:fs'; | ||
| export declare function prependNodePath(env?: NodeJS.ProcessEnv, nodeExecutable?: string): NodeJS.ProcessEnv; | ||
| export declare function prependPathDirectory(directory: string, env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv; | ||
| export declare function findExecutable(name: string, options?: { | ||
@@ -31,0 +32,0 @@ env?: NodeJS.ProcessEnv; |
+3
-3
@@ -60,8 +60,8 @@ import fs from 'node:fs'; | ||
| return env; | ||
| return prependPath(path.dirname(command), env); | ||
| return prependPathDirectory(path.dirname(command), env); | ||
| } | ||
| export function prependNodePath(env = process.env, nodeExecutable = process.execPath) { | ||
| return prependPath(path.dirname(nodeExecutable), env); | ||
| return prependPathDirectory(path.dirname(nodeExecutable), env); | ||
| } | ||
| function prependPath(directory, env) { | ||
| export function prependPathDirectory(directory, env = process.env) { | ||
| const pathKey = Object.keys(env).find(key => key.toLowerCase() === 'path') || 'PATH'; | ||
@@ -68,0 +68,0 @@ const inherited = env[pathKey] || ''; |
+9
-1
| { | ||
| "name": "@mearl/setup", | ||
| "version": "2.3.0", | ||
| "version": "2.4.0", | ||
| "description": "One-shot installer, updater, and uninstaller for Mearl environments", | ||
@@ -21,5 +21,13 @@ "type": "module", | ||
| }, | ||
| "./package-manager": { | ||
| "types": "./dist/packageManager.d.ts", | ||
| "import": "./dist/packageManager.js" | ||
| }, | ||
| "./native-host": { | ||
| "types": "./dist/nativeHost.d.ts", | ||
| "import": "./dist/nativeHost.js" | ||
| }, | ||
| "./version": { | ||
| "types": "./dist/version.d.ts", | ||
| "import": "./dist/version.js" | ||
| } | ||
@@ -26,0 +34,0 @@ }, |
+12
-0
@@ -21,2 +21,14 @@ # @mearl/setup | ||
| ## 跟随浏览器扩展自动更新 | ||
| 浏览器扩展与 Native Host 首次建联时会交换版本。每次扩展后台启动只检查一次;当扩展 | ||
| 进入更高的 `major.minor` release line 时,旧 Host 会在保持当前连接可用的前提下,调用 | ||
| 对应 `major.minor.x` 范围内最新的 `@mearl/setup`,把 `@mearl/native-host` 与 | ||
| `@mearl/client` 一起更新到该范围内的最新版本。成功后 Host 退出,扩展通过已有重连机制 | ||
| 启动新版本。只有两个本地 CLI 版本一致且都属于扩展的 release line 时,更新才会被视为成功。 | ||
| 自动更新不会降级较新的 Host,也不会改动 cloud、MCP 或独立安装的 Mearl Skill。 | ||
| 目标 npm 版本尚未发布、网络不可用或更新失败时,旧 Host 会继续运行;下一次扩展后台 | ||
| 生命周期可重新尝试,也可手动执行 `npx @mearl/setup update --local`。 | ||
| ## 人工与 Agent 使用 | ||
@@ -23,0 +35,0 @@ |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
61085
17.22%20
11.11%1315
15.35%65
22.64%24
9.09%