flower-trellis 0.1.0 → 0.2.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/LICENSE +21 -0
- package/README.md +89 -72
- package/bin/flower-trellis.js +0 -0
- package/enhancements/0.6/overrides/workflow-states/no_task.md +2 -0
- package/enhancements/0.6/overrides/workflow.md +7 -5
- package/enhancements/MANIFEST.json +2 -2
- package/package.json +3 -2
- package/src/cli.js +9 -0
- package/src/commands/init.js +5 -0
- package/src/commands/update.js +4 -0
- package/src/constants.js +1 -0
- package/src/lib/update-check.js +168 -0
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
|
-
#
|
|
1
|
+
# flower-trellis
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/flower-trellis)
|
|
4
|
+
[](https://nodejs.org)
|
|
5
|
+
[](./LICENSE)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
> 一条命令装好 [Trellis](https://docs.trytrellis.app/) 工程框架,并自动融合 **skill-garden** 强化包。
|
|
6
8
|
|
|
7
|
-
`flower-trellis`
|
|
9
|
+
`flower-trellis` 是 Trellis 的 npm 封装 CLI,把原本要分两步的工作合并为一键完成:安装/升级 Trellis 本体,并在其上叠加 skill-garden 的强化包(一组 `trellis-*` 技能与 workflow override)。强化包以快照形式随包发布,安装过程**零网络依赖**。
|
|
8
10
|
|
|
9
|
-
|
|
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
|
-
>
|
|
17
|
-
> `flower-trellis` 即「开满花的棚架」:框架装好、强化包到位,即开即用。
|
|
13
|
+
> **命名由来**:`flower-` 是这一系列 AI 工程工具的统一前缀(flower 系列),`trellis` 表示本工具负责包装 Trellis 框架。
|
|
18
14
|
|
|
19
|
-
##
|
|
15
|
+
## 安装
|
|
20
16
|
|
|
21
17
|
```bash
|
|
22
|
-
#
|
|
23
|
-
|
|
18
|
+
# 全局安装(推荐),之后可用 flower-trellis 或简写 ftl / ft
|
|
19
|
+
npm i -g flower-trellis
|
|
24
20
|
|
|
25
|
-
#
|
|
26
|
-
|
|
21
|
+
# 升级到最新版
|
|
22
|
+
npm i -g flower-trellis@latest
|
|
23
|
+
```
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
npx flower-trellis init -u your-name -y
|
|
25
|
+
也可**免安装**通过 `npx flower-trellis <命令>` 直接运行(每次执行拉取最新版,适合临时试用)。
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
npx flower-trellis update
|
|
27
|
+
**环境要求**:Node.js ≥ 18.17.0。
|
|
33
28
|
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
## 用法
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# 交互安装:平台多选菜单 → Trellis 原生模板/monorepo 菜单
|
|
33
|
+
flower-trellis init -u <your-name>
|
|
36
34
|
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
npx flower-trellis init --enhance-only
|
|
35
|
+
# 指定平台,跳过平台菜单(透传给 trellis)
|
|
36
|
+
flower-trellis init -u <your-name> --claude
|
|
40
37
|
|
|
41
|
-
#
|
|
42
|
-
|
|
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
|
-
|
|
48
|
+
flower-trellis -v
|
|
46
49
|
```
|
|
47
50
|
|
|
48
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
76
|
+
### 自动版本检测
|
|
66
77
|
|
|
67
|
-
|
|
78
|
+
运行 `init` / `update` 时,flower-trellis 会顺带检测**自身**在 npm 上是否有新版本:
|
|
68
79
|
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
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
|
-
|
|
87
|
+
`init` 的执行流程:
|
|
85
88
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
93
|
-
2. **`workflow.md`**:每次注入前先 strip 掉所有旧的 skill-garden 块(按 `BEGIN/END` 标记整段删),再重注入 —— 块数恒定、绝不翻倍;首次注入前备份 `.bak`。
|
|
94
|
-
3. **升级清理**:跨 variant / 跨快照版本删除淘汰的 skill / command。
|
|
98
|
+
## 强化包与更新
|
|
95
99
|
|
|
96
|
-
|
|
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
|
|
103
|
-
node bin/flower-trellis.js init -u you --target
|
|
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
|
-
|
|
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
|
|
113
|
-
| skill-garden |
|
|
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)
|
package/bin/flower-trellis.js
CHANGED
|
File without changes
|
|
@@ -3,4 +3,6 @@ HIGHEST PRIORITY SKILL-GARDEN STATE GUARD (no_task):
|
|
|
3
3
|
Creating/resuming a task is not implementation permission.
|
|
4
4
|
After PRD ready and task started, next implementation action = `trellis-route(implement)`.
|
|
5
5
|
If no active task exists, scan `.trellis/tasks/*/task.json` once per session for in-progress tasks with `last_push_snapshot`; surface completed_steps + next_step and suggest rebinding the active task before resuming.
|
|
6
|
+
Do NOT use the harness built-in plan mode (`EnterPlanMode` / `ExitPlanMode`) as a substitute for this gate. Planning is Trellis-only: classify the turn, ask for task-creation consent, then `trellis-brainstorm` for complex work.
|
|
7
|
+
If the turn is a meta edit to Trellis itself (Trellis tracking would be overkill), say so and ask to skip Trellis — never silently swap built-in plan mode in for the consent gate.
|
|
6
8
|
<!-- END skill-garden workflow-state no_task v0.6 -->
|
|
@@ -29,18 +29,20 @@ Check routing has no 4h preference file. Before `trellis-check`, `trellis-check-
|
|
|
29
29
|
|
|
30
30
|
#### Finish-work Bookkeeping Guard
|
|
31
31
|
|
|
32
|
-
`session_auto_commit`
|
|
32
|
+
`session_auto_commit` governs ONLY the script-managed bookkeeping commits that `task.py archive` and `add_session.py` make for their own files (`.trellis/tasks/**` archive moves and `.trellis/workspace/**` journal/index). It is NOT a global auto-commit switch, and it has NO authority over code commits.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
Code commits always belong to Phase 3.4: the agent drafts the batched commit plan and commits only after the user confirms — in EVERY case, whether `session_auto_commit` is `true` or `false`. Never treat `session_auto_commit: true` as permission to auto-commit code, to skip the Phase 3.4 confirmation prompt, or to commit any path outside the two scripts' own bookkeeping files. The two are independent: this switch never decides whether code is committed, and it never decides whether to ask first.
|
|
35
|
+
|
|
36
|
+
When `session_auto_commit: true` (the default): `task.py archive` produces a `chore(task): archive ...` commit and `add_session.py` produces a `chore: record journal` commit, each touching only its own bookkeeping files. Code stays dirty for the Phase 3.4 plan.
|
|
37
|
+
|
|
38
|
+
When `.trellis/config.yaml` sets `session_auto_commit: false`, finish-work must treat archive and journal writes as disk-only bookkeeping:
|
|
35
39
|
|
|
36
40
|
- Running `python3 ./.trellis/scripts/task.py archive <task>` may move task files, but must not be described as producing a `chore(task): archive ...` commit.
|
|
37
41
|
- Running `python3 ./.trellis/scripts/add_session.py ...` may write workspace journal/index files, but must not be described as producing a `chore: record journal` commit.
|
|
38
42
|
- Do not run a compensating `git add` / `git commit` for `.trellis/tasks/**` or `.trellis/workspace/**` just because those scripts skipped auto-commit.
|
|
39
43
|
- Report the resulting `.trellis/tasks/**` and `.trellis/workspace/**` dirty paths to the user as bookkeeping changes for manual review.
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
This guard only prevents archive/journal bookkeeping commits from being forced when `session_auto_commit: false`.
|
|
45
|
+
This guard scopes `session_auto_commit` to the two bookkeeping commits above; Phase 3.4 code-work commits stay confirmation-gated regardless of its value.
|
|
44
46
|
|
|
45
47
|
#### Push Progress Recovery / Snapshot
|
|
46
48
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"syncedAt": "2026-06-
|
|
2
|
+
"syncedAt": "2026-06-08T16:26:44.336Z",
|
|
3
3
|
"syncedFrom": "/root/project/skill-garden/.trellis",
|
|
4
|
-
"sourceCommit": "
|
|
4
|
+
"sourceCommit": "1dcf26869e7c90be0777ebe3448501f340e05b45",
|
|
5
5
|
"variants": {
|
|
6
6
|
"old": {
|
|
7
7
|
"claudeSkills": [],
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flower-trellis",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
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
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -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");
|
package/src/commands/update.js
CHANGED
|
@@ -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
|
@@ -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
|
+
}
|