flower-trellis 0.2.4 → 0.2.5-beta.2
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 +27 -3
- package/enhancements/0.6/.agents/skills/trellis-extract-prd/SKILL.md +106 -63
- package/enhancements/0.6/.agents/skills/trellis-plan-version/SKILL.md +147 -25
- package/enhancements/0.6/.agents/skills/trellis-push/SKILL.md +279 -169
- package/enhancements/0.6/.agents/skills/trellis-route/SKILL.md +179 -75
- package/enhancements/0.6/.agents/skills/trellis-verify-task/SKILL.md +154 -5
- package/enhancements/0.6/.claude/skills/trellis-extract-prd/SKILL.md +106 -63
- package/enhancements/0.6/.claude/skills/trellis-plan-version/SKILL.md +147 -25
- package/enhancements/0.6/.claude/skills/trellis-push/SKILL.md +279 -169
- package/enhancements/0.6/.claude/skills/trellis-route/SKILL.md +179 -75
- package/enhancements/0.6/.claude/skills/trellis-verify-task/SKILL.md +154 -5
- package/enhancements/0.6/overrides/workflow-states/in_progress-inline.md +1 -1
- package/enhancements/0.6/overrides/workflow.md +9 -3
- package/enhancements/MANIFEST.json +2 -2
- package/package.json +1 -1
- package/src/lib/update-check.js +142 -36
package/package.json
CHANGED
package/src/lib/update-check.js
CHANGED
|
@@ -6,10 +6,10 @@ import { flowerVersion } from "./versions.js";
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* 版本自动检测 —— 在 init / update 启动时尽力而为地比对 npm 上 flower-trellis 自身的
|
|
9
|
-
*
|
|
9
|
+
* 可用版本,发现新版时提示用户并(交互场景下)询问是否立即升级。
|
|
10
10
|
*
|
|
11
11
|
* 设计基调与本项目一致:**绝不阻断主流程**。网络探测带超时,离线/超时/失败一律静默跳过;
|
|
12
|
-
* 仅在「全局直跑 +
|
|
12
|
+
* 仅在「全局直跑 + 有新版」时才打扰用户。稳定版只跟 latest;预发布版同时看 latest / beta。
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
/** npm registry 根地址。 */
|
|
@@ -20,23 +20,26 @@ const PKG = "flower-trellis";
|
|
|
20
20
|
const TIMEOUT_MS = 2500;
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* 取 npm 上 flower-trellis 的
|
|
23
|
+
* 取 npm 上 flower-trellis 的 dist-tags;任何失败(离线/超时/非 200/解析异常)一律
|
|
24
24
|
* 返回 null —— 调用方据此「拿不到就当没这回事」继续主流程。
|
|
25
25
|
*
|
|
26
26
|
* 用 AbortController 给内置 fetch 加超时,finally 清除定时器防句柄泄漏。
|
|
27
|
-
* @returns {Promise<string|null>}
|
|
27
|
+
* @returns {Promise<{latest:string|null,beta:string|null}|null>} 可用 dist-tags,或失败时 null
|
|
28
28
|
*/
|
|
29
|
-
export async function
|
|
29
|
+
export async function fetchPackageDistTags() {
|
|
30
30
|
const ac = new AbortController();
|
|
31
31
|
const timer = setTimeout(() => ac.abort(), TIMEOUT_MS);
|
|
32
32
|
try {
|
|
33
|
-
const res = await fetch(`${REGISTRY}/${PKG}
|
|
33
|
+
const res = await fetch(`${REGISTRY}/${PKG}`, {
|
|
34
34
|
signal: ac.signal,
|
|
35
35
|
headers: { Accept: "application/json" },
|
|
36
36
|
});
|
|
37
37
|
if (!res.ok) return null; // 非 200(404/5xx 等)→ 静默跳过
|
|
38
38
|
const json = await res.json();
|
|
39
|
-
|
|
39
|
+
const tags = json && typeof json === "object" ? json["dist-tags"] : null;
|
|
40
|
+
const latest = typeof tags?.latest === "string" ? tags.latest : null;
|
|
41
|
+
const beta = typeof tags?.beta === "string" ? tags.beta : null;
|
|
42
|
+
return latest || beta ? { latest, beta } : null;
|
|
40
43
|
} catch {
|
|
41
44
|
return null; // AbortError(超时)/ fetch failed(离线)/ JSON 解析失败 → 静默
|
|
42
45
|
} finally {
|
|
@@ -44,28 +47,130 @@ export async function fetchLatestVersion() {
|
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
|
|
50
|
+
/**
|
|
51
|
+
* 取 npm 上 flower-trellis 的 latest 版本号。
|
|
52
|
+
*
|
|
53
|
+
* 保留这个导出是为了兼容已有调用方;新逻辑应优先使用 `fetchPackageDistTags()`。
|
|
54
|
+
* @returns {Promise<string|null>} latest 版本号,或失败时 null
|
|
55
|
+
*/
|
|
56
|
+
export async function fetchLatestVersion() {
|
|
57
|
+
const tags = await fetchPackageDistTags();
|
|
58
|
+
return tags?.latest ?? null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 解析项目需要的轻量 semver 版本号。
|
|
63
|
+
*
|
|
64
|
+
* @param {string} version 版本号
|
|
65
|
+
* @returns {{major:number,minor:number,patch:number,prerelease:string[]}|null} 解析结果
|
|
66
|
+
*/
|
|
67
|
+
function parseVersion(version) {
|
|
68
|
+
const match = String(version || "")
|
|
69
|
+
.trim()
|
|
70
|
+
.replace(/^v/, "")
|
|
71
|
+
.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+.*)?$/);
|
|
72
|
+
if (!match) return null;
|
|
73
|
+
return {
|
|
74
|
+
major: Number(match[1]),
|
|
75
|
+
minor: Number(match[2]),
|
|
76
|
+
patch: Number(match[3]),
|
|
77
|
+
prerelease: match[4] ? match[4].split(".") : [],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 判断版本号是否为 prerelease。
|
|
83
|
+
*
|
|
84
|
+
* @param {string} version 版本号
|
|
85
|
+
* @returns {boolean}
|
|
86
|
+
*/
|
|
87
|
+
export function isPrerelease(version) {
|
|
88
|
+
const parsed = parseVersion(version);
|
|
89
|
+
return Boolean(parsed && parsed.prerelease.length);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 比较 prerelease 标识符数组。
|
|
94
|
+
*
|
|
95
|
+
* 同一 base 下,稳定版高于 prerelease;相同 label(如 beta.1 → beta.2)按数值递增比较。
|
|
96
|
+
* 不同 label 之间不做主观排序,返回 0 以避免把 alpha/rc/beta 的跨线比较误判成升级。
|
|
97
|
+
*
|
|
98
|
+
* @param {string[]} aParts A prerelease 标识符
|
|
99
|
+
* @param {string[]} bParts B prerelease 标识符
|
|
100
|
+
* @returns {-1|0|1}
|
|
101
|
+
*/
|
|
102
|
+
function comparePrerelease(aParts, bParts) {
|
|
103
|
+
if (!aParts.length && !bParts.length) return 0;
|
|
104
|
+
if (!aParts.length) return 1;
|
|
105
|
+
if (!bParts.length) return -1;
|
|
106
|
+
if (aParts[0] !== bParts[0]) return 0;
|
|
107
|
+
|
|
108
|
+
const len = Math.max(aParts.length, bParts.length);
|
|
109
|
+
for (let i = 0; i < len; i++) {
|
|
110
|
+
const a = aParts[i];
|
|
111
|
+
const b = bParts[i];
|
|
112
|
+
if (a === undefined) return -1;
|
|
113
|
+
if (b === undefined) return 1;
|
|
114
|
+
if (a === b) continue;
|
|
115
|
+
|
|
116
|
+
const aNum = /^\d+$/.test(a);
|
|
117
|
+
const bNum = /^\d+$/.test(b);
|
|
118
|
+
if (aNum && bNum) {
|
|
119
|
+
const delta = Number(a) - Number(b);
|
|
120
|
+
if (delta !== 0) return delta > 0 ? 1 : -1;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (aNum !== bNum) return aNum ? -1 : 1;
|
|
124
|
+
return a > b ? 1 : -1;
|
|
125
|
+
}
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
47
129
|
/**
|
|
48
130
|
* 比较两个版本号,返回 1 / 0 / -1(a 比 b 新 / 相同 / 旧)。
|
|
49
131
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* 天然不含预发布优先级排序问题。与 src/lib/variant.js「剥 -beta.x 再比数值」的先例一致。
|
|
132
|
+
* 支持 `major.minor.patch` 与项目 beta 通道使用的 `major.minor.patch-beta.n`。
|
|
133
|
+
* 不引 `semver` 依赖,因为这里仅服务启动时的轻量升级提示,失败/不认识时宁可不提示。
|
|
53
134
|
* @param {string} a 版本号 A
|
|
54
135
|
* @param {string} b 版本号 B
|
|
55
136
|
* @returns {-1|0|1}
|
|
56
137
|
*/
|
|
57
138
|
export function compareVersions(a, b) {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
139
|
+
const av = parseVersion(a);
|
|
140
|
+
const bv = parseVersion(b);
|
|
141
|
+
if (!av || !bv) return 0;
|
|
142
|
+
|
|
143
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
144
|
+
if (av[key] !== bv[key]) return av[key] > bv[key] ? 1 : -1;
|
|
145
|
+
}
|
|
146
|
+
return comparePrerelease(av.prerelease, bv.prerelease);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 根据当前版本和 npm dist-tags 生成升级推荐。
|
|
151
|
+
*
|
|
152
|
+
* 稳定版只跟随稳定形态的 latest,避免把稳定用户引导到预发布通道;预发布版同时看
|
|
153
|
+
* 稳定形态的 latest / beta,且 latest 高于当前版本时优先推荐 latest,用于 beta 线被
|
|
154
|
+
* 稳定版追上后的回归稳定。
|
|
155
|
+
*
|
|
156
|
+
* @param {string} current 当前安装的 flower-trellis 版本
|
|
157
|
+
* @param {{latest?:string|null,beta?:string|null}|null} tags npm dist-tags
|
|
158
|
+
* @returns {{version:string,tag:"latest"|"beta",command:string}|null} 升级推荐
|
|
159
|
+
*/
|
|
160
|
+
export function getUpdateRecommendation(current, tags) {
|
|
161
|
+
if (!current || !tags) return null;
|
|
162
|
+
const currentIsPrerelease = isPrerelease(current);
|
|
163
|
+
const latest = typeof tags.latest === "string" ? tags.latest : null;
|
|
164
|
+
const beta = typeof tags.beta === "string" ? tags.beta : null;
|
|
165
|
+
const latestIsStable = latest && !isPrerelease(latest);
|
|
166
|
+
|
|
167
|
+
if (latestIsStable && compareVersions(latest, current) === 1) {
|
|
168
|
+
return { version: latest, tag: "latest", command: `npm i -g ${PKG}@latest` };
|
|
169
|
+
}
|
|
170
|
+
if (currentIsPrerelease && beta && compareVersions(beta, current) === 1) {
|
|
171
|
+
return { version: beta, tag: "beta", command: `npm i -g ${PKG}@beta` };
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
69
174
|
}
|
|
70
175
|
|
|
71
176
|
/**
|
|
@@ -92,10 +197,10 @@ export function isRunningViaNpx() {
|
|
|
92
197
|
*
|
|
93
198
|
* 短路条件(任一命中即跳过,什么都不打印):关闭开关(`--no-update-check` 使
|
|
94
199
|
* `ctx.updateCheck===false`,或环境变量 `FLOWER_NO_UPDATE_CHECK` 非空)、经 npx 运行、
|
|
95
|
-
*
|
|
200
|
+
* 网络探测失败、无升级推荐。
|
|
96
201
|
*
|
|
97
202
|
* 发现新版时的行为:
|
|
98
|
-
* - 交互 TTY:打印通知 → confirm 询问是否升级 →
|
|
203
|
+
* - 交互 TTY:打印通知 → confirm 询问是否升级 → 同意则执行推荐的 npm install 命令;
|
|
99
204
|
* 成功后打印「请重新运行」并 **process.exit(0)**(不做 re-exec 自动重跑),失败则降级为
|
|
100
205
|
* 打印手动升级命令并继续主流程;拒绝则继续主流程。
|
|
101
206
|
* - 非交互(`-y`/`--yes` 或非 TTY):仅打印通知 + 升级命令,不弹确认、不阻塞。
|
|
@@ -110,19 +215,20 @@ export async function checkForUpdate(ctx, commandLabel) {
|
|
|
110
215
|
// 2. npx 本就是最新版,跳过(连通知都不打,避免误导)
|
|
111
216
|
if (isRunningViaNpx()) return;
|
|
112
217
|
|
|
113
|
-
// 3. 尽力而为取
|
|
114
|
-
const
|
|
115
|
-
if (!
|
|
218
|
+
// 3. 尽力而为取 dist-tags;拿不到就静默退出
|
|
219
|
+
const tags = await fetchPackageDistTags();
|
|
220
|
+
if (!tags) return;
|
|
116
221
|
|
|
117
|
-
// 4.
|
|
222
|
+
// 4. 根据本地版本通道生成推荐;无推荐时不打扰
|
|
118
223
|
const current = flowerVersion();
|
|
119
|
-
|
|
224
|
+
const recommendation = getUpdateRecommendation(current, tags);
|
|
225
|
+
if (!recommendation) return;
|
|
120
226
|
|
|
121
227
|
// 5. 打印发现新版本通知(粉色品牌色,与 banner 一致)
|
|
122
228
|
console.log(
|
|
123
229
|
"\n🌸 " +
|
|
124
|
-
chalk.hex("#ff6fb5")(`发现 flower-trellis 新版本 ${chalk.bold(
|
|
125
|
-
chalk.gray(`(当前 ${current})`),
|
|
230
|
+
chalk.hex("#ff6fb5")(`发现 flower-trellis 新版本 ${chalk.bold(recommendation.version)}`) +
|
|
231
|
+
chalk.gray(`(当前 ${current}, 通道 ${recommendation.tag})`),
|
|
126
232
|
);
|
|
127
233
|
|
|
128
234
|
// 6. 非交互(-y/--yes 或非 TTY):仅打印升级命令,不弹确认、不阻塞
|
|
@@ -131,14 +237,14 @@ export async function checkForUpdate(ctx, commandLabel) {
|
|
|
131
237
|
ctx.passthrough.includes("--yes") ||
|
|
132
238
|
!process.stdin.isTTY;
|
|
133
239
|
if (nonInteractive) {
|
|
134
|
-
console.log(` ·
|
|
135
|
-
console.log(
|
|
240
|
+
console.log(` · 升级:${recommendation.command}`);
|
|
241
|
+
console.log(` · 升级后请重跑 ft ${commandLabel},让新版强化包重新叠加到现有项目`);
|
|
136
242
|
return;
|
|
137
243
|
}
|
|
138
244
|
|
|
139
245
|
// 7. 交互:询问是否升级(@inquirer/confirm,返回 boolean)
|
|
140
246
|
const doUpgrade = await confirm({
|
|
141
|
-
message: `是否现在升级到 ${
|
|
247
|
+
message: `是否现在升级到 ${recommendation.version}(${recommendation.tag})?(升级后需重新运行命令)`,
|
|
142
248
|
default: true,
|
|
143
249
|
});
|
|
144
250
|
if (!doUpgrade) {
|
|
@@ -147,18 +253,18 @@ export async function checkForUpdate(ctx, commandLabel) {
|
|
|
147
253
|
}
|
|
148
254
|
|
|
149
255
|
// 8. 执行全局升级。失败(含 EACCES 权限问题、npm 不存在)不自行提权,降级为打印手动命令
|
|
150
|
-
const res = spawnSync("npm", ["i", "-g", `${PKG}
|
|
256
|
+
const res = spawnSync("npm", ["i", "-g", `${PKG}@${recommendation.tag}`], {
|
|
151
257
|
stdio: "inherit",
|
|
152
258
|
shell: process.platform === "win32", // Windows 上 npm 实为 npm.cmd
|
|
153
259
|
});
|
|
154
260
|
if (res.status === 0) {
|
|
155
|
-
console.log(`\n ✓ 已升级到 ${
|
|
261
|
+
console.log(`\n ✓ 已升级到 ${recommendation.version}(${recommendation.tag})`);
|
|
156
262
|
console.log(` · 请重新运行 ft ${commandLabel} 以使用新版本`);
|
|
157
263
|
console.log(" · 强化包随版本更新,升级后可 ft update 重新叠加到现有项目");
|
|
158
264
|
// 当前进程内存里仍是旧代码,必须退出由用户重跑新版本(不做 re-exec,规避权限/平台/进程态坑)
|
|
159
265
|
process.exit(0);
|
|
160
266
|
} else {
|
|
161
|
-
console.log(` ·
|
|
267
|
+
console.log(` · 自动升级失败,请手动运行:${recommendation.command}`);
|
|
162
268
|
// 升级未成功:以当前版本继续 init/update,不阻断
|
|
163
269
|
}
|
|
164
270
|
}
|