UNPKG

62 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.getAutoVersion = exports.determineNextVersion = void 0;
4const tslib_1 = require("tslib");
5const dotenv_1 = tslib_1.__importDefault(require("dotenv"));
6const env_ci_1 = tslib_1.__importDefault(require("env-ci"));
7const fs_1 = tslib_1.__importDefault(require("fs"));
8const path_1 = tslib_1.__importDefault(require("path"));
9const terminal_link_1 = tslib_1.__importDefault(require("terminal-link"));
10const log_symbols_1 = tslib_1.__importDefault(require("log-symbols"));
11const chalk_1 = tslib_1.__importDefault(require("chalk"));
12const semver_1 = require("semver");
13const endent_1 = tslib_1.__importDefault(require("endent"));
14const url_1 = require("url");
15const await_to_js_1 = tslib_1.__importDefault(require("await-to-js"));
16const https_proxy_agent_1 = tslib_1.__importDefault(require("https-proxy-agent"));
17const semver_2 = require("./semver");
18const config_1 = tslib_1.__importDefault(require("./config"));
19const git_1 = tslib_1.__importDefault(require("./git"));
20const init_1 = tslib_1.__importDefault(require("./init"));
21const release_1 = tslib_1.__importStar(require("./release"));
22const semver_3 = tslib_1.__importStar(require("./semver"));
23const exec_promise_1 = tslib_1.__importDefault(require("./utils/exec-promise"));
24const load_plugins_1 = require("./utils/load-plugins");
25const logger_1 = tslib_1.__importStar(require("./utils/logger"));
26const make_hooks_1 = require("./utils/make-hooks");
27const get_current_branch_1 = require("./utils/get-current-branch");
28const match_sha_to_pr_1 = require("./match-sha-to-pr");
29const get_repository_1 = tslib_1.__importDefault(require("./utils/get-repository"));
30const validate_config_1 = require("./validate-config");
31const omit_1 = require("./utils/omit");
32const child_process_1 = require("child_process");
33const is_binary_1 = tslib_1.__importDefault(require("./utils/is-binary"));
34const git_reset_1 = require("./utils/git-reset");
35const proxyUrl = process.env.https_proxy || process.env.http_proxy;
36const env = env_ci_1.default();
37/** Make a HTML detail */
38const makeDetail = (summary, body) => endent_1.default `
39 <details>
40 <summary>${summary}</summary>
41 <br />
42
43 ${body}
44 </details>
45`;
46/** Load the .env file into process.env. Useful for local usage. */
47const loadEnv = () => {
48 const envFile = path_1.default.resolve(process.cwd(), ".env");
49 if (!fs_1.default.existsSync(envFile)) {
50 return;
51 }
52 const envConfig = dotenv_1.default.parse(fs_1.default.readFileSync(envFile));
53 Object.entries(envConfig).forEach(([key, value]) => {
54 process.env[key] = value;
55 });
56};
57/** Get the pr number from user input or the CI env. */
58function getPrNumberFromEnv(pr) {
59 const envPr = "pr" in env && Number(env.pr);
60 const prNumber = pr || envPr;
61 return prNumber;
62}
63/**
64 * Bump the version but no too much.
65 *
66 * @example
67 * currentVersion = 1.0.0
68 * nextVersion = 2.0.0-next.0
69 * output = 2.0.0-next.1
70 */
71function determineNextVersion(lastVersion, currentVersion, bump, tag) {
72 const next = semver_1.inc(lastVersion, `pre${bump}`, tag);
73 return !next || semver_1.lte(next, currentVersion)
74 ? semver_1.inc(currentVersion, "prerelease", tag) || "prerelease"
75 : next;
76}
77exports.determineNextVersion = determineNextVersion;
78/** Print the current version of "auto" */
79function getAutoVersion() {
80 const packagePath = path_1.default.join(__dirname, "../package.json");
81 const packageJson = JSON.parse(fs_1.default.readFileSync(packagePath, "utf8"));
82 return packageJson.version;
83}
84exports.getAutoVersion = getAutoVersion;
85/** Escape a string for use in a Regex */
86function escapeRegExp(str) {
87 return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
88}
89/**
90 * The "auto" node API. Its public interface matches the
91 * commands you can run from the CLI
92 */
93class Auto {
94 /** Initialize auto and it's environment */
95 constructor(options = {}) {
96 /** Check if `git status` is clean. */
97 this.checkClean = async () => {
98 const status = await exec_promise_1.default("git", ["status", "--porcelain"]);
99 if (!status) {
100 return;
101 }
102 this.logger.log.error("Changed Files:\n", status);
103 throw new Error("Working directory is not clean, make sure all files are committed");
104 };
105 /** Prefix a version with a "v" if needed */
106 this.prefixRelease = (release) => {
107 var _a;
108 if (!this.release) {
109 throw this.createErrorMessage();
110 }
111 return ((_a = this.config) === null || _a === void 0 ? void 0 : _a.noVersionPrefix) || release.startsWith("v")
112 ? release
113 : `v${release}`;
114 };
115 this.options = options;
116 this.baseBranch = options.baseBranch || "master";
117 logger_1.setLogLevel("quiet" in options && options.quiet
118 ? "quiet"
119 : Array.isArray(options.verbose) && options.verbose.length > 1
120 ? "veryVerbose"
121 : options.verbose
122 ? "verbose"
123 : undefined);
124 this.logger = logger_1.default();
125 this.hooks = make_hooks_1.makeHooks();
126 this.hooks.getRepository.tapPromise("Get repo info from origin", get_repository_1.default);
127 this.hooks.onCreateRelease.tap("Link onCreateChangelog", (release) => {
128 release.hooks.onCreateChangelog.tap("Link onCreateChangelog", (changelog, version) => {
129 this.hooks.onCreateChangelog.call(changelog, version);
130 });
131 });
132 this.hooks.onCreateRelease.tap("Link onCreateLogParse", (release) => {
133 release.hooks.onCreateLogParse.tap("Link onCreateLogParse", (logParse) => {
134 this.hooks.onCreateLogParse.call(logParse);
135 });
136 });
137 this.hooks.beforeCommitChangelog.tapPromise("Old Version Branches", async ({ bump }) => {
138 var _a;
139 if (bump === semver_3.default.major && ((_a = this.config) === null || _a === void 0 ? void 0 : _a.versionBranches)) {
140 const branch = `${this.config.versionBranches}${semver_1.major(await this.hooks.getPreviousVersion.promise())}`;
141 await exec_promise_1.default("git", ["branch", branch]);
142 this.logger.log.success(`Created old version branch: ${branch}`);
143 await exec_promise_1.default("git", ["push", this.remote, branch]);
144 }
145 });
146 /**
147 * Determine if repo is behind HEAD of current branch. We do this in
148 * the "afterVersion" hook so the check happens as late as possible.
149 */
150 this.hooks.afterVersion.tapPromise("Check remote for commits", async () => {
151 // Credit from https://github.com/semantic-release/semantic-release/blob/b2b7b57fbd51af3fe25accdd6cd8499beb9005e5/lib/git.js#L179
152 // `true` is the HEAD of the current local branch is the same as the HEAD of the remote branch, falsy otherwise.
153 try {
154 const currentBranch = get_current_branch_1.getCurrentBranch();
155 const heads = await exec_promise_1.default("git", [
156 "ls-remote",
157 "--heads",
158 this.remote,
159 currentBranch,
160 ]);
161 this.logger.verbose.info("Branch:", currentBranch);
162 this.logger.verbose.info("HEADs:", heads);
163 const [, remoteHead] = heads.match(/^(\w+)?/) || [];
164 if (remoteHead) {
165 // This will throw if the branch is ahead of the current branch
166 child_process_1.execSync(`git merge-base --is-ancestor ${remoteHead} HEAD`, {
167 stdio: "ignore",
168 });
169 }
170 this.logger.verbose.info("Current branch is up to date, proceeding with release");
171 }
172 catch (error) {
173 // If we are behind or there is no match, exit and skip the release
174 this.logger.log.warn("Current commit is behind, skipping the release to avoid collisions.");
175 this.logger.verbose.warn(error);
176 process.exit(0);
177 }
178 });
179 loadEnv();
180 this.logger.verbose.info("ENV:", env);
181 }
182 /** List some of the plugins available to auto */
183 async listPlugins() {
184 await load_plugins_1.listPlugins(this.config, this.logger, this.getExtendedLocation(this.config));
185 }
186 /**
187 * Load the default hook behaviors. Should run after loadPlugins so
188 * plugins take precedence.
189 */
190 loadDefaultBehavior() {
191 this.hooks.makeRelease.tapPromise("Default", async (options) => {
192 if (options.dryRun) {
193 const bump = await this.getVersion({ from: options.from });
194 this.logger.log.info(`Would have created a release on GitHub for version: ${semver_1.inc(options.newVersion, bump)}`);
195 this.logger.log.note('The above version would only get released if ran with "shipit" or a custom script that bumps the version using the "version" command');
196 }
197 else {
198 this.logger.log.info(`Releasing ${options.newVersion} to GitHub.`);
199 const release = await this.git.publish(options.fullReleaseNotes, options.newVersion, options.isPrerelease);
200 this.logger.log.info(release.data.html_url);
201 return release;
202 }
203 });
204 }
205 /**
206 * Load the .autorc from the file system, set up defaults, combine with CLI args
207 * load the extends property, load the plugins and start the git remote interface.
208 */
209 async loadConfig() {
210 const configLoader = new config_1.default(this.logger);
211 const userConfig = await configLoader.loadConfig();
212 this.logger.verbose.success("Loaded `auto` with config:", userConfig);
213 // Allow plugins to be overriden for testing
214 this.config = Object.assign(Object.assign({}, userConfig), { plugins: this.options.plugins || userConfig.plugins });
215 this.loadPlugins(this.config);
216 this.loadDefaultBehavior();
217 this.config = this.hooks.modifyConfig.call(this.config);
218 this.labels = this.config.labels;
219 this.semVerLabels = release_1.getVersionMap(this.config.labels);
220 await this.hooks.beforeRun.promise(this.config);
221 const errors = [
222 ...(await validate_config_1.validateAutoRc(this.config)),
223 ...(await validate_config_1.validatePlugins(this.hooks.validateConfig, this.config)),
224 ];
225 if (errors.length) {
226 this.logger.log.error(endent_1.default `
227 Found configuration errors:
228
229 ${errors.map(validate_config_1.formatError).join("\n")}
230 `, "\n");
231 this.logger.log.warn("These errors are for the fully loaded configuration (this is why some paths might seem off).");
232 if (this.config.extends) {
233 this.logger.log.warn("Some errors might originate from an extend config.");
234 }
235 process.exit(1);
236 }
237 const config = Object.assign(Object.assign(Object.assign({}, this.config), omit_1.omit(this.options, ["_command", "_all", "main"])), { baseBranch: this.baseBranch });
238 this.config = config;
239 const repository = await this.getRepo(config);
240 const token = (repository === null || repository === void 0 ? void 0 : repository.token) || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
241 if (!token || token === "undefined") {
242 this.logger.log.error("No GitHub was found. Make sure it is available on process.env.GH_TOKEN.");
243 throw new Error("GitHub token not found!");
244 }
245 const githubOptions = Object.assign(Object.assign({ owner: config.owner, repo: config.repo }, repository), { token, agent: proxyUrl ? https_proxy_agent_1.default(proxyUrl) : undefined, baseUrl: config.githubApi || "https://api.github.com", graphqlBaseUrl: config.githubGraphqlApi || config.githubApi || "https://api.github.com" });
246 this.git = this.startGit(githubOptions);
247 this.release = new release_1.default(this.git, config, this.logger);
248 this.remote = await this.getRemote();
249 this.logger.verbose.info(`Using remote: ${this.remote.replace(token, `****${token.substring(0, 4)}`)}`);
250 this.hooks.onCreateRelease.call(this.release);
251 return config;
252 }
253 /** Determine the remote we have auth to push to. */
254 async getRemote() {
255 const [, configuredRemote = "origin"] = await await_to_js_1.default(exec_promise_1.default("git", ["remote", "get-url", "origin"]));
256 if (!this.git) {
257 return configuredRemote;
258 }
259 const { html_url } = (await this.git.getProject()) || { html_url: "" };
260 const GIT_TOKENS = {
261 // GitHub Actions require the "x-access-token:" prefix for git access
262 // https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/#http-based-git-access-by-an-installation
263 GITHUB_TOKEN: process.env.GITHUB_ACTION
264 ? `x-access-token:${process.env.GITHUB_TOKEN}`
265 : undefined,
266 };
267 const envVar = Object.keys(GIT_TOKENS).find((v) => process.env[v]) || "";
268 const gitCredentials = GIT_TOKENS[envVar] || process.env.GH_TOKEN;
269 if (gitCredentials) {
270 const _a = url_1.parse(html_url), { port, hostname } = _a, parsed = tslib_1.__rest(_a, ["port", "hostname"]);
271 const urlWithAuth = url_1.format(Object.assign(Object.assign({}, parsed), { auth: gitCredentials, host: `${hostname}${port ? `:${port}` : ""}` }));
272 if (await this.git.verifyAuth(urlWithAuth)) {
273 this.logger.veryVerbose.note("Using token + html URL as remote");
274 return urlWithAuth;
275 }
276 }
277 if (html_url && (await this.git.verifyAuth(html_url))) {
278 this.logger.veryVerbose.note("Using bare html URL as remote");
279 return html_url;
280 }
281 this.logger.veryVerbose.note("Using remote set in environment");
282 return configuredRemote;
283 }
284 /** Interactive prompt for initializing an .autorc */
285 async init() {
286 const init = new init_1.default(this);
287 await init.run();
288 }
289 /** Check if auto is set up correctly */
290 async info(args) {
291 var _a, _b;
292 if (!this.git) {
293 return { hasError: false };
294 }
295 const [, gitVersion = ""] = await await_to_js_1.default(exec_promise_1.default("git", ["--version"]));
296 const [noProject, project] = await await_to_js_1.default(this.git.getProject());
297 const repo = (await this.getRepo(this.config)) || {};
298 const repoLink = terminal_link_1.default(`${repo.owner}/${repo.repo}`, project === null || project === void 0 ? void 0 : project.html_url);
299 const author = (await this.getGitUser()) || {};
300 const [, lastRelease = "0.0.0"] = await await_to_js_1.default(this.git.getLatestRelease());
301 const version = await this.getCurrentVersion(lastRelease);
302 const [err, latestRelease] = await await_to_js_1.default(this.git.getLatestReleaseInfo());
303 const latestReleaseLink = latestRelease
304 ? terminal_link_1.default(latestRelease.tag_name, latestRelease.html_url)
305 : "";
306 const { headers } = await this.git.github.request("HEAD /");
307 const access = headers;
308 const rateLimitRefresh = new Date(Number(access["x-ratelimit-reset"]) * 1000);
309 const token = this.git.options.token || "";
310 const tokenRefresh = `${rateLimitRefresh.toLocaleTimeString()} ${rateLimitRefresh.toLocaleDateString("en-us")}`;
311 const projectLabels = await this.git.getProjectLabels();
312 const hasLabels = (_a = this.config) === null || _a === void 0 ? void 0 : _a.labels.reduce((acc, label) => {
313 var _a, _b;
314 if (label.name === "release" &&
315 !((_a = this.config) === null || _a === void 0 ? void 0 : _a.onlyPublishWithReleaseLabel)) {
316 return acc;
317 }
318 if (label.name === "skip-release" && ((_b = this.config) === null || _b === void 0 ? void 0 : _b.onlyPublishWithReleaseLabel)) {
319 return acc;
320 }
321 return acc && projectLabels.includes(label.name);
322 }, true);
323 const { permission, user } = (await this.git.getTokenPermissionLevel()) || {};
324 let hasError = false;
325 /** Log if a configuration is correct. */
326 const logSuccess = (err) => {
327 if (err) {
328 hasError = true;
329 return log_symbols_1.default.error;
330 }
331 return log_symbols_1.default.success;
332 };
333 console.log("");
334 // prettier-ignore
335 console.log(endent_1.default `
336 ${chalk_1.default.underline.white('Environment Information:')}
337
338 "auto" version: v${getAutoVersion()}
339 "git" version: v${gitVersion.replace('git version ', '')}
340 "node" version: ${process.version.trim()}${access['x-github-enterprise-version']
341 ? `GHE version: v${access['x-github-enterprise-version']}\n`
342 : '\n'}
343 ${chalk_1.default.underline.white('Project Information:')}
344
345 ${logSuccess(noProject)} Repository: ${repoLink}
346 ${logSuccess(!author.name)} Author Name: ${author.name}
347 ${logSuccess(!author.email)} Author Email: ${author.email}
348 ${logSuccess(!version)} Current Version: ${this.prefixRelease(version)}
349 ${logSuccess(err)} Latest Release: ${latestReleaseLink}
350
351 ${logSuccess(!hasLabels)} Labels configured on GitHub project ${hasLabels ? '' : '(Try running "auto create-labels")'}
352
353 ${chalk_1.default.underline.white('GitHub Token Information:')}
354
355 ${logSuccess(!token)} Token: ${`[Token starting with ${token.substring(0, 4)}]`}
356 ${logSuccess(!(permission === 'admin' || permission === 'write'))} Repo Permission: ${permission}
357 ${logSuccess(!(user === null || user === void 0 ? void 0 : user.login))} User: ${user === null || user === void 0 ? void 0 : user.login}
358 ${logSuccess()} API: ${terminal_link_1.default(this.git.options.baseUrl, this.git.options.baseUrl)}
359 ${logSuccess(!((_b = access['x-oauth-scopes']) === null || _b === void 0 ? void 0 : _b.includes('repo')))} Enabled Scopes: ${access['x-oauth-scopes']}
360 ${logSuccess(Number(access['x-ratelimit-remaining']) === 0)} Rate Limit: ${access['x-ratelimit-remaining'] || '∞'}/${access['x-ratelimit-limit'] || '∞'} ${access['ratelimit-reset'] ? `(Renews @ ${tokenRefresh})` : ''}
361 `);
362 console.log("");
363 if (args.listPlugins) {
364 await this.listPlugins();
365 }
366 return { hasError };
367 }
368 /** Determine if the repo is currently in a prerelease branch */
369 inPrereleaseBranch() {
370 var _a;
371 const branch = get_current_branch_1.getCurrentBranch();
372 const prereleaseBranches = (_a = this.config) === null || _a === void 0 ? void 0 : _a.prereleaseBranches;
373 return Boolean(branch && prereleaseBranches.includes(branch));
374 }
375 /** Determine if the repo is currently in a old-version branch */
376 inOldVersionBranch() {
377 var _a;
378 const branch = get_current_branch_1.getCurrentBranch();
379 const oldVersionBranchPrefix = (_a = this.config) === null || _a === void 0 ? void 0 : _a.versionBranches;
380 return Boolean(oldVersionBranchPrefix &&
381 branch &&
382 new RegExp(`^${escapeRegExp(oldVersionBranchPrefix)}`).test(branch));
383 }
384 /**
385 * Create all of the user's labels on the git remote if the don't already exist
386 */
387 async createLabels(options = {}) {
388 if (!this.release || !this.labels) {
389 throw this.createErrorMessage();
390 }
391 await this.release.addLabelsToProject(this.labels, options);
392 }
393 /**
394 * Get the labels on a specific PR. Defaults to the labels of the last merged PR
395 */
396 async label({ pr } = {}) {
397 if (!this.git) {
398 throw this.createErrorMessage();
399 }
400 this.logger.verbose.info("Using command: 'label'");
401 const number = getPrNumberFromEnv(pr);
402 let labels = [];
403 if (number) {
404 labels = await this.git.getLabels(number);
405 }
406 else {
407 const pulls = await this.git.getPullRequests({
408 state: "closed",
409 });
410 const lastMerged = pulls
411 .sort((a, b) => new Date(b.merged_at).getTime() - new Date(a.merged_at).getTime())
412 .find((pull) => pull.merged_at);
413 if (lastMerged) {
414 labels = lastMerged.labels.map((label) => label.name);
415 }
416 }
417 if (labels.length) {
418 console.log(labels.join("\n"));
419 }
420 }
421 /**
422 * Create a status on a PR.
423 */
424 async prStatus(_a) {
425 var { dryRun, pr, url } = _a, options = tslib_1.__rest(_a, ["dryRun", "pr", "url"]);
426 if (!this.git) {
427 throw this.createErrorMessage();
428 }
429 let { sha } = options;
430 let prNumber;
431 try {
432 prNumber = this.getPrNumber("pr", pr);
433 }
434 catch (error) {
435 // default to sha if no PR found
436 }
437 this.logger.verbose.info("Using command: 'pr-status'");
438 if (!sha && prNumber) {
439 this.logger.verbose.info("Getting commit SHA from PR.");
440 const res = await this.git.getPullRequest(prNumber);
441 sha = res.data.head.sha;
442 }
443 else if (!sha) {
444 this.logger.verbose.info("No PR found, getting commit SHA from HEAD.");
445 sha = await this.git.getSha();
446 }
447 this.logger.verbose.info("Found PR SHA:", sha);
448 const target_url = url;
449 if (dryRun) {
450 this.logger.verbose.info("`pr` dry run complete.");
451 }
452 else {
453 try {
454 await this.git.createStatus(Object.assign(Object.assign({}, options), { sha,
455 target_url }));
456 }
457 catch (error) {
458 throw new Error(`Failed to post status to Pull Request with error code ${error.status}`);
459 }
460 this.logger.log.success("Posted status to Pull Request.");
461 }
462 this.logger.verbose.success("Finished `pr` command");
463 }
464 /**
465 * Check that a PR has a SEMVER label. Set a success status on the PR.
466 */
467 async prCheck(_a) {
468 var _b, _c;
469 var { dryRun, pr, url } = _a, options = tslib_1.__rest(_a, ["dryRun", "pr", "url"]);
470 if (!this.git || !this.release || !this.semVerLabels) {
471 throw this.createErrorMessage();
472 }
473 this.logger.verbose.info(`Using command: 'pr-check' for '${url}'`);
474 const target_url = url;
475 const prNumber = this.getPrNumber("prCheck", pr);
476 let msg;
477 let sha;
478 try {
479 const res = await this.git.getPullRequest(prNumber);
480 sha = res.data.head.sha;
481 const labels = await this.git.getLabels(prNumber);
482 const labelValues = [...this.semVerLabels.values()];
483 const releaseTag = labels.find((l) => l === "release");
484 const skipReleaseLabels = (((_b = this.config) === null || _b === void 0 ? void 0 : _b.labels.filter((l) => l.releaseType === "skip")) || []).map((l) => l.name);
485 const skipReleaseTag = labels.find((l) => skipReleaseLabels.includes(l));
486 const semverTag = labels.find((l) => labelValues.some((labelValue) => labelValue.includes(l)) &&
487 !skipReleaseLabels.includes(l) &&
488 l !== "release");
489 const branch = get_current_branch_1.getCurrentBranch();
490 if (branch && ((_c = this.config) === null || _c === void 0 ? void 0 : _c.prereleaseBranches.includes(branch))) {
491 msg = {
492 description: "PR will graduate prerelease once merged",
493 state: "success",
494 };
495 }
496 else if (semverTag === undefined && !skipReleaseTag) {
497 throw new Error("No semver label!");
498 }
499 else {
500 this.logger.log.success(`PR is using label: ${semverTag || skipReleaseTag}`);
501 let description;
502 if (skipReleaseTag) {
503 description = "PR will not create a release";
504 }
505 else if (releaseTag) {
506 description = `PR will create release once merged - ${semverTag}`;
507 }
508 else {
509 description = `CI - ${semverTag}`;
510 }
511 msg = {
512 description,
513 state: "success",
514 };
515 }
516 }
517 catch (error) {
518 msg = {
519 description: error.message,
520 state: "error",
521 };
522 }
523 this.logger.verbose.info("Posting status to GitHub\n", msg);
524 if (dryRun) {
525 this.logger.verbose.info("`pr-check` dry run complete.");
526 }
527 else {
528 try {
529 await this.git.createStatus(Object.assign(Object.assign(Object.assign({}, options), msg), { target_url,
530 sha }));
531 this.logger.log.success("Posted status to Pull Request.");
532 }
533 catch (error) {
534 throw new Error(`Failed to post status to Pull Request with error code ${error.status}`);
535 }
536 }
537 this.logger.verbose.success("Finished `pr-check` command");
538 }
539 /**
540 * Comment on a PR. Only one comment will be present on the PR, Older comments are removed.
541 * You can use the "context" option to multiple comments on a PR.
542 */
543 async comment(args) {
544 const options = Object.assign(Object.assign({}, this.getCommandDefault("comment")), args);
545 const { message, pr, context = "default", dryRun, delete: deleteFlag, edit: editFlag, } = options;
546 if (!this.git) {
547 throw this.createErrorMessage();
548 }
549 this.logger.verbose.info("Using command: 'comment'");
550 const prNumber = this.getPrNumber("comment", pr);
551 if (dryRun) {
552 if (deleteFlag) {
553 this.logger.log.info(`Would have deleted comment on ${prNumber} under "${context}" context`);
554 }
555 else if (editFlag) {
556 this.logger.log.info(`Would have edited the comment on ${prNumber} under "${context}" context.\n\nNew message: ${message}`);
557 }
558 else {
559 this.logger.log.info(`Would have commented on ${prNumber} under "${context}" context:\n\n${message}`);
560 }
561 }
562 else if (editFlag && message) {
563 await this.git.editComment(message, prNumber, context);
564 this.logger.log.success(`Edited comment on PR #${prNumber} under context "${context}"`);
565 }
566 else {
567 if (deleteFlag) {
568 await this.git.deleteComment(prNumber, context);
569 this.logger.log.success(`Deleted comment on PR #${prNumber} under context "${context}"`);
570 }
571 if (message) {
572 await this.git.createComment(message, prNumber, context);
573 this.logger.log.success(`Commented on PR #${prNumber}`);
574 }
575 }
576 }
577 /**
578 * Update the body of a PR with a message. Only one message will be present in the PR,
579 * Older messages are removed. You can use the "context" option to multiple message
580 * in a PR body.
581 */
582 async prBody(options) {
583 const { message, pr, context = "default", dryRun, delete: deleteFlag, } = options;
584 if (!this.git) {
585 throw this.createErrorMessage();
586 }
587 this.logger.verbose.info("Using command: 'pr-body'");
588 const prNumber = this.getPrNumber("pr-body", pr);
589 if (dryRun) {
590 if (deleteFlag) {
591 this.logger.log.info(`Would have deleted PR body on ${prNumber} under "${context}" context`);
592 }
593 else {
594 this.logger.log.info(`Would have appended to PR body on ${prNumber} under "${context}" context:\n\n${message}`);
595 }
596 }
597 else {
598 if (deleteFlag) {
599 await this.git.addToPrBody("", prNumber, context);
600 }
601 if (message) {
602 await this.git.addToPrBody(message, prNumber, context);
603 }
604 this.logger.log.success(`Updated body on PR #${prNumber}`);
605 }
606 }
607 /**
608 * Calculate the version bump for the current state of the repository.
609 */
610 async version(options = {}) {
611 this.logger.verbose.info("Using command: 'version'");
612 const bump = await this.getVersion(options);
613 console.log(bump);
614 }
615 /**
616 * Calculate the the changelog and commit it.
617 */
618 async changelog(options) {
619 this.logger.verbose.info("Using command: 'changelog'");
620 await this.makeChangelog(options);
621 }
622 /**
623 * Make a release to the git remote with the changes.
624 */
625 async runRelease(options = {}) {
626 this.logger.verbose.info("Using command: 'release'");
627 await this.makeRelease(options);
628 }
629 /** Create a canary (or test) version of the project */
630 // eslint-disable-next-line complexity
631 async canary(args = {}) {
632 const options = Object.assign(Object.assign({}, this.getCommandDefault("canary")), args);
633 if (!this.git || !this.release) {
634 throw this.createErrorMessage();
635 }
636 if (!this.hooks.canary.isUsed()) {
637 this.logger.log.warn(endent_1.default `
638 None of the plugins that you are using implement the \`canary\` command!
639
640 "canary" releases are versions that are used solely to test changes. They make sense on some platforms (ex: npm) but not all!
641
642 If you think your package manager has the ability to support canaries please file an issue or submit a pull request,
643 `);
644 process.exit(0);
645 }
646 if (!args.dryRun) {
647 await this.checkClean();
648 }
649 let { pr, build } = await this.getPrEnvInfo();
650 pr = options.pr ? String(options.pr) : pr;
651 build = options.build ? String(options.build) : build;
652 this.logger.verbose.info("Canary info found:", { pr, build });
653 const from = (await this.git.shaExists("HEAD^")) ? "HEAD^" : "HEAD";
654 const head = await this.release.getCommitsInRelease(from);
655 const labels = head.map((commit) => commit.labels);
656 let version = semver_3.calculateSemVerBump(labels, this.semVerLabels, this.config);
657 if (version === semver_3.default.noVersion) {
658 if (options.force) {
659 version = semver_3.default.patch;
660 }
661 else {
662 this.logger.log.info("Skipping canary release due to PR being specifying no release. Use `auto canary --force` to override this setting");
663 return;
664 }
665 }
666 let canaryVersion = "";
667 let newVersion = "";
668 if (pr) {
669 canaryVersion = `${canaryVersion}.${pr}`;
670 }
671 if (build) {
672 canaryVersion = `${canaryVersion}.${build}`;
673 }
674 if (!pr || !build) {
675 canaryVersion = `${canaryVersion}.${await this.git.getSha(true)}`;
676 }
677 canaryVersion = `canary${canaryVersion}`;
678 if (options.dryRun) {
679 const lastRelease = await this.git.getLatestRelease();
680 const current = await this.getCurrentVersion(lastRelease);
681 if (semver_1.parse(current)) {
682 const next = determineNextVersion(lastRelease, current, version, canaryVersion);
683 if (options.quiet) {
684 console.log(next);
685 }
686 else {
687 this.logger.log.warn(`Published canary identifier would be: ${next}`);
688 }
689 }
690 else if (options.quiet) {
691 console.log(`-${canaryVersion}`);
692 }
693 else {
694 this.logger.log.warn(`Published canary identifier would be: "-${canaryVersion}"`);
695 }
696 }
697 else {
698 this.logger.verbose.info("Calling canary hook");
699 const result = await this.hooks.canary.promise(version, canaryVersion);
700 if (typeof result === "object" && "error" in result) {
701 this.logger.log.warn(result.error);
702 return;
703 }
704 if (!result) {
705 return;
706 }
707 newVersion = typeof result === "string" ? result : result.newVersion;
708 const messageHeader = (options.message || "📦 Published PR as canary version: %v").replace("%v", !newVersion || newVersion.includes("\n")
709 ? newVersion
710 : `<code>${newVersion}</code>`);
711 if (options.message !== "false" && pr) {
712 const message = typeof result === "string"
713 ? messageHeader
714 : makeDetail(messageHeader, result.details);
715 await this.prBody({
716 pr: Number(pr),
717 context: "canary-version",
718 message,
719 });
720 }
721 this.logger.log.success(`Published canary version${newVersion ? `: ${newVersion}` : ""}`);
722 if (args.quiet) {
723 console.log(newVersion);
724 }
725 await git_reset_1.gitReset();
726 }
727 let latestTag;
728 try {
729 latestTag = await this.git.getLatestTagInBranch();
730 }
731 catch (error) {
732 latestTag = await this.git.getFirstCommit();
733 }
734 const commitsInRelease = await this.release.getCommits(latestTag);
735 return { newVersion, commitsInRelease, context: "canary" };
736 }
737 /**
738 * Create a next (or test) version of the project. If on master will
739 * release to the default "next" branch.
740 */
741 async next(args) {
742 var _a;
743 const options = Object.assign(Object.assign({}, this.getCommandDefault("next")), args);
744 if (!this.git || !this.release) {
745 throw this.createErrorMessage();
746 }
747 if (!this.hooks.next.isUsed()) {
748 this.logger.log.warn(endent_1.default `
749 None of the plugins that you are using implement the \`next\` command!
750
751 "next" releases are pre-releases such as betas or alphas. They make sense on some platforms (ex: npm) but not all!
752
753 If you think your package manager has the ability to support "next" releases please file an issue or submit a pull request,
754 `);
755 process.exit(0);
756 }
757 if (!args.dryRun) {
758 await this.checkClean();
759 }
760 await this.setGitUser();
761 this.hooks.onCreateLogParse.tap("Omit merges from master", (logParse) => {
762 logParse.hooks.omitCommit.tap("Omit merges from master", (commit) => {
763 const shouldOmit = commit.subject.match(/^Merge (?:\S+\/)*master/);
764 if (shouldOmit) {
765 this.logger.verbose.info(`Omitting merge commit from master: ${commit.subject}`);
766 return true;
767 }
768 });
769 });
770 const currentBranch = get_current_branch_1.getCurrentBranch();
771 const forkPoints = (await exec_promise_1.default("git", [
772 "rev-list",
773 "--boundary",
774 `${currentBranch}...origin/${this.baseBranch}`,
775 "--left-only",
776 ]))
777 .split("\n")
778 .filter((line) => line.startsWith("-"));
779 const initialForkCommit = (forkPoints[forkPoints.length - 1] || "").slice(1);
780 const lastRelease = initialForkCommit || (await this.git.getLatestRelease());
781 const lastTag = (await this.git.getLastTagNotInBaseBranch(currentBranch)) ||
782 (await this.git.getFirstCommit());
783 const fullReleaseNotes = await this.release.generateReleaseNotes(lastRelease);
784 const commits = await this.release.getCommitsInRelease(lastTag);
785 const releaseNotes = await this.release.generateReleaseNotes(lastTag);
786 const labels = commits.map((commit) => commit.labels);
787 const bump = semver_3.calculateSemVerBump(labels, this.semVerLabels, this.config) ||
788 semver_3.default.patch;
789 if (!args.quiet) {
790 this.logger.log.info("Full Release notes for next release:");
791 console.log(fullReleaseNotes);
792 if (releaseNotes) {
793 this.logger.log.info("Release notes for last change in next release");
794 console.log(releaseNotes);
795 }
796 }
797 if (options.dryRun) {
798 const lastRelease = await this.git.getLatestRelease();
799 const current = await this.getCurrentVersion(lastRelease);
800 if (semver_1.parse(current)) {
801 const prereleaseBranches = (_a = this.config) === null || _a === void 0 ? void 0 : _a.prereleaseBranches;
802 const branch = get_current_branch_1.getCurrentBranch() || "";
803 const prereleaseBranch = prereleaseBranches.includes(branch)
804 ? branch
805 : prereleaseBranches[0];
806 const prerelease = determineNextVersion(lastRelease, current, bump, prereleaseBranch);
807 if (options.quiet) {
808 console.log(prerelease);
809 }
810 else {
811 this.logger.log.success(`Would have created prerelease version: ${prerelease}`);
812 }
813 }
814 else if (options.quiet) {
815 // The following cases could use some work. They are really just there for lerna independent
816 console.log(`${bump} on ${lastTag}`);
817 }
818 else {
819 this.logger.log.success(`Would have created prerelease version with: ${bump} on ${lastTag}`);
820 }
821 return { newVersion: "", commitsInRelease: commits, context: "next" };
822 }
823 this.logger.verbose.info(`Calling "next" hook with: ${bump}`);
824 const result = await this.hooks.next.promise([], bump, {
825 commits,
826 fullReleaseNotes,
827 releaseNotes,
828 });
829 const newVersion = result.join(", ");
830 await Promise.all(result.map(async (prerelease) => {
831 var _a;
832 const release = await ((_a = this.git) === null || _a === void 0 ? void 0 : _a.publish(releaseNotes, prerelease, true));
833 this.logger.verbose.info(release);
834 await this.hooks.afterRelease.promise({
835 lastRelease: lastTag,
836 newVersion: prerelease,
837 commits,
838 releaseNotes,
839 response: release,
840 });
841 }));
842 this.logger.log.success(`Published next version${result.length > 1 ? `s` : ""}: ${newVersion}`);
843 const { pr } = await this.getPrEnvInfo();
844 if (pr) {
845 const message = options.message || "Published prerelease version: %v";
846 if (pr) {
847 await this.prBody({
848 pr: Number(pr),
849 context: "prerelease-version",
850 message: endent_1.default `
851 # Version
852
853 ${message.replace("%v", result.map((r) => `\`${r}\``).join("\n"))}
854
855 <details>
856 <summary>Changelog</summary>
857
858 ${fullReleaseNotes}
859 </details>
860 `,
861 });
862 }
863 }
864 if (options.quiet) {
865 console.log(newVersion);
866 }
867 await git_reset_1.gitReset();
868 return { newVersion, commitsInRelease: commits, context: "next" };
869 }
870 /** Force a release to latest and bypass `shipit` safeguards. */
871 async latest(args = {}) {
872 const options = Object.assign(Object.assign({}, this.getCommandDefault("latest")), args);
873 return this.publishFullRelease(options);
874 }
875 /**
876 * Run the full workflow.
877 *
878 * 1. Calculate version
879 * 2. Make changelog
880 * 3. Publish code
881 * 4. Create a release
882 */
883 async shipit(args = {}) {
884 var _a, _b;
885 const options = Object.assign(Object.assign({}, this.getCommandDefault("shipit")), args);
886 if (!this.git || !this.release) {
887 throw this.createErrorMessage();
888 }
889 this.logger.verbose.info("Using command: 'shipit'");
890 const isPR = "isPr" in env && env.isPr;
891 const from = (await this.git.shaExists("HEAD^")) ? "HEAD^" : "HEAD";
892 const head = await this.release.getCommitsInRelease(from);
893 // env-ci sets branch to target branch (ex: master) in some CI services.
894 // so we should make sure we aren't in a PR just to be safe
895 const currentBranch = get_current_branch_1.getCurrentBranch();
896 const isBaseBranch = !isPR && currentBranch === this.baseBranch;
897 const shouldGraduate = !options.onlyGraduateWithReleaseLabel ||
898 (options.onlyGraduateWithReleaseLabel &&
899 head[0].labels.some((l) => { var _a, _b; return (_b = (_a = this.semVerLabels) === null || _a === void 0 ? void 0 : _a.get("release")) === null || _b === void 0 ? void 0 : _b.includes(l); }));
900 const isPrereleaseBranch = (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.prereleaseBranches) === null || _b === void 0 ? void 0 : _b.some((branch) => currentBranch === branch);
901 const publishPrerelease = isPrereleaseBranch ||
902 (currentBranch === this.baseBranch &&
903 options.onlyGraduateWithReleaseLabel);
904 this.logger.veryVerbose.info({
905 currentBranch,
906 isBaseBranch,
907 isPR,
908 shouldGraduate,
909 isPrereleaseBranch,
910 publishPrerelease,
911 });
912 let publishInfo;
913 let releaseType = "canary";
914 if (isBaseBranch && shouldGraduate) {
915 releaseType = "latest";
916 }
917 else if (this.inOldVersionBranch()) {
918 releaseType = "old";
919 }
920 else if (publishPrerelease) {
921 releaseType = "next";
922 }
923 await this.hooks.beforeShipIt.promise({ releaseType });
924 if (releaseType === "latest") {
925 publishInfo = await this.latest(options);
926 }
927 else if (releaseType === "old") {
928 publishInfo = await this.oldRelease(options);
929 }
930 else if (releaseType === "next") {
931 publishInfo = await this.next(options);
932 }
933 else {
934 publishInfo = await this.canary(options);
935 if (options.dryRun && !options.quiet) {
936 this.logger.log.success("Below is what would happen upon merge of the current branch into master");
937 await this.publishFullRelease(options);
938 }
939 }
940 if (!publishInfo) {
941 return;
942 }
943 const { newVersion, commitsInRelease, context } = publishInfo;
944 await this.hooks.afterShipIt.promise(newVersion, commitsInRelease, {
945 context,
946 });
947 }
948 /** Get the latest version number of the project */
949 async getCurrentVersion(lastRelease) {
950 this.hooks.getPreviousVersion.tap("None", () => {
951 this.logger.veryVerbose.info("No previous release found, using 0.0.0 as previous version.");
952 return this.prefixRelease("0.0.0");
953 });
954 const lastVersion = await this.hooks.getPreviousVersion.promise();
955 if (semver_1.parse(lastRelease) &&
956 semver_1.parse(lastVersion) &&
957 semver_1.gt(lastRelease, lastVersion)) {
958 this.logger.veryVerbose.info("Using latest release as previous version");
959 return lastRelease;
960 }
961 return lastVersion;
962 }
963 /**
964 * A utility function for plugins to check the process for tokens.
965 */
966 checkEnv(pluginName, key) {
967 if (!process.env[key]) {
968 this.logger.log.warn(`${pluginName}: No "${key}" found in environment`);
969 }
970 }
971 /** Make a release to an old version */
972 async oldRelease(options) {
973 var _a;
974 const latestTag = await ((_a = this.git) === null || _a === void 0 ? void 0 : _a.getLatestTagInBranch());
975 const result = await this.publishFullRelease(Object.assign(Object.assign({}, options), { from: latestTag }));
976 if (result) {
977 result.context = "old";
978 }
979 return result;
980 }
981 /** Publish a new version with changelog, publish, and release */
982 async publishFullRelease(options) {
983 if (!this.git || !this.release) {
984 throw this.createErrorMessage();
985 }
986 const version = await this.getVersion(options);
987 this.logger.log.success(`Calculated version bump: ${version || "none"}`);
988 if (version === "") {
989 this.logger.log.info("No version published.");
990 return;
991 }
992 const lastRelease = options.from || (await this.git.getLatestRelease());
993 const commitsInRelease = await this.release.getCommitsInRelease(lastRelease);
994 await this.makeChangelog(Object.assign(Object.assign({}, options), { quiet: undefined, noCommit: options.noChangelog }));
995 if (!options.dryRun) {
996 await this.checkClean();
997 this.logger.verbose.info("Calling version hook");
998 await this.hooks.version.promise(version);
999 this.logger.verbose.info("Calling after version hook");
1000 await this.hooks.afterVersion.promise();
1001 this.logger.verbose.info("Calling publish hook");
1002 await this.hooks.publish.promise(version);
1003 this.logger.verbose.info("Calling after publish hook");
1004 await this.hooks.afterPublish.promise();
1005 }
1006 const newVersion = await this.makeRelease(options);
1007 if (options.dryRun) {
1008 const current = await this.getCurrentVersion(lastRelease);
1009 if (semver_1.parse(current)) {
1010 const next = semver_1.inc(current, version);
1011 if (options.quiet) {
1012 console.log(next);
1013 }
1014 else {
1015 this.logger.log.warn(`Published version would be: ${next}`);
1016 }
1017 }
1018 }
1019 else if (options.quiet) {
1020 console.log(newVersion);
1021 }
1022 return { newVersion, commitsInRelease, context: "latest" };
1023 }
1024 /** Get a pr number from user input or the env */
1025 getPrNumber(command, pr) {
1026 const prNumber = getPrNumberFromEnv(pr);
1027 if (!prNumber) {
1028 this.logger.log.error(endent_1.default `
1029 Could not detect PR number. ${command} must be run from either a PR or have the PR number supplied via the --pr flag.
1030
1031 In some CIs your branch might be built before you open a PR and posting the canary version will fail. In this case subsequent builds should succeed.
1032 `);
1033 process.exit(1);
1034 }
1035 return prNumber;
1036 }
1037 /** Create a client to interact with git */
1038 startGit(gitOptions) {
1039 if (!gitOptions.owner || !gitOptions.repo || !gitOptions.token) {
1040 throw new Error("Must set owner, repo, and GitHub token.");
1041 }
1042 this.logger.verbose.info("Options contain repo information.");
1043 // So that --verbose can be used on public CIs
1044 const tokenlessArgs = Object.assign(Object.assign({}, gitOptions), { token: `[Token starting with ${gitOptions.token.substring(0, 4)}]` });
1045 this.logger.verbose.info("Initializing GitHub API with:\n", tokenlessArgs);
1046 return new git_1.default({
1047 owner: gitOptions.owner,
1048 repo: gitOptions.repo,
1049 token: gitOptions.token,
1050 baseUrl: gitOptions.baseUrl,
1051 baseBranch: this.baseBranch,
1052 graphqlBaseUrl: gitOptions.graphqlBaseUrl,
1053 agent: gitOptions.agent,
1054 }, this.logger);
1055 }
1056 /** Calculate a version from a tag using labels */
1057 async getVersion({ from } = {}) {
1058 if (!this.git || !this.release) {
1059 throw this.createErrorMessage();
1060 }
1061 const isPrerelease = this.inPrereleaseBranch();
1062 const lastRelease = from ||
1063 (isPrerelease && (await this.git.getLatestTagInBranch())) ||
1064 (await this.git.getLatestRelease());
1065 const calculatedBump = await this.release.getSemverBump(lastRelease);
1066 const bump = (isPrerelease && semver_2.preVersionMap.get(calculatedBump)) || calculatedBump;
1067 this.versionBump = bump;
1068 return bump;
1069 }
1070 /** Make a changelog over a range of commits */
1071 async makeChangelog(args = {}) {
1072 const options = Object.assign(Object.assign({}, this.getCommandDefault("changelog")), args);
1073 const { dryRun, from, to, title, message = "Update CHANGELOG.md [skip ci]", noCommit, } = options;
1074 if (!this.release || !this.git) {
1075 throw this.createErrorMessage();
1076 }
1077 await this.setGitUser();
1078 if (title) {
1079 this.release.hooks.createChangelogTitle.tap("Changelog Flag", () => title);
1080 }
1081 const lastRelease = from || (await this.git.getLatestRelease());
1082 const bump = await this.release.getSemverBump(lastRelease, to);
1083 const releaseNotes = await this.release.generateReleaseNotes(lastRelease, to, this.versionBump);
1084 if (dryRun) {
1085 this.logger.log.info("Potential Changelog Addition:\n", releaseNotes);
1086 this.logger.verbose.info("`changelog` dry run complete.");
1087 return;
1088 }
1089 if (args.quiet) {
1090 console.log(releaseNotes);
1091 }
1092 else {
1093 this.logger.log.info("New Release Notes\n", releaseNotes);
1094 }
1095 const currentVersion = await this.getCurrentVersion(lastRelease);
1096 const context = {
1097 bump,
1098 commits: await this.release.getCommits(lastRelease, to || undefined),
1099 releaseNotes,
1100 lastRelease,
1101 currentVersion,
1102 };
1103 if (!noCommit) {
1104 await this.release.addToChangelog(releaseNotes, lastRelease, currentVersion);
1105 await this.hooks.beforeCommitChangelog.promise(context);
1106 await exec_promise_1.default("git", ["commit", "-m", `"${message}"`, "--no-verify"]);
1107 this.logger.verbose.info("Committed new changelog.");
1108 }
1109 await this.hooks.afterAddToChangelog.promise(context);
1110 }
1111 /** Make a release over a range of commits */
1112 async makeRelease(args = {}) {
1113 const options = Object.assign(Object.assign({}, this.getCommandDefault("release")), args);
1114 const { dryRun, from, useVersion, prerelease = false } = options;
1115 if (!this.release || !this.git) {
1116 throw this.createErrorMessage();
1117 }
1118 // This will usually resolve to something on head
1119 const [err, latestTag] = await await_to_js_1.default(this.git.getLatestTagInBranch());
1120 // If its a dry run we want to show what would happen. Otherwise no
1121 // tags indicates that something would definitely go wrong.
1122 if ((err === null || err === void 0 ? void 0 : err.message.includes("No names found")) && !args.dryRun) {
1123 this.logger.log.error(endent_1.default `
1124 Could not find any tags in the local repository. Exiting early.
1125
1126 The "release" command creates GitHub releases for tags that have already been created in your repo.
1127
1128 If there are no tags there is nothing to release. If you don't use "shipit" ensure you tag your releases with the new version number.
1129 `, "\n");
1130 this.logger.verbose.error(err);
1131 return process.exit(1);
1132 }
1133 const isPrerelease = prerelease || this.inPrereleaseBranch();
1134 let lastRelease = from ||
1135 (isPrerelease && (await this.git.getPreviousTagInBranch())) ||
1136 (await this.git.getLatestRelease());
1137 // Find base commit or latest release to generate the changelog to HEAD (new tag)
1138 this.logger.veryVerbose.info(`Using ${lastRelease} as previous release.`);
1139 if (lastRelease.match(/^\d+\.\d+\.\d+/)) {
1140 lastRelease = this.prefixRelease(lastRelease);
1141 }
1142 this.logger.log.info('Current "Latest Release" on Github:', lastRelease);
1143 const commitsInRelease = await this.release.getCommitsInRelease(lastRelease);
1144 const releaseNotes = await this.release.generateReleaseNotes(lastRelease, undefined, this.versionBump);
1145 this.logger.log.info(`Using release notes:\n${releaseNotes}`);
1146 const rawVersion = useVersion ||
1147 (isPrerelease && latestTag) ||
1148 (await this.getCurrentVersion(lastRelease)) ||
1149 latestTag;
1150 if (!rawVersion) {
1151 this.logger.log.error("Could not calculate next version from last tag.");
1152 return;
1153 }
1154 const newVersion = semver_1.parse(rawVersion)
1155 ? this.prefixRelease(rawVersion)
1156 : rawVersion;
1157 if (!dryRun &&
1158 semver_1.parse(newVersion) &&
1159 semver_1.parse(lastRelease) &&
1160 semver_1.eq(newVersion, lastRelease)) {
1161 this.logger.log.warn(`Nothing released to Github. Version to be released is the same as the latest release on Github: ${newVersion}`);
1162 return;
1163 }
1164 const release = await this.hooks.makeRelease.promise({
1165 dryRun,
1166 from: lastRelease,
1167 isPrerelease,
1168 newVersion,
1169 fullReleaseNotes: releaseNotes,
1170 commits: commitsInRelease,
1171 });
1172 if (release) {
1173 await this.hooks.afterRelease.promise({
1174 lastRelease,
1175 newVersion,
1176 commits: commitsInRelease,
1177 releaseNotes,
1178 response: release,
1179 });
1180 }
1181 return newVersion;
1182 }
1183 /** Create an auto initialization error */
1184 createErrorMessage() {
1185 return new Error(`Auto is not initialized! Make sure the have run Auto.loadConfig`);
1186 }
1187 /** Get the current git user */
1188 async getGitUser() {
1189 try {
1190 return {
1191 /** The git user is already set in the current env */
1192 system: true,
1193 email: await exec_promise_1.default("git", ["config", "user.email"]),
1194 name: await exec_promise_1.default("git", ["config", "user.name"]),
1195 };
1196 }
1197 catch (error) {
1198 this.logger.verbose.warn("Could not find git user or email configured in git config");
1199 if (!this.release) {
1200 return;
1201 }
1202 let { email, name } = this.release.config;
1203 this.logger.verbose.warn(`Got author from options: email: ${email}, name ${name}`);
1204 const packageAuthor = await this.hooks.getAuthor.promise();
1205 email = !email && packageAuthor ? packageAuthor.email : email;
1206 name = !name && packageAuthor ? packageAuthor.name : name;
1207 this.logger.verbose.warn(`Using author: ${name} <${email}>`);
1208 return { email, name };
1209 }
1210 }
1211 /**
1212 * Set the git user to make releases and commit with.
1213 */
1214 async setGitUser() {
1215 const user = await this.getGitUser();
1216 if (user && !user.system) {
1217 if (!env.isCi) {
1218 this.logger.log.note(endent_1.default `
1219 Detected local environment, will not set git user. This happens automatically in a CI environment.
1220
1221 If a command fails manually run:
1222
1223 - git config user.email your@email.com
1224 - git config user.name "Your Name"
1225 `);
1226 return;
1227 }
1228 if (user.email) {
1229 await exec_promise_1.default("git", ["config", "user.email", `"${user.email}"`]);
1230 this.logger.verbose.warn(`Set git email to ${user.email}`);
1231 }
1232 if (user.name) {
1233 await exec_promise_1.default("git", ["config", "user.name", `"${user.name}"`]);
1234 this.logger.verbose.warn(`Set git name to ${user.name}`);
1235 }
1236 }
1237 }
1238 /** Get the repo to interact with */
1239 async getRepo(config) {
1240 if (config.owner && config.repo) {
1241 return config;
1242 }
1243 const author = await this.hooks.getRepository.promise();
1244 if (!author || !author.owner || !author.repo) {
1245 this.logger.log.error(endent_1.default `
1246 Cannot find project owner and repository name!
1247
1248 You must do one of the following:
1249
1250 - configure the repo for your package manager (ex: set "repository" in package.json)
1251 - configure your git remote 'origin' to point to your project on GitHub.
1252 `, "");
1253 process.exit(1);
1254 }
1255 return author;
1256 }
1257 /** Find the location of the extended configuration */
1258 getExtendedLocation(config) {
1259 let extendedLocation;
1260 try {
1261 if (config.extends) {
1262 extendedLocation = require.resolve(config.extends);
1263 }
1264 }
1265 catch (error) {
1266 this.logger.veryVerbose.error(error);
1267 }
1268 return extendedLocation;
1269 }
1270 /**
1271 * Apply all of the plugins in the config.
1272 */
1273 loadPlugins(config) {
1274 config.plugins = config.plugins || [is_binary_1.default() ? "git-tag" : "npm"];
1275 const extendedLocation = this.getExtendedLocation(config);
1276 const pluginsPaths = [
1277 require.resolve("./plugins/filter-non-pull-request"),
1278 ...config.plugins,
1279 ];
1280 pluginsPaths
1281 .map((plugin) =>
1282 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1283 typeof plugin === "string" ? [plugin, {}] : plugin)
1284 .map((plugin) => load_plugins_1.loadPlugin(plugin, this.logger, extendedLocation))
1285 .filter((plugin) => Boolean(plugin))
1286 .forEach((plugin) => {
1287 this.logger.verbose.info(`Using ${plugin.name} Plugin...`);
1288 plugin.apply(this);
1289 });
1290 }
1291 /** Get the branch and build number when in CI environment */
1292 async getPrEnvInfo() {
1293 var _a, _b, _c, _d, _e, _f;
1294 // SailEnv falls back to commit SHA
1295 let pr;
1296 let build;
1297 if ("pr" in env && "build" in env) {
1298 ({ pr } = env);
1299 ({ build } = env);
1300 }
1301 else if ("pr" in env && "commit" in env) {
1302 ({ pr } = env);
1303 build = env.commit;
1304 }
1305 // If we haven't detected the PR from the env vars try to match
1306 // the commit to a PR
1307 if (env.isCi && !pr && ((_a = this.git) === null || _a === void 0 ? void 0 : _a.options.owner) && ((_b = this.git) === null || _b === void 0 ? void 0 : _b.options.repo)) {
1308 const commit = await this.git.getSha();
1309 const query = match_sha_to_pr_1.buildSearchQuery((_c = this.git) === null || _c === void 0 ? void 0 : _c.options.owner, (_d = this.git) === null || _d === void 0 ? void 0 : _d.options.repo, [commit]);
1310 if (query) {
1311 const result = await this.git.graphql(query);
1312 if (result === null || result === void 0 ? void 0 : result[`hash_${commit}`]) {
1313 const number = (_f = (_e = result[`hash_${commit}`].edges[0]) === null || _e === void 0 ? void 0 : _e.node) === null || _f === void 0 ? void 0 : _f.number;
1314 if (number) {
1315 pr = String(number);
1316 }
1317 }
1318 }
1319 }
1320 return { pr, build };
1321 }
1322 /** Get the default for a command from the config */
1323 getCommandDefault(name) {
1324 if (!this.config) {
1325 return {};
1326 }
1327 const commandConfig = this.config[name];
1328 return typeof commandConfig === "object" ? commandConfig : {};
1329 }
1330}
1331exports.default = Auto;
1332// Plugin Utils
1333tslib_1.__exportStar(require("./auto-args"), exports);
1334var init_2 = require("./init");
1335Object.defineProperty(exports, "InteractiveInit", { enumerable: true, get: function () { return init_2.default; } });
1336var get_current_branch_2 = require("./utils/get-current-branch");
1337Object.defineProperty(exports, "getCurrentBranch", { enumerable: true, get: function () { return get_current_branch_2.getCurrentBranch; } });
1338var validate_config_2 = require("./validate-config");
1339Object.defineProperty(exports, "validatePluginConfiguration", { enumerable: true, get: function () { return validate_config_2.validatePluginConfiguration; } });
1340var auto_1 = require("./auto");
1341Object.defineProperty(exports, "Auto", { enumerable: true, get: function () { return auto_1.default; } });
1342var semver_4 = require("./semver");
1343Object.defineProperty(exports, "SEMVER", { enumerable: true, get: function () { return semver_4.default; } });
1344var exec_promise_2 = require("./utils/exec-promise");
1345Object.defineProperty(exports, "execPromise", { enumerable: true, get: function () { return exec_promise_2.default; } });
1346var get_lerna_packages_1 = require("./utils/get-lerna-packages");
1347Object.defineProperty(exports, "getLernaPackages", { enumerable: true, get: function () { return get_lerna_packages_1.default; } });
1348var in_folder_1 = require("./utils/in-folder");
1349Object.defineProperty(exports, "inFolder", { enumerable: true, get: function () { return in_folder_1.default; } });
1350//# sourceMappingURL=auto.js.map
\No newline at end of file