@geoly-ai/skills-hub 0.3.0 → 0.3.1
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/package.json +1 -1
- package/src/commands/context.mjs +27 -1
- package/src/commands/preheat-run.mjs +27 -2
- package/src/download.mjs +8 -1
package/package.json
CHANGED
package/src/commands/context.mjs
CHANGED
|
@@ -176,6 +176,32 @@ export function uintArg(name, val) {
|
|
|
176
176
|
* @param {object} globals parseGlobals 的产物
|
|
177
177
|
* @param {object} deps 🔴 只从 `main(argv, deps)` 的第二个形参来,argv/env 到不了这里
|
|
178
178
|
*/
|
|
179
|
+
/**
|
|
180
|
+
* 本 CLI 自己的版本 —— 🔴 **从 package.json 读,不要硬编码。**
|
|
181
|
+
*
|
|
182
|
+
* ⚠️ 这里原本是字面量 `'0.0.0-m1'`。后果不是「显示得不好看」:
|
|
183
|
+
* `timestamp.min_cli_version` 是按真实版本号比对的策略门,
|
|
184
|
+
* 而 `bin/skills-hub.mjs` 一个 dep 都不传 —— 于是**发布出去的 CLI
|
|
185
|
+
* 自报 0.0.0-m1,会被自己的 min_cli_version 当场挡死**。
|
|
186
|
+
* 2026-09-03 首次端到端安装时撞到:timestamp 写着 0.3.0,
|
|
187
|
+
* 而刚从 npm 装下来的 0.3.0 报的是 0.0.0-m1 → E_MIN_CLI_VERSION。
|
|
188
|
+
*
|
|
189
|
+
* 🔴 读不到就**抛**,不要退回一个假版本号:一个编出来的版本号会让
|
|
190
|
+
* 版本门做出错误判定,而那正是这道门要防的事。
|
|
191
|
+
*/
|
|
192
|
+
let cachedVersion;
|
|
193
|
+
export function ownVersion() {
|
|
194
|
+
if (cachedVersion === undefined) {
|
|
195
|
+
const p = new URL('../../package.json', import.meta.url);
|
|
196
|
+
const v = JSON.parse(readFileSync(p, 'utf8')).version;
|
|
197
|
+
if (typeof v !== 'string' || v === '') {
|
|
198
|
+
throw new Error('读不出本 CLI 的版本号(package.json 的 version 不是非空字符串)');
|
|
199
|
+
}
|
|
200
|
+
cachedVersion = v;
|
|
201
|
+
}
|
|
202
|
+
return cachedVersion;
|
|
203
|
+
}
|
|
204
|
+
|
|
179
205
|
export function makeContext(globals, deps = {}) {
|
|
180
206
|
const env = deps.env ?? process.env;
|
|
181
207
|
const home = deps.home ?? homedir();
|
|
@@ -213,7 +239,7 @@ export function makeContext(globals, deps = {}) {
|
|
|
213
239
|
cacheDir,
|
|
214
240
|
/** 🔴 时间只从这里取:canonical JSON 要求严格 `YYYY-MM-DDTHH:MM:SSZ`,测试要能定死它 */
|
|
215
241
|
now: deps.now ?? (() => new Date()),
|
|
216
|
-
cliVersion: deps.cliVersion ?? env.GEOLY_CLI_VERSION ??
|
|
242
|
+
cliVersion: deps.cliVersion ?? env.GEOLY_CLI_VERSION ?? ownVersion(),
|
|
217
243
|
/**
|
|
218
244
|
* 🔴 验签器**没有逃生口**:`deps.verifier` 只有 `main(argv, deps)` 的调用方能给,
|
|
219
245
|
* 而生产入口 `bin/skills-hub.mjs` 一个 dep 都不传。
|
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
import { existsSync } from 'node:fs';
|
|
17
17
|
import { resolveCurrent } from '../snapshot.mjs';
|
|
18
18
|
import { readTrustFloor, resolveStateDir } from '../trust.mjs';
|
|
19
|
+
import { mkdirChainFsync } from '../atomic-fs.mjs';
|
|
19
20
|
import { preheatAssets, preheatMetadata, promoteMetadata, discard, newBudget } from '../preheat.mjs';
|
|
20
21
|
import { createCacheRegistry } from './registry.mjs';
|
|
22
|
+
import { getVerifier } from './snapshot-access.mjs';
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
25
|
* 联网刷新一次 metadata(timestamp + 当前快照)到本地缓存。
|
|
@@ -28,6 +30,16 @@ export async function preheatOnce({
|
|
|
28
30
|
cacheDir, stateDir, verifier, cliVersion, now, fetchImpl, timeoutMs,
|
|
29
31
|
budget = newBudget(),
|
|
30
32
|
}) {
|
|
33
|
+
// 🔴 preheat 现在是**第一个**碰这两个目录的人。
|
|
34
|
+
// 在它之前,建目录那一步藏在 `resolveSnapshotForCommand()` 里面 ——
|
|
35
|
+
// 而 preheat 排在它前面,于是干净 home 上第一次安装直接 ENOENT。
|
|
36
|
+
// 2026-09-03 端到端撞到:`lstat '<home>/.local'`。
|
|
37
|
+
// ⚠️ 顺序要紧:`resolveStateDir()` 内部是 `realpathSync`,**要求目录已存在**。
|
|
38
|
+
// 我第一版写成 `mkdirChainFsync(resolveStateDir(stateDir))` —— 先解析后创建,
|
|
39
|
+
// 在干净 home 上必然 ENOENT,而且报的是 `lstat '<home>/.local'`,
|
|
40
|
+
// 看起来完全不像「目录还没建」。
|
|
41
|
+
mkdirChainFsync(cacheDir);
|
|
42
|
+
mkdirChainFsync(stateDir);
|
|
31
43
|
const { stagingDir, n } = await preheatMetadata({ cacheDir, fetchImpl, timeoutMs, budget });
|
|
32
44
|
try {
|
|
33
45
|
// 🔴 registry 指向 **staging**,不是 cache —— 验的必须是刚下回来的那份。
|
|
@@ -70,10 +82,23 @@ export async function preheatForInstall(ctx, out) {
|
|
|
70
82
|
if (ctx.registryFactory) return { refreshed: false, reason: 'custom-registry' };
|
|
71
83
|
const haveCache = existsSync(`${ctx.cacheDir}/timestamp.json`);
|
|
72
84
|
try {
|
|
85
|
+
// 🔴 `ctx.verifier` 的缺省值是 **null**(不是 undefined)——
|
|
86
|
+
// 直接传会得到 `E_VERIFIER_MISSING`。命令面统一走 `getVerifier()`,
|
|
87
|
+
// 它在没注入时落到**真验签器**(内置信任根)。
|
|
88
|
+
// ⚠️ 与 fetchImpl 是**同一个形状**:注入点缺省为 null,而下游按
|
|
89
|
+
// 「没给就用默认」写。一处栽了就该全仓找同形状的——这是第二处。
|
|
73
90
|
const r = await preheatOnce({
|
|
74
91
|
cacheDir: ctx.cacheDir, stateDir: ctx.stateDir,
|
|
75
|
-
verifier:
|
|
76
|
-
|
|
92
|
+
verifier: await getVerifier(ctx),
|
|
93
|
+
cliVersion: ctx.cliVersion,
|
|
94
|
+
// 🔴 `ctx.now` 是**函数**(`() => new Date()`),而 `resolveCurrent` 要的是
|
|
95
|
+
// **毫秒数**。直接传函数会一路走到 `makeFloor` 里 `new Date(fn).toISOString()`
|
|
96
|
+
// → `RangeError: Invalid time value`,报出来的话完全看不出是这儿。
|
|
97
|
+
// 命令面原本就写着 `now: ctx.now().getTime()`(snapshot-access.mjs:54)——
|
|
98
|
+
// ⚠️ 这是我今天**第三次**把 ctx 字段原样传下去而没看形状
|
|
99
|
+
// (前两次:fetchImpl 与 verifier 的缺省是 null)。
|
|
100
|
+
now: ctx.now().getTime(),
|
|
101
|
+
fetchImpl: ctx.fetchImpl ?? undefined,
|
|
77
102
|
});
|
|
78
103
|
if (r.refreshed) out?.note?.(`已刷新到快照 ${r.n}`);
|
|
79
104
|
return r;
|
package/src/download.mjs
CHANGED
|
@@ -135,11 +135,18 @@ export async function download(url, {
|
|
|
135
135
|
redirectHosts = REDIRECT_HOSTS,
|
|
136
136
|
maxRedirects = MAX_REDIRECTS,
|
|
137
137
|
cap = MAX_DOWNLOAD_BYTES,
|
|
138
|
-
fetchImpl
|
|
138
|
+
fetchImpl,
|
|
139
139
|
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
140
140
|
what = url,
|
|
141
141
|
} = {}) {
|
|
142
142
|
assertDownloadUrl(url, host);
|
|
143
|
+
// 🔴 用 `??` 而不是默认参数:**默认参数只对 `undefined` 生效**。
|
|
144
|
+
// `commands/context.mjs` 里 `fetchImpl` 的缺省值是 **`null`**(与 verifier
|
|
145
|
+
// 等注入点一致),null 会原样穿过默认参数,于是生产路径上拿到的是 null。
|
|
146
|
+
// ⚠️ 单元测试**永远抓不到这一条** —— 它们每次都注入替身,走不到缺省分支。
|
|
147
|
+
// 2026-09-03 干净 home 端到端首次安装时才炸出来:
|
|
148
|
+
// 「当前 Node 没有内建 fetch」 —— 而 Node 25 明明有。
|
|
149
|
+
fetchImpl = fetchImpl ?? globalThis.fetch;
|
|
143
150
|
if (typeof fetchImpl !== 'function') {
|
|
144
151
|
throw new NetworkError('当前 Node 没有内建 fetch,且调用方没有注入 fetchImpl');
|
|
145
152
|
}
|