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