UNPKG

61 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const tslib_1 = require("tslib");
4const dotenv_1 = tslib_1.__importDefault(require("dotenv"));
5const env_ci_1 = tslib_1.__importDefault(require("env-ci"));
6const fs_1 = tslib_1.__importDefault(require("fs"));
7const path_1 = tslib_1.__importDefault(require("path"));
8const terminal_link_1 = tslib_1.__importDefault(require("terminal-link"));
9const log_symbols_1 = tslib_1.__importDefault(require("log-symbols"));
10const chalk_1 = tslib_1.__importDefault(require("chalk"));
11const semver_1 = require("semver");
12const endent_1 = tslib_1.__importDefault(require("endent"));
13const url_1 = require("url");
14const await_to_js_1 = tslib_1.__importDefault(require("await-to-js"));
15const https_proxy_agent_1 = tslib_1.__importDefault(require("https-proxy-agent"));
16const semver_2 = require("./semver");
17const config_1 = tslib_1.__importDefault(require("./config"));
18const git_1 = tslib_1.__importDefault(require("./git"));
19const init_1 = tslib_1.__importDefault(require("./init"));
20const release_1 = tslib_1.__importStar(require("./release"));
21const semver_3 = tslib_1.__importStar(require("./semver"));
22const exec_promise_1 = tslib_1.__importDefault(require("./utils/exec-promise"));
23const load_plugins_1 = require("./utils/load-plugins");
24const logger_1 = tslib_1.__importStar(require("./utils/logger"));
25const make_hooks_1 = require("./utils/make-hooks");
26const get_current_branch_1 = require("./utils/get-current-branch");
27const match_sha_to_pr_1 = require("./match-sha-to-pr");
28const get_repository_1 = tslib_1.__importDefault(require("./utils/get-repository"));
29const validate_config_1 = require("./validate-config");
30const omit_1 = require("./utils/omit");
31const child_process_1 = require("child_process");
32const is_binary_1 = tslib_1.__importDefault(require("./utils/is-binary"));
33const git_reset_1 = require("./utils/git-reset");
34const proxyUrl = process.env.https_proxy || process.env.http_proxy;
35const env = env_ci_1.default();
36/** Make a HTML detail */
37const makeDetail = (summary, body) => endent_1.default `
38 <details>
39 <summary>${summary}</summary>
40 <br />
41
42 ${body}
43 </details>
44`;
45/** Load the .env file into process.env. Useful for local usage. */
46const loadEnv = () => {
47 const envFile = path_1.default.resolve(process.cwd(), ".env");
48 if (!fs_1.default.existsSync(envFile)) {
49 return;
50 }
51 const envConfig = dotenv_1.default.parse(fs_1.default.readFileSync(envFile));
52 Object.entries(envConfig).forEach(([key, value]) => {
53 process.env[key] = value;
54 });
55};
56/** Get the pr number from user input or the CI env. */
57function getPrNumberFromEnv(pr) {
58 const envPr = "pr" in env && Number(env.pr);
59 const prNumber = pr || envPr;
60 return prNumber;
61}
62/**
63 * Bump the version but no too much.
64 *
65 * @example
66 * currentVersion = 1.0.0
67 * nextVersion = 2.0.0-next.0
68 * output = 2.0.0-next.1
69 */
70function determineNextVersion(lastVersion, currentVersion, bump, tag) {
71 const next = semver_1.inc(lastVersion, `pre${bump}`, tag);
72 return !next || semver_1.lte(next, currentVersion)
73 ? semver_1.inc(currentVersion, "prerelease", tag) || "prerelease"
74 : next;
75}
76exports.determineNextVersion = determineNextVersion;
77/** Print the current version of "auto" */
78function getAutoVersion() {
79 const packagePath = path_1.default.join(__dirname, "../package.json");
80 const packageJson = JSON.parse(fs_1.default.readFileSync(packagePath, "utf8"));
81 return packageJson.version;
82}
83exports.getAutoVersion = getAutoVersion;
84/** Escape a string for use in a Regex */
85function escapeRegExp(str) {
86 return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
87}
88/**
89 * The "auto" node API. Its public interface matches the
90 * commands you can run from the CLI
91 */
92class Auto {
93 /** Initialize auto and it's environment */
94 constructor(options = {}) {
95 /** Check if `git status` is clean. */
96 this.checkClean = async () => {
97 const status = await exec_promise_1.default("git", ["status", "--porcelain"]);
98 if (!status) {
99 return;
100 }
101 this.logger.log.error("Changed Files:\n", status);
102 throw new Error("Working directory is not clean, make sure all files are committed");
103 };
104 /** Prefix a version with a "v" if needed */
105 this.prefixRelease = (release) => {
106 var _a;
107 if (!this.release) {
108 throw this.createErrorMessage();
109 }
110 return ((_a = this.config) === null || _a === void 0 ? void 0 : _a.noVersionPrefix) || release.startsWith("v")
111 ? release
112 : `v${release}`;
113 };
114 this.options = options;
115 this.baseBranch = options.baseBranch || "master";
116 logger_1.setLogLevel("quiet" in options && options.quiet
117 ? "quiet"
118 : Array.isArray(options.verbose) && options.verbose.length > 1
119 ? "veryVerbose"
120 : options.verbose
121 ? "verbose"
122 : undefined);
123 this.logger = logger_1.default();
124 this.hooks = make_hooks_1.makeHooks();
125 this.hooks.getRepository.tapPromise("Get repo info from origin", get_repository_1.default);
126 this.hooks.onCreateRelease.tap("Link onCreateChangelog", (release) => {
127 release.hooks.onCreateChangelog.tap("Link onCreateChangelog", (changelog, version) => {
128 this.hooks.onCreateChangelog.call(changelog, version);
129 });
130 });
131 this.hooks.onCreateRelease.tap("Link onCreateLogParse", (release) => {
132 release.hooks.onCreateLogParse.tap("Link onCreateLogParse", (logParse) => {
133 this.hooks.onCreateLogParse.call(logParse);
134 });
135 });
136 this.hooks.beforeCommitChangelog.tapPromise("Old Version Branches", async ({ bump }) => {
137 var _a, _b;
138 if (bump === semver_3.default.major && ((_a = this.config) === null || _a === void 0 ? void 0 : _a.versionBranches)) {
139 const branch = `${this.config.versionBranches}${semver_1.major(await this.hooks.getPreviousVersion.promise())}`;
140 await exec_promise_1.default("git", [
141 "branch",
142 await ((_b = this.git) === null || _b === void 0 ? void 0 : _b.getLatestTagInBranch()),
143 ]);
144 await exec_promise_1.default("git", ["push", this.remote, branch]);
145 }
146 });
147 /**
148 * Determine if repo is behind HEAD of current branch. We do this in
149 * the "afterVersion" hook so the check happens as late as possible.
150 */
151 this.hooks.afterVersion.tapPromise("Check remote for commits", async () => {
152 // Credit from https://github.com/semantic-release/semantic-release/blob/b2b7b57fbd51af3fe25accdd6cd8499beb9005e5/lib/git.js#L179
153 // `true` is the HEAD of the current local branch is the same as the HEAD of the remote branch, falsy otherwise.
154 try {
155 const currentBranch = get_current_branch_1.getCurrentBranch();
156 const heads = await exec_promise_1.default("git", [
157 "ls-remote",
158 "--heads",
159 this.remote,
160 currentBranch,
161 ]);
162 this.logger.verbose.info("Branch:", currentBranch);
163 this.logger.verbose.info("HEADs:", heads);
164 const [, remoteHead] = heads.match(/^(\w+)?/) || [];
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 this.hooks.beforeRun.call(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 || "https://api.github.com", graphqlBaseUrl: config.githubGraphqlApi || config.githubApi || "https://api.github.com" });
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 prereleaseBranchPrefix = (_a = this.config) === null || _a === void 0 ? void 0 : _a.versionBranches;
381 return Boolean(prereleaseBranchPrefix &&
382 branch &&
383 new RegExp(`^${escapeRegExp(prereleaseBranchPrefix)}`).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 const version = semver_3.calculateSemVerBump(labels, this.semVerLabels, this.config);
658 if (version === semver_3.default.noVersion && !options.force) {
659 this.logger.log.info("Skipping canary release due to PR being specifying no release. Use `auto canary --force` to override this setting");
660 return;
661 }
662 let canaryVersion = "";
663 let newVersion = "";
664 if (pr) {
665 canaryVersion = `${canaryVersion}.${pr}`;
666 }
667 if (build) {
668 canaryVersion = `${canaryVersion}.${build}`;
669 }
670 if (!pr || !build) {
671 canaryVersion = `${canaryVersion}.${await this.git.getSha(true)}`;
672 }
673 canaryVersion = `canary${canaryVersion}`;
674 if (options.dryRun) {
675 const lastRelease = await this.git.getLatestRelease();
676 const current = await this.getCurrentVersion(lastRelease);
677 if (semver_1.parse(current)) {
678 const next = determineNextVersion(lastRelease, current, version, canaryVersion);
679 if (options.quiet) {
680 console.log(next);
681 }
682 else {
683 this.logger.log.warn(`Published canary identifier would be: ${next}`);
684 }
685 }
686 else if (options.quiet) {
687 console.log(`-${canaryVersion}`);
688 }
689 else {
690 this.logger.log.warn(`Published canary identifier would be: "-${canaryVersion}"`);
691 }
692 }
693 else {
694 this.logger.verbose.info("Calling canary hook");
695 const result = await this.hooks.canary.promise(version, canaryVersion);
696 if (typeof result === "object" && "error" in result) {
697 this.logger.log.warn(result.error);
698 return;
699 }
700 if (!result) {
701 return;
702 }
703 newVersion = typeof result === "string" ? result : result.newVersion;
704 const messageHeader = (options.message || "📦 Published PR as canary version: %v").replace("%v", !newVersion || newVersion.includes("\n")
705 ? newVersion
706 : `<code>${newVersion}</code>`);
707 if (options.message !== "false" && pr) {
708 const message = typeof result === "string"
709 ? messageHeader
710 : makeDetail(messageHeader, result.details);
711 await this.prBody({
712 pr: Number(pr),
713 context: "canary-version",
714 message,
715 });
716 }
717 this.logger.log.success(`Published canary version${newVersion ? `: ${newVersion}` : ""}`);
718 if (args.quiet) {
719 console.log(newVersion);
720 }
721 await git_reset_1.gitReset();
722 }
723 let latestTag;
724 try {
725 latestTag = await this.git.getLatestTagInBranch();
726 }
727 catch (error) {
728 latestTag = await this.git.getFirstCommit();
729 }
730 const commitsInRelease = await this.release.getCommits(latestTag);
731 return { newVersion, commitsInRelease, context: "canary" };
732 }
733 /**
734 * Create a next (or test) version of the project. If on master will
735 * release to the default "next" branch.
736 */
737 async next(args) {
738 var _a;
739 const options = Object.assign(Object.assign({}, this.getCommandDefault("next")), args);
740 if (!this.git || !this.release) {
741 throw this.createErrorMessage();
742 }
743 if (!this.hooks.next.isUsed()) {
744 this.logger.log.warn(endent_1.default `
745 None of the plugins that you are using implement the \`next\` command!
746
747 "next" releases are pre-releases such as betas or alphas. They make sense on some platforms (ex: npm) but not all!
748
749 If you think your package manager has the ability to support "next" releases please file an issue or submit a pull request,
750 `);
751 process.exit(0);
752 }
753 if (!args.dryRun) {
754 await this.checkClean();
755 }
756 await this.setGitUser();
757 this.hooks.onCreateLogParse.tap("Omit merges from master", (logParse) => {
758 logParse.hooks.omitCommit.tap("Omit merges from master", (commit) => {
759 const shouldOmit = commit.subject.match(/^Merge (?:\S+\/)*master/);
760 this.logger.verbose.info(`Omit merges from master?" ${shouldOmit}: ${commit.subject}`);
761 if (shouldOmit) {
762 return true;
763 }
764 });
765 });
766 const currentBranch = get_current_branch_1.getCurrentBranch();
767 const initialForkCommit = ((await exec_promise_1.default("git", [
768 "rev-list",
769 "--boundary",
770 `${currentBranch}...origin/${this.baseBranch}`,
771 "--left-only",
772 ]))
773 .split("\n")
774 .filter((line) => line.startsWith("-"))[0] || "").slice(1);
775 const lastRelease = initialForkCommit || (await this.git.getLatestRelease());
776 const lastTag = await this.git.getLastTagNotInBaseBranch(currentBranch);
777 const fullReleaseNotes = await this.release.generateReleaseNotes(lastRelease);
778 const commits = await this.release.getCommitsInRelease(lastTag);
779 const releaseNotes = await this.release.generateReleaseNotes(lastTag);
780 const labels = commits.map((commit) => commit.labels);
781 const bump = semver_3.calculateSemVerBump(labels, this.semVerLabels, this.config) ||
782 semver_3.default.patch;
783 if (!args.quiet) {
784 this.logger.log.info("Full Release notes for next release:");
785 console.log(fullReleaseNotes);
786 if (releaseNotes) {
787 this.logger.log.info("Release notes for last change in next release");
788 console.log(releaseNotes);
789 }
790 }
791 if (options.dryRun) {
792 const lastRelease = await this.git.getLatestRelease();
793 const current = await this.getCurrentVersion(lastRelease);
794 if (semver_1.parse(current)) {
795 const prereleaseBranches = (_a = this.config) === null || _a === void 0 ? void 0 : _a.prereleaseBranches;
796 const branch = get_current_branch_1.getCurrentBranch() || "";
797 const prereleaseBranch = prereleaseBranches.includes(branch)
798 ? branch
799 : prereleaseBranches[0];
800 const prerelease = determineNextVersion(lastRelease, current, bump, prereleaseBranch);
801 if (options.quiet) {
802 console.log(prerelease);
803 }
804 else {
805 this.logger.log.success(`Would have created prerelease version: ${prerelease}`);
806 }
807 }
808 else if (options.quiet) {
809 // The following cases could use some work. They are really just there for lerna independent
810 console.log(`${bump} on ${lastTag}`);
811 }
812 else {
813 this.logger.log.success(`Would have created prerelease version with: ${bump} on ${lastTag}`);
814 }
815 return { newVersion: "", commitsInRelease: commits, context: "next" };
816 }
817 this.logger.verbose.info(`Calling "next" hook with: ${bump}`);
818 const result = await this.hooks.next.promise([], bump);
819 const newVersion = result.join(", ");
820 await Promise.all(result.map(async (prerelease) => {
821 var _a;
822 const release = await ((_a = this.git) === null || _a === void 0 ? void 0 : _a.publish(releaseNotes, prerelease, true));
823 this.logger.verbose.info(release);
824 await this.hooks.afterRelease.promise({
825 lastRelease: lastTag,
826 newVersion: prerelease,
827 commits,
828 releaseNotes,
829 response: release,
830 });
831 }));
832 this.logger.log.success(`Published next version${result.length > 1 ? `s` : ""}: ${newVersion}`);
833 const { pr } = await this.getPrEnvInfo();
834 if (pr) {
835 const message = options.message || "Published prerelease version: %v";
836 if (pr) {
837 await this.prBody({
838 pr: Number(pr),
839 context: "prerelease-version",
840 message: endent_1.default `
841 # Version
842
843 ${message.replace("%v", result.map((r) => `\`${r}\``).join("\n"))}
844
845 <details>
846 <summary>Changelog</summary>
847
848 ${fullReleaseNotes}
849 </details>
850 `,
851 });
852 }
853 }
854 if (options.quiet) {
855 console.log(newVersion);
856 }
857 await git_reset_1.gitReset();
858 return { newVersion, commitsInRelease: commits, context: "next" };
859 }
860 /** Force a release to latest and bypass `shipit` safeguards. */
861 async latest(args = {}) {
862 const options = Object.assign(Object.assign({}, this.getCommandDefault("latest")), args);
863 return this.publishFullRelease(options);
864 }
865 /**
866 * Run the full workflow.
867 *
868 * 1. Calculate version
869 * 2. Make changelog
870 * 3. Publish code
871 * 4. Create a release
872 */
873 async shipit(args = {}) {
874 var _a, _b;
875 const options = Object.assign(Object.assign({}, this.getCommandDefault("shipit")), args);
876 if (!this.git || !this.release) {
877 throw this.createErrorMessage();
878 }
879 this.logger.verbose.info("Using command: 'shipit'");
880 const isPR = "isPr" in env && env.isPr;
881 const from = (await this.git.shaExists("HEAD^")) ? "HEAD^" : "HEAD";
882 const head = await this.release.getCommitsInRelease(from);
883 // env-ci sets branch to target branch (ex: master) in some CI services.
884 // so we should make sure we aren't in a PR just to be safe
885 const currentBranch = get_current_branch_1.getCurrentBranch();
886 const isBaseBranch = !isPR && currentBranch === this.baseBranch;
887 const shouldGraduate = !options.onlyGraduateWithReleaseLabel ||
888 (options.onlyGraduateWithReleaseLabel &&
889 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); }));
890 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);
891 const publishPrerelease = isPrereleaseBranch ||
892 (currentBranch === this.baseBranch &&
893 options.onlyGraduateWithReleaseLabel);
894 this.logger.veryVerbose.info({
895 currentBranch,
896 isBaseBranch,
897 isPR,
898 shouldGraduate,
899 isPrereleaseBranch,
900 publishPrerelease,
901 });
902 let publishInfo;
903 let releaseType = "canary";
904 if (isBaseBranch && shouldGraduate) {
905 releaseType = "latest";
906 }
907 else if (this.inOldVersionBranch()) {
908 releaseType = "old";
909 }
910 else if (publishPrerelease) {
911 releaseType = "next";
912 }
913 await this.hooks.beforeShipIt.promise({ releaseType });
914 if (releaseType === "latest") {
915 publishInfo = await this.latest(options);
916 }
917 else if (releaseType === "old") {
918 publishInfo = await this.oldRelease(options);
919 }
920 else if (releaseType === "next") {
921 publishInfo = await this.next(options);
922 }
923 else {
924 publishInfo = await this.canary(options);
925 if (options.dryRun && !options.quiet) {
926 this.logger.log.success("Below is what would happen upon merge of the current branch into master");
927 await this.publishFullRelease(options);
928 }
929 }
930 if (!publishInfo) {
931 return;
932 }
933 const { newVersion, commitsInRelease, context } = publishInfo;
934 await this.hooks.afterShipIt.promise(newVersion, commitsInRelease, {
935 context,
936 });
937 }
938 /** Get the latest version number of the project */
939 async getCurrentVersion(lastRelease) {
940 this.hooks.getPreviousVersion.tap("None", () => {
941 this.logger.veryVerbose.info("No previous release found, using 0.0.0 as previous version.");
942 return this.prefixRelease("0.0.0");
943 });
944 const lastVersion = await this.hooks.getPreviousVersion.promise();
945 if (semver_1.parse(lastRelease) &&
946 semver_1.parse(lastVersion) &&
947 semver_1.gt(lastRelease, lastVersion)) {
948 this.logger.veryVerbose.info("Using latest release as previous version");
949 return lastRelease;
950 }
951 return lastVersion;
952 }
953 /**
954 * A utility function for plugins to check the process for tokens.
955 */
956 checkEnv(pluginName, key) {
957 if (!process.env[key]) {
958 this.logger.log.warn(`${pluginName}: No "${key}" found in environment`);
959 }
960 }
961 /** Make a release to an old version */
962 async oldRelease(options) {
963 var _a;
964 const latestTag = await ((_a = this.git) === null || _a === void 0 ? void 0 : _a.getLatestTagInBranch());
965 const result = await this.publishFullRelease(Object.assign(Object.assign({}, options), { from: latestTag }));
966 if (result) {
967 result.context = "old";
968 }
969 return result;
970 }
971 /** Publish a new version with changelog, publish, and release */
972 async publishFullRelease(options) {
973 if (!this.git || !this.release) {
974 throw this.createErrorMessage();
975 }
976 const version = await this.getVersion(options);
977 this.logger.log.success(`Calculated version bump: ${version || "none"}`);
978 if (version === "") {
979 this.logger.log.info("No version published.");
980 return;
981 }
982 const lastRelease = options.from || (await this.git.getLatestRelease());
983 const commitsInRelease = await this.release.getCommitsInRelease(lastRelease);
984 await this.makeChangelog(Object.assign(Object.assign({}, options), { quiet: undefined, noCommit: options.noChangelog }));
985 if (!options.dryRun) {
986 await this.checkClean();
987 this.logger.verbose.info("Calling version hook");
988 await this.hooks.version.promise(version);
989 this.logger.verbose.info("Calling after version hook");
990 await this.hooks.afterVersion.promise();
991 this.logger.verbose.info("Calling publish hook");
992 await this.hooks.publish.promise(version);
993 this.logger.verbose.info("Calling after publish hook");
994 await this.hooks.afterPublish.promise();
995 }
996 const newVersion = await this.makeRelease(options);
997 if (options.dryRun) {
998 const current = await this.getCurrentVersion(lastRelease);
999 if (semver_1.parse(current)) {
1000 const next = semver_1.inc(current, version);
1001 if (options.quiet) {
1002 console.log(next);
1003 }
1004 else {
1005 this.logger.log.warn(`Published version would be: ${next}`);
1006 }
1007 }
1008 }
1009 else if (options.quiet) {
1010 console.log(newVersion);
1011 }
1012 return { newVersion, commitsInRelease, context: "latest" };
1013 }
1014 /** Get a pr number from user input or the env */
1015 getPrNumber(command, pr) {
1016 const prNumber = getPrNumberFromEnv(pr);
1017 if (!prNumber) {
1018 this.logger.log.error(endent_1.default `
1019 Could not detect PR number. ${command} must be run from either a PR or have the PR number supplied via the --pr flag.
1020
1021 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.
1022 `);
1023 process.exit(1);
1024 }
1025 return prNumber;
1026 }
1027 /** Create a client to interact with git */
1028 startGit(gitOptions) {
1029 if (!gitOptions.owner || !gitOptions.repo || !gitOptions.token) {
1030 throw new Error("Must set owner, repo, and GitHub token.");
1031 }
1032 this.logger.verbose.info("Options contain repo information.");
1033 // So that --verbose can be used on public CIs
1034 const tokenlessArgs = Object.assign(Object.assign({}, gitOptions), { token: `[Token starting with ${gitOptions.token.substring(0, 4)}]` });
1035 this.logger.verbose.info("Initializing GitHub API with:\n", tokenlessArgs);
1036 return new git_1.default({
1037 owner: gitOptions.owner,
1038 repo: gitOptions.repo,
1039 token: gitOptions.token,
1040 baseUrl: gitOptions.baseUrl,
1041 baseBranch: this.baseBranch,
1042 graphqlBaseUrl: gitOptions.graphqlBaseUrl,
1043 agent: gitOptions.agent,
1044 }, this.logger);
1045 }
1046 /** Calculate a version from a tag using labels */
1047 async getVersion({ from } = {}) {
1048 if (!this.git || !this.release) {
1049 throw this.createErrorMessage();
1050 }
1051 const isPrerelease = this.inPrereleaseBranch();
1052 const lastRelease = from ||
1053 (isPrerelease && (await this.git.getLatestTagInBranch())) ||
1054 (await this.git.getLatestRelease());
1055 const calculatedBump = await this.release.getSemverBump(lastRelease);
1056 const bump = (isPrerelease && semver_2.preVersionMap.get(calculatedBump)) || calculatedBump;
1057 this.versionBump = bump;
1058 return bump;
1059 }
1060 /** Make a changelog over a range of commits */
1061 async makeChangelog(args = {}) {
1062 const options = Object.assign(Object.assign({}, this.getCommandDefault("changelog")), args);
1063 const { dryRun, from, to, title, message = "Update CHANGELOG.md [skip ci]", noCommit, } = options;
1064 if (!this.release || !this.git) {
1065 throw this.createErrorMessage();
1066 }
1067 await this.setGitUser();
1068 if (title) {
1069 this.release.hooks.createChangelogTitle.tap("Changelog Flag", () => title);
1070 }
1071 const lastRelease = from || (await this.git.getLatestRelease());
1072 const bump = await this.release.getSemverBump(lastRelease, to);
1073 const releaseNotes = await this.release.generateReleaseNotes(lastRelease, to, this.versionBump);
1074 if (dryRun) {
1075 this.logger.log.info("Potential Changelog Addition:\n", releaseNotes);
1076 this.logger.verbose.info("`changelog` dry run complete.");
1077 return;
1078 }
1079 if (args.quiet) {
1080 console.log(releaseNotes);
1081 }
1082 else {
1083 this.logger.log.info("New Release Notes\n", releaseNotes);
1084 }
1085 const currentVersion = await this.getCurrentVersion(lastRelease);
1086 const context = {
1087 bump,
1088 commits: await this.release.getCommits(lastRelease, to || undefined),
1089 releaseNotes,
1090 lastRelease,
1091 currentVersion,
1092 };
1093 if (!noCommit) {
1094 await this.release.addToChangelog(releaseNotes, lastRelease, currentVersion);
1095 await this.hooks.beforeCommitChangelog.promise(context);
1096 await exec_promise_1.default("git", ["commit", "-m", `"${message}"`, "--no-verify"]);
1097 this.logger.verbose.info("Committed new changelog.");
1098 }
1099 await this.hooks.afterAddToChangelog.promise(context);
1100 }
1101 /** Make a release over a range of commits */
1102 async makeRelease(args = {}) {
1103 const options = Object.assign(Object.assign({}, this.getCommandDefault("release")), args);
1104 const { dryRun, from, useVersion, prerelease = false } = options;
1105 if (!this.release || !this.git) {
1106 throw this.createErrorMessage();
1107 }
1108 // This will usually resolve to something on head
1109 const [err, latestTag] = await await_to_js_1.default(this.git.getLatestTagInBranch());
1110 // If its a dry run we want to show what would happen. Otherwise no
1111 // tags indicates that something would definitely go wrong.
1112 if ((err === null || err === void 0 ? void 0 : err.message.includes("No names found")) && !args.dryRun) {
1113 this.logger.log.error(endent_1.default `
1114 Could not find any tags in the local repository. Exiting early.
1115
1116 The "release" command creates GitHub releases for tags that have already been created in your repo.
1117
1118 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.
1119 `, "\n");
1120 this.logger.verbose.error(err);
1121 return process.exit(1);
1122 }
1123 const isPrerelease = prerelease || this.inPrereleaseBranch();
1124 let lastRelease = from ||
1125 (isPrerelease && (await this.git.getPreviousTagInBranch())) ||
1126 (await this.git.getLatestRelease());
1127 // Find base commit or latest release to generate the changelog to HEAD (new tag)
1128 this.logger.veryVerbose.info(`Using ${lastRelease} as previous release.`);
1129 if (lastRelease.match(/^\d+\.\d+\.\d+/)) {
1130 lastRelease = this.prefixRelease(lastRelease);
1131 }
1132 this.logger.log.info('Current "Latest Release" on Github:', lastRelease);
1133 const commitsInRelease = await this.release.getCommitsInRelease(lastRelease);
1134 const releaseNotes = await this.release.generateReleaseNotes(lastRelease, undefined, this.versionBump);
1135 this.logger.log.info(`Using release notes:\n${releaseNotes}`);
1136 const rawVersion = useVersion ||
1137 (isPrerelease && latestTag) ||
1138 (await this.getCurrentVersion(lastRelease)) ||
1139 latestTag;
1140 if (!rawVersion) {
1141 this.logger.log.error("Could not calculate next version from last tag.");
1142 return;
1143 }
1144 const newVersion = semver_1.parse(rawVersion)
1145 ? this.prefixRelease(rawVersion)
1146 : rawVersion;
1147 if (!dryRun &&
1148 semver_1.parse(newVersion) &&
1149 semver_1.parse(lastRelease) &&
1150 semver_1.eq(newVersion, lastRelease)) {
1151 this.logger.log.warn(`Nothing released to Github. Version to be released is the same as the latest release on Github: ${newVersion}`);
1152 return;
1153 }
1154 const release = await this.hooks.makeRelease.promise({
1155 dryRun,
1156 from: lastRelease,
1157 isPrerelease,
1158 newVersion,
1159 fullReleaseNotes: releaseNotes,
1160 commits: commitsInRelease,
1161 });
1162 if (release) {
1163 await this.hooks.afterRelease.promise({
1164 lastRelease,
1165 newVersion,
1166 commits: commitsInRelease,
1167 releaseNotes,
1168 response: release,
1169 });
1170 }
1171 return newVersion;
1172 }
1173 /** Create an auto initialization error */
1174 createErrorMessage() {
1175 return new Error(`Auto is not initialized! Make sure the have run Auto.loadConfig`);
1176 }
1177 /** Get the current git user */
1178 async getGitUser() {
1179 try {
1180 return {
1181 /** The git user is already set in the current env */
1182 system: true,
1183 email: await exec_promise_1.default("git", ["config", "user.email"]),
1184 name: await exec_promise_1.default("git", ["config", "user.name"]),
1185 };
1186 }
1187 catch (error) {
1188 this.logger.verbose.warn("Could not find git user or email configured in git config");
1189 if (!this.release) {
1190 return;
1191 }
1192 let { email, name } = this.release.config;
1193 this.logger.verbose.warn(`Got author from options: email: ${email}, name ${name}`);
1194 const packageAuthor = await this.hooks.getAuthor.promise();
1195 email = !email && packageAuthor ? packageAuthor.email : email;
1196 name = !name && packageAuthor ? packageAuthor.name : name;
1197 this.logger.verbose.warn(`Using author: ${name} <${email}>`);
1198 return { email, name };
1199 }
1200 }
1201 /**
1202 * Set the git user to make releases and commit with.
1203 */
1204 async setGitUser() {
1205 const user = await this.getGitUser();
1206 if (user && !user.system) {
1207 if (!env.isCi) {
1208 this.logger.log.note(endent_1.default `
1209 Detected local environment, will not set git user. This happens automatically in a CI environment.
1210
1211 If a command fails manually run:
1212
1213 - git config user.email your@email.com
1214 - git config user.name "Your Name"
1215 `);
1216 return;
1217 }
1218 if (user.email) {
1219 await exec_promise_1.default("git", ["config", "user.email", `"${user.email}"`]);
1220 this.logger.verbose.warn(`Set git email to ${user.email}`);
1221 }
1222 if (user.name) {
1223 await exec_promise_1.default("git", ["config", "user.name", `"${user.name}"`]);
1224 this.logger.verbose.warn(`Set git name to ${user.name}`);
1225 }
1226 }
1227 }
1228 /** Get the repo to interact with */
1229 async getRepo(config) {
1230 if (config.owner && config.repo) {
1231 return config;
1232 }
1233 const author = await this.hooks.getRepository.promise();
1234 if (!author || !author.owner || !author.repo) {
1235 this.logger.log.error(endent_1.default `
1236 Cannot find project owner and repository name!
1237
1238 You must do one of the following:
1239
1240 - configure the repo for your package manager (ex: set "repository" in package.json)
1241 - configure your git remote 'origin' to point to your project on GitHub.
1242 `, "");
1243 process.exit(1);
1244 }
1245 return author;
1246 }
1247 /** Find the location of the extended configuration */
1248 getExtendedLocation(config) {
1249 let extendedLocation;
1250 try {
1251 if (config.extends) {
1252 extendedLocation = require.resolve(config.extends);
1253 }
1254 }
1255 catch (error) {
1256 this.logger.veryVerbose.error(error);
1257 }
1258 return extendedLocation;
1259 }
1260 /**
1261 * Apply all of the plugins in the config.
1262 */
1263 loadPlugins(config) {
1264 config.plugins = config.plugins || [is_binary_1.default() ? "git-tag" : "npm"];
1265 const extendedLocation = this.getExtendedLocation(config);
1266 const pluginsPaths = [
1267 require.resolve("./plugins/filter-non-pull-request"),
1268 ...config.plugins,
1269 ];
1270 pluginsPaths
1271 .map((plugin) =>
1272 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1273 typeof plugin === "string" ? [plugin, {}] : plugin)
1274 .map((plugin) => load_plugins_1.loadPlugin(plugin, this.logger, extendedLocation))
1275 .filter((plugin) => Boolean(plugin))
1276 .forEach((plugin) => {
1277 this.logger.verbose.info(`Using ${plugin.name} Plugin...`);
1278 plugin.apply(this);
1279 });
1280 }
1281 /** Get the branch and build number when in CI environment */
1282 async getPrEnvInfo() {
1283 var _a, _b, _c, _d, _e, _f;
1284 // SailEnv falls back to commit SHA
1285 let pr;
1286 let build;
1287 if ("pr" in env && "build" in env) {
1288 ({ pr } = env);
1289 ({ build } = env);
1290 }
1291 else if ("pr" in env && "commit" in env) {
1292 ({ pr } = env);
1293 build = env.commit;
1294 }
1295 // If we haven't detected the PR from the env vars try to match
1296 // the commit to a PR
1297 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)) {
1298 const commit = await this.git.getSha();
1299 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]);
1300 if (query) {
1301 const result = await this.git.graphql(query);
1302 if (result === null || result === void 0 ? void 0 : result[`hash_${commit}`]) {
1303 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;
1304 if (number) {
1305 pr = String(number);
1306 }
1307 }
1308 }
1309 }
1310 return { pr, build };
1311 }
1312 /** Get the default for a command from the config */
1313 getCommandDefault(name) {
1314 if (!this.config) {
1315 return {};
1316 }
1317 const commandConfig = this.config[name];
1318 return typeof commandConfig === "object" ? commandConfig : {};
1319 }
1320}
1321exports.default = Auto;
1322var init_2 = require("./init");
1323exports.InteractiveInit = init_2.default;
1324var get_current_branch_2 = require("./utils/get-current-branch");
1325exports.getCurrentBranch = get_current_branch_2.getCurrentBranch;
1326var validate_config_2 = require("./validate-config");
1327exports.validatePluginConfiguration = validate_config_2.validatePluginConfiguration;
1328var auto_1 = require("./auto");
1329exports.Auto = auto_1.default;
1330var semver_4 = require("./semver");
1331exports.SEMVER = semver_4.default;
1332var exec_promise_2 = require("./utils/exec-promise");
1333exports.execPromise = exec_promise_2.default;
1334var get_lerna_packages_1 = require("./utils/get-lerna-packages");
1335exports.getLernaPackages = get_lerna_packages_1.default;
1336var in_folder_1 = require("./utils/in-folder");
1337exports.inFolder = in_folder_1.default;
1338//# sourceMappingURL=auto.js.map
\No newline at end of file