@peiyanlu/release 2.0.0 → 2.1.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/README.md CHANGED
@@ -36,7 +36,7 @@
36
36
 
37
37
  ## 环境要求
38
38
 
39
- - Node.js 20 或更新版本
39
+ - Node.js 22.18+
40
40
  - Git 仓库
41
41
  - npm 发布需要可用的 npm 身份验证;GitHub API 操作需要 GitHub token
42
42
 
@@ -53,7 +53,7 @@ pnpm add -D @peiyanlu/release
53
53
  release [release-type] [options]
54
54
  ```
55
55
 
56
- 不指定 `release-type` 时,工具根据提交记录计算升级类型。可指定 `major`、`minor`、`patch` 或 `prerelease`。
56
+ 不指定 `release-type` 时,会根据提交记录推断并预选升级类型;CI 模式下自动采用该类型。可指定 `major`、`minor`、`patch` 或 `prerelease`。
57
57
 
58
58
  ```sh
59
59
  pnpm exec release --dry-run
@@ -63,15 +63,16 @@ pnpm exec release --ci
63
63
 
64
64
  常用选项:
65
65
 
66
- | 选项 | 说明 |
67
- |------------------|--------------------------|
68
- | `-n, --dry-run` | 预览发布步骤,不写入变更 |
69
- | `--ci` | CI 模式,关闭交互式流程 |
70
- | `--skip-git` | 跳过 Git 检查和操作 |
71
- | `--skip-publish` | 跳过包发布 |
72
- | `--skip-release` | 跳过仓库 Release |
73
- | `-v, --version` | 显示版本 |
74
- | `-h, --help` | 显示完整帮助 |
66
+ | 选项 | 说明 |
67
+ |------------------|----------------------------------------------------|
68
+ | `-n, --dry-run` | 预览发布步骤,不写入变更 |
69
+ | `--ci` | CI 模式,关闭交互式流程 |
70
+ | `--skip-bump` | 跳过项目版本更新 |
71
+ | `--skip-git` | 跳过 Git 提交、标签、推送;版本推断仍读取 Git 历史 |
72
+ | `--skip-publish` | 跳过项目发布 |
73
+ | `--skip-release` | 跳过仓库托管服务上创建发布 |
74
+ | `-v, --version` | 显示版本 |
75
+ | `-h, --help` | 显示完整帮助 |
75
76
 
76
77
  ## 配置
77
78
 
