@peiyanlu/release 1.3.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -356
- package/dist/action.mjs +1036 -0
- package/dist/cli.mjs +878 -1411
- package/dist/index.d.mts +850 -253
- package/dist/index.mjs +2848 -149
- package/package.json +27 -12
- package/template/release.config.hbs +29 -0
- package/template/release.text +82 -0
package/dist/action.mjs
ADDED
|
@@ -0,0 +1,1036 @@
|
|
|
1
|
+
import { getBooleanInput, getInput, setFailed, setOutput } from "@actions/core";
|
|
2
|
+
import { context, getOctokit } from "@actions/github";
|
|
3
|
+
import { assert, dedent, isBoolean, isEmpty, isIndex, isNotEmpty, isNull, isString, isUndefined, newline, sleep, throwError, tryCall } from "@peiyanlu/ts-utils";
|
|
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";
|
|
13
|
+
import "conventional-changelog";
|
|
14
|
+
import "conventional-changelog-conventionalcommits";
|
|
15
|
+
import "conventional-recommended-bump";
|
|
16
|
+
import "@conventional-changelog/template";
|
|
17
|
+
//#region src/config/index.ts
|
|
18
|
+
const loadJavaScriptFile = async (file) => {
|
|
19
|
+
const { href } = pathToFileURL(file);
|
|
20
|
+
const mod = await import(href);
|
|
21
|
+
const temp = "default" in mod ? mod.default : mod;
|
|
22
|
+
return typeof temp === "function" ? temp() : temp;
|
|
23
|
+
};
|
|
24
|
+
const findFile = (cwd, filename, extensions) => {
|
|
25
|
+
for (const ext of extensions) {
|
|
26
|
+
const path = resolve(cwd, `${filename}.${ext}`);
|
|
27
|
+
if (existsSync(path)) return path;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const loadConfig = async (cwd = process.cwd()) => {
|
|
31
|
+
const file = findFile(cwd, "release.config", [
|
|
32
|
+
"ts",
|
|
33
|
+
"mts",
|
|
34
|
+
"js",
|
|
35
|
+
"mjs"
|
|
36
|
+
]);
|
|
37
|
+
if (!file) return {
|
|
38
|
+
file: "",
|
|
39
|
+
config: {}
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
file,
|
|
43
|
+
config: await loadJavaScriptFile(file)
|
|
44
|
+
};
|
|
45
|
+
};
|
|
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
|
|
172
|
+
const defaultPrinter = ({ dryRun, stage, message }) => {
|
|
173
|
+
console.log(`${dryRun ? `[dry-run]` : ""}[${stage}]:`, message);
|
|
174
|
+
};
|
|
175
|
+
/** 一个子消息记录器,绑定到特定阶段 */
|
|
176
|
+
var ChildLogger = class {
|
|
177
|
+
parent;
|
|
178
|
+
stage;
|
|
179
|
+
/**
|
|
180
|
+
* 创建子日志记录器。
|
|
181
|
+
* @param parent - 要继承其选项的父日志记录器。
|
|
182
|
+
* @param stage - 此日志记录器所属的处理阶段。
|
|
183
|
+
*/
|
|
184
|
+
constructor(parent, stage) {
|
|
185
|
+
this.parent = parent;
|
|
186
|
+
this.stage = stage;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* 记录一条普通信息。
|
|
190
|
+
* @param message - 要记录的信息。
|
|
191
|
+
*/
|
|
192
|
+
info(message) {
|
|
193
|
+
this.parent.info(this.stage, message);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* 记录一条详细信息。
|
|
197
|
+
* @param message - 要记录的信息。
|
|
198
|
+
*/
|
|
199
|
+
verbose(message) {
|
|
200
|
+
this.parent.verbose(this.stage, message);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
/** 消息记录器 */
|
|
204
|
+
var Logger = class {
|
|
205
|
+
options;
|
|
206
|
+
printer;
|
|
207
|
+
colors;
|
|
208
|
+
colorMap = /* @__PURE__ */ new Map();
|
|
209
|
+
constructor(options = {}) {
|
|
210
|
+
this.options = options;
|
|
211
|
+
const { colors = [], printer = defaultPrinter } = options;
|
|
212
|
+
this.colors = colors;
|
|
213
|
+
this.printer = printer;
|
|
214
|
+
}
|
|
215
|
+
info(stage, message) {
|
|
216
|
+
const { silent, dryRun } = this.options;
|
|
217
|
+
if (!silent) {
|
|
218
|
+
const color = this.getColor(stage);
|
|
219
|
+
this.printer({
|
|
220
|
+
dryRun,
|
|
221
|
+
stage,
|
|
222
|
+
message,
|
|
223
|
+
color
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
verbose(stage, message) {
|
|
228
|
+
const { silent, verbose, dryRun } = this.options;
|
|
229
|
+
if (!silent && (verbose || dryRun)) {
|
|
230
|
+
const color = this.getColor(stage);
|
|
231
|
+
this.printer({
|
|
232
|
+
dryRun,
|
|
233
|
+
stage,
|
|
234
|
+
message,
|
|
235
|
+
color
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
createChild(stage) {
|
|
240
|
+
return new ChildLogger(this, stage);
|
|
241
|
+
}
|
|
242
|
+
getColor(stage) {
|
|
243
|
+
const cached = this.colorMap.get(stage);
|
|
244
|
+
if (cached) return cached;
|
|
245
|
+
const index = this.colorMap.size % this.colors.length;
|
|
246
|
+
const color = this.colors[index] ?? ((text) => text);
|
|
247
|
+
this.colorMap.set(stage, color);
|
|
248
|
+
return color;
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region src/core/utils.ts
|
|
253
|
+
const when = (condition, ...value) => condition ? [...value] : [];
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/core/releaser.ts
|
|
256
|
+
var Releaser = class {
|
|
257
|
+
options;
|
|
258
|
+
project;
|
|
259
|
+
hosting;
|
|
260
|
+
logger;
|
|
261
|
+
stepsOptions = {};
|
|
262
|
+
skippedSteps = {};
|
|
263
|
+
state = {};
|
|
264
|
+
queue = [];
|
|
265
|
+
constructor(options) {
|
|
266
|
+
this.options = options;
|
|
267
|
+
const { project, hosting, logger, ...log } = options;
|
|
268
|
+
this.project = project;
|
|
269
|
+
this.hosting = hosting;
|
|
270
|
+
this.logger = logger ?? new Logger(log);
|
|
271
|
+
}
|
|
272
|
+
enqueue(fn) {
|
|
273
|
+
this.queue.push(fn);
|
|
274
|
+
return this;
|
|
275
|
+
}
|
|
276
|
+
setOptions(options) {
|
|
277
|
+
const logger = this.logger.createChild("set-options");
|
|
278
|
+
logger.verbose("Setting options for steps:");
|
|
279
|
+
logger.verbose(options);
|
|
280
|
+
for (const [key, value] of Object.entries(options)) Object.assign(this.stepsOptions[key] ??= {}, value);
|
|
281
|
+
logger.verbose("Final options for steps:");
|
|
282
|
+
logger.verbose(this.stepsOptions);
|
|
283
|
+
return this;
|
|
284
|
+
}
|
|
285
|
+
setUser(options) {
|
|
286
|
+
return this.enqueue(async () => {
|
|
287
|
+
const { options: { dryRun }, stepsOptions: { setUser } } = this;
|
|
288
|
+
const { username, email } = {
|
|
289
|
+
...setUser,
|
|
290
|
+
...options
|
|
291
|
+
};
|
|
292
|
+
const logger = this.logger.createChild("set-user");
|
|
293
|
+
if (this.shouldSkip("setUser")) {
|
|
294
|
+
logger.info("Skipping set git user.");
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
logger.info("Setting git user...");
|
|
298
|
+
if (!username && !email) {
|
|
299
|
+
logger.info("No git user configuration provided.");
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (username) {
|
|
303
|
+
logger.verbose(`Setting git user.name to "${username}".`);
|
|
304
|
+
if (!dryRun) await gitConfigSet("user.name", username);
|
|
305
|
+
}
|
|
306
|
+
if (email) {
|
|
307
|
+
logger.verbose(`Setting git user.email to "${email}".`);
|
|
308
|
+
if (!dryRun) await gitConfigSet("user.email", email);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
setSkippedSteps(options) {
|
|
313
|
+
const logger = this.logger.createChild("set-skipped-steps");
|
|
314
|
+
logger.verbose("Setting skipped for steps:");
|
|
315
|
+
logger.verbose(options);
|
|
316
|
+
Object.assign(this.skippedSteps, options);
|
|
317
|
+
logger.verbose("Final skipped for steps:");
|
|
318
|
+
logger.verbose(this.skippedSteps);
|
|
319
|
+
return this;
|
|
320
|
+
}
|
|
321
|
+
checkout(options) {
|
|
322
|
+
return this.enqueue(async () => {
|
|
323
|
+
const { options: { dryRun }, stepsOptions: { checkout } } = this;
|
|
324
|
+
const { branch: headBranch = "release-branch", fetch, force } = {
|
|
325
|
+
...checkout,
|
|
326
|
+
...options
|
|
327
|
+
};
|
|
328
|
+
const logger = this.logger.createChild("checkout");
|
|
329
|
+
if (this.shouldSkip("checkout")) {
|
|
330
|
+
logger.info("Skipping checkout.");
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
logger.info(`Checking out branch ${headBranch}...`);
|
|
334
|
+
this.state.headBranch = headBranch;
|
|
335
|
+
this.state.baseBranch = await this.getBaseBranch();
|
|
336
|
+
if (fetch) {
|
|
337
|
+
logger.verbose(`Fetching all commits and tags from the remote repository...`);
|
|
338
|
+
if (!dryRun) await gitFetch([
|
|
339
|
+
"--prune",
|
|
340
|
+
"--unshallow",
|
|
341
|
+
"--tags",
|
|
342
|
+
"--",
|
|
343
|
+
"origin",
|
|
344
|
+
"HEAD"
|
|
345
|
+
]);
|
|
346
|
+
}
|
|
347
|
+
if (force) {
|
|
348
|
+
logger.verbose(`Force checking out branch ${headBranch}...`);
|
|
349
|
+
if (!dryRun) await gitCheckoutBranchForce(headBranch);
|
|
350
|
+
} else {
|
|
351
|
+
logger.verbose(`Checking out branch ${headBranch}...`);
|
|
352
|
+
if (!dryRun) await gitCheckoutBranch(headBranch);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
bump(options) {
|
|
357
|
+
return this.enqueue(async () => {
|
|
358
|
+
const { project, options: { dryRun }, stepsOptions: { bump } } = this;
|
|
359
|
+
const logger = this.logger.createChild("bump");
|
|
360
|
+
if (this.shouldSkip("bump")) {
|
|
361
|
+
logger.info("Skipping bump.");
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
logger.info("Bumping version...");
|
|
365
|
+
const done = await project.bump({
|
|
366
|
+
dryRun,
|
|
367
|
+
logger,
|
|
368
|
+
...bump,
|
|
369
|
+
...options
|
|
370
|
+
});
|
|
371
|
+
this.state.bump = done;
|
|
372
|
+
if (!done) logger.info("No version changes detected.");
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
commit(options) {
|
|
376
|
+
return this.enqueue(async () => {
|
|
377
|
+
const { project, options: { dryRun }, state, stepsOptions: { commit } } = this;
|
|
378
|
+
const { sign, allowEmpty } = {
|
|
379
|
+
...commit,
|
|
380
|
+
...options
|
|
381
|
+
};
|
|
382
|
+
const logger = this.logger.createChild("commit");
|
|
383
|
+
if (this.shouldSkip("commit")) {
|
|
384
|
+
logger.info("Skipping commit.");
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
logger.info(`Committing changes...`);
|
|
388
|
+
if (!state.bump) {
|
|
389
|
+
logger.info("No changes to commit.");
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
const files = project.changedFiles;
|
|
393
|
+
const message = await project.getCommitMessage();
|
|
394
|
+
logger.verbose(`Files to commit:`);
|
|
395
|
+
files.forEach((file) => {
|
|
396
|
+
logger.verbose(`- ${file}`);
|
|
397
|
+
});
|
|
398
|
+
logger.verbose(message.includes("\n") ? `Commit message:\n\n${message}` : `Commit message: ${message}`);
|
|
399
|
+
if (!dryRun) {
|
|
400
|
+
await gitAdd(["--", ...files]);
|
|
401
|
+
await gitCommitMessage(message, [
|
|
402
|
+
...when(sign, "--gpg-sign"),
|
|
403
|
+
...when(allowEmpty, "--allow-empty"),
|
|
404
|
+
"--no-verify"
|
|
405
|
+
]);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
revert() {
|
|
410
|
+
return this.enqueue(async () => {
|
|
411
|
+
const { project, logger, options: { dryRun } } = this;
|
|
412
|
+
logger.info("revert", "Reverting version updates...");
|
|
413
|
+
if (!await project.revert({
|
|
414
|
+
dryRun,
|
|
415
|
+
logger: logger.createChild("revert")
|
|
416
|
+
})) {
|
|
417
|
+
logger.info("revert", "No version updates to revert.");
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
this.state.bump = false;
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
maintenanceBranch(options) {
|
|
424
|
+
return this.enqueue(async () => {
|
|
425
|
+
const { project, options: { dryRun }, stepsOptions: { maintenanceBranch } } = this;
|
|
426
|
+
const { enabled, force } = {
|
|
427
|
+
...maintenanceBranch,
|
|
428
|
+
...options
|
|
429
|
+
};
|
|
430
|
+
const logger = this.logger.createChild("maintenance-branch");
|
|
431
|
+
if (this.shouldSkip("maintenanceBranch")) {
|
|
432
|
+
logger.info("Skipping create maintenance branches.");
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
logger.info("Creating maintenance branches...");
|
|
436
|
+
if (!enabled) {
|
|
437
|
+
logger.info("Maintenance branch creation is disabled.");
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
logger.verbose("Fetching fresh tags from the remote repository...");
|
|
441
|
+
if (!dryRun) await gitFetch(["--tags"]);
|
|
442
|
+
const branches = await project.getMaintenanceBranches();
|
|
443
|
+
if (!branches.length) {
|
|
444
|
+
logger.info("No maintenance branches to create.");
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
logger.verbose("Branches to create:");
|
|
448
|
+
for (const { from, to } of branches) {
|
|
449
|
+
logger.verbose(`- ${to} from ${from}`);
|
|
450
|
+
if (dryRun) continue;
|
|
451
|
+
if ((await gitRevParse(["--verify", `refs/heads/${to}`]) || await gitLsRemote([
|
|
452
|
+
"--heads",
|
|
453
|
+
"origin",
|
|
454
|
+
to
|
|
455
|
+
])) && !force) {
|
|
456
|
+
logger.info(`Branch ${to} already exists, skipping.`);
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const args = when(force, "--force");
|
|
460
|
+
await gitBranch([
|
|
461
|
+
...args,
|
|
462
|
+
"--",
|
|
463
|
+
to,
|
|
464
|
+
from
|
|
465
|
+
]);
|
|
466
|
+
await gitPushBranch("origin", to, ["--no-verify", ...args]);
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
tag(options) {
|
|
471
|
+
return this.enqueue(async () => {
|
|
472
|
+
const { project, options: { dryRun }, stepsOptions: { tag } } = this;
|
|
473
|
+
const { fetch, sign } = {
|
|
474
|
+
...tag,
|
|
475
|
+
...options
|
|
476
|
+
};
|
|
477
|
+
const logger = this.logger.createChild("tag");
|
|
478
|
+
if (this.shouldSkip("tag")) {
|
|
479
|
+
logger.info("Skipping tag.");
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
logger.info("Tagging version...");
|
|
483
|
+
if (fetch) {
|
|
484
|
+
logger.verbose("Fetching fresh tags from the remote repository...");
|
|
485
|
+
if (!dryRun) await gitFetch(["--tags"]);
|
|
486
|
+
}
|
|
487
|
+
const tags = await project.getTags();
|
|
488
|
+
this.state.tags = tags.length > 0;
|
|
489
|
+
if (!this.state.tags) {
|
|
490
|
+
logger.info("No version changes detected.");
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
logger.verbose(`Tags to create:`);
|
|
494
|
+
for (const tag of tags) {
|
|
495
|
+
logger.verbose(`- ${tag}`);
|
|
496
|
+
if (!dryRun) await gitTag([
|
|
497
|
+
sign ? "--sign" : "--annotate",
|
|
498
|
+
"--message",
|
|
499
|
+
tag,
|
|
500
|
+
tag
|
|
501
|
+
]);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
push(options) {
|
|
506
|
+
return this.enqueue(async () => {
|
|
507
|
+
const { options: { dryRun }, state, stepsOptions: { push } } = this;
|
|
508
|
+
const { force } = {
|
|
509
|
+
...push,
|
|
510
|
+
...options
|
|
511
|
+
};
|
|
512
|
+
const logger = this.logger.createChild("push");
|
|
513
|
+
if (this.shouldSkip("push")) {
|
|
514
|
+
logger.info("Skipping push.");
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const branch = await gitBranchCurrent();
|
|
518
|
+
logger.info(`Pushing changes to "${branch}"...`);
|
|
519
|
+
if (!state.bump && !state.tags) {
|
|
520
|
+
logger.info("Nothing to push.");
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (!dryRun) {
|
|
524
|
+
if (state.bump) await gitPushBranch("origin", branch, [...when(force, "--force"), "--no-verify"]);
|
|
525
|
+
if (state.tags) await gitPush([
|
|
526
|
+
"origin",
|
|
527
|
+
"--tags",
|
|
528
|
+
"--no-verify"
|
|
529
|
+
]);
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
publish(options) {
|
|
534
|
+
return this.enqueue(async () => {
|
|
535
|
+
const { project, options: { dryRun }, stepsOptions: { publish } } = this;
|
|
536
|
+
const logger = this.logger.createChild("publish");
|
|
537
|
+
if (this.shouldSkip("publish")) {
|
|
538
|
+
logger.info("Skipping publish.");
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
logger.info("Publishing...");
|
|
542
|
+
const tagsMap = await project.getDefaultPublishTag();
|
|
543
|
+
const name = await project.manifest.getName();
|
|
544
|
+
const tag = tagsMap.get(name);
|
|
545
|
+
await project.publish({
|
|
546
|
+
dryRun,
|
|
547
|
+
logger,
|
|
548
|
+
tag,
|
|
549
|
+
...publish,
|
|
550
|
+
...options
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
release(option) {
|
|
555
|
+
const { hosting } = this;
|
|
556
|
+
if (!hosting) throwError("Hosting service is not configured. Please set the hosting option.");
|
|
557
|
+
return this.enqueue(async () => {
|
|
558
|
+
const { project, options: { dryRun }, stepsOptions: { release } } = this;
|
|
559
|
+
const logger = this.logger.createChild("release");
|
|
560
|
+
if (this.shouldSkip("release")) {
|
|
561
|
+
logger.info("Skipping release.");
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
logger.info("Creating release...");
|
|
565
|
+
await hosting.createRelease({
|
|
566
|
+
project,
|
|
567
|
+
dryRun,
|
|
568
|
+
logger,
|
|
569
|
+
...release,
|
|
570
|
+
...option
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
pullRequest(option) {
|
|
575
|
+
const { hosting } = this;
|
|
576
|
+
if (!hosting) throwError("Hosting service is not configured. Please set the hosting option.");
|
|
577
|
+
return this.enqueue(async () => {
|
|
578
|
+
const { project, state, options: { dryRun }, stepsOptions: { pullRequest } } = this;
|
|
579
|
+
const logger = this.logger.createChild("pull-request");
|
|
580
|
+
if (this.shouldSkip("pullRequest")) {
|
|
581
|
+
logger.info("Skipping pull request.");
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
logger.info("Creating pull request...");
|
|
585
|
+
if (!state.bump) {
|
|
586
|
+
logger.info("No version changes detected, skipping pull request creation.");
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
if (!state.headBranch) {
|
|
590
|
+
logger.info("Head branch is not set, skipping pull request creation.");
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
await hosting.createPullRequest({
|
|
594
|
+
project,
|
|
595
|
+
dryRun,
|
|
596
|
+
logger,
|
|
597
|
+
from: state.headBranch,
|
|
598
|
+
to: await this.getBaseBranch(),
|
|
599
|
+
...pullRequest,
|
|
600
|
+
...option
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
async run(condition) {
|
|
605
|
+
let shouldRun = true;
|
|
606
|
+
if (condition) {
|
|
607
|
+
const logger = this.logger.createChild("run");
|
|
608
|
+
logger.info("Checking condition...");
|
|
609
|
+
shouldRun = await condition(this);
|
|
610
|
+
if (shouldRun) logger.info("Condition passed, running release steps...");
|
|
611
|
+
else logger.info("Condition failed, skipping release steps.");
|
|
612
|
+
}
|
|
613
|
+
if (shouldRun) for (const fn of this.queue) await fn();
|
|
614
|
+
const logger = this.logger.createChild("done");
|
|
615
|
+
this.queue.length = 0;
|
|
616
|
+
logger.info(`Done!`);
|
|
617
|
+
}
|
|
618
|
+
shouldSkip(step) {
|
|
619
|
+
return this.skippedSteps[step] === true;
|
|
620
|
+
}
|
|
621
|
+
async getBaseBranch() {
|
|
622
|
+
const { state: { baseBranch } } = this;
|
|
623
|
+
if (baseBranch) return baseBranch;
|
|
624
|
+
return gitBranchCurrent();
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
//#endregion
|
|
628
|
+
//#region src/core/hosting/hosting.ts
|
|
629
|
+
/** Git 仓库托管服务的基类 */
|
|
630
|
+
var GitRepositoryHosting = class {};
|
|
631
|
+
//#endregion
|
|
632
|
+
//#region src/hosting-github/github.ts
|
|
633
|
+
var GithubHosting = class extends GitRepositoryHosting {
|
|
634
|
+
options;
|
|
635
|
+
octokit;
|
|
636
|
+
browser;
|
|
637
|
+
constructor(options = {}) {
|
|
638
|
+
const { octokit, token, browser } = options;
|
|
639
|
+
super();
|
|
640
|
+
this.options = options;
|
|
641
|
+
this.browser = browser ?? (!octokit && !token);
|
|
642
|
+
this.octokit = octokit ?? new Octokit({ auth: token });
|
|
643
|
+
}
|
|
644
|
+
async createRelease(options) {
|
|
645
|
+
const { project, dryRun, logger } = options;
|
|
646
|
+
const { octokit, browser } = this;
|
|
647
|
+
const releaseData = await project.getReleaseData();
|
|
648
|
+
if (isEmpty(releaseData)) {
|
|
649
|
+
logger?.info("No release data found. Skipping release creation.");
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
const [owner, repo] = await this.getRepositoryId(project);
|
|
653
|
+
for (const data of releaseData) {
|
|
654
|
+
logger?.verbose(`Creating GitHub release "${data.title}"...`);
|
|
655
|
+
const browserRelease = async () => {
|
|
656
|
+
const url = new URL(`${getGithubUrl(owner, repo)}/releases/new`);
|
|
657
|
+
Object.entries({
|
|
658
|
+
tag: data.nextTag,
|
|
659
|
+
title: data.title,
|
|
660
|
+
body: data.notes,
|
|
661
|
+
prerelease: data.isPrerelease
|
|
662
|
+
}).forEach(([key, value]) => {
|
|
663
|
+
url.searchParams.set(key, String(value));
|
|
664
|
+
});
|
|
665
|
+
logger?.verbose(`Opening GitHub release page for tag "${data.nextTag}"...`);
|
|
666
|
+
await open(url.toString());
|
|
667
|
+
await sleep(1500);
|
|
668
|
+
};
|
|
669
|
+
const resetRelease = async () => {
|
|
670
|
+
const res = await (async () => {
|
|
671
|
+
const release = await this.getRelease({
|
|
672
|
+
owner,
|
|
673
|
+
repo,
|
|
674
|
+
tag: data.nextTag
|
|
675
|
+
});
|
|
676
|
+
const opts = {
|
|
677
|
+
owner,
|
|
678
|
+
repo,
|
|
679
|
+
tag_name: data.nextTag,
|
|
680
|
+
name: data.title,
|
|
681
|
+
body: data.notes,
|
|
682
|
+
prerelease: data.isPrerelease,
|
|
683
|
+
make_latest: data.isLatest ? "true" : "false"
|
|
684
|
+
};
|
|
685
|
+
if (release) {
|
|
686
|
+
const { data } = await octokit.rest.repos.updateRelease({
|
|
687
|
+
...opts,
|
|
688
|
+
release_id: release.id
|
|
689
|
+
});
|
|
690
|
+
return data;
|
|
691
|
+
} else {
|
|
692
|
+
const { data } = await octokit.rest.repos.createRelease({
|
|
693
|
+
...opts,
|
|
694
|
+
draft: false
|
|
695
|
+
});
|
|
696
|
+
return data;
|
|
697
|
+
}
|
|
698
|
+
})();
|
|
699
|
+
if (!res) return;
|
|
700
|
+
await this.uploadAssets({
|
|
701
|
+
owner,
|
|
702
|
+
repo,
|
|
703
|
+
release_id: res.id,
|
|
704
|
+
assets: []
|
|
705
|
+
});
|
|
706
|
+
};
|
|
707
|
+
if (!dryRun) browser ? await browserRelease() : await resetRelease();
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
async createPullRequest(options) {
|
|
711
|
+
const { project, dryRun, draft, logger } = options;
|
|
712
|
+
let { from, to } = options;
|
|
713
|
+
const { octokit } = this;
|
|
714
|
+
const [owner, repo] = await this.getRepositoryId(project);
|
|
715
|
+
from ||= await gitBranchCurrent();
|
|
716
|
+
to ||= await this.getDefaultBranch(owner, repo);
|
|
717
|
+
if (from === to) throwError("Cannot create a pull request from the same branch.");
|
|
718
|
+
const [pr] = await this.listPullRequest({
|
|
719
|
+
owner,
|
|
720
|
+
repo,
|
|
721
|
+
from
|
|
722
|
+
});
|
|
723
|
+
if (pr) logger?.info(`Updating existing pull request #${pr.number}`);
|
|
724
|
+
logger?.verbose("Pull request data:");
|
|
725
|
+
if (!pr && draft) logger?.verbose("Draft: true");
|
|
726
|
+
const { title, body } = await this.getPullRequestData(project, from, to);
|
|
727
|
+
logger?.verbose(`${from} -> ${to}`);
|
|
728
|
+
logger?.verbose(`Title: ${title}`);
|
|
729
|
+
logger?.verbose(`Body:\n\n${body}`);
|
|
730
|
+
const opts = {
|
|
731
|
+
owner,
|
|
732
|
+
repo,
|
|
733
|
+
title,
|
|
734
|
+
body
|
|
735
|
+
};
|
|
736
|
+
if (!dryRun) {
|
|
737
|
+
if (pr) await octokit.rest.pulls.update({
|
|
738
|
+
...opts,
|
|
739
|
+
pull_number: pr.number
|
|
740
|
+
});
|
|
741
|
+
else await octokit.rest.pulls.create({
|
|
742
|
+
...opts,
|
|
743
|
+
base: to,
|
|
744
|
+
head: from,
|
|
745
|
+
draft
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
}
|
|
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
|
+
async getRepositoryId(project) {
|
|
757
|
+
let { options: { owner, repo } } = this;
|
|
758
|
+
const i = project.getHostedGitInfo();
|
|
759
|
+
return [owner ?? (await i).owner, repo ?? (await i).repo];
|
|
760
|
+
}
|
|
761
|
+
async uploadAssets(options) {
|
|
762
|
+
const { octokit } = this;
|
|
763
|
+
const { owner, repo, release_id, assets } = options;
|
|
764
|
+
const files = await glob(assets);
|
|
765
|
+
await Promise.all(files.map((file) => {
|
|
766
|
+
const { base: filename } = parse(file);
|
|
767
|
+
return octokit.rest.repos.uploadReleaseAsset({
|
|
768
|
+
owner,
|
|
769
|
+
repo,
|
|
770
|
+
release_id,
|
|
771
|
+
name: filename,
|
|
772
|
+
data: readFileSync(file, "utf-8")
|
|
773
|
+
});
|
|
774
|
+
}));
|
|
775
|
+
}
|
|
776
|
+
async getRelease(options) {
|
|
777
|
+
const { octokit } = this;
|
|
778
|
+
const { data } = await tryCall(() => octokit.rest.repos.getReleaseByTag({ ...options }), { data: void 0 });
|
|
779
|
+
return data;
|
|
780
|
+
}
|
|
781
|
+
async listPullRequest(options) {
|
|
782
|
+
const { octokit } = this;
|
|
783
|
+
const { owner, repo, from } = options;
|
|
784
|
+
const { data } = await tryCall(() => octokit.rest.pulls.list({
|
|
785
|
+
owner,
|
|
786
|
+
repo,
|
|
787
|
+
head: `${owner}:${from}`,
|
|
788
|
+
state: "open"
|
|
789
|
+
}), { data: [] });
|
|
790
|
+
return data;
|
|
791
|
+
}
|
|
792
|
+
async getDefaultBranch(owner, repo) {
|
|
793
|
+
const { octokit } = this;
|
|
794
|
+
const { data: { default_branch } } = await tryCall(() => octokit.rest.repos.get({
|
|
795
|
+
owner,
|
|
796
|
+
repo
|
|
797
|
+
}), { data: { default_branch: "master" } });
|
|
798
|
+
return default_branch;
|
|
799
|
+
}
|
|
800
|
+
async getPullRequestData(project, from, to) {
|
|
801
|
+
return {
|
|
802
|
+
title: (await project.getCommitMessage()).split("\n")[0],
|
|
803
|
+
body: [project.versionUpdates.map(({ name, notes }) => `# ${name}${newline(2)}${notes.trim()}`).join(newline(2)), dedent(`
|
|
804
|
+
<!--
|
|
805
|
+
Release pull request metadata.
|
|
806
|
+
release-pull-request: true
|
|
807
|
+
release-branch-from: ${from}
|
|
808
|
+
release-branch-to: ${to}
|
|
809
|
+
-->
|
|
810
|
+
`)].join(newline(2))
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
//#endregion
|
|
815
|
+
//#region src/releaser-github-action/releaser.ts
|
|
816
|
+
var ReleaserGithubAction = class extends Releaser {
|
|
817
|
+
setUser(options = {}) {
|
|
818
|
+
return super.setUser({
|
|
819
|
+
username: "github-actions[bot]",
|
|
820
|
+
email: "github-actions[bot]@users.noreply.github.com",
|
|
821
|
+
...options
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
checkout(options = {}) {
|
|
825
|
+
return super.checkout({
|
|
826
|
+
fetch: true,
|
|
827
|
+
force: true,
|
|
828
|
+
...options
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
tag(options = {}) {
|
|
832
|
+
return super.tag({
|
|
833
|
+
fetch: true,
|
|
834
|
+
...options
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
push(options = {}) {
|
|
838
|
+
return super.push({
|
|
839
|
+
force: true,
|
|
840
|
+
...options
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
constructor(options) {
|
|
844
|
+
super({
|
|
845
|
+
hosting: new GithubHosting({ octokit: options.octokit }),
|
|
846
|
+
...options
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
fetchOptions() {
|
|
850
|
+
return this.enqueue(async () => {
|
|
851
|
+
const { state: { headBranch }, stepsOptions: { bump: bumpOpts } } = this;
|
|
852
|
+
if (!headBranch) throwError("Head branch is not set. Please call `checkout` before `fetchParams`.");
|
|
853
|
+
const { project, hosting } = this;
|
|
854
|
+
const logger = this.logger.createChild("fetch-options");
|
|
855
|
+
logger.info("Fetching options from pull request comments...");
|
|
856
|
+
const { octokit } = hosting;
|
|
857
|
+
const [owner, repo] = await hosting.getRepositoryId(project);
|
|
858
|
+
const { data: [pr] } = await octokit.rest.pulls.list({
|
|
859
|
+
owner,
|
|
860
|
+
repo,
|
|
861
|
+
head: `${owner}:${headBranch}`,
|
|
862
|
+
state: "open"
|
|
863
|
+
});
|
|
864
|
+
if (!pr) {
|
|
865
|
+
logger.info("No open pull request found.");
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
const { data: comments } = await octokit.rest.issues.listComments({
|
|
869
|
+
owner,
|
|
870
|
+
repo,
|
|
871
|
+
issue_number: pr.number
|
|
872
|
+
});
|
|
873
|
+
let optionsApplied = false;
|
|
874
|
+
let preamble;
|
|
875
|
+
const preambleByName = /* @__PURE__ */ new Map();
|
|
876
|
+
for (let i = comments.length, comment; i >= 0; i--) {
|
|
877
|
+
comment = comments[i];
|
|
878
|
+
if (!optionsApplied) {
|
|
879
|
+
const json = parseSetOptionsComment(comment);
|
|
880
|
+
if (json) {
|
|
881
|
+
try {
|
|
882
|
+
const options = JSON.parse(json);
|
|
883
|
+
logger.verbose("Found set-options comment:");
|
|
884
|
+
logger.verbose(json);
|
|
885
|
+
this.setOptions(options);
|
|
886
|
+
optionsApplied = true;
|
|
887
|
+
} catch {
|
|
888
|
+
logger.verbose("Failed to parse parameters comment:");
|
|
889
|
+
logger.verbose(json);
|
|
890
|
+
}
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
const setPreamble = parseSetPreambleComment(comment);
|
|
895
|
+
if (setPreamble) {
|
|
896
|
+
const { name, preamble: markdown } = setPreamble;
|
|
897
|
+
if (name) {
|
|
898
|
+
if (!preambleByName.has(name)) preambleByName.set(name, markdown);
|
|
899
|
+
} else if (isUndefined(preamble)) preamble = markdown;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
if (isEmpty(preamble) && isEmpty(preambleByName)) return;
|
|
903
|
+
const bump = { ...bumpOpts };
|
|
904
|
+
if (isNotEmpty(preamble)) {
|
|
905
|
+
logger.verbose("Found global set-preamble comment.");
|
|
906
|
+
bump.preamble = preamble;
|
|
907
|
+
}
|
|
908
|
+
if (isNotEmpty(preambleByName)) for (const [name, markdown] of preambleByName) (bump.byProject ??= {})[name] = {
|
|
909
|
+
...bump.byProject?.[name],
|
|
910
|
+
preamble: markdown
|
|
911
|
+
};
|
|
912
|
+
this.setOptions({ bump });
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
fetch() {
|
|
916
|
+
return this.enqueue(async () => {
|
|
917
|
+
const { logger, options: { dryRun } } = this;
|
|
918
|
+
logger.info("fetch", "Fetching all commits and tags from the remote repository...");
|
|
919
|
+
if (!dryRun) {
|
|
920
|
+
const unshallow = await gitRevParse(["--is-shallow-repository"]) === "true" ? ["--unshallow"] : [];
|
|
921
|
+
await gitFetch([
|
|
922
|
+
"--prune",
|
|
923
|
+
"--tags",
|
|
924
|
+
...unshallow,
|
|
925
|
+
"--",
|
|
926
|
+
"origin",
|
|
927
|
+
"HEAD"
|
|
928
|
+
]);
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
async runPullRequestAction() {
|
|
933
|
+
await this.setUser().checkout().fetchOptions().bump().commit().push().pullRequest().run();
|
|
934
|
+
}
|
|
935
|
+
async runReleaseAction(check = true) {
|
|
936
|
+
await this.setUser().maintenanceBranch().tag().push().publish().release().run(check ? ifReleaseCommit : void 0);
|
|
937
|
+
}
|
|
938
|
+
async runSnapshotAction(snapshotTag) {
|
|
939
|
+
if (!snapshotTag) throwError("Snapshot tag is required.");
|
|
940
|
+
await this.fetch().bump({
|
|
941
|
+
snapshot: snapshotTag,
|
|
942
|
+
skipChangelog: true
|
|
943
|
+
}).publish({ tag: snapshotTag }).revert().run();
|
|
944
|
+
}
|
|
945
|
+
async runAction() {
|
|
946
|
+
const logger = this.logger.createChild("run");
|
|
947
|
+
logger.info("Detecting action...");
|
|
948
|
+
const commandComment = ifCommandComment();
|
|
949
|
+
let isReleaseCommit = false;
|
|
950
|
+
if (commandComment === false) {
|
|
951
|
+
logger.info("Action triggered by a pull request comment.");
|
|
952
|
+
logger.info("No command comment found in a comment. Stopping action.");
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
if (!isNull(commandComment)) {
|
|
956
|
+
logger.info("Action triggered by a pull request comment.");
|
|
957
|
+
if (await gitBranchCurrent() !== commandComment) this.checkout({
|
|
958
|
+
branch: commandComment,
|
|
959
|
+
fetch: true,
|
|
960
|
+
force: false
|
|
961
|
+
});
|
|
962
|
+
} else isReleaseCommit = await ifReleaseCommit(this);
|
|
963
|
+
if (isReleaseCommit) {
|
|
964
|
+
logger.info("Running release action...");
|
|
965
|
+
await this.runReleaseAction(false);
|
|
966
|
+
} else {
|
|
967
|
+
logger.info("Running pull request action...");
|
|
968
|
+
await this.runPullRequestAction();
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
//#endregion
|
|
973
|
+
//#region src/action.ts
|
|
974
|
+
console.log(`Triggered by event: ${context.eventName}`);
|
|
975
|
+
const workflow = getInput("workflow");
|
|
976
|
+
if (workflow === "check" && ifCommandComment() === false) {
|
|
977
|
+
setOutput("continue", "false");
|
|
978
|
+
setOutput("workflow", "none");
|
|
979
|
+
process.exit(0);
|
|
980
|
+
}
|
|
981
|
+
const GITHUB_TOKEN = getInput("github-token");
|
|
982
|
+
const NODE_AUTH_TOKEN = getInput("npm-token");
|
|
983
|
+
const inputOptions = getInputOptions();
|
|
984
|
+
const skippedSteps = getInputSkippedSteps();
|
|
985
|
+
if (NODE_AUTH_TOKEN) process.env.NODE_AUTH_TOKEN = NODE_AUTH_TOKEN;
|
|
986
|
+
try {
|
|
987
|
+
const { config: { project, hosting, releaser, ...options }, file } = await loadConfig(process.cwd());
|
|
988
|
+
console.log(`Legacy config file: ${file}`);
|
|
989
|
+
if (!project) throwError("Project is required.");
|
|
990
|
+
const gha = new ReleaserGithubAction({
|
|
991
|
+
project,
|
|
992
|
+
octokit: getOctokit(GITHUB_TOKEN),
|
|
993
|
+
...releaser
|
|
994
|
+
}).setOptions({
|
|
995
|
+
...options,
|
|
996
|
+
checkout: {
|
|
997
|
+
...options.checkout,
|
|
998
|
+
...inputOptions.checkout
|
|
999
|
+
},
|
|
1000
|
+
bump: {
|
|
1001
|
+
...options.bump,
|
|
1002
|
+
...inputOptions.bump
|
|
1003
|
+
},
|
|
1004
|
+
maintenanceBranch: {
|
|
1005
|
+
...options.maintenanceBranch,
|
|
1006
|
+
...inputOptions.maintenanceBranch
|
|
1007
|
+
},
|
|
1008
|
+
publish: {
|
|
1009
|
+
...options.publish,
|
|
1010
|
+
...inputOptions.publish
|
|
1011
|
+
}
|
|
1012
|
+
}).setSkippedSteps(skippedSteps);
|
|
1013
|
+
switch (workflow) {
|
|
1014
|
+
case "check":
|
|
1015
|
+
const workflow = await ifReleaseCommit(gha) ? "release" : "pull-request";
|
|
1016
|
+
setOutput("continue", "true");
|
|
1017
|
+
setOutput("workflow", workflow);
|
|
1018
|
+
break;
|
|
1019
|
+
case "full":
|
|
1020
|
+
await gha.runAction();
|
|
1021
|
+
break;
|
|
1022
|
+
case "pull-request":
|
|
1023
|
+
await gha.runPullRequestAction();
|
|
1024
|
+
break;
|
|
1025
|
+
case "release":
|
|
1026
|
+
await gha.runReleaseAction();
|
|
1027
|
+
break;
|
|
1028
|
+
case "snapshot": await gha.runSnapshotAction(inputOptions.bump?.snapshot);
|
|
1029
|
+
}
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
if (error instanceof Error) setFailed(error.message);
|
|
1032
|
+
else setFailed("An unknown error occurred");
|
|
1033
|
+
throw error;
|
|
1034
|
+
}
|
|
1035
|
+
//#endregion
|
|
1036
|
+
export {};
|