@ranger1/dx 0.1.64 → 0.1.66
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/codex/skills/backend-artifact-deploy/SKILL.md +122 -0
- package/codex/skills/backend-artifact-deploy/agents/openai.yaml +4 -0
- package/codex/skills/backend-artifact-deploy/references/deployment-checklist.md +66 -0
- package/codex/skills/gh-dependabot-cleanup/SKILL.md +54 -0
- package/codex/skills/gh-dependabot-cleanup/agents/openai.yaml +4 -0
- package/codex/skills/git-commit-and-pr/SKILL.md +208 -0
- package/codex/skills/git-commit-and-pr/agents/openai.yaml +4 -0
- package/codex/skills/git-release/SKILL.md +187 -0
- package/codex/skills/git-release/agents/openai.yaml +4 -0
- package/lib/opencode-initial.js +68 -0
- package/lib/vercel-deploy.js +18 -11
- package/package.json +2 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: backend-artifact-deploy
|
|
3
|
+
description: 将后端部署从“目标机拉源码并编译”改造为“本地构建制品、目标机仅安装运行依赖并启动”的标准流程。用于 Node/NestJS/Nx/Prisma 等后端项目,尤其适合需要无源码部署、支持 dev/staging/prod 多环境、要求双层环境文件覆盖(如 .env.production 与 .env.production.local)、以及需要在 pm2 与 direct 启动方式之间切换的场景。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 后端制品部署
|
|
7
|
+
|
|
8
|
+
## 概览
|
|
9
|
+
|
|
10
|
+
使用该技能时,先识别项目当前的环境变量加载链路与启动链路,再落地“制品打包脚本 + 服务器发布脚本 + 回滚策略”。
|
|
11
|
+
目标是保证目标机器不需要源码编译,同时保持与项目既有环境覆盖规则一致。
|
|
12
|
+
|
|
13
|
+
## 执行流程
|
|
14
|
+
|
|
15
|
+
### 第一步:确认现状链路
|
|
16
|
+
|
|
17
|
+
依次核对:
|
|
18
|
+
|
|
19
|
+
1. 构建链路是否依赖源码目录(例如 dist 里软链回源码 `node_modules`)。
|
|
20
|
+
2. 运行链路如何加载环境变量(是否是两层覆盖,是否通过 `dotenv -e A -e B`)。
|
|
21
|
+
3. 数据库迁移链路是否依赖运行时环境(Prisma `generate` / `migrate deploy` 前是否已加载 env)。
|
|
22
|
+
4. 进程启动是否仅支持 pm2,是否需要 direct 前台测试模式。
|
|
23
|
+
|
|
24
|
+
如果项目已有统一入口(例如 `dx` 或内部脚手架),优先复用该入口,不要绕开既有环境策略。
|
|
25
|
+
|
|
26
|
+
### 第二步:定义制品边界
|
|
27
|
+
|
|
28
|
+
默认采用“轻制品”模式:
|
|
29
|
+
|
|
30
|
+
1. 本地只打包编译产物与必要运行文件,不打包 `node_modules`。
|
|
31
|
+
2. 目标机解压后再安装生产依赖。
|
|
32
|
+
3. 制品命名固定含版本与时间片,例如 `backend-v<version>-<月-日-时-分>.tgz`。
|
|
33
|
+
|
|
34
|
+
制品最小清单应包含:
|
|
35
|
+
|
|
36
|
+
1. 编译产物目录(如 `dist/backend/**`)。
|
|
37
|
+
2. 数据库 schema 与迁移目录(如 `prisma/schema/**`)。
|
|
38
|
+
3. 生产依赖清单(`package.production.json` 重命名为 `package.json`)。
|
|
39
|
+
4. 锁文件(`pnpm-lock.yaml`)。
|
|
40
|
+
5. 启动配置(如 `ecosystem.config.cjs`)。
|
|
41
|
+
6. 双层环境文件(`.env.<env>` 与 `.env.<env>.local`)。
|
|
42
|
+
|
|
43
|
+
### 第三步:实现打包脚本
|
|
44
|
+
|
|
45
|
+
打包脚本应支持参数:
|
|
46
|
+
|
|
47
|
+
1. `--env dev|staging|prod`。
|
|
48
|
+
2. `--version`(默认取后端 `package.json` 版本)。
|
|
49
|
+
3. `--time`(格式 `MM-DD-HH-mm`)。
|
|
50
|
+
|
|
51
|
+
脚本关键行为:
|
|
52
|
+
|
|
53
|
+
1. 按环境构建(`dev -> --dev`,`staging/prod -> --prod`)。
|
|
54
|
+
2. 复制双层环境文件到制品目录。
|
|
55
|
+
3. 不在本地安装运行依赖。
|
|
56
|
+
4. 生成 `tgz`。
|
|
57
|
+
|
|
58
|
+
### 第四步:实现发布脚本
|
|
59
|
+
|
|
60
|
+
发布脚本应支持参数:
|
|
61
|
+
|
|
62
|
+
1. `--archive`(必填)。
|
|
63
|
+
2. `--env dev|staging|prod`。
|
|
64
|
+
3. `--start-mode pm2|direct`(默认 `pm2`)。
|
|
65
|
+
4. `--env-file` 与 `--env-local-file`(可选覆盖路径)。
|
|
66
|
+
5. `--skip-install`、`--skip-migration`、`--skip-pm2`。
|
|
67
|
+
|
|
68
|
+
发布顺序建议:
|
|
69
|
+
|
|
70
|
+
1. 解压到 `releases/<version>`。
|
|
71
|
+
2. 准备双层 env 文件。
|
|
72
|
+
3. 安装生产依赖。
|
|
73
|
+
4. 执行 `prisma generate`。
|
|
74
|
+
5. 执行 `prisma migrate deploy`。
|
|
75
|
+
6. 切换 `current` 软链。
|
|
76
|
+
7. 启动服务(pm2 或 direct)。
|
|
77
|
+
8. 清理旧版本。
|
|
78
|
+
|
|
79
|
+
### 第五步:双层环境加载规则(必须一致)
|
|
80
|
+
|
|
81
|
+
所有关键步骤统一使用相同加载顺序:
|
|
82
|
+
|
|
83
|
+
1. 基础层 `.env.<env>`。
|
|
84
|
+
2. 覆盖层 `.env.<env>.local`。
|
|
85
|
+
|
|
86
|
+
推荐显式写法:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
APP_ENV="<env-name>" pnpm exec dotenv -e ".env.<env-name>" -e ".env.<env-name>.local" -- <command>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
命令示例中的 `<command>` 包括:
|
|
93
|
+
|
|
94
|
+
1. `pnpm exec prisma generate --schema=...`
|
|
95
|
+
2. `pnpm exec prisma migrate deploy --schema=...`
|
|
96
|
+
3. `pm2 startOrReload ...` 或 `node apps/backend/src/main.js`
|
|
97
|
+
|
|
98
|
+
## 验证清单
|
|
99
|
+
|
|
100
|
+
交付前必须至少验证:
|
|
101
|
+
|
|
102
|
+
1. 打包脚本 `--help` 与语法检查通过。
|
|
103
|
+
2. 发布脚本 `--help` 与语法检查通过。
|
|
104
|
+
3. 制品内同时包含 `.env.<env>` 与 `.env.<env>.local`。
|
|
105
|
+
4. 发布脚本在默认路径下能正确识别并使用两层 env。
|
|
106
|
+
5. `start-mode=direct` 可前台启动。
|
|
107
|
+
6. `start-mode=pm2` 可重载或启动。
|
|
108
|
+
7. 版本目录与 `current` 切换正常,可回滚。
|
|
109
|
+
|
|
110
|
+
## 常见陷阱
|
|
111
|
+
|
|
112
|
+
1. 把 env 文件链接到自身,造成坏链路。
|
|
113
|
+
2. 只加载 `.env.<env>`,遗漏 `.local` 覆盖。
|
|
114
|
+
3. 迁移与启动阶段用不同 env 加载逻辑,导致行为不一致。
|
|
115
|
+
4. staging 构建误用 development 或 production 的 env 层。
|
|
116
|
+
5. 打包包含本机 `node_modules`,跨系统运行失败。
|
|
117
|
+
|
|
118
|
+
## 参考资料
|
|
119
|
+
|
|
120
|
+
需要细化实现时,读取:
|
|
121
|
+
|
|
122
|
+
- `references/deployment-checklist.md`
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# 后端制品部署检查清单
|
|
2
|
+
|
|
3
|
+
## 一、改造前采样
|
|
4
|
+
|
|
5
|
+
1. 查找构建后是否存在软链回源码依赖:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
rg -n "ln -sfn.*node_modules|node_modules.*ln -sfn" <backend-package-json-path>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
2. 查找环境加载入口:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
rg -n "dotenv -e|dotenv --override|ConfigModule.forRoot|loadEnvironment|env-layers" -S <repo-root>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
3. 查找启动命令:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
rg -n "start:prod|pm2|node .*main" -S <repo-root>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 二、打包脚本最低要求
|
|
24
|
+
|
|
25
|
+
1. 接收 `--env`、`--version`、`--time`。
|
|
26
|
+
2. 制品名包含版本与时间片。
|
|
27
|
+
3. 打入 `.env.<env>` 与 `.env.<env>.local`。
|
|
28
|
+
4. 不打入 `node_modules`(轻制品模式)。
|
|
29
|
+
|
|
30
|
+
## 三、发布脚本最低要求
|
|
31
|
+
|
|
32
|
+
1. 解压到 `releases/<version>` 并切换 `current`。
|
|
33
|
+
2. 支持 `pm2` 与 `direct` 两种启动方式。
|
|
34
|
+
3. 在 install、migrate、start 三阶段都用同一套双层 env 加载顺序。
|
|
35
|
+
4. 支持 `--env-file` 与 `--env-local-file` 覆盖路径。
|
|
36
|
+
|
|
37
|
+
## 四、上线前验证命令
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
bash -n scripts/release/backend-build-release.sh
|
|
41
|
+
bash -n scripts/release/backend-deploy-release.sh
|
|
42
|
+
scripts/release/backend-build-release.sh --env staging
|
|
43
|
+
tar -tzf release/backend/*.tgz | rg "\.env\.staging(\.local)?$"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 五、发布后验证
|
|
47
|
+
|
|
48
|
+
1. 进程检查:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pm2 status
|
|
52
|
+
pm2 logs backend --lines 120
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
2. 健康检查:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
curl -f http://127.0.0.1:3000/health
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
3. 回滚检查:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
ln -sfn /opt/ai-backend/releases/<old-version> /opt/ai-backend/current
|
|
65
|
+
pm2 reload backend --update-env
|
|
66
|
+
```
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gh-dependabot-cleanup
|
|
3
|
+
description: Use when a user asks to check or remediate GitHub Dependabot alerts and deliver one closed-loop PR, including alert triage, dependency updates, verification, and PR creation with unresolved items clearly documented.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GH Dependabot Cleanup
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
Use this skill to complete a Dependabot remediation loop with minimal manual input. Rely on `gh` directly, fix all patchable alerts in scope, and document unpatched alerts without auto-dismiss.
|
|
10
|
+
|
|
11
|
+
## Closed Loop Workflow
|
|
12
|
+
1. Confirm target and scope in one sentence.
|
|
13
|
+
2. Fetch open alerts with `gh api`.
|
|
14
|
+
3. Classify alerts into:
|
|
15
|
+
- patchable: has `first_patched_version`
|
|
16
|
+
- unpatched: no `first_patched_version`
|
|
17
|
+
- direct vs transitive
|
|
18
|
+
4. Announce remediation plan before edits.
|
|
19
|
+
5. Apply dependency changes (prefer overrides/resolutions for transitive alerts).
|
|
20
|
+
6. Refresh lockfile.
|
|
21
|
+
7. Run required project verification commands.
|
|
22
|
+
8. Commit, push, and open exactly one PR.
|
|
23
|
+
9. Report residual risk (unpatched alerts) in PR and final reply.
|
|
24
|
+
|
|
25
|
+
## Default Commands
|
|
26
|
+
```bash
|
|
27
|
+
# 1) Fetch open alerts JSON
|
|
28
|
+
gh api -H 'Accept: application/vnd.github+json' \
|
|
29
|
+
'/repos/OWNER/REPO/dependabot/alerts?state=open&per_page=100'
|
|
30
|
+
|
|
31
|
+
# 2) Tabular triage view
|
|
32
|
+
gh api -H 'Accept: application/vnd.github+json' \
|
|
33
|
+
'/repos/OWNER/REPO/dependabot/alerts?state=open&per_page=100' \
|
|
34
|
+
| jq -r '.[] | [.number, .security_vulnerability.package.name, .dependency.relationship, .security_advisory.severity, (.security_vulnerability.first_patched_version.identifier // "none"), .security_vulnerability.vulnerable_version_range, .html_url] | @tsv'
|
|
35
|
+
|
|
36
|
+
# 3) Typical lock refresh (adapt to repo)
|
|
37
|
+
pnpm install --lockfile-only
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Decision Rules
|
|
41
|
+
- Keep unpatched alerts open by default; do not dismiss unless explicitly requested.
|
|
42
|
+
- If one package maps to multiple alerts, upgrade once to the highest required safe version.
|
|
43
|
+
- Keep the PR focused on security dependency remediation only.
|
|
44
|
+
- If repo has branch/issue conventions, follow them strictly.
|
|
45
|
+
|
|
46
|
+
## PR Requirements
|
|
47
|
+
Include these sections:
|
|
48
|
+
1. Fixed alerts: alert id, package, target version
|
|
49
|
+
2. Remaining alerts: alert id, reason (for example, no upstream patch)
|
|
50
|
+
3. Verification: exact commands run and outcomes
|
|
51
|
+
4. Risk note: what is deferred and why
|
|
52
|
+
|
|
53
|
+
## Fast Trigger
|
|
54
|
+
修复安全警告
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: git-commit-and-pr
|
|
3
|
+
description: 在 Git 仓库中执行自动化 Issue、Commit、PR 工作流。用于以下场景:需要零输入一键完成 Issue→分支→Commit→PR;需要根据仓库状态自动判断下一步;需要创建结构化 GitHub Issue;需要基于暂存变更生成规范 commit;需要推送分支并创建 Pull Request;需要串联输出清晰阶段结果。支持仅建 Issue、仅建 PR、指定 Issue 编号、指定 PR 基准分支。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Git Commit And PR
|
|
7
|
+
|
|
8
|
+
## 目标
|
|
9
|
+
|
|
10
|
+
在最少人工输入下完成 `Issue -> Branch -> Commit -> PR` 标准流程,并保持可审计输出。
|
|
11
|
+
|
|
12
|
+
## 默认行为(零输入)
|
|
13
|
+
|
|
14
|
+
当用户只调用 `/git-commit-and-pr` 且不带参数时,执行“全自动模式”,不再向用户追问:
|
|
15
|
+
|
|
16
|
+
1. 检查仓库状态与当前分支。
|
|
17
|
+
2. 若工作树有未提交改动:执行 Issue -> Branch -> Commit -> PR。
|
|
18
|
+
3. 若工作树干净:对比当前分支与 `main`(或默认基准)差异,并检查最近提交,判断是否应直接创建 PR。
|
|
19
|
+
4. 若工作树干净且存在分支差异:直接推送并创建 PR(不再创建新 Issue/Commit)。
|
|
20
|
+
5. 输出 Issue、Commit、PR 链接与下一步命令。
|
|
21
|
+
|
|
22
|
+
说明:保留所有旧能力,不影响 `--issue`、`--issue-only`、`--pr --base`。
|
|
23
|
+
|
|
24
|
+
## 执行原则
|
|
25
|
+
|
|
26
|
+
- 全程中文输出。
|
|
27
|
+
- 先检查状态,再决定执行阶段。
|
|
28
|
+
- 禁止在 `main/master` 直接提交代码。
|
|
29
|
+
- 优先自动完成,不因“小缺信息”中断;只在不可执行时报阻塞。
|
|
30
|
+
- 输出必须包含:已完成项、阻塞原因、下一步建议。
|
|
31
|
+
|
|
32
|
+
## 输入形式
|
|
33
|
+
|
|
34
|
+
- 自动模式:`/git-commit-and-pr`
|
|
35
|
+
- 指定 Issue:`/git-commit-and-pr --issue <ID>`
|
|
36
|
+
- 仅创建 Issue:`/git-commit-and-pr --issue-only`
|
|
37
|
+
- 仅创建 PR:`/git-commit-and-pr --pr --base <BRANCH>`
|
|
38
|
+
|
|
39
|
+
## 流程
|
|
40
|
+
|
|
41
|
+
### 一、状态检测(并行)
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
git status --short
|
|
45
|
+
git branch --show-current
|
|
46
|
+
git log -1 --format='%H %s' 2>/dev/null || echo "no-commits"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
补充检测:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null || echo "no-upstream"
|
|
53
|
+
git remote -v
|
|
54
|
+
git rev-parse --abbrev-ref origin/HEAD 2>/dev/null || echo "origin/main"
|
|
55
|
+
git log --oneline -n 5
|
|
56
|
+
git log origin/main..HEAD --oneline
|
|
57
|
+
git diff --stat origin/main...HEAD
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
决策规则:
|
|
61
|
+
|
|
62
|
+
- `--issue-only`:仅走 Issue 创建并结束。
|
|
63
|
+
- `--pr`:跳过 Issue/Commit,直接走 PR 流程。
|
|
64
|
+
- 默认模式:
|
|
65
|
+
- 若 `git status --short` 非空:按 `Issue -> Branch -> Commit -> PR` 全链路执行。
|
|
66
|
+
- 若 `git status --short` 为空且 `origin/main..HEAD` 有提交差异:直接执行 PR 创建。
|
|
67
|
+
- 若 `git status --short` 为空且无分支差异:输出“无需创建 PR”,并结束。
|
|
68
|
+
|
|
69
|
+
### 二、Issue 创建
|
|
70
|
+
|
|
71
|
+
1. 收集上下文:
|
|
72
|
+
- `git status --short`
|
|
73
|
+
- `git diff --stat`
|
|
74
|
+
- 可选去重:`gh issue list --search "<关键词>" --limit 5`
|
|
75
|
+
|
|
76
|
+
2. 生成标题:`[模块] 简洁描述` 或 `[类型] 功能/问题描述`。
|
|
77
|
+
|
|
78
|
+
3. 标签:从 `bug`、`enhancement`、`documentation`、`performance`、`refactor`、`backend`、`frontend`、`infrastructure` 中选择。
|
|
79
|
+
|
|
80
|
+
4. 正文结构固定:背景、现状/问题、期望行为、执行计划、影响范围、相关资源。
|
|
81
|
+
|
|
82
|
+
5. 创建命令(heredoc):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
gh issue create \
|
|
86
|
+
--title "[模块] 问题摘要" \
|
|
87
|
+
--label label1 --label label2 \
|
|
88
|
+
--body-file - <<'MSG'
|
|
89
|
+
## 背景
|
|
90
|
+
[背景]
|
|
91
|
+
|
|
92
|
+
## 现状/问题
|
|
93
|
+
[问题]
|
|
94
|
+
|
|
95
|
+
## 期望行为
|
|
96
|
+
[目标]
|
|
97
|
+
|
|
98
|
+
## 执行计划
|
|
99
|
+
- [ ] 步骤一
|
|
100
|
+
- [ ] 步骤二
|
|
101
|
+
|
|
102
|
+
## 影响范围
|
|
103
|
+
[范围]
|
|
104
|
+
MSG
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 三、分支处理(新增强制步骤)
|
|
108
|
+
|
|
109
|
+
1. 若当前分支是 `main/master`,必须切新分支后再提交。
|
|
110
|
+
2. 若存在 Issue 编号,分支名优先:`codex/fix/<issue-id>-<slug>`。
|
|
111
|
+
3. 若已在合法功能分支且用户显式要求保留,则继续使用当前分支。
|
|
112
|
+
|
|
113
|
+
示例:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
git switch -c codex/fix/1234-short-topic
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 四、Commit 流程
|
|
120
|
+
|
|
121
|
+
1. 暂存与检查:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
git add -A
|
|
125
|
+
git diff --cached --stat
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
2. 根据 `git diff --cached` 生成提交:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
git commit -F - <<'EOF'
|
|
132
|
+
<type>: <概要>
|
|
133
|
+
|
|
134
|
+
变更说明:
|
|
135
|
+
- <变更项一>
|
|
136
|
+
- <变更项二>
|
|
137
|
+
|
|
138
|
+
Refs: #<issue-id>
|
|
139
|
+
EOF
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
3. `type` 仅用:`feat/fix/refactor/docs/chore/test`。
|
|
143
|
+
|
|
144
|
+
4. 提交后确认:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
git status
|
|
148
|
+
git log -1 --oneline
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### 五、PR 创建
|
|
152
|
+
|
|
153
|
+
1. 推送分支:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
git push -u origin HEAD
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
2. 变更分析:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
git log origin/main..HEAD --oneline
|
|
163
|
+
git diff origin/main...HEAD --stat
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
3. 创建 PR(默认 `--base main`):
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
gh pr create --base main --title '<type>: <概要>' --body-file - <<'EOF'
|
|
170
|
+
## 变更说明
|
|
171
|
+
|
|
172
|
+
- <变更项>
|
|
173
|
+
|
|
174
|
+
## 测试
|
|
175
|
+
|
|
176
|
+
- [ ] 本地测试通过
|
|
177
|
+
|
|
178
|
+
Closes: #<issue-id>
|
|
179
|
+
EOF
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
4. 成功后输出评审命令:`/pr-review-loop --pr <PR_NUMBER>`。
|
|
183
|
+
|
|
184
|
+
## 质量检查
|
|
185
|
+
|
|
186
|
+
- 标题简洁可读,语义明确。
|
|
187
|
+
- 描述包含背景、问题、目标、影响范围。
|
|
188
|
+
- 提交与 PR 文案可核对到 diff。
|
|
189
|
+
- 标签与提交类型匹配。
|
|
190
|
+
- 不泄露敏感信息。
|
|
191
|
+
|
|
192
|
+
## 失败与阻塞输出
|
|
193
|
+
|
|
194
|
+
输出以下四项:
|
|
195
|
+
|
|
196
|
+
- 停止阶段
|
|
197
|
+
- 已完成列表
|
|
198
|
+
- 阻塞原因
|
|
199
|
+
- 继续执行命令建议
|
|
200
|
+
|
|
201
|
+
## 成功输出
|
|
202
|
+
|
|
203
|
+
输出以下四项:
|
|
204
|
+
|
|
205
|
+
- Issue 编号与标题
|
|
206
|
+
- Commit 摘要
|
|
207
|
+
- PR 编号与链接
|
|
208
|
+
- 下一步评审命令
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: git-release
|
|
3
|
+
description: 在 Git 仓库中执行标准化版本发布流程并自动生成高质量中文发行说明。用于以下场景:需要从 release 分支发布新版本;需要从分支名提取并校验语义化版本(含 alpha/beta/rc 预发布);需要批量更新多个 package.json 的 version 字段并提交;需要基于最近 GitHub Release 汇总提交与 PR 信息、分类变更、生成发布摘要;需要创建 annotated tag、推送远端并创建 GitHub Release。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Git Release
|
|
7
|
+
|
|
8
|
+
## 目标
|
|
9
|
+
|
|
10
|
+
在 `release/vX.Y.Z` 或 `release/vX.Y.Z-<prerelease>.N` 分支上,完成从发布前检查到 GitHub Release 创建的全流程。
|
|
11
|
+
|
|
12
|
+
## 执行原则
|
|
13
|
+
|
|
14
|
+
- 全程使用中文输出。
|
|
15
|
+
- 严格执行前置校验,任何硬性条件不满足时立即终止。
|
|
16
|
+
- 发行说明必须结构化、可读、可追溯。
|
|
17
|
+
- 命令默认在仓库根目录执行。
|
|
18
|
+
|
|
19
|
+
## 流程
|
|
20
|
+
|
|
21
|
+
### 一、发布前检查
|
|
22
|
+
|
|
23
|
+
1. 检查工作区是否干净:`git status --porcelain`。
|
|
24
|
+
2. 若存在未提交变更,列出文件并终止流程。
|
|
25
|
+
3. 检查当前分支:`git branch --show-current`。
|
|
26
|
+
4. 仅接受以下正则:`^release/v\d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?$`。
|
|
27
|
+
5. 从分支名提取版本号,例如:
|
|
28
|
+
- `release/v1.2.3` -> `v1.2.3` -> `1.2.3`
|
|
29
|
+
- `release/v1.2.3-beta.2` -> `v1.2.3-beta.2` -> `1.2.3-beta.2`
|
|
30
|
+
6. 检查目标 tag 是否已存在:`git tag -l "v<VERSION>"`。
|
|
31
|
+
7. 向用户确认版本号;若用户修改版本号,重新校验格式与 tag 冲突。
|
|
32
|
+
|
|
33
|
+
### 二、更新版本号
|
|
34
|
+
|
|
35
|
+
1. 更新以下文件的 `version` 字段为纯版本号(不带 `v` 前缀):
|
|
36
|
+
- `package.json`
|
|
37
|
+
- `apps/backend/package.json`
|
|
38
|
+
- `apps/front/package.json`
|
|
39
|
+
- `apps/admin-front/package.json`
|
|
40
|
+
2. 仅修改 `version` 字段,不变更其他内容。
|
|
41
|
+
3. 执行提交:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
git add package.json apps/*/package.json
|
|
45
|
+
git commit -F - <<'MSG'
|
|
46
|
+
chore: bump version to <VERSION>
|
|
47
|
+
|
|
48
|
+
更新所有 package.json 版本号为 <VERSION>
|
|
49
|
+
|
|
50
|
+
发布准备提交
|
|
51
|
+
MSG
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 三、收集与分析变更
|
|
55
|
+
|
|
56
|
+
1. 优先获取最近已发布版本:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
gh release list --limit 1 --json tagName,publishedAt --jq '.[0].tagName'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
2. 若无 GitHub Release,回退:`git describe --tags --abbrev=0`。
|
|
63
|
+
3. 采集范围:`<last-release-tag>..HEAD`。
|
|
64
|
+
4. 收集数据:
|
|
65
|
+
- `git log <last-release-tag>..HEAD --oneline`
|
|
66
|
+
- `git log <last-release-tag>..HEAD --pretty=format:"%H|%s|%b"`
|
|
67
|
+
- `git diff <last-release-tag>..HEAD --shortstat`
|
|
68
|
+
5. 从提交中提取 PR 编号(合并提交、Refs、Closes 等),并用 `gh pr view` 获取标题与标签。
|
|
69
|
+
6. 去重同一 PR。
|
|
70
|
+
7. 分类变更:
|
|
71
|
+
- 新增:`feat` 或 feature 标签
|
|
72
|
+
- 优化:`refactor`、`perf`、`chore`
|
|
73
|
+
- 修复:`fix` 或 bug 标签
|
|
74
|
+
- 技术改进:`docs`、`test`、`build`、`ci`
|
|
75
|
+
8. 过滤噪音:忽略无意义合并记录与 `chore: bump version`。
|
|
76
|
+
9. 识别运维提醒:环境变量、数据库迁移、依赖更新、配置与部署变更。
|
|
77
|
+
|
|
78
|
+
### 四、生成发行说明
|
|
79
|
+
|
|
80
|
+
1. 生成 3-5 条发布摘要,按业务影响排序。
|
|
81
|
+
2. 输出分类变更清单,关联 PR 或 Issue。
|
|
82
|
+
3. 使用以下结构:
|
|
83
|
+
|
|
84
|
+
```markdown
|
|
85
|
+
# v<VERSION> 发行说明
|
|
86
|
+
|
|
87
|
+
## 发布摘要
|
|
88
|
+
|
|
89
|
+
- <核心变更1> (#PR)
|
|
90
|
+
- <核心变更2> (#PR)
|
|
91
|
+
- <核心变更3> (#PR)
|
|
92
|
+
|
|
93
|
+
发布日期:<YYYY-MM-DD>
|
|
94
|
+
对比分支:`<last-tag>...v<VERSION>`
|
|
95
|
+
|
|
96
|
+
## 新增
|
|
97
|
+
|
|
98
|
+
- <新增项> (#PR)
|
|
99
|
+
|
|
100
|
+
## 优化
|
|
101
|
+
|
|
102
|
+
- <优化项> (#PR)
|
|
103
|
+
|
|
104
|
+
## 修复
|
|
105
|
+
|
|
106
|
+
- <修复项> (#PR)
|
|
107
|
+
|
|
108
|
+
## 技术改进
|
|
109
|
+
|
|
110
|
+
- <技术改进项> (#PR)
|
|
111
|
+
|
|
112
|
+
## 运维提醒
|
|
113
|
+
|
|
114
|
+
- <提醒项>
|
|
115
|
+
|
|
116
|
+
## 引用
|
|
117
|
+
|
|
118
|
+
- PRs:#1, #2
|
|
119
|
+
- Issues:#10
|
|
120
|
+
- 共计 <X> 个提交
|
|
121
|
+
|
|
122
|
+
## 升级指南
|
|
123
|
+
|
|
124
|
+
1. <步骤1>
|
|
125
|
+
2. <步骤2>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 五、创建发布
|
|
129
|
+
|
|
130
|
+
1. 创建 annotated tag:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
git tag -a v<VERSION> -m "Release v<VERSION>"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
2. 推送 tag:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
git push origin v<VERSION>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
3. 创建 GitHub Release:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
gh release create v<VERSION> \
|
|
146
|
+
--title "v<VERSION>" \
|
|
147
|
+
--notes-file - <<'EOF'
|
|
148
|
+
<完整发行说明>
|
|
149
|
+
EOF
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
4. 输出发布 URL 与发布后检查清单。
|
|
153
|
+
|
|
154
|
+
## 终止条件
|
|
155
|
+
|
|
156
|
+
以下任一情况出现时终止流程并给出明确原因:
|
|
157
|
+
|
|
158
|
+
- 工作区存在未提交修改。
|
|
159
|
+
- 当前分支不符合 release 分支命名规则。
|
|
160
|
+
- 版本号格式非法或与现有 tag 冲突。
|
|
161
|
+
- 自上次发布以来无新提交。
|
|
162
|
+
|
|
163
|
+
## 输出模板
|
|
164
|
+
|
|
165
|
+
### 发布前状态
|
|
166
|
+
|
|
167
|
+
- 工作目录状态
|
|
168
|
+
- 当前分支
|
|
169
|
+
- 解析出的版本号
|
|
170
|
+
- 版本格式校验结果
|
|
171
|
+
- tag 冲突校验结果
|
|
172
|
+
|
|
173
|
+
### 变更分析
|
|
174
|
+
|
|
175
|
+
- 基准版本
|
|
176
|
+
- 提交范围
|
|
177
|
+
- 提交数与 PR 数
|
|
178
|
+
- 代码变更统计
|
|
179
|
+
- 分类统计
|
|
180
|
+
|
|
181
|
+
### 发布结果
|
|
182
|
+
|
|
183
|
+
- 版本号
|
|
184
|
+
- 分支名
|
|
185
|
+
- tag 推送状态
|
|
186
|
+
- Release URL
|
|
187
|
+
- 发布后清单
|
package/lib/opencode-initial.js
CHANGED
|
@@ -39,6 +39,37 @@ async function collectTemplateFiles(dir) {
|
|
|
39
39
|
return out
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
async function collectAllFiles(dir) {
|
|
43
|
+
const out = []
|
|
44
|
+
|
|
45
|
+
async function walk(current) {
|
|
46
|
+
let entries
|
|
47
|
+
try {
|
|
48
|
+
entries = await fs.readdir(current, { withFileTypes: true })
|
|
49
|
+
} catch {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
const full = join(current, entry.name)
|
|
55
|
+
if (entry.isDirectory()) {
|
|
56
|
+
if (entry.name === '__pycache__' || entry.name === '.pytest_cache') {
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
await walk(full)
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
if (!entry.isFile()) continue
|
|
63
|
+
const lowerName = entry.name.toLowerCase()
|
|
64
|
+
if (lowerName.endsWith('.pyc') || lowerName.endsWith('.pyo') || lowerName.endsWith('.pyd')) continue
|
|
65
|
+
out.push(full)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
await walk(dir)
|
|
70
|
+
return out
|
|
71
|
+
}
|
|
72
|
+
|
|
42
73
|
async function ensureDir(path) {
|
|
43
74
|
await fs.mkdir(path, { recursive: true })
|
|
44
75
|
}
|
|
@@ -84,6 +115,37 @@ async function copyTemplateTree({ srcDir, dstDir }) {
|
|
|
84
115
|
return { total: files.length, md: mdCount, py: pyCount, json: jsonCount }
|
|
85
116
|
}
|
|
86
117
|
|
|
118
|
+
async function copyDirMerge({ srcDir, dstDir }) {
|
|
119
|
+
const files = await collectAllFiles(srcDir)
|
|
120
|
+
|
|
121
|
+
for (const file of files) {
|
|
122
|
+
const rel = relative(srcDir, file)
|
|
123
|
+
const target = join(dstDir, rel)
|
|
124
|
+
await ensureDir(dirname(target))
|
|
125
|
+
await fs.copyFile(file, target)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { fileCount: files.length }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function copySkillsDirectories({ srcSkillsDir, dstSkillsDir }) {
|
|
132
|
+
const entries = await fs.readdir(srcSkillsDir, { withFileTypes: true })
|
|
133
|
+
let copiedDirs = 0
|
|
134
|
+
let copiedFiles = 0
|
|
135
|
+
|
|
136
|
+
for (const entry of entries) {
|
|
137
|
+
if (!entry.isDirectory()) continue
|
|
138
|
+
const srcDir = join(srcSkillsDir, entry.name)
|
|
139
|
+
const dstDir = join(dstSkillsDir, entry.name)
|
|
140
|
+
await ensureDir(dstDir)
|
|
141
|
+
const stats = await copyDirMerge({ srcDir, dstDir })
|
|
142
|
+
copiedDirs++
|
|
143
|
+
copiedFiles += stats.fileCount
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { copiedDirs, copiedFiles }
|
|
147
|
+
}
|
|
148
|
+
|
|
87
149
|
function resolveTemplateRoot(packageRoot) {
|
|
88
150
|
return [join(packageRoot, '@opencode')]
|
|
89
151
|
}
|
|
@@ -117,15 +179,20 @@ export async function runOpenCodeInitial(options = {}) {
|
|
|
117
179
|
const srcCommands = join(existingRoot, 'commands')
|
|
118
180
|
const dstAgents = join(dstRoot, 'agents')
|
|
119
181
|
const dstCommands = join(dstRoot, 'commands')
|
|
182
|
+
const srcSkills = join(packageRoot, 'codex', 'skills')
|
|
183
|
+
const dstSkills = join(homeDir, '.codex', 'skills')
|
|
120
184
|
|
|
121
185
|
await assertDirExists(srcAgents, '模板目录 agents')
|
|
122
186
|
await assertDirExists(srcCommands, '模板目录 commands')
|
|
187
|
+
await assertDirExists(srcSkills, '模板目录 codex/skills')
|
|
123
188
|
|
|
124
189
|
await ensureDir(dstAgents)
|
|
125
190
|
await ensureDir(dstCommands)
|
|
191
|
+
await ensureDir(dstSkills)
|
|
126
192
|
|
|
127
193
|
const agentsStats = await copyTemplateTree({ srcDir: srcAgents, dstDir: dstAgents })
|
|
128
194
|
const commandsStats = await copyTemplateTree({ srcDir: srcCommands, dstDir: dstCommands })
|
|
195
|
+
const skillsStats = await copySkillsDirectories({ srcSkillsDir: srcSkills, dstSkillsDir: dstSkills })
|
|
129
196
|
|
|
130
197
|
logger.success(`已初始化 OpenCode 模板到: ${dstRoot}`)
|
|
131
198
|
logger.info(
|
|
@@ -138,4 +205,5 @@ export async function runOpenCodeInitial(options = {}) {
|
|
|
138
205
|
`${commandsStats.py > 0 ? ` + ${commandsStats.py} 个 .py 文件` : ''}` +
|
|
139
206
|
`${commandsStats.json > 0 ? ` + ${commandsStats.json} 个 .json 文件` : ''}`
|
|
140
207
|
)
|
|
208
|
+
logger.info(`skills: ${skillsStats.copiedDirs} 个目录,覆盖复制 ${skillsStats.copiedFiles} 个文件 -> ${dstSkills}`)
|
|
141
209
|
}
|
package/lib/vercel-deploy.js
CHANGED
|
@@ -18,8 +18,9 @@ const TARGET_CONFIGS = {
|
|
|
18
18
|
admin: {
|
|
19
19
|
configFile: 'vercel.admin.json',
|
|
20
20
|
projectIdEnvVar: 'VERCEL_PROJECT_ID_ADMIN',
|
|
21
|
-
deployCwd: '
|
|
22
|
-
deployMode: 'prebuilt'
|
|
21
|
+
deployCwd: '.',
|
|
22
|
+
deployMode: 'prebuilt',
|
|
23
|
+
prebuiltCwd: '.'
|
|
23
24
|
},
|
|
24
25
|
'telegram-bot': {
|
|
25
26
|
configFile: 'vercel.telegram-bot.json',
|
|
@@ -168,8 +169,13 @@ function readLinkedProjectContext(contextRoot) {
|
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
function clearLinkedProjectContext(contextRoot) {
|
|
171
|
-
const
|
|
172
|
-
rmSync(
|
|
172
|
+
const linkDir = join(contextRoot, '.vercel')
|
|
173
|
+
rmSync(linkDir, { force: true, recursive: true })
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function clearVercelBuildOutput(contextRoot) {
|
|
177
|
+
const outputDir = join(contextRoot, '.vercel', 'output')
|
|
178
|
+
rmSync(outputDir, { force: true, recursive: true })
|
|
173
179
|
}
|
|
174
180
|
|
|
175
181
|
async function runVercel(args, options = {}) {
|
|
@@ -375,9 +381,8 @@ export async function deployToVercel(target, options = {}) {
|
|
|
375
381
|
`检测到 ${VERCEL_PROJECT_LINK_PATH} 但解析失败: ${linkedContext.parseError.message}`
|
|
376
382
|
)
|
|
377
383
|
if (strictContext) {
|
|
378
|
-
logger.
|
|
379
|
-
|
|
380
|
-
return
|
|
384
|
+
logger.warn('strictContext 已开启:自动清理 .vercel 后继续部署')
|
|
385
|
+
clearLinkedProjectContext(prebuiltCwd)
|
|
381
386
|
}
|
|
382
387
|
}
|
|
383
388
|
|
|
@@ -388,11 +393,11 @@ export async function deployToVercel(target, options = {}) {
|
|
|
388
393
|
` 本地链接: org=${maskIdentifier(linkedContext.orgId)} project=${maskIdentifier(linkedContext.projectId)}`
|
|
389
394
|
)
|
|
390
395
|
if (strictContext) {
|
|
391
|
-
logger.
|
|
392
|
-
|
|
393
|
-
|
|
396
|
+
logger.warn('strictContext 已开启:检测到冲突,自动清理 .vercel 后继续部署')
|
|
397
|
+
clearLinkedProjectContext(prebuiltCwd)
|
|
398
|
+
} else {
|
|
399
|
+
logger.warn('strictContext 已关闭,继续执行(可能存在误部署风险)')
|
|
394
400
|
}
|
|
395
|
-
logger.warn('strictContext 已关闭,继续执行(可能存在误部署风险)')
|
|
396
401
|
}
|
|
397
402
|
|
|
398
403
|
logger.info(
|
|
@@ -431,6 +436,8 @@ export async function deployToVercel(target, options = {}) {
|
|
|
431
436
|
if (strictContext && process.env.DX_VERCEL_KEEP_LINK !== '1') {
|
|
432
437
|
clearLinkedProjectContext(prebuiltCwd)
|
|
433
438
|
}
|
|
439
|
+
// 避免 Vercel 本地预构建产物残留导致 EEXIST(如 _global-error.func 符号链接冲突)
|
|
440
|
+
clearVercelBuildOutput(prebuiltCwd)
|
|
434
441
|
|
|
435
442
|
// 第一步:本地构建
|
|
436
443
|
logger.step(`本地构建 ${t} (${environment})`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ranger1/dx",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.66",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"bin/",
|
|
20
20
|
"lib/",
|
|
21
21
|
"@opencode/",
|
|
22
|
+
"codex/",
|
|
22
23
|
"LICENSE",
|
|
23
24
|
"README.md",
|
|
24
25
|
"package.json"
|