Verify the version of the generated build artifacts to ensure that the version published to NPM is the version we expect. PR Close #39789
		
			
				
	
	
		
			6999 lines
		
	
	
		
			314 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			6999 lines
		
	
	
		
			314 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env node
 | ||
| 'use strict';
 | ||
| 
 | ||
| function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
 | ||
| 
 | ||
| var yargs = require('yargs');
 | ||
| var tslib = require('tslib');
 | ||
| var chalk = _interopDefault(require('chalk'));
 | ||
| var fs = require('fs');
 | ||
| var inquirer = require('inquirer');
 | ||
| var inquirerAutocomplete = require('inquirer-autocomplete-prompt');
 | ||
| var path = require('path');
 | ||
| var shelljs = require('shelljs');
 | ||
| var url = require('url');
 | ||
| var child_process = require('child_process');
 | ||
| var graphql = require('@octokit/graphql');
 | ||
| var Octokit = require('@octokit/rest');
 | ||
| var typedGraphqlify = require('typed-graphqlify');
 | ||
| var fetch = _interopDefault(require('node-fetch'));
 | ||
| var semver = require('semver');
 | ||
| var multimatch = require('multimatch');
 | ||
| var yaml = require('yaml');
 | ||
| var cliProgress = require('cli-progress');
 | ||
| var os = require('os');
 | ||
| var minimatch = require('minimatch');
 | ||
| var ora = require('ora');
 | ||
| var glob = require('glob');
 | ||
| var ts = require('typescript');
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Runs an given command as child process. By default, child process
 | ||
|  * output will not be printed.
 | ||
|  */
 | ||
| function exec(cmd, opts) {
 | ||
|     return shelljs.exec(cmd, tslib.__assign(tslib.__assign({ silent: true }, opts), { async: false }));
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Whether ts-node has been installed and is available to ng-dev. */
 | ||
| function isTsNodeAvailable() {
 | ||
|     try {
 | ||
|         require.resolve('ts-node');
 | ||
|         return true;
 | ||
|     }
 | ||
|     catch (_a) {
 | ||
|         return false;
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * The filename expected for creating the ng-dev config, without the file
 | ||
|  * extension to allow either a typescript or javascript file to be used.
 | ||
|  */
 | ||
| var CONFIG_FILE_PATH = '.ng-dev/config';
 | ||
| /** The configuration for ng-dev. */
 | ||
| var cachedConfig = null;
 | ||
| /**
 | ||
|  * The filename expected for local user config, without the file extension to allow a typescript,
 | ||
|  * javascript or json file to be used.
 | ||
|  */
 | ||
| var USER_CONFIG_FILE_PATH = '.ng-dev.user';
 | ||
| /** The local user configuration for ng-dev. */
 | ||
| var userConfig = null;
 | ||
| /**
 | ||
|  * Get the configuration from the file system, returning the already loaded
 | ||
|  * copy if it is defined.
 | ||
|  */
 | ||
| function getConfig() {
 | ||
|     // If the global config is not defined, load it from the file system.
 | ||
|     if (cachedConfig === null) {
 | ||
|         // The full path to the configuration file.
 | ||
|         var configPath = path.join(getRepoBaseDir(), CONFIG_FILE_PATH);
 | ||
|         // Read the configuration and validate it before caching it for the future.
 | ||
|         cachedConfig = validateCommonConfig(readConfigFile(configPath));
 | ||
|     }
 | ||
|     // Return a clone of the cached global config to ensure that a new instance of the config
 | ||
|     // is returned each time, preventing unexpected effects of modifications to the config object.
 | ||
|     return tslib.__assign({}, cachedConfig);
 | ||
| }
 | ||
| /** Validate the common configuration has been met for the ng-dev command. */
 | ||
| function validateCommonConfig(config) {
 | ||
|     var errors = [];
 | ||
|     // Validate the github configuration.
 | ||
|     if (config.github === undefined) {
 | ||
|         errors.push("Github repository not configured. Set the \"github\" option.");
 | ||
|     }
 | ||
|     else {
 | ||
|         if (config.github.name === undefined) {
 | ||
|             errors.push("\"github.name\" is not defined");
 | ||
|         }
 | ||
|         if (config.github.owner === undefined) {
 | ||
|             errors.push("\"github.owner\" is not defined");
 | ||
|         }
 | ||
|     }
 | ||
|     assertNoErrors(errors);
 | ||
|     return config;
 | ||
| }
 | ||
| /**
 | ||
|  * Resolves and reads the specified configuration file, optionally returning an empty object if the
 | ||
|  * configuration file cannot be read.
 | ||
|  */
 | ||
| function readConfigFile(configPath, returnEmptyObjectOnError) {
 | ||
|     if (returnEmptyObjectOnError === void 0) { returnEmptyObjectOnError = false; }
 | ||
|     // If the the `.ts` extension has not been set up already, and a TypeScript based
 | ||
|     // version of the given configuration seems to exist, set up `ts-node` if available.
 | ||
|     if (require.extensions['.ts'] === undefined && fs.existsSync(configPath + ".ts") &&
 | ||
|         isTsNodeAvailable()) {
 | ||
|         // Ensure the module target is set to `commonjs`. This is necessary because the
 | ||
|         // dev-infra tool runs in NodeJS which does not support ES modules by default.
 | ||
|         // Additionally, set the `dir` option to the directory that contains the configuration
 | ||
|         // file. This allows for custom compiler options (such as `--strict`).
 | ||
|         require('ts-node').register({ dir: path.dirname(configPath), transpileOnly: true, compilerOptions: { module: 'commonjs' } });
 | ||
|     }
 | ||
|     try {
 | ||
|         return require(configPath);
 | ||
|     }
 | ||
|     catch (e) {
 | ||
|         if (returnEmptyObjectOnError) {
 | ||
|             debug("Could not read configuration file at " + configPath + ", returning empty object instead.");
 | ||
|             debug(e);
 | ||
|             return {};
 | ||
|         }
 | ||
|         error("Could not read configuration file at " + configPath + ".");
 | ||
|         error(e);
 | ||
|         process.exit(1);
 | ||
|     }
 | ||
| }
 | ||
| /**
 | ||
|  * Asserts the provided array of error messages is empty. If any errors are in the array,
 | ||
|  * logs the errors and exit the process as a failure.
 | ||
|  */
 | ||
| function assertNoErrors(errors) {
 | ||
|     var e_1, _a;
 | ||
|     if (errors.length == 0) {
 | ||
|         return;
 | ||
|     }
 | ||
|     error("Errors discovered while loading configuration file:");
 | ||
|     try {
 | ||
|         for (var errors_1 = tslib.__values(errors), errors_1_1 = errors_1.next(); !errors_1_1.done; errors_1_1 = errors_1.next()) {
 | ||
|             var err = errors_1_1.value;
 | ||
|             error("  - " + err);
 | ||
|         }
 | ||
|     }
 | ||
|     catch (e_1_1) { e_1 = { error: e_1_1 }; }
 | ||
|     finally {
 | ||
|         try {
 | ||
|             if (errors_1_1 && !errors_1_1.done && (_a = errors_1.return)) _a.call(errors_1);
 | ||
|         }
 | ||
|         finally { if (e_1) throw e_1.error; }
 | ||
|     }
 | ||
|     process.exit(1);
 | ||
| }
 | ||
| /** Gets the path of the directory for the repository base. */
 | ||
| function getRepoBaseDir() {
 | ||
|     var baseRepoDir = exec("git rev-parse --show-toplevel");
 | ||
|     if (baseRepoDir.code) {
 | ||
|         throw Error("Unable to find the path to the base directory of the repository.\n" +
 | ||
|             "Was the command run from inside of the repo?\n\n" +
 | ||
|             ("ERROR:\n " + baseRepoDir.stderr));
 | ||
|     }
 | ||
|     return baseRepoDir.trim();
 | ||
| }
 | ||
| /**
 | ||
|  * Get the local user configuration from the file system, returning the already loaded copy if it is
 | ||
|  * defined.
 | ||
|  *
 | ||
|  * @returns The user configuration object, or an empty object if no user configuration file is
 | ||
|  * present. The object is an untyped object as there are no required user configurations.
 | ||
|  */
 | ||
| function getUserConfig() {
 | ||
|     // If the global config is not defined, load it from the file system.
 | ||
|     if (userConfig === null) {
 | ||
|         // The full path to the configuration file.
 | ||
|         var configPath = path.join(getRepoBaseDir(), USER_CONFIG_FILE_PATH);
 | ||
|         // Set the global config object.
 | ||
|         userConfig = readConfigFile(configPath, true);
 | ||
|     }
 | ||
|     // Return a clone of the user config to ensure that a new instance of the config is returned
 | ||
|     // each time, preventing unexpected effects of modifications to the config object.
 | ||
|     return tslib.__assign({}, userConfig);
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Reexport of chalk colors for convenient access. */
 | ||
| var red = chalk.red;
 | ||
| var green = chalk.green;
 | ||
| var yellow = chalk.yellow;
 | ||
| var bold = chalk.bold;
 | ||
| var blue = chalk.blue;
 | ||
| /** Prompts the user with a confirmation question and a specified message. */
 | ||
| function promptConfirm(message, defaultValue) {
 | ||
|     if (defaultValue === void 0) { defaultValue = false; }
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|         return tslib.__generator(this, function (_a) {
 | ||
|             switch (_a.label) {
 | ||
|                 case 0: return [4 /*yield*/, inquirer.prompt({
 | ||
|                         type: 'confirm',
 | ||
|                         name: 'result',
 | ||
|                         message: message,
 | ||
|                         default: defaultValue,
 | ||
|                     })];
 | ||
|                 case 1: return [2 /*return*/, (_a.sent())
 | ||
|                         .result];
 | ||
|             }
 | ||
|         });
 | ||
|     });
 | ||
| }
 | ||
| function promptAutocomplete(message, choices, noChoiceText) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|         var prompt, result;
 | ||
|         return tslib.__generator(this, function (_a) {
 | ||
|             switch (_a.label) {
 | ||
|                 case 0:
 | ||
|                     prompt = inquirer.createPromptModule({}).registerPrompt('autocomplete', inquirerAutocomplete);
 | ||
|                     if (noChoiceText) {
 | ||
|                         choices = tslib.__spread([noChoiceText], choices);
 | ||
|                     }
 | ||
|                     return [4 /*yield*/, prompt({
 | ||
|                             type: 'autocomplete',
 | ||
|                             name: 'result',
 | ||
|                             message: message,
 | ||
|                             source: function (_, input) {
 | ||
|                                 if (!input) {
 | ||
|                                     return Promise.resolve(choices);
 | ||
|                                 }
 | ||
|                                 return Promise.resolve(choices.filter(function (choice) {
 | ||
|                                     if (typeof choice === 'string') {
 | ||
|                                         return choice.includes(input);
 | ||
|                                     }
 | ||
|                                     return choice.name.includes(input);
 | ||
|                                 }));
 | ||
|                             }
 | ||
|                         })];
 | ||
|                 case 1:
 | ||
|                     result = (_a.sent()).result;
 | ||
|                     if (result === noChoiceText) {
 | ||
|                         return [2 /*return*/, false];
 | ||
|                     }
 | ||
|                     return [2 /*return*/, result];
 | ||
|             }
 | ||
|         });
 | ||
|     });
 | ||
| }
 | ||
| /** Prompts the user for one line of input. */
 | ||
| function promptInput(message) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|         return tslib.__generator(this, function (_a) {
 | ||
|             switch (_a.label) {
 | ||
|                 case 0: return [4 /*yield*/, inquirer.prompt({ type: 'input', name: 'result', message: message })];
 | ||
|                 case 1: return [2 /*return*/, (_a.sent()).result];
 | ||
|             }
 | ||
|         });
 | ||
|     });
 | ||
| }
 | ||
| /**
 | ||
|  * Supported levels for logging functions.
 | ||
|  *
 | ||
|  * Levels are mapped to numbers to represent a hierarchy of logging levels.
 | ||
|  */
 | ||
| var LOG_LEVELS;
 | ||
| (function (LOG_LEVELS) {
 | ||
|     LOG_LEVELS[LOG_LEVELS["SILENT"] = 0] = "SILENT";
 | ||
|     LOG_LEVELS[LOG_LEVELS["ERROR"] = 1] = "ERROR";
 | ||
|     LOG_LEVELS[LOG_LEVELS["WARN"] = 2] = "WARN";
 | ||
|     LOG_LEVELS[LOG_LEVELS["LOG"] = 3] = "LOG";
 | ||
|     LOG_LEVELS[LOG_LEVELS["INFO"] = 4] = "INFO";
 | ||
|     LOG_LEVELS[LOG_LEVELS["DEBUG"] = 5] = "DEBUG";
 | ||
| })(LOG_LEVELS || (LOG_LEVELS = {}));
 | ||
| /** Default log level for the tool. */
 | ||
| var DEFAULT_LOG_LEVEL = LOG_LEVELS.INFO;
 | ||
| /** Write to the console for at INFO logging level */
 | ||
| var info = buildLogLevelFunction(function () { return console.info; }, LOG_LEVELS.INFO);
 | ||
| /** Write to the console for at ERROR logging level */
 | ||
| var error = buildLogLevelFunction(function () { return console.error; }, LOG_LEVELS.ERROR);
 | ||
| /** Write to the console for at DEBUG logging level */
 | ||
| var debug = buildLogLevelFunction(function () { return console.debug; }, LOG_LEVELS.DEBUG);
 | ||
| /** Write to the console for at LOG logging level */
 | ||
| // tslint:disable-next-line: no-console
 | ||
| var log = buildLogLevelFunction(function () { return console.log; }, LOG_LEVELS.LOG);
 | ||
| /** Write to the console for at WARN logging level */
 | ||
| var warn = buildLogLevelFunction(function () { return console.warn; }, LOG_LEVELS.WARN);
 | ||
| /** Build an instance of a logging function for the provided level. */
 | ||
| function buildLogLevelFunction(loadCommand, level) {
 | ||
|     /** Write to stdout for the LOG_LEVEL. */
 | ||
|     var loggingFunction = function () {
 | ||
|         var text = [];
 | ||
|         for (var _i = 0; _i < arguments.length; _i++) {
 | ||
|             text[_i] = arguments[_i];
 | ||
|         }
 | ||
|         runConsoleCommand.apply(void 0, tslib.__spread([loadCommand, level], text));
 | ||
|     };
 | ||
|     /** Start a group at the LOG_LEVEL, optionally starting it as collapsed. */
 | ||
|     loggingFunction.group = function (text, collapsed) {
 | ||
|         if (collapsed === void 0) { collapsed = false; }
 | ||
|         var command = collapsed ? console.groupCollapsed : console.group;
 | ||
|         runConsoleCommand(function () { return command; }, level, text);
 | ||
|     };
 | ||
|     /** End the group at the LOG_LEVEL. */
 | ||
|     loggingFunction.groupEnd = function () {
 | ||
|         runConsoleCommand(function () { return console.groupEnd; }, level);
 | ||
|     };
 | ||
|     return loggingFunction;
 | ||
| }
 | ||
| /**
 | ||
|  * Run the console command provided, if the environments logging level greater than the
 | ||
|  * provided logging level.
 | ||
|  *
 | ||
|  * The loadCommand takes in a function which is called to retrieve the console.* function
 | ||
|  * to allow for jasmine spies to still work in testing.  Without this method of retrieval
 | ||
|  * the console.* function, the function is saved into the closure of the created logging
 | ||
|  * function before jasmine can spy.
 | ||
|  */
 | ||
| function runConsoleCommand(loadCommand, logLevel) {
 | ||
|     var text = [];
 | ||
|     for (var _i = 2; _i < arguments.length; _i++) {
 | ||
|         text[_i - 2] = arguments[_i];
 | ||
|     }
 | ||
|     if (getLogLevel() >= logLevel) {
 | ||
|         loadCommand().apply(void 0, tslib.__spread(text));
 | ||
|     }
 | ||
|     printToLogFile.apply(void 0, tslib.__spread([logLevel], text));
 | ||
| }
 | ||
| /**
 | ||
|  * Retrieve the log level from environment variables, if the value found
 | ||
|  * based on the LOG_LEVEL environment variable is undefined, return the default
 | ||
|  * logging level.
 | ||
|  */
 | ||
| function getLogLevel() {
 | ||
|     var logLevelEnvValue = (process.env["LOG_LEVEL"] || '').toUpperCase();
 | ||
|     var logLevel = LOG_LEVELS[logLevelEnvValue];
 | ||
|     if (logLevel === undefined) {
 | ||
|         return DEFAULT_LOG_LEVEL;
 | ||
|     }
 | ||
|     return logLevel;
 | ||
| }
 | ||
| /** All text to write to the log file. */
 | ||
| var LOGGED_TEXT = '';
 | ||
| /** Whether file logging as been enabled. */
 | ||
| var FILE_LOGGING_ENABLED = false;
 | ||
| /**
 | ||
|  * The number of columns used in the prepended log level information on each line of the logging
 | ||
|  * output file.
 | ||
|  */
 | ||
| var LOG_LEVEL_COLUMNS = 7;
 | ||
| /**
 | ||
|  * Enable writing the logged outputs to the log file on process exit, sets initial lines from the
 | ||
|  * command execution, containing information about the timing and command parameters.
 | ||
|  *
 | ||
|  * This is expected to be called only once during a command run, and should be called by the
 | ||
|  * middleware of yargs to enable the file logging before the rest of the command parsing and
 | ||
|  * response is executed.
 | ||
|  */
 | ||
| function captureLogOutputForCommand(argv) {
 | ||
|     if (FILE_LOGGING_ENABLED) {
 | ||
|         throw Error('`captureLogOutputForCommand` cannot be called multiple times');
 | ||
|     }
 | ||
|     /** The date time used for timestamping when the command was invoked. */
 | ||
|     var now = new Date();
 | ||
|     /** Header line to separate command runs in log files. */
 | ||
|     var headerLine = Array(100).fill('#').join('');
 | ||
|     LOGGED_TEXT += headerLine + "\nCommand: " + argv.$0 + " " + argv._.join(' ') + "\nRan at: " + now + "\n";
 | ||
|     // On process exit, write the logged output to the appropriate log files
 | ||
|     process.on('exit', function (code) {
 | ||
|         LOGGED_TEXT += "Command ran in " + (new Date().getTime() - now.getTime()) + "ms";
 | ||
|         /** Path to the log file location. */
 | ||
|         var logFilePath = path.join(getRepoBaseDir(), '.ng-dev.log');
 | ||
|         // Strip ANSI escape codes from log outputs.
 | ||
|         LOGGED_TEXT = LOGGED_TEXT.replace(/\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]/g, '');
 | ||
|         fs.writeFileSync(logFilePath, LOGGED_TEXT);
 | ||
|         // For failure codes greater than 1, the new logged lines should be written to a specific log
 | ||
|         // file for the command run failure.
 | ||
|         if (code > 1) {
 | ||
|             fs.writeFileSync(path.join(getRepoBaseDir(), ".ng-dev.err-" + now.getTime() + ".log"), LOGGED_TEXT);
 | ||
|         }
 | ||
|     });
 | ||
|     // Mark file logging as enabled to prevent the function from executing multiple times.
 | ||
|     FILE_LOGGING_ENABLED = true;
 | ||
| }
 | ||
| /** Write the provided text to the log file, prepending each line with the log level.  */
 | ||
| function printToLogFile(logLevel) {
 | ||
|     var text = [];
 | ||
|     for (var _i = 1; _i < arguments.length; _i++) {
 | ||
|         text[_i - 1] = arguments[_i];
 | ||
|     }
 | ||
|     var logLevelText = (LOG_LEVELS[logLevel] + ":").padEnd(LOG_LEVEL_COLUMNS);
 | ||
|     LOGGED_TEXT += text.join(' ').split('\n').map(function (l) { return logLevelText + " " + l + "\n"; }).join('');
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** URL to the Github page where personal access tokens can be managed. */
 | ||
| var GITHUB_TOKEN_SETTINGS_URL = 'https://github.com/settings/tokens';
 | ||
| /** URL to the Github page where personal access tokens can be generated. */
 | ||
| var GITHUB_TOKEN_GENERATE_URL = 'https://github.com/settings/tokens/new';
 | ||
| /** Adds the provided token to the given Github HTTPs remote url. */
 | ||
| function addTokenToGitHttpsUrl(githubHttpsUrl, token) {
 | ||
|     var url$1 = new url.URL(githubHttpsUrl);
 | ||
|     url$1.username = token;
 | ||
|     return url$1.href;
 | ||
| }
 | ||
| /** Gets the repository Git URL for the given github config. */
 | ||
| function getRepositoryGitUrl(config, githubToken) {
 | ||
|     if (config.useSsh) {
 | ||
|         return "git@github.com:" + config.owner + "/" + config.name + ".git";
 | ||
|     }
 | ||
|     var baseHttpUrl = "https://github.com/" + config.owner + "/" + config.name + ".git";
 | ||
|     if (githubToken !== undefined) {
 | ||
|         return addTokenToGitHttpsUrl(baseHttpUrl, githubToken);
 | ||
|     }
 | ||
|     return baseHttpUrl;
 | ||
| }
 | ||
| /** Gets a Github URL that refers to a list of recent commits within a specified branch. */
 | ||
| function getListCommitsInBranchUrl(_a, branchName) {
 | ||
|     var remoteParams = _a.remoteParams;
 | ||
|     return "https://github.com/" + remoteParams.owner + "/" + remoteParams.repo + "/commits/" + branchName;
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Sets up the `github-token` command option for the given Yargs instance. */
 | ||
| function addGithubTokenOption(yargs) {
 | ||
|     return yargs
 | ||
|         // 'github-token' is casted to 'githubToken' to properly set up typings to reflect the key in
 | ||
|         // the Argv object being camelCase rather than kebob case due to the `camel-case-expansion`
 | ||
|         // config: https://github.com/yargs/yargs-parser#camel-case-expansion
 | ||
|         .option('github-token', {
 | ||
|         type: 'string',
 | ||
|         description: 'Github token. If not set, token is retrieved from the environment variables.',
 | ||
|         coerce: function (token) {
 | ||
|             var githubToken = token || process.env.GITHUB_TOKEN || process.env.TOKEN;
 | ||
|             if (!githubToken) {
 | ||
|                 error(red('No Github token set. Please set the `GITHUB_TOKEN` environment variable.'));
 | ||
|                 error(red('Alternatively, pass the `--github-token` command line flag.'));
 | ||
|                 error(yellow("You can generate a token here: " + GITHUB_TOKEN_GENERATE_URL));
 | ||
|                 process.exit(1);
 | ||
|             }
 | ||
|             return githubToken;
 | ||
|         },
 | ||
|     })
 | ||
|         .default('github-token', '', '<LOCAL TOKEN>');
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Error for failed Github API requests. */
 | ||
| var GithubApiRequestError = /** @class */ (function (_super) {
 | ||
|     tslib.__extends(GithubApiRequestError, _super);
 | ||
|     function GithubApiRequestError(status, message) {
 | ||
|         var _this = _super.call(this, message) || this;
 | ||
|         _this.status = status;
 | ||
|         return _this;
 | ||
|     }
 | ||
|     return GithubApiRequestError;
 | ||
| }(Error));
 | ||
| /**
 | ||
|  * A Github client for interacting with the Github APIs.
 | ||
|  *
 | ||
|  * Additionally, provides convenience methods for actions which require multiple requests, or
 | ||
|  * would provide value from memoized style responses.
 | ||
|  **/
 | ||
| var GithubClient = /** @class */ (function (_super) {
 | ||
|     tslib.__extends(GithubClient, _super);
 | ||
|     function GithubClient(token) {
 | ||
|         var _this = 
 | ||
|         // Pass in authentication token to base Octokit class.
 | ||
|         _super.call(this, { auth: token }) || this;
 | ||
|         /** The current user based on checking against the Github API. */
 | ||
|         _this._currentUser = null;
 | ||
|         _this.hook.error('request', function (error) {
 | ||
|             // Wrap API errors in a known error class. This allows us to
 | ||
|             // expect Github API errors better and in a non-ambiguous way.
 | ||
|             throw new GithubApiRequestError(error.status, error.message);
 | ||
|         });
 | ||
|         // Create authenticated graphql client.
 | ||
|         _this.graphql = new GithubGraphqlClient(token);
 | ||
|         return _this;
 | ||
|     }
 | ||
|     /** Retrieve the login of the current user from Github. */
 | ||
|     GithubClient.prototype.getCurrentUser = function () {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|             var result;
 | ||
|             return tslib.__generator(this, function (_a) {
 | ||
|                 switch (_a.label) {
 | ||
|                     case 0:
 | ||
|                         // If the current user has already been retrieved return the current user value again.
 | ||
|                         if (this._currentUser !== null) {
 | ||
|                             return [2 /*return*/, this._currentUser];
 | ||
|                         }
 | ||
|                         return [4 /*yield*/, this.graphql.query({
 | ||
|                                 viewer: {
 | ||
|                                     login: typedGraphqlify.types.string,
 | ||
|                                 }
 | ||
|                             })];
 | ||
|                     case 1:
 | ||
|                         result = _a.sent();
 | ||
|                         return [2 /*return*/, this._currentUser = result.viewer.login];
 | ||
|                 }
 | ||
|             });
 | ||
|         });
 | ||
|     };
 | ||
|     return GithubClient;
 | ||
| }(Octokit));
 | ||
| /** A client for interacting with Github's GraphQL API. */
 | ||
| var GithubGraphqlClient = /** @class */ (function () {
 | ||
|     function GithubGraphqlClient(token) {
 | ||
|         /** The Github GraphQL (v4) API. */
 | ||
|         this.graqhql = graphql.graphql;
 | ||
|         // Set the default headers to include authorization with the provided token for all
 | ||
|         // graphQL calls.
 | ||
|         if (token) {
 | ||
|             this.graqhql = this.graqhql.defaults({ headers: { authorization: "token " + token } });
 | ||
|         }
 | ||
|     }
 | ||
|     /** Perform a query using Github's GraphQL API. */
 | ||
|     GithubGraphqlClient.prototype.query = function (queryObject, params) {
 | ||
|         if (params === void 0) { params = {}; }
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|             var queryString;
 | ||
|             return tslib.__generator(this, function (_a) {
 | ||
|                 switch (_a.label) {
 | ||
|                     case 0:
 | ||
|                         queryString = typedGraphqlify.query(queryObject);
 | ||
|                         return [4 /*yield*/, this.graqhql(queryString, params)];
 | ||
|                     case 1: return [2 /*return*/, (_a.sent())];
 | ||
|                 }
 | ||
|             });
 | ||
|         });
 | ||
|     };
 | ||
|     return GithubGraphqlClient;
 | ||
| }());
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Error for failed Git commands. */
 | ||
| var GitCommandError = /** @class */ (function (_super) {
 | ||
|     tslib.__extends(GitCommandError, _super);
 | ||
|     function GitCommandError(client, args) {
 | ||
|         var _this = 
 | ||
|         // Errors are not guaranteed to be caught. To ensure that we don't
 | ||
|         // accidentally leak the Github token that might be used in a command,
 | ||
|         // we sanitize the command that will be part of the error message.
 | ||
|         _super.call(this, "Command failed: git " + client.omitGithubTokenFromMessage(args.join(' '))) || this;
 | ||
|         _this.args = args;
 | ||
|         return _this;
 | ||
|     }
 | ||
|     return GitCommandError;
 | ||
| }(Error));
 | ||
| /**
 | ||
|  * Common client for performing Git interactions with a given remote.
 | ||
|  *
 | ||
|  * Takes in two optional arguments:
 | ||
|  *   `githubToken`: the token used for authentication in Github interactions, by default empty
 | ||
|  *     allowing readonly actions.
 | ||
|  *   `config`: The dev-infra configuration containing information about the remote. By default
 | ||
|  *     the dev-infra configuration is loaded with its Github configuration.
 | ||
|  **/
 | ||
| var GitClient = /** @class */ (function () {
 | ||
|     function GitClient(githubToken, _config, _projectRoot) {
 | ||
|         if (_config === void 0) { _config = getConfig(); }
 | ||
|         if (_projectRoot === void 0) { _projectRoot = getRepoBaseDir(); }
 | ||
|         this.githubToken = githubToken;
 | ||
|         this._config = _config;
 | ||
|         this._projectRoot = _projectRoot;
 | ||
|         /** Short-hand for accessing the default remote configuration. */
 | ||
|         this.remoteConfig = this._config.github;
 | ||
|         /** Octokit request parameters object for targeting the configured remote. */
 | ||
|         this.remoteParams = { owner: this.remoteConfig.owner, repo: this.remoteConfig.name };
 | ||
|         /** Git URL that resolves to the configured repository. */
 | ||
|         this.repoGitUrl = getRepositoryGitUrl(this.remoteConfig, this.githubToken);
 | ||
|         /** Instance of the authenticated Github octokit API. */
 | ||
|         this.github = new GithubClient(this.githubToken);
 | ||
|         /** The OAuth scopes available for the provided Github token. */
 | ||
|         this._cachedOauthScopes = null;
 | ||
|         /**
 | ||
|          * Regular expression that matches the provided Github token. Used for
 | ||
|          * sanitizing the token from Git child process output.
 | ||
|          */
 | ||
|         this._githubTokenRegex = null;
 | ||
|         // If a token has been specified (and is not empty), pass it to the Octokit API and
 | ||
|         // also create a regular expression that can be used for sanitizing Git command output
 | ||
|         // so that it does not print the token accidentally.
 | ||
|         if (githubToken != null) {
 | ||
|             this._githubTokenRegex = new RegExp(githubToken, 'g');
 | ||
|         }
 | ||
|     }
 | ||
|     /** Executes the given git command. Throws if the command fails. */
 | ||
|     GitClient.prototype.run = function (args, options) {
 | ||
|         var result = this.runGraceful(args, options);
 | ||
|         if (result.status !== 0) {
 | ||
|             throw new GitCommandError(this, args);
 | ||
|         }
 | ||
|         // Omit `status` from the type so that it's obvious that the status is never
 | ||
|         // non-zero as explained in the method description.
 | ||
|         return result;
 | ||
|     };
 | ||
|     /**
 | ||
|      * Spawns a given Git command process. Does not throw if the command fails. Additionally,
 | ||
|      * if there is any stderr output, the output will be printed. This makes it easier to
 | ||
|      * info failed commands.
 | ||
|      */
 | ||
|     GitClient.prototype.runGraceful = function (args, options) {
 | ||
|         if (options === void 0) { options = {}; }
 | ||
|         // To improve the debugging experience in case something fails, we print all executed Git
 | ||
|         // commands to better understand the git actions occuring. Depending on the command being
 | ||
|         // executed, this debugging information should be logged at different logging levels.
 | ||
|         var printFn = (!GitClient.LOG_COMMANDS || options.stdio === 'ignore') ? debug : info;
 | ||
|         // Note that we do not want to print the token if it is contained in the command. It's common
 | ||
|         // to share errors with others if the tool failed, and we do not want to leak tokens.
 | ||
|         printFn('Executing: git', this.omitGithubTokenFromMessage(args.join(' ')));
 | ||
|         var result = child_process.spawnSync('git', args, tslib.__assign(tslib.__assign({ cwd: this._projectRoot, stdio: 'pipe' }, options), { 
 | ||
|             // Encoding is always `utf8` and not overridable. This ensures that this method
 | ||
|             // always returns `string` as output instead of buffers.
 | ||
|             encoding: 'utf8' }));
 | ||
|         if (result.stderr !== null) {
 | ||
|             // Git sometimes prints the command if it failed. This means that it could
 | ||
|             // potentially leak the Github token used for accessing the remote. To avoid
 | ||
|             // printing a token, we sanitize the string before printing the stderr output.
 | ||
|             process.stderr.write(this.omitGithubTokenFromMessage(result.stderr));
 | ||
|         }
 | ||
|         return result;
 | ||
|     };
 | ||
|     /** Whether the given branch contains the specified SHA. */
 | ||
|     GitClient.prototype.hasCommit = function (branchName, sha) {
 | ||
|         return this.run(['branch', branchName, '--contains', sha]).stdout !== '';
 | ||
|     };
 | ||
|     /** Gets the currently checked out branch or revision. */
 | ||
|     GitClient.prototype.getCurrentBranchOrRevision = function () {
 | ||
|         var branchName = this.run(['rev-parse', '--abbrev-ref', 'HEAD']).stdout.trim();
 | ||
|         // If no branch name could be resolved. i.e. `HEAD` has been returned, then Git
 | ||
|         // is currently in a detached state. In those cases, we just want to return the
 | ||
|         // currently checked out revision/SHA.
 | ||
|         if (branchName === 'HEAD') {
 | ||
|             return this.run(['rev-parse', 'HEAD']).stdout.trim();
 | ||
|         }
 | ||
|         return branchName;
 | ||
|     };
 | ||
|     /** Gets whether the current Git repository has uncommitted changes. */
 | ||
|     GitClient.prototype.hasUncommittedChanges = function () {
 | ||
|         return this.runGraceful(['diff-index', '--quiet', 'HEAD']).status !== 0;
 | ||
|     };
 | ||
|     /** Whether the repo has any local changes. */
 | ||
|     GitClient.prototype.hasLocalChanges = function () {
 | ||
|         return this.runGraceful(['diff-index', '--quiet', 'HEAD']).status !== 0;
 | ||
|     };
 | ||
|     /** Sanitizes a given message by omitting the provided Github token if present. */
 | ||
|     GitClient.prototype.omitGithubTokenFromMessage = function (value) {
 | ||
|         // If no token has been defined (i.e. no token regex), we just return the
 | ||
|         // value as is. There is no secret value that needs to be omitted.
 | ||
|         if (this._githubTokenRegex === null) {
 | ||
|             return value;
 | ||
|         }
 | ||
|         return value.replace(this._githubTokenRegex, '<TOKEN>');
 | ||
|     };
 | ||
|     /**
 | ||
|      * Checks out a requested branch or revision, optionally cleaning the state of the repository
 | ||
|      * before attempting the checking. Returns a boolean indicating whether the branch or revision
 | ||
|      * was cleanly checked out.
 | ||
|      */
 | ||
|     GitClient.prototype.checkout = function (branchOrRevision, cleanState) {
 | ||
|         if (cleanState) {
 | ||
|             // Abort any outstanding ams.
 | ||
|             this.runGraceful(['am', '--abort'], { stdio: 'ignore' });
 | ||
|             // Abort any outstanding cherry-picks.
 | ||
|             this.runGraceful(['cherry-pick', '--abort'], { stdio: 'ignore' });
 | ||
|             // Abort any outstanding rebases.
 | ||
|             this.runGraceful(['rebase', '--abort'], { stdio: 'ignore' });
 | ||
|             // Clear any changes in the current repo.
 | ||
|             this.runGraceful(['reset', '--hard'], { stdio: 'ignore' });
 | ||
|         }
 | ||
|         return this.runGraceful(['checkout', branchOrRevision], { stdio: 'ignore' }).status === 0;
 | ||
|     };
 | ||
|     /**
 | ||
|      * Assert the GitClient instance is using a token with permissions for the all of the
 | ||
|      * provided OAuth scopes.
 | ||
|      */
 | ||
|     GitClient.prototype.hasOauthScopes = function (testFn) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|             var scopes, missingScopes, error;
 | ||
|             return tslib.__generator(this, function (_a) {
 | ||
|                 switch (_a.label) {
 | ||
|                     case 0: return [4 /*yield*/, this.getAuthScopesForToken()];
 | ||
|                     case 1:
 | ||
|                         scopes = _a.sent();
 | ||
|                         missingScopes = [];
 | ||
|                         // Test Github OAuth scopes and collect missing ones.
 | ||
|                         testFn(scopes, missingScopes);
 | ||
|                         // If no missing scopes are found, return true to indicate all OAuth Scopes are available.
 | ||
|                         if (missingScopes.length === 0) {
 | ||
|                             return [2 /*return*/, true];
 | ||
|                         }
 | ||
|                         error = "The provided <TOKEN> does not have required permissions due to missing scope(s): " +
 | ||
|                             (yellow(missingScopes.join(', ')) + "\n\n") +
 | ||
|                             "Update the token in use at:\n" +
 | ||
|                             ("  " + GITHUB_TOKEN_SETTINGS_URL + "\n\n") +
 | ||
|                             ("Alternatively, a new token can be created at: " + GITHUB_TOKEN_GENERATE_URL + "\n");
 | ||
|                         return [2 /*return*/, { error: error }];
 | ||
|                 }
 | ||
|             });
 | ||
|         });
 | ||
|     };
 | ||
|     /**
 | ||
|      * Retrieve the OAuth scopes for the loaded Github token.
 | ||
|      **/
 | ||
|     GitClient.prototype.getAuthScopesForToken = function () {
 | ||
|         // If the OAuth scopes have already been loaded, return the Promise containing them.
 | ||
|         if (this._cachedOauthScopes !== null) {
 | ||
|             return this._cachedOauthScopes;
 | ||
|         }
 | ||
|         // OAuth scopes are loaded via the /rate_limit endpoint to prevent
 | ||
|         // usage of a request against that rate_limit for this lookup.
 | ||
|         return this._cachedOauthScopes = this.github.rateLimit.get().then(function (_response) {
 | ||
|             var response = _response;
 | ||
|             var scopes = response.headers['x-oauth-scopes'] || '';
 | ||
|             return scopes.split(',').map(function (scope) { return scope.trim(); });
 | ||
|         });
 | ||
|     };
 | ||
|     /** Whether verbose logging of Git actions should be used. */
 | ||
|     GitClient.LOG_COMMANDS = true;
 | ||
|     return GitClient;
 | ||
| }());
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Retrieve and validate the config as `CaretakerConfig`. */
 | ||
| function getCaretakerConfig() {
 | ||
|     // List of errors encountered validating the config.
 | ||
|     const errors = [];
 | ||
|     // The non-validated config object.
 | ||
|     const config = getConfig();
 | ||
|     assertNoErrors(errors);
 | ||
|     return config;
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Class describing a release-train. */
 | ||
| class ReleaseTrain {
 | ||
|     constructor(
 | ||
|     /** Name of the branch for this release-train. */
 | ||
|     branchName, 
 | ||
|     /** Most recent version for this release train. */
 | ||
|     version) {
 | ||
|         this.branchName = branchName;
 | ||
|         this.version = version;
 | ||
|         /** Whether the release train is currently targeting a major. */
 | ||
|         this.isMajor = this.version.minor === 0 && this.version.patch === 0;
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Regular expression that matches version-branches. */
 | ||
| const versionBranchNameRegex = /^(\d+)\.(\d+)\.x$/;
 | ||
| /** Gets the version of a given branch by reading the `package.json` upstream. */
 | ||
| function getVersionOfBranch(repo, branchName) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const { data } = yield repo.api.repos.getContents({ owner: repo.owner, repo: repo.name, path: '/package.json', ref: branchName });
 | ||
|         const { version } = JSON.parse(Buffer.from(data.content, 'base64').toString());
 | ||
|         const parsedVersion = semver.parse(version);
 | ||
|         if (parsedVersion === null) {
 | ||
|             throw Error(`Invalid version detected in following branch: ${branchName}.`);
 | ||
|         }
 | ||
|         return parsedVersion;
 | ||
|     });
 | ||
| }
 | ||
| /** Whether the given branch corresponds to a version branch. */
 | ||
| function isVersionBranch(branchName) {
 | ||
|     return versionBranchNameRegex.test(branchName);
 | ||
| }
 | ||
| /**
 | ||
|  * Converts a given version-branch into a SemVer version that can be used with SemVer
 | ||
|  * utilities. e.g. to determine semantic order, extract major digit, compare.
 | ||
|  *
 | ||
|  * For example `10.0.x` will become `10.0.0` in SemVer. The patch digit is not
 | ||
|  * relevant but needed for parsing. SemVer does not allow `x` as patch digit.
 | ||
|  */
 | ||
| function getVersionForVersionBranch(branchName) {
 | ||
|     return semver.parse(branchName.replace(versionBranchNameRegex, '$1.$2.0'));
 | ||
| }
 | ||
| /**
 | ||
|  * Gets the version branches for the specified major versions in descending
 | ||
|  * order. i.e. latest version branches first.
 | ||
|  */
 | ||
| function getBranchesForMajorVersions(repo, majorVersions) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const { data: branchData } = yield repo.api.repos.listBranches({ owner: repo.owner, repo: repo.name, protected: true });
 | ||
|         const branches = [];
 | ||
|         for (const { name } of branchData) {
 | ||
|             if (!isVersionBranch(name)) {
 | ||
|                 continue;
 | ||
|             }
 | ||
|             // Convert the version-branch into a SemVer version that can be used with the
 | ||
|             // SemVer utilities. e.g. to determine semantic order, compare versions.
 | ||
|             const parsed = getVersionForVersionBranch(name);
 | ||
|             // Collect all version-branches that match the specified major versions.
 | ||
|             if (parsed !== null && majorVersions.includes(parsed.major)) {
 | ||
|                 branches.push({ name, parsed });
 | ||
|             }
 | ||
|         }
 | ||
|         // Sort captured version-branches in descending order.
 | ||
|         return branches.sort((a, b) => semver.rcompare(a.parsed, b.parsed));
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Branch name for the `next` branch. */
 | ||
| const nextBranchName = 'master';
 | ||
| /** Fetches the active release trains for the configured project. */
 | ||
| function fetchActiveReleaseTrains(repo) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const nextVersion = yield getVersionOfBranch(repo, nextBranchName);
 | ||
|         const next = new ReleaseTrain(nextBranchName, nextVersion);
 | ||
|         const majorVersionsToConsider = [];
 | ||
|         let expectedReleaseCandidateMajor;
 | ||
|         // If the `next` branch (i.e. `master` branch) is for an upcoming major version, we know
 | ||
|         // that there is no patch branch or feature-freeze/release-candidate branch for this major
 | ||
|         // digit. If the current `next` version is the first minor of a major version, we know that
 | ||
|         // the feature-freeze/release-candidate branch can only be the actual major branch. The
 | ||
|         // patch branch is based on that, either the actual major branch or the last minor from the
 | ||
|         // preceding major version. In all other cases, the patch branch and feature-freeze or
 | ||
|         // release-candidate branch are part of the same major version. Consider the following:
 | ||
|         //
 | ||
|         //  CASE 1. next: 11.0.0-next.0: patch and feature-freeze/release-candidate can only be
 | ||
|         //          most recent `10.<>.x` branches. The FF/RC branch can only be the last-minor of v10.
 | ||
|         //  CASE 2. next: 11.1.0-next.0: patch can be either `11.0.x` or last-minor in v10 based
 | ||
|         //          on whether there is a feature-freeze/release-candidate branch (=> `11.0.x`).
 | ||
|         //  CASE 3. next: 10.6.0-next.0: patch can be either `10.5.x` or `10.4.x` based on whether
 | ||
|         //          there is a feature-freeze/release-candidate branch (=> `10.5.x`)
 | ||
|         if (nextVersion.minor === 0) {
 | ||
|             expectedReleaseCandidateMajor = nextVersion.major - 1;
 | ||
|             majorVersionsToConsider.push(nextVersion.major - 1);
 | ||
|         }
 | ||
|         else if (nextVersion.minor === 1) {
 | ||
|             expectedReleaseCandidateMajor = nextVersion.major;
 | ||
|             majorVersionsToConsider.push(nextVersion.major, nextVersion.major - 1);
 | ||
|         }
 | ||
|         else {
 | ||
|             expectedReleaseCandidateMajor = nextVersion.major;
 | ||
|             majorVersionsToConsider.push(nextVersion.major);
 | ||
|         }
 | ||
|         // Collect all version-branches that should be considered for the latest version-branch,
 | ||
|         // or the feature-freeze/release-candidate.
 | ||
|         const branches = (yield getBranchesForMajorVersions(repo, majorVersionsToConsider));
 | ||
|         const { latest, releaseCandidate } = yield findActiveReleaseTrainsFromVersionBranches(repo, nextVersion, branches, expectedReleaseCandidateMajor);
 | ||
|         if (latest === null) {
 | ||
|             throw Error(`Unable to determine the latest release-train. The following branches ` +
 | ||
|                 `have been considered: [${branches.map(b => b.name).join(', ')}]`);
 | ||
|         }
 | ||
|         return { releaseCandidate, latest, next };
 | ||
|     });
 | ||
| }
 | ||
| /** Finds the currently active release trains from the specified version branches. */
 | ||
| function findActiveReleaseTrainsFromVersionBranches(repo, nextVersion, branches, expectedReleaseCandidateMajor) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         // Version representing the release-train currently in the next phase. Note that we ignore
 | ||
|         // patch and pre-release segments in order to be able to compare the next release train to
 | ||
|         // other release trains from version branches (which follow the `N.N.x` pattern).
 | ||
|         const nextReleaseTrainVersion = semver.parse(`${nextVersion.major}.${nextVersion.minor}.0`);
 | ||
|         let latest = null;
 | ||
|         let releaseCandidate = null;
 | ||
|         // Iterate through the captured branches and find the latest non-prerelease branch and a
 | ||
|         // potential release candidate branch. From the collected branches we iterate descending
 | ||
|         // order (most recent semantic version-branch first). The first branch is either the latest
 | ||
|         // active version branch (i.e. patch) or a feature-freeze/release-candidate branch. A FF/RC
 | ||
|         // branch cannot be older than the latest active version-branch, so we stop iterating once
 | ||
|         // we found such a branch. Otherwise, if we found a FF/RC branch, we continue looking for the
 | ||
|         // next version-branch as that one is supposed to be the latest active version-branch. If it
 | ||
|         // is not, then an error will be thrown due to two FF/RC branches existing at the same time.
 | ||
|         for (const { name, parsed } of branches) {
 | ||
|             // It can happen that version branches have been accidentally created which are more recent
 | ||
|             // than the release-train in the next branch (i.e. `master`). We could ignore such branches
 | ||
|             // silently, but it might be symptomatic for an outdated version in the `next` branch, or an
 | ||
|             // accidentally created branch by the caretaker. In either way we want to raise awareness.
 | ||
|             if (semver.gt(parsed, nextReleaseTrainVersion)) {
 | ||
|                 throw Error(`Discovered unexpected version-branch "${name}" for a release-train that is ` +
 | ||
|                     `more recent than the release-train currently in the "${nextBranchName}" branch. ` +
 | ||
|                     `Please either delete the branch if created by accident, or update the outdated ` +
 | ||
|                     `version in the next branch (${nextBranchName}).`);
 | ||
|             }
 | ||
|             else if (semver.eq(parsed, nextReleaseTrainVersion)) {
 | ||
|                 throw Error(`Discovered unexpected version-branch "${name}" for a release-train that is already ` +
 | ||
|                     `active in the "${nextBranchName}" branch. Please either delete the branch if ` +
 | ||
|                     `created by accident, or update the version in the next branch (${nextBranchName}).`);
 | ||
|             }
 | ||
|             const version = yield getVersionOfBranch(repo, name);
 | ||
|             const releaseTrain = new ReleaseTrain(name, version);
 | ||
|             const isPrerelease = version.prerelease[0] === 'rc' || version.prerelease[0] === 'next';
 | ||
|             if (isPrerelease) {
 | ||
|                 if (releaseCandidate !== null) {
 | ||
|                     throw Error(`Unable to determine latest release-train. Found two consecutive ` +
 | ||
|                         `branches in feature-freeze/release-candidate phase. Did not expect both "${name}" ` +
 | ||
|                         `and "${releaseCandidate.branchName}" to be in feature-freeze/release-candidate mode.`);
 | ||
|                 }
 | ||
|                 else if (version.major !== expectedReleaseCandidateMajor) {
 | ||
|                     throw Error(`Discovered unexpected old feature-freeze/release-candidate branch. Expected no ` +
 | ||
|                         `version-branch in feature-freeze/release-candidate mode for v${version.major}.`);
 | ||
|                 }
 | ||
|                 releaseCandidate = releaseTrain;
 | ||
|             }
 | ||
|             else {
 | ||
|                 latest = releaseTrain;
 | ||
|                 break;
 | ||
|             }
 | ||
|         }
 | ||
|         return { releaseCandidate, latest };
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Cache for requested NPM package information. A cache is desirable as the NPM
 | ||
|  * registry requests are usually very large and slow.
 | ||
|  */
 | ||
| const _npmPackageInfoCache = {};
 | ||
| /**
 | ||
|  * Fetches the NPM package representing the project. Angular repositories usually contain
 | ||
|  * multiple packages in a monorepo scheme, but packages dealt with as part of the release
 | ||
|  * tooling are released together with the same versioning and branching. This means that
 | ||
|  * a single package can be used as source of truth for NPM package queries.
 | ||
|  */
 | ||
| function fetchProjectNpmPackageInfo(config) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const pkgName = getRepresentativeNpmPackage(config);
 | ||
|         return yield fetchPackageInfoFromNpmRegistry(pkgName);
 | ||
|     });
 | ||
| }
 | ||
| /** Gets whether the given version is published to NPM or not */
 | ||
| function isVersionPublishedToNpm(version, config) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const { versions } = yield fetchProjectNpmPackageInfo(config);
 | ||
|         return versions[version.format()] !== undefined;
 | ||
|     });
 | ||
| }
 | ||
| /**
 | ||
|  * Gets the representative NPM package for the specified release configuration. Angular
 | ||
|  * repositories usually contain multiple packages in a monorepo scheme, but packages dealt with
 | ||
|  * as part of the release tooling are released together with the same versioning and branching.
 | ||
|  * This means that a single package can be used as source of truth for NPM package queries.
 | ||
|  */
 | ||
| function getRepresentativeNpmPackage(config) {
 | ||
|     return config.npmPackages[0];
 | ||
| }
 | ||
| /** Fetches the specified NPM package from the NPM registry. */
 | ||
| function fetchPackageInfoFromNpmRegistry(pkgName) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         if (_npmPackageInfoCache[pkgName] === undefined) {
 | ||
|             _npmPackageInfoCache[pkgName] =
 | ||
|                 fetch(`https://registry.npmjs.org/${pkgName}`).then(r => r.json());
 | ||
|         }
 | ||
|         return yield _npmPackageInfoCache[pkgName];
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Number of months a major version in Angular is actively supported. See:
 | ||
|  * https://angular.io/guide/releases#support-policy-and-schedule.
 | ||
|  */
 | ||
| const majorActiveSupportDuration = 6;
 | ||
| /**
 | ||
|  * Number of months a major version has active long-term support. See:
 | ||
|  * https://angular.io/guide/releases#support-policy-and-schedule.
 | ||
|  */
 | ||
| const majorLongTermSupportDuration = 12;
 | ||
| /** Regular expression that matches LTS NPM dist tags. */
 | ||
| const ltsNpmDistTagRegex = /^v(\d+)-lts$/;
 | ||
| /** Finds all long-term support release trains from the specified NPM package. */
 | ||
| function fetchLongTermSupportBranchesFromNpm(config) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const { 'dist-tags': distTags, time } = yield fetchProjectNpmPackageInfo(config);
 | ||
|         const today = new Date();
 | ||
|         const active = [];
 | ||
|         const inactive = [];
 | ||
|         // Iterate through the NPM package information and determine active/inactive LTS versions with
 | ||
|         // their corresponding branches. We assume that an LTS tagged version in NPM belongs to the
 | ||
|         // last-minor branch of a given major (i.e. we assume there are no outdated LTS NPM dist tags).
 | ||
|         for (const npmDistTag in distTags) {
 | ||
|             if (ltsNpmDistTagRegex.test(npmDistTag)) {
 | ||
|                 const version = semver.parse(distTags[npmDistTag]);
 | ||
|                 const branchName = `${version.major}.${version.minor}.x`;
 | ||
|                 const majorReleaseDate = new Date(time[`${version.major}.0.0`]);
 | ||
|                 const ltsEndDate = computeLtsEndDateOfMajor(majorReleaseDate);
 | ||
|                 const ltsBranch = { name: branchName, version, npmDistTag };
 | ||
|                 // Depending on whether the LTS phase is still active, add the branch
 | ||
|                 // to the list of active or inactive LTS branches.
 | ||
|                 if (today <= ltsEndDate) {
 | ||
|                     active.push(ltsBranch);
 | ||
|                 }
 | ||
|                 else {
 | ||
|                     inactive.push(ltsBranch);
 | ||
|                 }
 | ||
|             }
 | ||
|         }
 | ||
|         // Sort LTS branches in descending order. i.e. most recent ones first.
 | ||
|         active.sort((a, b) => semver.rcompare(a.version, b.version));
 | ||
|         inactive.sort((a, b) => semver.rcompare(a.version, b.version));
 | ||
|         return { active, inactive };
 | ||
|     });
 | ||
| }
 | ||
| /**
 | ||
|  * Computes the date when long-term support ends for a major released at the
 | ||
|  * specified date.
 | ||
|  */
 | ||
| function computeLtsEndDateOfMajor(majorReleaseDate) {
 | ||
|     return new Date(majorReleaseDate.getFullYear(), majorReleaseDate.getMonth() + majorActiveSupportDuration + majorLongTermSupportDuration, majorReleaseDate.getDate(), majorReleaseDate.getHours(), majorReleaseDate.getMinutes(), majorReleaseDate.getSeconds(), majorReleaseDate.getMilliseconds());
 | ||
| }
 | ||
| /** Gets the long-term support NPM dist tag for a given major version. */
 | ||
| function getLtsNpmDistTagOfMajor(major) {
 | ||
|     // LTS versions should be tagged in NPM in the following format: `v{major}-lts`.
 | ||
|     return `v${major}-lts`;
 | ||
| }
 | ||
| 
 | ||
| /** The BaseModule to extend modules for caretaker checks from. */
 | ||
| class BaseModule {
 | ||
|     constructor(git, config) {
 | ||
|         this.git = git;
 | ||
|         this.config = config;
 | ||
|         /** The data for the module. */
 | ||
|         this.data = this.retrieveData();
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| class CiModule extends BaseModule {
 | ||
|     retrieveData() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const gitRepoWithApi = Object.assign({ api: this.git.github }, this.git.remoteConfig);
 | ||
|             const releaseTrains = yield fetchActiveReleaseTrains(gitRepoWithApi);
 | ||
|             const ciResultPromises = Object.entries(releaseTrains).map(([trainName, train]) => tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|                 if (train === null) {
 | ||
|                     return {
 | ||
|                         active: false,
 | ||
|                         name: trainName,
 | ||
|                         label: '',
 | ||
|                         status: 'not found',
 | ||
|                     };
 | ||
|                 }
 | ||
|                 return {
 | ||
|                     active: true,
 | ||
|                     name: train.branchName,
 | ||
|                     label: `${trainName} (${train.branchName})`,
 | ||
|                     status: yield this.getBranchStatusFromCi(train.branchName),
 | ||
|                 };
 | ||
|             }));
 | ||
|             return yield Promise.all(ciResultPromises);
 | ||
|         });
 | ||
|     }
 | ||
|     printToTerminal() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const data = yield this.data;
 | ||
|             const minLabelLength = Math.max(...data.map(result => result.label.length));
 | ||
|             info.group(bold(`CI`));
 | ||
|             data.forEach(result => {
 | ||
|                 if (result.active === false) {
 | ||
|                     debug(`No active release train for ${result.name}`);
 | ||
|                     return;
 | ||
|                 }
 | ||
|                 const label = result.label.padEnd(minLabelLength);
 | ||
|                 if (result.status === 'not found') {
 | ||
|                     info(`${result.name} was not found on CircleCI`);
 | ||
|                 }
 | ||
|                 else if (result.status === 'success') {
 | ||
|                     info(`${label} ✅`);
 | ||
|                 }
 | ||
|                 else {
 | ||
|                     info(`${label} ❌`);
 | ||
|                 }
 | ||
|             });
 | ||
|             info.groupEnd();
 | ||
|             info();
 | ||
|         });
 | ||
|     }
 | ||
|     /** Get the CI status of a given branch from CircleCI. */
 | ||
|     getBranchStatusFromCi(branch) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const { owner, name } = this.git.remoteConfig;
 | ||
|             const url = `https://circleci.com/gh/${owner}/${name}/tree/${branch}.svg?style=shield`;
 | ||
|             const result = yield fetch(url).then(result => result.text());
 | ||
|             if (result && !result.includes('no builds')) {
 | ||
|                 return result.includes('passing') ? 'success' : 'failed';
 | ||
|             }
 | ||
|             return 'not found';
 | ||
|         });
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| class G3Module extends BaseModule {
 | ||
|     retrieveData() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const toCopyToG3 = this.getG3FileIncludeAndExcludeLists();
 | ||
|             const latestSha = this.getLatestShas();
 | ||
|             if (toCopyToG3 === null || latestSha === null) {
 | ||
|                 return;
 | ||
|             }
 | ||
|             return this.getDiffStats(latestSha.g3, latestSha.master, toCopyToG3.include, toCopyToG3.exclude);
 | ||
|         });
 | ||
|     }
 | ||
|     printToTerminal() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const stats = yield this.data;
 | ||
|             if (!stats) {
 | ||
|                 return;
 | ||
|             }
 | ||
|             info.group(bold('g3 branch check'));
 | ||
|             if (stats.files === 0) {
 | ||
|                 info(`${stats.commits} commits between g3 and master`);
 | ||
|                 info('✅  No sync is needed at this time');
 | ||
|             }
 | ||
|             else {
 | ||
|                 info(`${stats.files} files changed, ${stats.insertions} insertions(+), ${stats.deletions} ` +
 | ||
|                     `deletions(-) from ${stats.commits} commits will be included in the next sync`);
 | ||
|             }
 | ||
|             info.groupEnd();
 | ||
|             info();
 | ||
|         });
 | ||
|     }
 | ||
|     /** Fetch and retrieve the latest sha for a specific branch. */
 | ||
|     getShaForBranchLatest(branch) {
 | ||
|         const { owner, name } = this.git.remoteConfig;
 | ||
|         /** The result fo the fetch command. */
 | ||
|         const fetchResult = this.git.runGraceful(['fetch', '-q', `https://github.com/${owner}/${name}.git`, branch]);
 | ||
|         if (fetchResult.status !== 0 &&
 | ||
|             fetchResult.stderr.includes(`couldn't find remote ref ${branch}`)) {
 | ||
|             debug(`No '${branch}' branch exists on upstream, skipping.`);
 | ||
|             return null;
 | ||
|         }
 | ||
|         return this.git.runGraceful(['rev-parse', 'FETCH_HEAD']).stdout.trim();
 | ||
|     }
 | ||
|     /**
 | ||
|      * Get git diff stats between master and g3, for all files and filtered to only g3 affecting
 | ||
|      * files.
 | ||
|      */
 | ||
|     getDiffStats(g3Ref, masterRef, includeFiles, excludeFiles) {
 | ||
|         /** The diff stats to be returned. */
 | ||
|         const stats = {
 | ||
|             insertions: 0,
 | ||
|             deletions: 0,
 | ||
|             files: 0,
 | ||
|             commits: 0,
 | ||
|         };
 | ||
|         // Determine the number of commits between master and g3 refs. */
 | ||
|         stats.commits =
 | ||
|             parseInt(this.git.run(['rev-list', '--count', `${g3Ref}..${masterRef}`]).stdout, 10);
 | ||
|         // Get the numstat information between master and g3
 | ||
|         this.git.run(['diff', `${g3Ref}...${masterRef}`, '--numstat'])
 | ||
|             .stdout
 | ||
|             // Remove the extra space after git's output.
 | ||
|             .trim()
 | ||
|             // Split each line of git output into array
 | ||
|             .split('\n')
 | ||
|             // Split each line from the git output into components parts: insertions,
 | ||
|             // deletions and file name respectively
 | ||
|             .map(line => line.trim().split('\t'))
 | ||
|             // Parse number value from the insertions and deletions values
 | ||
|             // Example raw line input:
 | ||
|             //   10\t5\tsrc/file/name.ts
 | ||
|             .map(line => [Number(line[0]), Number(line[1]), line[2]])
 | ||
|             // Add each line's value to the diff stats, and conditionally to the g3
 | ||
|             // stats as well if the file name is included in the files synced to g3.
 | ||
|             .forEach(([insertions, deletions, fileName]) => {
 | ||
|             if (this.checkMatchAgainstIncludeAndExclude(fileName, includeFiles, excludeFiles)) {
 | ||
|                 stats.insertions += insertions;
 | ||
|                 stats.deletions += deletions;
 | ||
|                 stats.files += 1;
 | ||
|             }
 | ||
|         });
 | ||
|         return stats;
 | ||
|     }
 | ||
|     /** Determine whether the file name passes both include and exclude checks. */
 | ||
|     checkMatchAgainstIncludeAndExclude(file, includes, excludes) {
 | ||
|         return (multimatch.call(undefined, file, includes).length >= 1 &&
 | ||
|             multimatch.call(undefined, file, excludes).length === 0);
 | ||
|     }
 | ||
|     getG3FileIncludeAndExcludeLists() {
 | ||
|         var _a, _b, _c, _d;
 | ||
|         const angularRobotFilePath = path.join(getRepoBaseDir(), '.github/angular-robot.yml');
 | ||
|         if (!fs.existsSync(angularRobotFilePath)) {
 | ||
|             debug('No angular robot configuration file exists, skipping.');
 | ||
|             return null;
 | ||
|         }
 | ||
|         /** The configuration defined for the angular robot. */
 | ||
|         const robotConfig = yaml.parse(fs.readFileSync(angularRobotFilePath).toString());
 | ||
|         /** The files to be included in the g3 sync. */
 | ||
|         const include = ((_b = (_a = robotConfig === null || robotConfig === void 0 ? void 0 : robotConfig.merge) === null || _a === void 0 ? void 0 : _a.g3Status) === null || _b === void 0 ? void 0 : _b.include) || [];
 | ||
|         /** The files to be expected in the g3 sync. */
 | ||
|         const exclude = ((_d = (_c = robotConfig === null || robotConfig === void 0 ? void 0 : robotConfig.merge) === null || _c === void 0 ? void 0 : _c.g3Status) === null || _d === void 0 ? void 0 : _d.exclude) || [];
 | ||
|         if (include.length === 0 && exclude.length === 0) {
 | ||
|             debug('No g3Status include or exclude lists are defined in the angular robot configuration');
 | ||
|             return null;
 | ||
|         }
 | ||
|         return { include, exclude };
 | ||
|     }
 | ||
|     getLatestShas() {
 | ||
|         /** The latest sha for the g3 branch. */
 | ||
|         const g3 = this.getShaForBranchLatest('g3');
 | ||
|         /** The latest sha for the master branch. */
 | ||
|         const master = this.getShaForBranchLatest('master');
 | ||
|         if (g3 === null || master === null) {
 | ||
|             debug('Either the g3 or master was unable to be retrieved');
 | ||
|             return null;
 | ||
|         }
 | ||
|         return { g3, master };
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** The fragment for a result from Github's api for a Github query. */
 | ||
| const GithubQueryResultFragment = {
 | ||
|     issueCount: typedGraphqlify.types.number,
 | ||
|     nodes: [Object.assign({}, typedGraphqlify.onUnion({
 | ||
|             PullRequest: {
 | ||
|                 url: typedGraphqlify.types.string,
 | ||
|             },
 | ||
|             Issue: {
 | ||
|                 url: typedGraphqlify.types.string,
 | ||
|             },
 | ||
|         }))],
 | ||
| };
 | ||
| /**
 | ||
|  * Cap the returned issues in the queries to an arbitrary 20. At that point, caretaker has a lot
 | ||
|  * of work to do and showing more than that isn't really useful.
 | ||
|  */
 | ||
| const MAX_RETURNED_ISSUES = 20;
 | ||
| class GithubQueriesModule extends BaseModule {
 | ||
|     retrieveData() {
 | ||
|         var _a;
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             // Non-null assertion is used here as the check for undefined immediately follows to confirm the
 | ||
|             // assertion.  Typescript's type filtering does not seem to work as needed to understand
 | ||
|             // whether githubQueries is undefined or not.
 | ||
|             let queries = (_a = this.config.caretaker) === null || _a === void 0 ? void 0 : _a.githubQueries;
 | ||
|             if (queries === undefined || queries.length === 0) {
 | ||
|                 debug('No github queries defined in the configuration, skipping');
 | ||
|                 return;
 | ||
|             }
 | ||
|             /** The results of the generated github query. */
 | ||
|             const queryResult = yield this.git.github.graphql.query(this.buildGraphqlQuery(queries));
 | ||
|             const results = Object.values(queryResult);
 | ||
|             const { owner, name: repo } = this.git.remoteConfig;
 | ||
|             return results.map((result, i) => {
 | ||
|                 return {
 | ||
|                     queryName: queries[i].name,
 | ||
|                     count: result.issueCount,
 | ||
|                     queryUrl: encodeURI(`https://github.com/${owner}/${repo}/issues?q=${queries[i].query}`),
 | ||
|                     matchedUrls: result.nodes.map(node => node.url)
 | ||
|                 };
 | ||
|             });
 | ||
|         });
 | ||
|     }
 | ||
|     /** Build a Graphql query statement for the provided queries. */
 | ||
|     buildGraphqlQuery(queries) {
 | ||
|         /** The query object for graphql. */
 | ||
|         const graphQlQuery = {};
 | ||
|         const { owner, name: repo } = this.git.remoteConfig;
 | ||
|         /** The Github search filter for the configured repository. */
 | ||
|         const repoFilter = `repo:${owner}/${repo}`;
 | ||
|         queries.forEach(({ name, query }) => {
 | ||
|             /** The name of the query, with spaces removed to match GraphQL requirements. */
 | ||
|             const queryKey = typedGraphqlify.alias(name.replace(/ /g, ''), 'search');
 | ||
|             graphQlQuery[queryKey] = typedGraphqlify.params({
 | ||
|                 type: 'ISSUE',
 | ||
|                 first: MAX_RETURNED_ISSUES,
 | ||
|                 query: `"${repoFilter} ${query.replace(/"/g, '\\"')}"`,
 | ||
|             }, Object.assign({}, GithubQueryResultFragment));
 | ||
|         });
 | ||
|         return graphQlQuery;
 | ||
|     }
 | ||
|     printToTerminal() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const queryResults = yield this.data;
 | ||
|             if (!queryResults) {
 | ||
|                 return;
 | ||
|             }
 | ||
|             info.group(bold('Github Tasks'));
 | ||
|             const minQueryNameLength = Math.max(...queryResults.map(result => result.queryName.length));
 | ||
|             for (const queryResult of queryResults) {
 | ||
|                 info(`${queryResult.queryName.padEnd(minQueryNameLength)}  ${queryResult.count}`);
 | ||
|                 if (queryResult.count > 0) {
 | ||
|                     info.group(queryResult.queryUrl);
 | ||
|                     queryResult.matchedUrls.forEach(url => info(`- ${url}`));
 | ||
|                     if (queryResult.count > MAX_RETURNED_ISSUES) {
 | ||
|                         info(`... ${queryResult.count - MAX_RETURNED_ISSUES} additional matches`);
 | ||
|                     }
 | ||
|                     info.groupEnd();
 | ||
|                 }
 | ||
|             }
 | ||
|             info.groupEnd();
 | ||
|             info();
 | ||
|         });
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** List of services Angular relies on. */
 | ||
| const services = [
 | ||
|     {
 | ||
|         url: 'https://status.us-west-1.saucelabs.com/api/v2/status.json',
 | ||
|         name: 'Saucelabs',
 | ||
|     },
 | ||
|     {
 | ||
|         url: 'https://status.npmjs.org/api/v2/status.json',
 | ||
|         name: 'Npm',
 | ||
|     },
 | ||
|     {
 | ||
|         url: 'https://status.circleci.com/api/v2/status.json',
 | ||
|         name: 'CircleCi',
 | ||
|     },
 | ||
|     {
 | ||
|         url: 'https://www.githubstatus.com/api/v2/status.json',
 | ||
|         name: 'Github',
 | ||
|     },
 | ||
| ];
 | ||
| class ServicesModule extends BaseModule {
 | ||
|     retrieveData() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             return Promise.all(services.map(service => this.getStatusFromStandardApi(service)));
 | ||
|         });
 | ||
|     }
 | ||
|     printToTerminal() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const statuses = yield this.data;
 | ||
|             const serviceNameMinLength = Math.max(...statuses.map(service => service.name.length));
 | ||
|             info.group(bold('Service Statuses'));
 | ||
|             for (const status of statuses) {
 | ||
|                 const name = status.name.padEnd(serviceNameMinLength);
 | ||
|                 if (status.status === 'passing') {
 | ||
|                     info(`${name} ✅`);
 | ||
|                 }
 | ||
|                 else {
 | ||
|                     info.group(`${name} ❌ (Updated: ${status.lastUpdated.toLocaleString()})`);
 | ||
|                     info(`  Details: ${status.description}`);
 | ||
|                     info.groupEnd();
 | ||
|                 }
 | ||
|             }
 | ||
|             info.groupEnd();
 | ||
|             info();
 | ||
|         });
 | ||
|     }
 | ||
|     /** Retrieve the status information for a service which uses a standard API response. */
 | ||
|     getStatusFromStandardApi(service) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const result = yield fetch(service.url).then(result => result.json());
 | ||
|             const status = result.status.indicator === 'none' ? 'passing' : 'failing';
 | ||
|             return {
 | ||
|                 name: service.name,
 | ||
|                 status,
 | ||
|                 description: result.status.description,
 | ||
|                 lastUpdated: new Date(result.page.updated_at)
 | ||
|             };
 | ||
|         });
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** List of modules checked for the caretaker check command. */
 | ||
| const moduleList = [
 | ||
|     GithubQueriesModule,
 | ||
|     ServicesModule,
 | ||
|     CiModule,
 | ||
|     G3Module,
 | ||
| ];
 | ||
| /** Check the status of services which Angular caretakers need to monitor. */
 | ||
| function checkServiceStatuses(githubToken) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         /** The configuration for the caretaker commands. */
 | ||
|         const config = getCaretakerConfig();
 | ||
|         /** The GitClient for interacting with git and Github. */
 | ||
|         const git = new GitClient(githubToken, config);
 | ||
|         // Prevent logging of the git commands being executed during the check.
 | ||
|         GitClient.LOG_COMMANDS = false;
 | ||
|         /** List of instances of Caretaker Check modules */
 | ||
|         const caretakerCheckModules = moduleList.map(module => new module(git, config));
 | ||
|         // Module's `data` is casted as Promise<unknown> because the data types of the `module`'s `data`
 | ||
|         // promises do not match typings, however our usage here is only to determine when the promise
 | ||
|         // resolves.
 | ||
|         yield Promise.all(caretakerCheckModules.map(module => module.data));
 | ||
|         for (const module of caretakerCheckModules) {
 | ||
|             yield module.printToTerminal();
 | ||
|         }
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Builds the command. */
 | ||
| function builder(yargs) {
 | ||
|     return addGithubTokenOption(yargs);
 | ||
| }
 | ||
| /** Handles the command. */
 | ||
| function handler({ githubToken }) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         yield checkServiceStatuses(githubToken);
 | ||
|     });
 | ||
| }
 | ||
| /** yargs command module for checking status information for the repository  */
 | ||
| const CheckModule = {
 | ||
|     handler,
 | ||
|     builder,
 | ||
|     command: 'check',
 | ||
|     describe: 'Check the status of information the caretaker manages for the repository',
 | ||
| };
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Build the parser for the caretaker commands. */
 | ||
| function buildCaretakerParser(yargs) {
 | ||
|     return yargs.command(CheckModule);
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Load the commit message draft from the file system if it exists. */
 | ||
| function loadCommitMessageDraft(basePath) {
 | ||
|     const commitMessageDraftPath = `${basePath}.ngDevSave`;
 | ||
|     if (fs.existsSync(commitMessageDraftPath)) {
 | ||
|         return fs.readFileSync(commitMessageDraftPath).toString();
 | ||
|     }
 | ||
|     return '';
 | ||
| }
 | ||
| /** Remove the commit message draft from the file system. */
 | ||
| function deleteCommitMessageDraft(basePath) {
 | ||
|     const commitMessageDraftPath = `${basePath}.ngDevSave`;
 | ||
|     if (fs.existsSync(commitMessageDraftPath)) {
 | ||
|         fs.unlinkSync(commitMessageDraftPath);
 | ||
|     }
 | ||
| }
 | ||
| /** Save the commit message draft to the file system for later retrieval. */
 | ||
| function saveCommitMessageDraft(basePath, commitMessage) {
 | ||
|     fs.writeFileSync(`${basePath}.ngDevSave`, commitMessage);
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Restore the commit message draft to the git to be used as the default commit message.
 | ||
|  *
 | ||
|  * The source provided may be one of the sources described in
 | ||
|  *   https://git-scm.com/docs/githooks#_prepare_commit_msg
 | ||
|  */
 | ||
| function restoreCommitMessage(filePath, source) {
 | ||
|     if (!!source) {
 | ||
|         log('Skipping commit message restoration attempt');
 | ||
|         if (source === 'message') {
 | ||
|             debug('A commit message was already provided via the command with a -m or -F flag');
 | ||
|         }
 | ||
|         if (source === 'template') {
 | ||
|             debug('A commit message was already provided via the -t flag or config.template setting');
 | ||
|         }
 | ||
|         if (source === 'squash') {
 | ||
|             debug('A commit message was already provided as a merge action or via .git/MERGE_MSG');
 | ||
|         }
 | ||
|         if (source === 'commit') {
 | ||
|             debug('A commit message was already provided through a revision specified via --fixup, -c,');
 | ||
|             debug('-C or --amend flag');
 | ||
|         }
 | ||
|         process.exit(0);
 | ||
|     }
 | ||
|     /** A draft of a commit message. */
 | ||
|     const commitMessage = loadCommitMessageDraft(filePath);
 | ||
|     // If the commit message draft has content, restore it into the provided filepath.
 | ||
|     if (commitMessage) {
 | ||
|         fs.writeFileSync(filePath, commitMessage);
 | ||
|     }
 | ||
|     // Exit the process
 | ||
|     process.exit(0);
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Builds the command. */
 | ||
| function builder$1(yargs) {
 | ||
|     return yargs
 | ||
|         .option('file-env-variable', {
 | ||
|         type: 'string',
 | ||
|         description: 'The key for the environment variable which holds the arguments for the\n' +
 | ||
|             'prepare-commit-msg hook as described here:\n' +
 | ||
|             'https://git-scm.com/docs/githooks#_prepare_commit_msg'
 | ||
|     })
 | ||
|         .positional('file', { type: 'string' })
 | ||
|         .positional('source', { type: 'string' });
 | ||
| }
 | ||
| /** Handles the command. */
 | ||
| function handler$1({ fileEnvVariable, file, source }) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         // File and source are provided as command line parameters
 | ||
|         if (file !== undefined) {
 | ||
|             restoreCommitMessage(file, source);
 | ||
|             return;
 | ||
|         }
 | ||
|         // File and source are provided as values held in an environment variable.
 | ||
|         if (fileEnvVariable !== undefined) {
 | ||
|             const [fileFromEnv, sourceFromEnv] = (process.env[fileEnvVariable] || '').split(' ');
 | ||
|             if (!fileFromEnv) {
 | ||
|                 throw new Error(`Provided environment variable "${fileEnvVariable}" was not found.`);
 | ||
|             }
 | ||
|             restoreCommitMessage(fileFromEnv, sourceFromEnv);
 | ||
|             return;
 | ||
|         }
 | ||
|         throw new Error('No file path and commit message source provide.  Provide values via positional command ' +
 | ||
|             'arguments, or via the --file-env-variable flag');
 | ||
|     });
 | ||
| }
 | ||
| /** yargs command module describing the command.  */
 | ||
| const RestoreCommitMessageModule = {
 | ||
|     handler: handler$1,
 | ||
|     builder: builder$1,
 | ||
|     command: 'restore-commit-message-draft [file] [source]',
 | ||
|     // Description: Restore a commit message draft if one has been saved from a failed commit attempt.
 | ||
|     // No describe is defiend to hide the command from the --help.
 | ||
|     describe: false,
 | ||
| };
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Retrieve and validate the config as `CommitMessageConfig`. */
 | ||
| function getCommitMessageConfig() {
 | ||
|     // List of errors encountered validating the config.
 | ||
|     const errors = [];
 | ||
|     // The non-validated config object.
 | ||
|     const config = getConfig();
 | ||
|     if (config.commitMessage === undefined) {
 | ||
|         errors.push(`No configuration defined for "commitMessage"`);
 | ||
|     }
 | ||
|     assertNoErrors(errors);
 | ||
|     return config;
 | ||
| }
 | ||
| /** Scope requirement level to be set for each commit type.  */
 | ||
| var ScopeRequirement;
 | ||
| (function (ScopeRequirement) {
 | ||
|     ScopeRequirement[ScopeRequirement["Required"] = 0] = "Required";
 | ||
|     ScopeRequirement[ScopeRequirement["Optional"] = 1] = "Optional";
 | ||
|     ScopeRequirement[ScopeRequirement["Forbidden"] = 2] = "Forbidden";
 | ||
| })(ScopeRequirement || (ScopeRequirement = {}));
 | ||
| /** The valid commit types for Angular commit messages. */
 | ||
| const COMMIT_TYPES = {
 | ||
|     build: {
 | ||
|         name: 'build',
 | ||
|         description: 'Changes to local repository build system and tooling',
 | ||
|         scope: ScopeRequirement.Optional,
 | ||
|     },
 | ||
|     ci: {
 | ||
|         name: 'ci',
 | ||
|         description: 'Changes to CI configuration and CI specific tooling',
 | ||
|         scope: ScopeRequirement.Forbidden,
 | ||
|     },
 | ||
|     docs: {
 | ||
|         name: 'docs',
 | ||
|         description: 'Changes which exclusively affects documentation.',
 | ||
|         scope: ScopeRequirement.Optional,
 | ||
|     },
 | ||
|     feat: {
 | ||
|         name: 'feat',
 | ||
|         description: 'Creates a new feature',
 | ||
|         scope: ScopeRequirement.Required,
 | ||
|     },
 | ||
|     fix: {
 | ||
|         name: 'fix',
 | ||
|         description: 'Fixes a previously discovered failure/bug',
 | ||
|         scope: ScopeRequirement.Required,
 | ||
|     },
 | ||
|     perf: {
 | ||
|         name: 'perf',
 | ||
|         description: 'Improves performance without any change in functionality or API',
 | ||
|         scope: ScopeRequirement.Required,
 | ||
|     },
 | ||
|     refactor: {
 | ||
|         name: 'refactor',
 | ||
|         description: 'Refactor without any change in functionality or API (includes style changes)',
 | ||
|         scope: ScopeRequirement.Required,
 | ||
|     },
 | ||
|     release: {
 | ||
|         name: 'release',
 | ||
|         description: 'A release point in the repository',
 | ||
|         scope: ScopeRequirement.Forbidden,
 | ||
|     },
 | ||
|     test: {
 | ||
|         name: 'test',
 | ||
|         description: 'Improvements or corrections made to the project\'s test suite',
 | ||
|         scope: ScopeRequirement.Required,
 | ||
|     },
 | ||
| };
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Regex determining if a commit is a fixup. */
 | ||
| const FIXUP_PREFIX_RE = /^fixup! /i;
 | ||
| /** Regex finding all github keyword links. */
 | ||
| const GITHUB_LINKING_RE = /((closed?s?)|(fix(es)?(ed)?)|(resolved?s?))\s\#(\d+)/ig;
 | ||
| /** Regex determining if a commit is a squash. */
 | ||
| const SQUASH_PREFIX_RE = /^squash! /i;
 | ||
| /** Regex determining if a commit is a revert. */
 | ||
| const REVERT_PREFIX_RE = /^revert:? /i;
 | ||
| /** Regex determining the scope of a commit if provided. */
 | ||
| const TYPE_SCOPE_RE = /^(\w+)(?:\(([^)]+)\))?\:\s(.+)$/;
 | ||
| /** Regex determining the entire header line of the commit. */
 | ||
| const COMMIT_HEADER_RE = /^(.*)/i;
 | ||
| /** Regex determining the body of the commit. */
 | ||
| const COMMIT_BODY_RE = /^.*\n\n([\s\S]*)$/;
 | ||
| /** Parse a full commit message into its composite parts. */
 | ||
| function parseCommitMessage(commitMsg) {
 | ||
|     // Ignore comments (i.e. lines starting with `#`). Comments are automatically removed by git and
 | ||
|     // should not be considered part of the final commit message.
 | ||
|     commitMsg = commitMsg.split('\n').filter(line => !line.startsWith('#')).join('\n');
 | ||
|     let header = '';
 | ||
|     let body = '';
 | ||
|     let bodyWithoutLinking = '';
 | ||
|     let type = '';
 | ||
|     let scope = '';
 | ||
|     let subject = '';
 | ||
|     if (COMMIT_HEADER_RE.test(commitMsg)) {
 | ||
|         header = COMMIT_HEADER_RE.exec(commitMsg)[1]
 | ||
|             .replace(FIXUP_PREFIX_RE, '')
 | ||
|             .replace(SQUASH_PREFIX_RE, '');
 | ||
|     }
 | ||
|     if (COMMIT_BODY_RE.test(commitMsg)) {
 | ||
|         body = COMMIT_BODY_RE.exec(commitMsg)[1];
 | ||
|         bodyWithoutLinking = body.replace(GITHUB_LINKING_RE, '');
 | ||
|     }
 | ||
|     if (TYPE_SCOPE_RE.test(header)) {
 | ||
|         const parsedCommitHeader = TYPE_SCOPE_RE.exec(header);
 | ||
|         type = parsedCommitHeader[1];
 | ||
|         scope = parsedCommitHeader[2];
 | ||
|         subject = parsedCommitHeader[3];
 | ||
|     }
 | ||
|     return {
 | ||
|         header,
 | ||
|         body,
 | ||
|         bodyWithoutLinking,
 | ||
|         type,
 | ||
|         scope,
 | ||
|         subject,
 | ||
|         isFixup: FIXUP_PREFIX_RE.test(commitMsg),
 | ||
|         isSquash: SQUASH_PREFIX_RE.test(commitMsg),
 | ||
|         isRevert: REVERT_PREFIX_RE.test(commitMsg),
 | ||
|     };
 | ||
| }
 | ||
| /** Retrieve and parse each commit message in a provide range. */
 | ||
| function parseCommitMessagesForRange(range) {
 | ||
|     /** A random number used as a split point in the git log result. */
 | ||
|     const randomValueSeparator = `${Math.random()}`;
 | ||
|     /**
 | ||
|      * Custom git log format that provides the commit header and body, separated as expected with the
 | ||
|      * custom separator as the trailing value.
 | ||
|      */
 | ||
|     const gitLogFormat = `%s%n%n%b${randomValueSeparator}`;
 | ||
|     // Retrieve the commits in the provided range.
 | ||
|     const result = exec(`git log --reverse --format=${gitLogFormat} ${range}`);
 | ||
|     if (result.code) {
 | ||
|         throw new Error(`Failed to get all commits in the range:\n  ${result.stderr}`);
 | ||
|     }
 | ||
|     return result
 | ||
|         // Separate the commits from a single string into individual commits.
 | ||
|         .split(randomValueSeparator)
 | ||
|         // Remove extra space before and after each commit message.
 | ||
|         .map(l => l.trim())
 | ||
|         // Remove any superfluous lines which remain from the split.
 | ||
|         .filter(line => !!line)
 | ||
|         // Parse each commit message.
 | ||
|         .map(commit => parseCommitMessage(commit));
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Regex matching a URL for an entire commit body line. */
 | ||
| const COMMIT_BODY_URL_LINE_RE = /^https?:\/\/.*$/;
 | ||
| /** Validate a commit message against using the local repo's config. */
 | ||
| function validateCommitMessage(commitMsg, options = {}) {
 | ||
|     const config = getCommitMessageConfig().commitMessage;
 | ||
|     const commit = typeof commitMsg === 'string' ? parseCommitMessage(commitMsg) : commitMsg;
 | ||
|     const errors = [];
 | ||
|     /** Perform the validation checks against the parsed commit. */
 | ||
|     function validateCommitAndCollectErrors() {
 | ||
|         // TODO(josephperrott): Remove early return calls when commit message errors are found
 | ||
|         var _a;
 | ||
|         ////////////////////////////////////
 | ||
|         // Checking revert, squash, fixup //
 | ||
|         ////////////////////////////////////
 | ||
|         // All revert commits are considered valid.
 | ||
|         if (commit.isRevert) {
 | ||
|             return true;
 | ||
|         }
 | ||
|         // All squashes are considered valid, as the commit will be squashed into another in
 | ||
|         // the git history anyway, unless the options provided to not allow squash commits.
 | ||
|         if (commit.isSquash) {
 | ||
|             if (options.disallowSquash) {
 | ||
|                 errors.push('The commit must be manually squashed into the target commit');
 | ||
|                 return false;
 | ||
|             }
 | ||
|             return true;
 | ||
|         }
 | ||
|         // Fixups commits are considered valid, unless nonFixupCommitHeaders are provided to check
 | ||
|         // against. If `nonFixupCommitHeaders` is not empty, we check whether there is a corresponding
 | ||
|         // non-fixup commit (i.e. a commit whose header is identical to this commit's header after
 | ||
|         // stripping the `fixup! ` prefix), otherwise we assume this verification will happen in another
 | ||
|         // check.
 | ||
|         if (commit.isFixup) {
 | ||
|             if (options.nonFixupCommitHeaders && !options.nonFixupCommitHeaders.includes(commit.header)) {
 | ||
|                 errors.push('Unable to find match for fixup commit among prior commits: ' +
 | ||
|                     (options.nonFixupCommitHeaders.map(x => `\n      ${x}`).join('') || '-'));
 | ||
|                 return false;
 | ||
|             }
 | ||
|             return true;
 | ||
|         }
 | ||
|         ////////////////////////////
 | ||
|         // Checking commit header //
 | ||
|         ////////////////////////////
 | ||
|         if (commit.header.length > config.maxLineLength) {
 | ||
|             errors.push(`The commit message header is longer than ${config.maxLineLength} characters`);
 | ||
|             return false;
 | ||
|         }
 | ||
|         if (!commit.type) {
 | ||
|             errors.push(`The commit message header does not match the expected format.`);
 | ||
|             return false;
 | ||
|         }
 | ||
|         if (COMMIT_TYPES[commit.type] === undefined) {
 | ||
|             errors.push(`'${commit.type}' is not an allowed type.\n => TYPES: ${Object.keys(COMMIT_TYPES).join(', ')}`);
 | ||
|             return false;
 | ||
|         }
 | ||
|         /** The scope requirement level for the provided type of the commit message. */
 | ||
|         const scopeRequirementForType = COMMIT_TYPES[commit.type].scope;
 | ||
|         if (scopeRequirementForType === ScopeRequirement.Forbidden && commit.scope) {
 | ||
|             errors.push(`Scopes are forbidden for commits with type '${commit.type}', but a scope of '${commit.scope}' was provided.`);
 | ||
|             return false;
 | ||
|         }
 | ||
|         if (scopeRequirementForType === ScopeRequirement.Required && !commit.scope) {
 | ||
|             errors.push(`Scopes are required for commits with type '${commit.type}', but no scope was provided.`);
 | ||
|             return false;
 | ||
|         }
 | ||
|         if (commit.scope && !config.scopes.includes(commit.scope)) {
 | ||
|             errors.push(`'${commit.scope}' is not an allowed scope.\n => SCOPES: ${config.scopes.join(', ')}`);
 | ||
|             return false;
 | ||
|         }
 | ||
|         // Commits with the type of `release` do not require a commit body.
 | ||
|         if (commit.type === 'release') {
 | ||
|             return true;
 | ||
|         }
 | ||
|         //////////////////////////
 | ||
|         // Checking commit body //
 | ||
|         //////////////////////////
 | ||
|         if (!((_a = config.minBodyLengthTypeExcludes) === null || _a === void 0 ? void 0 : _a.includes(commit.type)) &&
 | ||
|             commit.bodyWithoutLinking.trim().length < config.minBodyLength) {
 | ||
|             errors.push(`The commit message body does not meet the minimum length of ${config.minBodyLength} characters`);
 | ||
|             return false;
 | ||
|         }
 | ||
|         const bodyByLine = commit.body.split('\n');
 | ||
|         const lineExceedsMaxLength = bodyByLine.some(line => {
 | ||
|             // Check if any line exceeds the max line length limit. The limit is ignored for
 | ||
|             // lines that just contain an URL (as these usually cannot be wrapped or shortened).
 | ||
|             return line.length > config.maxLineLength && !COMMIT_BODY_URL_LINE_RE.test(line);
 | ||
|         });
 | ||
|         if (lineExceedsMaxLength) {
 | ||
|             errors.push(`The commit message body contains lines greater than ${config.maxLineLength} characters`);
 | ||
|             return false;
 | ||
|         }
 | ||
|         return true;
 | ||
|     }
 | ||
|     return { valid: validateCommitAndCollectErrors(), errors, commit };
 | ||
| }
 | ||
| /** Print the error messages from the commit message validation to the console. */
 | ||
| function printValidationErrors(errors, print = error) {
 | ||
|     print.group(`Error${errors.length === 1 ? '' : 's'}:`);
 | ||
|     errors.forEach(line => print(line));
 | ||
|     print.groupEnd();
 | ||
|     print();
 | ||
|     print('The expected format for a commit is: ');
 | ||
|     print('<type>(<scope>): <summary>');
 | ||
|     print();
 | ||
|     print('<body>');
 | ||
|     print();
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Validate commit message at the provided file path. */
 | ||
| function validateFile(filePath, isErrorMode) {
 | ||
|     const commitMessage = fs.readFileSync(path.resolve(getRepoBaseDir(), filePath), 'utf8');
 | ||
|     const { valid, errors } = validateCommitMessage(commitMessage);
 | ||
|     if (valid) {
 | ||
|         info(`${green('√')}  Valid commit message`);
 | ||
|         deleteCommitMessageDraft(filePath);
 | ||
|         process.exitCode = 0;
 | ||
|         return;
 | ||
|     }
 | ||
|     /** Function used to print to the console log. */
 | ||
|     let printFn = isErrorMode ? error : log;
 | ||
|     printFn(`${isErrorMode ? red('✘') : yellow('!')}  Invalid commit message`);
 | ||
|     printValidationErrors(errors, printFn);
 | ||
|     if (isErrorMode) {
 | ||
|         printFn(red('Aborting commit attempt due to invalid commit message.'));
 | ||
|         printFn(red('Commit message aborted as failure rather than warning due to local configuration.'));
 | ||
|     }
 | ||
|     else {
 | ||
|         printFn(yellow('Before this commit can be merged into the upstream repository, it must be'));
 | ||
|         printFn(yellow('amended to follow commit message guidelines.'));
 | ||
|     }
 | ||
|     // On all invalid commit messages, the commit message should be saved as a draft to be
 | ||
|     // restored on the next commit attempt.
 | ||
|     saveCommitMessageDraft(filePath, commitMessage);
 | ||
|     // Set the correct exit code based on if invalid commit message is an error.
 | ||
|     process.exitCode = isErrorMode ? 1 : 0;
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Builds the command. */
 | ||
| function builder$2(yargs) {
 | ||
|     var _a;
 | ||
|     return yargs
 | ||
|         .option('file', {
 | ||
|         type: 'string',
 | ||
|         conflicts: ['file-env-variable'],
 | ||
|         description: 'The path of the commit message file.',
 | ||
|     })
 | ||
|         .option('file-env-variable', {
 | ||
|         type: 'string',
 | ||
|         conflicts: ['file'],
 | ||
|         description: 'The key of the environment variable for the path of the commit message file.',
 | ||
|         coerce: (arg) => {
 | ||
|             const file = process.env[arg];
 | ||
|             if (!file) {
 | ||
|                 throw new Error(`Provided environment variable "${arg}" was not found.`);
 | ||
|             }
 | ||
|             return file;
 | ||
|         },
 | ||
|     })
 | ||
|         .option('error', {
 | ||
|         type: 'boolean',
 | ||
|         description: 'Whether invalid commit messages should be treated as failures rather than a warning',
 | ||
|         default: !!((_a = getUserConfig().commitMessage) === null || _a === void 0 ? void 0 : _a.errorOnInvalidMessage) || !!process.env['CI']
 | ||
|     });
 | ||
| }
 | ||
| /** Handles the command. */
 | ||
| function handler$2({ error, file, fileEnvVariable }) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const filePath = file || fileEnvVariable || '.git/COMMIT_EDITMSG';
 | ||
|         validateFile(filePath, error);
 | ||
|     });
 | ||
| }
 | ||
| /** yargs command module describing the command.  */
 | ||
| const ValidateFileModule = {
 | ||
|     handler: handler$2,
 | ||
|     builder: builder$2,
 | ||
|     command: 'pre-commit-validate',
 | ||
|     describe: 'Validate the most recent commit message',
 | ||
| };
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| // Whether the provided commit is a fixup commit.
 | ||
| const isNonFixup = (commit) => !commit.isFixup;
 | ||
| // Extracts commit header (first line of commit message).
 | ||
| const extractCommitHeader = (commit) => commit.header;
 | ||
| /** Validate all commits in a provided git commit range. */
 | ||
| function validateCommitRange(range) {
 | ||
|     /** A list of tuples of the commit header string and a list of error messages for the commit. */
 | ||
|     const errors = [];
 | ||
|     /** A list of parsed commit messages from the range. */
 | ||
|     const commits = parseCommitMessagesForRange(range);
 | ||
|     info(`Examining ${commits.length} commit(s) in the provided range: ${range}`);
 | ||
|     /**
 | ||
|      * Whether all commits in the range are valid, commits are allowed to be fixup commits for other
 | ||
|      * commits in the provided commit range.
 | ||
|      */
 | ||
|     const allCommitsInRangeValid = commits.every((commit, i) => {
 | ||
|         const options = {
 | ||
|             disallowSquash: true,
 | ||
|             nonFixupCommitHeaders: isNonFixup(commit) ?
 | ||
|                 undefined :
 | ||
|                 commits.slice(0, i).filter(isNonFixup).map(extractCommitHeader)
 | ||
|         };
 | ||
|         const { valid, errors: localErrors } = validateCommitMessage(commit, options);
 | ||
|         if (localErrors.length) {
 | ||
|             errors.push([commit.header, localErrors]);
 | ||
|         }
 | ||
|         return valid;
 | ||
|     });
 | ||
|     if (allCommitsInRangeValid) {
 | ||
|         info('√  All commit messages in range valid.');
 | ||
|     }
 | ||
|     else {
 | ||
|         error('✘  Invalid commit message');
 | ||
|         errors.forEach(([header, validationErrors]) => {
 | ||
|             error.group(header);
 | ||
|             printValidationErrors(validationErrors);
 | ||
|             error.groupEnd();
 | ||
|         });
 | ||
|         // Exit with a non-zero exit code if invalid commit messages have
 | ||
|         // been discovered.
 | ||
|         process.exit(1);
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Builds the command. */
 | ||
| function builder$3(yargs) {
 | ||
|     return yargs.option('range', {
 | ||
|         description: 'The range of commits to check, e.g. --range abc123..xyz456',
 | ||
|         demandOption: '  A range must be provided, e.g. --range abc123..xyz456',
 | ||
|         type: 'string',
 | ||
|         requiresArg: true,
 | ||
|     });
 | ||
| }
 | ||
| /** Handles the command. */
 | ||
| function handler$3({ range }) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         // If on CI, and no pull request number is provided, assume the branch
 | ||
|         // being run on is an upstream branch.
 | ||
|         if (process.env['CI'] && process.env['CI_PULL_REQUEST'] === 'false') {
 | ||
|             info(`Since valid commit messages are enforced by PR linting on CI, we do not`);
 | ||
|             info(`need to validate commit messages on CI runs on upstream branches.`);
 | ||
|             info();
 | ||
|             info(`Skipping check of provided commit range`);
 | ||
|             return;
 | ||
|         }
 | ||
|         validateCommitRange(range);
 | ||
|     });
 | ||
| }
 | ||
| /** yargs command module describing the command.  */
 | ||
| const ValidateRangeModule = {
 | ||
|     handler: handler$3,
 | ||
|     builder: builder$3,
 | ||
|     command: 'validate-range',
 | ||
|     describe: 'Validate a range of commit messages',
 | ||
| };
 | ||
| 
 | ||
| /** Validate commit message at the provided file path. */
 | ||
| function buildCommitMessage() {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         // TODO(josephperrott): Add support for skipping wizard with local untracked config file
 | ||
|         // TODO(josephperrott): Add default commit message information/commenting into generated messages
 | ||
|         info('Just a few questions to start building the commit message!');
 | ||
|         /** The commit message type. */
 | ||
|         const type = yield promptForCommitMessageType();
 | ||
|         /** The commit message scope. */
 | ||
|         const scope = yield promptForCommitMessageScopeForType(type);
 | ||
|         /** The commit message summary. */
 | ||
|         const summary = yield promptForCommitMessageSummary();
 | ||
|         return `${type.name}${scope ? '(' + scope + ')' : ''}: ${summary}\n\n`;
 | ||
|     });
 | ||
| }
 | ||
| /** Prompts in the terminal for the commit message's type. */
 | ||
| function promptForCommitMessageType() {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         info('The type of change in the commit. Allows a reader to know the effect of the change,');
 | ||
|         info('whether it brings a new feature, adds additional testing, documents the `project, etc.');
 | ||
|         /** List of commit type options for the autocomplete prompt. */
 | ||
|         const typeOptions = Object.values(COMMIT_TYPES).map(({ description, name }) => {
 | ||
|             return {
 | ||
|                 name: `${name} - ${description}`,
 | ||
|                 value: name,
 | ||
|                 short: name,
 | ||
|             };
 | ||
|         });
 | ||
|         /** The key of a commit message type, selected by the user via prompt. */
 | ||
|         const typeName = yield promptAutocomplete('Select a type for the commit:', typeOptions);
 | ||
|         return COMMIT_TYPES[typeName];
 | ||
|     });
 | ||
| }
 | ||
| /** Prompts in the terminal for the commit message's scope. */
 | ||
| function promptForCommitMessageScopeForType(type) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         // If the commit type's scope requirement is forbidden, return early.
 | ||
|         if (type.scope === ScopeRequirement.Forbidden) {
 | ||
|             info(`Skipping scope selection as the '${type.name}' type does not allow scopes`);
 | ||
|             return false;
 | ||
|         }
 | ||
|         /** Commit message configuration */
 | ||
|         const config = getCommitMessageConfig();
 | ||
|         info('The area of the repository the changes in this commit most affects.');
 | ||
|         return yield promptAutocomplete('Select a scope for the commit:', config.commitMessage.scopes, type.scope === ScopeRequirement.Optional ? '<no scope>' : '');
 | ||
|     });
 | ||
| }
 | ||
| /** Prompts in the terminal for the commit message's summary. */
 | ||
| function promptForCommitMessageSummary() {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         info('Provide a short summary of what the changes in the commit do');
 | ||
|         return yield promptInput('Provide a short summary of the commit');
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /** The default commit message used if the wizard does not procude a commit message. */
 | ||
| const defaultCommitMessage = `<type>(<scope>): <summary>
 | ||
| 
 | ||
| # <Describe the motivation behind this change - explain WHY you are making this change. Wrap all
 | ||
| #  lines at 100 characters.>\n\n`;
 | ||
| function runWizard(args) {
 | ||
|     var _a;
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         if ((_a = getUserConfig().commitMessage) === null || _a === void 0 ? void 0 : _a.disableWizard) {
 | ||
|             debug('Skipping commit message wizard due to enabled `commitMessage.disableWizard` option in');
 | ||
|             debug('user config.');
 | ||
|             process.exitCode = 0;
 | ||
|             return;
 | ||
|         }
 | ||
|         if (args.source !== undefined) {
 | ||
|             info(`Skipping commit message wizard because the commit was created via '${args.source}' source`);
 | ||
|             process.exitCode = 0;
 | ||
|             return;
 | ||
|         }
 | ||
|         // Set the default commit message to be updated if the user cancels out of the wizard in progress
 | ||
|         fs.writeFileSync(args.filePath, defaultCommitMessage);
 | ||
|         /** The generated commit message. */
 | ||
|         const commitMessage = yield buildCommitMessage();
 | ||
|         fs.writeFileSync(args.filePath, commitMessage);
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Builds the command. */
 | ||
| function builder$4(yargs) {
 | ||
|     return yargs
 | ||
|         .positional('filePath', {
 | ||
|         description: 'The file path to write the generated commit message into',
 | ||
|         type: 'string',
 | ||
|         demandOption: true,
 | ||
|     })
 | ||
|         .positional('source', {
 | ||
|         choices: ['message', 'template', 'merge', 'squash', 'commit'],
 | ||
|         description: 'The source of the commit message as described here: ' +
 | ||
|             'https://git-scm.com/docs/githooks#_prepare_commit_msg'
 | ||
|     })
 | ||
|         .positional('commitSha', {
 | ||
|         description: 'The commit sha if source is set to `commit`',
 | ||
|         type: 'string',
 | ||
|     });
 | ||
| }
 | ||
| /** Handles the command. */
 | ||
| function handler$4(args) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         yield runWizard(args);
 | ||
|     });
 | ||
| }
 | ||
| /** yargs command module describing the command.  */
 | ||
| const WizardModule = {
 | ||
|     handler: handler$4,
 | ||
|     builder: builder$4,
 | ||
|     command: 'wizard <filePath> [source] [commitSha]',
 | ||
|     // Description: Run the wizard to build a base commit message before opening to complete.
 | ||
|     // No describe is defiend to hide the command from the --help.
 | ||
|     describe: false,
 | ||
| };
 | ||
| 
 | ||
| /** Build the parser for the commit-message commands. */
 | ||
| function buildCommitMessageParser(localYargs) {
 | ||
|     return localYargs.help()
 | ||
|         .strict()
 | ||
|         .command(RestoreCommitMessageModule)
 | ||
|         .command(WizardModule)
 | ||
|         .command(ValidateFileModule)
 | ||
|         .command(ValidateRangeModule);
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * A list of all files currently in the repo which have been modified since the provided sha.
 | ||
|  *
 | ||
|  * git diff
 | ||
|  * Deleted files (--diff-filter=d) are not included as they are not longer present in the repo
 | ||
|  * and can not be checked anymore.
 | ||
|  *
 | ||
|  * git ls-files
 | ||
|  * Untracked files (--others), which are not matched by .gitignore (--exclude-standard)
 | ||
|  * as they are expected to become tracked files.
 | ||
|  */
 | ||
| function allChangedFilesSince(sha) {
 | ||
|     if (sha === void 0) { sha = 'HEAD'; }
 | ||
|     var diffFiles = gitOutputAsArray("git diff --name-only --diff-filter=d " + sha);
 | ||
|     var untrackedFiles = gitOutputAsArray("git ls-files --others --exclude-standard");
 | ||
|     // Use a set to deduplicate the list as its possible for a file to show up in both lists.
 | ||
|     return Array.from(new Set(tslib.__spread(diffFiles, untrackedFiles)));
 | ||
| }
 | ||
| /**
 | ||
|  * A list of all staged files which have been modified.
 | ||
|  *
 | ||
|  * Only added, created and modified files are listed as others (deleted, renamed, etc) aren't
 | ||
|  * changed or available as content to act upon.
 | ||
|  */
 | ||
| function allStagedFiles() {
 | ||
|     return gitOutputAsArray("git diff --staged --name-only --diff-filter=ACM");
 | ||
| }
 | ||
| function allFiles() {
 | ||
|     return gitOutputAsArray("git ls-files");
 | ||
| }
 | ||
| function gitOutputAsArray(cmd) {
 | ||
|     return exec(cmd, { cwd: getRepoBaseDir() }).split('\n').map(function (x) { return x.trim(); }).filter(function (x) { return !!x; });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Retrieve and validate the config as `FormatConfig`. */
 | ||
| function getFormatConfig() {
 | ||
|     // List of errors encountered validating the config.
 | ||
|     const errors = [];
 | ||
|     // The unvalidated config object.
 | ||
|     const config = getConfig();
 | ||
|     if (config.format === undefined) {
 | ||
|         errors.push(`No configuration defined for "format"`);
 | ||
|     }
 | ||
|     for (const [key, value] of Object.entries(config.format)) {
 | ||
|         switch (typeof value) {
 | ||
|             case 'boolean':
 | ||
|                 break;
 | ||
|             case 'object':
 | ||
|                 checkFormatterConfig(key, value, errors);
 | ||
|                 break;
 | ||
|             default:
 | ||
|                 errors.push(`"format.${key}" is not a boolean or Formatter object`);
 | ||
|         }
 | ||
|     }
 | ||
|     assertNoErrors(errors);
 | ||
|     return config;
 | ||
| }
 | ||
| /** Validate an individual Formatter config. */
 | ||
| function checkFormatterConfig(key, config, errors) {
 | ||
|     if (config.matchers === undefined) {
 | ||
|         errors.push(`Missing "format.${key}.matchers" value`);
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * The base class for formatters to run against provided files.
 | ||
|  */
 | ||
| class Formatter {
 | ||
|     constructor(config) {
 | ||
|         this.config = config;
 | ||
|     }
 | ||
|     /**
 | ||
|      * Retrieve the command to execute the provided action, including both the binary
 | ||
|      * and command line flags.
 | ||
|      */
 | ||
|     commandFor(action) {
 | ||
|         switch (action) {
 | ||
|             case 'check':
 | ||
|                 return `${this.binaryFilePath} ${this.actions.check.commandFlags}`;
 | ||
|             case 'format':
 | ||
|                 return `${this.binaryFilePath} ${this.actions.format.commandFlags}`;
 | ||
|             default:
 | ||
|                 throw Error('Unknown action type');
 | ||
|         }
 | ||
|     }
 | ||
|     /**
 | ||
|      * Retrieve the callback for the provided action to determine if an action
 | ||
|      * failed in formatting.
 | ||
|      */
 | ||
|     callbackFor(action) {
 | ||
|         switch (action) {
 | ||
|             case 'check':
 | ||
|                 return this.actions.check.callback;
 | ||
|             case 'format':
 | ||
|                 return this.actions.format.callback;
 | ||
|             default:
 | ||
|                 throw Error('Unknown action type');
 | ||
|         }
 | ||
|     }
 | ||
|     /** Whether the formatter is enabled in the provided config. */
 | ||
|     isEnabled() {
 | ||
|         return !!this.config[this.name];
 | ||
|     }
 | ||
|     /** Retrieve the active file matcher for the formatter. */
 | ||
|     getFileMatcher() {
 | ||
|         return this.getFileMatcherFromConfig() || this.defaultFileMatcher;
 | ||
|     }
 | ||
|     /**
 | ||
|      * Retrieves the file matcher from the config provided to the constructor if provided.
 | ||
|      */
 | ||
|     getFileMatcherFromConfig() {
 | ||
|         const formatterConfig = this.config[this.name];
 | ||
|         if (typeof formatterConfig === 'boolean') {
 | ||
|             return undefined;
 | ||
|         }
 | ||
|         return formatterConfig.matchers;
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Formatter for running buildifier against bazel related files.
 | ||
|  */
 | ||
| class Buildifier extends Formatter {
 | ||
|     constructor() {
 | ||
|         super(...arguments);
 | ||
|         this.name = 'buildifier';
 | ||
|         this.binaryFilePath = path.join(getRepoBaseDir(), 'node_modules/.bin/buildifier');
 | ||
|         this.defaultFileMatcher = ['**/*.bzl', '**/BUILD.bazel', '**/WORKSPACE', '**/BUILD'];
 | ||
|         this.actions = {
 | ||
|             check: {
 | ||
|                 commandFlags: `${BAZEL_WARNING_FLAG} --lint=warn --mode=check --format=json`,
 | ||
|                 callback: (_, code, stdout) => {
 | ||
|                     return code !== 0 || !JSON.parse(stdout)['success'];
 | ||
|                 },
 | ||
|             },
 | ||
|             format: {
 | ||
|                 commandFlags: `${BAZEL_WARNING_FLAG} --lint=fix --mode=fix`,
 | ||
|                 callback: (file, code, _, stderr) => {
 | ||
|                     if (code !== 0) {
 | ||
|                         error(`Error running buildifier on: ${file}`);
 | ||
|                         error(stderr);
 | ||
|                         error();
 | ||
|                         return true;
 | ||
|                     }
 | ||
|                     return false;
 | ||
|                 }
 | ||
|             }
 | ||
|         };
 | ||
|     }
 | ||
| }
 | ||
| // The warning flag for buildifier copied from angular/angular's usage.
 | ||
| const BAZEL_WARNING_FLAG = `--warnings=attr-cfg,attr-license,attr-non-empty,attr-output-default,` +
 | ||
|     `attr-single-file,constant-glob,ctx-args,depset-iteration,depset-union,dict-concatenation,` +
 | ||
|     `duplicated-name,filetype,git-repository,http-archive,integer-division,load,load-on-top,` +
 | ||
|     `native-build,native-package,output-group,package-name,package-on-top,positional-args,` +
 | ||
|     `redefined-variable,repository-name,same-origin-load,string-iteration,unused-variable`;
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Formatter for running clang-format against Typescript and Javascript files
 | ||
|  */
 | ||
| class ClangFormat extends Formatter {
 | ||
|     constructor() {
 | ||
|         super(...arguments);
 | ||
|         this.name = 'clang-format';
 | ||
|         this.binaryFilePath = path.join(getRepoBaseDir(), 'node_modules/.bin/clang-format');
 | ||
|         this.defaultFileMatcher = ['**/*.{t,j}s'];
 | ||
|         this.actions = {
 | ||
|             check: {
 | ||
|                 commandFlags: `--Werror -n -style=file`,
 | ||
|                 callback: (_, code) => {
 | ||
|                     return code !== 0;
 | ||
|                 },
 | ||
|             },
 | ||
|             format: {
 | ||
|                 commandFlags: `-i -style=file`,
 | ||
|                 callback: (file, code, _, stderr) => {
 | ||
|                     if (code !== 0) {
 | ||
|                         error(`Error running clang-format on: ${file}`);
 | ||
|                         error(stderr);
 | ||
|                         error();
 | ||
|                         return true;
 | ||
|                     }
 | ||
|                     return false;
 | ||
|                 }
 | ||
|             }
 | ||
|         };
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Get all defined formatters which are active based on the current loaded config.
 | ||
|  */
 | ||
| function getActiveFormatters() {
 | ||
|     const config = getFormatConfig().format;
 | ||
|     return [new Buildifier(config), new ClangFormat(config)].filter(formatter => formatter.isEnabled());
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| const AVAILABLE_THREADS = Math.max(os.cpus().length - 1, 1);
 | ||
| /**
 | ||
|  * Run the provided commands in parallel for each provided file.
 | ||
|  *
 | ||
|  * Running the formatter is split across (number of available cpu threads - 1) processess.
 | ||
|  * The task is done in multiple processess to speed up the overall time of the task, as running
 | ||
|  * across entire repositories takes a large amount of time.
 | ||
|  * As a data point for illustration, using 8 process rather than 1 cut the execution
 | ||
|  * time from 276 seconds to 39 seconds for the same 2700 files.
 | ||
|  *
 | ||
|  * A promise is returned, completed when the command has completed running for each file.
 | ||
|  * The promise resolves with a list of failures, or `false` if no formatters have matched.
 | ||
|  */
 | ||
| function runFormatterInParallel(allFiles, action) {
 | ||
|     return new Promise((resolve) => {
 | ||
|         const formatters = getActiveFormatters();
 | ||
|         const failures = [];
 | ||
|         const pendingCommands = [];
 | ||
|         for (const formatter of formatters) {
 | ||
|             pendingCommands.push(...multimatch.call(undefined, allFiles, formatter.getFileMatcher(), { dot: true })
 | ||
|                 .map(file => ({ formatter, file })));
 | ||
|         }
 | ||
|         // If no commands are generated, resolve the promise as `false` as no files
 | ||
|         // were run against the any formatters.
 | ||
|         if (pendingCommands.length === 0) {
 | ||
|             return resolve(false);
 | ||
|         }
 | ||
|         switch (action) {
 | ||
|             case 'format':
 | ||
|                 info(`Formatting ${pendingCommands.length} file(s)`);
 | ||
|                 break;
 | ||
|             case 'check':
 | ||
|                 info(`Checking format of ${pendingCommands.length} file(s)`);
 | ||
|                 break;
 | ||
|             default:
 | ||
|                 throw Error(`Invalid format action "${action}": allowed actions are "format" and "check"`);
 | ||
|         }
 | ||
|         // The progress bar instance to use for progress tracking.
 | ||
|         const progressBar = new cliProgress.Bar({ format: `[{bar}] ETA: {eta}s | {value}/{total} files`, clearOnComplete: true });
 | ||
|         // A local copy of the files to run the command on.
 | ||
|         // An array to represent the current usage state of each of the threads for parallelization.
 | ||
|         const threads = new Array(AVAILABLE_THREADS).fill(false);
 | ||
|         // Recursively run the command on the next available file from the list using the provided
 | ||
|         // thread.
 | ||
|         function runCommandInThread(thread) {
 | ||
|             const nextCommand = pendingCommands.pop();
 | ||
|             // If no file was pulled from the array, return as there are no more files to run against.
 | ||
|             if (nextCommand === undefined) {
 | ||
|                 threads[thread] = false;
 | ||
|                 return;
 | ||
|             }
 | ||
|             // Get the file and formatter for the next command.
 | ||
|             const { file, formatter } = nextCommand;
 | ||
|             shelljs.exec(`${formatter.commandFor(action)} ${file}`, { async: true, silent: true }, (code, stdout, stderr) => {
 | ||
|                 // Run the provided callback function.
 | ||
|                 const failed = formatter.callbackFor(action)(file, code, stdout, stderr);
 | ||
|                 if (failed) {
 | ||
|                     failures.push(file);
 | ||
|                 }
 | ||
|                 // Note in the progress bar another file being completed.
 | ||
|                 progressBar.increment(1);
 | ||
|                 // If more files exist in the list, run again to work on the next file,
 | ||
|                 // using the same slot.
 | ||
|                 if (pendingCommands.length) {
 | ||
|                     return runCommandInThread(thread);
 | ||
|                 }
 | ||
|                 // If not more files are available, mark the thread as unused.
 | ||
|                 threads[thread] = false;
 | ||
|                 // If all of the threads are false, as they are unused, mark the progress bar
 | ||
|                 // completed and resolve the promise.
 | ||
|                 if (threads.every(active => !active)) {
 | ||
|                     progressBar.stop();
 | ||
|                     resolve(failures);
 | ||
|                 }
 | ||
|             });
 | ||
|             // Mark the thread as in use as the command execution has been started.
 | ||
|             threads[thread] = true;
 | ||
|         }
 | ||
|         // Start the progress bar
 | ||
|         progressBar.start(pendingCommands.length, 0);
 | ||
|         // Start running the command on files from the least in each available thread.
 | ||
|         threads.forEach((_, idx) => runCommandInThread(idx));
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Format provided files in place.
 | ||
|  */
 | ||
| function formatFiles(files) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         // Whether any files failed to format.
 | ||
|         let failures = yield runFormatterInParallel(files, 'format');
 | ||
|         if (failures === false) {
 | ||
|             info('No files matched for formatting.');
 | ||
|             process.exit(0);
 | ||
|         }
 | ||
|         // The process should exit as a failure if any of the files failed to format.
 | ||
|         if (failures.length !== 0) {
 | ||
|             error(`Formatting failed, see errors above for more information.`);
 | ||
|             process.exit(1);
 | ||
|         }
 | ||
|         info(`√  Formatting complete.`);
 | ||
|         process.exit(0);
 | ||
|     });
 | ||
| }
 | ||
| /**
 | ||
|  * Check provided files for formatting correctness.
 | ||
|  */
 | ||
| function checkFiles(files) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         // Files which are currently not formatted correctly.
 | ||
|         const failures = yield runFormatterInParallel(files, 'check');
 | ||
|         if (failures === false) {
 | ||
|             info('No files matched for formatting check.');
 | ||
|             process.exit(0);
 | ||
|         }
 | ||
|         if (failures.length) {
 | ||
|             // Provide output expressing which files are failing formatting.
 | ||
|             info.group('\nThe following files are out of format:');
 | ||
|             for (const file of failures) {
 | ||
|                 info(`  - ${file}`);
 | ||
|             }
 | ||
|             info.groupEnd();
 | ||
|             info();
 | ||
|             // If the command is run in a non-CI environment, prompt to format the files immediately.
 | ||
|             let runFormatter = false;
 | ||
|             if (!process.env['CI']) {
 | ||
|                 runFormatter = yield promptConfirm('Format the files now?', true);
 | ||
|             }
 | ||
|             if (runFormatter) {
 | ||
|                 // Format the failing files as requested.
 | ||
|                 yield formatFiles(failures);
 | ||
|                 process.exit(0);
 | ||
|             }
 | ||
|             else {
 | ||
|                 // Inform user how to format files in the future.
 | ||
|                 info();
 | ||
|                 info(`To format the failing file run the following command:`);
 | ||
|                 info(`  yarn ng-dev format files ${failures.join(' ')}`);
 | ||
|                 process.exit(1);
 | ||
|             }
 | ||
|         }
 | ||
|         else {
 | ||
|             info('√  All files correctly formatted.');
 | ||
|             process.exit(0);
 | ||
|         }
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /** Build the parser for the format commands. */
 | ||
| function buildFormatParser(localYargs) {
 | ||
|     return localYargs.help()
 | ||
|         .strict()
 | ||
|         .demandCommand()
 | ||
|         .option('check', {
 | ||
|         type: 'boolean',
 | ||
|         default: process.env['CI'] ? true : false,
 | ||
|         description: 'Run the formatter to check formatting rather than updating code format'
 | ||
|     })
 | ||
|         .command('all', 'Run the formatter on all files in the repository', args => args, ({ check }) => {
 | ||
|         const executionCmd = check ? checkFiles : formatFiles;
 | ||
|         executionCmd(allFiles());
 | ||
|     })
 | ||
|         .command('changed [shaOrRef]', 'Run the formatter on files changed since the provided sha/ref', args => args.positional('shaOrRef', { type: 'string' }), ({ shaOrRef, check }) => {
 | ||
|         const sha = shaOrRef || 'master';
 | ||
|         const executionCmd = check ? checkFiles : formatFiles;
 | ||
|         executionCmd(allChangedFilesSince(sha));
 | ||
|     })
 | ||
|         .command('staged', 'Run the formatter on all staged files', args => args, ({ check }) => {
 | ||
|         const executionCmd = check ? checkFiles : formatFiles;
 | ||
|         executionCmd(allStagedFiles());
 | ||
|     })
 | ||
|         .command('files <files..>', 'Run the formatter on provided files', args => args.positional('files', { array: true, type: 'string' }), ({ check, files }) => {
 | ||
|         const executionCmd = check ? checkFiles : formatFiles;
 | ||
|         executionCmd(files);
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| function verify() {
 | ||
|     /** Full path to NgBot config file */
 | ||
|     const NGBOT_CONFIG_YAML_PATH = path.resolve(getRepoBaseDir(), '.github/angular-robot.yml');
 | ||
|     /** The NgBot config file */
 | ||
|     const ngBotYaml = fs.readFileSync(NGBOT_CONFIG_YAML_PATH, 'utf8');
 | ||
|     try {
 | ||
|         // Try parsing the config file to verify that the syntax is correct.
 | ||
|         yaml.parse(ngBotYaml);
 | ||
|         info(`${green('√')}  Valid NgBot YAML config`);
 | ||
|     }
 | ||
|     catch (e) {
 | ||
|         error(`${red('!')} Invalid NgBot YAML config`);
 | ||
|         error(e);
 | ||
|         process.exitCode = 1;
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /** Build the parser for the NgBot commands. */
 | ||
| function buildNgbotParser(localYargs) {
 | ||
|     return localYargs.help().strict().demandCommand().command('verify', 'Verify the NgBot config', {}, () => verify());
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Loads and validates the merge configuration. */
 | ||
| function loadAndValidateConfig(config, api) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|         var mergeConfig, errors;
 | ||
|         return tslib.__generator(this, function (_a) {
 | ||
|             switch (_a.label) {
 | ||
|                 case 0:
 | ||
|                     if (config.merge === undefined) {
 | ||
|                         return [2 /*return*/, { errors: ['No merge configuration found. Set the `merge` configuration.'] }];
 | ||
|                     }
 | ||
|                     if (typeof config.merge !== 'function') {
 | ||
|                         return [2 /*return*/, { errors: ['Expected merge configuration to be defined lazily through a function.'] }];
 | ||
|                     }
 | ||
|                     return [4 /*yield*/, config.merge(api)];
 | ||
|                 case 1:
 | ||
|                     mergeConfig = _a.sent();
 | ||
|                     errors = validateMergeConfig(mergeConfig);
 | ||
|                     if (errors.length) {
 | ||
|                         return [2 /*return*/, { errors: errors }];
 | ||
|                     }
 | ||
|                     return [2 /*return*/, { config: mergeConfig }];
 | ||
|             }
 | ||
|         });
 | ||
|     });
 | ||
| }
 | ||
| /** Validates the specified configuration. Returns a list of failure messages. */
 | ||
| function validateMergeConfig(config) {
 | ||
|     var errors = [];
 | ||
|     if (!config.labels) {
 | ||
|         errors.push('No label configuration.');
 | ||
|     }
 | ||
|     else if (!Array.isArray(config.labels)) {
 | ||
|         errors.push('Label configuration needs to be an array.');
 | ||
|     }
 | ||
|     if (!config.claSignedLabel) {
 | ||
|         errors.push('No CLA signed label configured.');
 | ||
|     }
 | ||
|     if (!config.mergeReadyLabel) {
 | ||
|         errors.push('No merge ready label configured.');
 | ||
|     }
 | ||
|     if (config.githubApiMerge === undefined) {
 | ||
|         errors.push('No explicit choice of merge strategy. Please set `githubApiMerge`.');
 | ||
|     }
 | ||
|     return errors;
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Checks whether the specified value matches the given pattern. */
 | ||
| function matchesPattern(value, pattern) {
 | ||
|     return typeof pattern === 'string' ? value === pattern : pattern.test(value);
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Unique error that can be thrown in the merge configuration if an
 | ||
|  * invalid branch is targeted.
 | ||
|  */
 | ||
| var InvalidTargetBranchError = /** @class */ (function () {
 | ||
|     function InvalidTargetBranchError(failureMessage) {
 | ||
|         this.failureMessage = failureMessage;
 | ||
|     }
 | ||
|     return InvalidTargetBranchError;
 | ||
| }());
 | ||
| /**
 | ||
|  * Unique error that can be thrown in the merge configuration if an
 | ||
|  * invalid label has been applied to a pull request.
 | ||
|  */
 | ||
| var InvalidTargetLabelError = /** @class */ (function () {
 | ||
|     function InvalidTargetLabelError(failureMessage) {
 | ||
|         this.failureMessage = failureMessage;
 | ||
|     }
 | ||
|     return InvalidTargetLabelError;
 | ||
| }());
 | ||
| /** Gets the target label from the specified pull request labels. */
 | ||
| function getTargetLabelFromPullRequest(config, labels) {
 | ||
|     var e_1, _a;
 | ||
|     var _loop_1 = function (label) {
 | ||
|         var match = config.labels.find(function (_a) {
 | ||
|             var pattern = _a.pattern;
 | ||
|             return matchesPattern(label, pattern);
 | ||
|         });
 | ||
|         if (match !== undefined) {
 | ||
|             return { value: match };
 | ||
|         }
 | ||
|     };
 | ||
|     try {
 | ||
|         for (var labels_1 = tslib.__values(labels), labels_1_1 = labels_1.next(); !labels_1_1.done; labels_1_1 = labels_1.next()) {
 | ||
|             var label = labels_1_1.value;
 | ||
|             var state_1 = _loop_1(label);
 | ||
|             if (typeof state_1 === "object")
 | ||
|                 return state_1.value;
 | ||
|         }
 | ||
|     }
 | ||
|     catch (e_1_1) { e_1 = { error: e_1_1 }; }
 | ||
|     finally {
 | ||
|         try {
 | ||
|             if (labels_1_1 && !labels_1_1.done && (_a = labels_1.return)) _a.call(labels_1);
 | ||
|         }
 | ||
|         finally { if (e_1) throw e_1.error; }
 | ||
|     }
 | ||
|     return null;
 | ||
| }
 | ||
| /**
 | ||
|  * Gets the branches from the specified target label.
 | ||
|  *
 | ||
|  * @throws {InvalidTargetLabelError} Invalid label has been applied to pull request.
 | ||
|  * @throws {InvalidTargetBranchError} Invalid Github target branch has been selected.
 | ||
|  */
 | ||
| function getBranchesFromTargetLabel(label, githubTargetBranch) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|         var _a;
 | ||
|         return tslib.__generator(this, function (_b) {
 | ||
|             switch (_b.label) {
 | ||
|                 case 0:
 | ||
|                     if (!(typeof label.branches === 'function')) return [3 /*break*/, 2];
 | ||
|                     return [4 /*yield*/, label.branches(githubTargetBranch)];
 | ||
|                 case 1:
 | ||
|                     _a = _b.sent();
 | ||
|                     return [3 /*break*/, 4];
 | ||
|                 case 2: return [4 /*yield*/, label.branches];
 | ||
|                 case 3:
 | ||
|                     _a = _b.sent();
 | ||
|                     _b.label = 4;
 | ||
|                 case 4: return [2 /*return*/, _a];
 | ||
|             }
 | ||
|         });
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| function checkTargetBranchesForPr(prNumber, jsonOutput = false) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         /** The ng-dev configuration. */
 | ||
|         const config = getConfig();
 | ||
|         /** Repo owner and name for the github repository. */
 | ||
|         const { owner, name: repo } = config.github;
 | ||
|         /** The git client to get a Github API service instance. */
 | ||
|         const git = new GitClient(undefined, config);
 | ||
|         /** The validated merge config. */
 | ||
|         const { config: mergeConfig, errors } = yield loadAndValidateConfig(config, git.github);
 | ||
|         if (errors !== undefined) {
 | ||
|             throw Error(`Invalid configuration found: ${errors}`);
 | ||
|         }
 | ||
|         /** The current state of the pull request from Github. */
 | ||
|         const prData = (yield git.github.pulls.get({ owner, repo, pull_number: prNumber })).data;
 | ||
|         /** The list of labels on the PR as strings. */
 | ||
|         const labels = prData.labels.map(l => l.name);
 | ||
|         /** The branch targetted via the Github UI. */
 | ||
|         const githubTargetBranch = prData.base.ref;
 | ||
|         /** The active label which is being used for targetting the PR. */
 | ||
|         const targetLabel = getTargetLabelFromPullRequest(mergeConfig, labels);
 | ||
|         if (targetLabel === null) {
 | ||
|             error(red(`No target label was found on pr #${prNumber}`));
 | ||
|             process.exitCode = 1;
 | ||
|             return;
 | ||
|         }
 | ||
|         /** The target branches based on the target label and branch targetted in the Github UI. */
 | ||
|         const targets = yield getBranchesFromTargetLabel(targetLabel, githubTargetBranch);
 | ||
|         // When requested, print a json output to stdout, rather than using standard ng-dev logging.
 | ||
|         if (jsonOutput) {
 | ||
|             process.stdout.write(JSON.stringify(targets));
 | ||
|             return;
 | ||
|         }
 | ||
|         info.group(`PR #${prNumber} will merge into:`);
 | ||
|         targets.forEach(target => info(`- ${target}`));
 | ||
|         info.groupEnd();
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Builds the command. */
 | ||
| function builder$5(yargs) {
 | ||
|     return yargs
 | ||
|         .positional('pr', {
 | ||
|         description: 'The pull request number',
 | ||
|         type: 'number',
 | ||
|         demandOption: true,
 | ||
|     })
 | ||
|         .option('json', {
 | ||
|         type: 'boolean',
 | ||
|         default: false,
 | ||
|         description: 'Print response as json',
 | ||
|     });
 | ||
| }
 | ||
| /** Handles the command. */
 | ||
| function handler$5({ pr, json }) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         yield checkTargetBranchesForPr(pr, json);
 | ||
|     });
 | ||
| }
 | ||
| /** yargs command module describing the command.  */
 | ||
| const CheckTargetBranchesModule = {
 | ||
|     handler: handler$5,
 | ||
|     builder: builder$5,
 | ||
|     command: 'check-target-branches <pr>',
 | ||
|     describe: 'Check a PR to determine what branches it is currently targeting',
 | ||
| };
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Get a PR from github  */
 | ||
| function getPr(prSchema, prNumber, git) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|         var _a, owner, name, PR_QUERY, result;
 | ||
|         return tslib.__generator(this, function (_b) {
 | ||
|             switch (_b.label) {
 | ||
|                 case 0:
 | ||
|                     _a = git.remoteConfig, owner = _a.owner, name = _a.name;
 | ||
|                     PR_QUERY = typedGraphqlify.params({
 | ||
|                         $number: 'Int!',
 | ||
|                         $owner: 'String!',
 | ||
|                         $name: 'String!',
 | ||
|                     }, {
 | ||
|                         repository: typedGraphqlify.params({ owner: '$owner', name: '$name' }, {
 | ||
|                             pullRequest: typedGraphqlify.params({ number: '$number' }, prSchema),
 | ||
|                         })
 | ||
|                     });
 | ||
|                     return [4 /*yield*/, git.github.graphql.query(PR_QUERY, { number: prNumber, owner: owner, name: name })];
 | ||
|                 case 1:
 | ||
|                     result = (_b.sent());
 | ||
|                     return [2 /*return*/, result.repository.pullRequest];
 | ||
|             }
 | ||
|         });
 | ||
|     });
 | ||
| }
 | ||
| /** Get all pending PRs from github  */
 | ||
| function getPendingPrs(prSchema, git) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|         var _a, owner, name, PRS_QUERY, cursor, hasNextPage, prs, params_1, results;
 | ||
|         return tslib.__generator(this, function (_b) {
 | ||
|             switch (_b.label) {
 | ||
|                 case 0:
 | ||
|                     _a = git.remoteConfig, owner = _a.owner, name = _a.name;
 | ||
|                     PRS_QUERY = typedGraphqlify.params({
 | ||
|                         $first: 'Int',
 | ||
|                         $after: 'String',
 | ||
|                         $owner: 'String!',
 | ||
|                         $name: 'String!',
 | ||
|                     }, {
 | ||
|                         repository: typedGraphqlify.params({ owner: '$owner', name: '$name' }, {
 | ||
|                             pullRequests: typedGraphqlify.params({
 | ||
|                                 first: '$first',
 | ||
|                                 after: '$after',
 | ||
|                                 states: "OPEN",
 | ||
|                             }, {
 | ||
|                                 nodes: [prSchema],
 | ||
|                                 pageInfo: {
 | ||
|                                     hasNextPage: typedGraphqlify.types.boolean,
 | ||
|                                     endCursor: typedGraphqlify.types.string,
 | ||
|                                 },
 | ||
|                             }),
 | ||
|                         })
 | ||
|                     });
 | ||
|                     hasNextPage = true;
 | ||
|                     prs = [];
 | ||
|                     _b.label = 1;
 | ||
|                 case 1:
 | ||
|                     if (!hasNextPage) return [3 /*break*/, 3];
 | ||
|                     params_1 = {
 | ||
|                         after: cursor || null,
 | ||
|                         first: 100,
 | ||
|                         owner: owner,
 | ||
|                         name: name,
 | ||
|                     };
 | ||
|                     return [4 /*yield*/, git.github.graphql.query(PRS_QUERY, params_1)];
 | ||
|                 case 2:
 | ||
|                     results = _b.sent();
 | ||
|                     prs.push.apply(prs, tslib.__spread(results.repository.pullRequests.nodes));
 | ||
|                     hasNextPage = results.repository.pullRequests.pageInfo.hasNextPage;
 | ||
|                     cursor = results.repository.pullRequests.pageInfo.endCursor;
 | ||
|                     return [3 /*break*/, 1];
 | ||
|                 case 3: return [2 /*return*/, prs];
 | ||
|             }
 | ||
|         });
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /* GraphQL schema for the response body for a pending PR. */
 | ||
| const PR_SCHEMA = {
 | ||
|     state: typedGraphqlify.types.string,
 | ||
|     maintainerCanModify: typedGraphqlify.types.boolean,
 | ||
|     viewerDidAuthor: typedGraphqlify.types.boolean,
 | ||
|     headRefOid: typedGraphqlify.types.string,
 | ||
|     headRef: {
 | ||
|         name: typedGraphqlify.types.string,
 | ||
|         repository: {
 | ||
|             url: typedGraphqlify.types.string,
 | ||
|             nameWithOwner: typedGraphqlify.types.string,
 | ||
|         },
 | ||
|     },
 | ||
|     baseRef: {
 | ||
|         name: typedGraphqlify.types.string,
 | ||
|         repository: {
 | ||
|             url: typedGraphqlify.types.string,
 | ||
|             nameWithOwner: typedGraphqlify.types.string,
 | ||
|         },
 | ||
|     },
 | ||
| };
 | ||
| class UnexpectedLocalChangesError extends Error {
 | ||
|     constructor(m) {
 | ||
|         super(m);
 | ||
|         Object.setPrototypeOf(this, UnexpectedLocalChangesError.prototype);
 | ||
|     }
 | ||
| }
 | ||
| class MaintainerModifyAccessError extends Error {
 | ||
|     constructor(m) {
 | ||
|         super(m);
 | ||
|         Object.setPrototypeOf(this, MaintainerModifyAccessError.prototype);
 | ||
|     }
 | ||
| }
 | ||
| /**
 | ||
|  * Rebase the provided PR onto its merge target branch, and push up the resulting
 | ||
|  * commit to the PRs repository.
 | ||
|  */
 | ||
| function checkOutPullRequestLocally(prNumber, githubToken, opts = {}) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         /** Authenticated Git client for git and Github interactions. */
 | ||
|         const git = new GitClient(githubToken);
 | ||
|         // In order to preserve local changes, checkouts cannot occur if local changes are present in the
 | ||
|         // git environment. Checked before retrieving the PR to fail fast.
 | ||
|         if (git.hasLocalChanges()) {
 | ||
|             throw new UnexpectedLocalChangesError('Unable to checkout PR due to uncommitted changes.');
 | ||
|         }
 | ||
|         /**
 | ||
|          * The branch or revision originally checked out before this method performed
 | ||
|          * any Git operations that may change the working branch.
 | ||
|          */
 | ||
|         const previousBranchOrRevision = git.getCurrentBranchOrRevision();
 | ||
|         /* The PR information from Github. */
 | ||
|         const pr = yield getPr(PR_SCHEMA, prNumber, git);
 | ||
|         /** The branch name of the PR from the repository the PR came from. */
 | ||
|         const headRefName = pr.headRef.name;
 | ||
|         /** The full ref for the repository and branch the PR came from. */
 | ||
|         const fullHeadRef = `${pr.headRef.repository.nameWithOwner}:${headRefName}`;
 | ||
|         /** The full URL path of the repository the PR came from with github token as authentication. */
 | ||
|         const headRefUrl = addTokenToGitHttpsUrl(pr.headRef.repository.url, githubToken);
 | ||
|         // Note: Since we use a detached head for rebasing the PR and therefore do not have
 | ||
|         // remote-tracking branches configured, we need to set our expected ref and SHA. This
 | ||
|         // allows us to use `--force-with-lease` for the detached head while ensuring that we
 | ||
|         // never accidentally override upstream changes that have been pushed in the meanwhile.
 | ||
|         // See:
 | ||
|         // https://git-scm.com/docs/git-push#Documentation/git-push.txt---force-with-leaseltrefnamegtltexpectgt
 | ||
|         /** Flag for a force push with leage back to upstream. */
 | ||
|         const forceWithLeaseFlag = `--force-with-lease=${headRefName}:${pr.headRefOid}`;
 | ||
|         // If the PR does not allow maintainers to modify it, exit as the rebased PR cannot
 | ||
|         // be pushed up.
 | ||
|         if (!pr.maintainerCanModify && !pr.viewerDidAuthor && !opts.allowIfMaintainerCannotModify) {
 | ||
|             throw new MaintainerModifyAccessError('PR is not set to allow maintainers to modify the PR');
 | ||
|         }
 | ||
|         try {
 | ||
|             // Fetch the branch at the commit of the PR, and check it out in a detached state.
 | ||
|             info(`Checking out PR #${prNumber} from ${fullHeadRef}`);
 | ||
|             git.run(['fetch', '-q', headRefUrl, headRefName]);
 | ||
|             git.run(['checkout', '--detach', 'FETCH_HEAD']);
 | ||
|         }
 | ||
|         catch (e) {
 | ||
|             git.checkout(previousBranchOrRevision, true);
 | ||
|             throw e;
 | ||
|         }
 | ||
|         return {
 | ||
|             /**
 | ||
|              * Pushes the current local branch to the PR on the upstream repository.
 | ||
|              *
 | ||
|              * @returns true If the command did not fail causing a GitCommandError to be thrown.
 | ||
|              * @throws GitCommandError Thrown when the push back to upstream fails.
 | ||
|              */
 | ||
|             pushToUpstream: () => {
 | ||
|                 git.run(['push', headRefUrl, `HEAD:${headRefName}`, forceWithLeaseFlag]);
 | ||
|                 return true;
 | ||
|             },
 | ||
|             /** Restores the state of the local repository to before the PR checkout occured. */
 | ||
|             resetGitState: () => {
 | ||
|                 return git.checkout(previousBranchOrRevision, true);
 | ||
|             }
 | ||
|         };
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Builds the checkout pull request command. */
 | ||
| function builder$6(yargs) {
 | ||
|     return addGithubTokenOption(yargs).positional('prNumber', { type: 'number', demandOption: true });
 | ||
| }
 | ||
| /** Handles the checkout pull request command. */
 | ||
| function handler$6({ prNumber, githubToken }) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const prCheckoutOptions = { allowIfMaintainerCannotModify: true, branchName: `pr-${prNumber}` };
 | ||
|         yield checkOutPullRequestLocally(prNumber, githubToken, prCheckoutOptions);
 | ||
|     });
 | ||
| }
 | ||
| /** yargs command module for checking out a PR  */
 | ||
| const CheckoutCommandModule = {
 | ||
|     handler: handler$6,
 | ||
|     builder: builder$6,
 | ||
|     command: 'checkout <pr-number>',
 | ||
|     describe: 'Checkout a PR from the upstream repo',
 | ||
| };
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /* GraphQL schema for the response body for each pending PR. */
 | ||
| const PR_SCHEMA$1 = {
 | ||
|     headRef: {
 | ||
|         name: typedGraphqlify.types.string,
 | ||
|         repository: {
 | ||
|             url: typedGraphqlify.types.string,
 | ||
|             nameWithOwner: typedGraphqlify.types.string,
 | ||
|         },
 | ||
|     },
 | ||
|     baseRef: {
 | ||
|         name: typedGraphqlify.types.string,
 | ||
|         repository: {
 | ||
|             url: typedGraphqlify.types.string,
 | ||
|             nameWithOwner: typedGraphqlify.types.string,
 | ||
|         },
 | ||
|     },
 | ||
|     updatedAt: typedGraphqlify.types.string,
 | ||
|     number: typedGraphqlify.types.number,
 | ||
|     mergeable: typedGraphqlify.types.string,
 | ||
|     title: typedGraphqlify.types.string,
 | ||
| };
 | ||
| /** Convert raw Pull Request response from Github to usable Pull Request object. */
 | ||
| function processPr(pr) {
 | ||
|     return Object.assign(Object.assign({}, pr), { updatedAt: (new Date(pr.updatedAt)).getTime() });
 | ||
| }
 | ||
| /** Name of a temporary local branch that is used for checking conflicts. **/
 | ||
| const tempWorkingBranch = '__NgDevRepoBaseAfterChange__';
 | ||
| /** Checks if the provided PR will cause new conflicts in other pending PRs. */
 | ||
| function discoverNewConflictsForPr(newPrNumber, updatedAfter, config = getConfig()) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const git = new GitClient();
 | ||
|         // If there are any local changes in the current repository state, the
 | ||
|         // check cannot run as it needs to move between branches.
 | ||
|         if (git.hasLocalChanges()) {
 | ||
|             error('Cannot run with local changes. Please make sure there are no local changes.');
 | ||
|             process.exit(1);
 | ||
|         }
 | ||
|         /** The active github branch or revision before we performed any Git commands. */
 | ||
|         const previousBranchOrRevision = git.getCurrentBranchOrRevision();
 | ||
|         /* Progress bar to indicate progress. */
 | ||
|         const progressBar = new cliProgress.Bar({ format: `[{bar}] ETA: {eta}s | {value}/{total}` });
 | ||
|         /* PRs which were found to be conflicting. */
 | ||
|         const conflicts = [];
 | ||
|         info(`Requesting pending PRs from Github`);
 | ||
|         /** List of PRs from github currently known as mergable. */
 | ||
|         const allPendingPRs = (yield getPendingPrs(PR_SCHEMA$1, git)).map(processPr);
 | ||
|         /** The PR which is being checked against. */
 | ||
|         const requestedPr = allPendingPRs.find(pr => pr.number === newPrNumber);
 | ||
|         if (requestedPr === undefined) {
 | ||
|             error(`The request PR, #${newPrNumber} was not found as a pending PR on github, please confirm`);
 | ||
|             error(`the PR number is correct and is an open PR`);
 | ||
|             process.exit(1);
 | ||
|         }
 | ||
|         const pendingPrs = allPendingPRs.filter(pr => {
 | ||
|             return (
 | ||
|             // PRs being merged into the same target branch as the requested PR
 | ||
|             pr.baseRef.name === requestedPr.baseRef.name &&
 | ||
|                 // PRs which either have not been processed or are determined as mergable by Github
 | ||
|                 pr.mergeable !== 'CONFLICTING' &&
 | ||
|                 // PRs updated after the provided date
 | ||
|                 pr.updatedAt >= updatedAfter);
 | ||
|         });
 | ||
|         info(`Retrieved ${allPendingPRs.length} total pending PRs`);
 | ||
|         info(`Checking ${pendingPrs.length} PRs for conflicts after a merge of #${newPrNumber}`);
 | ||
|         // Fetch and checkout the PR being checked.
 | ||
|         exec(`git fetch ${requestedPr.headRef.repository.url} ${requestedPr.headRef.name}`);
 | ||
|         exec(`git checkout -B ${tempWorkingBranch} FETCH_HEAD`);
 | ||
|         // Rebase the PR against the PRs target branch.
 | ||
|         exec(`git fetch ${requestedPr.baseRef.repository.url} ${requestedPr.baseRef.name}`);
 | ||
|         const result = exec(`git rebase FETCH_HEAD`);
 | ||
|         if (result.code) {
 | ||
|             error('The requested PR currently has conflicts');
 | ||
|             cleanUpGitState(previousBranchOrRevision);
 | ||
|             process.exit(1);
 | ||
|         }
 | ||
|         // Start the progress bar
 | ||
|         progressBar.start(pendingPrs.length, 0);
 | ||
|         // Check each PR to determine if it can merge cleanly into the repo after the target PR.
 | ||
|         for (const pr of pendingPrs) {
 | ||
|             // Fetch and checkout the next PR
 | ||
|             exec(`git fetch ${pr.headRef.repository.url} ${pr.headRef.name}`);
 | ||
|             exec(`git checkout --detach FETCH_HEAD`);
 | ||
|             // Check if the PR cleanly rebases into the repo after the target PR.
 | ||
|             const result = exec(`git rebase ${tempWorkingBranch}`);
 | ||
|             if (result.code !== 0) {
 | ||
|                 conflicts.push(pr);
 | ||
|             }
 | ||
|             // Abort any outstanding rebase attempt.
 | ||
|             exec(`git rebase --abort`);
 | ||
|             progressBar.increment(1);
 | ||
|         }
 | ||
|         // End the progress bar as all PRs have been processed.
 | ||
|         progressBar.stop();
 | ||
|         info();
 | ||
|         info(`Result:`);
 | ||
|         cleanUpGitState(previousBranchOrRevision);
 | ||
|         // If no conflicts are found, exit successfully.
 | ||
|         if (conflicts.length === 0) {
 | ||
|             info(`No new conflicting PRs found after #${newPrNumber} merging`);
 | ||
|             process.exit(0);
 | ||
|         }
 | ||
|         // Inform about discovered conflicts, exit with failure.
 | ||
|         error.group(`${conflicts.length} PR(s) which conflict(s) after #${newPrNumber} merges:`);
 | ||
|         for (const pr of conflicts) {
 | ||
|             error(`  - #${pr.number}: ${pr.title}`);
 | ||
|         }
 | ||
|         error.groupEnd();
 | ||
|         process.exit(1);
 | ||
|     });
 | ||
| }
 | ||
| /** Reset git back to the provided branch or revision. */
 | ||
| function cleanUpGitState(previousBranchOrRevision) {
 | ||
|     // Ensure that any outstanding rebases are aborted.
 | ||
|     exec(`git rebase --abort`);
 | ||
|     // Ensure that any changes in the current repo state are cleared.
 | ||
|     exec(`git reset --hard`);
 | ||
|     // Checkout the original branch from before the run began.
 | ||
|     exec(`git checkout ${previousBranchOrRevision}`);
 | ||
|     // Delete the generated branch.
 | ||
|     exec(`git branch -D ${tempWorkingBranch}`);
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Builds the discover-new-conflicts pull request command. */
 | ||
| function buildDiscoverNewConflictsCommand(yargs) {
 | ||
|     return yargs
 | ||
|         .option('date', {
 | ||
|         description: 'Only consider PRs updated since provided date',
 | ||
|         defaultDescription: '30 days ago',
 | ||
|         coerce: (date) => typeof date === 'number' ? date : Date.parse(date),
 | ||
|         default: getThirtyDaysAgoDate(),
 | ||
|     })
 | ||
|         .positional('pr-number', { demandOption: true, type: 'number' });
 | ||
| }
 | ||
| /** Handles the discover-new-conflicts pull request command. */
 | ||
| function handleDiscoverNewConflictsCommand({ 'pr-number': prNumber, date }) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         // If a provided date is not able to be parsed, yargs provides it as NaN.
 | ||
|         if (isNaN(date)) {
 | ||
|             error('Unable to parse the value provided via --date flag');
 | ||
|             process.exit(1);
 | ||
|         }
 | ||
|         yield discoverNewConflictsForPr(prNumber, date);
 | ||
|     });
 | ||
| }
 | ||
| /** Gets a date object 30 days ago from today. */
 | ||
| function getThirtyDaysAgoDate() {
 | ||
|     const date = new Date();
 | ||
|     // Set the hours, minutes and seconds to 0 to only consider date.
 | ||
|     date.setHours(0, 0, 0, 0);
 | ||
|     // Set the date to 30 days in the past.
 | ||
|     date.setDate(date.getDate() - 30);
 | ||
|     return date.getTime();
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Class that can be used to describe pull request failures. A failure
 | ||
|  * is described through a human-readable message and a flag indicating
 | ||
|  * whether it is non-fatal or not.
 | ||
|  */
 | ||
| var PullRequestFailure = /** @class */ (function () {
 | ||
|     function PullRequestFailure(
 | ||
|     /** Human-readable message for the failure */
 | ||
|     message, 
 | ||
|     /** Whether the failure is non-fatal and can be forcibly ignored. */
 | ||
|     nonFatal) {
 | ||
|         if (nonFatal === void 0) { nonFatal = false; }
 | ||
|         this.message = message;
 | ||
|         this.nonFatal = nonFatal;
 | ||
|     }
 | ||
|     PullRequestFailure.claUnsigned = function () {
 | ||
|         return new this("CLA has not been signed. Please make sure the PR author has signed the CLA.");
 | ||
|     };
 | ||
|     PullRequestFailure.failingCiJobs = function () {
 | ||
|         return new this("Failing CI jobs.", true);
 | ||
|     };
 | ||
|     PullRequestFailure.pendingCiJobs = function () {
 | ||
|         return new this("Pending CI jobs.", true);
 | ||
|     };
 | ||
|     PullRequestFailure.notMergeReady = function () {
 | ||
|         return new this("Not marked as merge ready.");
 | ||
|     };
 | ||
|     PullRequestFailure.noTargetLabel = function () {
 | ||
|         return new this("No target branch could be determined. Please ensure a target label is set.");
 | ||
|     };
 | ||
|     PullRequestFailure.mismatchingTargetBranch = function (allowedBranches) {
 | ||
|         return new this("Pull request is set to wrong base branch. Please update the PR in the Github UI " +
 | ||
|             ("to one of the following branches: " + allowedBranches.join(', ') + "."));
 | ||
|     };
 | ||
|     PullRequestFailure.unsatisfiedBaseSha = function () {
 | ||
|         return new this("Pull request has not been rebased recently and could be bypassing CI checks. " +
 | ||
|             "Please rebase the PR.");
 | ||
|     };
 | ||
|     PullRequestFailure.mergeConflicts = function (failedBranches) {
 | ||
|         return new this("Could not merge pull request into the following branches due to merge " +
 | ||
|             ("conflicts: " + failedBranches.join(', ') + ". Please rebase the PR or update the target label."));
 | ||
|     };
 | ||
|     PullRequestFailure.unknownMergeError = function () {
 | ||
|         return new this("Unknown merge error occurred. Please see console output above for debugging.");
 | ||
|     };
 | ||
|     PullRequestFailure.unableToFixupCommitMessageSquashOnly = function () {
 | ||
|         return new this("Unable to fixup commit message of pull request. Commit message can only be " +
 | ||
|             "modified if the PR is merged using squash.");
 | ||
|     };
 | ||
|     PullRequestFailure.notFound = function () {
 | ||
|         return new this("Pull request could not be found upstream.");
 | ||
|     };
 | ||
|     PullRequestFailure.insufficientPermissionsToMerge = function (message) {
 | ||
|         if (message === void 0) { message = "Insufficient Github API permissions to merge pull request. Please ensure that " +
 | ||
|             "your auth token has write access."; }
 | ||
|         return new this(message);
 | ||
|     };
 | ||
|     return PullRequestFailure;
 | ||
| }());
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| function getCaretakerNotePromptMessage(pullRequest) {
 | ||
|     return red('Pull request has a caretaker note applied. Please make sure you read it.') +
 | ||
|         ("\nQuick link to PR: " + pullRequest.url + "\nDo you want to proceed merging?");
 | ||
| }
 | ||
| function getTargettedBranchesConfirmationPromptMessage(pullRequest) {
 | ||
|     var targetBranchListAsString = pullRequest.targetBranches.map(function (b) { return " - " + b + "\n"; }).join('');
 | ||
|     return "Pull request #" + pullRequest.prNumber + " will merge into:\n" + targetBranchListAsString + "\nDo you want to proceed merging?";
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Loads and validates the specified pull request against the given configuration.
 | ||
|  * If the pull requests fails, a pull request failure is returned.
 | ||
|  */
 | ||
| function loadAndValidatePullRequest(_a, prNumber, ignoreNonFatalFailures) {
 | ||
|     var git = _a.git, config = _a.config;
 | ||
|     if (ignoreNonFatalFailures === void 0) { ignoreNonFatalFailures = false; }
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|         var prData, labels, targetLabel, state, githubTargetBranch, requiredBaseSha, needsCommitMessageFixup, hasCaretakerNote, targetBranches, error_1;
 | ||
|         return tslib.__generator(this, function (_b) {
 | ||
|             switch (_b.label) {
 | ||
|                 case 0: return [4 /*yield*/, fetchPullRequestFromGithub(git, prNumber)];
 | ||
|                 case 1:
 | ||
|                     prData = _b.sent();
 | ||
|                     if (prData === null) {
 | ||
|                         return [2 /*return*/, PullRequestFailure.notFound()];
 | ||
|                     }
 | ||
|                     labels = prData.labels.map(function (l) { return l.name; });
 | ||
|                     if (!labels.some(function (name) { return matchesPattern(name, config.mergeReadyLabel); })) {
 | ||
|                         return [2 /*return*/, PullRequestFailure.notMergeReady()];
 | ||
|                     }
 | ||
|                     if (!labels.some(function (name) { return matchesPattern(name, config.claSignedLabel); })) {
 | ||
|                         return [2 /*return*/, PullRequestFailure.claUnsigned()];
 | ||
|                     }
 | ||
|                     targetLabel = getTargetLabelFromPullRequest(config, labels);
 | ||
|                     if (targetLabel === null) {
 | ||
|                         return [2 /*return*/, PullRequestFailure.noTargetLabel()];
 | ||
|                     }
 | ||
|                     return [4 /*yield*/, git.github.repos.getCombinedStatusForRef(tslib.__assign(tslib.__assign({}, git.remoteParams), { ref: prData.head.sha }))];
 | ||
|                 case 2:
 | ||
|                     state = (_b.sent()).data.state;
 | ||
|                     if (state === 'failure' && !ignoreNonFatalFailures) {
 | ||
|                         return [2 /*return*/, PullRequestFailure.failingCiJobs()];
 | ||
|                     }
 | ||
|                     if (state === 'pending' && !ignoreNonFatalFailures) {
 | ||
|                         return [2 /*return*/, PullRequestFailure.pendingCiJobs()];
 | ||
|                     }
 | ||
|                     githubTargetBranch = prData.base.ref;
 | ||
|                     requiredBaseSha = config.requiredBaseCommits && config.requiredBaseCommits[githubTargetBranch];
 | ||
|                     needsCommitMessageFixup = !!config.commitMessageFixupLabel &&
 | ||
|                         labels.some(function (name) { return matchesPattern(name, config.commitMessageFixupLabel); });
 | ||
|                     hasCaretakerNote = !!config.caretakerNoteLabel &&
 | ||
|                         labels.some(function (name) { return matchesPattern(name, config.caretakerNoteLabel); });
 | ||
|                     _b.label = 3;
 | ||
|                 case 3:
 | ||
|                     _b.trys.push([3, 5, , 6]);
 | ||
|                     return [4 /*yield*/, getBranchesFromTargetLabel(targetLabel, githubTargetBranch)];
 | ||
|                 case 4:
 | ||
|                     targetBranches = _b.sent();
 | ||
|                     return [3 /*break*/, 6];
 | ||
|                 case 5:
 | ||
|                     error_1 = _b.sent();
 | ||
|                     if (error_1 instanceof InvalidTargetBranchError || error_1 instanceof InvalidTargetLabelError) {
 | ||
|                         return [2 /*return*/, new PullRequestFailure(error_1.failureMessage)];
 | ||
|                     }
 | ||
|                     throw error_1;
 | ||
|                 case 6: return [2 /*return*/, {
 | ||
|                         url: prData.html_url,
 | ||
|                         prNumber: prNumber,
 | ||
|                         labels: labels,
 | ||
|                         requiredBaseSha: requiredBaseSha,
 | ||
|                         githubTargetBranch: githubTargetBranch,
 | ||
|                         needsCommitMessageFixup: needsCommitMessageFixup,
 | ||
|                         hasCaretakerNote: hasCaretakerNote,
 | ||
|                         targetBranches: targetBranches,
 | ||
|                         title: prData.title,
 | ||
|                         commitCount: prData.commits,
 | ||
|                     }];
 | ||
|             }
 | ||
|         });
 | ||
|     });
 | ||
| }
 | ||
| /** Fetches a pull request from Github. Returns null if an error occurred. */
 | ||
| function fetchPullRequestFromGithub(git, prNumber) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|         var result, e_1;
 | ||
|         return tslib.__generator(this, function (_a) {
 | ||
|             switch (_a.label) {
 | ||
|                 case 0:
 | ||
|                     _a.trys.push([0, 2, , 3]);
 | ||
|                     return [4 /*yield*/, git.github.pulls.get(tslib.__assign(tslib.__assign({}, git.remoteParams), { pull_number: prNumber }))];
 | ||
|                 case 1:
 | ||
|                     result = _a.sent();
 | ||
|                     return [2 /*return*/, result.data];
 | ||
|                 case 2:
 | ||
|                     e_1 = _a.sent();
 | ||
|                     // If the pull request could not be found, we want to return `null` so
 | ||
|                     // that the error can be handled gracefully.
 | ||
|                     if (e_1.status === 404) {
 | ||
|                         return [2 /*return*/, null];
 | ||
|                     }
 | ||
|                     throw e_1;
 | ||
|                 case 3: return [2 /*return*/];
 | ||
|             }
 | ||
|         });
 | ||
|     });
 | ||
| }
 | ||
| /** Whether the specified value resolves to a pull request. */
 | ||
| function isPullRequest(v) {
 | ||
|     return v.targetBranches !== undefined;
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Name of a temporary branch that contains the head of a currently-processed PR. Note
 | ||
|  * that a branch name should be used that most likely does not conflict with other local
 | ||
|  * development branches.
 | ||
|  */
 | ||
| var TEMP_PR_HEAD_BRANCH = 'merge_pr_head';
 | ||
| /**
 | ||
|  * Base class for merge strategies. A merge strategy accepts a pull request and
 | ||
|  * merges it into the determined target branches.
 | ||
|  */
 | ||
| var MergeStrategy = /** @class */ (function () {
 | ||
|     function MergeStrategy(git) {
 | ||
|         this.git = git;
 | ||
|     }
 | ||
|     /**
 | ||
|      * Prepares a merge of the given pull request. The strategy by default will
 | ||
|      * fetch all target branches and the pull request into local temporary branches.
 | ||
|      */
 | ||
|     MergeStrategy.prototype.prepare = function (pullRequest) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|             return tslib.__generator(this, function (_a) {
 | ||
|                 this.fetchTargetBranches(pullRequest.targetBranches, "pull/" + pullRequest.prNumber + "/head:" + TEMP_PR_HEAD_BRANCH);
 | ||
|                 return [2 /*return*/];
 | ||
|             });
 | ||
|         });
 | ||
|     };
 | ||
|     /** Cleans up the pull request merge. e.g. deleting temporary local branches. */
 | ||
|     MergeStrategy.prototype.cleanup = function (pullRequest) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|             var _this = this;
 | ||
|             return tslib.__generator(this, function (_a) {
 | ||
|                 // Delete all temporary target branches.
 | ||
|                 pullRequest.targetBranches.forEach(function (branchName) { return _this.git.run(['branch', '-D', _this.getLocalTargetBranchName(branchName)]); });
 | ||
|                 // Delete temporary branch for the pull request head.
 | ||
|                 this.git.run(['branch', '-D', TEMP_PR_HEAD_BRANCH]);
 | ||
|                 return [2 /*return*/];
 | ||
|             });
 | ||
|         });
 | ||
|     };
 | ||
|     /** Gets the revision range for all commits in the given pull request. */
 | ||
|     MergeStrategy.prototype.getPullRequestRevisionRange = function (pullRequest) {
 | ||
|         return this.getPullRequestBaseRevision(pullRequest) + ".." + TEMP_PR_HEAD_BRANCH;
 | ||
|     };
 | ||
|     /** Gets the base revision of a pull request. i.e. the commit the PR is based on. */
 | ||
|     MergeStrategy.prototype.getPullRequestBaseRevision = function (pullRequest) {
 | ||
|         return TEMP_PR_HEAD_BRANCH + "~" + pullRequest.commitCount;
 | ||
|     };
 | ||
|     /** Gets a deterministic local branch name for a given branch. */
 | ||
|     MergeStrategy.prototype.getLocalTargetBranchName = function (targetBranch) {
 | ||
|         return "merge_pr_target_" + targetBranch.replace(/\//g, '_');
 | ||
|     };
 | ||
|     /**
 | ||
|      * Cherry-picks the given revision range into the specified target branches.
 | ||
|      * @returns A list of branches for which the revisions could not be cherry-picked into.
 | ||
|      */
 | ||
|     MergeStrategy.prototype.cherryPickIntoTargetBranches = function (revisionRange, targetBranches, options) {
 | ||
|         var e_1, _a;
 | ||
|         if (options === void 0) { options = {}; }
 | ||
|         var cherryPickArgs = [revisionRange];
 | ||
|         var failedBranches = [];
 | ||
|         if (options.dryRun) {
 | ||
|             // https://git-scm.com/docs/git-cherry-pick#Documentation/git-cherry-pick.txt---no-commit
 | ||
|             // This causes `git cherry-pick` to not generate any commits. Instead, the changes are
 | ||
|             // applied directly in the working tree. This allow us to easily discard the changes
 | ||
|             // for dry-run purposes.
 | ||
|             cherryPickArgs.push('--no-commit');
 | ||
|         }
 | ||
|         if (options.linkToOriginalCommits) {
 | ||
|             // We add `-x` when cherry-picking as that will allow us to easily jump to original
 | ||
|             // commits for cherry-picked commits. With that flag set, Git will automatically append
 | ||
|             // the original SHA/revision to the commit message. e.g. `(cherry picked from commit <..>)`.
 | ||
|             // https://git-scm.com/docs/git-cherry-pick#Documentation/git-cherry-pick.txt--x.
 | ||
|             cherryPickArgs.push('-x');
 | ||
|         }
 | ||
|         try {
 | ||
|             // Cherry-pick the refspec into all determined target branches.
 | ||
|             for (var targetBranches_1 = tslib.__values(targetBranches), targetBranches_1_1 = targetBranches_1.next(); !targetBranches_1_1.done; targetBranches_1_1 = targetBranches_1.next()) {
 | ||
|                 var branchName = targetBranches_1_1.value;
 | ||
|                 var localTargetBranch = this.getLocalTargetBranchName(branchName);
 | ||
|                 // Checkout the local target branch.
 | ||
|                 this.git.run(['checkout', localTargetBranch]);
 | ||
|                 // Cherry-pick the refspec into the target branch.
 | ||
|                 if (this.git.runGraceful(tslib.__spread(['cherry-pick'], cherryPickArgs)).status !== 0) {
 | ||
|                     // Abort the failed cherry-pick. We do this because Git persists the failed
 | ||
|                     // cherry-pick state globally in the repository. This could prevent future
 | ||
|                     // pull request merges as a Git thinks a cherry-pick is still in progress.
 | ||
|                     this.git.runGraceful(['cherry-pick', '--abort']);
 | ||
|                     failedBranches.push(branchName);
 | ||
|                 }
 | ||
|                 // If we run with dry run mode, we reset the local target branch so that all dry-run
 | ||
|                 // cherry-pick changes are discard. Changes are applied to the working tree and index.
 | ||
|                 if (options.dryRun) {
 | ||
|                     this.git.run(['reset', '--hard', 'HEAD']);
 | ||
|                 }
 | ||
|             }
 | ||
|         }
 | ||
|         catch (e_1_1) { e_1 = { error: e_1_1 }; }
 | ||
|         finally {
 | ||
|             try {
 | ||
|                 if (targetBranches_1_1 && !targetBranches_1_1.done && (_a = targetBranches_1.return)) _a.call(targetBranches_1);
 | ||
|             }
 | ||
|             finally { if (e_1) throw e_1.error; }
 | ||
|         }
 | ||
|         return failedBranches;
 | ||
|     };
 | ||
|     /**
 | ||
|      * Fetches the given target branches. Also accepts a list of additional refspecs that
 | ||
|      * should be fetched. This is helpful as multiple slow fetches could be avoided.
 | ||
|      */
 | ||
|     MergeStrategy.prototype.fetchTargetBranches = function (names) {
 | ||
|         var _this = this;
 | ||
|         var extraRefspecs = [];
 | ||
|         for (var _i = 1; _i < arguments.length; _i++) {
 | ||
|             extraRefspecs[_i - 1] = arguments[_i];
 | ||
|         }
 | ||
|         var fetchRefspecs = names.map(function (targetBranch) {
 | ||
|             var localTargetBranch = _this.getLocalTargetBranchName(targetBranch);
 | ||
|             return "refs/heads/" + targetBranch + ":" + localTargetBranch;
 | ||
|         });
 | ||
|         // Fetch all target branches with a single command. We don't want to fetch them
 | ||
|         // individually as that could cause an unnecessary slow-down.
 | ||
|         this.git.run(tslib.__spread(['fetch', '-q', '-f', this.git.repoGitUrl], fetchRefspecs, extraRefspecs));
 | ||
|     };
 | ||
|     /** Pushes the given target branches upstream. */
 | ||
|     MergeStrategy.prototype.pushTargetBranchesUpstream = function (names) {
 | ||
|         var _this = this;
 | ||
|         var pushRefspecs = names.map(function (targetBranch) {
 | ||
|             var localTargetBranch = _this.getLocalTargetBranchName(targetBranch);
 | ||
|             return localTargetBranch + ":refs/heads/" + targetBranch;
 | ||
|         });
 | ||
|         // Push all target branches with a single command if we don't run in dry-run mode.
 | ||
|         // We don't want to push them individually as that could cause an unnecessary slow-down.
 | ||
|         this.git.run(tslib.__spread(['push', this.git.repoGitUrl], pushRefspecs));
 | ||
|     };
 | ||
|     return MergeStrategy;
 | ||
| }());
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Separator between commit message header and body. */
 | ||
| var COMMIT_HEADER_SEPARATOR = '\n\n';
 | ||
| /**
 | ||
|  * Merge strategy that primarily leverages the Github API. The strategy merges a given
 | ||
|  * pull request into a target branch using the API. This ensures that Github displays
 | ||
|  * the pull request as merged. The merged commits are then cherry-picked into the remaining
 | ||
|  * target branches using the local Git instance. The benefit is that the Github merged state
 | ||
|  * is properly set, but a notable downside is that PRs cannot use fixup or squash commits.
 | ||
|  */
 | ||
| var GithubApiMergeStrategy = /** @class */ (function (_super) {
 | ||
|     tslib.__extends(GithubApiMergeStrategy, _super);
 | ||
|     function GithubApiMergeStrategy(git, _config) {
 | ||
|         var _this = _super.call(this, git) || this;
 | ||
|         _this._config = _config;
 | ||
|         return _this;
 | ||
|     }
 | ||
|     GithubApiMergeStrategy.prototype.merge = function (pullRequest) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|             var githubTargetBranch, prNumber, targetBranches, requiredBaseSha, needsCommitMessageFixup, method, cherryPickTargetBranches, failure, mergeOptions, mergeStatusCode, targetSha, result, e_1, targetCommitsCount, failedBranches;
 | ||
|             return tslib.__generator(this, function (_a) {
 | ||
|                 switch (_a.label) {
 | ||
|                     case 0:
 | ||
|                         githubTargetBranch = pullRequest.githubTargetBranch, prNumber = pullRequest.prNumber, targetBranches = pullRequest.targetBranches, requiredBaseSha = pullRequest.requiredBaseSha, needsCommitMessageFixup = pullRequest.needsCommitMessageFixup;
 | ||
|                         // If the pull request does not have its base branch set to any determined target
 | ||
|                         // branch, we cannot merge using the API.
 | ||
|                         if (targetBranches.every(function (t) { return t !== githubTargetBranch; })) {
 | ||
|                             return [2 /*return*/, PullRequestFailure.mismatchingTargetBranch(targetBranches)];
 | ||
|                         }
 | ||
|                         // In cases where a required base commit is specified for this pull request, check if
 | ||
|                         // the pull request contains the given commit. If not, return a pull request failure.
 | ||
|                         // This check is useful for enforcing that PRs are rebased on top of a given commit.
 | ||
|                         // e.g. a commit that changes the code ownership validation. PRs which are not rebased
 | ||
|                         // could bypass new codeowner ship rules.
 | ||
|                         if (requiredBaseSha && !this.git.hasCommit(TEMP_PR_HEAD_BRANCH, requiredBaseSha)) {
 | ||
|                             return [2 /*return*/, PullRequestFailure.unsatisfiedBaseSha()];
 | ||
|                         }
 | ||
|                         method = this._getMergeActionFromPullRequest(pullRequest);
 | ||
|                         cherryPickTargetBranches = targetBranches.filter(function (b) { return b !== githubTargetBranch; });
 | ||
|                         return [4 /*yield*/, this._checkMergability(pullRequest, cherryPickTargetBranches)];
 | ||
|                     case 1:
 | ||
|                         failure = _a.sent();
 | ||
|                         // If the PR could not be cherry-picked into all target branches locally, we know it can't
 | ||
|                         // be done through the Github API either. We abort merging and pass-through the failure.
 | ||
|                         if (failure !== null) {
 | ||
|                             return [2 /*return*/, failure];
 | ||
|                         }
 | ||
|                         mergeOptions = tslib.__assign({ pull_number: prNumber, merge_method: method }, this.git.remoteParams);
 | ||
|                         if (!needsCommitMessageFixup) return [3 /*break*/, 3];
 | ||
|                         // Commit message fixup does not work with other merge methods as the Github API only
 | ||
|                         // allows commit message modifications for squash merging.
 | ||
|                         if (method !== 'squash') {
 | ||
|                             return [2 /*return*/, PullRequestFailure.unableToFixupCommitMessageSquashOnly()];
 | ||
|                         }
 | ||
|                         return [4 /*yield*/, this._promptCommitMessageEdit(pullRequest, mergeOptions)];
 | ||
|                     case 2:
 | ||
|                         _a.sent();
 | ||
|                         _a.label = 3;
 | ||
|                     case 3:
 | ||
|                         _a.trys.push([3, 5, , 6]);
 | ||
|                         return [4 /*yield*/, this.git.github.pulls.merge(mergeOptions)];
 | ||
|                     case 4:
 | ||
|                         result = _a.sent();
 | ||
|                         mergeStatusCode = result.status;
 | ||
|                         targetSha = result.data.sha;
 | ||
|                         return [3 /*break*/, 6];
 | ||
|                     case 5:
 | ||
|                         e_1 = _a.sent();
 | ||
|                         // Note: Github usually returns `404` as status code if the API request uses a
 | ||
|                         // token with insufficient permissions. Github does this because it doesn't want
 | ||
|                         // to leak whether a repository exists or not. In our case we expect a certain
 | ||
|                         // repository to exist, so we always treat this as a permission failure.
 | ||
|                         if (e_1.status === 403 || e_1.status === 404) {
 | ||
|                             return [2 /*return*/, PullRequestFailure.insufficientPermissionsToMerge()];
 | ||
|                         }
 | ||
|                         throw e_1;
 | ||
|                     case 6:
 | ||
|                         // https://developer.github.com/v3/pulls/#response-if-merge-cannot-be-performed
 | ||
|                         // Pull request cannot be merged due to merge conflicts.
 | ||
|                         if (mergeStatusCode === 405) {
 | ||
|                             return [2 /*return*/, PullRequestFailure.mergeConflicts([githubTargetBranch])];
 | ||
|                         }
 | ||
|                         if (mergeStatusCode !== 200) {
 | ||
|                             return [2 /*return*/, PullRequestFailure.unknownMergeError()];
 | ||
|                         }
 | ||
|                         // If the PR does  not need to be merged into any other target branches,
 | ||
|                         // we exit here as we already completed the merge.
 | ||
|                         if (!cherryPickTargetBranches.length) {
 | ||
|                             return [2 /*return*/, null];
 | ||
|                         }
 | ||
|                         // Refresh the target branch the PR has been merged into through the API. We need
 | ||
|                         // to re-fetch as otherwise we cannot cherry-pick the new commits into the remaining
 | ||
|                         // target branches.
 | ||
|                         this.fetchTargetBranches([githubTargetBranch]);
 | ||
|                         targetCommitsCount = method === 'squash' ? 1 : pullRequest.commitCount;
 | ||
|                         return [4 /*yield*/, this.cherryPickIntoTargetBranches(targetSha + "~" + targetCommitsCount + ".." + targetSha, cherryPickTargetBranches, {
 | ||
|                                 // Commits that have been created by the Github API do not necessarily contain
 | ||
|                                 // a reference to the source pull request (unless the squash strategy is used).
 | ||
|                                 // To ensure that original commits can be found when a commit is viewed in a
 | ||
|                                 // target branch, we add a link to the original commits when cherry-picking.
 | ||
|                                 linkToOriginalCommits: true,
 | ||
|                             })];
 | ||
|                     case 7:
 | ||
|                         failedBranches = _a.sent();
 | ||
|                         // We already checked whether the PR can be cherry-picked into the target branches,
 | ||
|                         // but in case the cherry-pick somehow fails, we still handle the conflicts here. The
 | ||
|                         // commits created through the Github API could be different (i.e. through squash).
 | ||
|                         if (failedBranches.length) {
 | ||
|                             return [2 /*return*/, PullRequestFailure.mergeConflicts(failedBranches)];
 | ||
|                         }
 | ||
|                         this.pushTargetBranchesUpstream(cherryPickTargetBranches);
 | ||
|                         return [2 /*return*/, null];
 | ||
|                 }
 | ||
|             });
 | ||
|         });
 | ||
|     };
 | ||
|     /**
 | ||
|      * Prompts the user for the commit message changes. Unlike as in the autosquash merge
 | ||
|      * strategy, we cannot start an interactive rebase because we merge using the Github API.
 | ||
|      * The Github API only allows modifications to PR title and body for squash merges.
 | ||
|      */
 | ||
|     GithubApiMergeStrategy.prototype._promptCommitMessageEdit = function (pullRequest, mergeOptions) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|             var commitMessage, result, _a, newTitle, newMessage;
 | ||
|             return tslib.__generator(this, function (_b) {
 | ||
|                 switch (_b.label) {
 | ||
|                     case 0: return [4 /*yield*/, this._getDefaultSquashCommitMessage(pullRequest)];
 | ||
|                     case 1:
 | ||
|                         commitMessage = _b.sent();
 | ||
|                         return [4 /*yield*/, inquirer.prompt({
 | ||
|                                 type: 'editor',
 | ||
|                                 name: 'result',
 | ||
|                                 message: 'Please update the commit message',
 | ||
|                                 default: commitMessage,
 | ||
|                             })];
 | ||
|                     case 2:
 | ||
|                         result = (_b.sent()).result;
 | ||
|                         _a = tslib.__read(result.split(COMMIT_HEADER_SEPARATOR)), newTitle = _a[0], newMessage = _a.slice(1);
 | ||
|                         // Update the merge options so that the changes are reflected in there.
 | ||
|                         mergeOptions.commit_title = newTitle + " (#" + pullRequest.prNumber + ")";
 | ||
|                         mergeOptions.commit_message = newMessage.join(COMMIT_HEADER_SEPARATOR);
 | ||
|                         return [2 /*return*/];
 | ||
|                 }
 | ||
|             });
 | ||
|         });
 | ||
|     };
 | ||
|     /**
 | ||
|      * Gets a commit message for the given pull request. Github by default concatenates
 | ||
|      * multiple commit messages if a PR is merged in squash mode. We try to replicate this
 | ||
|      * behavior here so that we have a default commit message that can be fixed up.
 | ||
|      */
 | ||
|     GithubApiMergeStrategy.prototype._getDefaultSquashCommitMessage = function (pullRequest) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|             var commits, messageBase, joinedMessages;
 | ||
|             return tslib.__generator(this, function (_a) {
 | ||
|                 switch (_a.label) {
 | ||
|                     case 0: return [4 /*yield*/, this._getPullRequestCommitMessages(pullRequest)];
 | ||
|                     case 1:
 | ||
|                         commits = (_a.sent())
 | ||
|                             .map(function (message) { return ({ message: message, parsed: parseCommitMessage(message) }); });
 | ||
|                         messageBase = "" + pullRequest.title + COMMIT_HEADER_SEPARATOR;
 | ||
|                         if (commits.length <= 1) {
 | ||
|                             return [2 /*return*/, "" + messageBase + commits[0].parsed.body];
 | ||
|                         }
 | ||
|                         joinedMessages = commits.map(function (c) { return "* " + c.message; }).join(COMMIT_HEADER_SEPARATOR);
 | ||
|                         return [2 /*return*/, "" + messageBase + joinedMessages];
 | ||
|                 }
 | ||
|             });
 | ||
|         });
 | ||
|     };
 | ||
|     /** Gets all commit messages of commits in the pull request. */
 | ||
|     GithubApiMergeStrategy.prototype._getPullRequestCommitMessages = function (_a) {
 | ||
|         var prNumber = _a.prNumber;
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|             var request, allCommits;
 | ||
|             return tslib.__generator(this, function (_b) {
 | ||
|                 switch (_b.label) {
 | ||
|                     case 0:
 | ||
|                         request = this.git.github.pulls.listCommits.endpoint.merge(tslib.__assign(tslib.__assign({}, this.git.remoteParams), { pull_number: prNumber }));
 | ||
|                         return [4 /*yield*/, this.git.github.paginate(request)];
 | ||
|                     case 1:
 | ||
|                         allCommits = _b.sent();
 | ||
|                         return [2 /*return*/, allCommits.map(function (_a) {
 | ||
|                                 var commit = _a.commit;
 | ||
|                                 return commit.message;
 | ||
|                             })];
 | ||
|                 }
 | ||
|             });
 | ||
|         });
 | ||
|     };
 | ||
|     /**
 | ||
|      * Checks if given pull request could be merged into its target branches.
 | ||
|      * @returns A pull request failure if it the PR could not be merged.
 | ||
|      */
 | ||
|     GithubApiMergeStrategy.prototype._checkMergability = function (pullRequest, targetBranches) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|             var revisionRange, failedBranches;
 | ||
|             return tslib.__generator(this, function (_a) {
 | ||
|                 revisionRange = this.getPullRequestRevisionRange(pullRequest);
 | ||
|                 failedBranches = this.cherryPickIntoTargetBranches(revisionRange, targetBranches, { dryRun: true });
 | ||
|                 if (failedBranches.length) {
 | ||
|                     return [2 /*return*/, PullRequestFailure.mergeConflicts(failedBranches)];
 | ||
|                 }
 | ||
|                 return [2 /*return*/, null];
 | ||
|             });
 | ||
|         });
 | ||
|     };
 | ||
|     /** Determines the merge action from the given pull request. */
 | ||
|     GithubApiMergeStrategy.prototype._getMergeActionFromPullRequest = function (_a) {
 | ||
|         var labels = _a.labels;
 | ||
|         if (this._config.labels) {
 | ||
|             var matchingLabel = this._config.labels.find(function (_a) {
 | ||
|                 var pattern = _a.pattern;
 | ||
|                 return labels.some(function (l) { return matchesPattern(l, pattern); });
 | ||
|             });
 | ||
|             if (matchingLabel !== undefined) {
 | ||
|                 return matchingLabel.method;
 | ||
|             }
 | ||
|         }
 | ||
|         return this._config.default;
 | ||
|     };
 | ||
|     return GithubApiMergeStrategy;
 | ||
| }(MergeStrategy));
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Path to the commit message filter script. Git expects this paths to use forward slashes. */
 | ||
| var MSG_FILTER_SCRIPT = path.join(__dirname, './commit-message-filter.js').replace(/\\/g, '/');
 | ||
| /**
 | ||
|  * Merge strategy that does not use the Github API for merging. Instead, it fetches
 | ||
|  * all target branches and the PR locally. The PR is then cherry-picked with autosquash
 | ||
|  * enabled into the target branches. The benefit is the support for fixup and squash commits.
 | ||
|  * A notable downside though is that Github does not show the PR as `Merged` due to non
 | ||
|  * fast-forward merges
 | ||
|  */
 | ||
| var AutosquashMergeStrategy = /** @class */ (function (_super) {
 | ||
|     tslib.__extends(AutosquashMergeStrategy, _super);
 | ||
|     function AutosquashMergeStrategy() {
 | ||
|         return _super !== null && _super.apply(this, arguments) || this;
 | ||
|     }
 | ||
|     /**
 | ||
|      * Merges the specified pull request into the target branches and pushes the target
 | ||
|      * branches upstream. This method requires the temporary target branches to be fetched
 | ||
|      * already as we don't want to fetch the target branches per pull request merge. This
 | ||
|      * would causes unnecessary multiple fetch requests when multiple PRs are merged.
 | ||
|      * @throws {GitCommandError} An unknown Git command error occurred that is not
 | ||
|      *   specific to the pull request merge.
 | ||
|      * @returns A pull request failure or null in case of success.
 | ||
|      */
 | ||
|     AutosquashMergeStrategy.prototype.merge = function (pullRequest) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|             var prNumber, targetBranches, requiredBaseSha, needsCommitMessageFixup, baseSha, revisionRange, branchOrRevisionBeforeRebase, rebaseEnv, failedBranches;
 | ||
|             return tslib.__generator(this, function (_a) {
 | ||
|                 prNumber = pullRequest.prNumber, targetBranches = pullRequest.targetBranches, requiredBaseSha = pullRequest.requiredBaseSha, needsCommitMessageFixup = pullRequest.needsCommitMessageFixup;
 | ||
|                 // In case a required base is specified for this pull request, check if the pull
 | ||
|                 // request contains the given commit. If not, return a pull request failure. This
 | ||
|                 // check is useful for enforcing that PRs are rebased on top of a given commit. e.g.
 | ||
|                 // a commit that changes the codeowner ship validation. PRs which are not rebased
 | ||
|                 // could bypass new codeowner ship rules.
 | ||
|                 if (requiredBaseSha && !this.git.hasCommit(TEMP_PR_HEAD_BRANCH, requiredBaseSha)) {
 | ||
|                     return [2 /*return*/, PullRequestFailure.unsatisfiedBaseSha()];
 | ||
|                 }
 | ||
|                 baseSha = this.git.run(['rev-parse', this.getPullRequestBaseRevision(pullRequest)]).stdout.trim();
 | ||
|                 revisionRange = baseSha + ".." + TEMP_PR_HEAD_BRANCH;
 | ||
|                 branchOrRevisionBeforeRebase = this.git.getCurrentBranchOrRevision();
 | ||
|                 rebaseEnv = needsCommitMessageFixup ? undefined : tslib.__assign(tslib.__assign({}, process.env), { GIT_SEQUENCE_EDITOR: 'true' });
 | ||
|                 this.git.run(['rebase', '--interactive', '--autosquash', baseSha, TEMP_PR_HEAD_BRANCH], { stdio: 'inherit', env: rebaseEnv });
 | ||
|                 // Update pull requests commits to reference the pull request. This matches what
 | ||
|                 // Github does when pull requests are merged through the Web UI. The motivation is
 | ||
|                 // that it should be easy to determine which pull request contained a given commit.
 | ||
|                 // Note: The filter-branch command relies on the working tree, so we want to make sure
 | ||
|                 // that we are on the initial branch or revision where the merge script has been invoked.
 | ||
|                 this.git.run(['checkout', '-f', branchOrRevisionBeforeRebase]);
 | ||
|                 this.git.run(['filter-branch', '-f', '--msg-filter', MSG_FILTER_SCRIPT + " " + prNumber, revisionRange]);
 | ||
|                 failedBranches = this.cherryPickIntoTargetBranches(revisionRange, targetBranches);
 | ||
|                 if (failedBranches.length) {
 | ||
|                     return [2 /*return*/, PullRequestFailure.mergeConflicts(failedBranches)];
 | ||
|                 }
 | ||
|                 this.pushTargetBranchesUpstream(targetBranches);
 | ||
|                 return [2 /*return*/, null];
 | ||
|             });
 | ||
|         });
 | ||
|     };
 | ||
|     return AutosquashMergeStrategy;
 | ||
| }(MergeStrategy));
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Class that accepts a merge script configuration and Github token. It provides
 | ||
|  * a programmatic interface for merging multiple pull requests based on their
 | ||
|  * labels that have been resolved through the merge script configuration.
 | ||
|  */
 | ||
| var PullRequestMergeTask = /** @class */ (function () {
 | ||
|     function PullRequestMergeTask(config, git) {
 | ||
|         this.config = config;
 | ||
|         this.git = git;
 | ||
|     }
 | ||
|     /**
 | ||
|      * Merges the given pull request and pushes it upstream.
 | ||
|      * @param prNumber Pull request that should be merged.
 | ||
|      * @param force Whether non-critical pull request failures should be ignored.
 | ||
|      */
 | ||
|     PullRequestMergeTask.prototype.merge = function (prNumber, force) {
 | ||
|         if (force === void 0) { force = false; }
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|             var hasOauthScopes, pullRequest, _a, strategy, previousBranchOrRevision, failure, e_1;
 | ||
|             var _this = this;
 | ||
|             return tslib.__generator(this, function (_b) {
 | ||
|                 switch (_b.label) {
 | ||
|                     case 0: return [4 /*yield*/, this.git.hasOauthScopes(function (scopes, missing) {
 | ||
|                             if (!scopes.includes('repo')) {
 | ||
|                                 if (_this.config.remote.private) {
 | ||
|                                     missing.push('repo');
 | ||
|                                 }
 | ||
|                                 else if (!scopes.includes('public_repo')) {
 | ||
|                                     missing.push('public_repo');
 | ||
|                                 }
 | ||
|                             }
 | ||
|                         })];
 | ||
|                     case 1:
 | ||
|                         hasOauthScopes = _b.sent();
 | ||
|                         if (hasOauthScopes !== true) {
 | ||
|                             return [2 /*return*/, {
 | ||
|                                     status: 5 /* GITHUB_ERROR */,
 | ||
|                                     failure: PullRequestFailure.insufficientPermissionsToMerge(hasOauthScopes.error)
 | ||
|                                 }];
 | ||
|                         }
 | ||
|                         if (this.git.hasUncommittedChanges()) {
 | ||
|                             return [2 /*return*/, { status: 1 /* DIRTY_WORKING_DIR */ }];
 | ||
|                         }
 | ||
|                         return [4 /*yield*/, loadAndValidatePullRequest(this, prNumber, force)];
 | ||
|                     case 2:
 | ||
|                         pullRequest = _b.sent();
 | ||
|                         if (!isPullRequest(pullRequest)) {
 | ||
|                             return [2 /*return*/, { status: 3 /* FAILED */, failure: pullRequest }];
 | ||
|                         }
 | ||
|                         return [4 /*yield*/, promptConfirm(getTargettedBranchesConfirmationPromptMessage(pullRequest))];
 | ||
|                     case 3:
 | ||
|                         if (!(_b.sent())) {
 | ||
|                             return [2 /*return*/, { status: 4 /* USER_ABORTED */ }];
 | ||
|                         }
 | ||
|                         _a = pullRequest.hasCaretakerNote;
 | ||
|                         if (!_a) return [3 /*break*/, 5];
 | ||
|                         return [4 /*yield*/, promptConfirm(getCaretakerNotePromptMessage(pullRequest))];
 | ||
|                     case 4:
 | ||
|                         _a = !(_b.sent());
 | ||
|                         _b.label = 5;
 | ||
|                     case 5:
 | ||
|                         // If the pull request has a caretaker note applied, raise awareness by prompting
 | ||
|                         // the caretaker. The caretaker can then decide to proceed or abort the merge.
 | ||
|                         if (_a) {
 | ||
|                             return [2 /*return*/, { status: 4 /* USER_ABORTED */ }];
 | ||
|                         }
 | ||
|                         strategy = this.config.githubApiMerge ?
 | ||
|                             new GithubApiMergeStrategy(this.git, this.config.githubApiMerge) :
 | ||
|                             new AutosquashMergeStrategy(this.git);
 | ||
|                         previousBranchOrRevision = null;
 | ||
|                         _b.label = 6;
 | ||
|                     case 6:
 | ||
|                         _b.trys.push([6, 10, 11, 12]);
 | ||
|                         previousBranchOrRevision = this.git.getCurrentBranchOrRevision();
 | ||
|                         // Run preparations for the merge (e.g. fetching branches).
 | ||
|                         return [4 /*yield*/, strategy.prepare(pullRequest)];
 | ||
|                     case 7:
 | ||
|                         // Run preparations for the merge (e.g. fetching branches).
 | ||
|                         _b.sent();
 | ||
|                         return [4 /*yield*/, strategy.merge(pullRequest)];
 | ||
|                     case 8:
 | ||
|                         failure = _b.sent();
 | ||
|                         if (failure !== null) {
 | ||
|                             return [2 /*return*/, { status: 3 /* FAILED */, failure: failure }];
 | ||
|                         }
 | ||
|                         // Switch back to the previous branch. We need to do this before deleting the temporary
 | ||
|                         // branches because we cannot delete branches which are currently checked out.
 | ||
|                         this.git.run(['checkout', '-f', previousBranchOrRevision]);
 | ||
|                         return [4 /*yield*/, strategy.cleanup(pullRequest)];
 | ||
|                     case 9:
 | ||
|                         _b.sent();
 | ||
|                         // Return a successful merge status.
 | ||
|                         return [2 /*return*/, { status: 2 /* SUCCESS */ }];
 | ||
|                     case 10:
 | ||
|                         e_1 = _b.sent();
 | ||
|                         // Catch all git command errors and return a merge result w/ git error status code.
 | ||
|                         // Other unknown errors which aren't caused by a git command are re-thrown.
 | ||
|                         if (e_1 instanceof GitCommandError) {
 | ||
|                             return [2 /*return*/, { status: 0 /* UNKNOWN_GIT_ERROR */ }];
 | ||
|                         }
 | ||
|                         throw e_1;
 | ||
|                     case 11:
 | ||
|                         // Always try to restore the branch if possible. We don't want to leave
 | ||
|                         // the repository in a different state than before.
 | ||
|                         if (previousBranchOrRevision !== null) {
 | ||
|                             this.git.runGraceful(['checkout', '-f', previousBranchOrRevision]);
 | ||
|                         }
 | ||
|                         return [7 /*endfinally*/];
 | ||
|                     case 12: return [2 /*return*/];
 | ||
|                 }
 | ||
|             });
 | ||
|         });
 | ||
|     };
 | ||
|     return PullRequestMergeTask;
 | ||
| }());
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Merges a given pull request based on labels configured in the given merge configuration.
 | ||
|  * Pull requests can be merged with different strategies such as the Github API merge
 | ||
|  * strategy, or the local autosquash strategy. Either strategy has benefits and downsides.
 | ||
|  * More information on these strategies can be found in their dedicated strategy classes.
 | ||
|  *
 | ||
|  * See {@link GithubApiMergeStrategy} and {@link AutosquashMergeStrategy}
 | ||
|  *
 | ||
|  * @param prNumber Number of the pull request that should be merged.
 | ||
|  * @param githubToken Github token used for merging (i.e. fetching and pushing)
 | ||
|  * @param projectRoot Path to the local Git project that is used for merging.
 | ||
|  * @param config Configuration for merging pull requests.
 | ||
|  */
 | ||
| function mergePullRequest(prNumber, githubToken, projectRoot, config) {
 | ||
|     if (projectRoot === void 0) { projectRoot = getRepoBaseDir(); }
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|         /** Performs the merge and returns whether it was successful or not. */
 | ||
|         function performMerge(ignoreFatalErrors) {
 | ||
|             return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|                 var result, e_1;
 | ||
|                 return tslib.__generator(this, function (_a) {
 | ||
|                     switch (_a.label) {
 | ||
|                         case 0:
 | ||
|                             _a.trys.push([0, 3, , 4]);
 | ||
|                             return [4 /*yield*/, api.merge(prNumber, ignoreFatalErrors)];
 | ||
|                         case 1:
 | ||
|                             result = _a.sent();
 | ||
|                             return [4 /*yield*/, handleMergeResult(result, ignoreFatalErrors)];
 | ||
|                         case 2: return [2 /*return*/, _a.sent()];
 | ||
|                         case 3:
 | ||
|                             e_1 = _a.sent();
 | ||
|                             // Catch errors to the Github API for invalid requests. We want to
 | ||
|                             // exit the script with a better explanation of the error.
 | ||
|                             if (e_1 instanceof GithubApiRequestError && e_1.status === 401) {
 | ||
|                                 error(red('Github API request failed. ' + e_1.message));
 | ||
|                                 error(yellow('Please ensure that your provided token is valid.'));
 | ||
|                                 error(yellow("You can generate a token here: " + GITHUB_TOKEN_GENERATE_URL));
 | ||
|                                 process.exit(1);
 | ||
|                             }
 | ||
|                             throw e_1;
 | ||
|                         case 4: return [2 /*return*/];
 | ||
|                     }
 | ||
|                 });
 | ||
|             });
 | ||
|         }
 | ||
|         /**
 | ||
|          * Prompts whether the specified pull request should be forcibly merged. If so, merges
 | ||
|          * the specified pull request forcibly (ignoring non-critical failures).
 | ||
|          * @returns Whether the specified pull request has been forcibly merged.
 | ||
|          */
 | ||
|         function promptAndPerformForceMerge() {
 | ||
|             return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|                 return tslib.__generator(this, function (_a) {
 | ||
|                     switch (_a.label) {
 | ||
|                         case 0: return [4 /*yield*/, promptConfirm('Do you want to forcibly proceed with merging?')];
 | ||
|                         case 1:
 | ||
|                             if (_a.sent()) {
 | ||
|                                 // Perform the merge in force mode. This means that non-fatal failures
 | ||
|                                 // are ignored and the merge continues.
 | ||
|                                 return [2 /*return*/, performMerge(true)];
 | ||
|                             }
 | ||
|                             return [2 /*return*/, false];
 | ||
|                     }
 | ||
|                 });
 | ||
|             });
 | ||
|         }
 | ||
|         /**
 | ||
|          * Handles the merge result by printing console messages, exiting the process
 | ||
|          * based on the result, or by restarting the merge if force mode has been enabled.
 | ||
|          * @returns Whether the merge completed without errors or not.
 | ||
|          */
 | ||
|         function handleMergeResult(result, disableForceMergePrompt) {
 | ||
|             if (disableForceMergePrompt === void 0) { disableForceMergePrompt = false; }
 | ||
|             return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|                 var failure, status, canForciblyMerge, _a;
 | ||
|                 return tslib.__generator(this, function (_b) {
 | ||
|                     switch (_b.label) {
 | ||
|                         case 0:
 | ||
|                             failure = result.failure, status = result.status;
 | ||
|                             canForciblyMerge = failure && failure.nonFatal;
 | ||
|                             _a = status;
 | ||
|                             switch (_a) {
 | ||
|                                 case 2 /* SUCCESS */: return [3 /*break*/, 1];
 | ||
|                                 case 1 /* DIRTY_WORKING_DIR */: return [3 /*break*/, 2];
 | ||
|                                 case 0 /* UNKNOWN_GIT_ERROR */: return [3 /*break*/, 3];
 | ||
|                                 case 5 /* GITHUB_ERROR */: return [3 /*break*/, 4];
 | ||
|                                 case 4 /* USER_ABORTED */: return [3 /*break*/, 5];
 | ||
|                                 case 3 /* FAILED */: return [3 /*break*/, 6];
 | ||
|                             }
 | ||
|                             return [3 /*break*/, 9];
 | ||
|                         case 1:
 | ||
|                             info(green("Successfully merged the pull request: #" + prNumber));
 | ||
|                             return [2 /*return*/, true];
 | ||
|                         case 2:
 | ||
|                             error(red("Local working repository not clean. Please make sure there are " +
 | ||
|                                 "no uncommitted changes."));
 | ||
|                             return [2 /*return*/, false];
 | ||
|                         case 3:
 | ||
|                             error(red('An unknown Git error has been thrown. Please check the output ' +
 | ||
|                                 'above for details.'));
 | ||
|                             return [2 /*return*/, false];
 | ||
|                         case 4:
 | ||
|                             error(red('An error related to interacting with Github has been discovered.'));
 | ||
|                             error(failure.message);
 | ||
|                             return [2 /*return*/, false];
 | ||
|                         case 5:
 | ||
|                             info("Merge of pull request has been aborted manually: #" + prNumber);
 | ||
|                             return [2 /*return*/, true];
 | ||
|                         case 6:
 | ||
|                             error(yellow("Could not merge the specified pull request."));
 | ||
|                             error(red(failure.message));
 | ||
|                             if (!(canForciblyMerge && !disableForceMergePrompt)) return [3 /*break*/, 8];
 | ||
|                             info();
 | ||
|                             info(yellow('The pull request above failed due to non-critical errors.'));
 | ||
|                             info(yellow("This error can be forcibly ignored if desired."));
 | ||
|                             return [4 /*yield*/, promptAndPerformForceMerge()];
 | ||
|                         case 7: return [2 /*return*/, _b.sent()];
 | ||
|                         case 8: return [2 /*return*/, false];
 | ||
|                         case 9: throw Error("Unexpected merge result: " + status);
 | ||
|                     }
 | ||
|                 });
 | ||
|             });
 | ||
|         }
 | ||
|         var api;
 | ||
|         return tslib.__generator(this, function (_a) {
 | ||
|             switch (_a.label) {
 | ||
|                 case 0:
 | ||
|                     // Set the environment variable to skip all git commit hooks triggered by husky. We are unable to
 | ||
|                     // rely on `--no-verify` as some hooks still run, notably the `prepare-commit-msg` hook.
 | ||
|                     process.env['HUSKY_SKIP_HOOKS'] = '1';
 | ||
|                     return [4 /*yield*/, createPullRequestMergeTask(githubToken, projectRoot, config)];
 | ||
|                 case 1:
 | ||
|                     api = _a.sent();
 | ||
|                     return [4 /*yield*/, performMerge(false)];
 | ||
|                 case 2:
 | ||
|                     // Perform the merge. Force mode can be activated through a command line flag.
 | ||
|                     // Alternatively, if the merge fails with non-fatal failures, the script
 | ||
|                     // will prompt whether it should rerun in force mode.
 | ||
|                     if (!(_a.sent())) {
 | ||
|                         process.exit(1);
 | ||
|                     }
 | ||
|                     return [2 /*return*/];
 | ||
|             }
 | ||
|         });
 | ||
|     });
 | ||
| }
 | ||
| /**
 | ||
|  * Creates the pull request merge task from the given Github token, project root
 | ||
|  * and optional explicit configuration. An explicit configuration can be specified
 | ||
|  * when the merge script is used outside of a `ng-dev` configured repository.
 | ||
|  */
 | ||
| function createPullRequestMergeTask(githubToken, projectRoot, explicitConfig) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|         var git_1, devInfraConfig, git, _a, config, errors;
 | ||
|         return tslib.__generator(this, function (_b) {
 | ||
|             switch (_b.label) {
 | ||
|                 case 0:
 | ||
|                     if (explicitConfig !== undefined) {
 | ||
|                         git_1 = new GitClient(githubToken, { github: explicitConfig.remote }, projectRoot);
 | ||
|                         return [2 /*return*/, new PullRequestMergeTask(explicitConfig, git_1)];
 | ||
|                     }
 | ||
|                     devInfraConfig = getConfig();
 | ||
|                     git = new GitClient(githubToken, devInfraConfig, projectRoot);
 | ||
|                     return [4 /*yield*/, loadAndValidateConfig(devInfraConfig, git.github)];
 | ||
|                 case 1:
 | ||
|                     _a = _b.sent(), config = _a.config, errors = _a.errors;
 | ||
|                     if (errors) {
 | ||
|                         error(red('Invalid merge configuration:'));
 | ||
|                         errors.forEach(function (desc) { return error(yellow("  -  " + desc)); });
 | ||
|                         process.exit(1);
 | ||
|                     }
 | ||
|                     // Set the remote so that the merge tool has access to information about
 | ||
|                     // the remote it intends to merge to.
 | ||
|                     config.remote = devInfraConfig.github;
 | ||
|                     // We can cast this to a merge config with remote because we always set the
 | ||
|                     // remote above.
 | ||
|                     return [2 /*return*/, new PullRequestMergeTask(config, git)];
 | ||
|             }
 | ||
|         });
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Builds the options for the merge command. */
 | ||
| function buildMergeCommand(yargs) {
 | ||
|     return addGithubTokenOption(yargs).help().strict().positional('pr-number', { demandOption: true, type: 'number' });
 | ||
| }
 | ||
| /** Handles the merge command. i.e. performs the merge of a specified pull request. */
 | ||
| function handleMergeCommand(_a) {
 | ||
|     var pr = _a["pr-number"], githubToken = _a.githubToken;
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function () {
 | ||
|         return tslib.__generator(this, function (_b) {
 | ||
|             switch (_b.label) {
 | ||
|                 case 0: return [4 /*yield*/, mergePullRequest(pr, githubToken)];
 | ||
|                 case 1:
 | ||
|                     _b.sent();
 | ||
|                     return [2 /*return*/];
 | ||
|             }
 | ||
|         });
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /* GraphQL schema for the response body for each pending PR. */
 | ||
| const PR_SCHEMA$2 = {
 | ||
|     state: typedGraphqlify.types.string,
 | ||
|     maintainerCanModify: typedGraphqlify.types.boolean,
 | ||
|     viewerDidAuthor: typedGraphqlify.types.boolean,
 | ||
|     headRefOid: typedGraphqlify.types.string,
 | ||
|     headRef: {
 | ||
|         name: typedGraphqlify.types.string,
 | ||
|         repository: {
 | ||
|             url: typedGraphqlify.types.string,
 | ||
|             nameWithOwner: typedGraphqlify.types.string,
 | ||
|         },
 | ||
|     },
 | ||
|     baseRef: {
 | ||
|         name: typedGraphqlify.types.string,
 | ||
|         repository: {
 | ||
|             url: typedGraphqlify.types.string,
 | ||
|             nameWithOwner: typedGraphqlify.types.string,
 | ||
|         },
 | ||
|     },
 | ||
| };
 | ||
| /**
 | ||
|  * Rebase the provided PR onto its merge target branch, and push up the resulting
 | ||
|  * commit to the PRs repository.
 | ||
|  */
 | ||
| function rebasePr(prNumber, githubToken, config = getConfig()) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const git = new GitClient(githubToken);
 | ||
|         // TODO: Rely on a common assertNoLocalChanges function.
 | ||
|         if (git.hasLocalChanges()) {
 | ||
|             error('Cannot perform rebase of PR with local changes.');
 | ||
|             process.exit(1);
 | ||
|         }
 | ||
|         /**
 | ||
|          * The branch or revision originally checked out before this method performed
 | ||
|          * any Git operations that may change the working branch.
 | ||
|          */
 | ||
|         const previousBranchOrRevision = git.getCurrentBranchOrRevision();
 | ||
|         /* Get the PR information from Github. */
 | ||
|         const pr = yield getPr(PR_SCHEMA$2, prNumber, git);
 | ||
|         const headRefName = pr.headRef.name;
 | ||
|         const baseRefName = pr.baseRef.name;
 | ||
|         const fullHeadRef = `${pr.headRef.repository.nameWithOwner}:${headRefName}`;
 | ||
|         const fullBaseRef = `${pr.baseRef.repository.nameWithOwner}:${baseRefName}`;
 | ||
|         const headRefUrl = addTokenToGitHttpsUrl(pr.headRef.repository.url, githubToken);
 | ||
|         const baseRefUrl = addTokenToGitHttpsUrl(pr.baseRef.repository.url, githubToken);
 | ||
|         // Note: Since we use a detached head for rebasing the PR and therefore do not have
 | ||
|         // remote-tracking branches configured, we need to set our expected ref and SHA. This
 | ||
|         // allows us to use `--force-with-lease` for the detached head while ensuring that we
 | ||
|         // never accidentally override upstream changes that have been pushed in the meanwhile.
 | ||
|         // See:
 | ||
|         // https://git-scm.com/docs/git-push#Documentation/git-push.txt---force-with-leaseltrefnamegtltexpectgt
 | ||
|         const forceWithLeaseFlag = `--force-with-lease=${headRefName}:${pr.headRefOid}`;
 | ||
|         // If the PR does not allow maintainers to modify it, exit as the rebased PR cannot
 | ||
|         // be pushed up.
 | ||
|         if (!pr.maintainerCanModify && !pr.viewerDidAuthor) {
 | ||
|             error(`Cannot rebase as you did not author the PR and the PR does not allow maintainers` +
 | ||
|                 `to modify the PR`);
 | ||
|             process.exit(1);
 | ||
|         }
 | ||
|         try {
 | ||
|             // Fetch the branch at the commit of the PR, and check it out in a detached state.
 | ||
|             info(`Checking out PR #${prNumber} from ${fullHeadRef}`);
 | ||
|             git.run(['fetch', '-q', headRefUrl, headRefName]);
 | ||
|             git.run(['checkout', '-q', '--detach', 'FETCH_HEAD']);
 | ||
|             // Fetch the PRs target branch and rebase onto it.
 | ||
|             info(`Fetching ${fullBaseRef} to rebase #${prNumber} on`);
 | ||
|             git.run(['fetch', '-q', baseRefUrl, baseRefName]);
 | ||
|             const commonAncestorSha = git.run(['merge-base', 'HEAD', 'FETCH_HEAD']).stdout.trim();
 | ||
|             const commits = parseCommitMessagesForRange(`${commonAncestorSha}..HEAD`);
 | ||
|             let squashFixups = commits.filter((commit) => commit.isFixup).length === 0 ?
 | ||
|                 false :
 | ||
|                 yield promptConfirm(`PR #${prNumber} contains fixup commits, would you like to squash them during rebase?`, true);
 | ||
|             info(`Attempting to rebase PR #${prNumber} on ${fullBaseRef}`);
 | ||
|             /**
 | ||
|              * Tuple of flags to be added to the rebase command and env object to run the git command.
 | ||
|              *
 | ||
|              * Additional flags to perform the autosquashing are added when the user confirm squashing of
 | ||
|              * fixup commits should occur.
 | ||
|              */
 | ||
|             const [flags, env] = squashFixups ?
 | ||
|                 [['--interactive', '--autosquash'], Object.assign(Object.assign({}, process.env), { GIT_SEQUENCE_EDITOR: 'true' })] :
 | ||
|                 [[], undefined];
 | ||
|             const rebaseResult = git.runGraceful(['rebase', ...flags, 'FETCH_HEAD'], { env: env });
 | ||
|             // If the rebase was clean, push the rebased PR up to the authors fork.
 | ||
|             if (rebaseResult.status === 0) {
 | ||
|                 info(`Rebase was able to complete automatically without conflicts`);
 | ||
|                 info(`Pushing rebased PR #${prNumber} to ${fullHeadRef}`);
 | ||
|                 git.run(['push', headRefUrl, `HEAD:${headRefName}`, forceWithLeaseFlag]);
 | ||
|                 info(`Rebased and updated PR #${prNumber}`);
 | ||
|                 git.checkout(previousBranchOrRevision, true);
 | ||
|                 process.exit(0);
 | ||
|             }
 | ||
|         }
 | ||
|         catch (err) {
 | ||
|             error(err.message);
 | ||
|             git.checkout(previousBranchOrRevision, true);
 | ||
|             process.exit(1);
 | ||
|         }
 | ||
|         // On automatic rebase failures, prompt to choose if the rebase should be continued
 | ||
|         // manually or aborted now.
 | ||
|         info(`Rebase was unable to complete automatically without conflicts.`);
 | ||
|         // If the command is run in a non-CI environment, prompt to format the files immediately.
 | ||
|         const continueRebase = process.env['CI'] === undefined && (yield promptConfirm('Manually complete rebase?'));
 | ||
|         if (continueRebase) {
 | ||
|             info(`After manually completing rebase, run the following command to update PR #${prNumber}:`);
 | ||
|             info(` $ git push ${pr.headRef.repository.url} HEAD:${headRefName} ${forceWithLeaseFlag}`);
 | ||
|             info();
 | ||
|             info(`To abort the rebase and return to the state of the repository before this command`);
 | ||
|             info(`run the following command:`);
 | ||
|             info(` $ git rebase --abort && git reset --hard && git checkout ${previousBranchOrRevision}`);
 | ||
|             process.exit(1);
 | ||
|         }
 | ||
|         else {
 | ||
|             info(`Cleaning up git state, and restoring previous state.`);
 | ||
|         }
 | ||
|         git.checkout(previousBranchOrRevision, true);
 | ||
|         process.exit(1);
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Builds the rebase pull request command. */
 | ||
| function buildRebaseCommand(yargs) {
 | ||
|     return addGithubTokenOption(yargs).positional('prNumber', { type: 'number', demandOption: true });
 | ||
| }
 | ||
| /** Handles the rebase pull request command. */
 | ||
| function handleRebaseCommand({ prNumber, githubToken }) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         yield rebasePr(prNumber, githubToken);
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Build the parser for pull request commands. */
 | ||
| function buildPrParser(localYargs) {
 | ||
|     return localYargs.help()
 | ||
|         .strict()
 | ||
|         .demandCommand()
 | ||
|         .command('merge <pr-number>', 'Merge pull requests', buildMergeCommand, handleMergeCommand)
 | ||
|         .command('discover-new-conflicts <pr-number>', 'Check if a pending PR causes new conflicts for other pending PRs', buildDiscoverNewConflictsCommand, handleDiscoverNewConflictsCommand)
 | ||
|         .command('rebase <pr-number>', 'Rebase a pending PR and push the rebased commits back to Github', buildRebaseCommand, handleRebaseCommand)
 | ||
|         .command(CheckoutCommandModule)
 | ||
|         .command(CheckTargetBranchesModule);
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Create logs for each pullapprove group result. */
 | ||
| function logGroup(group, conditionsToPrint, printMessageFn = info) {
 | ||
|     const conditions = group[conditionsToPrint];
 | ||
|     printMessageFn.group(`[${group.groupName}]`);
 | ||
|     if (conditions.length) {
 | ||
|         conditions.forEach(groupCondition => {
 | ||
|             const count = groupCondition.matchedFiles.size;
 | ||
|             if (conditionsToPrint === 'unverifiableConditions') {
 | ||
|                 printMessageFn(`${groupCondition.expression}`);
 | ||
|             }
 | ||
|             else {
 | ||
|                 printMessageFn(`${count} ${count === 1 ? 'match' : 'matches'} - ${groupCondition.expression}`);
 | ||
|             }
 | ||
|         });
 | ||
|         printMessageFn.groupEnd();
 | ||
|     }
 | ||
| }
 | ||
| /** Logs a header within a text drawn box. */
 | ||
| function logHeader(...params) {
 | ||
|     const totalWidth = 80;
 | ||
|     const fillWidth = totalWidth - 2;
 | ||
|     const headerText = params.join(' ').substr(0, fillWidth);
 | ||
|     const leftSpace = Math.ceil((fillWidth - headerText.length) / 2);
 | ||
|     const rightSpace = fillWidth - leftSpace - headerText.length;
 | ||
|     const fill = (count, content) => content.repeat(count);
 | ||
|     info(`┌${fill(fillWidth, '─')}┐`);
 | ||
|     info(`│${fill(leftSpace, ' ')}${headerText}${fill(rightSpace, ' ')}│`);
 | ||
|     info(`└${fill(fillWidth, '─')}┘`);
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Map that holds patterns and their corresponding Minimatch globs. */
 | ||
| const patternCache = new Map();
 | ||
| /**
 | ||
|  * Gets a glob for the given pattern. The cached glob will be returned
 | ||
|  * if available. Otherwise a new glob will be created and cached.
 | ||
|  */
 | ||
| function getOrCreateGlob(pattern) {
 | ||
|     if (patternCache.has(pattern)) {
 | ||
|         return patternCache.get(pattern);
 | ||
|     }
 | ||
|     const glob = new minimatch.Minimatch(pattern, { dot: true });
 | ||
|     patternCache.set(pattern, glob);
 | ||
|     return glob;
 | ||
| }
 | ||
| 
 | ||
| class PullApproveGroupStateDependencyError extends Error {
 | ||
|     constructor(message) {
 | ||
|         super(message);
 | ||
|         // Set the prototype explicitly because in ES5, the prototype is accidentally
 | ||
|         // lost due to a limitation in down-leveling.
 | ||
|         // https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work.
 | ||
|         Object.setPrototypeOf(this, PullApproveGroupStateDependencyError.prototype);
 | ||
|         // Error names are displayed in their stack but can't be set in the constructor.
 | ||
|         this.name = PullApproveGroupStateDependencyError.name;
 | ||
|     }
 | ||
| }
 | ||
| /**
 | ||
|  * Superset of a native array. The superset provides methods which mimic the
 | ||
|  * list data structure used in PullApprove for files in conditions.
 | ||
|  */
 | ||
| class PullApproveStringArray extends Array {
 | ||
|     constructor(...elements) {
 | ||
|         super(...elements);
 | ||
|         // Set the prototype explicitly because in ES5, the prototype is accidentally
 | ||
|         // lost due to a limitation in down-leveling.
 | ||
|         // https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work.
 | ||
|         Object.setPrototypeOf(this, PullApproveStringArray.prototype);
 | ||
|     }
 | ||
|     /** Returns a new array which only includes files that match the given pattern. */
 | ||
|     include(pattern) {
 | ||
|         return new PullApproveStringArray(...this.filter(s => getOrCreateGlob(pattern).match(s)));
 | ||
|     }
 | ||
|     /** Returns a new array which only includes files that did not match the given pattern. */
 | ||
|     exclude(pattern) {
 | ||
|         return new PullApproveStringArray(...this.filter(s => !getOrCreateGlob(pattern).match(s)));
 | ||
|     }
 | ||
| }
 | ||
| /**
 | ||
|  * Superset of a native array. The superset provides methods which mimic the
 | ||
|  * list data structure used in PullApprove for groups in conditions.
 | ||
|  */
 | ||
| class PullApproveGroupArray extends Array {
 | ||
|     constructor(...elements) {
 | ||
|         super(...elements);
 | ||
|         // Set the prototype explicitly because in ES5, the prototype is accidentally
 | ||
|         // lost due to a limitation in down-leveling.
 | ||
|         // https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work.
 | ||
|         Object.setPrototypeOf(this, PullApproveGroupArray.prototype);
 | ||
|     }
 | ||
|     include(pattern) {
 | ||
|         return new PullApproveGroupArray(...this.filter(s => s.groupName.match(pattern)));
 | ||
|     }
 | ||
|     /** Returns a new array which only includes files that did not match the given pattern. */
 | ||
|     exclude(pattern) {
 | ||
|         return new PullApproveGroupArray(...this.filter(s => s.groupName.match(pattern)));
 | ||
|     }
 | ||
|     get pending() {
 | ||
|         throw new PullApproveGroupStateDependencyError();
 | ||
|     }
 | ||
|     get active() {
 | ||
|         throw new PullApproveGroupStateDependencyError();
 | ||
|     }
 | ||
|     get inactive() {
 | ||
|         throw new PullApproveGroupStateDependencyError();
 | ||
|     }
 | ||
|     get rejected() {
 | ||
|         throw new PullApproveGroupStateDependencyError();
 | ||
|     }
 | ||
|     get names() {
 | ||
|         return this.map(g => g.groupName);
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Context that is provided to conditions. Conditions can use various helpers
 | ||
|  * that PullApprove provides. We try to mock them here. Consult the official
 | ||
|  * docs for more details: https://docs.pullapprove.com/config/conditions.
 | ||
|  */
 | ||
| const conditionContext = {
 | ||
|     'len': (value) => value.length,
 | ||
|     'contains_any_globs': (files, patterns) => {
 | ||
|         // Note: Do not always create globs for the same pattern again. This method
 | ||
|         // could be called for each source file. Creating glob's is expensive.
 | ||
|         return files.some(f => patterns.some(pattern => getOrCreateGlob(pattern).match(f)));
 | ||
|     },
 | ||
| };
 | ||
| /**
 | ||
|  * Converts a given condition to a function that accepts a set of files. The returned
 | ||
|  * function can be called to check if the set of files matches the condition.
 | ||
|  */
 | ||
| function convertConditionToFunction(expr) {
 | ||
|     // Creates a dynamic function with the specified expression.
 | ||
|     // The first parameter will be `files` as that corresponds to the supported `files` variable that
 | ||
|     // can be accessed in PullApprove condition expressions. The second parameter is the list of
 | ||
|     // PullApproveGroups that are accessible in the condition expressions. The followed parameters
 | ||
|     // correspond to other context variables provided by PullApprove for conditions.
 | ||
|     const evaluateFn = new Function('files', 'groups', ...Object.keys(conditionContext), `
 | ||
|     return (${transformExpressionToJs(expr)});
 | ||
|   `);
 | ||
|     // Create a function that calls the dynamically constructed function which mimics
 | ||
|     // the condition expression that is usually evaluated with Python in PullApprove.
 | ||
|     return (files, groups) => {
 | ||
|         const result = evaluateFn(new PullApproveStringArray(...files), new PullApproveGroupArray(...groups), ...Object.values(conditionContext));
 | ||
|         // If an array is returned, we consider the condition as active if the array is not
 | ||
|         // empty. This matches PullApprove's condition evaluation that is based on Python.
 | ||
|         if (Array.isArray(result)) {
 | ||
|             return result.length !== 0;
 | ||
|         }
 | ||
|         return !!result;
 | ||
|     };
 | ||
| }
 | ||
| /**
 | ||
|  * Transforms a condition expression from PullApprove that is based on python
 | ||
|  * so that it can be run inside JavaScript. Current transformations:
 | ||
|  *   1. `not <..>` -> `!<..>`
 | ||
|  */
 | ||
| function transformExpressionToJs(expression) {
 | ||
|     return expression.replace(/not\s+/g, '!');
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| // Regular expression that matches conditions for the global approval.
 | ||
| const GLOBAL_APPROVAL_CONDITION_REGEX = /^"global-(docs-)?approvers" not in groups.approved$/;
 | ||
| // Name of the PullApprove group that serves as fallback. This group should never capture
 | ||
| // any conditions as it would always match specified files. This is not desired as we want
 | ||
| // to figure out as part of this tool, whether there actually are unmatched files.
 | ||
| const FALLBACK_GROUP_NAME = 'fallback';
 | ||
| /** A PullApprove group to be able to test files against. */
 | ||
| class PullApproveGroup {
 | ||
|     constructor(groupName, config, precedingGroups = []) {
 | ||
|         this.groupName = groupName;
 | ||
|         this.precedingGroups = precedingGroups;
 | ||
|         /** List of conditions for the group. */
 | ||
|         this.conditions = [];
 | ||
|         this._captureConditions(config);
 | ||
|     }
 | ||
|     _captureConditions(config) {
 | ||
|         if (config.conditions && this.groupName !== FALLBACK_GROUP_NAME) {
 | ||
|             return config.conditions.forEach(condition => {
 | ||
|                 const expression = condition.trim();
 | ||
|                 if (expression.match(GLOBAL_APPROVAL_CONDITION_REGEX)) {
 | ||
|                     // Currently a noop as we don't take any action for global approval conditions.
 | ||
|                     return;
 | ||
|                 }
 | ||
|                 try {
 | ||
|                     this.conditions.push({
 | ||
|                         expression,
 | ||
|                         checkFn: convertConditionToFunction(expression),
 | ||
|                         matchedFiles: new Set(),
 | ||
|                         unverifiable: false,
 | ||
|                     });
 | ||
|                 }
 | ||
|                 catch (e) {
 | ||
|                     error(`Could not parse condition in group: ${this.groupName}`);
 | ||
|                     error(` - ${expression}`);
 | ||
|                     error(`Error:`);
 | ||
|                     error(e.message);
 | ||
|                     error(e.stack);
 | ||
|                     process.exit(1);
 | ||
|                 }
 | ||
|             });
 | ||
|         }
 | ||
|     }
 | ||
|     /**
 | ||
|      * Tests a provided file path to determine if it would be considered matched by
 | ||
|      * the pull approve group's conditions.
 | ||
|      */
 | ||
|     testFile(filePath) {
 | ||
|         return this.conditions.every((condition) => {
 | ||
|             const { matchedFiles, checkFn, expression } = condition;
 | ||
|             try {
 | ||
|                 const matchesFile = checkFn([filePath], this.precedingGroups);
 | ||
|                 if (matchesFile) {
 | ||
|                     matchedFiles.add(filePath);
 | ||
|                 }
 | ||
|                 return matchesFile;
 | ||
|             }
 | ||
|             catch (e) {
 | ||
|                 // In the case of a condition that depends on the state of groups we want to
 | ||
|                 // ignore that the verification can't accurately evaluate the condition and then
 | ||
|                 // continue processing. Other types of errors fail the verification, as conditions
 | ||
|                 // should otherwise be able to execute without throwing.
 | ||
|                 if (e instanceof PullApproveGroupStateDependencyError) {
 | ||
|                     condition.unverifiable = true;
 | ||
|                     // Return true so that `this.conditions.every` can continue evaluating.
 | ||
|                     return true;
 | ||
|                 }
 | ||
|                 else {
 | ||
|                     const errMessage = `Condition could not be evaluated: \n\n` +
 | ||
|                         `From the [${this.groupName}] group:\n` +
 | ||
|                         ` - ${expression}` +
 | ||
|                         `\n\n${e.message} ${e.stack}\n\n`;
 | ||
|                     error(errMessage);
 | ||
|                     process.exit(1);
 | ||
|                 }
 | ||
|             }
 | ||
|         });
 | ||
|     }
 | ||
|     /** Retrieve the results for the Group, all matched and unmatched conditions. */
 | ||
|     getResults() {
 | ||
|         const matchedConditions = this.conditions.filter(c => c.matchedFiles.size > 0);
 | ||
|         const unmatchedConditions = this.conditions.filter(c => c.matchedFiles.size === 0 && !c.unverifiable);
 | ||
|         const unverifiableConditions = this.conditions.filter(c => c.unverifiable);
 | ||
|         return {
 | ||
|             matchedConditions,
 | ||
|             matchedCount: matchedConditions.length,
 | ||
|             unmatchedConditions,
 | ||
|             unmatchedCount: unmatchedConditions.length,
 | ||
|             unverifiableConditions,
 | ||
|             groupName: this.groupName,
 | ||
|         };
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| function parsePullApproveYaml(rawYaml) {
 | ||
|     return yaml.parse(rawYaml, { merge: true });
 | ||
| }
 | ||
| /** Parses all of the groups defined in the pullapprove yaml. */
 | ||
| function getGroupsFromYaml(pullApproveYamlRaw) {
 | ||
|     /** JSON representation of the pullapprove yaml file. */
 | ||
|     const pullApprove = parsePullApproveYaml(pullApproveYamlRaw);
 | ||
|     return Object.entries(pullApprove.groups).reduce((groups, [groupName, group]) => {
 | ||
|         return groups.concat(new PullApproveGroup(groupName, group, groups));
 | ||
|     }, []);
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| function verify$1() {
 | ||
|     /** Full path to PullApprove config file */
 | ||
|     const PULL_APPROVE_YAML_PATH = path.resolve(getRepoBaseDir(), '.pullapprove.yml');
 | ||
|     /** All tracked files in the repository. */
 | ||
|     const REPO_FILES = allFiles();
 | ||
|     /** The pull approve config file. */
 | ||
|     const pullApproveYamlRaw = fs.readFileSync(PULL_APPROVE_YAML_PATH, 'utf8');
 | ||
|     /** All of the groups defined in the pullapprove yaml. */
 | ||
|     const groups = getGroupsFromYaml(pullApproveYamlRaw);
 | ||
|     /**
 | ||
|      * PullApprove groups without conditions. These are skipped in the verification
 | ||
|      * as those would always be active and cause zero unmatched files.
 | ||
|      */
 | ||
|     const groupsSkipped = groups.filter(group => !group.conditions.length);
 | ||
|     /** PullApprove groups with conditions. */
 | ||
|     const groupsWithConditions = groups.filter(group => !!group.conditions.length);
 | ||
|     /** Files which are matched by at least one group. */
 | ||
|     const matchedFiles = [];
 | ||
|     /** Files which are not matched by at least one group. */
 | ||
|     const unmatchedFiles = [];
 | ||
|     // Test each file in the repo against each group for being matched.
 | ||
|     REPO_FILES.forEach((file) => {
 | ||
|         if (groupsWithConditions.filter(group => group.testFile(file)).length) {
 | ||
|             matchedFiles.push(file);
 | ||
|         }
 | ||
|         else {
 | ||
|             unmatchedFiles.push(file);
 | ||
|         }
 | ||
|     });
 | ||
|     /** Results for each group */
 | ||
|     const resultsByGroup = groupsWithConditions.map(group => group.getResults());
 | ||
|     /**
 | ||
|      * Whether all group condition lines match at least one file and all files
 | ||
|      * are matched by at least one group.
 | ||
|      */
 | ||
|     const verificationSucceeded = resultsByGroup.every(r => !r.unmatchedCount) && !unmatchedFiles.length;
 | ||
|     /**
 | ||
|      * Overall result
 | ||
|      */
 | ||
|     logHeader('Overall Result');
 | ||
|     if (verificationSucceeded) {
 | ||
|         info('PullApprove verification succeeded!');
 | ||
|     }
 | ||
|     else {
 | ||
|         info(`PullApprove verification failed.`);
 | ||
|         info();
 | ||
|         info(`Please update '.pullapprove.yml' to ensure that all necessary`);
 | ||
|         info(`files/directories have owners and all patterns that appear in`);
 | ||
|         info(`the file correspond to actual files/directories in the repo.`);
 | ||
|     }
 | ||
|     /**
 | ||
|      * File by file Summary
 | ||
|      */
 | ||
|     logHeader('PullApprove results by file');
 | ||
|     info.group(`Matched Files (${matchedFiles.length} files)`);
 | ||
|     matchedFiles.forEach(file => debug(file));
 | ||
|     info.groupEnd();
 | ||
|     info.group(`Unmatched Files (${unmatchedFiles.length} files)`);
 | ||
|     unmatchedFiles.forEach(file => info(file));
 | ||
|     info.groupEnd();
 | ||
|     /**
 | ||
|      * Group by group Summary
 | ||
|      */
 | ||
|     logHeader('PullApprove results by group');
 | ||
|     info.group(`Groups skipped (${groupsSkipped.length} groups)`);
 | ||
|     groupsSkipped.forEach(group => debug(`${group.groupName}`));
 | ||
|     info.groupEnd();
 | ||
|     const matchedGroups = resultsByGroup.filter(group => !group.unmatchedCount);
 | ||
|     info.group(`Matched conditions by Group (${matchedGroups.length} groups)`);
 | ||
|     matchedGroups.forEach(group => logGroup(group, 'matchedConditions', debug));
 | ||
|     info.groupEnd();
 | ||
|     const unmatchedGroups = resultsByGroup.filter(group => group.unmatchedCount);
 | ||
|     info.group(`Unmatched conditions by Group (${unmatchedGroups.length} groups)`);
 | ||
|     unmatchedGroups.forEach(group => logGroup(group, 'unmatchedConditions'));
 | ||
|     info.groupEnd();
 | ||
|     const unverifiableConditionsInGroups = resultsByGroup.filter(group => group.unverifiableConditions.length > 0);
 | ||
|     info.group(`Unverifiable conditions by Group (${unverifiableConditionsInGroups.length} groups)`);
 | ||
|     unverifiableConditionsInGroups.forEach(group => logGroup(group, 'unverifiableConditions'));
 | ||
|     info.groupEnd();
 | ||
|     // Provide correct exit code based on verification success.
 | ||
|     process.exit(verificationSucceeded ? 0 : 1);
 | ||
| }
 | ||
| 
 | ||
| /** Build the parser for the pullapprove commands. */
 | ||
| function buildPullapproveParser(localYargs) {
 | ||
|     return localYargs.help().strict().demandCommand().command('verify', 'Verify the pullapprove config', {}, () => verify$1());
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Retrieve and validate the config as `ReleaseConfig`. */
 | ||
| function getReleaseConfig(config = getConfig()) {
 | ||
|     var _a, _b, _c;
 | ||
|     // List of errors encountered validating the config.
 | ||
|     const errors = [];
 | ||
|     if (config.release === undefined) {
 | ||
|         errors.push(`No configuration defined for "release"`);
 | ||
|     }
 | ||
|     if (((_a = config.release) === null || _a === void 0 ? void 0 : _a.npmPackages) === undefined) {
 | ||
|         errors.push(`No "npmPackages" configured for releasing.`);
 | ||
|     }
 | ||
|     if (((_b = config.release) === null || _b === void 0 ? void 0 : _b.buildPackages) === undefined) {
 | ||
|         errors.push(`No "buildPackages" function configured for releasing.`);
 | ||
|     }
 | ||
|     if (((_c = config.release) === null || _c === void 0 ? void 0 : _c.generateReleaseNotesForHead) === undefined) {
 | ||
|         errors.push(`No "generateReleaseNotesForHead" function configured for releasing.`);
 | ||
|     }
 | ||
|     assertNoErrors(errors);
 | ||
|     return config.release;
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Builds the release output without polluting the process stdout. Build scripts commonly
 | ||
|  * print messages to stderr or stdout. This is fine in most cases, but sometimes other tooling
 | ||
|  * reserves stdout for data transfer (e.g. when `ng release build --json` is invoked). To not
 | ||
|  * pollute the stdout in such cases, we launch a child process for building the release packages
 | ||
|  * and redirect all stdout output to the stderr channel (which can be read in the terminal).
 | ||
|  */
 | ||
| function buildReleaseOutput() {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         return new Promise(resolve => {
 | ||
|             const buildProcess = child_process.fork(require.resolve('./build-worker'), [], {
 | ||
|                 // The stdio option is set to redirect any "stdout" output directly to the "stderr" file
 | ||
|                 // descriptor. An additional "ipc" file descriptor is created to support communication with
 | ||
|                 // the build process. https://nodejs.org/api/child_process.html#child_process_options_stdio.
 | ||
|                 stdio: ['inherit', 2, 2, 'ipc'],
 | ||
|             });
 | ||
|             let builtPackages = null;
 | ||
|             // The child process will pass the `buildPackages()` output through the
 | ||
|             // IPC channel. We keep track of it so that we can use it as resolve value.
 | ||
|             buildProcess.on('message', buildResponse => builtPackages = buildResponse);
 | ||
|             // On child process exit, resolve the promise with the received output.
 | ||
|             buildProcess.on('exit', () => resolve(builtPackages));
 | ||
|         });
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Yargs command builder for configuring the `ng-dev release build` command. */
 | ||
| function builder$7(argv) {
 | ||
|     return argv.option('json', {
 | ||
|         type: 'boolean',
 | ||
|         description: 'Whether the built packages should be printed to stdout as JSON.',
 | ||
|         default: false,
 | ||
|     });
 | ||
| }
 | ||
| /** Yargs command handler for building a release. */
 | ||
| function handler$7(args) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const { npmPackages } = getReleaseConfig();
 | ||
|         let builtPackages = yield buildReleaseOutput();
 | ||
|         // If package building failed, print an error and exit with an error code.
 | ||
|         if (builtPackages === null) {
 | ||
|             error(red(`  ✘   Could not build release output. Please check output above.`));
 | ||
|             process.exit(1);
 | ||
|         }
 | ||
|         // If no packages have been built, we assume that this is never correct
 | ||
|         // and exit with an error code.
 | ||
|         if (builtPackages.length === 0) {
 | ||
|             error(red(`  ✘   No release packages have been built. Please ensure that the`));
 | ||
|             error(red(`      build script is configured correctly in ".ng-dev".`));
 | ||
|             process.exit(1);
 | ||
|         }
 | ||
|         const missingPackages = npmPackages.filter(pkgName => !builtPackages.find(b => b.name === pkgName));
 | ||
|         // Check for configured release packages which have not been built. We want to
 | ||
|         // error and exit if any configured package has not been built.
 | ||
|         if (missingPackages.length > 0) {
 | ||
|             error(red(`  ✘   Release output missing for the following packages:`));
 | ||
|             missingPackages.forEach(pkgName => error(red(`      - ${pkgName}`)));
 | ||
|             process.exit(1);
 | ||
|         }
 | ||
|         if (args.json) {
 | ||
|             process.stdout.write(JSON.stringify(builtPackages, null, 2));
 | ||
|         }
 | ||
|         else {
 | ||
|             info(green('  ✓   Built release packages.'));
 | ||
|             builtPackages.forEach(({ name }) => info(green(`      - ${name}`)));
 | ||
|         }
 | ||
|     });
 | ||
| }
 | ||
| /** CLI command module for building release output. */
 | ||
| const ReleaseBuildCommandModule = {
 | ||
|     builder: builder$7,
 | ||
|     handler: handler$7,
 | ||
|     command: 'build',
 | ||
|     describe: 'Builds the release output for the current branch.',
 | ||
| };
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Prints the active release trains to the console.
 | ||
|  * @params active Active release trains that should be printed.
 | ||
|  * @params config Release configuration used for querying NPM on published versions.
 | ||
|  */
 | ||
| function printActiveReleaseTrains(active, config) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const { releaseCandidate, next, latest } = active;
 | ||
|         const isNextPublishedToNpm = yield isVersionPublishedToNpm(next.version, config);
 | ||
|         const nextTrainType = next.isMajor ? 'major' : 'minor';
 | ||
|         const ltsBranches = yield fetchLongTermSupportBranchesFromNpm(config);
 | ||
|         info();
 | ||
|         info(blue('Current version branches in the project:'));
 | ||
|         // Print information for release trains in the feature-freeze/release-candidate phase.
 | ||
|         if (releaseCandidate !== null) {
 | ||
|             const rcVersion = releaseCandidate.version;
 | ||
|             const rcTrainType = releaseCandidate.isMajor ? 'major' : 'minor';
 | ||
|             const rcTrainPhase = rcVersion.prerelease[0] === 'next' ? 'feature-freeze' : 'release-candidate';
 | ||
|             info(` • ${bold(releaseCandidate.branchName)} contains changes for an upcoming ` +
 | ||
|                 `${rcTrainType} that is currently in ${bold(rcTrainPhase)} phase.`);
 | ||
|             info(`   Most recent pre-release for this branch is "${bold(`v${rcVersion}`)}".`);
 | ||
|         }
 | ||
|         // Print information about the release-train in the latest phase. i.e. the patch branch.
 | ||
|         info(` • ${bold(latest.branchName)} contains changes for the most recent patch.`);
 | ||
|         info(`   Most recent patch version for this branch is "${bold(`v${latest.version}`)}".`);
 | ||
|         // Print information about the release-train in the next phase.
 | ||
|         info(` • ${bold(next.branchName)} contains changes for a ${nextTrainType} ` +
 | ||
|             `currently in active development.`);
 | ||
|         // Note that there is a special case for versions in the next release-train. The version in
 | ||
|         // the next branch is not always published to NPM. This can happen when we recently branched
 | ||
|         // off for a feature-freeze release-train. More details are in the next pre-release action.
 | ||
|         if (isNextPublishedToNpm) {
 | ||
|             info(`   Most recent pre-release version for this branch is "${bold(`v${next.version}`)}".`);
 | ||
|         }
 | ||
|         else {
 | ||
|             info(`   Version is currently set to "${bold(`v${next.version}`)}", but has not been ` +
 | ||
|                 `published yet.`);
 | ||
|         }
 | ||
|         // If no release-train in release-candidate or feature-freeze phase is active,
 | ||
|         // we print a message as last bullet point to make this clear.
 | ||
|         if (releaseCandidate === null) {
 | ||
|             info(' • No release-candidate or feature-freeze branch currently active.');
 | ||
|         }
 | ||
|         info();
 | ||
|         info(blue('Current active LTS version branches:'));
 | ||
|         // Print all active LTS branches (each branch as own bullet point).
 | ||
|         if (ltsBranches.active.length !== 0) {
 | ||
|             for (const ltsBranch of ltsBranches.active) {
 | ||
|                 info(` • ${bold(ltsBranch.name)} is currently in active long-term support phase.`);
 | ||
|                 info(`   Most recent patch version for this branch is "${bold(`v${ltsBranch.version}`)}".`);
 | ||
|             }
 | ||
|         }
 | ||
|         info();
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Error that will be thrown if the user manually aborted a release action. */
 | ||
| class UserAbortedReleaseActionError extends Error {
 | ||
|     constructor() {
 | ||
|         super();
 | ||
|         // Set the prototype explicitly because in ES5, the prototype is accidentally lost due to
 | ||
|         // a limitation in down-leveling.
 | ||
|         // https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work.
 | ||
|         Object.setPrototypeOf(this, UserAbortedReleaseActionError.prototype);
 | ||
|     }
 | ||
| }
 | ||
| /** Error that will be thrown if the action has been aborted due to a fatal error. */
 | ||
| class FatalReleaseActionError extends Error {
 | ||
|     constructor() {
 | ||
|         super();
 | ||
|         // Set the prototype explicitly because in ES5, the prototype is accidentally lost due to
 | ||
|         // a limitation in down-leveling.
 | ||
|         // https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work.
 | ||
|         Object.setPrototypeOf(this, FatalReleaseActionError.prototype);
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Increments a specified SemVer version. Compared to the original increment in SemVer,
 | ||
|  * the version is cloned to not modify the original version instance.
 | ||
|  */
 | ||
| function semverInc(version, release, identifier) {
 | ||
|     const clone = new semver.SemVer(version.version);
 | ||
|     return clone.inc(release, identifier);
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Spawns a given command with the specified arguments inside a shell. All process stdout
 | ||
|  * output is captured and returned as resolution on completion. Depending on the chosen
 | ||
|  * output mode, stdout/stderr output is also printed to the console, or only on error.
 | ||
|  *
 | ||
|  * @returns a Promise resolving with captured stdout on success. The promise
 | ||
|  *   rejects on command failure.
 | ||
|  */
 | ||
| function spawnWithDebugOutput(command, args, options) {
 | ||
|     if (options === void 0) { options = {}; }
 | ||
|     return new Promise(function (resolve, reject) {
 | ||
|         var commandText = command + " " + args.join(' ');
 | ||
|         var outputMode = options.mode;
 | ||
|         debug("Executing command: " + commandText);
 | ||
|         var childProcess = child_process.spawn(command, args, tslib.__assign(tslib.__assign({}, options), { shell: true, stdio: ['inherit', 'pipe', 'pipe'] }));
 | ||
|         var logOutput = '';
 | ||
|         var stdout = '';
 | ||
|         // Capture the stdout separately so that it can be passed as resolve value.
 | ||
|         // This is useful if commands return parsable stdout.
 | ||
|         childProcess.stderr.on('data', function (message) {
 | ||
|             logOutput += message;
 | ||
|             // If console output is enabled, print the message directly to the stderr. Note that
 | ||
|             // we intentionally print all output to stderr as stdout should not be polluted.
 | ||
|             if (outputMode === undefined || outputMode === 'enabled') {
 | ||
|                 process.stderr.write(message);
 | ||
|             }
 | ||
|         });
 | ||
|         childProcess.stdout.on('data', function (message) {
 | ||
|             stdout += message;
 | ||
|             logOutput += message;
 | ||
|             // If console output is enabled, print the message directly to the stderr. Note that
 | ||
|             // we intentionally print all output to stderr as stdout should not be polluted.
 | ||
|             if (outputMode === undefined || outputMode === 'enabled') {
 | ||
|                 process.stderr.write(message);
 | ||
|             }
 | ||
|         });
 | ||
|         childProcess.on('exit', function (status, signal) {
 | ||
|             var exitDescription = status !== null ? "exit code \"" + status + "\"" : "signal \"" + signal + "\"";
 | ||
|             var printFn = outputMode === 'on-error' ? error : debug;
 | ||
|             printFn("Command \"" + commandText + "\" completed with " + exitDescription + ".");
 | ||
|             printFn("Process output: \n" + logOutput);
 | ||
|             // On success, resolve the promise. Otherwise reject with the captured stderr
 | ||
|             // and stdout log output if the output mode was set to `silent`.
 | ||
|             if (status === 0) {
 | ||
|                 resolve({ stdout: stdout });
 | ||
|             }
 | ||
|             else {
 | ||
|                 reject(outputMode === 'silent' ? logOutput : undefined);
 | ||
|             }
 | ||
|         });
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Runs NPM publish within a specified package directory.
 | ||
|  * @throws With the process log output if the publish failed.
 | ||
|  */
 | ||
| function runNpmPublish(packagePath, distTag, registryUrl) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const args = ['publish', '--access', 'public', '--tag', distTag];
 | ||
|         // If a custom registry URL has been specified, add the `--registry` flag.
 | ||
|         if (registryUrl !== undefined) {
 | ||
|             args.push('--registry', registryUrl);
 | ||
|         }
 | ||
|         yield spawnWithDebugOutput('npm', args, { cwd: packagePath, mode: 'silent' });
 | ||
|     });
 | ||
| }
 | ||
| /**
 | ||
|  * Sets the NPM tag to the specified version for the given package.
 | ||
|  * @throws With the process log output if the tagging failed.
 | ||
|  */
 | ||
| function setNpmTagForPackage(packageName, distTag, version, registryUrl) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const args = ['dist-tag', 'add', `${packageName}@${version}`, distTag];
 | ||
|         // If a custom registry URL has been specified, add the `--registry` flag.
 | ||
|         if (registryUrl !== undefined) {
 | ||
|             args.push('--registry', registryUrl);
 | ||
|         }
 | ||
|         yield spawnWithDebugOutput('npm', args, { mode: 'silent' });
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Gets the commit message for a new release point in the project. */
 | ||
| function getCommitMessageForRelease(newVersion) {
 | ||
|     return `release: cut the v${newVersion} release`;
 | ||
| }
 | ||
| /**
 | ||
|  * Gets the commit message for an exceptional version bump in the next branch. The next
 | ||
|  * branch version will be bumped without the release being published in some situations.
 | ||
|  * More details can be found in the `MoveNextIntoFeatureFreeze` release action and in:
 | ||
|  * https://hackmd.io/2Le8leq0S6G_R5VEVTNK9A.
 | ||
|  */
 | ||
| function getCommitMessageForExceptionalNextVersionBump(newVersion) {
 | ||
|     return `release: bump the next branch to v${newVersion}`;
 | ||
| }
 | ||
| /** Gets the commit message for a release notes cherry-pick commit */
 | ||
| function getReleaseNoteCherryPickCommitMessage(newVersion) {
 | ||
|     return `docs: release notes for the v${newVersion} release`;
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Project-relative path for the changelog file. */
 | ||
| const changelogPath = 'CHANGELOG.md';
 | ||
| /** Project-relative path for the "package.json" file. */
 | ||
| const packageJsonPath = 'package.json';
 | ||
| /** Default interval in milliseconds to check whether a pull request has been merged. */
 | ||
| const waitForPullRequestInterval = 10000;
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /*
 | ||
|  * ###############################################################
 | ||
|  *
 | ||
|  * This file contains helpers for invoking external `ng-dev` commands. A subset of actions,
 | ||
|  * like building release output or setting aν NPM dist tag for release packages, cannot be
 | ||
|  * performed directly as part of the release tool and need to be delegated to external `ng-dev`
 | ||
|  * commands that exist across arbitrary version branches.
 | ||
|  *
 | ||
|  * In a concrete example: Consider a new patch version is released and that a new release
 | ||
|  * package has been added to the `next` branch. The patch branch will not contain the new
 | ||
|  * release package, so we could not build the release output for it. To work around this, we
 | ||
|  * call the ng-dev build command for the patch version branch and expect it to return a list
 | ||
|  * of built packages that need to be released as part of this release train.
 | ||
|  *
 | ||
|  * ###############################################################
 | ||
|  */
 | ||
| /**
 | ||
|  * Invokes the `ng-dev release set-dist-tag` command in order to set the specified
 | ||
|  * NPM dist tag for all packages in the checked out branch to the given version.
 | ||
|  */
 | ||
| function invokeSetNpmDistCommand(npmDistTag, version) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         try {
 | ||
|             // Note: No progress indicator needed as that is the responsibility of the command.
 | ||
|             yield spawnWithDebugOutput('yarn', ['--silent', 'ng-dev', 'release', 'set-dist-tag', npmDistTag, version.format()]);
 | ||
|             info(green(`  ✓   Set "${npmDistTag}" NPM dist tag for all packages to v${version}.`));
 | ||
|         }
 | ||
|         catch (e) {
 | ||
|             error(e);
 | ||
|             error(red(`  ✘   An error occurred while setting the NPM dist tag for "${npmDistTag}".`));
 | ||
|             throw new FatalReleaseActionError();
 | ||
|         }
 | ||
|     });
 | ||
| }
 | ||
| /**
 | ||
|  * Invokes the `ng-dev release build` command in order to build the release
 | ||
|  * packages for the currently checked out branch.
 | ||
|  */
 | ||
| function invokeReleaseBuildCommand() {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const spinner = ora.call(undefined).start('Building release output.');
 | ||
|         try {
 | ||
|             // Since we expect JSON to be printed from the `ng-dev release build` command,
 | ||
|             // we spawn the process in silent mode. We have set up an Ora progress spinner.
 | ||
|             const { stdout } = yield spawnWithDebugOutput('yarn', ['--silent', 'ng-dev', 'release', 'build', '--json'], { mode: 'silent' });
 | ||
|             spinner.stop();
 | ||
|             info(green('  ✓   Built release output for all packages.'));
 | ||
|             // The `ng-dev release build` command prints a JSON array to stdout
 | ||
|             // that represents the built release packages and their output paths.
 | ||
|             return JSON.parse(stdout.trim());
 | ||
|         }
 | ||
|         catch (e) {
 | ||
|             spinner.stop();
 | ||
|             error(e);
 | ||
|             error(red('  ✘   An error occurred while building the release packages.'));
 | ||
|             throw new FatalReleaseActionError();
 | ||
|         }
 | ||
|     });
 | ||
| }
 | ||
| /**
 | ||
|  * Invokes the `yarn install` command in order to install dependencies for
 | ||
|  * the configured project with the currently checked out revision.
 | ||
|  */
 | ||
| function invokeYarnInstallCommand(projectDir) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         try {
 | ||
|             // Note: No progress indicator needed as that is the responsibility of the command.
 | ||
|             // TODO: Consider using an Ora spinner instead to ensure minimal console output.
 | ||
|             yield spawnWithDebugOutput('yarn', ['install', '--frozen-lockfile', '--non-interactive'], { cwd: projectDir });
 | ||
|             info(green('  ✓   Installed project dependencies.'));
 | ||
|         }
 | ||
|         catch (e) {
 | ||
|             error(e);
 | ||
|             error(red('  ✘   An error occurred while installing dependencies.'));
 | ||
|             throw new FatalReleaseActionError();
 | ||
|         }
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Graphql Github API query that can be used to find forks of a given repository
 | ||
|  * that are owned by the current viewer authenticated with the Github API.
 | ||
|  */
 | ||
| const findOwnedForksOfRepoQuery = typedGraphqlify.params({
 | ||
|     $owner: 'String!',
 | ||
|     $name: 'String!',
 | ||
| }, {
 | ||
|     repository: typedGraphqlify.params({ owner: '$owner', name: '$name' }, {
 | ||
|         forks: typedGraphqlify.params({ affiliations: 'OWNER', first: 1 }, {
 | ||
|             nodes: [{
 | ||
|                     owner: {
 | ||
|                         login: typedGraphqlify.types.string,
 | ||
|                     },
 | ||
|                     name: typedGraphqlify.types.string,
 | ||
|                 }],
 | ||
|         }),
 | ||
|     }),
 | ||
| });
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Gets whether a given pull request has been merged. */
 | ||
| function getPullRequestState(api, id) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const { data } = yield api.github.pulls.get(Object.assign(Object.assign({}, api.remoteParams), { pull_number: id }));
 | ||
|         if (data.merged) {
 | ||
|             return 'merged';
 | ||
|         }
 | ||
|         else if (data.closed_at !== null) {
 | ||
|             return (yield isPullRequestClosedWithAssociatedCommit(api, id)) ? 'merged' : 'closed';
 | ||
|         }
 | ||
|         else {
 | ||
|             return 'open';
 | ||
|         }
 | ||
|     });
 | ||
| }
 | ||
| /**
 | ||
|  * Whether the pull request has been closed with an associated commit. This is usually
 | ||
|  * the case if a PR has been merged using the autosquash merge script strategy. Since
 | ||
|  * the merge is not fast-forward, Github does not consider the PR as merged and instead
 | ||
|  * shows the PR as closed. See for example: https://github.com/angular/angular/pull/37918.
 | ||
|  */
 | ||
| function isPullRequestClosedWithAssociatedCommit(api, id) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const request = api.github.issues.listEvents.endpoint.merge(Object.assign(Object.assign({}, api.remoteParams), { issue_number: id }));
 | ||
|         const events = yield api.github.paginate(request);
 | ||
|         // Iterate through the events of the pull request in reverse. We want to find the most
 | ||
|         // recent events and check if the PR has been closed with a commit associated with it.
 | ||
|         // If the PR has been closed through a commit, we assume that the PR has been merged
 | ||
|         // using the autosquash merge strategy. For more details. See the `AutosquashMergeStrategy`.
 | ||
|         for (let i = events.length - 1; i >= 0; i--) {
 | ||
|             const { event, commit_id } = events[i];
 | ||
|             // If we come across a "reopened" event, we abort looking for referenced commits. Any
 | ||
|             // commits that closed the PR before, are no longer relevant and did not close the PR.
 | ||
|             if (event === 'reopened') {
 | ||
|                 return false;
 | ||
|             }
 | ||
|             // If a `closed` event is captured with a commit assigned, then we assume that
 | ||
|             // this PR has been merged properly.
 | ||
|             if (event === 'closed' && commit_id) {
 | ||
|                 return true;
 | ||
|             }
 | ||
|             // If the PR has been referenced by a commit, check if the commit closes this pull
 | ||
|             // request. Note that this is needed besides checking `closed` as PRs could be merged
 | ||
|             // into any non-default branch where the `Closes <..>` keyword does not work and the PR
 | ||
|             // is simply closed without an associated `commit_id`. For more details see:
 | ||
|             // https://docs.github.com/en/enterprise/2.16/user/github/managing-your-work-on-github/closing-issues-using-keywords#:~:text=non-default.
 | ||
|             if (event === 'referenced' && commit_id &&
 | ||
|                 (yield isCommitClosingPullRequest(api, commit_id, id))) {
 | ||
|                 return true;
 | ||
|             }
 | ||
|         }
 | ||
|         return false;
 | ||
|     });
 | ||
| }
 | ||
| /** Checks whether the specified commit is closing the given pull request. */
 | ||
| function isCommitClosingPullRequest(api, sha, id) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const { data } = yield api.github.repos.getCommit(Object.assign(Object.assign({}, api.remoteParams), { ref: sha }));
 | ||
|         // Matches the closing keyword supported in commit messages. See:
 | ||
|         // https://docs.github.com/en/enterprise/2.16/user/github/managing-your-work-on-github/closing-issues-using-keywords.
 | ||
|         return data.commit.message.match(new RegExp(`(?:close[sd]?|fix(?:e[sd]?)|resolve[sd]?):? #${id}(?!\\d)`, 'i'));
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Gets the default pattern for extracting release notes for the given version.
 | ||
|  * This pattern matches for the conventional-changelog Angular preset.
 | ||
|  */
 | ||
| function getDefaultExtractReleaseNotesPattern(version) {
 | ||
|     const escapedVersion = version.format().replace('.', '\\.');
 | ||
|     // TODO: Change this once we have a canonical changelog generation tool. Also update this
 | ||
|     // based on the conventional-changelog version. They removed anchors in more recent versions.
 | ||
|     return new RegExp(`(<a name="${escapedVersion}"></a>.*?)(?:<a name="|$)`, 's');
 | ||
| }
 | ||
| /** Gets the path for the changelog file in a given project. */
 | ||
| function getLocalChangelogFilePath(projectDir) {
 | ||
|     return path.join(projectDir, changelogPath);
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Abstract base class for a release action. A release action is selectable by the caretaker
 | ||
|  * if active, and can perform changes for releasing, such as staging a release, bumping the
 | ||
|  * version, cherry-picking the changelog, branching off from master. etc.
 | ||
|  */
 | ||
| class ReleaseAction {
 | ||
|     constructor(active, git, config, projectDir) {
 | ||
|         this.active = active;
 | ||
|         this.git = git;
 | ||
|         this.config = config;
 | ||
|         this.projectDir = projectDir;
 | ||
|         /** Cached found fork of the configured project. */
 | ||
|         this._cachedForkRepo = null;
 | ||
|     }
 | ||
|     /** Whether the release action is currently active. */
 | ||
|     static isActive(_trains) {
 | ||
|         throw Error('Not implemented.');
 | ||
|     }
 | ||
|     /** Updates the version in the project top-level `package.json` file. */
 | ||
|     updateProjectVersion(newVersion) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const pkgJsonPath = path.join(this.projectDir, packageJsonPath);
 | ||
|             const pkgJson = JSON.parse(yield fs.promises.readFile(pkgJsonPath, 'utf8'));
 | ||
|             pkgJson.version = newVersion.format();
 | ||
|             // Write the `package.json` file. Note that we add a trailing new line
 | ||
|             // to avoid unnecessary diff. IDEs usually add a trailing new line.
 | ||
|             yield fs.promises.writeFile(pkgJsonPath, `${JSON.stringify(pkgJson, null, 2)}\n`);
 | ||
|             info(green(`  ✓   Updated project version to ${pkgJson.version}`));
 | ||
|         });
 | ||
|     }
 | ||
|     /** Gets the most recent commit of a specified branch. */
 | ||
|     _getCommitOfBranch(branchName) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const { data: { commit } } = yield this.git.github.repos.getBranch(Object.assign(Object.assign({}, this.git.remoteParams), { branch: branchName }));
 | ||
|             return commit.sha;
 | ||
|         });
 | ||
|     }
 | ||
|     /** Verifies that the latest commit for the given branch is passing all statuses. */
 | ||
|     verifyPassingGithubStatus(branchName) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const commitSha = yield this._getCommitOfBranch(branchName);
 | ||
|             const { data: { state } } = yield this.git.github.repos.getCombinedStatusForRef(Object.assign(Object.assign({}, this.git.remoteParams), { ref: commitSha }));
 | ||
|             const branchCommitsUrl = getListCommitsInBranchUrl(this.git, branchName);
 | ||
|             if (state === 'failure') {
 | ||
|                 error(red(`  ✘   Cannot stage release. Commit "${commitSha}" does not pass all github ` +
 | ||
|                     'status checks. Please make sure this commit passes all checks before re-running.'));
 | ||
|                 error(`      Please have a look at: ${branchCommitsUrl}`);
 | ||
|                 if (yield promptConfirm('Do you want to ignore the Github status and proceed?')) {
 | ||
|                     info(yellow('  ⚠   Upstream commit is failing CI checks, but status has been forcibly ignored.'));
 | ||
|                     return;
 | ||
|                 }
 | ||
|                 throw new UserAbortedReleaseActionError();
 | ||
|             }
 | ||
|             else if (state === 'pending') {
 | ||
|                 error(red(`  ✘   Commit "${commitSha}" still has pending github statuses that ` +
 | ||
|                     'need to succeed before staging a release.'));
 | ||
|                 error(red(`      Please have a look at: ${branchCommitsUrl}`));
 | ||
|                 if (yield promptConfirm('Do you want to ignore the Github status and proceed?')) {
 | ||
|                     info(yellow('  ⚠   Upstream commit is pending CI, but status has been forcibly ignored.'));
 | ||
|                     return;
 | ||
|                 }
 | ||
|                 throw new UserAbortedReleaseActionError();
 | ||
|             }
 | ||
|             info(green('  ✓   Upstream commit is passing all github status checks.'));
 | ||
|         });
 | ||
|     }
 | ||
|     /** Generates the changelog for the specified for the current `HEAD`. */
 | ||
|     _generateReleaseNotesForHead(version) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const changelogPath = getLocalChangelogFilePath(this.projectDir);
 | ||
|             yield this.config.generateReleaseNotesForHead(changelogPath);
 | ||
|             info(green(`  ✓   Updated the changelog to capture changes for "${version}".`));
 | ||
|         });
 | ||
|     }
 | ||
|     /** Extract the release notes for the given version from the changelog file. */
 | ||
|     _extractReleaseNotesForVersion(changelogContent, version) {
 | ||
|         const pattern = this.config.extractReleaseNotesPattern !== undefined ?
 | ||
|             this.config.extractReleaseNotesPattern(version) :
 | ||
|             getDefaultExtractReleaseNotesPattern(version);
 | ||
|         const matchedNotes = pattern.exec(changelogContent);
 | ||
|         return matchedNotes === null ? null : matchedNotes[1];
 | ||
|     }
 | ||
|     /**
 | ||
|      * Prompts the user for potential release notes edits that need to be made. Once
 | ||
|      * confirmed, a new commit for the release point is created.
 | ||
|      */
 | ||
|     waitForEditsAndCreateReleaseCommit(newVersion) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             info(yellow('  ⚠   Please review the changelog and ensure that the log contains only changes ' +
 | ||
|                 'that apply to the public API surface. Manual changes can be made. When done, please ' +
 | ||
|                 'proceed with the prompt below.'));
 | ||
|             if (!(yield promptConfirm('Do you want to proceed and commit the changes?'))) {
 | ||
|                 throw new UserAbortedReleaseActionError();
 | ||
|             }
 | ||
|             // Commit message for the release point.
 | ||
|             const commitMessage = getCommitMessageForRelease(newVersion);
 | ||
|             // Create a release staging commit including changelog and version bump.
 | ||
|             yield this.createCommit(commitMessage, [packageJsonPath, changelogPath]);
 | ||
|             info(green(`  ✓   Created release commit for: "${newVersion}".`));
 | ||
|         });
 | ||
|     }
 | ||
|     /**
 | ||
|      * Gets an owned fork for the configured project of the authenticated user. Aborts the
 | ||
|      * process with an error if no fork could be found. Also caches the determined fork
 | ||
|      * repository as the authenticated user cannot change during action execution.
 | ||
|      */
 | ||
|     _getForkOfAuthenticatedUser() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             if (this._cachedForkRepo !== null) {
 | ||
|                 return this._cachedForkRepo;
 | ||
|             }
 | ||
|             const { owner, name } = this.git.remoteConfig;
 | ||
|             const result = yield this.git.github.graphql.query(findOwnedForksOfRepoQuery, { owner, name });
 | ||
|             const forks = result.repository.forks.nodes;
 | ||
|             if (forks.length === 0) {
 | ||
|                 error(red('  ✘   Unable to find fork for currently authenticated user.'));
 | ||
|                 error(red(`      Please ensure you created a fork of: ${owner}/${name}.`));
 | ||
|                 throw new FatalReleaseActionError();
 | ||
|             }
 | ||
|             const fork = forks[0];
 | ||
|             return this._cachedForkRepo = { owner: fork.owner.login, name: fork.name };
 | ||
|         });
 | ||
|     }
 | ||
|     /** Checks whether a given branch name is reserved in the specified repository. */
 | ||
|     _isBranchNameReservedInRepo(repo, name) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             try {
 | ||
|                 yield this.git.github.repos.getBranch({ owner: repo.owner, repo: repo.name, branch: name });
 | ||
|                 return true;
 | ||
|             }
 | ||
|             catch (e) {
 | ||
|                 // If the error has a `status` property set to `404`, then we know that the branch
 | ||
|                 // does not exist. Otherwise, it might be an API error that we want to report/re-throw.
 | ||
|                 if (e.status === 404) {
 | ||
|                     return false;
 | ||
|                 }
 | ||
|                 throw e;
 | ||
|             }
 | ||
|         });
 | ||
|     }
 | ||
|     /** Finds a non-reserved branch name in the repository with respect to a base name. */
 | ||
|     _findAvailableBranchName(repo, baseName) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             let currentName = baseName;
 | ||
|             let suffixNum = 0;
 | ||
|             while (yield this._isBranchNameReservedInRepo(repo, currentName)) {
 | ||
|                 suffixNum++;
 | ||
|                 currentName = `${baseName}_${suffixNum}`;
 | ||
|             }
 | ||
|             return currentName;
 | ||
|         });
 | ||
|     }
 | ||
|     /**
 | ||
|      * Creates a local branch from the current Git `HEAD`. Will override
 | ||
|      * existing branches in case of a collision.
 | ||
|      */
 | ||
|     createLocalBranchFromHead(branchName) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             this.git.run(['checkout', '-B', branchName]);
 | ||
|         });
 | ||
|     }
 | ||
|     /** Pushes the current Git `HEAD` to the given remote branch in the configured project. */
 | ||
|     pushHeadToRemoteBranch(branchName) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             // Push the local `HEAD` to the remote branch in the configured project.
 | ||
|             this.git.run(['push', this.git.repoGitUrl, `HEAD:refs/heads/${branchName}`]);
 | ||
|         });
 | ||
|     }
 | ||
|     /**
 | ||
|      * Pushes the current Git `HEAD` to a fork for the configured project that is owned by
 | ||
|      * the authenticated user. If the specified branch name exists in the fork already, a
 | ||
|      * unique one will be generated based on the proposed name to avoid collisions.
 | ||
|      * @param proposedBranchName Proposed branch name for the fork.
 | ||
|      * @param trackLocalBranch Whether the fork branch should be tracked locally. i.e. whether
 | ||
|      *   a local branch with remote tracking should be set up.
 | ||
|      * @returns The fork and branch name containing the pushed changes.
 | ||
|      */
 | ||
|     _pushHeadToFork(proposedBranchName, trackLocalBranch) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const fork = yield this._getForkOfAuthenticatedUser();
 | ||
|             // Compute a repository URL for pushing to the fork. Note that we want to respect
 | ||
|             // the SSH option from the dev-infra github configuration.
 | ||
|             const repoGitUrl = getRepositoryGitUrl(Object.assign(Object.assign({}, fork), { useSsh: this.git.remoteConfig.useSsh }), this.git.githubToken);
 | ||
|             const branchName = yield this._findAvailableBranchName(fork, proposedBranchName);
 | ||
|             const pushArgs = [];
 | ||
|             // If a local branch should track the remote fork branch, create a branch matching
 | ||
|             // the remote branch. Later with the `git push`, the remote is set for the branch.
 | ||
|             if (trackLocalBranch) {
 | ||
|                 yield this.createLocalBranchFromHead(branchName);
 | ||
|                 pushArgs.push('--set-upstream');
 | ||
|             }
 | ||
|             // Push the local `HEAD` to the remote branch in the fork.
 | ||
|             this.git.run(['push', repoGitUrl, `HEAD:refs/heads/${branchName}`, ...pushArgs]);
 | ||
|             return { fork, branchName };
 | ||
|         });
 | ||
|     }
 | ||
|     /**
 | ||
|      * Pushes changes to a fork for the configured project that is owned by the currently
 | ||
|      * authenticated user. A pull request is then created for the pushed changes on the
 | ||
|      * configured project that targets the specified target branch.
 | ||
|      * @returns An object describing the created pull request.
 | ||
|      */
 | ||
|     pushChangesToForkAndCreatePullRequest(targetBranch, proposedForkBranchName, title, body) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const repoSlug = `${this.git.remoteParams.owner}/${this.git.remoteParams.repo}`;
 | ||
|             const { fork, branchName } = yield this._pushHeadToFork(proposedForkBranchName, true);
 | ||
|             const { data } = yield this.git.github.pulls.create(Object.assign(Object.assign({}, this.git.remoteParams), { head: `${fork.owner}:${branchName}`, base: targetBranch, body,
 | ||
|                 title }));
 | ||
|             info(green(`  ✓   Created pull request #${data.number} in ${repoSlug}.`));
 | ||
|             return {
 | ||
|                 id: data.number,
 | ||
|                 url: data.html_url,
 | ||
|                 fork,
 | ||
|                 forkBranch: branchName,
 | ||
|             };
 | ||
|         });
 | ||
|     }
 | ||
|     /**
 | ||
|      * Waits for the given pull request to be merged. Default interval for checking the Github
 | ||
|      * API is 10 seconds (to not exceed any rate limits). If the pull request is closed without
 | ||
|      * merge, the script will abort gracefully (considering a manual user abort).
 | ||
|      */
 | ||
|     waitForPullRequestToBeMerged(id, interval = waitForPullRequestInterval) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             return new Promise((resolve, reject) => {
 | ||
|                 debug(`Waiting for pull request #${id} to be merged.`);
 | ||
|                 const spinner = ora.call(undefined).start(`Waiting for pull request #${id} to be merged.`);
 | ||
|                 const intervalId = setInterval(() => tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|                     const prState = yield getPullRequestState(this.git, id);
 | ||
|                     if (prState === 'merged') {
 | ||
|                         spinner.stop();
 | ||
|                         info(green(`  ✓   Pull request #${id} has been merged.`));
 | ||
|                         clearInterval(intervalId);
 | ||
|                         resolve();
 | ||
|                     }
 | ||
|                     else if (prState === 'closed') {
 | ||
|                         spinner.stop();
 | ||
|                         warn(yellow(`  ✘   Pull request #${id} has been closed.`));
 | ||
|                         clearInterval(intervalId);
 | ||
|                         reject(new UserAbortedReleaseActionError());
 | ||
|                     }
 | ||
|                 }), interval);
 | ||
|             });
 | ||
|         });
 | ||
|     }
 | ||
|     /**
 | ||
|      * Prepend releases notes for a version published in a given branch to the changelog in
 | ||
|      * the current Git `HEAD`. This is useful for cherry-picking the changelog.
 | ||
|      * @returns A boolean indicating whether the release notes have been prepended.
 | ||
|      */
 | ||
|     prependReleaseNotesFromVersionBranch(version, containingBranch) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const { data } = yield this.git.github.repos.getContents(Object.assign(Object.assign({}, this.git.remoteParams), { path: '/' + changelogPath, ref: containingBranch }));
 | ||
|             const branchChangelog = Buffer.from(data.content, 'base64').toString();
 | ||
|             let releaseNotes = this._extractReleaseNotesForVersion(branchChangelog, version);
 | ||
|             // If no release notes could be extracted, return "false" so that the caller
 | ||
|             // can tell that changelog prepending failed.
 | ||
|             if (releaseNotes === null) {
 | ||
|                 return false;
 | ||
|             }
 | ||
|             const localChangelogPath = getLocalChangelogFilePath(this.projectDir);
 | ||
|             const localChangelog = yield fs.promises.readFile(localChangelogPath, 'utf8');
 | ||
|             // If the extracted release notes do not have any new lines at the end and the
 | ||
|             // local changelog is not empty, we add lines manually so that there is space
 | ||
|             // between the previous and cherry-picked release notes.
 | ||
|             if (!/[\r\n]+$/.test(releaseNotes) && localChangelog !== '') {
 | ||
|                 releaseNotes = `${releaseNotes}\n\n`;
 | ||
|             }
 | ||
|             // Prepend the extracted release notes to the local changelog and write it back.
 | ||
|             yield fs.promises.writeFile(localChangelogPath, releaseNotes + localChangelog);
 | ||
|             return true;
 | ||
|         });
 | ||
|     }
 | ||
|     /** Checks out an upstream branch with a detached head. */
 | ||
|     checkoutUpstreamBranch(branchName) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             this.git.run(['fetch', '-q', this.git.repoGitUrl, branchName]);
 | ||
|             this.git.run(['checkout', 'FETCH_HEAD', '--detach']);
 | ||
|         });
 | ||
|     }
 | ||
|     /**
 | ||
|      * Creates a commit for the specified files with the given message.
 | ||
|      * @param message Message for the created commit
 | ||
|      * @param files List of project-relative file paths to be commited.
 | ||
|      */
 | ||
|     createCommit(message, files) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             this.git.run(['commit', '--no-verify', '-m', message, ...files]);
 | ||
|         });
 | ||
|     }
 | ||
|     /**
 | ||
|      * Creates a cherry-pick commit for the release notes of the specified version that
 | ||
|      * has been pushed to the given branch.
 | ||
|      * @returns a boolean indicating whether the commit has been created successfully.
 | ||
|      */
 | ||
|     createCherryPickReleaseNotesCommitFrom(version, branchName) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const commitMessage = getReleaseNoteCherryPickCommitMessage(version);
 | ||
|             // Fetch, extract and prepend the release notes to the local changelog. If that is not
 | ||
|             // possible, abort so that we can ask the user to manually cherry-pick the changelog.
 | ||
|             if (!(yield this.prependReleaseNotesFromVersionBranch(version, branchName))) {
 | ||
|                 return false;
 | ||
|             }
 | ||
|             // Create a changelog cherry-pick commit.
 | ||
|             yield this.createCommit(commitMessage, [changelogPath]);
 | ||
|             info(green(`  ✓   Created changelog cherry-pick commit for: "${version}".`));
 | ||
|             return true;
 | ||
|         });
 | ||
|     }
 | ||
|     /**
 | ||
|      * Stages the specified new version for the current branch and creates a
 | ||
|      * pull request that targets the given base branch.
 | ||
|      * @returns an object describing the created pull request.
 | ||
|      */
 | ||
|     stageVersionForBranchAndCreatePullRequest(newVersion, pullRequestBaseBranch) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             yield this.updateProjectVersion(newVersion);
 | ||
|             yield this._generateReleaseNotesForHead(newVersion);
 | ||
|             yield this.waitForEditsAndCreateReleaseCommit(newVersion);
 | ||
|             const pullRequest = yield this.pushChangesToForkAndCreatePullRequest(pullRequestBaseBranch, `release-stage-${newVersion}`, `Bump version to "v${newVersion}" with changelog.`);
 | ||
|             info(green('  ✓   Release staging pull request has been created.'));
 | ||
|             info(yellow(`      Please ask team members to review: ${pullRequest.url}.`));
 | ||
|             return pullRequest;
 | ||
|         });
 | ||
|     }
 | ||
|     /**
 | ||
|      * Checks out the specified target branch, verifies its CI status and stages
 | ||
|      * the specified new version in order to create a pull request.
 | ||
|      * @returns an object describing the created pull request.
 | ||
|      */
 | ||
|     checkoutBranchAndStageVersion(newVersion, stagingBranch) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             yield this.verifyPassingGithubStatus(stagingBranch);
 | ||
|             yield this.checkoutUpstreamBranch(stagingBranch);
 | ||
|             return yield this.stageVersionForBranchAndCreatePullRequest(newVersion, stagingBranch);
 | ||
|         });
 | ||
|     }
 | ||
|     /**
 | ||
|      * Cherry-picks the release notes of a version that have been pushed to a given branch
 | ||
|      * into the `next` primary development branch. A pull request is created for this.
 | ||
|      * @returns a boolean indicating successful creation of the cherry-pick pull request.
 | ||
|      */
 | ||
|     cherryPickChangelogIntoNextBranch(newVersion, stagingBranch) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const nextBranch = this.active.next.branchName;
 | ||
|             const commitMessage = getReleaseNoteCherryPickCommitMessage(newVersion);
 | ||
|             // Checkout the next branch.
 | ||
|             yield this.checkoutUpstreamBranch(nextBranch);
 | ||
|             // Cherry-pick the release notes into the current branch. If it fails,
 | ||
|             // ask the user to manually copy the release notes into the next branch.
 | ||
|             if (!(yield this.createCherryPickReleaseNotesCommitFrom(newVersion, stagingBranch))) {
 | ||
|                 error(yellow(`  ✘   Could not cherry-pick release notes for v${newVersion}.`));
 | ||
|                 error(yellow(`      Please copy the release notes manually into the "${nextBranch}" branch.`));
 | ||
|                 return false;
 | ||
|             }
 | ||
|             // Create a cherry-pick pull request that should be merged by the caretaker.
 | ||
|             const { url } = yield this.pushChangesToForkAndCreatePullRequest(nextBranch, `changelog-cherry-pick-${newVersion}`, commitMessage, `Cherry-picks the changelog from the "${stagingBranch}" branch to the next ` +
 | ||
|                 `branch (${nextBranch}).`);
 | ||
|             info(green(`  ✓   Pull request for cherry-picking the changelog into "${nextBranch}" ` +
 | ||
|                 'has been created.'));
 | ||
|             info(yellow(`      Please ask team members to review: ${url}.`));
 | ||
|             return true;
 | ||
|         });
 | ||
|     }
 | ||
|     /**
 | ||
|      * Creates a Github release for the specified version in the configured project.
 | ||
|      * The release is created by tagging the specified commit SHA.
 | ||
|      */
 | ||
|     _createGithubReleaseForVersion(newVersion, versionBumpCommitSha) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const tagName = newVersion.format();
 | ||
|             yield this.git.github.git.createRef(Object.assign(Object.assign({}, this.git.remoteParams), { ref: `refs/tags/${tagName}`, sha: versionBumpCommitSha }));
 | ||
|             info(green(`  ✓   Tagged v${newVersion} release upstream.`));
 | ||
|             yield this.git.github.repos.createRelease(Object.assign(Object.assign({}, this.git.remoteParams), { name: `v${newVersion}`, tag_name: tagName }));
 | ||
|             info(green(`  ✓   Created v${newVersion} release in Github.`));
 | ||
|         });
 | ||
|     }
 | ||
|     /**
 | ||
|      * Builds and publishes the given version in the specified branch.
 | ||
|      * @param newVersion The new version to be published.
 | ||
|      * @param publishBranch Name of the branch that contains the new version.
 | ||
|      * @param npmDistTag NPM dist tag where the version should be published to.
 | ||
|      */
 | ||
|     buildAndPublish(newVersion, publishBranch, npmDistTag) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const versionBumpCommitSha = yield this._getCommitOfBranch(publishBranch);
 | ||
|             if (!(yield this._isCommitForVersionStaging(newVersion, versionBumpCommitSha))) {
 | ||
|                 error(red(`  ✘   Latest commit in "${publishBranch}" branch is not a staging commit.`));
 | ||
|                 error(red('      Please make sure the staging pull request has been merged.'));
 | ||
|                 throw new FatalReleaseActionError();
 | ||
|             }
 | ||
|             // Checkout the publish branch and build the release packages.
 | ||
|             yield this.checkoutUpstreamBranch(publishBranch);
 | ||
|             // Install the project dependencies for the publish branch, and then build the release
 | ||
|             // packages. Note that we do not directly call the build packages function from the release
 | ||
|             // config. We only want to build and publish packages that have been configured in the given
 | ||
|             // publish branch. e.g. consider we publish patch version and a new package has been
 | ||
|             // created in the `next` branch. The new package would not be part of the patch branch,
 | ||
|             // so we cannot build and publish it.
 | ||
|             yield invokeYarnInstallCommand(this.projectDir);
 | ||
|             const builtPackages = yield invokeReleaseBuildCommand();
 | ||
|             // Verify the packages built are the correct version.
 | ||
|             yield this._verifyPackageVersions(newVersion, builtPackages);
 | ||
|             // Create a Github release for the new version.
 | ||
|             yield this._createGithubReleaseForVersion(newVersion, versionBumpCommitSha);
 | ||
|             // Walk through all built packages and publish them to NPM.
 | ||
|             for (const builtPackage of builtPackages) {
 | ||
|                 yield this._publishBuiltPackageToNpm(builtPackage, npmDistTag);
 | ||
|             }
 | ||
|             info(green('  ✓   Published all packages successfully'));
 | ||
|         });
 | ||
|     }
 | ||
|     /** Publishes the given built package to NPM with the specified NPM dist tag. */
 | ||
|     _publishBuiltPackageToNpm(pkg, npmDistTag) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             debug(`Starting publish of "${pkg.name}".`);
 | ||
|             const spinner = ora.call(undefined).start(`Publishing "${pkg.name}"`);
 | ||
|             try {
 | ||
|                 yield runNpmPublish(pkg.outputPath, npmDistTag, this.config.publishRegistry);
 | ||
|                 spinner.stop();
 | ||
|                 info(green(`  ✓   Successfully published "${pkg.name}.`));
 | ||
|             }
 | ||
|             catch (e) {
 | ||
|                 spinner.stop();
 | ||
|                 error(e);
 | ||
|                 error(red(`  ✘   An error occurred while publishing "${pkg.name}".`));
 | ||
|                 throw new FatalReleaseActionError();
 | ||
|             }
 | ||
|         });
 | ||
|     }
 | ||
|     /** Checks whether the given commit represents a staging commit for the specified version. */
 | ||
|     _isCommitForVersionStaging(version, commitSha) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const { data } = yield this.git.github.repos.getCommit(Object.assign(Object.assign({}, this.git.remoteParams), { ref: commitSha }));
 | ||
|             return data.commit.message.startsWith(getCommitMessageForRelease(version));
 | ||
|         });
 | ||
|     }
 | ||
|     /** Verify the version of each generated package exact matches the specified version. */
 | ||
|     _verifyPackageVersions(version, packages) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             for (const pkg of packages) {
 | ||
|                 const { version: packageJsonVersion } = JSON.parse(yield fs.promises.readFile(path.join(pkg.outputPath, 'package.json'), 'utf8'));
 | ||
|                 if (version.compare(packageJsonVersion) !== 0) {
 | ||
|                     error(red('The built package version does not match the version being released.'));
 | ||
|                     error(`  Release Version:   ${version.version}`);
 | ||
|                     error(`  Generated Version: ${packageJsonVersion}`);
 | ||
|                     throw new FatalReleaseActionError();
 | ||
|                 }
 | ||
|             }
 | ||
|         });
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Release action that cuts a new patch release for an active release-train in the long-term
 | ||
|  * support phase. The patch segment is incremented. The changelog is generated for the new
 | ||
|  * patch version, but also needs to be cherry-picked into the next development branch.
 | ||
|  */
 | ||
| class CutLongTermSupportPatchAction extends ReleaseAction {
 | ||
|     constructor() {
 | ||
|         super(...arguments);
 | ||
|         /** Promise resolving an object describing long-term support branches. */
 | ||
|         this.ltsBranches = fetchLongTermSupportBranchesFromNpm(this.config);
 | ||
|     }
 | ||
|     getDescription() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const { active } = yield this.ltsBranches;
 | ||
|             return `Cut a new release for an active LTS branch (${active.length} active).`;
 | ||
|         });
 | ||
|     }
 | ||
|     perform() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const ltsBranch = yield this._promptForTargetLtsBranch();
 | ||
|             const newVersion = semverInc(ltsBranch.version, 'patch');
 | ||
|             const { id } = yield this.checkoutBranchAndStageVersion(newVersion, ltsBranch.name);
 | ||
|             yield this.waitForPullRequestToBeMerged(id);
 | ||
|             yield this.buildAndPublish(newVersion, ltsBranch.name, ltsBranch.npmDistTag);
 | ||
|             yield this.cherryPickChangelogIntoNextBranch(newVersion, ltsBranch.name);
 | ||
|         });
 | ||
|     }
 | ||
|     /** Prompts the user to select an LTS branch for which a patch should but cut. */
 | ||
|     _promptForTargetLtsBranch() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const { active, inactive } = yield this.ltsBranches;
 | ||
|             const activeBranchChoices = active.map(branch => this._getChoiceForLtsBranch(branch));
 | ||
|             // If there are inactive LTS branches, we allow them to be selected. In some situations,
 | ||
|             // patch releases are still cut for inactive LTS branches. e.g. when the LTS duration
 | ||
|             // has been increased due to exceptional events ()
 | ||
|             if (inactive.length !== 0) {
 | ||
|                 activeBranchChoices.push({ name: 'Inactive LTS versions (not recommended)', value: null });
 | ||
|             }
 | ||
|             const { activeLtsBranch, inactiveLtsBranch } = yield inquirer.prompt([
 | ||
|                 {
 | ||
|                     name: 'activeLtsBranch',
 | ||
|                     type: 'list',
 | ||
|                     message: 'Please select a version for which you want to cut an LTS patch',
 | ||
|                     choices: activeBranchChoices,
 | ||
|                 },
 | ||
|                 {
 | ||
|                     name: 'inactiveLtsBranch',
 | ||
|                     type: 'list',
 | ||
|                     when: o => o.activeLtsBranch === null,
 | ||
|                     message: 'Please select an inactive LTS version for which you want to cut an LTS patch',
 | ||
|                     choices: inactive.map(branch => this._getChoiceForLtsBranch(branch)),
 | ||
|                 }
 | ||
|             ]);
 | ||
|             return activeLtsBranch !== null && activeLtsBranch !== void 0 ? activeLtsBranch : inactiveLtsBranch;
 | ||
|         });
 | ||
|     }
 | ||
|     /** Gets an inquirer choice for the given LTS branch. */
 | ||
|     _getChoiceForLtsBranch(branch) {
 | ||
|         return { name: `v${branch.version.major} (from ${branch.name})`, value: branch };
 | ||
|     }
 | ||
|     static isActive(active) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             // LTS patch versions can be only cut if there are release trains in LTS phase.
 | ||
|             // This action is always selectable as we support publishing of old LTS branches,
 | ||
|             // and have prompt for selecting an LTS branch when the action performs.
 | ||
|             return true;
 | ||
|         });
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Release action that cuts a new patch release for the current latest release-train version
 | ||
|  * branch (i.e. the patch branch). The patch segment is incremented. The changelog is generated
 | ||
|  * for the new patch version, but also needs to be cherry-picked into the next development branch.
 | ||
|  */
 | ||
| class CutNewPatchAction extends ReleaseAction {
 | ||
|     constructor() {
 | ||
|         super(...arguments);
 | ||
|         this._newVersion = semverInc(this.active.latest.version, 'patch');
 | ||
|     }
 | ||
|     getDescription() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const { branchName } = this.active.latest;
 | ||
|             const newVersion = this._newVersion;
 | ||
|             return `Cut a new patch release for the "${branchName}" branch (v${newVersion}).`;
 | ||
|         });
 | ||
|     }
 | ||
|     perform() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const { branchName } = this.active.latest;
 | ||
|             const newVersion = this._newVersion;
 | ||
|             const { id } = yield this.checkoutBranchAndStageVersion(newVersion, branchName);
 | ||
|             yield this.waitForPullRequestToBeMerged(id);
 | ||
|             yield this.buildAndPublish(newVersion, branchName, 'latest');
 | ||
|             yield this.cherryPickChangelogIntoNextBranch(newVersion, branchName);
 | ||
|         });
 | ||
|     }
 | ||
|     static isActive(active) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             // Patch versions can be cut at any time. See:
 | ||
|             // https://hackmd.io/2Le8leq0S6G_R5VEVTNK9A#Release-prompt-options.
 | ||
|             return true;
 | ||
|         });
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Computes the new pre-release version for the next release-train. */
 | ||
| function computeNewPrereleaseVersionForNext(active, config) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const { version: nextVersion } = active.next;
 | ||
|         const isNextPublishedToNpm = yield isVersionPublishedToNpm(nextVersion, config);
 | ||
|         // Special-case where the version in the `next` release-train is not published yet. This
 | ||
|         // happens when we recently branched off for feature-freeze. We already bump the version to
 | ||
|         // the next minor or major, but do not publish immediately. Cutting a release immediately would
 | ||
|         // be not helpful as there are no other changes than in the feature-freeze branch. If we happen
 | ||
|         // to detect this case, we stage the release as usual but do not increment the version.
 | ||
|         if (isNextPublishedToNpm) {
 | ||
|             return semverInc(nextVersion, 'prerelease');
 | ||
|         }
 | ||
|         else {
 | ||
|             return nextVersion;
 | ||
|         }
 | ||
|     });
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Release action that cuts a prerelease for the next branch. A version in the next
 | ||
|  * branch can have an arbitrary amount of next pre-releases.
 | ||
|  */
 | ||
| class CutNextPrereleaseAction extends ReleaseAction {
 | ||
|     constructor() {
 | ||
|         super(...arguments);
 | ||
|         /** Promise resolving with the new version if a NPM next pre-release is cut. */
 | ||
|         this._newVersion = this._computeNewVersion();
 | ||
|     }
 | ||
|     getDescription() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const { branchName } = this._getActivePrereleaseTrain();
 | ||
|             const newVersion = yield this._newVersion;
 | ||
|             return `Cut a new next pre-release for the "${branchName}" branch (v${newVersion}).`;
 | ||
|         });
 | ||
|     }
 | ||
|     perform() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const releaseTrain = this._getActivePrereleaseTrain();
 | ||
|             const { branchName } = releaseTrain;
 | ||
|             const newVersion = yield this._newVersion;
 | ||
|             const { id } = yield this.checkoutBranchAndStageVersion(newVersion, branchName);
 | ||
|             yield this.waitForPullRequestToBeMerged(id);
 | ||
|             yield this.buildAndPublish(newVersion, branchName, 'next');
 | ||
|             // If the pre-release has been cut from a branch that is not corresponding
 | ||
|             // to the next release-train, cherry-pick the changelog into the primary
 | ||
|             // development branch. i.e. the `next` branch that is usually `master`.
 | ||
|             if (releaseTrain !== this.active.next) {
 | ||
|                 yield this.cherryPickChangelogIntoNextBranch(newVersion, branchName);
 | ||
|             }
 | ||
|         });
 | ||
|     }
 | ||
|     /** Gets the release train for which NPM next pre-releases should be cut. */
 | ||
|     _getActivePrereleaseTrain() {
 | ||
|         var _a;
 | ||
|         return (_a = this.active.releaseCandidate) !== null && _a !== void 0 ? _a : this.active.next;
 | ||
|     }
 | ||
|     /** Gets the new pre-release version for this release action. */
 | ||
|     _computeNewVersion() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const releaseTrain = this._getActivePrereleaseTrain();
 | ||
|             // If a pre-release is cut for the next release-train, the new version is computed
 | ||
|             // with respect to special cases surfacing with FF/RC branches. Otherwise, the basic
 | ||
|             // pre-release increment of the version is used as new version.
 | ||
|             if (releaseTrain === this.active.next) {
 | ||
|                 return yield computeNewPrereleaseVersionForNext(this.active, this.config);
 | ||
|             }
 | ||
|             else {
 | ||
|                 return semverInc(releaseTrain.version, 'prerelease');
 | ||
|             }
 | ||
|         });
 | ||
|     }
 | ||
|     static isActive() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             // Pre-releases for the `next` NPM dist tag can always be cut. Depending on whether
 | ||
|             // there is a feature-freeze/release-candidate branch, the next pre-releases are either
 | ||
|             // cut from such a branch, or from the actual `next` release-train branch (i.e. master).
 | ||
|             return true;
 | ||
|         });
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Cuts the first release candidate for a release-train currently in the
 | ||
|  * feature-freeze phase. The version is bumped from `next` to `rc.0`.
 | ||
|  */
 | ||
| class CutReleaseCandidateAction extends ReleaseAction {
 | ||
|     constructor() {
 | ||
|         super(...arguments);
 | ||
|         this._newVersion = semverInc(this.active.releaseCandidate.version, 'prerelease', 'rc');
 | ||
|     }
 | ||
|     getDescription() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const newVersion = this._newVersion;
 | ||
|             return `Cut a first release-candidate for the feature-freeze branch (v${newVersion}).`;
 | ||
|         });
 | ||
|     }
 | ||
|     perform() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const { branchName } = this.active.releaseCandidate;
 | ||
|             const newVersion = this._newVersion;
 | ||
|             const { id } = yield this.checkoutBranchAndStageVersion(newVersion, branchName);
 | ||
|             yield this.waitForPullRequestToBeMerged(id);
 | ||
|             yield this.buildAndPublish(newVersion, branchName, 'next');
 | ||
|             yield this.cherryPickChangelogIntoNextBranch(newVersion, branchName);
 | ||
|         });
 | ||
|     }
 | ||
|     static isActive(active) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             // A release-candidate can be cut for an active release-train currently
 | ||
|             // in the feature-freeze phase.
 | ||
|             return active.releaseCandidate !== null &&
 | ||
|                 active.releaseCandidate.version.prerelease[0] === 'next';
 | ||
|         });
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Release action that cuts a stable version for the current release-train in the release
 | ||
|  * candidate phase. The pre-release release-candidate version label is removed.
 | ||
|  */
 | ||
| class CutStableAction extends ReleaseAction {
 | ||
|     constructor() {
 | ||
|         super(...arguments);
 | ||
|         this._newVersion = this._computeNewVersion();
 | ||
|     }
 | ||
|     getDescription() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const newVersion = this._newVersion;
 | ||
|             return `Cut a stable release for the release-candidate branch (v${newVersion}).`;
 | ||
|         });
 | ||
|     }
 | ||
|     perform() {
 | ||
|         var _a;
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const { branchName } = this.active.releaseCandidate;
 | ||
|             const newVersion = this._newVersion;
 | ||
|             const isNewMajor = (_a = this.active.releaseCandidate) === null || _a === void 0 ? void 0 : _a.isMajor;
 | ||
|             const { id } = yield this.checkoutBranchAndStageVersion(newVersion, branchName);
 | ||
|             yield this.waitForPullRequestToBeMerged(id);
 | ||
|             yield this.buildAndPublish(newVersion, branchName, 'latest');
 | ||
|             // If a new major version is published and becomes the "latest" release-train, we need
 | ||
|             // to set the LTS npm dist tag for the previous latest release-train (the current patch).
 | ||
|             if (isNewMajor) {
 | ||
|                 const previousPatchVersion = this.active.latest.version;
 | ||
|                 const ltsTagForPatch = getLtsNpmDistTagOfMajor(previousPatchVersion.major);
 | ||
|                 // Instead of directly setting the NPM dist tags, we invoke the ng-dev command for
 | ||
|                 // setting the NPM dist tag to the specified version. We do this because release NPM
 | ||
|                 // packages could be different in the previous patch branch, and we want to set the
 | ||
|                 // LTS tag for all packages part of the last major. It would not be possible to set the
 | ||
|                 // NPM dist tag for new packages part of the released major, nor would it be acceptable
 | ||
|                 // to skip the LTS tag for packages which are no longer part of the new major.
 | ||
|                 yield invokeYarnInstallCommand(this.projectDir);
 | ||
|                 yield invokeSetNpmDistCommand(ltsTagForPatch, previousPatchVersion);
 | ||
|             }
 | ||
|             yield this.cherryPickChangelogIntoNextBranch(newVersion, branchName);
 | ||
|         });
 | ||
|     }
 | ||
|     /** Gets the new stable version of the release candidate release-train. */
 | ||
|     _computeNewVersion() {
 | ||
|         const { version } = this.active.releaseCandidate;
 | ||
|         return semver.parse(`${version.major}.${version.minor}.${version.patch}`);
 | ||
|     }
 | ||
|     static isActive(active) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             // A stable version can be cut for an active release-train currently in the
 | ||
|             // release-candidate phase. Note: It is not possible to directly release from
 | ||
|             // feature-freeze phase into a stable version.
 | ||
|             return active.releaseCandidate !== null &&
 | ||
|                 active.releaseCandidate.version.prerelease[0] === 'rc';
 | ||
|         });
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Release action that moves the next release-train into the feature-freeze phase. This means
 | ||
|  * that a new version branch is created from the next branch, and a new next pre-release is
 | ||
|  * cut indicating the started feature-freeze.
 | ||
|  */
 | ||
| class MoveNextIntoFeatureFreezeAction extends ReleaseAction {
 | ||
|     constructor() {
 | ||
|         super(...arguments);
 | ||
|         this._newVersion = computeNewPrereleaseVersionForNext(this.active, this.config);
 | ||
|     }
 | ||
|     getDescription() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const { branchName } = this.active.next;
 | ||
|             const newVersion = yield this._newVersion;
 | ||
|             return `Move the "${branchName}" branch into feature-freeze phase (v${newVersion}).`;
 | ||
|         });
 | ||
|     }
 | ||
|     perform() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const newVersion = yield this._newVersion;
 | ||
|             const newBranch = `${newVersion.major}.${newVersion.minor}.x`;
 | ||
|             // Branch-off the next branch into a feature-freeze branch.
 | ||
|             yield this._createNewVersionBranchFromNext(newBranch);
 | ||
|             // Stage the new version for the newly created branch, and push changes to a
 | ||
|             // fork in order to create a staging pull request. Note that we re-use the newly
 | ||
|             // created branch instead of re-fetching from the upstream.
 | ||
|             const stagingPullRequest = yield this.stageVersionForBranchAndCreatePullRequest(newVersion, newBranch);
 | ||
|             // Wait for the staging PR to be merged. Then build and publish the feature-freeze next
 | ||
|             // pre-release. Finally, cherry-pick the release notes into the next branch in combination
 | ||
|             // with bumping the version to the next minor too.
 | ||
|             yield this.waitForPullRequestToBeMerged(stagingPullRequest.id);
 | ||
|             yield this.buildAndPublish(newVersion, newBranch, 'next');
 | ||
|             yield this._createNextBranchUpdatePullRequest(newVersion, newBranch);
 | ||
|         });
 | ||
|     }
 | ||
|     /** Creates a new version branch from the next branch. */
 | ||
|     _createNewVersionBranchFromNext(newBranch) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const { branchName: nextBranch } = this.active.next;
 | ||
|             yield this.verifyPassingGithubStatus(nextBranch);
 | ||
|             yield this.checkoutUpstreamBranch(nextBranch);
 | ||
|             yield this.createLocalBranchFromHead(newBranch);
 | ||
|             yield this.pushHeadToRemoteBranch(newBranch);
 | ||
|             info(green(`  ✓   Version branch "${newBranch}" created.`));
 | ||
|         });
 | ||
|     }
 | ||
|     /**
 | ||
|      * Creates a pull request for the next branch that bumps the version to the next
 | ||
|      * minor, and cherry-picks the changelog for the newly branched-off feature-freeze version.
 | ||
|      */
 | ||
|     _createNextBranchUpdatePullRequest(newVersion, newBranch) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const { branchName: nextBranch, version } = this.active.next;
 | ||
|             // We increase the version for the next branch to the next minor. The team can decide
 | ||
|             // later if they want next to be a major through the `Configure Next as Major` release action.
 | ||
|             const newNextVersion = semver.parse(`${version.major}.${version.minor + 1}.0-next.0`);
 | ||
|             const bumpCommitMessage = getCommitMessageForExceptionalNextVersionBump(newNextVersion);
 | ||
|             yield this.checkoutUpstreamBranch(nextBranch);
 | ||
|             yield this.updateProjectVersion(newNextVersion);
 | ||
|             // Create an individual commit for the next version bump. The changelog should go into
 | ||
|             // a separate commit that makes it clear where the changelog is cherry-picked from.
 | ||
|             yield this.createCommit(bumpCommitMessage, [packageJsonPath]);
 | ||
|             let nextPullRequestMessage = `The previous "next" release-train has moved into the ` +
 | ||
|                 `release-candidate phase. This PR updates the next branch to the subsequent ` +
 | ||
|                 `release-train.`;
 | ||
|             const hasChangelogCherryPicked = yield this.createCherryPickReleaseNotesCommitFrom(newVersion, newBranch);
 | ||
|             if (hasChangelogCherryPicked) {
 | ||
|                 nextPullRequestMessage += `\n\nAlso this PR cherry-picks the changelog for ` +
 | ||
|                     `v${newVersion} into the ${nextBranch} branch so that the changelog is up to date.`;
 | ||
|             }
 | ||
|             else {
 | ||
|                 error(yellow(`  ✘   Could not cherry-pick release notes for v${newVersion}.`));
 | ||
|                 error(yellow(`      Please copy the release note manually into "${nextBranch}".`));
 | ||
|             }
 | ||
|             const nextUpdatePullRequest = yield this.pushChangesToForkAndCreatePullRequest(nextBranch, `next-release-train-${newNextVersion}`, `Update next branch to reflect new release-train "v${newNextVersion}".`, nextPullRequestMessage);
 | ||
|             info(green(`  ✓   Pull request for updating the "${nextBranch}" branch has been created.`));
 | ||
|             info(yellow(`      Please ask team members to review: ${nextUpdatePullRequest.url}.`));
 | ||
|         });
 | ||
|     }
 | ||
|     static isActive(active) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             // A new feature-freeze/release-candidate branch can only be created if there
 | ||
|             // is no active release-train in feature-freeze/release-candidate phase.
 | ||
|             return active.releaseCandidate === null;
 | ||
|         });
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * List of release actions supported by the release staging tool. These are sorted
 | ||
|  * by priority. Actions which are selectable are sorted based on this declaration order.
 | ||
|  */
 | ||
| const actions = [
 | ||
|     CutStableAction,
 | ||
|     CutReleaseCandidateAction,
 | ||
|     CutNewPatchAction,
 | ||
|     CutNextPrereleaseAction,
 | ||
|     MoveNextIntoFeatureFreezeAction,
 | ||
|     CutLongTermSupportPatchAction,
 | ||
| ];
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| var CompletionState;
 | ||
| (function (CompletionState) {
 | ||
|     CompletionState[CompletionState["SUCCESS"] = 0] = "SUCCESS";
 | ||
|     CompletionState[CompletionState["FATAL_ERROR"] = 1] = "FATAL_ERROR";
 | ||
|     CompletionState[CompletionState["MANUALLY_ABORTED"] = 2] = "MANUALLY_ABORTED";
 | ||
| })(CompletionState || (CompletionState = {}));
 | ||
| class ReleaseTool {
 | ||
|     constructor(_config, _github, _githubToken, _projectRoot) {
 | ||
|         this._config = _config;
 | ||
|         this._github = _github;
 | ||
|         this._githubToken = _githubToken;
 | ||
|         this._projectRoot = _projectRoot;
 | ||
|         /** Client for interacting with the Github API and the local Git command. */
 | ||
|         this._git = new GitClient(this._githubToken, { github: this._github }, this._projectRoot);
 | ||
|     }
 | ||
|     /** Runs the interactive release tool. */
 | ||
|     run() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             log();
 | ||
|             log(yellow('--------------------------------------------'));
 | ||
|             log(yellow('  Angular Dev-Infra release staging script'));
 | ||
|             log(yellow('--------------------------------------------'));
 | ||
|             log();
 | ||
|             if (!(yield this._verifyNoUncommittedChanges()) || !(yield this._verifyRunningFromNextBranch())) {
 | ||
|                 return CompletionState.FATAL_ERROR;
 | ||
|             }
 | ||
|             const { owner, name } = this._github;
 | ||
|             const repo = { owner, name, api: this._git.github };
 | ||
|             const releaseTrains = yield fetchActiveReleaseTrains(repo);
 | ||
|             // Print the active release trains so that the caretaker can access
 | ||
|             // the current project branching state without switching context.
 | ||
|             yield printActiveReleaseTrains(releaseTrains, this._config);
 | ||
|             const action = yield this._promptForReleaseAction(releaseTrains);
 | ||
|             const previousGitBranchOrRevision = this._git.getCurrentBranchOrRevision();
 | ||
|             try {
 | ||
|                 yield action.perform();
 | ||
|             }
 | ||
|             catch (e) {
 | ||
|                 if (e instanceof UserAbortedReleaseActionError) {
 | ||
|                     return CompletionState.MANUALLY_ABORTED;
 | ||
|                 }
 | ||
|                 // Only print the error message and stack if the error is not a known fatal release
 | ||
|                 // action error (for which we print the error gracefully to the console with colors).
 | ||
|                 if (!(e instanceof FatalReleaseActionError) && e instanceof Error) {
 | ||
|                     console.error(e);
 | ||
|                 }
 | ||
|                 return CompletionState.FATAL_ERROR;
 | ||
|             }
 | ||
|             finally {
 | ||
|                 this._git.checkout(previousGitBranchOrRevision, true);
 | ||
|             }
 | ||
|             return CompletionState.SUCCESS;
 | ||
|         });
 | ||
|     }
 | ||
|     /** Prompts the caretaker for a release action that should be performed. */
 | ||
|     _promptForReleaseAction(activeTrains) {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const choices = [];
 | ||
|             // Find and instantiate all release actions which are currently valid.
 | ||
|             for (let actionType of actions) {
 | ||
|                 if (yield actionType.isActive(activeTrains)) {
 | ||
|                     const action = new actionType(activeTrains, this._git, this._config, this._projectRoot);
 | ||
|                     choices.push({ name: yield action.getDescription(), value: action });
 | ||
|                 }
 | ||
|             }
 | ||
|             info('Please select the type of release you want to perform.');
 | ||
|             const { releaseAction } = yield inquirer.prompt({
 | ||
|                 name: 'releaseAction',
 | ||
|                 message: 'Please select an action:',
 | ||
|                 type: 'list',
 | ||
|                 choices,
 | ||
|             });
 | ||
|             return releaseAction;
 | ||
|         });
 | ||
|     }
 | ||
|     /**
 | ||
|      * Verifies that there are no uncommitted changes in the project.
 | ||
|      * @returns a boolean indicating success or failure.
 | ||
|      */
 | ||
|     _verifyNoUncommittedChanges() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             if (this._git.hasUncommittedChanges()) {
 | ||
|                 error(red('  ✘   There are changes which are not committed and should be discarded.'));
 | ||
|                 return false;
 | ||
|             }
 | ||
|             return true;
 | ||
|         });
 | ||
|     }
 | ||
|     /**
 | ||
|      * Verifies that the next branch from the configured repository is checked out.
 | ||
|      * @returns a boolean indicating success or failure.
 | ||
|      */
 | ||
|     _verifyRunningFromNextBranch() {
 | ||
|         return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|             const headSha = this._git.run(['rev-parse', 'HEAD']).stdout.trim();
 | ||
|             const { data } = yield this._git.github.repos.getBranch(Object.assign(Object.assign({}, this._git.remoteParams), { branch: nextBranchName }));
 | ||
|             if (headSha !== data.commit.sha) {
 | ||
|                 error(red('  ✘   Running release tool from an outdated local branch.'));
 | ||
|                 error(red(`      Please make sure you are running from the "${nextBranchName}" branch.`));
 | ||
|                 return false;
 | ||
|             }
 | ||
|             return true;
 | ||
|         });
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Yargs command builder for configuring the `ng-dev release publish` command. */
 | ||
| function builder$8(argv) {
 | ||
|     return addGithubTokenOption(argv);
 | ||
| }
 | ||
| /** Yargs command handler for staging a release. */
 | ||
| function handler$8(args) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const config = getConfig();
 | ||
|         const releaseConfig = getReleaseConfig(config);
 | ||
|         const projectDir = getRepoBaseDir();
 | ||
|         const task = new ReleaseTool(releaseConfig, config.github, args.githubToken, projectDir);
 | ||
|         const result = yield task.run();
 | ||
|         switch (result) {
 | ||
|             case CompletionState.FATAL_ERROR:
 | ||
|                 error(red(`Release action has been aborted due to fatal errors. See above.`));
 | ||
|                 process.exitCode = 1;
 | ||
|                 break;
 | ||
|             case CompletionState.MANUALLY_ABORTED:
 | ||
|                 info(yellow(`Release action has been manually aborted.`));
 | ||
|                 break;
 | ||
|             case CompletionState.SUCCESS:
 | ||
|                 info(green(`Release action has completed successfully.`));
 | ||
|                 break;
 | ||
|         }
 | ||
|     });
 | ||
| }
 | ||
| /** CLI command module for publishing a release. */
 | ||
| const ReleasePublishCommandModule = {
 | ||
|     builder: builder$8,
 | ||
|     handler: handler$8,
 | ||
|     command: 'publish',
 | ||
|     describe: 'Publish new releases and configure version branches.',
 | ||
| };
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| function builder$9(args) {
 | ||
|     return args
 | ||
|         .positional('tagName', {
 | ||
|         type: 'string',
 | ||
|         demandOption: true,
 | ||
|         description: 'Name of the NPM dist tag.',
 | ||
|     })
 | ||
|         .positional('targetVersion', {
 | ||
|         type: 'string',
 | ||
|         demandOption: true,
 | ||
|         description: 'Version to which the dist tag should be set.'
 | ||
|     });
 | ||
| }
 | ||
| /** Yargs command handler for building a release. */
 | ||
| function handler$9(args) {
 | ||
|     return tslib.__awaiter(this, void 0, void 0, function* () {
 | ||
|         const { targetVersion: rawVersion, tagName } = args;
 | ||
|         const { npmPackages, publishRegistry } = getReleaseConfig();
 | ||
|         const version = semver.parse(rawVersion);
 | ||
|         if (version === null) {
 | ||
|             error(red(`Invalid version specified (${rawVersion}). Unable to set NPM dist tag.`));
 | ||
|             process.exit(1);
 | ||
|         }
 | ||
|         const spinner = ora.call(undefined).start();
 | ||
|         debug(`Setting "${tagName}" NPM dist tag for release packages to v${version}.`);
 | ||
|         for (const pkgName of npmPackages) {
 | ||
|             spinner.text = `Setting NPM dist tag for "${pkgName}"`;
 | ||
|             spinner.render();
 | ||
|             try {
 | ||
|                 yield setNpmTagForPackage(pkgName, tagName, version, publishRegistry);
 | ||
|                 debug(`Successfully set "${tagName}" NPM dist tag for "${pkgName}".`);
 | ||
|             }
 | ||
|             catch (e) {
 | ||
|                 spinner.stop();
 | ||
|                 error(e);
 | ||
|                 error(red(`  ✘   An error occurred while setting the NPM dist tag for "${pkgName}".`));
 | ||
|                 process.exit(1);
 | ||
|             }
 | ||
|         }
 | ||
|         spinner.stop();
 | ||
|         info(green(`  ✓   Set NPM dist tag for all release packages.`));
 | ||
|         info(green(`      ${bold(tagName)} will now point to ${bold(`v${version}`)}.`));
 | ||
|     });
 | ||
| }
 | ||
| /** CLI command module for setting an NPM dist tag. */
 | ||
| const ReleaseSetDistTagCommand = {
 | ||
|     builder: builder$9,
 | ||
|     handler: handler$9,
 | ||
|     command: 'set-dist-tag <tag-name> <target-version>',
 | ||
|     describe: 'Sets a given NPM dist tag for all release packages.',
 | ||
| };
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Log the environment variables expected by bazel for stamping.
 | ||
|  *
 | ||
|  * See the section on stamping in docs / BAZEL.md
 | ||
|  *
 | ||
|  * This script must be a NodeJS script in order to be cross-platform.
 | ||
|  * See https://github.com/bazelbuild/bazel/issues/5958
 | ||
|  * Note: git operations, especially git status, take a long time inside mounted docker volumes
 | ||
|  * in Windows or OSX hosts (https://github.com/docker/for-win/issues/188).
 | ||
|  */
 | ||
| function buildEnvStamp() {
 | ||
|     console.info(`BUILD_SCM_BRANCH ${getCurrentBranch()}`);
 | ||
|     console.info(`BUILD_SCM_COMMIT_SHA ${getCurrentSha()}`);
 | ||
|     console.info(`BUILD_SCM_HASH ${getCurrentSha()}`);
 | ||
|     console.info(`BUILD_SCM_LOCAL_CHANGES ${hasLocalChanges()}`);
 | ||
|     console.info(`BUILD_SCM_USER ${getCurrentGitUser()}`);
 | ||
|     console.info(`BUILD_SCM_VERSION ${getSCMVersion()}`);
 | ||
|     process.exit(0);
 | ||
| }
 | ||
| /** Run the exec command and return the stdout as a trimmed string. */
 | ||
| function exec$1(cmd) {
 | ||
|     return exec(cmd).trim();
 | ||
| }
 | ||
| /** Whether the repo has local changes. */
 | ||
| function hasLocalChanges() {
 | ||
|     return !!exec$1(`git status --untracked-files=no --porcelain`);
 | ||
| }
 | ||
| /** Get the version based on the most recent semver tag. */
 | ||
| function getSCMVersion() {
 | ||
|     const version = exec$1(`git describe --match [0-9]*.[0-9]*.[0-9]* --abbrev=7 --tags HEAD`);
 | ||
|     return `${version.replace(/-([0-9]+)-g/, '+$1.sha-')}${(hasLocalChanges() ? '.with-local-changes' : '')}`;
 | ||
| }
 | ||
| /** Get the current SHA of HEAD. */
 | ||
| function getCurrentSha() {
 | ||
|     return exec$1(`git rev-parse HEAD`);
 | ||
| }
 | ||
| /** Get the currently checked out branch. */
 | ||
| function getCurrentBranch() {
 | ||
|     return exec$1(`git symbolic-ref --short HEAD`);
 | ||
| }
 | ||
| /** Get the current git user based on the git config. */
 | ||
| function getCurrentGitUser() {
 | ||
|     const userName = exec$1(`git config user.name`);
 | ||
|     const userEmail = exec$1(`git config user.email`);
 | ||
|     return `${userName} <${userEmail}>`;
 | ||
| }
 | ||
| 
 | ||
| /** Build the parser for the release commands. */
 | ||
| function buildReleaseParser(localYargs) {
 | ||
|     return localYargs.help()
 | ||
|         .strict()
 | ||
|         .demandCommand()
 | ||
|         .command(ReleasePublishCommandModule)
 | ||
|         .command(ReleaseBuildCommandModule)
 | ||
|         .command(ReleaseSetDistTagCommand)
 | ||
|         .command('build-env-stamp', 'Build the environment stamping information', {}, () => buildEnvStamp());
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Gets the status of the specified file. Returns null if the file does not exist. */
 | ||
| function getFileStatus(filePath) {
 | ||
|     try {
 | ||
|         return fs.statSync(filePath);
 | ||
|     }
 | ||
|     catch (_a) {
 | ||
|         return null;
 | ||
|     }
 | ||
| }
 | ||
| /** Ensures that the specified path uses forward slashes as delimiter. */
 | ||
| function convertPathToForwardSlash(path) {
 | ||
|     return path.replace(/\\/g, '/');
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Finds all module references in the specified source file.
 | ||
|  * @param node Source file which should be parsed.
 | ||
|  * @returns List of import specifiers in the source file.
 | ||
|  */
 | ||
| function getModuleReferences(node) {
 | ||
|     const references = [];
 | ||
|     const visitNode = (node) => {
 | ||
|         if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) &&
 | ||
|             node.moduleSpecifier !== undefined && ts.isStringLiteral(node.moduleSpecifier)) {
 | ||
|             references.push(node.moduleSpecifier.text);
 | ||
|         }
 | ||
|         ts.forEachChild(node, visitNode);
 | ||
|     };
 | ||
|     ts.forEachChild(node, visitNode);
 | ||
|     return references;
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /** Default extensions that the analyzer uses for resolving imports. */
 | ||
| const DEFAULT_EXTENSIONS = ['ts', 'js', 'd.ts'];
 | ||
| /**
 | ||
|  * Analyzer that can be used to detect import cycles within source files. It supports
 | ||
|  * custom module resolution, source file caching and collects unresolved specifiers.
 | ||
|  */
 | ||
| class Analyzer {
 | ||
|     constructor(resolveModuleFn, extensions = DEFAULT_EXTENSIONS) {
 | ||
|         this.resolveModuleFn = resolveModuleFn;
 | ||
|         this.extensions = extensions;
 | ||
|         this._sourceFileCache = new Map();
 | ||
|         this.unresolvedModules = new Set();
 | ||
|         this.unresolvedFiles = new Map();
 | ||
|     }
 | ||
|     /** Finds all cycles in the specified source file. */
 | ||
|     findCycles(sf, visited = new WeakSet(), path = []) {
 | ||
|         const previousIndex = path.indexOf(sf);
 | ||
|         // If the given node is already part of the current path, then a cycle has
 | ||
|         // been found. Add the reference chain which represents the cycle to the results.
 | ||
|         if (previousIndex !== -1) {
 | ||
|             return [path.slice(previousIndex)];
 | ||
|         }
 | ||
|         // If the node has already been visited, then it's not necessary to go check its edges
 | ||
|         // again. Cycles would have been already detected and collected in the first check.
 | ||
|         if (visited.has(sf)) {
 | ||
|             return [];
 | ||
|         }
 | ||
|         path.push(sf);
 | ||
|         visited.add(sf);
 | ||
|         // Go through all edges, which are determined through import/exports, and collect cycles.
 | ||
|         const result = [];
 | ||
|         for (const ref of getModuleReferences(sf)) {
 | ||
|             const targetFile = this._resolveImport(ref, sf.fileName);
 | ||
|             if (targetFile !== null) {
 | ||
|                 result.push(...this.findCycles(this.getSourceFile(targetFile), visited, path.slice()));
 | ||
|             }
 | ||
|         }
 | ||
|         return result;
 | ||
|     }
 | ||
|     /** Gets the TypeScript source file of the specified path. */
 | ||
|     getSourceFile(filePath) {
 | ||
|         const resolvedPath = path.resolve(filePath);
 | ||
|         if (this._sourceFileCache.has(resolvedPath)) {
 | ||
|             return this._sourceFileCache.get(resolvedPath);
 | ||
|         }
 | ||
|         const fileContent = fs.readFileSync(resolvedPath, 'utf8');
 | ||
|         const sourceFile = ts.createSourceFile(resolvedPath, fileContent, ts.ScriptTarget.Latest, false);
 | ||
|         this._sourceFileCache.set(resolvedPath, sourceFile);
 | ||
|         return sourceFile;
 | ||
|     }
 | ||
|     /** Resolves the given import specifier with respect to the specified containing file path. */
 | ||
|     _resolveImport(specifier, containingFilePath) {
 | ||
|         if (specifier.charAt(0) === '.') {
 | ||
|             const resolvedPath = this._resolveFileSpecifier(specifier, containingFilePath);
 | ||
|             if (resolvedPath === null) {
 | ||
|                 this._trackUnresolvedFileImport(specifier, containingFilePath);
 | ||
|             }
 | ||
|             return resolvedPath;
 | ||
|         }
 | ||
|         if (this.resolveModuleFn) {
 | ||
|             const targetFile = this.resolveModuleFn(specifier);
 | ||
|             if (targetFile !== null) {
 | ||
|                 const resolvedPath = this._resolveFileSpecifier(targetFile);
 | ||
|                 if (resolvedPath !== null) {
 | ||
|                     return resolvedPath;
 | ||
|                 }
 | ||
|             }
 | ||
|         }
 | ||
|         this.unresolvedModules.add(specifier);
 | ||
|         return null;
 | ||
|     }
 | ||
|     /** Tracks the given file import as unresolved. */
 | ||
|     _trackUnresolvedFileImport(specifier, originFilePath) {
 | ||
|         if (!this.unresolvedFiles.has(originFilePath)) {
 | ||
|             this.unresolvedFiles.set(originFilePath, [specifier]);
 | ||
|         }
 | ||
|         this.unresolvedFiles.get(originFilePath).push(specifier);
 | ||
|     }
 | ||
|     /** Resolves the given import specifier to the corresponding source file. */
 | ||
|     _resolveFileSpecifier(specifier, containingFilePath) {
 | ||
|         const importFullPath = containingFilePath !== undefined ? path.join(path.dirname(containingFilePath), specifier) : specifier;
 | ||
|         const stat = getFileStatus(importFullPath);
 | ||
|         if (stat && stat.isFile()) {
 | ||
|             return importFullPath;
 | ||
|         }
 | ||
|         for (const extension of this.extensions) {
 | ||
|             const pathWithExtension = `${importFullPath}.${extension}`;
 | ||
|             const stat = getFileStatus(pathWithExtension);
 | ||
|             if (stat && stat.isFile()) {
 | ||
|                 return pathWithExtension;
 | ||
|             }
 | ||
|         }
 | ||
|         // Directories should be considered last. TypeScript first looks for source files, then
 | ||
|         // falls back to directories if no file with appropriate extension could be found.
 | ||
|         if (stat && stat.isDirectory()) {
 | ||
|             return this._resolveFileSpecifier(path.join(importFullPath, 'index'));
 | ||
|         }
 | ||
|         return null;
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Loads the configuration for the circular dependencies test. If the config cannot be
 | ||
|  * loaded, an error will be printed and the process exists with a non-zero exit code.
 | ||
|  */
 | ||
| function loadTestConfig(configPath) {
 | ||
|     const configBaseDir = path.dirname(configPath);
 | ||
|     const resolveRelativePath = (relativePath) => path.resolve(configBaseDir, relativePath);
 | ||
|     try {
 | ||
|         const config = require(configPath);
 | ||
|         if (!path.isAbsolute(config.baseDir)) {
 | ||
|             config.baseDir = resolveRelativePath(config.baseDir);
 | ||
|         }
 | ||
|         if (!path.isAbsolute(config.goldenFile)) {
 | ||
|             config.goldenFile = resolveRelativePath(config.goldenFile);
 | ||
|         }
 | ||
|         if (!path.isAbsolute(config.glob)) {
 | ||
|             config.glob = resolveRelativePath(config.glob);
 | ||
|         }
 | ||
|         return config;
 | ||
|     }
 | ||
|     catch (e) {
 | ||
|         error('Could not load test configuration file at: ' + configPath);
 | ||
|         error(`Failed with: ${e.message}`);
 | ||
|         process.exit(1);
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| /**
 | ||
|  * Converts a list of reference chains to a JSON-compatible golden object. Reference chains
 | ||
|  * by default use TypeScript source file objects. In order to make those chains printable,
 | ||
|  * the source file objects are mapped to their relative file names.
 | ||
|  */
 | ||
| function convertReferenceChainToGolden(refs, baseDir) {
 | ||
|     return refs
 | ||
|         .map(
 | ||
|     // Normalize cycles as the paths can vary based on which node in the cycle is visited
 | ||
|     // first in the analyzer. The paths represent cycles. Hence we can shift nodes in a
 | ||
|     // deterministic way so that the goldens don't change unnecessarily and cycle comparison
 | ||
|     // is simpler.
 | ||
|     chain => normalizeCircularDependency(chain.map(({ fileName }) => convertPathToForwardSlash(path.relative(baseDir, fileName)))))
 | ||
|         // Sort cycles so that the golden doesn't change unnecessarily when cycles are detected
 | ||
|         // in different order (e.g. new imports cause cycles to be detected earlier or later).
 | ||
|         .sort(compareCircularDependency);
 | ||
| }
 | ||
| /**
 | ||
|  * Compares the specified goldens and returns two lists that describe newly
 | ||
|  * added circular dependencies, or fixed circular dependencies.
 | ||
|  */
 | ||
| function compareGoldens(actual, expected) {
 | ||
|     const newCircularDeps = [];
 | ||
|     const fixedCircularDeps = [];
 | ||
|     actual.forEach(a => {
 | ||
|         if (!expected.find(e => isSameCircularDependency(a, e))) {
 | ||
|             newCircularDeps.push(a);
 | ||
|         }
 | ||
|     });
 | ||
|     expected.forEach(e => {
 | ||
|         if (!actual.find(a => isSameCircularDependency(e, a))) {
 | ||
|             fixedCircularDeps.push(e);
 | ||
|         }
 | ||
|     });
 | ||
|     return { newCircularDeps, fixedCircularDeps };
 | ||
| }
 | ||
| /**
 | ||
|  * Normalizes the a circular dependency by ensuring that the path starts with the first
 | ||
|  * node in alphabetical order. Since the path array represents a cycle, we can make a
 | ||
|  * specific node the first element in the path that represents the cycle.
 | ||
|  *
 | ||
|  * This method is helpful because the path of circular dependencies changes based on which
 | ||
|  * file in the path has been visited first by the analyzer. e.g. Assume we have a circular
 | ||
|  * dependency represented as: `A -> B -> C`. The analyzer will detect this cycle when it
 | ||
|  * visits `A`. Though when a source file that is analyzed before `A` starts importing `B`,
 | ||
|  * the cycle path will detected as `B -> C -> A`. This represents the same cycle, but is just
 | ||
|  * different due to a limitation of using a data structure that can be written to a text-based
 | ||
|  * golden file.
 | ||
|  *
 | ||
|  * To account for this non-deterministic behavior in goldens, we shift the circular
 | ||
|  * dependency path to the first node based on alphabetical order. e.g. `A` will always
 | ||
|  * be the first node in the path that represents the cycle.
 | ||
|  */
 | ||
| function normalizeCircularDependency(path) {
 | ||
|     if (path.length <= 1) {
 | ||
|         return path;
 | ||
|     }
 | ||
|     let indexFirstNode = 0;
 | ||
|     let valueFirstNode = path[0];
 | ||
|     // Find a node in the cycle path that precedes all other elements
 | ||
|     // in terms of alphabetical order.
 | ||
|     for (let i = 1; i < path.length; i++) {
 | ||
|         const value = path[i];
 | ||
|         if (value.localeCompare(valueFirstNode, 'en') < 0) {
 | ||
|             indexFirstNode = i;
 | ||
|             valueFirstNode = value;
 | ||
|         }
 | ||
|     }
 | ||
|     // If the alphabetically first node is already at start of the path, just
 | ||
|     // return the actual path as no changes need to be made.
 | ||
|     if (indexFirstNode === 0) {
 | ||
|         return path;
 | ||
|     }
 | ||
|     // Move the determined first node (as of alphabetical order) to the start of a new
 | ||
|     // path array. The nodes before the first node in the old path are then concatenated
 | ||
|     // to the end of the new path. This is possible because the path represents a cycle.
 | ||
|     return [...path.slice(indexFirstNode), ...path.slice(0, indexFirstNode)];
 | ||
| }
 | ||
| /** Checks whether the specified circular dependencies are equal. */
 | ||
| function isSameCircularDependency(actual, expected) {
 | ||
|     if (actual.length !== expected.length) {
 | ||
|         return false;
 | ||
|     }
 | ||
|     for (let i = 0; i < actual.length; i++) {
 | ||
|         if (actual[i] !== expected[i]) {
 | ||
|             return false;
 | ||
|         }
 | ||
|     }
 | ||
|     return true;
 | ||
| }
 | ||
| /**
 | ||
|  * Compares two circular dependencies by respecting the alphabetic order of nodes in the
 | ||
|  * cycle paths. The first nodes which don't match in both paths are decisive on the order.
 | ||
|  */
 | ||
| function compareCircularDependency(a, b) {
 | ||
|     // Go through nodes in both cycle paths and determine whether `a` should be ordered
 | ||
|     // before `b`. The first nodes which don't match decide on the order.
 | ||
|     for (let i = 0; i < Math.min(a.length, b.length); i++) {
 | ||
|         const compareValue = a[i].localeCompare(b[i], 'en');
 | ||
|         if (compareValue !== 0) {
 | ||
|             return compareValue;
 | ||
|         }
 | ||
|     }
 | ||
|     // If all nodes are equal in the cycles, the order is based on the length of both cycles.
 | ||
|     return a.length - b.length;
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google LLC All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| function tsCircularDependenciesBuilder(localYargs) {
 | ||
|     return localYargs.help()
 | ||
|         .strict()
 | ||
|         .demandCommand()
 | ||
|         .option('config', { type: 'string', demandOption: true, description: 'Path to the configuration file.' })
 | ||
|         .option('warnings', { type: 'boolean', description: 'Prints all warnings.' })
 | ||
|         .command('check', 'Checks if the circular dependencies have changed.', args => args, argv => {
 | ||
|         const { config: configArg, warnings } = argv;
 | ||
|         const configPath = path.isAbsolute(configArg) ? configArg : path.resolve(configArg);
 | ||
|         const config = loadTestConfig(configPath);
 | ||
|         process.exit(main(false, config, !!warnings));
 | ||
|     })
 | ||
|         .command('approve', 'Approves the current circular dependencies.', args => args, argv => {
 | ||
|         const { config: configArg, warnings } = argv;
 | ||
|         const configPath = path.isAbsolute(configArg) ? configArg : path.resolve(configArg);
 | ||
|         const config = loadTestConfig(configPath);
 | ||
|         process.exit(main(true, config, !!warnings));
 | ||
|     });
 | ||
| }
 | ||
| /**
 | ||
|  * Runs the ts-circular-dependencies tool.
 | ||
|  * @param approve Whether the detected circular dependencies should be approved.
 | ||
|  * @param config Configuration for the current circular dependencies test.
 | ||
|  * @param printWarnings Whether warnings should be printed out.
 | ||
|  * @returns Status code.
 | ||
|  */
 | ||
| function main(approve, config, printWarnings) {
 | ||
|     const { baseDir, goldenFile, glob: glob$1, resolveModule, approveCommand } = config;
 | ||
|     const analyzer = new Analyzer(resolveModule);
 | ||
|     const cycles = [];
 | ||
|     const checkedNodes = new WeakSet();
 | ||
|     glob.sync(glob$1, { absolute: true }).forEach(filePath => {
 | ||
|         const sourceFile = analyzer.getSourceFile(filePath);
 | ||
|         cycles.push(...analyzer.findCycles(sourceFile, checkedNodes));
 | ||
|     });
 | ||
|     const actual = convertReferenceChainToGolden(cycles, baseDir);
 | ||
|     info(green(`   Current number of cycles: ${yellow(cycles.length.toString())}`));
 | ||
|     if (approve) {
 | ||
|         fs.writeFileSync(goldenFile, JSON.stringify(actual, null, 2));
 | ||
|         info(green('✅  Updated golden file.'));
 | ||
|         return 0;
 | ||
|     }
 | ||
|     else if (!fs.existsSync(goldenFile)) {
 | ||
|         error(red(`❌  Could not find golden file: ${goldenFile}`));
 | ||
|         return 1;
 | ||
|     }
 | ||
|     const warningsCount = analyzer.unresolvedFiles.size + analyzer.unresolvedModules.size;
 | ||
|     // By default, warnings for unresolved files or modules are not printed. This is because
 | ||
|     // it's common that third-party modules are not resolved/visited. Also generated files
 | ||
|     // from the View Engine compiler (i.e. factories, summaries) cannot be resolved.
 | ||
|     if (printWarnings && warningsCount !== 0) {
 | ||
|         info(yellow('⚠  The following imports could not be resolved:'));
 | ||
|         Array.from(analyzer.unresolvedModules).sort().forEach(specifier => info(`  • ${specifier}`));
 | ||
|         analyzer.unresolvedFiles.forEach((value, key) => {
 | ||
|             info(`  • ${getRelativePath(baseDir, key)}`);
 | ||
|             value.sort().forEach(specifier => info(`      ${specifier}`));
 | ||
|         });
 | ||
|     }
 | ||
|     else {
 | ||
|         info(yellow(`⚠  ${warningsCount} imports could not be resolved.`));
 | ||
|         info(yellow(`   Please rerun with "--warnings" to inspect unresolved imports.`));
 | ||
|     }
 | ||
|     const expected = JSON.parse(fs.readFileSync(goldenFile, 'utf8'));
 | ||
|     const { fixedCircularDeps, newCircularDeps } = compareGoldens(actual, expected);
 | ||
|     const isMatching = fixedCircularDeps.length === 0 && newCircularDeps.length === 0;
 | ||
|     if (isMatching) {
 | ||
|         info(green('✅  Golden matches current circular dependencies.'));
 | ||
|         return 0;
 | ||
|     }
 | ||
|     error(red('❌  Golden does not match current circular dependencies.'));
 | ||
|     if (newCircularDeps.length !== 0) {
 | ||
|         error(yellow(`   New circular dependencies which are not allowed:`));
 | ||
|         newCircularDeps.forEach(c => error(`     • ${convertReferenceChainToString(c)}`));
 | ||
|         error();
 | ||
|     }
 | ||
|     if (fixedCircularDeps.length !== 0) {
 | ||
|         error(yellow(`   Fixed circular dependencies that need to be removed from the golden:`));
 | ||
|         fixedCircularDeps.forEach(c => error(`     • ${convertReferenceChainToString(c)}`));
 | ||
|         info(yellow(`\n   Total: ${newCircularDeps.length} new cycle(s), ${fixedCircularDeps.length} fixed cycle(s). \n`));
 | ||
|         if (approveCommand) {
 | ||
|             info(yellow(`   Please approve the new golden with: ${approveCommand}`));
 | ||
|         }
 | ||
|         else {
 | ||
|             info(yellow(`   Please update the golden. The following command can be ` +
 | ||
|                 `run: yarn ts-circular-deps approve ${getRelativePath(process.cwd(), goldenFile)}.`));
 | ||
|         }
 | ||
|     }
 | ||
|     return 1;
 | ||
| }
 | ||
| /** Gets the specified path relative to the base directory. */
 | ||
| function getRelativePath(baseDir, path$1) {
 | ||
|     return convertPathToForwardSlash(path.relative(baseDir, path$1));
 | ||
| }
 | ||
| /** Converts the given reference chain to its string representation. */
 | ||
| function convertReferenceChainToString(chain) {
 | ||
|     return chain.join(' → ');
 | ||
| }
 | ||
| 
 | ||
| yargs.scriptName('ng-dev')
 | ||
|     .middleware(captureLogOutputForCommand)
 | ||
|     .demandCommand()
 | ||
|     .recommendCommands()
 | ||
|     .command('commit-message <command>', '', buildCommitMessageParser)
 | ||
|     .command('format <command>', '', buildFormatParser)
 | ||
|     .command('pr <command>', '', buildPrParser)
 | ||
|     .command('pullapprove <command>', '', buildPullapproveParser)
 | ||
|     .command('release <command>', '', buildReleaseParser)
 | ||
|     .command('ts-circular-deps <command>', '', tsCircularDependenciesBuilder)
 | ||
|     .command('caretaker <command>', '', buildCaretakerParser)
 | ||
|     .command('ngbot <command>', false, buildNgbotParser)
 | ||
|     .wrap(120)
 | ||
|     .strict()
 | ||
|     .parse();
 |