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 +14 -3
- package/enhancements/MANIFEST.json +1 -1
- package/package.json +3 -3
- package/src/commands/update-check.js +7 -1
- package/src/lib/manifest.js +205 -37
- package/src/lib/self-check.js +54 -3
- package/src/lib/update-check.js +1 -1
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
|
-
|
|
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 | 行为 |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flower-trellis",
|
|
3
|
-
"version": "0.4.
|
|
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.
|
|
61
|
+
"version": "0.4.11",
|
|
62
62
|
"source": "CHANGELOG.md",
|
|
63
|
-
"body": "###
|
|
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
|
|
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
|
|
package/src/lib/manifest.js
CHANGED
|
@@ -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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
114
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
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
|
-
|
|
331
|
+
migrateLegacyUpdateCheckCache(target, data.updateCheck);
|
|
332
|
+
next.updateCheck = normalizeUpdateCheckPolicy(data.updateCheck);
|
|
165
333
|
} else {
|
|
166
|
-
next.updateCheck =
|
|
334
|
+
next.updateCheck = normalizeUpdateCheckPolicy(current?.updateCheck);
|
|
167
335
|
}
|
|
168
336
|
fs.writeFileSync(manifestPath(target), JSON.stringify(next, null, 2) + "\n");
|
|
169
337
|
}
|
package/src/lib/self-check.js
CHANGED
|
@@ -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
|
-
},
|
|
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
|
|
472
|
+
const metadata = await safeFetchPackageUpdateMetadata(fetchMetadata);
|
|
422
473
|
if (!metadata) {
|
|
423
474
|
if (writeCache && manifest) {
|
|
424
475
|
writeUpdateCheck(absoluteTarget, {
|
package/src/lib/update-check.js
CHANGED
|
@@ -348,7 +348,7 @@ export function getUpdateRecommendation(current, tags) {
|
|
|
348
348
|
return null;
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
/**
|
|
351
|
+
/** 尽力而为刷新目标项目的远端探测缓存。 */
|
|
352
352
|
function rememberRemoteTags(target, tags, status, releaseNotes = null) {
|
|
353
353
|
try {
|
|
354
354
|
if (!readManifest(target)) return;
|