@mearl/setup 3.0.0-beta.4 → 3.0.0-beta.5
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 +42 -36
- package/dist/browserPreset.d.ts +20 -0
- package/dist/browserPreset.js +82 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +14 -12
- package/dist/lifecycle.d.ts +5 -1
- package/dist/lifecycle.js +24 -8
- package/dist/preset.d.ts +7 -2
- package/dist/preset.js +26 -2
- package/dist/setupPreset.d.ts +9 -0
- package/dist/setupPreset.js +14 -0
- package/dist/setupVersion.js +1 -1
- package/package.json +6 -6
- package/dist/presetUpgrade.d.ts +0 -13
- package/dist/presetUpgrade.js +0 -42
package/README.md
CHANGED
|
@@ -5,20 +5,20 @@
|
|
|
5
5
|
Mearl 环境的一次性安装、更新、卸载与配置工具。无需先全局安装 setup:
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npx
|
|
9
|
-
npx
|
|
10
|
-
npx
|
|
11
|
-
npx
|
|
12
|
-
npx
|
|
13
|
-
npx
|
|
14
|
-
npx
|
|
15
|
-
npx
|
|
8
|
+
npx @mearl/setup@beta [install] [--local] [--cloud|--qoder-cloud] [--mcp]
|
|
9
|
+
npx @mearl/setup@beta update [--local] [--cloud|--qoder-cloud] [--mcp]
|
|
10
|
+
npx @mearl/setup@beta uninstall [--local] [--cloud|--qoder-cloud] [--mcp] [--full]
|
|
11
|
+
npx @mearl/setup@beta extension [--full] [--force]
|
|
12
|
+
npx @mearl/setup@beta plugin [name] <path>
|
|
13
|
+
npx @mearl/setup@beta plugin install <skills add 参数...>
|
|
14
|
+
npx @mearl/setup@beta plugin uninstall <name>
|
|
15
|
+
npx @mearl/setup@beta provider <install|update|uninstall> <package|id>
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
浏览器 Provider 独立安装和演进。安装后运行 `mearl <providerId>` 查看该版本声明的能力、
|
|
19
19
|
配置和启动参数。
|
|
20
20
|
|
|
21
|
-
在 Qoder Cloud Agent 环境中,使用 `npx
|
|
21
|
+
在 Qoder Cloud Agent 环境中,使用 `npx @mearl/setup@beta --qoder-cloud --yes` 选择 Qoder
|
|
22
22
|
配对模式。它与 `--cloud` 安装同一套 cloud-server、client 和 Skill,只把 cloud-server
|
|
23
23
|
模式记录为 Qoder;`mearl-cloud-server` 的命令交互保持不变,`start` 和 `restart` 输出新的
|
|
24
24
|
配对指令后退出,不监听端口或保留守护进程。配对 URL 不包含 Qoder PAT。
|
|
@@ -28,7 +28,7 @@ npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta provider <ins
|
|
|
28
28
|
一个命令安装 skill 并注册它的 CLI、SDK 和 MCP 能力:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
npx
|
|
31
|
+
npx @mearl/setup@beta plugin install https://github.com/F-loat/mearl/tree/v3.0.0-beta.1 --skill browser-tab-manager --yes
|
|
32
32
|
mearl tabs summary
|
|
33
33
|
```
|
|
34
34
|
|
|
@@ -45,8 +45,8 @@ Native preset 批次新建的插件注册使用 setup 托管归属,后续通
|
|
|
45
45
|
已通过其他渠道分发的 skill 或自定义入口,可以直接传入路径注册:
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
|
-
npx
|
|
49
|
-
npx
|
|
48
|
+
npx @mearl/setup@beta plugin /absolute/path/to/browser-tab-manager
|
|
49
|
+
npx @mearl/setup@beta plugin custom ./skills/custom/entry.mjs
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
path 接受目录或具体文件;目录默认读取 `scripts/main.mjs` 或 `scripts/main.py`,相对路径基于调用工作目录解析。插件名优先使用显式 name,其次读取入口的 `name` metadata,最后回退到 Skill 目录名;常规注册只传 path,显式 name 仅用于覆盖或兼容旧入口。Python 插件要求 `python3 >= 3.9`,可通过 `MEARL_PYTHON` 指定解释器路径;setup 只验证入口可加载,不安装 Python,也不执行 `pip install`。
|
|
@@ -59,7 +59,7 @@ native-host 会幂等初始化 Skills 模式。基础环境补齐或初始化失
|
|
|
59
59
|
不再需要插件时执行:
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
npx
|
|
62
|
+
npx @mearl/setup@beta plugin uninstall tabs
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
若入口位于 `~/.agents/skills/<skill>/`,setup 会先执行
|
|
@@ -75,9 +75,9 @@ Provider 由 npm 包分发,但包本身无需暴露 CLI 或安装脚本。setu
|
|
|
75
75
|
可创建的浏览器类型名称及其表单参数:
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
|
-
npx
|
|
79
|
-
npx
|
|
80
|
-
npx
|
|
78
|
+
npx @mearl/setup@beta provider install @example/mearl-provider-device
|
|
79
|
+
npx @mearl/setup@beta provider update example-device
|
|
80
|
+
npx @mearl/setup@beta provider uninstall example-device
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
安装参数接受包名、版本或 dist-tag;更新和卸载可使用 providerId 或完整包名。核心 @mearl 包固定使用公共 npm registry;其余 scope 沿用调用方的包管理器配置,组织 registry 由能力安装引导提供。安装沿用启动 setup 的包管理器,
|
|
@@ -88,8 +88,8 @@ npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta provider unin
|
|
|
88
88
|
## 行为
|
|
89
89
|
|
|
90
90
|
- Mearl 自有 npm 包的安装和更新固定使用 `https://registry.npmjs.org/`,与最新版本查询使用同一来源;第三方包沿用当前包管理器的源配置。
|
|
91
|
-
- `install` 可以省略;直接执行 `npx
|
|
92
|
-
以选项开头时也默认安装,例如 `npx
|
|
91
|
+
- `install` 可以省略;直接执行 `npx @mearl/setup@beta` 等同于 `npx @mearl/setup@beta install`。
|
|
92
|
+
以选项开头时也默认安装,例如 `npx @mearl/setup@beta --cloud --yes`。
|
|
93
93
|
- `install` 未指定环境时默认 `--local`。
|
|
94
94
|
- `-h` / `--help` 显示帮助,`-v` / `--version` 显示 setup 版本。
|
|
95
95
|
- `update` 和 `uninstall` 未指定环境时,从当前 `PATH` 中的
|
|
@@ -137,19 +137,19 @@ npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta provider unin
|
|
|
137
137
|
## 安装或更新浏览器扩展
|
|
138
138
|
|
|
139
139
|
```bash
|
|
140
|
-
npx
|
|
140
|
+
npx @mearl/setup@beta extension
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
首次使用时可一次安装完整的本地浏览器环境:
|
|
144
144
|
|
|
145
145
|
```bash
|
|
146
|
-
npx
|
|
146
|
+
npx @mearl/setup@beta extension --full
|
|
147
147
|
```
|
|
148
148
|
|
|
149
149
|
首次安装、旧版 setup 扩展首次迁移,或 Native Host reload 不可用时才需要 CDP。如果
|
|
150
150
|
Chrome 尚未开放 CDP,交互命令会提示打开
|
|
151
151
|
`chrome://inspect/#remote-debugging`、启用远程调试并允许连接,然后等待安装完成。面向人的
|
|
152
|
-
安装指引不要添加 `--yes`。无 TTY 环境使用 `npx
|
|
152
|
+
安装指引不要添加 `--yes`。无 TTY 环境使用 `npx @mearl/setup@beta extension --yes`;它只尝试
|
|
153
153
|
一次,CDP 不可用时立即中断。无法确认 setup 管理来源且 CDP 不可用时不会下载或修改插件
|
|
154
154
|
目录,打开 CDP 后重新执行同一命令即可。Chrome 版本过低、缺少扩展安装所需的 CDP 方法时,
|
|
155
155
|
命令会中断并提示升级 Chrome。
|
|
@@ -179,7 +179,7 @@ DevTools 面板的设置按钮会显示红点,快捷设置中提供“立即
|
|
|
179
179
|
|
|
180
180
|
自动更新不会降级较新的 Host,也不会改动 cloud、MCP 或独立安装的 Mearl Skill。
|
|
181
181
|
目标 npm 版本尚未发布、网络不可用或更新失败时,旧 Host 会继续运行;下一次扩展后台
|
|
182
|
-
生命周期可重新尝试,也可手动执行 `npx
|
|
182
|
+
生命周期可重新尝试,也可手动执行 `npx @mearl/setup@beta update --local`。
|
|
183
183
|
|
|
184
184
|
## 人工与 Agent 使用
|
|
185
185
|
|
|
@@ -194,41 +194,41 @@ DevTools 面板的设置按钮会显示红点,快捷设置中提供“立即
|
|
|
194
194
|
|
|
195
195
|
```bash
|
|
196
196
|
# 默认安装本地浏览器环境
|
|
197
|
-
npx
|
|
197
|
+
npx @mearl/setup
|
|
198
198
|
|
|
199
199
|
# 与上一条等价
|
|
200
|
-
npx
|
|
200
|
+
npx @mearl/setup@beta install
|
|
201
201
|
|
|
202
202
|
# Agent / CI 无交互安装
|
|
203
|
-
npx
|
|
203
|
+
npx @mearl/setup@beta --yes
|
|
204
204
|
|
|
205
205
|
# 安装云端 Agent 环境
|
|
206
|
-
npx
|
|
206
|
+
npx @mearl/setup@beta --cloud
|
|
207
207
|
|
|
208
208
|
# 同一台机器同时作为本地浏览器宿主和云端 Agent
|
|
209
|
-
npx
|
|
209
|
+
npx @mearl/setup@beta --local --cloud
|
|
210
210
|
|
|
211
211
|
# 自动更新当前 PATH 中检测到的全部 Mearl 环境
|
|
212
|
-
npx
|
|
212
|
+
npx @mearl/setup@beta update
|
|
213
213
|
|
|
214
214
|
# 独立安装或更新到最新浏览器扩展
|
|
215
|
-
npx
|
|
215
|
+
npx @mearl/setup@beta extension
|
|
216
216
|
|
|
217
217
|
# 安装完整本地环境(client、Native Host、Skill 和扩展)
|
|
218
|
-
npx
|
|
218
|
+
npx @mearl/setup@beta extension --full
|
|
219
219
|
|
|
220
220
|
# 只卸载本地环境
|
|
221
|
-
npx
|
|
221
|
+
npx @mearl/setup@beta uninstall --local
|
|
222
222
|
|
|
223
223
|
# 自动检测并无交互卸载
|
|
224
|
-
npx
|
|
224
|
+
npx @mearl/setup@beta uninstall --yes
|
|
225
225
|
|
|
226
226
|
# 卸载环境,并清理全部 CLI 插件与 Provider
|
|
227
|
-
npx
|
|
227
|
+
npx @mearl/setup@beta uninstall --full
|
|
228
228
|
|
|
229
229
|
# 安装可选浏览器 Provider
|
|
230
|
-
npx
|
|
231
|
-
npx
|
|
230
|
+
npx @mearl/setup@beta provider install @example/mearl-provider-device
|
|
231
|
+
npx @mearl/setup@beta provider install @example/mearl-provider-cloud
|
|
232
232
|
```
|
|
233
233
|
|
|
234
234
|
原有 Mearl 环境包的 npm/npx 安装方式和 CLI 保持可用;setup 是统一的便利入口。
|
|
@@ -237,6 +237,12 @@ npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta provider inst
|
|
|
237
237
|
|
|
238
238
|
`mearl-setup preset apply <source-url-or-path>` 准备确切资源、激活本机 Node 选择,并向兼容扩展同步同一批浏览器资源;`preset update` 保留用户覆盖,`preset status` 只读检查实际就绪状态。退出码 3 表示存在待同步/安装组件。完整扩展、默认设置、Skills/Provider 和网页一键安装使用同一实际状态核验,见 [本机安装合同](../../docs/preset-installation.md)。
|
|
239
239
|
|
|
240
|
-
普通 install/update 在替换包前保存选中 preset 的确切资源和期望选择,运行时初始化后再同步宿主和安装 Skills;`extension --full` 也在替换扩展前完成准备。已有本机 preset 优先保留,显式 `--preset`
|
|
240
|
+
普通 install/update 在替换包前保存选中 preset 的确切资源和期望选择,运行时初始化后再同步宿主和安装 Skills;`extension --full` 也在替换扩展前完成准备。已有本机 preset 优先保留,显式 `--preset` 可更新或修复资源。配置或资源不可用会在包变更前停止,不回退到无内部能力的安装。部分升级后的重试使用已保存安装记录,即使包版本已变为 3.x 也不重新下载。卸载不准备或安装 preset。
|
|
241
241
|
|
|
242
242
|
默认目录为公共站点的 `installation.json`,可通过 `MEARL_INSTALLATION_CONFIG_URL` 指定其他 HTTPS 目录(本地验证允许回环 HTTP)。核心不包含组织 ID 或私有来源;目录沿用网页安装的校验合同。本机与浏览器未全部确认时仍报告 pending,准备成功不表示已发行或已完成真实企业授权验收。
|
|
243
|
+
|
|
244
|
+
## 浏览器 preset 与发行入口
|
|
245
|
+
|
|
246
|
+
主动执行 setup 时,先沿用本机已选 preset;本机未选择时,在运行时初始化后、安装 Skill 前采用已连接浏览器的受信 preset。显式 `--preset <source>` 优先。多个浏览器的选择不同、来源不受信或状态读取失败时要求显式选择。首次连接最多等待约一分钟;浏览器关闭时只安装基础环境并说明重试方式。普通连接和后台核心更新不会自动安装首次本机 preset。
|
|
247
|
+
|
|
248
|
+
发行包可设置本机进程环境变量 `MEARL_SETUP_DEFAULT_PRESET` 提供默认清单来源;默认值仅在两端均无选择时生效,插件、Provider、卸载等子命令保持原行为。安装与状态核验仍由公共安装器负责。
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { invokeLocalHost, listLiveBrowsers } from '@mearl/client/local';
|
|
2
|
+
import { loadInstallationConfig } from '@mearl/preset/installation';
|
|
3
|
+
import { createNodePresetLoader } from './preset.js';
|
|
4
|
+
import type { StagedPresetInstallation } from './preset.js';
|
|
5
|
+
interface Options {
|
|
6
|
+
env?: NodeJS.ProcessEnv;
|
|
7
|
+
waitMs?: number;
|
|
8
|
+
writeLine?: (line: string) => void;
|
|
9
|
+
}
|
|
10
|
+
interface Dependencies {
|
|
11
|
+
browsers?: typeof listLiveBrowsers;
|
|
12
|
+
invoke?: typeof invokeLocalHost;
|
|
13
|
+
configuration?: () => ReturnType<typeof loadInstallationConfig>;
|
|
14
|
+
load?: ReturnType<typeof createNodePresetLoader>;
|
|
15
|
+
now?: () => number;
|
|
16
|
+
sleep?: (ms: number) => Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
/** Explicit setup may complete a trusted browser selection before installing any Skills. */
|
|
19
|
+
export declare function prepareBrowserSetupPreset(options?: Options, dependencies?: Dependencies): Promise<StagedPresetInstallation | undefined>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { invokeLocalHost, listLiveBrowsers } from '@mearl/client/local';
|
|
2
|
+
import { installationOriginCovered, loadInstallationConfig, matchesInstallationOrigin, trustedInstallationPreset } from '@mearl/preset/installation';
|
|
3
|
+
import { createNodePresetLoader, PresetInstaller, resolveInstallationConfigUrl } from './preset.js';
|
|
4
|
+
/** Explicit setup may complete a trusted browser selection before installing any Skills. */
|
|
5
|
+
export async function prepareBrowserSetupPreset(options = {}, dependencies = {}) {
|
|
6
|
+
const env = options.env ?? process.env;
|
|
7
|
+
const directory = env.MEARL_CAPABILITY_DIR;
|
|
8
|
+
const current = await new PresetInstaller({ directory }).store.current();
|
|
9
|
+
if (current.activation || current.receipt)
|
|
10
|
+
return;
|
|
11
|
+
const now = dependencies.now ?? Date.now;
|
|
12
|
+
const deadline = now() + (options.waitMs ?? 0);
|
|
13
|
+
const browsers = dependencies.browsers ?? listLiveBrowsers;
|
|
14
|
+
const invoke = dependencies.invoke ?? invokeLocalHost;
|
|
15
|
+
const sleep = dependencies.sleep ?? (ms => new Promise(resolve => setTimeout(resolve, ms)));
|
|
16
|
+
const configuration = dependencies.configuration ?? (() => {
|
|
17
|
+
const url = resolveInstallationConfigUrl(env.MEARL_INSTALLATION_CONFIG_URL);
|
|
18
|
+
return loadInstallationConfig(url, url.origin, AbortSignal.timeout(5_000));
|
|
19
|
+
});
|
|
20
|
+
const read = async (timeoutSec = 5) => {
|
|
21
|
+
const targets = browsers().filter(browser => !browser.provider && browser.transport === 'extension' && browser.status === 'connected' && (!env.MEARL_SOCKET_PATH || browser.socketPath === env.MEARL_SOCKET_PATH));
|
|
22
|
+
if (!targets.length)
|
|
23
|
+
return;
|
|
24
|
+
return (await Promise.all(targets.map(async (browser) => {
|
|
25
|
+
let value;
|
|
26
|
+
try {
|
|
27
|
+
value = await invoke('preset_browser_status', {}, { socketPath: browser.socketPath, timeoutSec });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
throw new Error(`Cannot read preset from ${browser.browserId}. Reconnect the extension and retry setup, or use --preset <source>.`);
|
|
31
|
+
}
|
|
32
|
+
if (value?.error || typeof value?.browserReady !== 'boolean' || !Object.hasOwn(value, 'browserRevision') || (value.presetId !== undefined && (typeof value.presetId !== 'string' || !value.presetId || typeof value.presetSource !== 'string' || typeof value.browserRevision !== 'string' || value.browserReady !== true)))
|
|
33
|
+
throw new Error(`Browser preset is unavailable in ${browser.browserId}. Reconnect the extension and retry setup, or use --preset <source>.`);
|
|
34
|
+
if (!value.presetId)
|
|
35
|
+
return [];
|
|
36
|
+
return [{ browserId: browser.browserId, presetId: value.presetId, presetSource: value.presetSource, browserRevision: value.browserRevision }];
|
|
37
|
+
}))).flat();
|
|
38
|
+
};
|
|
39
|
+
let selected = await read();
|
|
40
|
+
if (!selected && now() < deadline)
|
|
41
|
+
options.writeLine?.('Waiting for the Mearl extension to connect and checking its installed preset…');
|
|
42
|
+
while (!selected && now() < deadline) {
|
|
43
|
+
await sleep(Math.min(1000, deadline - now()));
|
|
44
|
+
if (now() < deadline)
|
|
45
|
+
selected = await read(Math.max(0.1, Math.min(5, (deadline - now()) / 1000)));
|
|
46
|
+
}
|
|
47
|
+
if (!selected) {
|
|
48
|
+
options.writeLine?.('No connected Mearl extension was found. Continuing without a browser preset; connect and rerun setup to adopt it, or use --preset <source> to select a preset.');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!selected.length)
|
|
52
|
+
return;
|
|
53
|
+
const accepted = selected;
|
|
54
|
+
const first = accepted[0];
|
|
55
|
+
if (accepted.some(value => value.presetId !== first.presetId || value.presetSource !== first.presetSource))
|
|
56
|
+
throw new Error('Connected browsers use different presets. Select one explicitly with --preset <source>.');
|
|
57
|
+
const trusted = trustedInstallationPreset(await configuration(), first.presetId, first.presetSource);
|
|
58
|
+
if (!trusted)
|
|
59
|
+
throw new Error('The browser preset is not in the trusted installation catalog. Install it explicitly with --preset <source>.');
|
|
60
|
+
const load = dependencies.load ?? createNodePresetLoader();
|
|
61
|
+
const installer = new PresetInstaller({ directory, load: (source, resourceOptions) => {
|
|
62
|
+
const url = new URL(source);
|
|
63
|
+
if (url.protocol !== 'https:' || url.username || url.password || !trusted.origins.some(origin => matchesInstallationOrigin(origin, url)))
|
|
64
|
+
throw new Error('Browser preset resource is outside the trusted installation catalog');
|
|
65
|
+
return load(source, resourceOptions);
|
|
66
|
+
} });
|
|
67
|
+
options.writeLine?.(`Using the browser's trusted preset: ${first.presetId}`);
|
|
68
|
+
return installer.stage(first.presetSource, {
|
|
69
|
+
expectedId: first.presetId, expectedRevision: null, signal: AbortSignal.timeout(120_000),
|
|
70
|
+
validatePrepared: async (prepared) => {
|
|
71
|
+
if (prepared.preset.permissions.origins.some(origin => !installationOriginCovered(origin, trusted.origins)))
|
|
72
|
+
throw new Error('Browser preset permissions exceed the trusted installation catalog');
|
|
73
|
+
},
|
|
74
|
+
confirm: async () => {
|
|
75
|
+
if (JSON.stringify(trustedInstallationPreset(await configuration(), first.presetId, first.presetSource)) !== JSON.stringify(trusted))
|
|
76
|
+
throw new Error('Browser preset trust changed during setup');
|
|
77
|
+
const latest = await read();
|
|
78
|
+
if (!latest || latest.length !== accepted.length || accepted.some(value => !latest.some(other => value.browserId === other.browserId && value.presetId === other.presetId && value.presetSource === other.presetSource && value.browserRevision === other.browserRevision)))
|
|
79
|
+
throw new Error('The browser preset changed during setup. Retry or use --preset <source>.');
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { MearlProfile } from './profiles.js';
|
|
|
5
5
|
import { ensureOfficialPlugin, installPlugins, setupPlugin, uninstallPlugin } from './plugin.js';
|
|
6
6
|
import { installProviderPackage, uninstallProviderPackage, updateProviderPackage } from './provider.js';
|
|
7
7
|
import { runPresetCommand } from './preset.js';
|
|
8
|
-
import { prepareSetupPreset } from './
|
|
8
|
+
import { prepareSetupPreset } from './setupPreset.js';
|
|
9
9
|
export { SETUP_VERSION } from './setupVersion.js';
|
|
10
10
|
interface ParsedArgs {
|
|
11
11
|
cloudMode?: 'websocket' | 'qoder';
|
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { isMearlReleaseLine } from './version.js';
|
|
|
6
6
|
import { ensureOfficialPlugin, installPlugins, setupPlugin, uninstallPlugin } from './plugin.js';
|
|
7
7
|
import { installProviderPackage, uninstallProviderPackage, updateProviderPackage, } from './provider.js';
|
|
8
8
|
import { runPresetCommand } from './preset.js';
|
|
9
|
-
import { prepareSetupPreset } from './
|
|
9
|
+
import { prepareSetupPreset } from './setupPreset.js';
|
|
10
10
|
import { SETUP_VERSION, SETUP_COMMAND } from './setupVersion.js';
|
|
11
11
|
export { SETUP_VERSION } from './setupVersion.js';
|
|
12
12
|
const baseParsedArgs = () => ({
|
|
@@ -177,7 +177,7 @@ function printHelp() {
|
|
|
177
177
|
const output = `Mearl Setup v${SETUP_VERSION}
|
|
178
178
|
|
|
179
179
|
Usage:
|
|
180
|
-
npx
|
|
180
|
+
npx @mearl/setup [command] [options]
|
|
181
181
|
|
|
182
182
|
Environment commands:
|
|
183
183
|
install [options] Install an environment (default command; target: local)
|
|
@@ -221,21 +221,23 @@ General options:
|
|
|
221
221
|
--help, -h Show this help
|
|
222
222
|
|
|
223
223
|
Examples:
|
|
224
|
-
npx
|
|
225
|
-
npx
|
|
226
|
-
npx
|
|
227
|
-
npx
|
|
228
|
-
npx
|
|
229
|
-
npx
|
|
230
|
-
npx
|
|
231
|
-
npx
|
|
232
|
-
console.log(output.replaceAll('npx
|
|
224
|
+
npx @mearl/setup
|
|
225
|
+
npx @mearl/setup --cloud
|
|
226
|
+
npx @mearl/setup --local --cloud
|
|
227
|
+
npx @mearl/setup update
|
|
228
|
+
npx @mearl/setup uninstall --full
|
|
229
|
+
npx @mearl/setup plugin uninstall yuque
|
|
230
|
+
npx @mearl/setup provider install @example/mearl-provider-brow
|
|
231
|
+
npx @mearl/setup extension --full`;
|
|
232
|
+
console.log(output.replaceAll('npx @mearl/setup', SETUP_COMMAND));
|
|
233
233
|
}
|
|
234
234
|
async function runSetupLifecycle(options, dependencies) {
|
|
235
235
|
let preset;
|
|
236
236
|
const callback = options.operation === 'uninstall' ? undefined : { onPresetStatus: (status) => { preset = status; } };
|
|
237
237
|
const lifecycle = dependencies.runLifecycle ?? runMearlLifecycle;
|
|
238
|
-
const
|
|
238
|
+
const defaultPresetSource = process.env.MEARL_SETUP_DEFAULT_PRESET;
|
|
239
|
+
const configured = defaultPresetSource ? { ...options, defaultPresetSource } : options;
|
|
240
|
+
const ok = callback ? await lifecycle(configured, callback) : await lifecycle(configured);
|
|
239
241
|
if (!ok)
|
|
240
242
|
return 1;
|
|
241
243
|
if (!preset) {
|
package/dist/lifecycle.d.ts
CHANGED
|
@@ -9,11 +9,14 @@ import { installManagedPlugins, uninstallManagedPlugins } from './managedPlugins
|
|
|
9
9
|
import { uninstallPlugin } from './plugin.js';
|
|
10
10
|
import { uninstallProviderPackage } from './provider.js';
|
|
11
11
|
import { cleanupUninstalledRuntime } from './runtimeCleanup.js';
|
|
12
|
-
import { prepareSetupPreset } from './
|
|
12
|
+
import { prepareSetupPreset } from './setupPreset.js';
|
|
13
|
+
import { prepareBrowserSetupPreset } from './browserPreset.js';
|
|
13
14
|
import type { PresetInstallStatus } from './preset.js';
|
|
14
15
|
export type MearlLifecycleOperation = 'install' | 'update' | 'uninstall';
|
|
15
16
|
export interface MearlLifecycleOptions {
|
|
16
17
|
presetSource?: string;
|
|
18
|
+
/** Distribution default used only when neither local nor browser selection exists. */
|
|
19
|
+
defaultPresetSource?: string;
|
|
17
20
|
operation: MearlLifecycleOperation;
|
|
18
21
|
cloudMode?: 'websocket' | 'qoder';
|
|
19
22
|
/** Uninstall every registered CLI plugin and Provider in addition to selected environments. */
|
|
@@ -27,6 +30,7 @@ export interface MearlLifecycleOptions {
|
|
|
27
30
|
env?: NodeJS.ProcessEnv;
|
|
28
31
|
}
|
|
29
32
|
export interface MearlLifecycleDependencies {
|
|
33
|
+
prepareBrowserPreset?: typeof prepareBrowserSetupPreset;
|
|
30
34
|
preparePreset?: typeof prepareSetupPreset;
|
|
31
35
|
onPresetStatus?: (status: PresetInstallStatus) => void;
|
|
32
36
|
/** Prepare the selected manager after packages are usable and before Skill operations. */
|
package/dist/lifecycle.js
CHANGED
|
@@ -16,7 +16,8 @@ import { uninstallPlugin } from './plugin.js';
|
|
|
16
16
|
import { uninstallProviderPackage } from './provider.js';
|
|
17
17
|
import { packageRegistryEnvironment } from './npmRegistry.js';
|
|
18
18
|
import { cleanupUninstalledRuntime } from './runtimeCleanup.js';
|
|
19
|
-
import { prepareSetupPreset } from './
|
|
19
|
+
import { prepareSetupPreset } from './setupPreset.js';
|
|
20
|
+
import { prepareBrowserSetupPreset } from './browserPreset.js';
|
|
20
21
|
const LEGACY_CLOUD_CLIENT = '@mearl/cloud-client';
|
|
21
22
|
const UNIFIED_CLIENT = '@mearl/client';
|
|
22
23
|
const PROFILE_ORDER = ['local', 'cloud', 'mcp'];
|
|
@@ -236,7 +237,7 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
236
237
|
writeLine('No installed Mearl environment was detected on the current PATH.');
|
|
237
238
|
if (options.operation === 'uninstall')
|
|
238
239
|
return true;
|
|
239
|
-
writeLine('Run: npx
|
|
240
|
+
writeLine('Run: npx @mearl/setup');
|
|
240
241
|
return false;
|
|
241
242
|
}
|
|
242
243
|
if (options.operation === 'update') {
|
|
@@ -281,16 +282,13 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
281
282
|
? detectPackage(UNIFIED_CLIENT, ['mearl'], packageDetectionOptions)
|
|
282
283
|
: null;
|
|
283
284
|
const detectedClient = detectedClientCandidate?.packageName === UNIFIED_CLIENT ? detectedClientCandidate : null;
|
|
285
|
+
const preparesPreset = options.operation !== 'uninstall' && !dependencies.prepareSkills && (!options.targetLine || Number(options.targetLine.split('.')[0]) >= 3);
|
|
286
|
+
const selectsInitialPreset = preparesPreset && !options.skipSkill && !env.MEARL_AUTO_UPDATE_CLIENT_ROOT && !env.MEARL_AUTO_UPDATE_PROFILES;
|
|
284
287
|
let stagedPreset;
|
|
285
|
-
if (options.operation !== 'uninstall' &&
|
|
288
|
+
if (preparesPreset || (options.operation !== 'uninstall' && options.presetSource && !dependencies.prepareSkills)) {
|
|
286
289
|
try {
|
|
287
290
|
stagedPreset = await (dependencies.preparePreset ?? prepareSetupPreset)({
|
|
288
291
|
source: options.presetSource, env,
|
|
289
|
-
installed: [
|
|
290
|
-
...lifecycleSelected.flatMap(profile => detected.get(profile) ? [detected.get(profile)] : []),
|
|
291
|
-
...(detectedClient ? [detectedClient] : []),
|
|
292
|
-
...(autoUpdateClient && env.MEARL_AUTO_UPDATE_CLIENT_ROOT ? [{ packageName: UNIFIED_CLIENT, packageRoot: env.MEARL_AUTO_UPDATE_CLIENT_ROOT }] : []),
|
|
293
|
-
],
|
|
294
292
|
});
|
|
295
293
|
}
|
|
296
294
|
catch (error) {
|
|
@@ -422,6 +420,24 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
422
420
|
}
|
|
423
421
|
}
|
|
424
422
|
}
|
|
423
|
+
if (packagesOk && selectsInitialPreset && !stagedPreset && !options.presetSource && lifecycleSelected.some(profile => profile === 'local' || profile === 'mcp')) {
|
|
424
|
+
try {
|
|
425
|
+
stagedPreset = await (dependencies.prepareBrowserPreset ?? prepareBrowserSetupPreset)({ env, waitMs: 65_000, writeLine });
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
writeLine(`Browser preset: ${error instanceof Error ? error.message : String(error)}`);
|
|
429
|
+
packagesOk = false;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (packagesOk && selectsInitialPreset && !stagedPreset && options.defaultPresetSource) {
|
|
433
|
+
try {
|
|
434
|
+
stagedPreset = await (dependencies.preparePreset ?? prepareSetupPreset)({ fallbackSource: options.defaultPresetSource, env });
|
|
435
|
+
}
|
|
436
|
+
catch (error) {
|
|
437
|
+
writeLine(`Default preset: ${error instanceof Error ? error.message : String(error)}`);
|
|
438
|
+
packagesOk = false;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
425
441
|
if (packagesOk && options.operation !== 'uninstall' && (stagedPreset || dependencies.prepareSkills)) {
|
|
426
442
|
try {
|
|
427
443
|
if (stagedPreset) {
|
package/dist/preset.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { invoke } from '@mearl/client/local';
|
|
2
2
|
import { type JsonObject } from '@mearl/preset';
|
|
3
|
-
import { type BundleLoader } from '@mearl/preset/bundles';
|
|
3
|
+
import { type BundleLoader, type PreparedPreset } from '@mearl/preset/bundles';
|
|
4
4
|
import { PresetInstallationStore } from './presetStore.js';
|
|
5
|
+
export declare const MEARL_INSTALLATION_CONFIG_URL: string;
|
|
6
|
+
/** Runtime overrides apply to this local process, never to browser-supplied install requests. */
|
|
7
|
+
export declare function resolveInstallationConfigUrl(value?: string | undefined, allowLoopback?: boolean): URL;
|
|
5
8
|
export { claimPresetInstallation } from './presetInstallationLease.js';
|
|
6
9
|
export { PresetInstallationStore, presetSelectionRevision } from './presetStore.js';
|
|
7
10
|
export type { PresetInstallReceipt } from './presetStore.js';
|
|
@@ -59,6 +62,8 @@ export interface PresetInstallerOptions {
|
|
|
59
62
|
invoke?: typeof invoke;
|
|
60
63
|
}
|
|
61
64
|
export interface PresetApplyOptions {
|
|
65
|
+
validatePrepared?: (prepared: PreparedPreset) => Promise<void>;
|
|
66
|
+
skipUnchanged?: boolean;
|
|
62
67
|
overrides?: JsonObject;
|
|
63
68
|
nodeConfig?: Record<string, JsonObject>;
|
|
64
69
|
signal?: AbortSignal;
|
|
@@ -87,7 +92,7 @@ export declare class PresetInstaller {
|
|
|
87
92
|
/** Save exact resources and desired selection; activate once after runtime setup, then retry via resume. */
|
|
88
93
|
stage(source: string, options?: PresetApplyOptions): Promise<StagedPresetInstallation>;
|
|
89
94
|
apply(source: string, options?: PresetApplyOptions): Promise<PresetInstallStatus>;
|
|
90
|
-
update(): Promise<PresetInstallStatus>;
|
|
95
|
+
update(options?: Pick<PresetApplyOptions, 'validatePrepared' | 'skipUnchanged' | 'signal'>): Promise<PresetInstallStatus>;
|
|
91
96
|
resume(expectedRevision: string): Promise<PresetInstallStatus>;
|
|
92
97
|
configure(value: JsonObject): Promise<PresetInstallStatus>;
|
|
93
98
|
}
|
package/dist/preset.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { lstat, open } from 'node:fs/promises';
|
|
2
2
|
import { constants } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
3
4
|
import { resolve } from 'node:path';
|
|
4
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
6
|
import { invoke, invokeLocalHost, listLiveBrowsers, SOCKET_PATH } from '@mearl/client/local';
|
|
6
7
|
import { mergePresetDefaults } from '@mearl/preset';
|
|
7
8
|
import { BundleError, createBundleFetchLoader, preparePreset } from '@mearl/preset/bundles';
|
|
8
9
|
import { PresetInstallationStore, presetSelectionRevision } from './presetStore.js';
|
|
10
|
+
const installationSite = createRequire(import.meta.url)('@mearl/preset/installation-site.json');
|
|
11
|
+
export const MEARL_INSTALLATION_CONFIG_URL = new URL('installation.json', installationSite.url).href;
|
|
12
|
+
/** Runtime overrides apply to this local process, never to browser-supplied install requests. */
|
|
13
|
+
export function resolveInstallationConfigUrl(value = process.env.MEARL_INSTALLATION_CONFIG_URL, allowLoopback = false) {
|
|
14
|
+
const url = new URL(value || MEARL_INSTALLATION_CONFIG_URL);
|
|
15
|
+
if (url.username || url.password || url.hash || (url.protocol !== 'https:' && !(allowLoopback && url.protocol === 'http:' && ['127.0.0.1', 'localhost', '[::1]'].includes(url.hostname))))
|
|
16
|
+
throw new Error('Invalid installation configuration URL');
|
|
17
|
+
return url;
|
|
18
|
+
}
|
|
9
19
|
export { claimPresetInstallation } from './presetInstallationLease.js';
|
|
10
20
|
export { PresetInstallationStore, presetSelectionRevision } from './presetStore.js';
|
|
11
21
|
export { PresetBrowserTransfer } from './presetBrowserTransfer.js';
|
|
@@ -225,6 +235,20 @@ export class PresetInstaller {
|
|
|
225
235
|
const prepared = await preparePreset(sourceUrl(source), { mearlApiVersion: this.store.mearlApiVersion, load: this.options.load ?? createNodePresetLoader(), signal: options.signal });
|
|
226
236
|
if (options.expectedId !== undefined && prepared.preset.id !== options.expectedId)
|
|
227
237
|
throw new BundleError('BUNDLE_INVALID', 'The preset identity differs from the accepted installation');
|
|
238
|
+
await options.validatePrepared?.(prepared);
|
|
239
|
+
if (options.skipUnchanged) {
|
|
240
|
+
const current = await this.store.current();
|
|
241
|
+
if (presetSelectionRevision(current) !== expectedRevision)
|
|
242
|
+
throw new BundleError('BUNDLE_CONFLICT', 'The installed preset changed during update');
|
|
243
|
+
if (current.receipt && current.prepared?.snapshot.revision === prepared.snapshot.revision) {
|
|
244
|
+
return { revision: current.receipt.revision, activate: async () => {
|
|
245
|
+
const status = await this.status();
|
|
246
|
+
if (status.revision !== expectedRevision)
|
|
247
|
+
throw new BundleError('BUNDLE_CONFLICT', 'The installed preset changed during update');
|
|
248
|
+
return status.ready ? status : this.resume(current.receipt.revision);
|
|
249
|
+
} };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
228
252
|
const receipt = await this.store.apply(prepared, { ...options, expectedRevision });
|
|
229
253
|
let activated = false;
|
|
230
254
|
return { revision: receipt.revision, activate: async () => {
|
|
@@ -249,11 +273,11 @@ export class PresetInstaller {
|
|
|
249
273
|
async apply(source, options = {}) {
|
|
250
274
|
return (await this.stage(source, options)).activate();
|
|
251
275
|
}
|
|
252
|
-
async update() {
|
|
276
|
+
async update(options = {}) {
|
|
253
277
|
const current = await this.store.current({ verifyResources: false });
|
|
254
278
|
if (!current.receipt)
|
|
255
279
|
throw new Error('No preset is installed');
|
|
256
|
-
return this.apply(current.receipt.snapshot.source, { expectedRevision: presetSelectionRevision(current) });
|
|
280
|
+
return this.apply(current.receipt.snapshot.source, { ...options, expectedRevision: presetSelectionRevision(current) });
|
|
257
281
|
}
|
|
258
282
|
async resume(expectedRevision) {
|
|
259
283
|
const current = await this.store.current();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PresetInstaller, type StagedPresetInstallation } from './preset.js';
|
|
2
|
+
/** Preserve the accepted installation and resume its exact resources before Skill operations. */
|
|
3
|
+
export declare function prepareSetupPreset(options: {
|
|
4
|
+
source?: string;
|
|
5
|
+
fallbackSource?: string;
|
|
6
|
+
env?: NodeJS.ProcessEnv;
|
|
7
|
+
}, dependencies?: {
|
|
8
|
+
installer?: PresetInstaller;
|
|
9
|
+
}): Promise<StagedPresetInstallation | undefined>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PresetInstaller } from './preset.js';
|
|
2
|
+
/** Preserve the accepted installation and resume its exact resources before Skill operations. */
|
|
3
|
+
export async function prepareSetupPreset(options, dependencies = {}) {
|
|
4
|
+
const installer = dependencies.installer ?? new PresetInstaller({ directory: (options.env ?? process.env).MEARL_CAPABILITY_DIR });
|
|
5
|
+
if (options.source)
|
|
6
|
+
return installer.stage(options.source);
|
|
7
|
+
const current = await installer.store.current();
|
|
8
|
+
if (current.receipt)
|
|
9
|
+
return { revision: current.receipt.revision, activate: () => installer.resume(current.receipt.revision) };
|
|
10
|
+
if (current.activation)
|
|
11
|
+
return;
|
|
12
|
+
if (options.fallbackSource)
|
|
13
|
+
return installer.stage(options.fallbackSource);
|
|
14
|
+
}
|
package/dist/setupVersion.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
const require = createRequire(import.meta.url);
|
|
3
3
|
export const SETUP_VERSION = require('../package.json').version;
|
|
4
|
-
export const SETUP_COMMAND = `npx
|
|
4
|
+
export const SETUP_COMMAND = `npx @mearl/setup${SETUP_VERSION.includes('-beta.') ? '@beta' : ''}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mearl/setup",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.5",
|
|
4
4
|
"description": "One-shot environment setup and configuration for Mearl",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -78,11 +78,11 @@
|
|
|
78
78
|
"extract-zip": "^2.0.1",
|
|
79
79
|
"ws": "^8.18.3",
|
|
80
80
|
"semver": "^7.8.5",
|
|
81
|
-
"@mearl/
|
|
82
|
-
"@mearl/
|
|
83
|
-
"@mearl/
|
|
84
|
-
"@mearl/
|
|
85
|
-
"@mearl/
|
|
81
|
+
"@mearl/provider": "3.0.0-beta.5",
|
|
82
|
+
"@mearl/daemon-core": "3.0.0-beta.5",
|
|
83
|
+
"@mearl/preset": "3.0.0-beta.5",
|
|
84
|
+
"@mearl/browser-core": "3.0.0-beta.5",
|
|
85
|
+
"@mearl/client": "3.0.0-beta.5"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
88
88
|
"@types/node": "^24.9.1",
|
package/dist/presetUpgrade.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { type InstallationConfig } from '@mearl/preset/installation';
|
|
2
|
-
import { PresetInstaller, type StagedPresetInstallation } from './preset.js';
|
|
3
|
-
import { type DetectedGlobalPackage } from './packageManager.js';
|
|
4
|
-
export declare const MEARL_INSTALLATION_CONFIG_URL = "https://f-loat.github.io/mearl/installation.json";
|
|
5
|
-
/** Reuse the installation receipt as the retry marker; organization defaults come from deployment data. */
|
|
6
|
-
export declare function prepareSetupPreset(options: {
|
|
7
|
-
source?: string;
|
|
8
|
-
installed?: Array<Pick<DetectedGlobalPackage, 'packageName' | 'packageRoot'>>;
|
|
9
|
-
env?: NodeJS.ProcessEnv;
|
|
10
|
-
}, dependencies?: {
|
|
11
|
-
installer?: PresetInstaller;
|
|
12
|
-
configuration?: () => Promise<InstallationConfig>;
|
|
13
|
-
}): Promise<StagedPresetInstallation | undefined>;
|
package/dist/presetUpgrade.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { loadInstallationConfig } from '@mearl/preset/installation';
|
|
4
|
-
import { PresetInstaller } from './preset.js';
|
|
5
|
-
import { detectGlobalPackage } from './packageManager.js';
|
|
6
|
-
import { detectMearlProfiles } from './profiles.js';
|
|
7
|
-
export const MEARL_INSTALLATION_CONFIG_URL = 'https://f-loat.github.io/mearl/installation.json';
|
|
8
|
-
function isLegacyPackage(installation) {
|
|
9
|
-
try {
|
|
10
|
-
const manifest = JSON.parse(readFileSync(join(installation.packageRoot, 'package.json'), 'utf8'));
|
|
11
|
-
return manifest.name === installation.packageName && typeof manifest.version === 'string' && /^[0-2]\.\d+\.\d+(?:[-+][\w.-]+)?$/.test(manifest.version);
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
/** Reuse the installation receipt as the retry marker; organization defaults come from deployment data. */
|
|
18
|
-
export async function prepareSetupPreset(options, dependencies = {}) {
|
|
19
|
-
const env = options.env ?? process.env;
|
|
20
|
-
const installer = dependencies.installer ?? new PresetInstaller({ directory: env.MEARL_CAPABILITY_DIR });
|
|
21
|
-
if (options.source)
|
|
22
|
-
return installer.stage(options.source);
|
|
23
|
-
const current = await installer.store.current();
|
|
24
|
-
if (current.receipt)
|
|
25
|
-
return { revision: current.receipt.revision, activate: () => installer.resume(current.receipt.revision) };
|
|
26
|
-
if (current.activation)
|
|
27
|
-
return;
|
|
28
|
-
const client = options.installed === undefined ? detectGlobalPackage('@mearl/client', ['mearl'], { env }) : null;
|
|
29
|
-
const installed = options.installed ?? [...detectMearlProfiles({ env }).values(), ...(client ? [client] : [])];
|
|
30
|
-
if (!installed.some(isLegacyPackage))
|
|
31
|
-
return;
|
|
32
|
-
const configuration = dependencies.configuration ?? (() => {
|
|
33
|
-
const url = new URL(env.MEARL_INSTALLATION_CONFIG_URL || MEARL_INSTALLATION_CONFIG_URL);
|
|
34
|
-
if (url.protocol !== 'https:' && !(url.protocol === 'http:' && ['127.0.0.1', 'localhost', '[::1]'].includes(url.hostname)))
|
|
35
|
-
throw new Error('Installation configuration requires HTTPS or loopback HTTP');
|
|
36
|
-
return loadInstallationConfig(url, url.origin, AbortSignal.timeout(5_000));
|
|
37
|
-
});
|
|
38
|
-
const config = await configuration();
|
|
39
|
-
if (!config.legacyPreset)
|
|
40
|
-
throw new Error('Legacy upgrade preset is unavailable. Retry when the installation catalog is available, or select a preset with --preset.');
|
|
41
|
-
return installer.stage(config.presets[config.legacyPreset].source, { expectedRevision: null, expectedId: config.legacyPreset });
|
|
42
|
-
}
|