@namewta/speculo 1.0.18 → 1.0.20
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 +8 -7
- package/THIRD-PARTY-NOTICES.md +33 -0
- package/dist/src/agent-files.js +2 -2
- package/dist/src/agent-files.js.map +1 -1
- package/dist/src/agent-skills.d.ts +81 -0
- package/dist/src/agent-skills.js +530 -0
- package/dist/src/agent-skills.js.map +1 -0
- package/dist/src/cli.js +11 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/config.js +5 -3
- package/dist/src/config.js.map +1 -1
- package/dist/src/doctor.js +7 -2
- package/dist/src/doctor.js.map +1 -1
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.js +23 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/manifest.d.ts +9 -0
- package/dist/src/manifest.js +45 -7
- package/dist/src/manifest.js.map +1 -1
- package/dist/src/refresh.js +40 -0
- package/dist/src/refresh.js.map +1 -1
- package/dist/src/structured.js +15 -4
- package/dist/src/structured.js.map +1 -1
- package/dist/src/transaction.d.ts +24 -3
- package/dist/src/transaction.js +191 -12
- package/dist/src/transaction.js.map +1 -1
- package/package.json +10 -4
- package/scripts/validate-skills.mjs +56 -0
- package/template/AGENTS.md +1 -1
- package/template/CLAUDE.md +1 -1
- package/template/canonical/canonical-specdev-goal-plan.md +158 -30
- package/template/canonical/canonical-specdev-grill-with-docs.md +155 -33
- package/template/canonical/canonical-specdev-spec.md +162 -161
- package/template/canonical/canonical-specdev-tickets.md +155 -29
- package/template/canonical/canonical-specdev-wayfinder.md +144 -29
- package/template/commands/handoff.md +16 -0
- package/template/commands/retro.md +12 -10
- package/template/skills/archive-and-consolidate/SKILL.md +163 -7
- package/template/skills/archive-and-consolidate/references/cleanup-rules.md +1 -1
- package/template/skills/docs-sync/references/agents/claude-redirect.md +8 -8
- package/template/skills/docs-sync/references/agents/content-contract.md +1 -1
- package/template/skills/docs-sync/references/agents/manifest-discovery.md +7 -7
- package/template/skills/docs-sync/references/agents/role-classification.md +4 -4
- package/template/skills/docs-sync/references/agents-contract.md +1 -1
- package/template/skills/engineering-standards-builder/SKILL.md +149 -9
- package/template/skills/engineering-standards-builder/agents/openai.yaml +2 -0
- package/template/skills/engineering-standards-builder/manifest.txt +2 -1
- package/template/skills/engineering-standards-builder/references/rules/09-mechanical-guardrails.md +9 -0
- package/template/skills/engineering-standards-builder/scripts/validate-builder.mjs +4 -7
- package/template/skills/git-history-squash/SKILL.md +87 -11
- package/template/skills/git-history-squash/agents/openai.yaml +2 -0
- package/template/skills/optimize-codex-config/SKILL.md +71 -11
- package/template/skills/retrospective/SKILL.md +43 -0
- package/template/skills/{speculo-retro → retrospective}/references/friction-taxonomy.md +22 -13
- package/template/skills/{speculo-retro → retrospective}/references/issue-drafting-sop.md +13 -21
- package/template/skills/source-code-zip/SKILL.md +44 -8
- package/template/skills/source-code-zip/references/{entry-procedure.md → cli-reference.md} +0 -43
- package/template/skills/upstream-fork-sync/SKILL.md +71 -8
- package/template/skills/{writing-great-skills → writing-for-agents}/GLOSSARY.md +1 -1
- package/template/skills/writing-for-agents/SKILL.md +22 -0
- package/template/skills/writing-for-agents/agents/openai.yaml +2 -0
- package/template/skills/{writing-great-skills/references/entry-procedure.md → writing-for-agents/references/authoring-principles.md} +2 -9
- package/template/workflows/learning/A-archive/A-archive.md +2 -5
- package/template/workflows/learning/A-assess-and-plan/A-assess-and-plan.md +2 -5
- package/template/workflows/learning/C-consolidate/C-consolidate.md +2 -5
- package/template/workflows/learning/G-goal/G-goal.md +5 -23
- package/template/workflows/learning/H-homework/H-homework.md +2 -5
- package/template/workflows/learning/I-init-setup/I-init-setup.md +4 -9
- package/template/workflows/learning/L-lesson/L-lesson.md +2 -5
- package/template/workflows/learning/Q-question/Q-question.md +16 -8
- package/template/workflows/learning/Q-question/inquiry-template.md +2 -0
- package/template/workflows/learning/Q-question/tools/validate-inquiry.mjs +112 -0
- package/template/workflows/learning/R-review/R-review.md +3 -6
- package/template/workflows/learning/README.md +2 -2
- package/template/workflows/learning/common/rules/knowledge-organization.md +1 -1
- package/template/workflows/learning/common/rules/mastery-policy.md +1 -1
- package/template/workflows/learning/common/skills/socratic-questioning/SKILL.md +1 -1
- package/template/workflows/ops/D-project-deploy/D-project-deploy.md +6 -2
- package/template/workflows/ops/H-host-manage/H-host-manage.md +10 -18
- package/template/workflows/ops/H-host-manage/references/host-profiles.md +21 -0
- package/template/workflows/ops/I-initialize/I-initialize.md +4 -4
- package/template/workflows/ops/README.md +22 -84
- package/template/workflows/ops/S-server-connect/S-server-connect.md +3 -3
- package/template/workflows/ops/V-inventory-view/V-inventory-view.md +2 -2
- package/template/workflows/ops/common/rules/deployment-layout.md +51 -0
- package/template/workflows/ops/common/rules/recovery.md +6 -0
- package/template/workflows/ops/common/rules/server-onboarding.md +6 -0
- package/template/workflows/ops/common/tests/test_ops.mjs +5 -8
- package/template/workflows/ops/common/tests/test_ops_bootstrap.mjs +71 -14
- package/template/workflows/ops/common/tests/test_ops_workspace.mjs +17 -8
- package/template/workflows/ops/common/tools/opslib/bootstrap.mjs +4 -4
- package/template/workflows/ops/common/tools/opslib/transport.mjs +3 -3
- package/template/workflows/person/M-mao-zedong-cognitive-os/M-mao-zedong-cognitive-os.md +13 -68
- package/template/workflows/person/S-steelman-deliberation/S-steelman-deliberation.md +11 -123
- package/template/workflows/person/S-steelman-deliberation/references/state-and-publication.md +86 -0
- package/template/workflows/specdev/A-archive-and-consolidate/A-archive-and-consolidate.md +2 -17
- package/template/workflows/specdev/A-archive-and-consolidate/consolidation-interview.md +13 -4
- package/template/workflows/specdev/C-code-review/C-code-review.md +4 -15
- package/template/workflows/specdev/C-code-review/code-review-template.md +4 -0
- package/template/workflows/specdev/D-diagnose-bugs/D-diagnose-bugs.md +4 -20
- package/template/workflows/specdev/G-grill-with-docs/G-grill-with-docs.md +2 -2
- package/template/workflows/specdev/G-grill-with-docs/references/interview-procedure.md +6 -6
- package/template/workflows/specdev/I-implement/I-implement.md +2 -2
- package/template/workflows/specdev/I-implement/evidence-template.md +8 -0
- package/template/workflows/specdev/I-implement/references/implementation-procedure.md +9 -43
- package/template/workflows/specdev/I-implement/tdd-rules.md +4 -0
- package/template/workflows/specdev/I-init-setup/I-init-setup.md +16 -117
- package/template/workflows/specdev/I-init-setup/config-template.json +5 -1
- package/template/workflows/specdev/I-init-setup/references/first-setup.md +46 -0
- package/template/workflows/specdev/INDEX.md +2 -2
- package/template/workflows/specdev/L-learn-change/L-learn-change.md +11 -89
- package/template/workflows/specdev/L-learn-change/references/lesson-format.md +47 -0
- package/template/workflows/specdev/P-goal-plan/P-goal-plan.md +2 -2
- package/template/workflows/specdev/P-goal-plan/planning-modes.md +1 -1
- package/template/workflows/specdev/P-prototype/P-prototype.md +7 -17
- package/template/workflows/specdev/P-prototype/logic-protocol.md +11 -0
- package/template/workflows/specdev/P-prototype/logic-template.md +27 -0
- package/template/workflows/specdev/P-prototype/project-preview-protocol.md +9 -0
- package/template/workflows/specdev/R-retro/R-retro.md +26 -0
- package/template/workflows/specdev/R-retro/retro-template.md +27 -0
- package/template/workflows/specdev/R-review-architecture/R-review-architecture.md +19 -57
- package/template/workflows/specdev/R-review-architecture/architecture-review-template.md +2 -1
- package/template/workflows/specdev/R-review-architecture/references/selected-design.md +23 -0
- package/template/workflows/specdev/R-review-architecture/review-rubric.md +2 -2
- package/template/workflows/specdev/README.md +17 -6
- package/template/workflows/specdev/S-spec/S-spec.md +13 -136
- package/template/workflows/specdev/T-tickets/T-tickets.md +6 -2
- package/template/workflows/specdev/T-triage/T-triage.md +32 -141
- package/template/workflows/specdev/T-triage/capture-protocol.md +1 -1
- package/template/workflows/specdev/T-triage/ci-security-protocol.md +11 -0
- package/template/workflows/specdev/T-triage/intake-protocol.md +29 -4
- package/template/workflows/specdev/T-triage/pr-delivery-protocol.md +13 -0
- package/template/workflows/specdev/T-triage/pr-record-template.md +39 -0
- package/template/workflows/specdev/T-triage/publish-protocol.md +1 -1
- package/template/workflows/specdev/T-triage/queue-protocol.md +7 -0
- package/template/workflows/specdev/T-triage/reconcile-protocol.md +1 -1
- package/template/workflows/specdev/T-triage/references/issue-pr-policy.md +23 -0
- package/template/workflows/specdev/T-triage/references/release-authentication.md +9 -0
- package/template/workflows/specdev/T-triage/references/release-notes-injection.md +21 -0
- package/template/workflows/specdev/T-triage/references/workflow-yaml-reference.md +37 -0
- package/template/workflows/specdev/T-triage/release-preflight.md +13 -0
- package/template/workflows/specdev/T-triage/release-protocol.md +12 -0
- package/template/workflows/specdev/T-triage/release-recovery.md +18 -0
- package/template/workflows/specdev/T-triage/remote-operations.md +25 -0
- package/template/workflows/specdev/T-triage/scripts/github-transport.mjs +208 -0
- package/template/workflows/specdev/T-triage/source-template.md +7 -0
- package/template/workflows/specdev/T-triage/triage-run-template.md +34 -0
- package/template/workflows/specdev/T-triage/triage-template.md +4 -1
- package/template/workflows/specdev/W-wayfinder/W-wayfinder.md +2 -2
- package/template/workflows/specdev/common/rules/artifact-contract.md +7 -0
- package/template/workflows/specdev/common/rules/change-completion.md +6 -0
- package/template/workflows/specdev/common/rules/deployment-handoff.md +23 -0
- package/template/workflows/specdev/common/rules/workflow-routing.md +6 -0
- package/template/workflows/specdev/common/rules/workflow-state-and-lifecycle.md +1 -1
- package/template/workflows/specdev/common/schemas/config.schema.json +137 -26
- package/template/workflows/specdev/common/schemas/logic-prototype.schema.json +62 -0
- package/template/workflows/specdev/common/schemas/pull-request.schema.json +90 -0
- package/template/workflows/specdev/common/schemas/retrospective.schema.json +58 -0
- package/template/workflows/specdev/common/schemas/triage-run.schema.json +149 -0
- package/template/workflows/specdev/common/schemas/triage.schema.json +117 -15
- package/template/workflows/specdev/common/skills/code-review/SKILL.md +5 -4
- package/template/workflows/specdev/common/skills/code-review/references/result-reuse.md +15 -0
- package/template/workflows/specdev/common/tools/delivery-artifacts.mjs +95 -0
- package/template/workflows/specdev/common/tools/validate-specdev.mjs +60 -19
- package/template/workflows/specdev/runtime-contract.json +4 -2
- package/template/skills/archive-and-consolidate/references/entry-procedure.md +0 -178
- package/template/skills/docs-sync/references/entry-procedure.md +0 -3
- package/template/skills/engineering-standards-builder/references/entry-procedure.md +0 -154
- package/template/skills/git-history-squash/references/entry-procedure.md +0 -94
- package/template/skills/github-npm-ops/SKILL.md +0 -19
- package/template/skills/github-npm-ops/references/ci-and-security-ops.md +0 -178
- package/template/skills/github-npm-ops/references/entry-procedure.md +0 -25
- package/template/skills/github-npm-ops/references/failure-recovery.md +0 -123
- package/template/skills/github-npm-ops/references/issue-pr-triage.md +0 -219
- package/template/skills/github-npm-ops/references/issue-transport.md +0 -26
- package/template/skills/github-npm-ops/references/package-json-checklist.md +0 -171
- package/template/skills/github-npm-ops/references/preflight-checklist.md +0 -39
- package/template/skills/github-npm-ops/references/publish-detection.md +0 -68
- package/template/skills/github-npm-ops/references/release-notes-injection.md +0 -238
- package/template/skills/github-npm-ops/references/release-pipeline.md +0 -108
- package/template/skills/github-npm-ops/references/setup-npm-token.md +0 -63
- package/template/skills/github-npm-ops/references/troubleshooting-playbook.md +0 -293
- package/template/skills/github-npm-ops/references/version-bump-flow.md +0 -217
- package/template/skills/github-npm-ops/references/workflow-yaml-reference.md +0 -268
- package/template/skills/github-npm-ops/scripts/issue-transport.mjs +0 -227
- package/template/skills/optimize-codex-config/references/entry-procedure.md +0 -78
- package/template/skills/speculo-retro/SKILL.md +0 -19
- package/template/skills/speculo-retro/references/entry-procedure.md +0 -23
- package/template/skills/upstream-fork-sync/references/entry-procedure.md +0 -78
- package/template/skills/writing-great-skills/SKILL.md +0 -23
- /package/template/skills/{writing-great-skills → writing-for-agents}/references/document-contract.md +0 -0
- /package/template/workflows/specdev/{G-grill-with-docs → common/rules}/adr-format.md +0 -0
- /package/template/workflows/specdev/{G-grill-with-docs → common/rules}/context-format.md +0 -0
- /package/template/workflows/specdev/{I-implement → common/rules}/design-it-twice.md +0 -0
- /package/template/workflows/specdev/{G-grill-with-docs/domain-modeling-rules.md → common/rules/domain-modeling.md} +0 -0
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
# 版本 bump → release commit → tag → push 端到端 SOP
|
|
2
|
-
|
|
3
|
-
把"想发个新版本"翻译成可复现的几条命令。本文档假设仓库已有 `release.yml`(`v*` tag 触发,含 release-notes 注入)。
|
|
4
|
-
|
|
5
|
-
## 0. 前置检查
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
# 1. main 干净,没有未提交改动
|
|
9
|
-
git status
|
|
10
|
-
|
|
11
|
-
# 2. 当前 HEAD 与 origin/main 的关系
|
|
12
|
-
git log origin/main..HEAD --oneline
|
|
13
|
-
|
|
14
|
-
# 3. 上一次发布的 tag
|
|
15
|
-
git tag --sort=-version:refname | head -3
|
|
16
|
-
|
|
17
|
-
# 4. CHANGELOG 顶部 [Unreleased] 段落已写好本次内容
|
|
18
|
-
head -30 CHANGELOG.md
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
任意一项异常先解决再发版:工作区不干净 → 先提交,或调用 docs-sync 按其清洁契约处理;`[Unreleased]` 为空 → 先做 docs-sync。禁止用 stash 隐藏待发布改动。
|
|
22
|
-
|
|
23
|
-
## 1. 维护 CHANGELOG
|
|
24
|
-
|
|
25
|
-
`CHANGELOG.md` 顶部必须始终保留 `[Unreleased]` 段落。新版本发布时:
|
|
26
|
-
|
|
27
|
-
1. 把 `[Unreleased]` 内容**整段迁移**到新建的 `## [X.Y.Z] — YYYY-MM-DD` 标题下
|
|
28
|
-
2. 重新插入空的 `[Unreleased]` 段落
|
|
29
|
-
3. 在文件末尾的"版本链接"段更新 compare 链接
|
|
30
|
-
|
|
31
|
-
```diff
|
|
32
|
-
## [Unreleased]
|
|
33
|
-
+
|
|
34
|
-
+---
|
|
35
|
-
+
|
|
36
|
-
+## [0.0.10] — 2026-05-17
|
|
37
|
-
|
|
38
|
-
### Changed
|
|
39
|
-
- ...
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
```diff
|
|
43
|
-
-- [Unreleased](https://github.com/owner/repo/compare/v0.0.9...HEAD)
|
|
44
|
-
+- [Unreleased](https://github.com/owner/repo/compare/v0.0.10...HEAD)
|
|
45
|
-
+- [0.0.10](https://github.com/owner/repo/releases/tag/v0.0.10)
|
|
46
|
-
- [0.0.9](https://github.com/owner/repo/releases/tag/v0.0.9)
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
完整 CHANGELOG 契约见 `<Path>{roots.skills}/docs-sync/references/changelog-contract.md</Path>`。
|
|
50
|
-
|
|
51
|
-
## 2. bump package.json
|
|
52
|
-
|
|
53
|
-
**用 `npm version` 还是手改?**
|
|
54
|
-
|
|
55
|
-
| 场景 | 推荐 |
|
|
56
|
-
|------|------|
|
|
57
|
-
| 单一 package.json,简单 bump | `npm version <major\|minor\|patch> --no-git-tag-version` |
|
|
58
|
-
| Monorepo / 多文件版本号要同步 | 手改 + `pnpm -r`(或脚本) |
|
|
59
|
-
| 已写好 CHANGELOG,只想改 package.json 一处 | 手改 |
|
|
60
|
-
|
|
61
|
-
`--no-git-tag-version` 的关键作用:**只改文件,不自动打 tag**。我们要把 tag 打在 release commit 上,不是在 bump 这一刻。
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
# 等价于手改 package.json 的 version 字段
|
|
65
|
-
npm version 0.0.10 --no-git-tag-version
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## 3. release commit(原子提交)
|
|
69
|
-
|
|
70
|
-
把 `package.json` + `CHANGELOG.md` 一并提交,使用 Conventional Commits 的 `chore(release):` 前缀。
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
git add package.json CHANGELOG.md
|
|
74
|
-
git commit -m "chore(release): v0.0.10" \
|
|
75
|
-
-m "迁移 [Unreleased] 段落到 0.0.10:<一两句关键变更摘要>"
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
**为什么必须是同一个 commit**:
|
|
79
|
-
- tag 要精确指向这个 commit
|
|
80
|
-
- workflow 的 `Verify tag matches package version` 步骤会校验该 commit 上的 `package.json` 版本与 tag 一致
|
|
81
|
-
- 利于后续 `git revert` 一键回滚发布
|
|
82
|
-
|
|
83
|
-
## 4. push main
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
git push origin main
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
push **必须先于** tag,否则远端 tag 会指向 origin 看不到的 commit,GitHub Release 创建时会找不到对应 commit 树。
|
|
90
|
-
|
|
91
|
-
## 5. 打 tag 并精确指向 release commit
|
|
92
|
-
|
|
93
|
-
```bash
|
|
94
|
-
# 显式指定 commit SHA,避免 HEAD 已经向前移动
|
|
95
|
-
RELEASE_COMMIT=$(git log -1 --format=%H --grep="^chore(release): v0.0.10")
|
|
96
|
-
|
|
97
|
-
git tag v0.0.10 "$RELEASE_COMMIT"
|
|
98
|
-
git push origin v0.0.10
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
**为什么要指定 SHA 而不是直接 `git tag v0.0.10`**:
|
|
102
|
-
|
|
103
|
-
如果 release commit 之后又做了几笔 docs / state 推进 commit,`HEAD` 不再是 release commit。直接 `git tag` 会把 tag 打在 docs commit 上,导致:
|
|
104
|
-
|
|
105
|
-
- `Verify tag matches package version` 仍然通过(版本号对得上)
|
|
106
|
-
- 但 `gh release view --json targetCommitish` 指向 docs commit
|
|
107
|
-
- 长远看 release tag 与"真正的发布动作"脱钩
|
|
108
|
-
|
|
109
|
-
简单场景下(release commit 就是 HEAD)直接 `git tag v0.0.10` 也对,但养成显式 SHA 的习惯更稳。
|
|
110
|
-
|
|
111
|
-
## 6. 监控 workflow
|
|
112
|
-
|
|
113
|
-
```bash
|
|
114
|
-
# 实时跟踪
|
|
115
|
-
gh run watch
|
|
116
|
-
|
|
117
|
-
# 或周期性查询
|
|
118
|
-
gh run list --workflow release.yml --limit 3 \
|
|
119
|
-
--json databaseId,status,conclusion,displayTitle,headBranch
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
预期顺序(11 步,缺一不可):
|
|
123
|
-
|
|
124
|
-
1. Set up job
|
|
125
|
-
2. Checkout
|
|
126
|
-
3. Setup environment
|
|
127
|
-
4. **Verify tag matches package version**
|
|
128
|
-
5. Run lint
|
|
129
|
-
6. Run tests
|
|
130
|
-
7. Build
|
|
131
|
-
8. **Verify bin entry**
|
|
132
|
-
9. **Extract release notes from CHANGELOG.md** ← v1.1.0 新增
|
|
133
|
-
10. **Publish to npm**
|
|
134
|
-
11. **Create GitHub Release**
|
|
135
|
-
|
|
136
|
-
## 7. 三端验证
|
|
137
|
-
|
|
138
|
-
完整命令与判定见 [release-pipeline.md](release-pipeline.md) Phase 5(`npm view` / `gh release view` / `gh run view`)。任一缺失视为发布残次品;Release 正文为空时按 [release-notes-injection.md](release-notes-injection.md) 事后修复。
|
|
139
|
-
|
|
140
|
-
## 8. 失败回滚
|
|
141
|
-
|
|
142
|
-
### 8.1 publish 前任意步骤失败
|
|
143
|
-
|
|
144
|
-
最干净:删 tag 重打。
|
|
145
|
-
|
|
146
|
-
```bash
|
|
147
|
-
git tag -d v0.0.10
|
|
148
|
-
git push origin :refs/tags/v0.0.10
|
|
149
|
-
|
|
150
|
-
# 修问题 → 重新 commit(若 release commit 也要改,用 amend)
|
|
151
|
-
git commit --amend
|
|
152
|
-
git push origin main --force-with-lease # 仅当 amend 了已 push 的 commit 时
|
|
153
|
-
|
|
154
|
-
# 重新打
|
|
155
|
-
git tag v0.0.10 <commit-sha>
|
|
156
|
-
git push origin v0.0.10
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### 8.2 publish 已成功但后续步骤失败
|
|
160
|
-
|
|
161
|
-
npm 已上线 → **不要**改版本号,只补后续动作。
|
|
162
|
-
|
|
163
|
-
下面的 `notes_file` 只是一次性传给 `gh --notes-file` 的中间文件;若需要保留发布摘要或 release notes,由调用方写入 `<Path>{roots.state}/commands/{command}/{date}-{scope}-{topic}[-NN].md</Path>`。
|
|
164
|
-
|
|
165
|
-
```bash
|
|
166
|
-
notes_file="$(mktemp -t speculo-release-notes.XXXXXX)"
|
|
167
|
-
trap 'rm -f "$notes_file"' EXIT
|
|
168
|
-
|
|
169
|
-
# 抽取 CHANGELOG 段落
|
|
170
|
-
awk -v v="0.0.10" '...' CHANGELOG.md > "$notes_file"
|
|
171
|
-
|
|
172
|
-
# 手动创建 GitHub Release
|
|
173
|
-
gh release create v0.0.10 --notes-file "$notes_file" --latest
|
|
174
|
-
|
|
175
|
-
# 或编辑已有 Release(如果 action 半成品创建过空 Release)
|
|
176
|
-
gh release edit v0.0.10 --notes-file "$notes_file"
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### 8.3 publish 失败但 npm 上其实有了(罕见)
|
|
180
|
-
|
|
181
|
-
```bash
|
|
182
|
-
# 确认状态
|
|
183
|
-
npm view @scope/pkg@0.0.10 version
|
|
184
|
-
|
|
185
|
-
# 如果有,说明上传完但 npm 返回失败码,直接当作 8.2 处理(已发,只补 Release)
|
|
186
|
-
# 如果无,按 8.1 处理(重打 tag)
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### 8.4 npm 不允许重发同一 version
|
|
190
|
-
|
|
191
|
-
铁律:即使 `npm unpublish` 过,**24h 内同一版本号也不能再发**。若 publish 真上传成功但需要修代码:
|
|
192
|
-
|
|
193
|
-
```bash
|
|
194
|
-
# bump 到下一个补丁
|
|
195
|
-
npm version 0.0.11 --no-git-tag-version
|
|
196
|
-
git add package.json CHANGELOG.md
|
|
197
|
-
git commit -m "chore(release): v0.0.11"
|
|
198
|
-
git push origin main
|
|
199
|
-
git tag v0.0.11
|
|
200
|
-
git push origin v0.0.11
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
## 9. CHECKLIST
|
|
204
|
-
|
|
205
|
-
发版前过一遍:
|
|
206
|
-
|
|
207
|
-
- [ ] `git status` 干净
|
|
208
|
-
- [ ] CHANGELOG 顶部已迁移 `[Unreleased]` 到新版本段
|
|
209
|
-
- [ ] 底部 compare 链接已更新
|
|
210
|
-
- [ ] `package.json` version 与即将打的 tag 严格一致(去掉 `v` 前缀)
|
|
211
|
-
- [ ] `pnpm check` 本地通过(lint + test + build)
|
|
212
|
-
- [ ] `pnpm build:check-bin` 通过(若是 CLI 包)
|
|
213
|
-
- [ ] release commit 已 push 到 origin/main
|
|
214
|
-
- [ ] tag 指向 release commit(不是 docs commit / HEAD 漂移后的 commit)
|
|
215
|
-
- [ ] tag 已 push,`gh run list` 看到 workflow 触发
|
|
216
|
-
- [ ] workflow 11 步全 success
|
|
217
|
-
- [ ] 三端验证齐全:npm 有 / Release body 非空 / workflow success
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
# release.yml 完整注解
|
|
2
|
-
|
|
3
|
-
逐行解释 `.github/workflows/release.yml` 中每个选择的原因。
|
|
4
|
-
|
|
5
|
-
## 完整示例
|
|
6
|
-
|
|
7
|
-
```yaml
|
|
8
|
-
name: Release
|
|
9
|
-
|
|
10
|
-
on:
|
|
11
|
-
push:
|
|
12
|
-
tags:
|
|
13
|
-
- 'v*'
|
|
14
|
-
|
|
15
|
-
jobs:
|
|
16
|
-
release:
|
|
17
|
-
runs-on: ubuntu-latest
|
|
18
|
-
name: Release to npm
|
|
19
|
-
permissions:
|
|
20
|
-
id-token: write
|
|
21
|
-
contents: write
|
|
22
|
-
packages: write
|
|
23
|
-
steps:
|
|
24
|
-
- name: Checkout
|
|
25
|
-
uses: actions/checkout@v4
|
|
26
|
-
|
|
27
|
-
- name: Setup environment
|
|
28
|
-
uses: ./.github/actions/setup
|
|
29
|
-
with:
|
|
30
|
-
registry-url: 'https://registry.npmjs.org'
|
|
31
|
-
|
|
32
|
-
- name: Verify tag matches package version
|
|
33
|
-
run: |
|
|
34
|
-
TAG="${{ github.ref }}"
|
|
35
|
-
TAG_VERSION="${TAG#refs/tags/v}"
|
|
36
|
-
PACKAGE_VERSION=$(node -e "console.log(require('./package.json').version)")
|
|
37
|
-
if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then
|
|
38
|
-
echo "Tag version ($TAG_VERSION) does not match package.json version ($PACKAGE_VERSION)"
|
|
39
|
-
exit 1
|
|
40
|
-
fi
|
|
41
|
-
echo "Version verified: v$PACKAGE_VERSION"
|
|
42
|
-
|
|
43
|
-
- name: Run lint
|
|
44
|
-
run: pnpm lint
|
|
45
|
-
|
|
46
|
-
- name: Run tests
|
|
47
|
-
run: pnpm test
|
|
48
|
-
|
|
49
|
-
- name: Build
|
|
50
|
-
run: pnpm build
|
|
51
|
-
|
|
52
|
-
- name: Verify bin entry
|
|
53
|
-
run: node scripts/verify-bin.mjs
|
|
54
|
-
|
|
55
|
-
- name: Extract release notes from CHANGELOG.md
|
|
56
|
-
run: |
|
|
57
|
-
VERSION="${GITHUB_REF_NAME#v}"
|
|
58
|
-
awk -v v="$VERSION" '
|
|
59
|
-
$0 ~ "^## \\["v"\\]" { found=1; print; next }
|
|
60
|
-
found && /^## \[/ { exit }
|
|
61
|
-
found && /^---[[:space:]]*$/ { next }
|
|
62
|
-
found { print }
|
|
63
|
-
' CHANGELOG.md > release-notes.md
|
|
64
|
-
|
|
65
|
-
if [ ! -s release-notes.md ]; then
|
|
66
|
-
echo "::warning::CHANGELOG.md 中未找到 [$VERSION] 段落,回退到自动生成。"
|
|
67
|
-
echo "See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md) for details." > release-notes.md
|
|
68
|
-
fi
|
|
69
|
-
|
|
70
|
-
- name: Publish to npm
|
|
71
|
-
run: npm publish --provenance --access public
|
|
72
|
-
env:
|
|
73
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
74
|
-
|
|
75
|
-
- name: Create GitHub Release
|
|
76
|
-
uses: softprops/action-gh-release@v2
|
|
77
|
-
if: success()
|
|
78
|
-
with:
|
|
79
|
-
tag_name: ${{ github.ref_name }}
|
|
80
|
-
name: Release ${{ github.ref_name }}
|
|
81
|
-
body_path: release-notes.md
|
|
82
|
-
generate_release_notes: true
|
|
83
|
-
draft: false
|
|
84
|
-
prerelease: false
|
|
85
|
-
env:
|
|
86
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## 逐项解释
|
|
90
|
-
|
|
91
|
-
### 触发条件
|
|
92
|
-
|
|
93
|
-
```yaml
|
|
94
|
-
on:
|
|
95
|
-
push:
|
|
96
|
-
tags:
|
|
97
|
-
- 'v*'
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
- 只在 push 带 `v` 开头的 tag 时触发(如 `v0.0.2`、`v1.2.3-rc1`)
|
|
101
|
-
- **不要**同时写 `branches`,否则每次 push main 都会触发 release
|
|
102
|
-
- 如果想预发,用独立的 tag 模式(如 `v*-rc*`)区分
|
|
103
|
-
|
|
104
|
-
### permissions
|
|
105
|
-
|
|
106
|
-
```yaml
|
|
107
|
-
permissions:
|
|
108
|
-
id-token: write
|
|
109
|
-
contents: write
|
|
110
|
-
packages: write
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
| 权限 | 作用 |
|
|
114
|
-
| ------------------ | -------------------------------------------------------------------------------------- |
|
|
115
|
-
| `id-token: write` | **必需**:用于 npm provenance(OIDC 签名) |
|
|
116
|
-
| `contents: write` | **必需**:`softprops/action-gh-release` 创建 GitHub Release 需要 |
|
|
117
|
-
| `packages: write` | 仅在同时发 GitHub Packages 时需要;纯 npm 发布可省略 |
|
|
118
|
-
|
|
119
|
-
默认 `GITHUB_TOKEN` 只有 `contents: read`,这三项必须显式声明。
|
|
120
|
-
|
|
121
|
-
### Checkout
|
|
122
|
-
|
|
123
|
-
```yaml
|
|
124
|
-
- uses: actions/checkout@v4
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
默认浅克隆(`fetch-depth: 1`)。如果发布说明需要完整历史(如 changelog 生成),加 `fetch-depth: 0`。
|
|
128
|
-
|
|
129
|
-
### Setup environment
|
|
130
|
-
|
|
131
|
-
```yaml
|
|
132
|
-
- uses: ./.github/actions/setup
|
|
133
|
-
with:
|
|
134
|
-
registry-url: 'https://registry.npmjs.org'
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
复合 action,通常封装:
|
|
138
|
-
|
|
139
|
-
```yaml
|
|
140
|
-
- uses: actions/setup-node@v4
|
|
141
|
-
with:
|
|
142
|
-
node-version: '24.14.1'
|
|
143
|
-
registry-url: 'https://registry.npmjs.org'
|
|
144
|
-
- uses: pnpm/action-setup@v2
|
|
145
|
-
with:
|
|
146
|
-
version: 9
|
|
147
|
-
- run: pnpm install --frozen-lockfile
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
关键点:`registry-url` **必须**在 `setup-node` 里设置,它会生成 `.npmrc` 并让 `NODE_AUTH_TOKEN` 生效。
|
|
151
|
-
|
|
152
|
-
### Verify tag matches package version
|
|
153
|
-
|
|
154
|
-
防止错发:tag `v0.0.2` 指向一个 `package.json` 版本为 `0.0.1` 的 commit,会导致用户安装到错误版本。
|
|
155
|
-
|
|
156
|
-
失败时 workflow 直接退出,不会执行任何发布动作。
|
|
157
|
-
|
|
158
|
-
### Lint / Test / Build
|
|
159
|
-
|
|
160
|
-
即使本地已经跑过,CI 再跑一遍保证:
|
|
161
|
-
|
|
162
|
-
1. 发布的产物来自干净环境
|
|
163
|
-
2. 锁文件(`pnpm-lock.yaml`)在 CI 下可复现
|
|
164
|
-
3. Build 产物与 tag 对应的源码一致
|
|
165
|
-
|
|
166
|
-
如果 repo 已有一个单独的 `ci.yml` 跑在 push/PR 上,这里仍保留是为了**再次校验 tag 指向的 commit**——tag 可能指向旧 commit。
|
|
167
|
-
|
|
168
|
-
### Verify bin entry
|
|
169
|
-
|
|
170
|
-
```yaml
|
|
171
|
-
- run: node scripts/verify-bin.mjs
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
CLI 包专有:确认 `dist/cli/index.js` 存在、有 shebang、可执行。防止 tsconfig 出问题导致产物不完整。
|
|
175
|
-
|
|
176
|
-
### Extract release notes from CHANGELOG.md
|
|
177
|
-
|
|
178
|
-
```yaml
|
|
179
|
-
- name: Extract release notes from CHANGELOG.md
|
|
180
|
-
run: |
|
|
181
|
-
VERSION="${GITHUB_REF_NAME#v}"
|
|
182
|
-
awk -v v="$VERSION" '
|
|
183
|
-
$0 ~ "^## \\["v"\\]" { found=1; print; next }
|
|
184
|
-
found && /^## \[/ { exit }
|
|
185
|
-
found && /^---[[:space:]]*$/ { next }
|
|
186
|
-
found { print }
|
|
187
|
-
' CHANGELOG.md > release-notes.md
|
|
188
|
-
|
|
189
|
-
if [ ! -s release-notes.md ]; then
|
|
190
|
-
echo "::warning::..." >&2
|
|
191
|
-
echo "See [CHANGELOG.md](...) for details." > release-notes.md
|
|
192
|
-
fi
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
把 `## [X.Y.Z]` 段落抽到 `release-notes.md`,让下面的 `softprops/action-gh-release` 通过 `body_path` 注入到 GitHub Release 正文。
|
|
196
|
-
|
|
197
|
-
**为什么必须有这一步**:仅用 `generate_release_notes: true` 时,GitHub 内部从 commit + PR + label 抽取 changelog;直 push main 流程没 PR,生成器只剩 `Full Changelog: vA...vB` 一行。CHANGELOG 是事实来源,注入它才能让 Release 正文非空。
|
|
198
|
-
|
|
199
|
-
**awk 规则细节**与边界用例(包含 `[Unreleased]` 同时存在、版本号含连字符 / 多语言 CHANGELOG / `body_path` + `generate_release_notes` 共存语义)见 [release-notes-injection.md](release-notes-injection.md)。
|
|
200
|
-
|
|
201
|
-
### Publish to npm
|
|
202
|
-
|
|
203
|
-
```yaml
|
|
204
|
-
- run: npm publish --provenance --access public
|
|
205
|
-
env:
|
|
206
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
- `--provenance`:生成 sigstore 签名,要求 `id-token: write`
|
|
210
|
-
- `--access public`:scoped 包首次发布必需;非 scoped 包加了也不会错
|
|
211
|
-
- **位置在 GitHub Release 之前**:npm publish 不可回滚,如果后续步骤失败,至少 npm 发布是成功的
|
|
212
|
-
|
|
213
|
-
### Create GitHub Release
|
|
214
|
-
|
|
215
|
-
```yaml
|
|
216
|
-
- uses: softprops/action-gh-release@v2
|
|
217
|
-
if: success()
|
|
218
|
-
with:
|
|
219
|
-
tag_name: ${{ github.ref_name }}
|
|
220
|
-
body_path: release-notes.md
|
|
221
|
-
generate_release_notes: true
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
- `if: success()`:只在 npm publish 成功后执行
|
|
225
|
-
- `body_path: release-notes.md`:**正文头部**注入上一步抽取的 CHANGELOG 段落
|
|
226
|
-
- `generate_release_notes: true`:**正文尾部**附 GitHub 自动生成的 What's Changed + Full Changelog compare 链接
|
|
227
|
-
- 两者共存不冲突,效果是「人写的精炼说明 + 机器可追溯的索引」叠加
|
|
228
|
-
- `softprops/action-gh-release@v2` 比废弃的 `actions/create-release@v1` 更稳定
|
|
229
|
-
|
|
230
|
-
仅用 `generate_release_notes: true` 而不注入 `body_path`,在没有 PR 流量的仓库会得到几乎为空的 Release 正文,详见 [release-notes-injection.md](release-notes-injection.md) §1。
|
|
231
|
-
|
|
232
|
-
## 常见变体
|
|
233
|
-
|
|
234
|
-
### 使用 pnpm publish 代替 npm publish
|
|
235
|
-
|
|
236
|
-
pnpm 也支持 `--access public --provenance`:
|
|
237
|
-
|
|
238
|
-
```yaml
|
|
239
|
-
- run: pnpm publish --provenance --access public --no-git-checks
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
`--no-git-checks` 避免 pnpm 检查当前 branch/tag(CI 中 detached HEAD 会被拒)。
|
|
243
|
-
|
|
244
|
-
### 发布到 GitHub Packages(可选)
|
|
245
|
-
|
|
246
|
-
```yaml
|
|
247
|
-
- name: Publish to GitHub Packages
|
|
248
|
-
run: npm publish --registry=https://npm.pkg.github.com
|
|
249
|
-
env:
|
|
250
|
-
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
需要单独的 setup step 指向 GitHub Packages,且 package 名必须是 `@owner/name` 形式。
|
|
254
|
-
|
|
255
|
-
### Dry run(在 PR 里预演)
|
|
256
|
-
|
|
257
|
-
```yaml
|
|
258
|
-
- run: npm publish --dry-run --access public
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
放在一个仅 PR 触发的 workflow 里,帮你在合并前发现 tarball 问题。
|
|
262
|
-
|
|
263
|
-
## 不推荐的做法
|
|
264
|
-
|
|
265
|
-
- 在同一个 job 里既跑 push-to-main CI 又跑 release — 混乱,难以调试
|
|
266
|
-
- 使用 `actions/create-release@v1` — 已废弃,推荐 `softprops/action-gh-release`
|
|
267
|
-
- 用 `secrets.NPM_TOKEN` 直接传给 `npm publish` 命令行 — 会在日志里泄露
|
|
268
|
-
- 跳过 `prepublishOnly` 或 CI 测试 — 已知会发布过坏包
|
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { execFile } from "node:child_process";
|
|
4
|
-
import { readFile } from "node:fs/promises";
|
|
5
|
-
import { promisify } from "node:util";
|
|
6
|
-
|
|
7
|
-
const execFileAsync = promisify(execFile);
|
|
8
|
-
|
|
9
|
-
function parseArgs(argv) {
|
|
10
|
-
const [operation, ...rest] = argv;
|
|
11
|
-
const options = { labels: [] };
|
|
12
|
-
for (let index = 0; index < rest.length; index += 1) {
|
|
13
|
-
const token = rest[index];
|
|
14
|
-
if (token === "--apply") {
|
|
15
|
-
options.apply = true;
|
|
16
|
-
continue;
|
|
17
|
-
}
|
|
18
|
-
if (!token.startsWith("--")) throw new Error(`unexpected argument: ${token}`);
|
|
19
|
-
const key = token.slice(2).replaceAll("-", "_");
|
|
20
|
-
const value = rest[index + 1];
|
|
21
|
-
if (value === undefined || value.startsWith("--")) {
|
|
22
|
-
throw new Error(`${token} requires a value`);
|
|
23
|
-
}
|
|
24
|
-
index += 1;
|
|
25
|
-
if (key === "label") options.labels.push(value);
|
|
26
|
-
else options[key] = value;
|
|
27
|
-
}
|
|
28
|
-
return { operation, options };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function requireOptions(options, keys) {
|
|
32
|
-
const missing = keys.filter((key) => !String(options[key] ?? "").trim());
|
|
33
|
-
if (missing.length) throw new Error(`missing options: ${missing.map((key) => `--${key.replaceAll("_", "-")}`).join(", ")}`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function gh(args) {
|
|
37
|
-
try {
|
|
38
|
-
const { stdout } = await execFileAsync("gh", args, {
|
|
39
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
40
|
-
encoding: "utf8",
|
|
41
|
-
});
|
|
42
|
-
return stdout.trim();
|
|
43
|
-
} catch (error) {
|
|
44
|
-
const detail = String(error?.stderr || error?.message || error).trim();
|
|
45
|
-
throw new Error(`gh ${args[0]} failed: ${detail}`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function readIssue(repo, number) {
|
|
50
|
-
return JSON.parse(
|
|
51
|
-
await gh([
|
|
52
|
-
"issue",
|
|
53
|
-
"view",
|
|
54
|
-
number,
|
|
55
|
-
"--repo",
|
|
56
|
-
repo,
|
|
57
|
-
"--json",
|
|
58
|
-
"number,title,body,state,url,author,labels,createdAt,updatedAt,comments",
|
|
59
|
-
]),
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function issueRead(options) {
|
|
64
|
-
requireOptions(options, ["repo", "number"]);
|
|
65
|
-
const issue = await readIssue(options.repo, options.number);
|
|
66
|
-
return {
|
|
67
|
-
operation: "issue-read",
|
|
68
|
-
provider: "github",
|
|
69
|
-
repo: options.repo,
|
|
70
|
-
kind: "issue",
|
|
71
|
-
...issue,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function prRead(options) {
|
|
76
|
-
requireOptions(options, ["repo", "number"]);
|
|
77
|
-
const pull = JSON.parse(
|
|
78
|
-
await gh([
|
|
79
|
-
"pr",
|
|
80
|
-
"view",
|
|
81
|
-
options.number,
|
|
82
|
-
"--repo",
|
|
83
|
-
options.repo,
|
|
84
|
-
"--json",
|
|
85
|
-
"number,title,body,state,url,author,labels,createdAt,updatedAt,comments,baseRefName,baseRefOid,headRefName,headRefOid,files",
|
|
86
|
-
]),
|
|
87
|
-
);
|
|
88
|
-
return {
|
|
89
|
-
operation: "pr-read",
|
|
90
|
-
provider: "github",
|
|
91
|
-
repo: options.repo,
|
|
92
|
-
kind: "pull-request",
|
|
93
|
-
...pull,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async function issueSearch(options) {
|
|
98
|
-
requireOptions(options, ["repo", "query"]);
|
|
99
|
-
const issues = JSON.parse(
|
|
100
|
-
await gh([
|
|
101
|
-
"issue",
|
|
102
|
-
"list",
|
|
103
|
-
"--repo",
|
|
104
|
-
options.repo,
|
|
105
|
-
"--search",
|
|
106
|
-
options.query,
|
|
107
|
-
"--state",
|
|
108
|
-
options.state || "all",
|
|
109
|
-
"--limit",
|
|
110
|
-
options.limit || "20",
|
|
111
|
-
"--json",
|
|
112
|
-
"number,title,state,url,labels,updatedAt",
|
|
113
|
-
]),
|
|
114
|
-
);
|
|
115
|
-
return { operation: "issue-search", provider: "github", repo: options.repo, issues };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async function issueCreate(options) {
|
|
119
|
-
requireOptions(options, ["repo", "title", "body_file"]);
|
|
120
|
-
const plan = {
|
|
121
|
-
operation: "issue-create",
|
|
122
|
-
provider: "github",
|
|
123
|
-
repo: options.repo,
|
|
124
|
-
title: options.title,
|
|
125
|
-
body_file: options.body_file,
|
|
126
|
-
labels: options.labels,
|
|
127
|
-
mode: options.apply ? "apply" : "dry-run",
|
|
128
|
-
};
|
|
129
|
-
if (!options.apply) return plan;
|
|
130
|
-
|
|
131
|
-
await readFile(options.body_file, "utf8");
|
|
132
|
-
const args = [
|
|
133
|
-
"issue",
|
|
134
|
-
"create",
|
|
135
|
-
"--repo",
|
|
136
|
-
options.repo,
|
|
137
|
-
"--title",
|
|
138
|
-
options.title,
|
|
139
|
-
"--body-file",
|
|
140
|
-
options.body_file,
|
|
141
|
-
];
|
|
142
|
-
for (const label of options.labels) args.push("--label", label);
|
|
143
|
-
return { ...plan, url: await gh(args), status: "created" };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
async function issueCommentClose(options) {
|
|
147
|
-
requireOptions(options, ["repo", "number", "comment_file", "marker"]);
|
|
148
|
-
const marker = `<!-- ${options.marker} -->`;
|
|
149
|
-
const comment = (await readFile(options.comment_file, "utf8")).trim();
|
|
150
|
-
const before = await readIssue(options.repo, options.number);
|
|
151
|
-
const markerExists = (before.comments ?? []).some((item) =>
|
|
152
|
-
String(item?.body ?? "").includes(marker),
|
|
153
|
-
);
|
|
154
|
-
const plan = {
|
|
155
|
-
operation: "issue-comment-close",
|
|
156
|
-
provider: "github",
|
|
157
|
-
repo: options.repo,
|
|
158
|
-
number: Number(options.number),
|
|
159
|
-
url: before.url,
|
|
160
|
-
mode: options.apply ? "apply" : "dry-run",
|
|
161
|
-
state_before: before.state,
|
|
162
|
-
comment_required: !markerExists,
|
|
163
|
-
close_required: String(before.state).toUpperCase() !== "CLOSED",
|
|
164
|
-
marker: options.marker,
|
|
165
|
-
steps: [],
|
|
166
|
-
};
|
|
167
|
-
if (!options.apply) return plan;
|
|
168
|
-
|
|
169
|
-
if (!markerExists) {
|
|
170
|
-
await gh([
|
|
171
|
-
"issue",
|
|
172
|
-
"comment",
|
|
173
|
-
options.number,
|
|
174
|
-
"--repo",
|
|
175
|
-
options.repo,
|
|
176
|
-
"--body",
|
|
177
|
-
`${comment}\n\n${marker}`,
|
|
178
|
-
]);
|
|
179
|
-
plan.steps.push("commented");
|
|
180
|
-
} else {
|
|
181
|
-
plan.steps.push("comment-already-present");
|
|
182
|
-
}
|
|
183
|
-
if (String(before.state).toUpperCase() !== "CLOSED") {
|
|
184
|
-
await gh([
|
|
185
|
-
"issue",
|
|
186
|
-
"close",
|
|
187
|
-
options.number,
|
|
188
|
-
"--repo",
|
|
189
|
-
options.repo,
|
|
190
|
-
"--reason",
|
|
191
|
-
options.reason || "completed",
|
|
192
|
-
]);
|
|
193
|
-
plan.steps.push("closed");
|
|
194
|
-
} else {
|
|
195
|
-
plan.steps.push("already-closed");
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const after = await readIssue(options.repo, options.number);
|
|
199
|
-
if (String(after.state).toUpperCase() !== "CLOSED") {
|
|
200
|
-
throw new Error(`issue remained ${after.state} after close operation`);
|
|
201
|
-
}
|
|
202
|
-
return { ...plan, state_after: after.state, status: "closed" };
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function usage() {
|
|
206
|
-
return `Usage:
|
|
207
|
-
issue-transport.mjs issue-read --repo OWNER/REPO --number N
|
|
208
|
-
issue-transport.mjs pr-read --repo OWNER/REPO --number N
|
|
209
|
-
issue-transport.mjs issue-search --repo OWNER/REPO --query TEXT [--state all] [--limit 20]
|
|
210
|
-
issue-transport.mjs issue-create --repo OWNER/REPO --title TEXT --body-file PATH [--label LABEL] [--apply]
|
|
211
|
-
issue-transport.mjs issue-comment-close --repo OWNER/REPO --number N --comment-file PATH --marker ID [--reason completed] [--apply]`;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
try {
|
|
215
|
-
const { operation, options } = parseArgs(process.argv.slice(2));
|
|
216
|
-
if (!operation || ["help", "--help", "-h"].includes(operation)) {
|
|
217
|
-
console.log(usage());
|
|
218
|
-
} else {
|
|
219
|
-
const handlers = { issueRead, prRead, issueSearch, issueCreate, issueCommentClose };
|
|
220
|
-
const handler = handlers[operation.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())];
|
|
221
|
-
if (!handler) throw new Error(`unknown operation: ${operation}\n${usage()}`);
|
|
222
|
-
console.log(JSON.stringify(await handler(options), null, 2));
|
|
223
|
-
}
|
|
224
|
-
} catch (error) {
|
|
225
|
-
console.error(`ERROR: ${error.message}`);
|
|
226
|
-
process.exitCode = 1;
|
|
227
|
-
}
|