angular-docs-cn/dev-infra/build-worker.js
Paul Gschwendtner 279e63f65f refactor(dev-infra): update octokit to latest version v18 (#42666)
We previously held off with updating Octokit to v18 due to
their more noticable issues with typings. This commit updates
us to the latest version in order to take advantage of the new
pagination API (which is also strongly-typed), and to not fall
behind too much over time (Octokit seems to change quite often..)

We work around the problem with the types for `getContent` by just
using a type cast with a TODO (and link to the issue). Similarly we
work around a problem where the Octokit types have an incorrect type
for the name of the labels array in an API response.

PR Close #42666
2021-06-29 10:33:54 -07:00

595 lines
25 KiB
JavaScript

'use strict';
var tslib = require('tslib');
var fs = require('fs');
var path = require('path');
require('chalk');
require('inquirer');
var child_process = require('child_process');
var semver = require('semver');
var graphql = require('@octokit/graphql');
var rest = require('@octokit/rest');
var typedGraphqlify = require('typed-graphqlify');
var url = require('url');
/**
* @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 current environment is in dry run mode. */
function isDryRun() {
return process.env['DRY_RUN'] !== undefined;
}
/** Error to be thrown when a function or method is called in dryRun mode and shouldn't be. */
var DryRunError = /** @class */ (function (_super) {
tslib.__extends(DryRunError, _super);
function DryRunError() {
var _this = _super.call(this, 'Cannot call this function in dryRun mode.') || this;
// 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, DryRunError.prototype);
return _this;
}
return DryRunError;
}(Error));
/**
* @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. */
var GithubClient = /** @class */ (function () {
function GithubClient(_octokitOptions) {
this._octokitOptions = _octokitOptions;
/** The octokit instance actually performing API requests. */
this._octokit = new rest.Octokit(this._octokitOptions);
this.pulls = this._octokit.pulls;
this.repos = this._octokit.repos;
this.issues = this._octokit.issues;
this.git = this._octokit.git;
this.rateLimit = this._octokit.rateLimit;
// Note: These are properties from `Octokit` that are brought in by optional plugins.
// TypeScript requires us to provide an explicit type for these.
this.rest = this._octokit.rest;
this.paginate = this._octokit.paginate;
}
return GithubClient;
}());
/**
* Extension of the `GithubClient` that provides utilities which are specific
* to authenticated instances.
*/
var AuthenticatedGithubClient = /** @class */ (function (_super) {
tslib.__extends(AuthenticatedGithubClient, _super);
function AuthenticatedGithubClient(_token) {
var _this =
// Set the token for the octokit instance.
_super.call(this, { auth: _token }) || this;
_this._token = _token;
/** The graphql instance with authentication set during construction. */
_this._graphql = graphql.graphql.defaults({ headers: { authorization: "token " + _this._token } });
return _this;
}
/** Perform a query using Github's Graphql API. */
AuthenticatedGithubClient.prototype.graphql = function (queryObject, params) {
if (params === void 0) { params = {}; }
return tslib.__awaiter(this, void 0, void 0, function () {
return tslib.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this._graphql(typedGraphqlify.query(queryObject).toString(), params)];
case 1: return [2 /*return*/, (_a.sent())];
}
});
});
};
return AuthenticatedGithubClient;
}(GithubClient));
/**
* @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
*/
/** 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;
}
/**
* @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.sanitizeConsoleOutput(args.join(' '))) || this;
_this.args = args;
return _this;
}
return GitCommandError;
}(Error));
/** Class that can be used to perform Git interactions with a given remote. **/
var GitClient = /** @class */ (function () {
function GitClient(
/** The full path to the root of the repository base. */
baseDir,
/** The configuration, containing the github specific configuration. */
config) {
if (baseDir === void 0) { baseDir = determineRepoBaseDirFromCwd(); }
if (config === void 0) { config = getConfig(baseDir); }
this.baseDir = baseDir;
this.config = config;
/** 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 };
/** Instance of the Github client. */
this.github = new GithubClient();
}
/** 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 = {}; }
/** The git command to be run. */
var gitCommand = args[0];
if (isDryRun() && gitCommand === 'push') {
debug("\"git push\" is not able to be run in dryRun mode.");
throw new DryRunError();
}
// To improve the debugging experience in case something fails, we print all executed Git
// commands at the DEBUG level to better understand the git actions occurring. Verbose logging,
// always logging at the INFO level, can be enabled either by setting the verboseLogging
// property on the GitClient class or the options object provided to the method.
var printFn = (GitClient.verboseLogging || options.verboseLogging) ? info : debug;
// Note that we sanitize the command before printing it to the console. We do not want to
// print an access 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.sanitizeConsoleOutput(args.join(' ')));
var result = child_process.spawnSync('git', args, tslib.__assign(tslib.__assign({ cwd: this.baseDir, 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.sanitizeConsoleOutput(result.stderr));
}
return result;
};
/** Git URL that resolves to the configured repository. */
GitClient.prototype.getRepoGitUrl = function () {
return getRepositoryGitUrl(this.remoteConfig);
};
/** 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;
};
/**
* 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;
};
/** Gets the latest git tag on the current branch that matches SemVer. */
GitClient.prototype.getLatestSemverTag = function () {
var semVerOptions = { loose: true };
var tags = this.runGraceful(['tag', '--sort=-committerdate', '--merged']).stdout.split('\n');
var latestTag = tags.find(function (tag) { return semver.parse(tag, semVerOptions); });
if (latestTag === undefined) {
throw new Error("Unable to find a SemVer matching tag on \"" + this.getCurrentBranchOrRevision() + "\"");
}
return new semver.SemVer(latestTag, semVerOptions);
};
/** Retrieve a list of all files in the repository changed since the provided shaOrRef. */
GitClient.prototype.allChangesFilesSince = function (shaOrRef) {
if (shaOrRef === void 0) { shaOrRef = 'HEAD'; }
return Array.from(new Set(tslib.__spreadArray(tslib.__spreadArray([], tslib.__read(gitOutputAsArray(this.runGraceful(['diff', '--name-only', '--diff-filter=d', shaOrRef])))), tslib.__read(gitOutputAsArray(this.runGraceful(['ls-files', '--others', '--exclude-standard']))))));
};
/** Retrieve a list of all files currently staged in the repostitory. */
GitClient.prototype.allStagedFiles = function () {
return gitOutputAsArray(this.runGraceful(['diff', '--name-only', '--diff-filter=ACM', '--staged']));
};
/** Retrieve a list of all files tracked in the repository. */
GitClient.prototype.allFiles = function () {
return gitOutputAsArray(this.runGraceful(['ls-files']));
};
/**
* Sanitizes the given console message. This method can be overridden by
* derived classes. e.g. to sanitize access tokens from Git commands.
*/
GitClient.prototype.sanitizeConsoleOutput = function (value) {
return value;
};
/** Set the verbose logging state of all git client instances. */
GitClient.setVerboseLoggingState = function (verbose) {
GitClient.verboseLogging = verbose;
};
/**
* Static method to get the singleton instance of the `GitClient`, creating it
* if it has not yet been created.
*/
GitClient.get = function () {
if (!this._unauthenticatedInstance) {
GitClient._unauthenticatedInstance = new GitClient();
}
return GitClient._unauthenticatedInstance;
};
/** Whether verbose logging of Git actions should be used. */
GitClient.verboseLogging = false;
return GitClient;
}());
/**
* Takes the output from `run` and `runGraceful` and returns an array of strings for each
* new line. Git commands typically return multiple output values for a command a set of
* strings separated by new lines.
*
* Note: This is specifically created as a locally available function for usage as convenience
* utility within `GitClient`'s methods to create outputs as array.
*/
function gitOutputAsArray(gitCommandResult) {
return gitCommandResult.stdout.split('\n').map(function (x) { return x.trim(); }).filter(function (x) { return !!x; });
}
/** Determines the repository base directory from the current working directory. */
function determineRepoBaseDirFromCwd() {
// TODO(devversion): Replace with common spawn sync utility once available.
var _a = child_process.spawnSync('git', ['rev-parse --show-toplevel'], { shell: true, stdio: 'pipe', encoding: 'utf8' }), stdout = _a.stdout, stderr = _a.stderr, status = _a.status;
if (status !== 0) {
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" +
("" + stderr));
}
return stdout.trim();
}
/**
* @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
*/
/**
* 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.__spreadArray([loadCommand, level], tslib.__read(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.__spreadArray([], tslib.__read(text)));
}
printToLogFile.apply(void 0, tslib.__spreadArray([logLevel], tslib.__read(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;
}
/**
* The number of columns used in the prepended log level information on each line of the logging
* output file.
*/
var LOG_LEVEL_COLUMNS = 7;
/** 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);
}
/**
* @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;
function getConfig(baseDir) {
// If the global config is not defined, load it from the file system.
if (cachedConfig === null) {
baseDir = baseDir || GitClient.get().baseDir;
// The full path to the configuration file.
var configPath = path.join(baseDir, 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 `.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);
}
/**
* @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.releaseNotes) === undefined) {
errors.push(`No "releaseNotes" 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
*/
// Start the release package building.
main(process.argv[2] === 'true');
/** Main function for building the release packages. */
function main(stampForRelease) {
return tslib.__awaiter(this, void 0, void 0, function* () {
if (process.send === undefined) {
throw Error('This script needs to be invoked as a NodeJS worker.');
}
const config = getReleaseConfig();
const builtPackages = yield config.buildPackages(stampForRelease);
// Transfer the built packages back to the parent process.
process.send(builtPackages);
});
}