flower-trellis 0.1.0 → 0.2.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 flower-ai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,113 +1,130 @@
1
- # 🌸 flower-trellis
1
+ # flower-trellis
2
2
 
3
- > 一条命令装好 [Trellis](https://docs.trytrellis.app/) 工程框架,并自动融合 **skill-garden** 的强化包。
3
+ [![npm version](https://img.shields.io/npm/v/flower-trellis.svg)](https://www.npmjs.com/package/flower-trellis)
4
+ [![node](https://img.shields.io/node/v/flower-trellis.svg)](https://nodejs.org)
5
+ [![license](https://img.shields.io/npm/l/flower-trellis.svg)](./LICENSE)
4
6
 
5
- ## 这是什么
7
+ > 一条命令装好 [Trellis](https://docs.trytrellis.app/) 工程框架,并自动融合 **skill-garden** 强化包。
6
8
 
7
- `flower-trellis` 是一个 Node CLI(npm ),把原本要分两步做的事合并成一键完成:
9
+ `flower-trellis` Trellis 的 npm 封装 CLI,把原本要分两步的工作合并为一键完成:安装/升级 Trellis 本体,并在其上叠加 skill-garden 的强化包(一组 `trellis-*` 技能与 workflow override)。强化包以快照形式随包发布,安装过程**零网络依赖**。
8
10
 
9
- 1. **装/升级 Trellis 本体** —— [trytrellis.app](https://docs.trytrellis.app/) 出品的 AI 编程工程框架,
10
- 把 specs / tasks / memory 持久化进你的仓库,让任意 AI 编程 Agent 都遵循你的工程规范。
11
- 底层调用官方 `@mindfoldhq/trellis` 的 `init` / `update`。
12
- 2. **叠加强化包** —— 自动套用 skill-garden 的强化补充包
13
- (按目标项目的 Trellis 版本智能选择 `old / 0.5 / 0.6` variant),
14
- 内含一组 `trellis-*` 技能与 workflow override。强化包随本包发布,安装零网络。
11
+ 底层调用官方 `@mindfoldhq/trellis` `init` / `update`,并按目标项目的 Trellis 版本自动选择匹配的强化包变体(`old` / `0.5` / `0.6`)。
15
12
 
16
- > **命名由来**:取自 skill-garden 的「园艺」主题 —— trellis 是花园里供藤蔓攀爬的棚架,
17
- > `flower-trellis` 即「开满花的棚架」:框架装好、强化包到位,即开即用。
13
+ > **命名由来**:`flower-` 是这一系列 AI 工程工具的统一前缀(flower 系列),`trellis` 表示本工具负责包装 Trellis 框架。
18
14
 
19
- ## 用法
15
+ ## 安装
20
16
 
21
17
  ```bash
22
- # 交互安装:flower 平台菜单(默认勾 codex + claude)→ Trellis 原生模板/monorepo 菜单
23
- npx flower-trellis init -u your-name
18
+ # 全局安装(推荐),之后可用 flower-trellis 或简写 ftl / ft
19
+ npm i -g flower-trellis
24
20
 
25
- # 指定平台(透传 trellis,跳过平台菜单),例如只装 claude
26
- npx flower-trellis init -u your-name --claude
21
+ # 升级到最新版
22
+ npm i -g flower-trellis@latest
23
+ ```
27
24
 
28
- # 完全非交互(平台默认 codex + claude;模板用空白默认)
29
- npx flower-trellis init -u your-name -y
25
+ 也可**免安装**通过 `npx flower-trellis <命令>` 直接运行(每次执行拉取最新版,适合临时试用)
30
26
 
31
- # 升级 Trellis 并按新版本重新套用强化包
32
- npx flower-trellis update
27
+ **环境要求**:Node.js 18.17.0。
33
28
 
34
- # 卸载:移除 Trellis 本体并清理强化包残留
35
- npx flower-trellis uninstall
29
+ ## 用法
30
+
31
+ ```bash
32
+ # 交互安装:平台多选菜单 → Trellis 原生模板/monorepo 菜单
33
+ flower-trellis init -u <your-name>
36
34
 
37
- # 只装 Trellis 不叠加强化包 / 已有项目只重叠加
38
- npx flower-trellis init --no-enhance
39
- npx flower-trellis init --enhance-only
35
+ # 指定平台,跳过平台菜单(透传给 trellis)
36
+ flower-trellis init -u <your-name> --claude
40
37
 
41
- # 其它 trellis 子命令一律透传(面向未来,零维护)
42
- npx flower-trellis <任意 trellis 命令>
38
+ # 完全非交互(平台默认 codex + claude)
39
+ flower-trellis init -u <your-name> -y
40
+
41
+ # 升级 Trellis 并按新版本重新叠加强化包
42
+ flower-trellis update
43
+
44
+ # 卸载:移除 Trellis 本体并清理强化包残留
45
+ flower-trellis uninstall
43
46
 
44
47
  # 查看版本(flower-trellis 自身 + 捆绑的 Trellis)
45
- npx flower-trellis -v
48
+ flower-trellis -v
46
49
  ```
47
50
 
48
- ## 交互流程(`init`)
51
+ > 已全局安装时可直接写 `flower-trellis`、`ftl` 或 `ft`(三者等价);未安装则在命令前加 `npx`。
49
52
 
50
- ```
51
- 🌸 FLOWER 品牌头部(ASCII logo + 👤 Developer)
52
-
53
- ? 选择 AI 工具(flower 菜单,默认勾 Claude Code + Codex)
54
-
55
- ? Select a spec template / monorepo 识别 …(Trellis 原生交互,原样保留)
56
-
57
- 叠加 skill-garden 强化包 + codex 后处理 → 完成
58
- ```
53
+ ### 命令
59
54
 
60
- 全程**只有 flower 一个 banner**:Trellis 子进程在伪终端(node-pty)里运行,
61
- 它原生的模板 / monorepo / 冲突等交互完整保留,而它重复打印的启动 banner / Developer 被过滤掉。
55
+ | 命令 | 说明 |
56
+ |------|------|
57
+ | `init` | 安装 Trellis 并叠加强化包(默认命令,裸跑等同 `init`) |
58
+ | `update` | 升级 Trellis,并按新版本重新叠加强化包 |
59
+ | `uninstall` | 移除 Trellis 本体并清理强化包残留(支持 `-y` / `--dry-run`) |
60
+ | `<其它命令>` | 原样透传给 Trellis,覆盖其现有及未来子命令 |
61
+ | `-v` / `-h` | 打印版本 / 帮助 |
62
+
63
+ ### 选项
64
+
65
+ | 选项 | 说明 |
66
+ |------|------|
67
+ | `--no-enhance` | 只安装 Trellis,不叠加强化包 |
68
+ | `--enhance-only` | 跳过 Trellis,仅叠加强化包(用于已有项目) |
69
+ | `--skills <a,b,...>` | 只安装指定技能(可省略 `trellis-` 前缀) |
70
+ | `--variant <old\|0.5\|0.6>` | 强制指定强化包变体(默认按 `.trellis/.version` 自动选) |
71
+ | `--target <dir>` | 目标目录(默认当前目录) |
72
+ | `--no-update-check` | 本次跳过 flower-trellis 新版本检测(等价环境变量 `FLOWER_NO_UPDATE_CHECK=1`) |
62
73
 
63
- ## 状态
74
+ 未指定平台时,交互模式会弹出多选菜单(默认勾选 Claude Code + Codex);也可直接传 `--claude` / `--codex` / `--cursor` 等指定,或用 `-y` 跳过菜单。其余未识别的 flag(如 `-u`、`-f`、`--template`)一律透传给 Trellis。
64
75
 
65
- **可用** —— 核心功能已实现并端到端验证。当前捆绑 Trellis `0.6.0-beta.8`。
76
+ ### 自动版本检测
66
77
 
67
- ## 功能
78
+ 运行 `init` / `update` 时,flower-trellis 会顺带检测**自身**在 npm 上是否有新版本:
68
79
 
69
- - [x] `init` / `update` / `uninstall`,其它 trellis 子命令兜底透传
70
- - [x] flower 品牌 banner(`init` 交互 + `update`)
71
- - [x] flower 平台多选菜单,默认勾 **codex + claude**;或 `-y` 用默认、传 `--claude/--codex/--cursor` 等指定
72
- - [x] **node-pty** 在伪终端运行 trellis,完整保留其模板 / monorepo / 冲突等原生交互,同时过滤掉重复 banner / Developer
73
- - [x] 自动识别 Trellis 版本,选择匹配的强化包 variant(`old / 0.5 / 0.6`)
74
- - [x] 强化 skill 跟随平台铺设(claude→`.claude/skills`,codex/gemini 等→`.agents/skills`)
75
- - [x] codex 后处理:注释 `config.toml` 的 `[features.multi_agent_v2]`、补全 `hooks.json` 的 `SessionStart`
76
- - [x] workflow override 幂等注入(先清旧块再注入 + 备份 `.bak`)
77
- - [x] 升级时清理过期强化项(`0.5`/`old` → `0.6` 删除淘汰的 skill/command,基于 flower manifest,只删自己铺过的)
78
- - [x] `Ctrl+C` 安全中止(取消时不会继续叠加)
79
- - [x] `-v` 同时打印 flower-trellis 与捆绑 Trellis 版本
80
- - [x] 幂等执行:重复运行安全
80
+ - **联网、尽力而为**:带 2.5s 超时,离线 / 超时 / 失败一律静默跳过,绝不阻断安装/升级主流程。
81
+ - **发现新版**(交互终端):提示并询问是否立即升级;同意则执行 `npm i -g flower-trellis@latest`,成功后请按提示重新运行命令(升级后强化包随新版更新,可再跑一次 `ft update` 重新叠加)
82
+ - **非交互**(`-y` 或非 TTY):仅打印一行升级提示,不弹确认、不阻塞。
83
+ - **跳过检测**:经 `npx` 运行(本就是最新版)、或显式 `--no-update-check` / `FLOWER_NO_UPDATE_CHECK=1` 时不检测。
81
84
 
82
- ## 强化包与更新机制
85
+ ## 工作原理
83
86
 
84
- 强化包以**快照**形式打包在本仓库 `enhancements/`(由 `npm run sync` 从 skill-garden 同步),随 npm 发布,安装零网络。因此:
87
+ `init` 的执行流程:
85
88
 
86
- - **Trellis 本体**:`update` 实时升级(trellis 自己拉最新)。
87
- - **强化包**:用当前安装的 flower-trellis 版本里的那份快照。要跟上 skill-garden 后续迭代,流程为:
88
- `skill-garden 改动 → npm run sync + 发新版 → npm i -g flower-trellis@latest → flower-trellis update`。
89
+ ```
90
+ flower banner 平台多选菜单 Trellis 原生交互(模板 / monorepo / 冲突)→ 叠加强化包 → codex 后处理
91
+ ```
89
92
 
90
- **重复安装 / 升级的正确性**(已实测,不会出现重复块或残留):
93
+ - **统一品牌头部**:Trellis 子进程在伪终端(`node-pty`)中运行,其原生的模板 / monorepo / 冲突等交互完整保留,但重复打印的启动 banner 被过滤,全程只呈现一个 flower banner。
94
+ - **按平台铺设技能**:Claude 铺到 `.claude/skills`,Codex / Gemini 等铺到 `.agents/skills`;并对 codex 做后处理(注释 `config.toml` 的 `[features.multi_agent_v2]`、补全 `hooks.json` 的 `SessionStart`)。
95
+ - **幂等执行**:`workflow.md` 注入前先按 `BEGIN/END` 标记清除旧块再重注入(块数恒定,不会翻倍,首次注入前备份 `.bak`);技能文件覆盖式铺设,并通过 `.trellis/.flower-manifest.json` 记录已铺路径,升级时删除已淘汰项。
96
+ - **安全中止**:`Ctrl+C` 取消后不会继续叠加。
91
97
 
92
- 1. **skill 文件**:覆盖式铺设 + `.trellis/.flower-manifest.json` 记录上次铺过的路径,删除本次不再包含的项。
93
- 2. **`workflow.md`**:每次注入前先 strip 掉所有旧的 skill-garden 块(按 `BEGIN/END` 标记整段删),再重注入 —— 块数恒定、绝不翻倍;首次注入前备份 `.bak`。
94
- 3. **升级清理**:跨 variant / 跨快照版本删除淘汰的 skill / command。
98
+ ## 强化包与更新
95
99
 
96
- > ⚠️ **维护约束**:`workflow.md` strip 依赖 `src/lib/workflow-inject.js` 里硬编码的 sentinel 名单。改现有块的**内容**无需动名单;但 skill-garden **新增一种 workflow 块类型**(新的 `BEGIN/END` 名)时,必须同步更新该名单,否则旧块清不掉。
100
+ 强化包以**快照**形式打包在 `enhancements/`(由 `npm run sync` skill-garden 同步),随 npm 发布。因此两者更新节奏不同:
101
+
102
+ - **Trellis 本体**:`update` 实时升级(由 Trellis 自身拉取最新)。
103
+ - **强化包**:使用当前安装版本内置的快照。要跟进 skill-garden 的迭代,需升级 flower-trellis 本身:
104
+
105
+ ```bash
106
+ npm i -g flower-trellis@latest && flower-trellis update
107
+ ```
97
108
 
98
109
  ## 开发
99
110
 
100
111
  ```bash
101
- npm install # 安装依赖
102
- npm run sync # 从 skill-garden 同步强化包快照到 enhancements/
103
- node bin/flower-trellis.js init -u you --target /某测试目录 # 本地试跑(勿在本仓库根直接 init)
112
+ npm install # 安装依赖
113
+ npm run sync # 从 skill-garden 同步强化包快照到 enhancements/
114
+ node bin/flower-trellis.js init -u you --target /tmp/test-project # 本地试跑(勿在本仓库根直接 init)
104
115
  ```
105
116
 
106
- 运行时依赖:`@mindfoldhq/trellis`(捆绑的 Trellis 本体)、`node-pty`(伪终端,保留 trellis 交互)、`inquirer`(平台菜单)、`figlet` + `chalk`(banner)。
117
+ 修改强化包后务必重新 `npm run sync`,再发布新版本。
118
+
119
+ > **维护约束**:`workflow.md` 的旧块清理依赖 `src/lib/workflow-inject.js` 中硬编码的 sentinel 名单。修改现有块的内容无需改动名单;但当 skill-garden **新增一种 workflow 块类型**(新的 `BEGIN/END` 名)时,必须同步更新该名单,否则旧块无法被清除。
107
120
 
108
121
  ## 相关项目
109
122
 
110
123
  | 项目 | 作用 |
111
124
  |------|------|
112
- | [Trellis](https://docs.trytrellis.app/)(`@mindfoldhq/trellis`) | AI 编程工程框架本体,本包作为 wrapper 调用其 `init`/`update`/`uninstall` |
113
- | skill-garden | 强化补充包的来源(`.trellis/` `old/0.5/0.6` variant) |
125
+ | [Trellis](https://docs.trytrellis.app/)(`@mindfoldhq/trellis`) | AI 编程工程框架本体,本包作为 wrapper 调用其 `init` / `update` / `uninstall` |
126
+ | skill-garden | 强化包来源,提供 `old` / `0.5` / `0.6` 各变体 |
127
+
128
+ ## 许可证
129
+
130
+ [MIT](./LICENSE)
File without changes
@@ -1,5 +1,5 @@
1
1
  {
2
- "syncedAt": "2026-06-08T12:58:29.928Z",
2
+ "syncedAt": "2026-06-08T15:47:02.818Z",
3
3
  "syncedFrom": "/root/project/skill-garden/.trellis",
4
4
  "sourceCommit": "824727cb279202c65bb653529fc84f5e135c25e2",
5
5
  "variants": {
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "flower-trellis",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "一键安装/升级 Trellis 并自动融合 skill-garden 强化包(默认 Claude + agents)",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "flower-trellis": "./bin/flower-trellis.js",
8
- "ftl": "./bin/flower-trellis.js"
8
+ "ftl": "./bin/flower-trellis.js",
9
+ "ft": "./bin/flower-trellis.js"
9
10
  },
10
11
  "engines": {
11
12
  "node": ">=18.17.0"
package/src/cli.js CHANGED
@@ -42,6 +42,10 @@ flower 自有 flag:
42
42
  --skills <a,b,...> 只装指定技能(支持去 trellis- 前缀匹配)
43
43
  --variant <old|0.5|0.6> 强制强化包变体(默认按 .trellis/.version 自动选)
44
44
  --target <dir> 目标目录(默认当前目录)
45
+ --no-update-check 本次跳过 flower-trellis 新版本检测(等价 FLOWER_NO_UPDATE_CHECK=1)
46
+
47
+ 命令别名:flower-trellis 可简写为 ftl 或 ft(三者完全等价)。
48
+ init / update 启动时会顺带检测 flower-trellis 自身是否有新版(联网、带超时,失败静默)。
45
49
 
46
50
  平台选择:未指定平台时,交互模式会弹出多选菜单(默认勾 Claude Code + Codex);
47
51
  也可直接传 --claude / --codex / --cursor 等指定,或用 -y 跳过菜单(默认 codex + claude)。
@@ -55,6 +59,7 @@ function parse(argv) {
55
59
  let enhanceOnly = false;
56
60
  let variant = null;
57
61
  let target = process.cwd();
62
+ let updateCheck = true;
58
63
  const skills = [];
59
64
  const passthrough = [];
60
65
 
@@ -83,6 +88,9 @@ function parse(argv) {
83
88
  case "--target":
84
89
  target = path.resolve(argv[++i] || ".");
85
90
  break;
91
+ case "--no-update-check":
92
+ updateCheck = false;
93
+ break;
86
94
  default:
87
95
  passthrough.push(a);
88
96
  }
@@ -97,6 +105,7 @@ function parse(argv) {
97
105
  enhanceOnly,
98
106
  skills,
99
107
  variant,
108
+ updateCheck,
100
109
  },
101
110
  };
102
111
  }
@@ -3,6 +3,7 @@ import { runTrellisPty } from "../lib/trellis-runner.js";
3
3
  import { applyEnhancements } from "../lib/apply-enhancements.js";
4
4
  import { pickPlatforms } from "../lib/pick-platforms.js";
5
5
  import { printBanner, getDeveloper } from "../lib/banner.js";
6
+ import { checkForUpdate } from "../lib/update-check.js";
6
7
  import { PLATFORM_FLAGS } from "../constants.js";
7
8
 
8
9
  /**
@@ -32,6 +33,10 @@ export async function init(ctx) {
32
33
  printBanner(getDeveloper(passthrough, target));
33
34
  }
34
35
 
36
+ // 主操作前尽力而为地检测 flower-trellis 自身新版本(失败静默;用户确认升级成功会直接退出)
37
+ // 放在平台菜单之前:若用户选择升级,不必先让他挑完平台再退出做无用功
38
+ await checkForUpdate(ctx, "init");
39
+
35
40
  if (!hasPlatform) {
36
41
  if (nonInteractive) {
37
42
  passthrough.push("--codex", "--claude");
@@ -1,6 +1,7 @@
1
1
  import { runTrellisPty } from "../lib/trellis-runner.js";
2
2
  import { applyEnhancements } from "../lib/apply-enhancements.js";
3
3
  import { printBanner, getDeveloper } from "../lib/banner.js";
4
+ import { checkForUpdate } from "../lib/update-check.js";
4
5
 
5
6
  /**
6
7
  * flower-trellis update:驱动 `trellis update`,随后按(可能已升级的)版本重新叠加强化包。
@@ -17,6 +18,9 @@ export async function update(ctx) {
17
18
 
18
19
  printBanner(getDeveloper(ctx.passthrough, target));
19
20
 
21
+ // 主操作前尽力而为地检测 flower-trellis 自身新版本(失败静默;用户确认升级成功会直接退出)
22
+ await checkForUpdate(ctx, "update");
23
+
20
24
  if (!ctx.enhanceOnly) {
21
25
  const code = await runTrellisPty(["update", ...ctx.passthrough], target, {
22
26
  stripBanner: true,
package/src/constants.js CHANGED
@@ -47,4 +47,5 @@ export const OWN_FLAGS = {
47
47
  "--skills": true,
48
48
  "--variant": true,
49
49
  "--target": true,
50
+ "--no-update-check": false,
50
51
  };
@@ -0,0 +1,168 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { fileURLToPath } from "node:url";
3
+ import inquirer from "inquirer";
4
+ import chalk from "chalk";
5
+ import { flowerVersion } from "./versions.js";
6
+
7
+ /**
8
+ * 版本自动检测 —— 在 init / update 启动时尽力而为地比对 npm 上 flower-trellis 自身的
9
+ * 最新版本,发现新版时提示用户并(交互场景下)询问是否立即升级。
10
+ *
11
+ * 设计基调与本项目一致:**绝不阻断主流程**。网络探测带超时,离线/超时/失败一律静默跳过;
12
+ * 仅在「全局直跑 + 有新版」时才打扰用户。详见 design.md / research/version-check-conventions.md。
13
+ */
14
+
15
+ /** npm registry 根地址。 */
16
+ const REGISTRY = "https://registry.npmjs.org";
17
+ /** 待检测的包名(即本包)。 */
18
+ const PKG = "flower-trellis";
19
+ /** 网络探测超时(毫秒)—— init/update 是重操作,2.5s 探测可接受且不显著影响体感。 */
20
+ const TIMEOUT_MS = 2500;
21
+
22
+ /**
23
+ * 取 npm 上 flower-trellis 的 latest 版本号;任何失败(离线/超时/非 200/解析异常)一律
24
+ * 返回 null —— 调用方据此「拿不到就当没这回事」继续主流程。
25
+ *
26
+ * 用 AbortController 给内置 fetch 加超时,finally 清除定时器防句柄泄漏。
27
+ * @returns {Promise<string|null>} latest 版本号,或失败时 null
28
+ */
29
+ export async function fetchLatestVersion() {
30
+ const ac = new AbortController();
31
+ const timer = setTimeout(() => ac.abort(), TIMEOUT_MS);
32
+ try {
33
+ const res = await fetch(`${REGISTRY}/${PKG}/latest`, {
34
+ signal: ac.signal,
35
+ headers: { Accept: "application/json" },
36
+ });
37
+ if (!res.ok) return null; // 非 200(404/5xx 等)→ 静默跳过
38
+ const json = await res.json();
39
+ return typeof json.version === "string" ? json.version : null;
40
+ } catch {
41
+ return null; // AbortError(超时)/ fetch failed(离线)/ JSON 解析失败 → 静默
42
+ } finally {
43
+ clearTimeout(timer);
44
+ }
45
+ }
46
+
47
+ /**
48
+ * 比较两个版本号,返回 1 / 0 / -1(a 比 b 新 / 相同 / 旧)。
49
+ *
50
+ * 只比较 major.minor.patch 三段数值;预发布后缀(-beta.x 等)在拆分前剥除。
51
+ * 之所以不引 semver 库:比较对象是 npm `latest` dist-tag(按语义永远指向稳定发布),
52
+ * 天然不含预发布优先级排序问题。与 src/lib/variant.js「剥 -beta.x 再比数值」的先例一致。
53
+ * @param {string} a 版本号 A
54
+ * @param {string} b 版本号 B
55
+ * @returns {-1|0|1}
56
+ */
57
+ export function compareVersions(a, b) {
58
+ const norm = (v) =>
59
+ String(v)
60
+ .split("-")[0]
61
+ .split(".")
62
+ .map((n) => parseInt(n, 10) || 0);
63
+ const [a1, a2, a3] = norm(a);
64
+ const [b1, b2, b3] = norm(b);
65
+ if (a1 !== b1) return a1 > b1 ? 1 : -1;
66
+ if (a2 !== b2) return a2 > b2 ? 1 : -1;
67
+ if (a3 !== b3) return a3 > b3 ? 1 : -1;
68
+ return 0;
69
+ }
70
+
71
+ /**
72
+ * 判断当前是否经 npx 执行。
73
+ *
74
+ * 经 npx 跑的永远是临时拉取的最新版,检测无意义,应跳过。最可靠的信号是执行路径里含
75
+ * npx 缓存目录标记 `_npx`(`~/.npm/_npx/<hash>/...`,跨平台一致);辅以 `npm_command==='exec'`
76
+ * (`npm exec` / npx)。全局安装直跑(flower-trellis / ftl / ft)时两者皆否。
77
+ * @returns {boolean}
78
+ */
79
+ export function isRunningViaNpx() {
80
+ try {
81
+ const selfPath = fileURLToPath(import.meta.url);
82
+ if (selfPath.includes("_npx")) return true;
83
+ } catch {
84
+ // 路径解析失败不应影响主流程;退而判 npm_command
85
+ }
86
+ return process.env.npm_command === "exec";
87
+ }
88
+
89
+ /**
90
+ * 检测 flower-trellis 自身是否有新版本,并按场景提示/引导升级。在 init / update 的
91
+ * 品牌头部之后、主操作之前调用。
92
+ *
93
+ * 短路条件(任一命中即跳过,什么都不打印):关闭开关(`--no-update-check` 使
94
+ * `ctx.updateCheck===false`,或环境变量 `FLOWER_NO_UPDATE_CHECK` 非空)、经 npx 运行、
95
+ * 网络探测失败、已是最新或本地更高。
96
+ *
97
+ * 发现新版时的行为:
98
+ * - 交互 TTY:打印通知 → inquirer 询问是否升级 → 同意则执行 `npm i -g flower-trellis@latest`;
99
+ * 成功后打印「请重新运行」并 **process.exit(0)**(不做 re-exec 自动重跑),失败则降级为
100
+ * 打印手动升级命令并继续主流程;拒绝则继续主流程。
101
+ * - 非交互(`-y`/`--yes` 或非 TTY):仅打印通知 + 升级命令,不弹确认、不阻塞。
102
+ *
103
+ * @param {object} ctx cli.js parse() 产出的上下文(用到 updateCheck / passthrough)
104
+ * @param {string} commandLabel 当前命令名,用于「请重新运行 ft <command>」文案(如 "init"/"update")
105
+ * @returns {Promise<void>} 注意:用户确认升级且成功时本函数会直接退出进程,不返回
106
+ */
107
+ export async function checkForUpdate(ctx, commandLabel) {
108
+ // 1. 关闭开关:显式 flag 或环境变量
109
+ if (ctx.updateCheck === false || process.env.FLOWER_NO_UPDATE_CHECK) return;
110
+ // 2. npx 本就是最新版,跳过(连通知都不打,避免误导)
111
+ if (isRunningViaNpx()) return;
112
+
113
+ // 3. 尽力而为取 latest;拿不到就静默退出
114
+ const latest = await fetchLatestVersion();
115
+ if (!latest) return;
116
+
117
+ // 4. 与本地版本比较;仅当 latest 严格更新时才打扰
118
+ const current = flowerVersion();
119
+ if (compareVersions(latest, current) !== 1) return;
120
+
121
+ // 5. 打印发现新版本通知(粉色品牌色,与 banner 一致)
122
+ console.log(
123
+ "\n🌸 " +
124
+ chalk.hex("#ff6fb5")(`发现 flower-trellis 新版本 ${chalk.bold(latest)}`) +
125
+ chalk.gray(`(当前 ${current})`),
126
+ );
127
+
128
+ // 6. 非交互(-y/--yes 或非 TTY):仅打印升级命令,不弹确认、不阻塞
129
+ const nonInteractive =
130
+ ctx.passthrough.includes("-y") ||
131
+ ctx.passthrough.includes("--yes") ||
132
+ !process.stdin.isTTY;
133
+ if (nonInteractive) {
134
+ console.log(` · 升级:npm i -g ${PKG}@latest`);
135
+ console.log(" · 升级后请重跑 ft update,让新版强化包重新叠加到现有项目");
136
+ return;
137
+ }
138
+
139
+ // 7. 交互:询问是否升级
140
+ const { doUpgrade } = await inquirer.prompt([
141
+ {
142
+ type: "confirm",
143
+ name: "doUpgrade",
144
+ message: `是否现在升级到 ${latest}?(升级后需重新运行命令)`,
145
+ default: true,
146
+ },
147
+ ]);
148
+ if (!doUpgrade) {
149
+ console.log(" · 已跳过升级");
150
+ return;
151
+ }
152
+
153
+ // 8. 执行全局升级。失败(含 EACCES 权限问题、npm 不存在)不自行提权,降级为打印手动命令
154
+ const res = spawnSync("npm", ["i", "-g", `${PKG}@latest`], {
155
+ stdio: "inherit",
156
+ shell: process.platform === "win32", // Windows 上 npm 实为 npm.cmd
157
+ });
158
+ if (res.status === 0) {
159
+ console.log(`\n ✓ 已升级到 ${latest}`);
160
+ console.log(` · 请重新运行 ft ${commandLabel} 以使用新版本`);
161
+ console.log(" · 强化包随版本更新,升级后可 ft update 重新叠加到现有项目");
162
+ // 当前进程内存里仍是旧代码,必须退出由用户重跑新版本(不做 re-exec,规避权限/平台/进程态坑)
163
+ process.exit(0);
164
+ } else {
165
+ console.log(` · 自动升级失败,请手动运行:npm i -g ${PKG}@latest`);
166
+ // 升级未成功:以当前版本继续 init/update,不阻断
167
+ }
168
+ }