UNPKG

35.2 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 semver_1 = require("semver");
9const endent_1 = tslib_1.__importDefault(require("endent"));
10const https_proxy_agent_1 = tslib_1.__importDefault(require("https-proxy-agent"));
11const config_1 = tslib_1.__importDefault(require("./config"));
12const git_1 = tslib_1.__importDefault(require("./git"));
13const init_1 = tslib_1.__importDefault(require("./init"));
14const release_1 = tslib_1.__importStar(require("./release"));
15const semver_2 = tslib_1.__importStar(require("./semver"));
16const exec_promise_1 = tslib_1.__importDefault(require("./utils/exec-promise"));
17const load_plugins_1 = tslib_1.__importDefault(require("./utils/load-plugins"));
18const logger_1 = tslib_1.__importDefault(require("./utils/logger"));
19const make_hooks_1 = require("./utils/make-hooks");
20const proxyUrl = process.env.https_proxy || process.env.http_proxy;
21const env = env_ci_1.default();
22/** Load the .env file into process.env. Useful for local usage. */
23const loadEnv = () => {
24 const envFile = path_1.default.resolve(process.cwd(), '.env');
25 if (!fs_1.default.existsSync(envFile)) {
26 return;
27 }
28 const envConfig = dotenv_1.default.parse(fs_1.default.readFileSync(envFile));
29 Object.entries(envConfig).forEach(([key, value]) => {
30 process.env[key] = value;
31 });
32};
33/** Get the pr number from user input or the CI env. */
34function getPrNumberFromEnv(pr) {
35 const envPr = 'pr' in env && Number(env.pr);
36 const prNumber = pr || envPr;
37 return prNumber;
38}
39/**
40 * Bump the version but no too much.
41 *
42 * @example
43 * currentVersion = 1.0.0
44 * nextVersion = 2.0.0-next.0
45 * output = 2.0.0-next.1
46 */
47function determineNextVersion(lastVersion, currentVersion, bump, tag) {
48 const next = semver_1.inc(lastVersion, `pre${bump}`, tag) || 'prerelease';
49 return semver_1.lte(next, currentVersion)
50 ? semver_1.inc(currentVersion, 'prerelease', tag) || 'prerelease'
51 : next;
52}
53exports.determineNextVersion = determineNextVersion;
54/**
55 * The "auto" node API. Its public interface matches the
56 * commands you can run from the CLI
57 */
58class Auto {
59 /** Initialize auto and it's environment */
60 constructor(options = {}) {
61 /** Check if `git status` is clean. */
62 this.checkClean = async () => {
63 const status = await exec_promise_1.default('git', ['status', '--porcelain']);
64 if (!status) {
65 return;
66 }
67 this.logger.log.error('Changed Files:\n', status);
68 throw new Error('Working direction is not clean, make sure all files are commited');
69 };
70 /** Prefix a version with a "v" if needed */
71 this.prefixRelease = (release) => {
72 var _a;
73 if (!this.release) {
74 throw this.createErrorMessage();
75 }
76 return ((_a = this.config) === null || _a === void 0 ? void 0 : _a.noVersionPrefix) || release.startsWith('v')
77 ? release
78 : `v${release}`;
79 };
80 this.options = options;
81 this.baseBranch = options.baseBranch || 'master';
82 this.logger = logger_1.default(Array.isArray(options.verbose) && options.verbose.length > 1
83 ? 'veryVerbose'
84 : options.verbose
85 ? 'verbose'
86 : undefined);
87 this.hooks = make_hooks_1.makeHooks();
88 this.hooks.onCreateRelease.tap('Link onCreateChangelog', release => {
89 release.hooks.onCreateChangelog.tap('Link onCreateChangelog', (changelog, version) => {
90 this.hooks.onCreateChangelog.call(changelog, version);
91 });
92 });
93 this.hooks.onCreateRelease.tap('Link onCreateLogParse', release => {
94 release.hooks.onCreateLogParse.tap('Link onCreateLogParse', logParse => {
95 this.hooks.onCreateLogParse.call(logParse);
96 });
97 });
98 loadEnv();
99 this.logger.verbose.info('ENV:', env);
100 }
101 /**
102 * Load the .autorc from the file system, set up defaults, combine with CLI args
103 * load the extends property, load the plugins and start the git remote interface.
104 */
105 async loadConfig() {
106 const configLoader = new config_1.default(this.logger);
107 const config = Object.assign(Object.assign({}, (await configLoader.loadConfig(this.options))), { baseBranch: this.baseBranch });
108 this.logger.verbose.success('Loaded `auto` with config:', config);
109 this.config = config;
110 this.labels = config.labels;
111 this.semVerLabels = release_1.getVersionMap(config.labels);
112 this.loadPlugins(config);
113 this.config = this.hooks.modifyConfig.call(config);
114 this.hooks.beforeRun.call(config);
115 const repository = await this.getRepo(config);
116 const token = (repository && repository.token) || process.env.GH_TOKEN;
117 if (!token || token === 'undefined') {
118 this.logger.log.error('No GitHub was found. Make sure it is available on process.env.GH_TOKEN.');
119 throw new Error('GitHub token not found!');
120 }
121 const githubOptions = Object.assign(Object.assign({ owner: config.owner, repo: config.repo }, repository), { token, agent: proxyUrl ? new https_proxy_agent_1.default(proxyUrl) : undefined, baseUrl: config.githubApi || 'https://api.github.com', graphqlBaseUrl: config.githubGraphqlApi || config.githubApi || 'https://api.github.com' });
122 this.git = this.startGit(githubOptions);
123 this.release = new release_1.default(this.git, config, this.logger);
124 this.hooks.onCreateRelease.call(this.release);
125 }
126 /**
127 * Interactive prompt for initializing an .autorc
128 */
129 async init(options = {}) {
130 await init_1.default(options, this.logger);
131 }
132 /**
133 * Create all of the user's labels on the git remote if the don't already exist
134 *
135 * @param options - Options for the createLabels functionality
136 */
137 async createLabels(options = {}) {
138 if (!this.release || !this.labels) {
139 throw this.createErrorMessage();
140 }
141 await this.release.addLabelsToProject(this.labels, options);
142 }
143 /**
144 * Get the labels on a specific PR. Defaults to the labels of the last merged PR
145 *
146 * @param options - Options for the createLabels functionality
147 */
148 async label({ pr } = {}) {
149 if (!this.git) {
150 throw this.createErrorMessage();
151 }
152 this.logger.verbose.info("Using command: 'label'");
153 const number = getPrNumberFromEnv(pr);
154 let labels = [];
155 if (number) {
156 labels = await this.git.getLabels(number);
157 }
158 else {
159 const pulls = await this.git.getPullRequests({
160 state: 'closed'
161 });
162 const lastMerged = pulls
163 .sort((a, b) => new Date(b.merged_at).getTime() - new Date(a.merged_at).getTime())
164 .find(pull => pull.merged_at);
165 if (lastMerged) {
166 labels = lastMerged.labels.map(label => label.name);
167 }
168 }
169 if (labels.length) {
170 console.log(labels.join('\n'));
171 }
172 }
173 /**
174 * Create a status on a PR.
175 *
176 * @param options - Options for the pr status functionality
177 */
178 async prStatus(_a) {
179 var { dryRun, pr, url } = _a, options = tslib_1.__rest(_a, ["dryRun", "pr", "url"]);
180 if (!this.git) {
181 throw this.createErrorMessage();
182 }
183 let { sha } = options;
184 let prNumber;
185 try {
186 prNumber = this.getPrNumber('pr', pr);
187 }
188 catch (error) {
189 // default to sha if no PR found
190 }
191 this.logger.verbose.info("Using command: 'pr-status'");
192 if (!sha && prNumber) {
193 this.logger.verbose.info('Getting commit SHA from PR.');
194 const res = await this.git.getPullRequest(prNumber);
195 sha = res.data.head.sha;
196 }
197 else if (!sha) {
198 this.logger.verbose.info('No PR found, getting commit SHA from HEAD.');
199 sha = await this.git.getSha();
200 }
201 this.logger.verbose.info('Found PR SHA:', sha);
202 const target_url = url;
203 if (dryRun) {
204 this.logger.verbose.info('`pr` dry run complete.');
205 }
206 else {
207 try {
208 await this.git.createStatus(Object.assign(Object.assign({}, options), { sha,
209 target_url }));
210 }
211 catch (error) {
212 throw new Error(`Failed to post status to Pull Request with error code ${error.status}`);
213 }
214 this.logger.log.success('Posted status to Pull Request.');
215 }
216 this.logger.verbose.success('Finished `pr` command');
217 }
218 /**
219 * Check that a PR has a SEMVER label. Set a success status on the PR.
220 *
221 * @param options - Options for the pr check functionality
222 */
223 async prCheck(_a) {
224 var _b;
225 var { dryRun, pr, url } = _a, options = tslib_1.__rest(_a, ["dryRun", "pr", "url"]);
226 if (!this.git || !this.release || !this.semVerLabels) {
227 throw this.createErrorMessage();
228 }
229 this.logger.verbose.info(`Using command: 'pr-check' for '${url}'`);
230 const target_url = url;
231 const prNumber = this.getPrNumber('prCheck', pr);
232 let msg;
233 let sha;
234 try {
235 const res = await this.git.getPullRequest(prNumber);
236 sha = res.data.head.sha;
237 const labels = await this.git.getLabels(prNumber);
238 const labelValues = [...this.semVerLabels.values()];
239 const releaseTag = labels.find(l => l === 'release');
240 const skipReleaseLabels = (((_b = this.config) === null || _b === void 0 ? void 0 : _b.labels.filter(l => l.releaseType === 'skip')) || []).map(l => l.name);
241 const skipReleaseTag = labels.find(l => skipReleaseLabels.includes(l));
242 const semverTag = labels.find(l => labelValues.some(labelValue => labelValue.includes(l)) &&
243 !skipReleaseLabels.includes(l) &&
244 l !== 'release');
245 if (semverTag === undefined && !skipReleaseTag) {
246 throw new Error('No semver label!');
247 }
248 this.logger.log.success(`PR is using label: ${semverTag || skipReleaseTag}`);
249 let description;
250 if (skipReleaseTag) {
251 description = 'PR will not create a release';
252 }
253 else if (releaseTag) {
254 description = `PR will create release once merged - ${semverTag}`;
255 }
256 else {
257 description = `CI - ${semverTag}`;
258 }
259 msg = {
260 description,
261 state: 'success'
262 };
263 }
264 catch (error) {
265 msg = {
266 description: error.message,
267 state: 'error'
268 };
269 }
270 this.logger.verbose.info('Posting status to GitHub\n', msg);
271 if (dryRun) {
272 this.logger.verbose.info('`pr-check` dry run complete.');
273 }
274 else {
275 try {
276 await this.git.createStatus(Object.assign(Object.assign(Object.assign({}, options), msg), { target_url,
277 sha }));
278 this.logger.log.success('Posted status to Pull Request.');
279 }
280 catch (error) {
281 throw new Error(`Failed to post status to Pull Request with error code ${error.status}`);
282 }
283 }
284 this.logger.verbose.success('Finished `pr-check` command');
285 }
286 /**
287 * Comment on a PR. Only one comment will be present on the PR, Older comments are removed.
288 * You can use the "context" option to multiple comments on a PR.
289 *
290 * @param options - Options for the comment functionality
291 */
292 async comment(options) {
293 const { message, pr, context = 'default', dryRun, delete: deleteFlag, edit: editFlag } = options;
294 if (!this.git) {
295 throw this.createErrorMessage();
296 }
297 this.logger.verbose.info("Using command: 'comment'");
298 const prNumber = this.getPrNumber('comment', pr);
299 if (dryRun) {
300 if (deleteFlag) {
301 this.logger.log.info(`Would have deleted comment on ${prNumber} under "${context}" context`);
302 }
303 else if (editFlag) {
304 this.logger.log.info(`Would have edited the comment on ${prNumber} under "${context}" context.\n\nNew message: ${message}`);
305 }
306 else {
307 this.logger.log.info(`Would have commented on ${prNumber} under "${context}" context:\n\n${message}`);
308 }
309 }
310 else if (editFlag && message) {
311 await this.git.editComment(message, prNumber, context);
312 this.logger.log.success(`Edited comment on PR #${prNumber} under context "${context}"`);
313 }
314 else {
315 if (deleteFlag) {
316 await this.git.deleteComment(prNumber, context);
317 this.logger.log.success(`Deleted comment on PR #${prNumber} under context "${context}"`);
318 }
319 if (message) {
320 await this.git.createComment(message, prNumber, context);
321 this.logger.log.success(`Commented on PR #${prNumber}`);
322 }
323 }
324 }
325 /**
326 * Update the body of a PR with a message. Only one message will be present in the PR,
327 * Older messages are removed. You can use the "context" option to multiple message
328 * in a PR body.
329 *
330 * @param options - Options
331 */
332 async prBody(options) {
333 const { message, pr, context = 'default', dryRun, delete: deleteFlag } = options;
334 if (!this.git) {
335 throw this.createErrorMessage();
336 }
337 this.logger.verbose.info("Using command: 'pr-body'");
338 const prNumber = this.getPrNumber('pr-body', pr);
339 if (dryRun) {
340 if (deleteFlag) {
341 this.logger.log.info(`Would have deleted PR body on ${prNumber} under "${context}" context`);
342 }
343 else {
344 this.logger.log.info(`Would have appended to PR body on ${prNumber} under "${context}" context:\n\n${message}`);
345 }
346 }
347 else {
348 if (deleteFlag) {
349 await this.git.addToPrBody('', prNumber, context);
350 }
351 if (message) {
352 await this.git.addToPrBody(message, prNumber, context);
353 }
354 this.logger.log.success(`Updated body on PR #${prNumber}`);
355 }
356 }
357 /**
358 * Calculate the version bump for the current state of the repository.
359 */
360 async version(options = {}) {
361 this.logger.verbose.info("Using command: 'version'");
362 const bump = await this.getVersion(options);
363 console.log(bump);
364 }
365 /**
366 * Calculate the the changelog and commit it.
367 */
368 async changelog(options) {
369 this.logger.verbose.info("Using command: 'changelog'");
370 await this.makeChangelog(options);
371 }
372 /**
373 * Make a release to the git remote with the changes.
374 */
375 async runRelease(options = {}) {
376 this.logger.verbose.info("Using command: 'release'");
377 await this.makeRelease(options);
378 }
379 /** Create a canary (or test) version of the project */
380 async canary(options = {}) {
381 if (!this.git || !this.release) {
382 throw this.createErrorMessage();
383 }
384 if (!this.hooks.canary.isUsed()) {
385 this.logger.log.error(endent_1.default `
386 None of the plugins that you are using implement the \`canary\` command!
387
388 "canary" releases are versions that are used solely to test changes. They make sense on some platforms (ex: npm) but not all!
389
390 If you think your package manager has the ability to support canaries please file an issue or submit a pull request,
391 `);
392 process.exit(1);
393 }
394 await this.checkClean();
395 // SailEnv falls back to commit SHA
396 let pr;
397 let build;
398 if ('pr' in env && 'build' in env) {
399 ({ pr } = env);
400 ({ build } = env);
401 }
402 else if ('pr' in env && 'commit' in env) {
403 ({ pr } = env);
404 build = env.commit;
405 }
406 pr = options.pr ? String(options.pr) : pr;
407 build = options.build ? String(options.build) : build;
408 this.logger.verbose.info('Canary info found:', { pr, build });
409 const head = await this.release.getCommitsInRelease('HEAD^');
410 const labels = head.map(commit => commit.labels);
411 const version = semver_2.calculateSemVerBump(labels, this.semVerLabels, this.config) ||
412 semver_2.default.patch;
413 let canaryVersion = '';
414 if (pr) {
415 canaryVersion = `${canaryVersion}.${pr}`;
416 }
417 if (build) {
418 canaryVersion = `${canaryVersion}.${build}`;
419 }
420 if (!pr || !build) {
421 canaryVersion = `${canaryVersion}.${await this.git.getSha(true)}`;
422 }
423 let newVersion = '';
424 if (options.dryRun) {
425 this.logger.log.warn(`Published canary identifier would be: "-canary${canaryVersion}"`);
426 }
427 else {
428 this.logger.verbose.info('Calling canary hook');
429 const result = await this.hooks.canary.promise(version, canaryVersion);
430 if (typeof result === 'object') {
431 this.logger.log.warn(result.error);
432 return;
433 }
434 newVersion = result;
435 const message = options.message || 'Published PR with canary version: %v';
436 if (message !== 'false' && pr) {
437 await this.prBody({
438 pr: Number(pr),
439 context: 'canary-version',
440 message: message.replace('%v', !newVersion || newVersion.includes('\n')
441 ? newVersion
442 : `\`${newVersion}\``)
443 });
444 }
445 this.logger.log.success(`Published canary version${newVersion ? `: ${newVersion}` : ''}`);
446 }
447 let latestTag;
448 try {
449 latestTag = await this.git.getLatestTagInBranch();
450 }
451 catch (error) {
452 latestTag = await this.git.getFirstCommit();
453 }
454 const commitsInRelease = await this.release.getCommits(latestTag);
455 return { newVersion, commitsInRelease };
456 }
457 /**
458 * Create a next (or test) version of the project. If on master will
459 * release to the default "next" branch.
460 */
461 async next(options) {
462 if (!this.git || !this.release) {
463 throw this.createErrorMessage();
464 }
465 if (!this.hooks.next.isUsed()) {
466 this.logger.log.error(endent_1.default `
467 None of the plugins that you are using implement the \`next\` command!
468
469 "next" releases are pre-releases such as betas or alphas. They make sense on some platforms (ex: npm) but not all!
470
471 If you think your package manager has the ability to support "next" releases please file an issue or submit a pull request,
472 `);
473 process.exit(1);
474 }
475 await this.checkClean();
476 await this.setGitUser();
477 const lastRelease = await this.git.getLatestRelease();
478 const lastTag = await this.git.getLatestTagInBranch();
479 const commits = await this.release.getCommitsInRelease(lastTag);
480 const releaseNotes = await this.release.generateReleaseNotes(lastTag);
481 const labels = commits.map(commit => commit.labels);
482 const bump = semver_2.calculateSemVerBump(labels, this.semVerLabels, this.config) ||
483 semver_2.default.patch;
484 if (options.dryRun) {
485 this.logger.log.success(`Would have created prerelease version with: ${bump}`);
486 return { newVersion: '', commitsInRelease: commits };
487 }
488 this.logger.verbose.info(`Calling "next" hook with: ${bump}`);
489 const result = await this.hooks.next.promise([], bump);
490 const newVersion = result.join(', ');
491 await Promise.all(result.map(async (prerelease) => {
492 var _a;
493 const release = await ((_a = this.git) === null || _a === void 0 ? void 0 : _a.publish(releaseNotes, prerelease, true));
494 this.logger.verbose.info(release);
495 await this.hooks.afterRelease.promise({
496 lastRelease: lastTag,
497 newVersion: prerelease,
498 commits,
499 releaseNotes,
500 response: release
501 });
502 }));
503 this.logger.log.success(`Published next version${result.length > 1 ? `s` : ''}: ${newVersion}`);
504 if ('isPr' in env && env.isPr) {
505 const message = options.message || 'Published prerelease version: %v';
506 const pr = 'pr' in env && env.pr;
507 if (pr) {
508 await this.prBody({
509 pr: Number(pr),
510 context: 'prerelease-version',
511 message: endent_1.default `
512 # Version
513
514 ${message.replace('%v', result.map(r => `\`${r}\``).join('\n'))}
515
516 <details>
517 <summary>Changelog</summary>
518 ${await this.release.generateReleaseNotes(lastRelease)}
519 </details>
520 `
521 });
522 }
523 }
524 return { newVersion, commitsInRelease: commits };
525 }
526 /**
527 * Run the full workflow.
528 *
529 * 1. Calculate version
530 * 2. Make changelog
531 * 3. Publish code
532 * 4. Create a release
533 */
534 async shipit(options = {}) {
535 var _a, _b;
536 if (!this.git || !this.release) {
537 throw this.createErrorMessage();
538 }
539 this.logger.verbose.info("Using command: 'shipit'");
540 this.hooks.beforeShipIt.call();
541 const isPR = 'isPr' in env && env.isPr;
542 const head = await this.release.getCommitsInRelease('HEAD^');
543 // env-ci sets branch to target branch (ex: master) in some CI services.
544 // so we should make sure we aren't in a PR just to be safe
545 const currentBranch = isPR
546 ? 'prBranch' in env && env.prBranch
547 : 'branch' in env && env.branch;
548 const isBaseBrach = !isPR && currentBranch === this.baseBranch;
549 const shouldGraduate = !options.onlyGraduateWithReleaseLabel ||
550 (options.onlyGraduateWithReleaseLabel &&
551 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); }));
552 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);
553 const publishPrerelease = isPrereleaseBranch ||
554 (currentBranch === this.baseBranch &&
555 options.onlyGraduateWithReleaseLabel);
556 this.logger.veryVerbose.info({
557 currentBranch,
558 isBaseBrach,
559 isPR,
560 shouldGraduate,
561 isPrereleaseBranch,
562 publishPrerelease
563 });
564 const publishInfo = isBaseBrach && shouldGraduate
565 ? await this.publishLatest(options)
566 : publishPrerelease
567 ? await this.next(options)
568 : await this.canary(options);
569 if (!publishInfo) {
570 return;
571 }
572 const { newVersion, commitsInRelease } = publishInfo;
573 await this.hooks.afterShipIt.promise(newVersion, commitsInRelease);
574 }
575 /** Get the latest version number of the project */
576 async getCurrentVersion(lastRelease) {
577 this.hooks.getPreviousVersion.tap('None', () => {
578 this.logger.veryVerbose.info('No previous release found, using 0.0.0 as previous version.');
579 return this.prefixRelease('0.0.0');
580 });
581 const lastVersion = await this.hooks.getPreviousVersion.promise();
582 if (semver_1.parse(lastRelease) &&
583 semver_1.parse(lastVersion) &&
584 semver_1.gt(lastRelease, lastVersion)) {
585 this.logger.veryVerbose.info('Using latest release as previous version');
586 return lastRelease;
587 }
588 return lastVersion;
589 }
590 /**
591 * A utility function for plugins to check the process for tokens.
592 */
593 checkEnv(pluginName, key) {
594 if (!process.env[key]) {
595 this.logger.log.warn(`${pluginName}: No "${key}" found in environment`);
596 }
597 }
598 /** On master: publish a new latest version */
599 async publishLatest(options) {
600 if (!this.git || !this.release) {
601 throw this.createErrorMessage();
602 }
603 const version = await this.getVersion();
604 if (version === '') {
605 this.logger.log.info('No version published.');
606 return;
607 }
608 const lastRelease = await this.git.getLatestRelease();
609 const commitsInRelease = await this.release.getCommitsInRelease(lastRelease);
610 await this.makeChangelog(options);
611 if (!options.dryRun) {
612 await this.checkClean();
613 this.logger.verbose.info('Calling version hook');
614 await this.hooks.version.promise(version);
615 this.logger.verbose.info('Calling after version hook');
616 await this.hooks.afterVersion.promise();
617 this.logger.verbose.info('Calling publish hook');
618 await this.hooks.publish.promise(version);
619 this.logger.verbose.info('Calling after publish hook');
620 await this.hooks.afterPublish.promise();
621 }
622 const newVersion = await this.makeRelease(options);
623 if (options.dryRun) {
624 this.logger.log.warn("The version reported in the line above hasn't been incremented during `dry-run`");
625 const current = await this.getCurrentVersion(lastRelease);
626 if (semver_1.parse(current)) {
627 this.logger.log.warn(`Published version would be: ${semver_1.inc(current, version)}`);
628 }
629 }
630 return { newVersion, commitsInRelease };
631 }
632 /** Get a pr number from user input or the env */
633 getPrNumber(command, pr) {
634 const prNumber = getPrNumberFromEnv(pr);
635 if (!prNumber) {
636 throw new Error(`Could not detect PR number. ${command} must be run from either a PR or have the PR number supplied via the --pr flag.`);
637 }
638 return prNumber;
639 }
640 /** Create a client to interact with git */
641 startGit(gitOptions) {
642 if (!gitOptions.owner || !gitOptions.repo || !gitOptions.token) {
643 throw new Error('Must set owner, repo, and GitHub token.');
644 }
645 this.logger.verbose.info('Options contain repo information.');
646 // So that --verbose can be used on public CIs
647 const tokenlessArgs = Object.assign(Object.assign({}, gitOptions), { token: `[Token starting with ${gitOptions.token.substring(0, 4)}]` });
648 this.logger.verbose.info('Initializing GitHub API with:\n', tokenlessArgs);
649 return new git_1.default({
650 owner: gitOptions.owner,
651 repo: gitOptions.repo,
652 token: gitOptions.token,
653 baseUrl: gitOptions.baseUrl,
654 graphqlBaseUrl: gitOptions.graphqlBaseUrl,
655 agent: gitOptions.agent
656 }, this.logger);
657 }
658 /** Calculate a version from a tag using labels */
659 async getVersion({ from } = {}) {
660 if (!this.git || !this.release) {
661 throw this.createErrorMessage();
662 }
663 const lastRelease = from || (await this.git.getLatestRelease());
664 const bump = await this.release.getSemverBump(lastRelease);
665 this.versionBump = bump;
666 return bump;
667 }
668 /** Make a changelog over a range of commits */
669 async makeChangelog({ dryRun, from, to, message = 'Update CHANGELOG.md [skip ci]' } = {}) {
670 if (!this.release || !this.git) {
671 throw this.createErrorMessage();
672 }
673 await this.setGitUser();
674 const lastRelease = from || (await this.git.getLatestRelease());
675 const bump = await this.release.getSemverBump(lastRelease, to);
676 const releaseNotes = await this.release.generateReleaseNotes(lastRelease, to || undefined, this.versionBump);
677 this.logger.log.info('New Release Notes\n', releaseNotes);
678 if (dryRun) {
679 this.logger.verbose.info('`changelog` dry run complete.');
680 return;
681 }
682 const currentVersion = await this.getCurrentVersion(lastRelease);
683 await this.release.addToChangelog(releaseNotes, lastRelease, currentVersion);
684 const options = {
685 bump,
686 commits: await this.release.getCommits(lastRelease, to || undefined),
687 releaseNotes,
688 lastRelease,
689 currentVersion
690 };
691 await this.hooks.beforeCommitChangelog.promise(options);
692 await exec_promise_1.default('git', ['commit', '-m', `"${message}"`, '--no-verify']);
693 this.logger.verbose.info('Committed new changelog.');
694 await this.hooks.afterAddToChangelog.promise(options);
695 }
696 /** Make a release over a range of commits */
697 async makeRelease({ dryRun, from, useVersion, prerelease = false } = {}) {
698 if (!this.release || !this.git) {
699 throw this.createErrorMessage();
700 }
701 let lastRelease = from || (await this.git.getLatestRelease());
702 // Find base commit or latest release to generate the changelog to HEAD (new tag)
703 this.logger.veryVerbose.info(`Using ${lastRelease} as previous release.`);
704 if (lastRelease.match(/^\d+\.\d+\.\d+/)) {
705 lastRelease = this.prefixRelease(lastRelease);
706 }
707 this.logger.log.info('Last used release:', lastRelease);
708 const commitsInRelease = await this.release.getCommitsInRelease(lastRelease);
709 const releaseNotes = await this.release.generateReleaseNotes(lastRelease, undefined, this.versionBump);
710 this.logger.log.info(`Using release notes:\n${releaseNotes}`);
711 const rawVersion = useVersion ||
712 (await this.getCurrentVersion(lastRelease)) ||
713 (await this.git.getLatestTagInBranch());
714 if (!rawVersion) {
715 this.logger.log.error('Could not calculate next version from last tag.');
716 return;
717 }
718 const newVersion = semver_1.parse(rawVersion)
719 ? this.prefixRelease(rawVersion)
720 : rawVersion;
721 if (!dryRun &&
722 semver_1.parse(newVersion) &&
723 semver_1.parse(lastRelease) &&
724 semver_1.eq(newVersion, lastRelease)) {
725 this.logger.log.warn(`Nothing released to Github. Version to be released is the same as the latest release on Github: ${newVersion}`);
726 return;
727 }
728 let release;
729 if (dryRun) {
730 this.logger.log.info(`Would have released (unless ran with "shipit"): ${newVersion}`);
731 }
732 else {
733 this.logger.log.info(`Releasing ${newVersion} to GitHub.`);
734 release = await this.git.publish(releaseNotes, newVersion, prerelease);
735 await this.hooks.afterRelease.promise({
736 lastRelease,
737 newVersion,
738 commits: commitsInRelease,
739 releaseNotes,
740 response: release
741 });
742 }
743 return newVersion;
744 }
745 /** Create an auto initialization error */
746 createErrorMessage() {
747 return new Error(`Auto is not initialized! Make sure the have run Auto.loadConfig`);
748 }
749 /**
750 * Set the git user to make releases and commit with.
751 */
752 async setGitUser() {
753 try {
754 // If these values are not set git config will exit with an error
755 await exec_promise_1.default('git', ['config', 'user.email']);
756 await exec_promise_1.default('git', ['config', 'user.name']);
757 }
758 catch (error) {
759 this.logger.verbose.warn('Could not find git user or email configured in environment');
760 if (!env.isCi) {
761 this.logger.log.note(`Detected local environment, will not set git user. This happens automatically in a CI environment.
762
763If a command fails manually run:
764
765 - git config user.email your@email.com
766 - git config user.name "Your Name"`);
767 return;
768 }
769 if (!this.release) {
770 return;
771 }
772 let { email, name } = this.release.config;
773 this.logger.verbose.warn(`Got author from options: email: ${email}, name ${name}`);
774 const packageAuthor = await this.hooks.getAuthor.promise();
775 this.logger.verbose.warn(`Got author: ${JSON.stringify(packageAuthor, undefined, 2)}`);
776 email = !email && packageAuthor ? packageAuthor.email : email;
777 name = !name && packageAuthor ? packageAuthor.name : name;
778 if (email) {
779 await exec_promise_1.default('git', ['config', 'user.email', `"${email}"`]);
780 this.logger.verbose.warn(`Set git email to ${email}`);
781 }
782 if (name) {
783 await exec_promise_1.default('git', ['config', 'user.name', `"${name}"`]);
784 this.logger.verbose.warn(`Set git name to ${name}`);
785 }
786 }
787 }
788 /** Get the repo to interact with */
789 async getRepo(config) {
790 if (config.owner && config.repo) {
791 return config;
792 }
793 return this.hooks.getRepository.promise();
794 }
795 /**
796 * Apply all of the plugins in the config.
797 */
798 loadPlugins(config) {
799 // eslint-disable-next-line @typescript-eslint/no-explicit-any
800 const defaultPlugins = [process.pkg ? 'git-tag' : 'npm'];
801 const pluginsPaths = [
802 require.resolve('./plugins/filter-non-pull-request'),
803 ...(config.plugins || defaultPlugins)
804 ];
805 pluginsPaths
806 .map(plugin =>
807 // eslint-disable-next-line @typescript-eslint/no-explicit-any
808 typeof plugin === 'string' ? [plugin, {}] : plugin)
809 .map(plugin => load_plugins_1.default(plugin, this.logger))
810 .filter((plugin) => Boolean(plugin))
811 .forEach(plugin => {
812 this.logger.verbose.info(`Using ${plugin.name} Plugin...`);
813 plugin.apply(this);
814 });
815 }
816}
817exports.default = Auto;
818var auto_1 = require("./auto");
819exports.Auto = auto_1.default;
820var semver_3 = require("./semver");
821exports.SEMVER = semver_3.default;
822var exec_promise_2 = require("./utils/exec-promise");
823exports.execPromise = exec_promise_2.default;
824//# sourceMappingURL=auto.js.map
\No newline at end of file