flower-trellis 0.4.9 → 0.4.11

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 CHANGED
@@ -131,22 +131,33 @@ flower-trellis self-check --json --target .
131
131
  - **本地一致性检查**:每次启动都会比对当前安装的 `flower-trellis` / 捆绑 Trellis 版本与项目 `.trellis/.flower-manifest.json` / `.trellis/.version`;不受 `intervalHours` 限制。
132
132
  - **远程版本探测**:访问 npm registry 读取 `flower-trellis` dist-tags;受 `intervalHours` 节流,带超时,失败静默。
133
133
 
134
- 策略和缓存统一保存在 `.trellis/.flower-manifest.json` 的 `updateCheck`:
134
+ 策略保存在 `.trellis/.flower-manifest.json` 的 `updateCheck`;运行缓存保存在 gitignored 的 `.trellis/.flower-update-check.tmp`,避免 `lastCheckedAt` 等字段反复污染 git。
135
+
136
+ manifest 中只保留用户策略:
135
137
 
136
138
  ```json
137
139
  {
138
140
  "updateCheck": {
139
141
  "enabled": true,
140
142
  "policy": "ask",
141
- "intervalHours": 8,
143
+ "intervalHours": 8
144
+ }
145
+ }
146
+ ```
147
+
148
+ tmp 中保存本地运行缓存:
149
+
150
+ ```json
151
+ {
142
152
  "lastCheckedAt": "2026-07-07T00:00:00.000Z",
143
153
  "lastRemote": { "latest": "0.4.2", "beta": null },
144
154
  "lastStatus": "update_available",
145
155
  "lastErrorCode": null
146
- }
147
156
  }
148
157
  ```
149
158
 
159
+ 旧项目如果已经在 manifest 的 `updateCheck` 里带有缓存字段,新版本会先读取兼容,并在下一次写入时清理这些旧字段。
160
+
150
161
  `policy` 可选:
151
162
 
152
163
  | policy | 行为 |
@@ -1,5 +1,5 @@
1
1
  {
2
- "syncedAt": "2026-07-08T02:50:48.728Z",
2
+ "syncedAt": "2026-07-09T03:31:09.573Z",
3
3
  "syncedFrom": "vendor/skill-garden",
4
4
  "sourceCommit": "6ecfb1b46320d82aeb3e83442235f271d92aa398",
5
5
  "common": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flower-trellis",
3
- "version": "0.4.9",
3
+ "version": "0.4.11",
4
4
  "description": "一键安装/升级 Trellis 并自动融合 skill-garden 强化包(默认 Claude + agents)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -58,9 +58,9 @@
58
58
  "commit-and-tag-version": "^12.7.3"
59
59
  },
60
60
  "flowerReleaseNotes": {
61
- "version": "0.4.9",
61
+ "version": "0.4.11",
62
62
  "source": "CHANGELOG.md",
63
- "body": "### 🐛 修复 Bug Fixes\n\n* **flower:** 强化项目知识发现门禁 ([d21b17e](https://github.com/SilentFlower/flower-trellis/commit/d21b17e860d3b91bb5c69c3921620d60b49608d5))\n* **flower:** 恢复 in_progress 完成门禁 ([304f5fa](https://github.com/SilentFlower/flower-trellis/commit/304f5fadfab139638cfa9658f168448a1f26f9e6))",
63
+ "body": "### 新功能 Features\n\n* **update-check:** 迁移运行缓存到 tmp ([986659a](https://github.com/SilentFlower/flower-trellis/commit/986659a7d3678384be055dcae0067c2c31c836fb))",
64
64
  "truncated": false
65
65
  }
66
66
  }
@@ -4,6 +4,7 @@ import {
4
4
  manifestPath,
5
5
  readManifest,
6
6
  readUpdateCheck,
7
+ updateCheckCachePath,
7
8
  writeUpdateCheck,
8
9
  } from "../lib/manifest.js";
9
10
 
@@ -24,7 +25,7 @@ function assertTrellisProject(target) {
24
25
  }
25
26
 
26
27
  /**
27
- * flower-trellis update-check:管理 manifest 内的启动更新检查策略。
28
+ * flower-trellis update-check:管理启动更新检查策略,并展示本地运行缓存。
28
29
  *
29
30
  * @param {object} ctx 见 cli.js 的 parse()
30
31
  */
