@whobot/cli 2.0.3 → 2.0.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 +2 -2
- package/bin/install.mjs +48 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ npx --yes @whobot/cli@latest install
|
|
|
20
20
|
npx --yes @whobot/cli@2.0.0 install
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
首次迁移不要运行旧的 `update apply`,也不要直接复用 `npx @whobot/cli@latest install
|
|
23
|
+
首次迁移不要运行旧的 `update apply`,也不要直接复用 `npx @whobot/cli@latest install`。已发布 v1 的全局程序可能软链接到同一标签的 npx 缓存;再次解析同标签可能在安装器启动前覆盖旧程序,使活动检查和原凭据端点校验失去依据。固定的新版本使用独立缓存入口,安装器先检查旧身份、原编译端点和活动通话,再替换程序。新安装的全局包与 npx 缓存分离。
|
|
24
24
|
|
|
25
25
|
v2 后续更新:
|
|
26
26
|
|
|
@@ -53,4 +53,4 @@ SHA-256 用于核对下载内容与 manifest 一致,不声称提供公开签
|
|
|
53
53
|
bun test
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
发布人员在启用最低版本前,应对实际发行源执行仓库的 `bun deploy/check-upgrade-release.mjs latest
|
|
56
|
+
发布人员在启用最低版本前,应对实际发行源执行仓库的 `bun deploy/check-upgrade-release.mjs latest`,验证两项门槛可满足、完整 Skill 包和说明可取,以及隔离 npm/native 安装成功。配置、先制品后门槛的顺序与用户人工宿主清单见仓库 `docs/releases/skill-cli-v2-cutover.md`;检查通过只覆盖当前平台,不表示真实宿主已验收。
|
package/bin/install.mjs
CHANGED
|
@@ -113,7 +113,7 @@ function writeJSONFailure(code, message) {
|
|
|
113
113
|
|
|
114
114
|
function bakedChannel() {
|
|
115
115
|
const ch = String(bootstrap.channel || "").trim();
|
|
116
|
-
if (ch !== "
|
|
116
|
+
if (ch !== "latest") {
|
|
117
117
|
throw new InstallerError("发行渠道缺失或非法", "CHANNEL_INVALID");
|
|
118
118
|
}
|
|
119
119
|
return ch;
|
|
@@ -226,7 +226,38 @@ export async function runPostinstall(env) {
|
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
|
|
229
|
+
// npxLauncherIgnore names the command entries of the npx copy at root, which
|
|
230
|
+
// npx puts first on PATH. POSIX npm links them to run.mjs; Windows npm writes
|
|
231
|
+
// cmd-shim files into the same node_modules/.bin, so a realpath alone cannot
|
|
232
|
+
// recognize them. Outside an npx copy nothing is ignored.
|
|
233
|
+
export function npxLauncherIgnore(root) {
|
|
234
|
+
if (!isNpxPath(root)) {
|
|
235
|
+
return {};
|
|
236
|
+
}
|
|
237
|
+
const ignore = { realpath: realpathSync(path.join(root, "bin", "run.mjs")) };
|
|
238
|
+
let modules = root;
|
|
239
|
+
while (path.basename(modules) !== "node_modules") {
|
|
240
|
+
const parent = path.dirname(modules);
|
|
241
|
+
if (parent === modules) {
|
|
242
|
+
return ignore;
|
|
243
|
+
}
|
|
244
|
+
modules = parent;
|
|
245
|
+
}
|
|
246
|
+
const binDir = path.join(modules, ".bin");
|
|
247
|
+
try {
|
|
248
|
+
ignore.binDir = realpathSync(binDir);
|
|
249
|
+
} catch {
|
|
250
|
+
// No .bin directory: no PATH entry can be inside it.
|
|
251
|
+
ignore.binDir = binDir;
|
|
252
|
+
}
|
|
253
|
+
return ignore;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function samePath(a, b) {
|
|
257
|
+
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function whichCommand(name, env, ignore = {}) {
|
|
230
261
|
const pathEnv = env.PATH || env.Path || "";
|
|
231
262
|
const exts = process.platform === "win32" ? [".cmd", ".exe", ".bat", ""] : [""];
|
|
232
263
|
for (const dir of pathEnv.split(path.delimiter)) {
|
|
@@ -237,7 +268,8 @@ export function whichCommand(name, env, ignoreRealpath = "") {
|
|
|
237
268
|
const candidate = path.join(dir, name + ext);
|
|
238
269
|
try {
|
|
239
270
|
accessSync(candidate, constants.X_OK);
|
|
240
|
-
if (
|
|
271
|
+
if (ignore.realpath && realpathSync(candidate) === ignore.realpath) continue;
|
|
272
|
+
if (ignore.binDir && samePath(realpathSync(dir), ignore.binDir)) continue;
|
|
241
273
|
return candidate;
|
|
242
274
|
} catch {
|
|
243
275
|
// try next
|
|
@@ -625,7 +657,7 @@ async function repairAndVerifyNative(native, version, env) {
|
|
|
625
657
|
}
|
|
626
658
|
}
|
|
627
659
|
|
|
628
|
-
export async function verifyInstalledCLI(tools, npmEnv, prefix, version, env, ignoreLauncher =
|
|
660
|
+
export async function verifyInstalledCLI(tools, npmEnv, prefix, version, env, ignoreLauncher = {}) {
|
|
629
661
|
const registered = readGlobalPackageVersion(tools, npmEnv);
|
|
630
662
|
if (registered !== version) {
|
|
631
663
|
throw new InstallerError(`npm registered version ${registered} does not match ${version}`, "UPDATE_VERIFY_FAILED");
|
|
@@ -688,6 +720,9 @@ export async function runExplicitInstall(argv, env) {
|
|
|
688
720
|
reportViaNative(stagedNative, "install_started", "ok", { attrs: reportAttrs }, env);
|
|
689
721
|
verifyNpmEntryVersion(stagedNative, pkg.version, env);
|
|
690
722
|
if (resetLocal) runResetLocal(stagedNative, env);
|
|
723
|
+
// An install stopped after moving the old program aside leaves only its
|
|
724
|
+
// backup; restore it as the installed command does before it runs.
|
|
725
|
+
await recoverNativeBackup(packageNativeBinary(prefix));
|
|
691
726
|
runInstallPhase(stagedNative, ["update", "check-install", "--previous-binary", packageNativeBinary(prefix)], env, "binding", registered);
|
|
692
727
|
runInstallPhase(stagedNative, updateArgs, env, "preflight", registered);
|
|
693
728
|
prepared = await prepareNativeReplace(packageNativeBinary(prefix));
|
|
@@ -696,7 +731,7 @@ export async function runExplicitInstall(argv, env) {
|
|
|
696
731
|
npmInstallGlobalFrom(tools, pkgRoot, npmEnv);
|
|
697
732
|
}
|
|
698
733
|
// Ignore this npx cache launcher only, not other stale host PATH entries.
|
|
699
|
-
const ignoreLauncher =
|
|
734
|
+
const ignoreLauncher = npxLauncherIgnore(pkgRoot);
|
|
700
735
|
const installed = await verifyInstalledCLI(tools, npmEnv, prefix, pkg.version, env, ignoreLauncher);
|
|
701
736
|
runInstallPhase(installed.native, updateArgs, env, "verify", registered);
|
|
702
737
|
await prepared.discard();
|
|
@@ -733,8 +768,10 @@ export function reportViaNative(native, name, result, { errorCode = "", attrs =
|
|
|
733
768
|
}
|
|
734
769
|
|
|
735
770
|
function failureCode(err) {
|
|
736
|
-
const code
|
|
737
|
-
|
|
771
|
+
for (const code of [err?.reportCode, err?.code]) {
|
|
772
|
+
if (typeof code === "string" && /^[A-Z][A-Z0-9_]{0,63}$/.test(code)) return code;
|
|
773
|
+
}
|
|
774
|
+
return "";
|
|
738
775
|
}
|
|
739
776
|
|
|
740
777
|
function runInstallPhase(native, args, env, phase, previous, wantVersion = pkg.version) {
|
|
@@ -750,7 +787,10 @@ function runInstallPhase(native, args, env, phase, previous, wantVersion = pkg.v
|
|
|
750
787
|
const code = response.error?.code || "UPDATE_VERIFY_FAILED";
|
|
751
788
|
// These two already name the recovery (install --reset-local, then sign in again).
|
|
752
789
|
const tail = RESET_LOCAL_CODES.has(code) ? "。" : ";请按上述原因处理后重试,不要删除客户端安装身份。";
|
|
753
|
-
|
|
790
|
+
const err = new InstallerError(`安装未完成: ${code} ${response.error?.detail || response.error?.userMessage || "目标程序未通过验收"}${tail}`, "UPDATE_VERIFY_FAILED");
|
|
791
|
+
// Analytics only: the launcher output and exit code stay UPDATE_VERIFY_FAILED.
|
|
792
|
+
err.reportCode = code;
|
|
793
|
+
throw err;
|
|
754
794
|
}
|
|
755
795
|
return response;
|
|
756
796
|
}
|