UNPKG

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