@@ -35,9 +36,14 @@ export async function updateCheck(ctx) {
35
36
 
36
37
  if (action === "get") {
37
38
  const manifest = readManifest(ctx.target);
39
+ const cachePath = updateCheckCachePath(ctx.target);
38
40
  console.log(`manifest: ${manifestPath(ctx.target)}`);
41
+ console.log(`cache: ${cachePath}`);
39
42
  console.log(JSON.stringify(readUpdateCheck(ctx.target), null, 2));
40
43
  if (!manifest) console.log(" · manifest 不存在,当前显示默认策略");
44
+ if (!fs.existsSync(cachePath)) {
45
+ console.log(" · cache 不存在,当前显示默认或旧 manifest 兼容缓存");
46
+ }
41
47
  return;
42
48
  }
43
49
 
@@ -11,17 +11,56 @@ import path from "node:path";
11
11
  * 放在 .trellis/ 下,随项目的 Trellis 生命周期存在(uninstall 删 .trellis 时一并消失)。
12
12
  */
13
13
  const MANIFEST_REL = path.join(".trellis", ".flower-manifest.json");
14
+ const UPDATE_CHECK_CACHE_REL = path.join(".trellis", ".flower-update-check.tmp");
14
15
  const UPDATE_POLICIES = new Set(["off", "notify", "ask", "auto"]);
15
- const DEFAULT_UPDATE_CHECK = {
16
+ const UPDATE_CHECK_POLICY_KEYS = new Set(["enabled", "policy", "intervalHours"]);
17
+ const UPDATE_CHECK_CACHE_KEYS = new Set([
18
+ "lastCheckedAt",
19
+ "lastRemote",
20
+ "lastReleaseNotes",
21
+ "lastStatus",
22
+ "lastErrorCode",
23
+ ]);
24
+ const DEFAULT_UPDATE_CHECK_POLICY = {
16
25
  enabled: true,
17
26
  policy: "ask",
18
27
  intervalHours: 8,
28
+ };
29
+ const DEFAULT_UPDATE_CHECK_CACHE = {
19
30
  lastCheckedAt: null,
20
31
  lastRemote: null,
21
32
  lastReleaseNotes: null,
22
33
  lastStatus: null,
23
34
  lastErrorCode: null,
24
35
  };
36
+ const DEFAULT_UPDATE_CHECK = {
37
+ ...DEFAULT_UPDATE_CHECK_POLICY,
38
+ ...DEFAULT_UPDATE_CHECK_CACHE,
39
+ };
40
+
41
+ /** 判断值是否为普通对象。 */
42
+ function isPlainObject(value) {
43
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
44
+ }
45
+
46
+ /** 判断对象是否显式包含给定字段。 */
47
+ function hasAnyOwn(value, keys) {
48
+ if (!isPlainObject(value)) return false;
49
+ for (const key of keys) {
50
+ if (Object.prototype.hasOwnProperty.call(value, key)) return true;
51
+ }
52
+ return false;
53
+ }
54
+
55
+ /** 从对象里取出指定字段。 */
56
+ function pickOwn(value, keys) {
57
+ const raw = isPlainObject(value) ? value : {};
58
+ const picked = {};
59
+ for (const key of keys) {
60
+ if (Object.prototype.hasOwnProperty.call(raw, key)) picked[key] = raw[key];
61
+ }
62
+ return picked;
63
+ }
25
64
 
26
65
  /**
27
66
  * 归一化 release notes 范围字段。
@@ -73,12 +112,126 @@ function normalizeLastReleaseNotes(value) {
73
112
  };
74
113
  }
75
114
 
76
- /** manifest 文件的绝对路径。 */
115
+ /**
116
+ * 归一化远端 dist-tags 缓存。
117
+ *
118
+ * @param {object|null|undefined} value 原始 lastRemote 字段
119
+ * @returns {{latest:string|null,beta:string|null}|null} 归一化后的 dist-tags
120
+ */
121
+ function normalizeLastRemote(value) {
122
+ return value && typeof value === "object"
123
+ ? {
124
+ latest: typeof value.latest === "string" ? value.latest : null,
125
+ beta: typeof value.beta === "string" ? value.beta : null,
126
+ }
127
+ : null;
128
+ }
129
+
130
+ /**
131
+ * 归一化启动更新检查策略字段。
132
+ *
133
+ * @param {object|null|undefined} value 原始 updateCheck 字段
134
+ * @returns {{enabled:boolean,policy:string,intervalHours:number}} 归一化后的策略
135
+ */
136
+ function normalizeUpdateCheckPolicy(value) {
137
+ const raw = value && typeof value === "object" ? value : {};
138
+ const policy = UPDATE_POLICIES.has(raw.policy) ? raw.policy : DEFAULT_UPDATE_CHECK.policy;
139
+ const interval = Number(raw.intervalHours);
140
+ const intervalHours = Number.isFinite(interval) && interval >= 0
141
+ ? interval
142
+ : DEFAULT_UPDATE_CHECK.intervalHours;
143
+ return {
144
+ enabled: typeof raw.enabled === "boolean" ? raw.enabled : DEFAULT_UPDATE_CHECK.enabled,
145
+ policy,
146
+ intervalHours,
147
+ };
148
+ }
149
+
150
+ /**
151
+ * 归一化启动更新检查运行缓存字段。
152
+ *
153
+ * @param {object|null|undefined} value 原始缓存字段
154
+ * @returns {{lastCheckedAt:string|null,lastRemote:object|null,lastReleaseNotes:object|null,lastStatus:string|null,lastErrorCode:string|null}} 归一化后的缓存
155
+ */
156
+ function normalizeUpdateCheckCache(value) {
157
+ const raw = value && typeof value === "object" ? value : {};
158
+ return {
159
+ lastCheckedAt: typeof raw.lastCheckedAt === "string" ? raw.lastCheckedAt : null,
160
+ lastRemote: normalizeLastRemote(raw.lastRemote),
161
+ lastReleaseNotes: normalizeLastReleaseNotes(raw.lastReleaseNotes),
162
+ lastStatus: typeof raw.lastStatus === "string" ? raw.lastStatus : null,
163
+ lastErrorCode: typeof raw.lastErrorCode === "string" ? raw.lastErrorCode : null,
164
+ };
165
+ }
166
+
167
+ /** 读取 tmp 内的 updateCheck 运行缓存;不存在或损坏时返回 null。 */
168
+ function readUpdateCheckCacheFile(target) {
169
+ try {
170
+ return normalizeUpdateCheckCache(
171
+ JSON.parse(fs.readFileSync(updateCheckCachePath(target), "utf8")),
172
+ );
173
+ } catch {
174
+ return null;
175
+ }
176
+ }
177
+
178
+ /** 判断 updateCheck 是否仍含旧版 manifest 缓存字段。 */
179
+ function hasLegacyUpdateCheckCache(updateCheck) {
180
+ return hasAnyOwn(updateCheck, UPDATE_CHECK_CACHE_KEYS);
181
+ }
182
+
183
+ /**
184
+ * 把旧 manifest 内的缓存字段迁移到 tmp。
185
+ *
186
+ * 只在 tmp 尚不存在时迁移,避免旧 manifest 里的陈旧缓存覆盖新版本地缓存。
187
+ */
188
+ function migrateLegacyUpdateCheckCache(target, updateCheck) {
189
+ if (!hasLegacyUpdateCheckCache(updateCheck)) return;
190
+ if (fs.existsSync(updateCheckCachePath(target))) return;
191
+ fs.writeFileSync(
192
+ updateCheckCachePath(target),
193
+ JSON.stringify(normalizeUpdateCheckCache(updateCheck), null, 2) + "\n",
194
+ );
195
+ }
196
+
197
+ /** 写入 tmp 内的 updateCheck 运行缓存。 */
198
+ function writeUpdateCheckCache(target, patch, legacyUpdateCheck) {
199
+ const current = readUpdateCheckCacheFile(target) ||
200
+ normalizeUpdateCheckCache(legacyUpdateCheck);
201
+ const cache = normalizeUpdateCheckCache({
202
+ ...current,
203
+ ...patch,
204
+ });
205
+ fs.writeFileSync(updateCheckCachePath(target), JSON.stringify(cache, null, 2) + "\n");
206
+ return cache;
207
+ }
208
+
209
+ /**
210
+ * manifest 文件的绝对路径。
211
+ *
212
+ * @param {string} target 目标项目根
213
+ * @returns {string} manifest 文件绝对路径
214
+ */
77
215
  export function manifestPath(target) {
78
216
  return path.join(target, MANIFEST_REL);
79
217
  }
80
218
 
81
- /** 读取 manifest;不存在或损坏时返回 null。 */
219
+ /**
220
+ * updateCheck 运行缓存 tmp 文件的绝对路径。
221
+ *
222
+ * @param {string} target 目标项目根
223
+ * @returns {string} updateCheck 运行缓存文件绝对路径
224
+ */
225
+ export function updateCheckCachePath(target) {
226
+ return path.join(target, UPDATE_CHECK_CACHE_REL);
227
+ }
228
+
229
+ /**
230
+ * 读取 manifest;不存在或损坏时返回 null。
231
+ *
232
+ * @param {string} target 目标项目根
233
+ * @returns {object|null} manifest 内容
234
+ */
82
235
  export function readManifest(target) {
83
236
  try {
84
237
  return JSON.parse(fs.readFileSync(manifestPath(target), "utf8"));
@@ -96,55 +249,65 @@ export function readManifest(target) {
96
249
  * @returns {{enabled:boolean,policy:string,intervalHours:number,lastCheckedAt:string|null,lastRemote:object|null,lastReleaseNotes:object|null,lastStatus:string|null,lastErrorCode:string|null}} 归一化后的 updateCheck
97
250
  */
98
251
  export function normalizeUpdateCheck(value) {
99
- const raw = value && typeof value === "object" ? value : {};
100
- const policy = UPDATE_POLICIES.has(raw.policy) ? raw.policy : DEFAULT_UPDATE_CHECK.policy;
101
- const interval = Number(raw.intervalHours);
102
- const intervalHours = Number.isFinite(interval) && interval >= 0
103
- ? interval
104
- : DEFAULT_UPDATE_CHECK.intervalHours;
105
- const lastRemote = raw.lastRemote && typeof raw.lastRemote === "object"
106
- ? {
107
- latest: typeof raw.lastRemote.latest === "string" ? raw.lastRemote.latest : null,
108
- beta: typeof raw.lastRemote.beta === "string" ? raw.lastRemote.beta : null,
109
- }
110
- : null;
111
-
112
252
  return {
113
- enabled: typeof raw.enabled === "boolean" ? raw.enabled : DEFAULT_UPDATE_CHECK.enabled,
114
- policy,
115
- intervalHours,
116
- lastCheckedAt: typeof raw.lastCheckedAt === "string" ? raw.lastCheckedAt : null,
117
- lastRemote,
118
- lastReleaseNotes: normalizeLastReleaseNotes(raw.lastReleaseNotes),
119
- lastStatus: typeof raw.lastStatus === "string" ? raw.lastStatus : null,
120
- lastErrorCode: typeof raw.lastErrorCode === "string" ? raw.lastErrorCode : null,
253
+ ...normalizeUpdateCheckPolicy(value),
254
+ ...normalizeUpdateCheckCache(value),
121
255
  };
122
256
  }
123
257
 
124
258
  /**
125
- * 读取 manifest 里的启动更新检查配置。
259
+ * 读取启动更新检查配置。
260
+ *
261
+ * 返回 manifest 策略与 tmp 运行缓存的合并视图;tmp 不存在时兼容读取旧 manifest 缓存字段。
126
262
  *
127
263
  * @param {string} target 目标项目根
128
264
  * @returns {ReturnType<typeof normalizeUpdateCheck>} 归一化后的配置
129
265
  */
130
266
  export function readUpdateCheck(target) {
131
- return normalizeUpdateCheck(readManifest(target)?.updateCheck);
267
+ const manifestUpdateCheck = readManifest(target)?.updateCheck;
268
+ return {
269
+ ...normalizeUpdateCheckPolicy(manifestUpdateCheck),
270
+ ...(
271
+ readUpdateCheckCacheFile(target) ||
272
+ // 兼容旧版本:首次写入前仍可复用 manifest 里的运行缓存。
273
+ normalizeUpdateCheckCache(manifestUpdateCheck)
274
+ ),
275
+ };
132
276
  }
133
277
 
134
278
  /**
135
- * 写入启动更新检查配置,保留 manifest 其它安装清单字段。
279
+ * 写入启动更新检查配置。
280
+ *
281
+ * 策略字段写入 `.flower-manifest.json`;运行缓存字段写入 `.flower-update-check.tmp`。
282
+ * 若旧 manifest 仍包含缓存字段,本函数会在写入时顺带清理,避免运行态继续进入 git。
136
283
  *
137
284
  * @param {string} target 目标项目根
138
285
  * @param {object} patch 要合并进 updateCheck 的字段
139
- * @returns {object} 写入后的 manifest
286
+ * @returns {object|null} 写入后的 manifest;没有 manifest 且仅写缓存时返回 null
140
287
  */
141
288
  export function writeUpdateCheck(target, patch) {
142
- const current = readManifest(target) || {};
143
- const updateCheck = normalizeUpdateCheck({
144
- ...normalizeUpdateCheck(current.updateCheck),
145
- ...(patch || {}),
146
- });
147
- const next = { ...current, updateCheck };
289
+ const current = readManifest(target);
290
+ const policyPatch = pickOwn(patch, UPDATE_CHECK_POLICY_KEYS);
291
+ const cachePatch = pickOwn(patch, UPDATE_CHECK_CACHE_KEYS);
292
+ const hasPolicyPatch = Object.keys(policyPatch).length > 0;
293
+ const hasCachePatch = Object.keys(cachePatch).length > 0;
294
+ const hasLegacyCache = hasLegacyUpdateCheckCache(current?.updateCheck);
295
+
296
+ if (hasCachePatch && current) {
297
+ writeUpdateCheckCache(target, cachePatch, current.updateCheck);
298
+ } else if (hasLegacyCache) {
299
+ migrateLegacyUpdateCheckCache(target, current.updateCheck);
300
+ }
301
+
302
+ if (!hasPolicyPatch && !hasLegacyCache) return current;
303
+
304
+ const next = {
305
+ ...(current || {}),
306
+ updateCheck: normalizeUpdateCheckPolicy({
307
+ ...normalizeUpdateCheckPolicy(current?.updateCheck),
308
+ ...policyPatch,
309
+ }),
310
+ };
148
311
  fs.writeFileSync(manifestPath(target), JSON.stringify(next, null, 2) + "\n");
149
312
  return next;
150
313
  }
@@ -152,7 +315,8 @@ export function writeUpdateCheck(target, patch) {
152
315
  /**
153
316
  * 写入 manifest。
154
317
  *
155
- * 若调用方没有显式传入 `updateCheck`,保留目标项目已有策略与缓存,避免全装重写时覆盖用户选择。
318
+ * 若调用方没有显式传入 `updateCheck`,保留目标项目已有策略,避免全装重写时覆盖用户选择。
319
+ * 运行缓存会迁移到 `.flower-update-check.tmp`,不得继续写入 manifest。
156
320
  *
157
321
  * @param {string} target 目标项目根
158
322
  * @param {object} data manifest 新内容
@@ -160,10 +324,14 @@ export function writeUpdateCheck(target, patch) {
160
324
  export function writeManifest(target, data) {
161
325
  const current = readManifest(target);
162
326
  const next = { ...data };
327
+ if (hasLegacyUpdateCheckCache(current?.updateCheck)) {
328
+ migrateLegacyUpdateCheckCache(target, current.updateCheck);
329
+ }
163
330
  if (Object.prototype.hasOwnProperty.call(data, "updateCheck")) {
164
- next.updateCheck = normalizeUpdateCheck(data.updateCheck);
331
+ migrateLegacyUpdateCheckCache(target, data.updateCheck);
332
+ next.updateCheck = normalizeUpdateCheckPolicy(data.updateCheck);
165
333
  } else {
166
- next.updateCheck = normalizeUpdateCheck(current?.updateCheck);
334
+ next.updateCheck = normalizeUpdateCheckPolicy(current?.updateCheck);
167
335
  }
168
336
  fs.writeFileSync(manifestPath(target), JSON.stringify(next, null, 2) + "\n");
169
337
  }
@@ -119,6 +119,46 @@ function releaseNotesFromMetadata(metadata, range) {
119
119
  return buildReleaseNotesSummary(metadata?.releaseNotesByVersion, range);
120
120
  }
121
121
 
122
+ /**
123
+ * 安全执行 npm metadata 拉取。
124
+ *
125
+ * 默认 fetcher 已经吞掉网络错误;这里再兜底测试注入的 fetcher,确保 release notes
126
+ * 补拉失败不会破坏 self-check 主流程。
127
+ *
128
+ * @param {() => Promise<object|null>} fetchMetadata npm metadata 拉取函数
129
+ * @returns {Promise<object|null>} registry metadata,失败时为 null
130
+ */
131
+ async function safeFetchPackageUpdateMetadata(fetchMetadata) {
132
+ try {
133
+ return await fetchMetadata();
134
+ } catch {
135
+ return null;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * 缓存命中但缺少摘要时,主动补拉 release notes。
141
+ *
142
+ * 这个补拉只服务当前 actionable 提示的摘要展示,不能把失败写成新的远程版本证据;
143
+ * 成功时也只写回 lastReleaseNotes,保持 lastRemote 仍只记录 dist-tags。
144
+ *
145
+ * @param {string} target 目标项目根
146
+ * @param {object|null} manifest 目标项目 manifest
147
+ * @param {boolean} writeCache 是否允许写缓存
148
+ * @param {object|null} range release notes 范围
149
+ * @param {() => Promise<object|null>} fetchMetadata npm metadata 拉取函数
150
+ * @returns {Promise<object|null>} release notes 摘要;范围有效但不可用时返回 unavailable 摘要
151
+ */
152
+ async function fetchMissingReleaseNotes(target, manifest, writeCache, range, fetchMetadata) {
153
+ if (!range) return null;
154
+ const metadata = await safeFetchPackageUpdateMetadata(fetchMetadata);
155
+ const releaseNotes = releaseNotesFromMetadata(metadata, range);
156
+ if (releaseNotes && !releaseNotes.unavailable && writeCache && manifest) {
157
+ writeUpdateCheck(target, { lastReleaseNotes: releaseNotes });
158
+ }
159
+ return releaseNotes;
160
+ }
161
+
122
162
  /** 检查目标目录 git 工作区是否 clean。 */
123
163
  function gitSafety(target) {
124
164
  const common = {
@@ -312,12 +352,15 @@ export function safetyState(target, status, command) {
312
352
  * 构建启动自更新检查结果。
313
353
  *
314
354
  * @param {string} target 目标项目根
315
- * @param {{writeCache?: boolean, forceRemote?: boolean}} options 检查选项
355
+ * @param {{writeCache?: boolean, forceRemote?: boolean, fetchMetadata?: () => Promise<object|null>}} options 检查选项
316
356
  * @returns {Promise<object>} 结构化检查结果
317
357
  */
318
358
  export async function buildSelfCheck(target, options = {}) {
319
359
  const writeCache = options.writeCache !== false;
320
360
  const forceRemote = options.forceRemote === true;
361
+ const fetchMetadata = typeof options.fetchMetadata === "function"
362
+ ? options.fetchMetadata
363
+ : fetchPackageUpdateMetadata;
321
364
  const now = new Date();
322
365
  const absoluteTarget = path.resolve(target);
323
366
  const trellisDir = path.join(absoluteTarget, ".trellis");
@@ -404,11 +447,19 @@ export async function buildSelfCheck(target, options = {}) {
404
447
  }
405
448
  if (projectOutOfSync) {
406
449
  const releaseNotesRange = projectReleaseNotesRange(projectFlower, currentFlower);
450
+ const releaseNotes = cachedReleaseNotes(updateCheck, releaseNotesRange) ||
451
+ await fetchMissingReleaseNotes(
452
+ absoluteTarget,
453
+ manifest,
454
+ writeCache,
455
+ releaseNotesRange,
456
+ fetchMetadata,
457
+ );
407
458
  return projectOutOfSyncResult(base, absoluteTarget, {
408
459
  tags,
409
460
  fromCache: true,
410
461
  skipped: true,
411
- }, cachedReleaseNotes(updateCheck, releaseNotesRange));
462
+ }, releaseNotes);
412
463
  }
413
464
  return {
414
465
  ...base,
@@ -418,7 +469,7 @@ export async function buildSelfCheck(target, options = {}) {
418
469
  };
419
470
  }
420
471
 
421
- const metadata = await fetchPackageUpdateMetadata();
472
+ const metadata = await safeFetchPackageUpdateMetadata(fetchMetadata);
422
473
  if (!metadata) {
423
474
  if (writeCache && manifest) {
424
475
  writeUpdateCheck(absoluteTarget, {
@@ -348,7 +348,7 @@ export function getUpdateRecommendation(current, tags) {
348
348
  return null;
349
349
  }
350
350
 
351
- /** 尽力而为刷新目标项目 manifest 里的远端探测缓存。 */
351
+ /** 尽力而为刷新目标项目的远端探测缓存。 */
352
352
  function rememberRemoteTags(target, tags, status, releaseNotes = null) {
353
353
  try {
354
354
  if (!readManifest(target)) return;