@@ -89,7 +90,6 @@ export default defineConfig({
89
90
  hosting: new GithubHosting({
90
91
  token: process.env.GITHUB_TOKEN,
91
92
  }),
92
- checkout: { branch: 'release' },
93
93
  bump: { prerelease: 'beta' },
94
94
  commit: { sign: false },
95
95
  publish: { tag: 'latest' },
@@ -117,10 +117,11 @@ export default defineConfig({
117
117
 
118
118
  ## GitHub Action
119
119
 
120
- 本仓库也可作为 Action 使用。Action 要求配置文件定义 `project`,并使用 `github-token` 访问 GitHub API。完整工作流默认值为 `full`。
120
+ 本仓库也可作为 Action 使用。Action 要求配置文件定义 `project`,并使用 `github-token` 访问 GitHub API。`workflow` 的默认值为 `full`。
121
121
 
122
122
  ```yaml
123
123
  name: Release
124
+
124
125
  on:
125
126
  issue_comment:
126
127
  types: [ created, deleted ]
@@ -140,26 +141,160 @@ jobs:
140
141
  with:
141
142
  fetch-depth: 0
142
143
  fetch-tags: true
143
-
144
+
144
145
  - uses: pnpm/action-setup@v6
145
146
  with:
146
147
  version: 12
147
-
148
+
148
149
  - uses: actions/setup-node@v7
149
150
  with:
150
151
  node-version: 24
151
152
  registry-url: https://registry.npmjs.org/
152
-
153
+ package-manager-cache: false
154
+
153
155
  - run: pnpm install --frozen-lockfile
154
-
155
- - uses: peiyanlu/release@v2
156
+
157
+ - uses: peiyanlu/release@master
156
158
  with:
157
159
  workflow: full
158
160
  github-token: ${{ secrets.GITHUB_TOKEN }}
159
161
  npm-token: ${{ secrets.NPM_TOKEN }}
160
162
  ```
161
163
 
162
- 确保仓库中已提交 `release.config.ts`,Action 能在 checkout 的项目中加载它。若发布到 npm,将 `npm-token` 配置为仓库 secret;它会作为 `NODE_AUTH_TOKEN` 提供给发布步骤。也可在仓库配置 npm Trusted Publishing。
164
+ 确保仓库中已提交 `release.config.ts`,Action 会从检出的项目中加载该文件。若发布到 npm,将 `npm-token` 配置为仓库 secret;它会作为 `NODE_AUTH_TOKEN` 提供给发布步骤。也可在仓库配置 npm Trusted Publishing。
165
+
166
+ ### Pull requests
167
+
168
+ 在仓库中 `Settings → Actions → General → Workflow permissions` 启用 `Allow GitHub Actions to create and approve pull requests`。
169
+
170
+ ### npm Trusted Publishing
171
+
172
+ [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers/) 使用 GitHub Actions 的 OIDC 身份发布 npm 包,不需要长期有效的 `NPM_TOKEN`。
173
+
174
+ 1. **确认条件**:包已至少发布过一个版本,使用公开 npm registry 和 GitHub 托管的 runner,在 package.json 中提供 `repository.url`。Trusted Publisher 按包和 workflow 文件分别注册。
175
+ 2. **在 npm 注册发布者**:打开 [npmjs.com](https://www.npmjs.com/) 上对应包的 **Settings → Trusted Publisher → GitHub Actions**:填写 GitHub 用户或组织名、仓库名和 workflow 文件名(例如 `release.yml`),勾选 `Allow npm publish`。
176
+ 如果 workflow 使用 GitHub environment,也填写相同的 environment 名称。
177
+ 3. **调整发布 job**:在发布 job 中授予 `id-token: write` 权限,使用 Node.js 22.14.0 或更高版本,以确保 npm 版本为 11.5.1 或更高版本,并从 `actions/setup-node` 移除 `registry-url`(v7 以下),从 Action step 输入中移除 `npm-token`。例如:
178
+
179
+ ```yaml
180
+ name: Release
181
+
182
+ on:
183
+ issue_comment:
184
+ types: [ created, deleted ]
185
+ push:
186
+ branches:
187
+ - master
188
+
189
+ permissions:
190
+ contents: write
191
+ pull-requests: write
192
+ id-token: write # 新增
193
+
194
+ jobs:
195
+ release:
196
+ runs-on: ubuntu-latest
197
+ steps:
198
+ - uses: actions/checkout@v7
199
+ with:
200
+ fetch-depth: 0
201
+ fetch-tags: true
202
+
203
+ - uses: pnpm/action-setup@v6
204
+ with:
205
+ version: 12
206
+
207
+ - uses: actions/setup-node@v7
208
+ with:
209
+ node-version: 24 # npm 11.5.1+
210
+ registry-url: https://registry.npmjs.org/ # v7 以下移除
211
+ package-manager-cache: false
212
+
213
+ - run: pnpm install --frozen-lockfile
214
+
215
+ - uses: peiyanlu/release@master
216
+ with:
217
+ workflow: full
218
+ github-token: ${{ secrets.GITHUB_TOKEN }}
219
+ #npm-token: ${{ secrets.NPM_TOKEN }} # 移除
220
+ ```
221
+
222
+ 配置正确后,npm/pnpm 会自动通过 OIDC 认证,公开仓库发布的版本还会自动附带 provenance(来源证明),无需额外开启。
223
+
224
+ 若出现 `ENEEDAUTH`:
225
+ 核对 npm 包是否已存在、注册的仓库和 workflow 文件名、GitHub environment,以及发布 job 是否有 `id-token: write` 并使用满足版本要求的 npm CLI。
226
+
227
+ ### Snapshots
228
+
229
+ 为 `workflow_dispatch` 添加 `snapshot` 字符串输入:手动运行时输入非空值会执行快照 job;push 和 issue comment 事件没有该输入,因此仍按原有流程执行。
230
+
231
+ ```yaml
232
+ name: Release
233
+
234
+ on:
235
+ issue_comment:
236
+ types: [ created, deleted ]
237
+ push:
238
+ branches:
239
+ - master
240
+ workflow_dispatch:
241
+ inputs:
242
+ snapshot:
243
+ description: Snapshot publish tag.
244
+ type: string
245
+
246
+ jobs:
247
+ check:
248
+ if: inputs.snapshot == ''
249
+ # 保留现有 check job 的其余配置
250
+
251
+ pull-request:
252
+ needs: check
253
+ if: needs.check.outputs.workflow == 'pull-request'
254
+ # 保留现有 pull-request job 的其余配置
255
+
256
+ release:
257
+ needs: check
258
+ if: needs.check.outputs.workflow == 'release'
259
+ # 保留现有 release job 的其余配置
260
+
261
+ snapshot:
262
+ if: inputs.snapshot != ''
263
+ name: Snapshot
264
+ runs-on: ubuntu-latest
265
+ permissions:
266
+ contents: read
267
+ id-token: write
268
+ steps:
269
+ - uses: actions/checkout@v7
270
+ with:
271
+ fetch-depth: 0
272
+ fetch-tags: true
273
+
274
+ - uses: pnpm/action-setup@v6
275
+ with:
276
+ version: 12
277
+
278
+ - uses: actions/setup-node@v7
279
+ with:
280
+ node-version: 24
281
+ registry-url: https://registry.npmjs.org/
282
+ package-manager-cache: false
283
+
284
+ - run: pnpm install --frozen-lockfile
285
+
286
+ - uses: peiyanlu/release@master
287
+ with:
288
+ workflow: snapshot
289
+ github-token: ${{ secrets.GITHUB_TOKEN }}
290
+ bump-snapshot: ${{ inputs.snapshot }}
291
+ ```
292
+
293
+ 在 GitHub Actions 页面手动运行 workflow 并填写 snapshot 为 canary:此时 `check`、`pull-request` 和 `release` 不执行,快照 job 发布 `1.2.0-canary.<timestamp>`。
294
+
295
+ Trusted Publisher 注册绑定到具体 workflow 文件。要让快照发布也使用同一注册,建议将快照 job 放在已注册的 workflow 中,而不是另建 workflow。
296
+
297
+ ### 参数
163
298
 
164
299
  `workflow` 支持:
165
300
 
package/dist/action.mjs CHANGED
@@ -2,19 +2,22 @@ import { getBooleanInput, getInput, setFailed, setOutput } from "@actions/core";
2
2
  import { context, getOctokit } from "@actions/github";
3
3
  import { assert, dedent, isBoolean, isEmpty, isIndex, isNotEmpty, isNull, isString, isUndefined, newline, sleep, throwError, tryCall } from "@peiyanlu/ts-utils";
4
4
  import { existsSync, readFileSync } from "node:fs";
5
- import { parse, resolve } from "node:path";
6
- import { pathToFileURL } from "node:url";
7
- import { getGithubUrl, gitAdd, gitBranch, gitBranchCurrent, gitCheckoutBranch, gitCheckoutBranchForce, gitCommitMessage, gitConfigGet, gitConfigSet, gitFetch, gitLog, gitLsRemote, gitPush, gitPushBranch, gitRevParse, gitTag, parseGitHubRepo } from "@peiyanlu/cli-utils";
8
- import "semver";
9
- import { Octokit } from "@octokit/rest";
10
- import open from "open";
11
- import { glob } from "tinyglobby";
12
- import "@peiyanlu/node-utils";
5
+ import path, { join, parse, resolve } from "node:path";
6
+ import { fileURLToPath, pathToFileURL } from "node:url";
7
+ import { getGithubUrl, gitAdd, gitBranch, gitBranchCurrent, gitCheckoutBranch, gitCheckoutBranchForce, gitCommitMessage, gitConfigSet, gitFetch, gitLog, gitLsRemote, gitPush, gitPushBranch, gitRevParse, gitTag } from "@peiyanlu/cli-utils";
8
+ import { readJsonFileSync } from "@peiyanlu/node-utils";
13
9
  import "conventional-changelog";
14
10
  import "conventional-changelog-conventionalcommits";
15
11
  import "conventional-recommended-bump";
16
12
  import "@conventional-changelog/template";
17
- //#region src/config/index.ts
13
+ import "semver";
14
+ import { Octokit } from "@octokit/rest";
15
+ import open from "open";
16
+ import { glob } from "tinyglobby";
17
+ import "yaml";
18
+ import "@clack/prompts";
19
+ import "ansis";
20
+ // src/config/index.ts
18
21
  const loadJavaScriptFile = async (file) => {
19
22
  const { href } = pathToFileURL(file);
20
23
  const mod = await import(href);
@@ -43,132 +46,12 @@ const loadConfig = async (cwd = process.cwd()) => {
43
46
  config: await loadJavaScriptFile(file)
44
47
  };
45
48
  };
46
- //#endregion
47
- //#region src/releaser-github-action/comment.ts
48
- const ALLOWED_AUTHOR_ASSOCIATIONS = [
49
- "OWNER",
50
- "COLLABORATOR",
51
- "MEMBER"
52
- ];
53
- const JSON_START = "```json";
54
- const JSON_START_OFFSET = 7;
55
- const JSON_END = "```";
56
- /** 所有支持的拉取请求评论命令 */
57
- const COMMANDS = ["!release/set-options", "!release/set-preamble"];
58
- /**
59
- * 判断正文是否以命令开头,并且命令后紧跟空白字符或正文结束。
60
- * 这样可以避免将更长的标记(例如 `!release/set-options-foo`)误判为匹配的命令
61
- */
62
- const startsWithCommand = (body, command) => {
63
- if (!body.startsWith(command)) return false;
64
- const nextChar = body[command.length];
65
- return isUndefined(nextChar) || /\s/.test(nextChar);
66
- };
67
- /** 评论内容是否是支持的命令评论之一 */
68
- const isCommandComment = (body) => isString(body) && COMMANDS.some((command) => startsWithCommand(body, command));
69
- /** 解析 releaser 参数 */
70
- const parseSetOptionsComment = (comment) => {
71
- if (ALLOWED_AUTHOR_ASSOCIATIONS.includes(comment.author_association)) {
72
- const { body } = comment;
73
- if (body && startsWithCommand(body, "!release/set-options")) {
74
- const start = body.indexOf(JSON_START);
75
- const end = isIndex(start) ? body.indexOf(JSON_END, start + JSON_START_OFFSET) : -1;
76
- if (!isIndex(start) || !isIndex(end)) return;
77
- return body.substring(start + JSON_START_OFFSET, end).trim();
78
- }
79
- }
80
- };
81
- /** 解析日志前言 */
82
- function parseSetPreambleComment(comment) {
83
- if (ALLOWED_AUTHOR_ASSOCIATIONS.includes(comment.author_association)) {
84
- const { body } = comment;
85
- if (body && startsWithCommand(body, "!release/set-preamble")) {
86
- const newlineIndex = body.indexOf("\n");
87
- const firstLine = isIndex(newlineIndex) ? body.slice(0, newlineIndex) : body;
88
- const secondLine = isIndex(newlineIndex) ? body.slice(newlineIndex + 1).trim() : "";
89
- const name = firstLine.slice(21).replace(/`/g, "").trim().split(/\s+/)[0];
90
- const preamble = secondLine;
91
- if (preamble) return {
92
- name,
93
- preamble
94
- };
95
- }
96
- }
97
- return null;
98
- }
99
- //#endregion
100
- //#region src/releaser-github-action/conditions.ts
101
- const ifReleaseCommit = async (releaser) => {
102
- const { project, options: { dryRun } } = releaser;
103
- const logger = releaser.logger.createChild("if-release-commit");
104
- if (!(await gitLog(["-1", "--pretty=%B"]))?.startsWith("chore(release):")) {
105
- logger.verbose("Skipping non-release commit.");
106
- return false;
107
- }
108
- logger.verbose("Checking existing release tags...");
109
- if (!dryRun) await gitFetch(["--tags"]);
110
- const tags = await project.getTags();
111
- const canRelease = tags.length > 0;
112
- if (canRelease) {
113
- logger.verbose(`Found release tags:`);
114
- for (const tag of tags) logger.verbose(`- ${tag}`);
115
- } else logger.verbose(`No release tags found.`);
116
- return canRelease;
117
- };
118
- /**
119
- * 检测当前事件是否为针对处于打开状态的发布拉取请求的受支持命令评论:
120
- * (`!release/set-options` 或 `!release/set-preamble`)
121
- *
122
- * @returns 如果是此类评论,则返回 `目标分支`;如果是评论事件但不是命令评论,则返回 `false`;如果根本不是评论事件,则返回 `null`
123
- */
124
- const ifCommandComment = () => {
125
- const { eventName, payload: { issue, comment } } = context;
126
- if (eventName !== "issue_comment") return null;
127
- const isPullRequest = issue?.pull_request;
128
- const issueAuthor = (issue?.user)?.login;
129
- const issueBody = issue?.body;
130
- const issueState = issue?.state;
131
- const commentBody = comment?.body;
132
- if (isPullRequest && issueAuthor === "github-actions[bot]" && issueState === "open" && issueBody?.includes("release-pull-request: true") && isCommandComment(commentBody)) {
133
- const matches = issueBody.match(/release-branch-to:\s*(\S+)/);
134
- return matches ? matches[1] : false;
135
- }
136
- return false;
137
- };
138
- //#endregion
139
- //#region src/releaser-github-action/options.ts
140
- const omitEmpty = (object) => {
141
- const result = {};
142
- for (const [key, value] of Object.entries(object)) if (isBoolean(value) || Boolean(value)) result[key] = value;
143
- return result;
144
- };
145
- const getOptionalBooleanInput = (name) => getInput(name) ? getBooleanInput(name) : void 0;
146
- const getOptionalJsonInput = (name) => {
147
- const value = getInput(name);
148
- return value ? JSON.parse(value) : void 0;
149
- };
150
- const getInputOptions = () => ({
151
- checkout: omitEmpty({ branch: getInput("checkout-branch") }),
152
- bump: omitEmpty({
153
- version: getInput("bump-version"),
154
- as: getInput("bump-as"),
155
- prerelease: getInput("bump-prerelease"),
156
- snapshot: getInput("bump-snapshot"),
157
- firstRelease: getOptionalBooleanInput("bump-first-release"),
158
- byProject: getOptionalJsonInput("bump-by-project")
159
- }),
160
- maintenanceBranch: omitEmpty({ enabled: getOptionalBooleanInput("maintenance-branch-enabled") }),
161
- publish: omitEmpty({
162
- access: getInput("publish-access"),
163
- tag: getInput("publish-tag")
164
- })
165
- });
166
- const getInputSkippedSteps = () => ({
167
- bump: getOptionalBooleanInput("bump-skip"),
168
- publish: getOptionalBooleanInput("publish-skip")
169
- });
170
- //#endregion
171
- //#region src/core/logger.ts
49
+ // src/core/hosting/hosting.ts
50
+ /** Git 仓库托管服务的基类 */
51
+ var GitRepositoryHosting = class {};
52
+ // src/core/utils.ts
53
+ const when = (condition, ...value) => condition ? [...value] : [];
54
+ // src/core/logger.ts
172
55
  const defaultPrinter = ({ dryRun, stage, message }) => {
173
56
  console.log(`${dryRun ? `[dry-run]` : ""}[${stage}]:`, message);
174
57
  };
@@ -248,11 +131,7 @@ var Logger = class {
248
131
  return color;
249
132
  }
250
133
  };
251
- //#endregion
252
- //#region src/core/utils.ts
253
- const when = (condition, ...value) => condition ? [...value] : [];
254
- //#endregion
255
- //#region src/core/releaser.ts
134
+ // src/core/releaser.ts
256
135
  var Releaser = class {
257
136
  options;
258
137
  project;
@@ -330,7 +209,7 @@ var Releaser = class {
330
209
  logger.info("Skipping checkout.");
331
210
  return;
332
211
  }
333
- logger.info(`Checking out branch ${headBranch}...`);
212
+ logger.info(`Checking out branch "${headBranch}"...`);
334
213
  this.state.headBranch = headBranch;
335
214
  this.state.baseBranch = await this.getBaseBranch();
336
215
  if (fetch) {
@@ -345,10 +224,10 @@ var Releaser = class {
345
224
  ]);
346
225
  }
347
226
  if (force) {
348
- logger.verbose(`Force checking out branch ${headBranch}...`);
227
+ logger.verbose(`Force checking out branch "${headBranch}"...`);
349
228
  if (!dryRun) await gitCheckoutBranchForce(headBranch);
350
229
  } else {
351
- logger.verbose(`Checking out branch ${headBranch}...`);
230
+ logger.verbose(`Checking out branch "${headBranch}"...`);
352
231
  if (!dryRun) await gitCheckoutBranch(headBranch);
353
232
  }
354
233
  });
@@ -624,12 +503,7 @@ var Releaser = class {
624
503
  return gitBranchCurrent();
625
504
  }
626
505
  };
627
- //#endregion
628
- //#region src/core/hosting/hosting.ts
629
- /** Git 仓库托管服务的基类 */
630
- var GitRepositoryHosting = class {};
631
- //#endregion
632
- //#region src/hosting-github/github.ts
506
+ // src/hosting-github/github.ts
633
507
  var GithubHosting = class extends GitRepositoryHosting {
634
508
  options;
635
509
  octokit;
@@ -746,13 +620,6 @@ var GithubHosting = class extends GitRepositoryHosting {
746
620
  });
747
621
  }
748
622
  }
749
- async releaseCheck(options = {}) {
750
- const { logger } = options;
751
- logger?.verbose("Checking GitHub repository configuration...");
752
- const remoteUrl = await gitConfigGet("remote.origin.url");
753
- const useGithub = isNotEmpty(parseGitHubRepo(remoteUrl));
754
- assert(useGithub, "Git remote \"origin\" is not a GitHub repository");
755
- }
756
623
  async getRepositoryId(project) {
757
624
  let { options: { owner, repo } } = this;
758
625
  const i = project.getHostedGitInfo();
@@ -811,8 +678,133 @@ var GithubHosting = class extends GitRepositoryHosting {
811
678
  };
812
679
  }
813
680
  };
814
- //#endregion
815
- //#region src/releaser-github-action/releaser.ts
681
+ // src/releaser-github-action/comment.ts
682
+ const ALLOWED_AUTHOR_ASSOCIATIONS = [
683
+ "OWNER",
684
+ "COLLABORATOR",
685
+ "MEMBER"
686
+ ];
687
+ const JSON_START = "```json";
688
+ const JSON_START_OFFSET = 7;
689
+ const JSON_END = "```";
690
+ /** 所有支持的拉取请求评论命令 */
691
+ const COMMANDS = ["!release/set-options", "!release/set-preamble"];
692
+ /**
693
+ * 判断正文是否以命令开头,并且命令后紧跟空白字符或正文结束。
694
+ * 这样可以避免将更长的标记(例如 `!release/set-options-foo`)误判为匹配的命令
695
+ */
696
+ const startsWithCommand = (body, command) => {
697
+ if (!body.startsWith(command)) return false;
698
+ const nextChar = body[command.length];
699
+ return isUndefined(nextChar) || /\s/.test(nextChar);
700
+ };
701
+ /** 评论内容是否是支持的命令评论之一 */
702
+ const isCommandComment = (body) => isString(body) && COMMANDS.some((command) => startsWithCommand(body, command));
703
+ /** 解析 releaser 参数 */
704
+ const parseSetOptionsComment = (comment) => {
705
+ if (ALLOWED_AUTHOR_ASSOCIATIONS.includes(comment.author_association)) {
706
+ const { body } = comment;
707
+ if (body && startsWithCommand(body, "!release/set-options")) {
708
+ const start = body.indexOf(JSON_START);
709
+ const end = isIndex(start) ? body.indexOf(JSON_END, start + JSON_START_OFFSET) : -1;
710
+ if (!isIndex(start) || !isIndex(end)) return;
711
+ return body.substring(start + JSON_START_OFFSET, end).trim();
712
+ }
713
+ }
714
+ };
715
+ /** 解析日志前言 */
716
+ function parseSetPreambleComment(comment) {
717
+ if (ALLOWED_AUTHOR_ASSOCIATIONS.includes(comment.author_association)) {
718
+ const { body } = comment;
719
+ if (body && startsWithCommand(body, "!release/set-preamble")) {
720
+ const newlineIndex = body.indexOf("\n");
721
+ const firstLine = isIndex(newlineIndex) ? body.slice(0, newlineIndex) : body;
722
+ const secondLine = isIndex(newlineIndex) ? body.slice(newlineIndex + 1).trim() : "";
723
+ const name = firstLine.slice(21).replace(/`/g, "").trim().split(/\s+/)[0];
724
+ const preamble = secondLine;
725
+ if (preamble) return {
726
+ name,
727
+ preamble
728
+ };
729
+ }
730
+ }
731
+ return null;
732
+ }
733
+ // src/releaser-github-action/conditions.ts
734
+ const ifReleaseCommit = async (releaser) => {
735
+ const { project, options: { dryRun } } = releaser;
736
+ const logger = releaser.logger.createChild("if-release-commit");
737
+ if (!(await gitLog(["-1", "--pretty=%B"]))?.startsWith("chore(release):")) {
738
+ logger.verbose("Current commit is not a release commit.");
739
+ return false;
740
+ }
741
+ const authorName = await gitLog(["-1", "--pretty=%an"]);
742
+ if (!authorName?.startsWith("github-actions[bot]")) {
743
+ logger.info(`Release commit author "${authorName ?? "unknown"}" is not "github-actions[bot]".`);
744
+ process.exit(0);
745
+ }
746
+ logger.verbose("Checking for existing release tags...");
747
+ if (!dryRun) await gitFetch(["--tags"]);
748
+ const tags = await project.getTags();
749
+ const canRelease = tags.length > 0;
750
+ if (canRelease) {
751
+ logger.verbose(`Found release tags:`);
752
+ for (const tag of tags) logger.verbose(`- ${tag}`);
753
+ } else logger.verbose(`No release tags found.`);
754
+ return canRelease;
755
+ };
756
+ /**
757
+ * 检测当前事件是否为针对处于打开状态的发布拉取请求的受支持命令评论:
758
+ * (`!release/set-options` 或 `!release/set-preamble`)
759
+ *
760
+ * @returns 如果是此类评论,则返回 `目标分支`;如果是评论事件但不是命令评论,则返回 `false`;如果根本不是评论事件,则返回 `null`
761
+ */
762
+ const ifCommandComment = () => {
763
+ const { eventName, payload: { issue, comment } } = context;
764
+ if (eventName !== "issue_comment") return null;
765
+ const isPullRequest = issue?.pull_request;
766
+ const issueAuthor = (issue?.user)?.login;
767
+ const issueBody = issue?.body;
768
+ const issueState = issue?.state;
769
+ const commentBody = comment?.body;
770
+ if (isPullRequest && issueAuthor === "github-actions[bot]" && issueState === "open" && issueBody?.includes("release-pull-request: true") && isCommandComment(commentBody)) {
771
+ const matches = issueBody.match(/release-branch-to:\s*(\S+)/);
772
+ return matches ? matches[1] : false;
773
+ }
774
+ return false;
775
+ };
776
+ // src/releaser-github-action/options.ts
777
+ const omitEmpty = (object) => {
778
+ const result = {};
779
+ for (const [key, value] of Object.entries(object)) if (isBoolean(value) || Boolean(value)) result[key] = value;
780
+ return result;
781
+ };
782
+ const getOptionalBooleanInput = (name) => getInput(name) ? getBooleanInput(name) : void 0;
783
+ const getOptionalJsonInput = (name) => {
784
+ const value = getInput(name);
785
+ return value ? JSON.parse(value) : void 0;
786
+ };
787
+ const getInputOptions = () => ({
788
+ checkout: omitEmpty({ branch: getInput("checkout-branch") }),
789
+ bump: omitEmpty({
790
+ version: getInput("bump-version"),
791
+ as: getInput("bump-as"),
792
+ prerelease: getInput("bump-prerelease"),
793
+ snapshot: getInput("bump-snapshot"),
794
+ firstRelease: getOptionalBooleanInput("bump-first-release"),
795
+ byProject: getOptionalJsonInput("bump-by-project")
796
+ }),
797
+ maintenanceBranch: omitEmpty({ enabled: getOptionalBooleanInput("maintenance-branch-enabled") }),
798
+ publish: omitEmpty({
799
+ access: getInput("publish-access"),
800
+ tag: getInput("publish-tag")
801
+ })
802
+ });
803
+ const getInputSkippedSteps = () => ({
804
+ bump: getOptionalBooleanInput("bump-skip"),
805
+ publish: getOptionalBooleanInput("publish-skip")
806
+ });
807
+ // src/releaser-github-action/releaser.ts
816
808
  var ReleaserGithubAction = class extends Releaser {
817
809
  setUser(options = {}) {
818
810
  return super.setUser({
@@ -873,7 +865,7 @@ var ReleaserGithubAction = class extends Releaser {
873
865
  let optionsApplied = false;
874
866
  let preamble;
875
867
  const preambleByName = /* @__PURE__ */ new Map();
876
- for (let i = comments.length, comment; i >= 0; i--) {
868
+ for (let i = comments.length - 1, comment; i >= 0; i--) {
877
869
  comment = comments[i];
878
870
  if (!optionsApplied) {
879
871
  const json = parseSetOptionsComment(comment);
@@ -969,24 +961,28 @@ var ReleaserGithubAction = class extends Releaser {
969
961
  }
970
962
  }
971
963
  };
972
- //#endregion
973
- //#region src/action.ts
974
- console.log(`Triggered by event: ${context.eventName}`);
964
+ // node_modules/.pnpm/tsdown@0.23.4_publint@0.3.25_typescript@6.0.3/node_modules/tsdown/esm-shims.js
965
+ const getFilename = () => fileURLToPath(import.meta.url);
966
+ const getDirname = () => path.dirname(getFilename());
967
+ readJsonFileSync(join(/* @__PURE__ */ getDirname(), "..", "package.json"));
968
+ // src/action.ts
975
969
  const workflow = getInput("workflow");
970
+ console.log(`Triggered by: ${context.eventName}`);
971
+ console.log(`Current workflow: ${workflow}`);
976
972
  if (workflow === "check" && ifCommandComment() === false) {
977
973
  setOutput("continue", "false");
978
974
  setOutput("workflow", "none");
979
975
  process.exit(0);
980
976
  }
981
- const GITHUB_TOKEN = getInput("github-token");
977
+ const GITHUB_TOKEN = getInput("github-token", { required: true });
982
978
  const NODE_AUTH_TOKEN = getInput("npm-token");
983
979
  const inputOptions = getInputOptions();
984
980
  const skippedSteps = getInputSkippedSteps();
985
981
  if (NODE_AUTH_TOKEN) process.env.NODE_AUTH_TOKEN = NODE_AUTH_TOKEN;
986
982
  try {
987
983
  const { config: { project, hosting, releaser, ...options }, file } = await loadConfig(process.cwd());
984
+ assert(project, "Project is required.");
988
985
  console.log(`Legacy config file: ${file}`);
989
- if (!project) throwError("Project is required.");
990
986
  const gha = new ReleaserGithubAction({
991
987
  project,
992
988
  octokit: getOctokit(GITHUB_TOKEN),
@@ -1013,6 +1009,7 @@ try {
1013
1009
  switch (workflow) {
1014
1010
  case "check":
1015
1011
  const workflow = await ifReleaseCommit(gha) ? "release" : "pull-request";
1012
+ console.log(`Next workflow: ${workflow}`);
1016
1013
  setOutput("continue", "true");
1017
1014
  setOutput("workflow", workflow);
1018
1015
  break;
@@ -1032,5 +1029,4 @@ try {
1032
1029
  else setFailed("An unknown error occurred");
1033
1030
  throw error;
1034
1031
  }
1035
- //#endregion
1036
1032
  export {};