From e73ffbcdec6e32336d408b2ff7c61488032f3ec4 Mon Sep 17 00:00:00 2001 From: eric sciple Date: Mon, 18 May 2020 12:29:21 -0400 Subject: [PATCH] support ghes (#157) --- dist/index.js | 444 ++++++++++++++++++++++++++++++++++++++++++---- package-lock.json | 13 +- package.json | 2 +- src/installer.ts | 83 ++++++--- src/main.ts | 11 +- 5 files changed, 481 insertions(+), 72 deletions(-) diff --git a/dist/index.js b/dist/index.js index e6aa40be..87ce6f0c 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1338,7 +1338,7 @@ var __importStar = (this && this.__importStar) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); const semver = __importStar(__webpack_require__(280)); -const core_1 = __webpack_require__(470); +const core_1 = __webpack_require__(902); // needs to be require for core node modules to be mocked /* eslint @typescript-eslint/no-require-imports: 0 */ const os = __webpack_require__(87); @@ -4631,6 +4631,7 @@ const core = __importStar(__webpack_require__(470)); const installer = __importStar(__webpack_require__(749)); const auth = __importStar(__webpack_require__(202)); const path = __importStar(__webpack_require__(622)); +const url_1 = __webpack_require__(835); function run() { return __awaiter(this, void 0, void 0, function* () { try { @@ -4645,8 +4646,9 @@ function run() { console.log(`version: ${version}`); if (version) { let token = core.getInput('token'); + let auth = !token || isGhes() ? undefined : `token ${token}`; let stable = (core.getInput('stable') || 'true').toUpperCase() === 'TRUE'; - yield installer.getNode(version, stable, token); + yield installer.getNode(version, stable, auth); } const registryUrl = core.getInput('registry-url'); const alwaysAuth = core.getInput('always-auth'); @@ -4664,6 +4666,10 @@ function run() { }); } exports.run = run; +function isGhes() { + const ghUrl = new url_1.URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com'); + return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM'; +} //# sourceMappingURL=main.js.map /***/ }), @@ -10883,7 +10889,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -const core = __importStar(__webpack_require__(470)); +const core = __importStar(__webpack_require__(902)); const io = __importStar(__webpack_require__(1)); const fs = __importStar(__webpack_require__(747)); const mm = __importStar(__webpack_require__(31)); @@ -10912,9 +10918,10 @@ const userAgent = 'actions/tool-cache'; * * @param url url of tool to download * @param dest path to download tool + * @param auth authorization header * @returns path to downloaded tool */ -function downloadTool(url, dest, token) { +function downloadTool(url, dest, auth) { return __awaiter(this, void 0, void 0, function* () { dest = dest || path.join(_getTempDirectory(), v4_1.default()); yield io.mkdirP(path.dirname(dest)); @@ -10925,7 +10932,7 @@ function downloadTool(url, dest, token) { const maxSeconds = _getGlobal('TEST_DOWNLOAD_TOOL_RETRY_MAX_SECONDS', 20); const retryHelper = new retry_helper_1.RetryHelper(maxAttempts, minSeconds, maxSeconds); return yield retryHelper.execute(() => __awaiter(this, void 0, void 0, function* () { - return yield downloadToolAttempt(url, dest || '', token); + return yield downloadToolAttempt(url, dest || '', auth); }), (err) => { if (err instanceof HTTPError && err.httpStatusCode) { // Don't retry anything less than 500, except 408 Request Timeout and 429 Too Many Requests @@ -10941,7 +10948,7 @@ function downloadTool(url, dest, token) { }); } exports.downloadTool = downloadTool; -function downloadToolAttempt(url, dest, token) { +function downloadToolAttempt(url, dest, auth) { return __awaiter(this, void 0, void 0, function* () { if (fs.existsSync(dest)) { throw new Error(`Destination file path ${dest} already exists`); @@ -10951,9 +10958,10 @@ function downloadToolAttempt(url, dest, token) { allowRetries: false }); let headers; - if (token) { + if (auth) { + core.debug('set auth'); headers = { - authorization: `token ${token}` + authorization: auth }; } const response = yield http.get(url, headers); @@ -11011,9 +11019,10 @@ function extract7z(file, dest, _7zPath) { process.chdir(dest); if (_7zPath) { try { + const logLevel = core.isDebug() ? '-bb1' : '-bb0'; const args = [ 'x', - '-bb1', + logLevel, '-bd', '-sccUTF-8', file @@ -11096,6 +11105,9 @@ function extractTar(file, dest, flags = 'xz') { else { args = [flags]; } + if (core.isDebug() && !flags.includes('v')) { + args.push('-v'); + } let destArg = dest; let fileArg = file; if (IS_WINDOWS && isGnuTar) { @@ -11145,7 +11157,7 @@ function extractZipWin(file, dest) { const escapedDest = dest.replace(/'/g, "''").replace(/"|\n|\r/g, ''); const command = `$ErrorActionPreference = 'Stop' ; try { Add-Type -AssemblyName System.IO.Compression.FileSystem } catch { } ; [System.IO.Compression.ZipFile]::ExtractToDirectory('${escapedFile}', '${escapedDest}')`; // run powershell - const powershellPath = yield io.which('powershell'); + const powershellPath = yield io.which('powershell', true); const args = [ '-NoLogo', '-Sta', @@ -11161,8 +11173,12 @@ function extractZipWin(file, dest) { } function extractZipNix(file, dest) { return __awaiter(this, void 0, void 0, function* () { - const unzipPath = yield io.which('unzip'); - yield exec_1.exec(`"${unzipPath}"`, [file], { cwd: dest }); + const unzipPath = yield io.which('unzip', true); + const args = [file]; + if (!core.isDebug()) { + args.unshift('-q'); + } + yield exec_1.exec(`"${unzipPath}"`, args, { cwd: dest }); }); } /** @@ -11290,14 +11306,16 @@ function findAllVersions(toolName, arch) { return versions; } exports.findAllVersions = findAllVersions; -function getManifestFromRepo(owner, repo, token, branch = 'master') { +function getManifestFromRepo(owner, repo, auth, branch = 'master') { return __awaiter(this, void 0, void 0, function* () { let releases = []; const treeUrl = `https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}`; const http = new httpm.HttpClient('tool-cache'); - const headers = { - authorization: `token ${token}` - }; + const headers = {}; + if (auth) { + core.debug('set auth'); + headers.authorization = auth; + } const response = yield http.getJson(treeUrl, headers); if (!response.result) { return releases; @@ -12976,12 +12994,11 @@ const tc = __importStar(__webpack_require__(533)); const path = __importStar(__webpack_require__(622)); const semver = __importStar(__webpack_require__(280)); const fs = __webpack_require__(747); -function getNode(versionSpec, stable, token) { +function getNode(versionSpec, stable, auth) { return __awaiter(this, void 0, void 0, function* () { let osPlat = os.platform(); let osArch = translateArchToDistUrl(os.arch()); // check cache - let info = null; let toolPath; toolPath = tc.find('node', versionSpec); // If not found in cache, download @@ -12990,31 +13007,58 @@ function getNode(versionSpec, stable, token) { } else { console.log(`Attempting to download ${versionSpec}...`); - let info = yield getInfoFromManifest(versionSpec, stable, token); - if (!info) { - console.log('Not found in manifest. Falling back to download directly from Node'); - info = yield getInfoFromDist(versionSpec); - } - if (!info) { - throw new Error(`Unable to find Node version '${versionSpec}' for platform ${osPlat} and architecture ${osArch}.`); - } - console.log(`Acquiring ${info.resolvedVersion} from ${info.downloadUrl}`); let downloadPath = ''; + let info = null; + // + // Try download from internal distribution (popular versions only) + // try { - downloadPath = yield tc.downloadTool(info.downloadUrl, undefined, token); + info = yield getInfoFromManifest(versionSpec, stable, auth); + if (info) { + console.log(`Acquiring ${info.resolvedVersion} from ${info.downloadUrl}`); + downloadPath = yield tc.downloadTool(info.downloadUrl, undefined, auth); + } + else { + console.log('Not found in manifest. Falling back to download directly from Node'); + } } catch (err) { - if (err instanceof tc.HTTPError && err.httpStatusCode == 404) { - yield acquireNodeFromFallbackLocation(info.resolvedVersion); - return; + // Rate limit? + if (err instanceof tc.HTTPError && + (err.httpStatusCode === 403 || err.httpStatusCode === 429)) { + console.log(`Received HTTP status code ${err.httpStatusCode}. This usually indicates the rate limit has been exceeded`); + } + else { + console.log(err.message); + } + core.debug(err.stack); + console.log('Falling back to download directly from Node'); + } + // + // Download from nodejs.org + // + if (!downloadPath) { + info = yield getInfoFromDist(versionSpec); + if (!info) { + throw new Error(`Unable to find Node version '${versionSpec}' for platform ${osPlat} and architecture ${osArch}.`); + } + console.log(`Acquiring ${info.resolvedVersion} from ${info.downloadUrl}`); + try { + downloadPath = yield tc.downloadTool(info.downloadUrl); + } + catch (err) { + if (err instanceof tc.HTTPError && err.httpStatusCode == 404) { + return yield acquireNodeFromFallbackLocation(info.resolvedVersion); + } + throw err; } - throw err; } // // Extract // console.log('Extracting ...'); let extPath; + info = info || {}; // satisfy compiler, never null when reaches here if (osPlat == 'win32') { let _7zPath = path.join(__dirname, '..', 'externals', '7zr.exe'); extPath = yield tc.extract7z(downloadPath, undefined, _7zPath); @@ -13052,10 +13096,10 @@ function getNode(versionSpec, stable, token) { }); } exports.getNode = getNode; -function getInfoFromManifest(versionSpec, stable, token) { +function getInfoFromManifest(versionSpec, stable, auth) { return __awaiter(this, void 0, void 0, function* () { let info = null; - const releases = yield tc.getManifestFromRepo('actions', 'node-versions', token); + const releases = yield tc.getManifestFromRepo('actions', 'node-versions', auth); console.log(`matching ${versionSpec}...`); const rel = yield tc.findFromManifest(versionSpec, stable, releases); if (rel && rel.files.length > 0) { @@ -13063,7 +13107,6 @@ function getInfoFromManifest(versionSpec, stable, token) { info.resolvedVersion = rel.version; info.downloadUrl = rel.files[0].download_url; info.fileName = rel.files[0].filename; - info.token = token; } return info; }); @@ -13072,7 +13115,6 @@ function getInfoFromDist(versionSpec) { return __awaiter(this, void 0, void 0, function* () { let osPlat = os.platform(); let osArch = translateArchToDistUrl(os.arch()); - let info = null; let version; version = yield queryDistForMatch(versionSpec); if (!version) { @@ -15956,6 +15998,105 @@ function set(object, path, value) { module.exports = set; +/***/ }), + +/***/ 888: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const os = __importStar(__webpack_require__(87)); +/** + * Commands + * + * Command Format: + * ::name key=value,key=value::message + * + * Examples: + * ::warning::This is the message + * ::set-env name=MY_VAR::some value + */ +function issueCommand(command, properties, message) { + const cmd = new Command(command, properties, message); + process.stdout.write(cmd.toString() + os.EOL); +} +exports.issueCommand = issueCommand; +function issue(name, message = '') { + issueCommand(name, {}, message); +} +exports.issue = issue; +const CMD_STRING = '::'; +class Command { + constructor(command, properties, message) { + if (!command) { + command = 'missing.command'; + } + this.command = command; + this.properties = properties; + this.message = message; + } + toString() { + let cmdStr = CMD_STRING + this.command; + if (this.properties && Object.keys(this.properties).length > 0) { + cmdStr += ' '; + let first = true; + for (const key in this.properties) { + if (this.properties.hasOwnProperty(key)) { + const val = this.properties[key]; + if (val) { + if (first) { + first = false; + } + else { + cmdStr += ','; + } + cmdStr += `${key}=${escapeProperty(val)}`; + } + } + } + } + cmdStr += `${CMD_STRING}${escapeData(this.message)}`; + return cmdStr; + } +} +/** + * Sanitizes an input into a string so it can be passed into issueCommand safely + * @param input input to sanitize into a string + */ +function toCommandValue(input) { + if (input === null || input === undefined) { + return ''; + } + else if (typeof input === 'string' || input instanceof String) { + return input; + } + return JSON.stringify(input); +} +exports.toCommandValue = toCommandValue; +function escapeData(s) { + return toCommandValue(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A'); +} +function escapeProperty(s) { + return toCommandValue(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A') + .replace(/:/g, '%3A') + .replace(/,/g, '%2C'); +} +//# sourceMappingURL=command.js.map + /***/ }), /***/ 899: @@ -16061,6 +16202,235 @@ function patchForDeprecation(octokit, apiOptions, method, methodName) { } +/***/ }), + +/***/ 902: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const command_1 = __webpack_require__(888); +const os = __importStar(__webpack_require__(87)); +const path = __importStar(__webpack_require__(622)); +/** + * The code to exit an action + */ +var ExitCode; +(function (ExitCode) { + /** + * A code indicating that the action was successful + */ + ExitCode[ExitCode["Success"] = 0] = "Success"; + /** + * A code indicating that the action was a failure + */ + ExitCode[ExitCode["Failure"] = 1] = "Failure"; +})(ExitCode = exports.ExitCode || (exports.ExitCode = {})); +//----------------------------------------------------------------------- +// Variables +//----------------------------------------------------------------------- +/** + * Sets env variable for this action and future actions in the job + * @param name the name of the variable to set + * @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function exportVariable(name, val) { + const convertedVal = command_1.toCommandValue(val); + process.env[name] = convertedVal; + command_1.issueCommand('set-env', { name }, convertedVal); +} +exports.exportVariable = exportVariable; +/** + * Registers a secret which will get masked from logs + * @param secret value of the secret + */ +function setSecret(secret) { + command_1.issueCommand('add-mask', {}, secret); +} +exports.setSecret = setSecret; +/** + * Prepends inputPath to the PATH (for this action and future actions) + * @param inputPath + */ +function addPath(inputPath) { + command_1.issueCommand('add-path', {}, inputPath); + process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`; +} +exports.addPath = addPath; +/** + * Gets the value of an input. The value is also trimmed. + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns string + */ +function getInput(name, options) { + const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || ''; + if (options && options.required && !val) { + throw new Error(`Input required and not supplied: ${name}`); + } + return val.trim(); +} +exports.getInput = getInput; +/** + * Sets the value of an output. + * + * @param name name of the output to set + * @param value value to store. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function setOutput(name, value) { + command_1.issueCommand('set-output', { name }, value); +} +exports.setOutput = setOutput; +/** + * Enables or disables the echoing of commands into stdout for the rest of the step. + * Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set. + * + */ +function setCommandEcho(enabled) { + command_1.issue('echo', enabled ? 'on' : 'off'); +} +exports.setCommandEcho = setCommandEcho; +//----------------------------------------------------------------------- +// Results +//----------------------------------------------------------------------- +/** + * Sets the action status to failed. + * When the action exits it will be with an exit code of 1 + * @param message add error issue message + */ +function setFailed(message) { + process.exitCode = ExitCode.Failure; + error(message); +} +exports.setFailed = setFailed; +//----------------------------------------------------------------------- +// Logging Commands +//----------------------------------------------------------------------- +/** + * Gets whether Actions Step Debug is on or not + */ +function isDebug() { + return process.env['RUNNER_DEBUG'] === '1'; +} +exports.isDebug = isDebug; +/** + * Writes debug message to user log + * @param message debug message + */ +function debug(message) { + command_1.issueCommand('debug', {}, message); +} +exports.debug = debug; +/** + * Adds an error issue + * @param message error issue message. Errors will be converted to string via toString() + */ +function error(message) { + command_1.issue('error', message instanceof Error ? message.toString() : message); +} +exports.error = error; +/** + * Adds an warning issue + * @param message warning issue message. Errors will be converted to string via toString() + */ +function warning(message) { + command_1.issue('warning', message instanceof Error ? message.toString() : message); +} +exports.warning = warning; +/** + * Writes info to log with console.log. + * @param message info message + */ +function info(message) { + process.stdout.write(message + os.EOL); +} +exports.info = info; +/** + * Begin an output group. + * + * Output until the next `groupEnd` will be foldable in this group + * + * @param name The name of the output group + */ +function startGroup(name) { + command_1.issue('group', name); +} +exports.startGroup = startGroup; +/** + * End an output group. + */ +function endGroup() { + command_1.issue('endgroup'); +} +exports.endGroup = endGroup; +/** + * Wrap an asynchronous function call in a group. + * + * Returns the same type as the function itself. + * + * @param name The name of the group + * @param fn The function to wrap in the group + */ +function group(name, fn) { + return __awaiter(this, void 0, void 0, function* () { + startGroup(name); + let result; + try { + result = yield fn(); + } + finally { + endGroup(); + } + return result; + }); +} +exports.group = group; +//----------------------------------------------------------------------- +// Wrapper action state +//----------------------------------------------------------------------- +/** + * Saves state for current action, the state can only be retrieved by this action's post job execution. + * + * @param name name of the state to store + * @param value value to store. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function saveState(name, value) { + command_1.issueCommand('save-state', { name }, value); +} +exports.saveState = saveState; +/** + * Gets the value of an state set by this action's main execution. + * + * @param name name of the state to get + * @returns string + */ +function getState(name) { + return process.env[`STATE_${name}`] || ''; +} +exports.getState = getState; +//# sourceMappingURL=core.js.map + /***/ }), /***/ 929: @@ -16702,7 +17072,7 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -const core = __importStar(__webpack_require__(470)); +const core = __importStar(__webpack_require__(902)); /** * Internal class for retries */ diff --git a/package-lock.json b/package-lock.json index 26be5a56..be7fec2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,11 +40,11 @@ "integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg==" }, "@actions/tool-cache": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-1.5.3.tgz", - "integrity": "sha512-G6OMdGvKVkApJv+nRURpi1nZUKonqWq37fqK8rdJLJr5PuWAEo/cqD/ibano7Da/LRx1yYFEMwO6RXkB2VaIqQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-1.5.4.tgz", + "integrity": "sha512-72ijIBM0s/dx2D0eYYxaxaeKWeVatOK8OHPNctJ5cyKjZp1j12egX+nW/N+tnQRNMVxTp9WjudZO5wizUBxC/w==", "requires": { - "@actions/core": "^1.2.0", + "@actions/core": "^1.2.3", "@actions/exec": "^1.0.0", "@actions/http-client": "^1.0.8", "@actions/io": "^1.0.1", @@ -52,6 +52,11 @@ "uuid": "^3.3.2" }, "dependencies": { + "@actions/core": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.4.tgz", + "integrity": "sha512-YJCEq8BE3CdN8+7HPZ/4DxJjk/OkZV2FFIf+DlZTC/4iBlzYCD5yjRR6eiOS5llO11zbRltIRuKAjMKaWTE6cg==" + }, "@actions/http-client": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.8.tgz", diff --git a/package.json b/package.json index a6c45d6f..9a3f1c57 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@actions/github": "^1.1.0", "@actions/http-client": "^1.0.6", "@actions/io": "^1.0.2", - "@actions/tool-cache": "^1.5.3", + "@actions/tool-cache": "^1.5.4", "semver": "^6.1.1" }, "devDependencies": { diff --git a/src/installer.ts b/src/installer.ts index 12294824..ee96bda4 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -6,7 +6,6 @@ import * as io from '@actions/io'; import * as tc from '@actions/tool-cache'; import * as path from 'path'; import * as semver from 'semver'; -import {Url} from 'url'; import fs = require('fs'); // @@ -20,7 +19,6 @@ export interface INodeVersion { interface INodeVersionInfo { downloadUrl: string; - token: string | null; resolvedVersion: string; fileName: string; } @@ -28,13 +26,12 @@ interface INodeVersionInfo { export async function getNode( versionSpec: string, stable: boolean, - token: string + auth: string | undefined ) { let osPlat: string = os.platform(); let osArch: string = translateArchToDistUrl(os.arch()); // check cache - let info: INodeVersionInfo | null = null; let toolPath: string; toolPath = tc.find('node', versionSpec); @@ -43,32 +40,61 @@ export async function getNode( console.log(`Found in cache @ ${toolPath}`); } else { console.log(`Attempting to download ${versionSpec}...`); - let info = await getInfoFromManifest(versionSpec, stable, token); - if (!info) { - console.log( - 'Not found in manifest. Falling back to download directly from Node' - ); - info = await getInfoFromDist(versionSpec); - } - - if (!info) { - throw new Error( - `Unable to find Node version '${versionSpec}' for platform ${osPlat} and architecture ${osArch}.` - ); - } - - console.log(`Acquiring ${info.resolvedVersion} from ${info.downloadUrl}`); - let downloadPath = ''; + let info: INodeVersionInfo | null = null; + + // + // Try download from internal distribution (popular versions only) + // try { - downloadPath = await tc.downloadTool(info.downloadUrl, undefined, token); + info = await getInfoFromManifest(versionSpec, stable, auth); + if (info) { + console.log( + `Acquiring ${info.resolvedVersion} from ${info.downloadUrl}` + ); + downloadPath = await tc.downloadTool(info.downloadUrl, undefined, auth); + } else { + console.log( + 'Not found in manifest. Falling back to download directly from Node' + ); + } } catch (err) { - if (err instanceof tc.HTTPError && err.httpStatusCode == 404) { - await acquireNodeFromFallbackLocation(info.resolvedVersion); - return; + // Rate limit? + if ( + err instanceof tc.HTTPError && + (err.httpStatusCode === 403 || err.httpStatusCode === 429) + ) { + console.log( + `Received HTTP status code ${err.httpStatusCode}. This usually indicates the rate limit has been exceeded` + ); + } else { + console.log(err.message); + } + core.debug(err.stack); + console.log('Falling back to download directly from Node'); + } + + // + // Download from nodejs.org + // + if (!downloadPath) { + info = await getInfoFromDist(versionSpec); + if (!info) { + throw new Error( + `Unable to find Node version '${versionSpec}' for platform ${osPlat} and architecture ${osArch}.` + ); } - throw err; + console.log(`Acquiring ${info.resolvedVersion} from ${info.downloadUrl}`); + try { + downloadPath = await tc.downloadTool(info.downloadUrl); + } catch (err) { + if (err instanceof tc.HTTPError && err.httpStatusCode == 404) { + return await acquireNodeFromFallbackLocation(info.resolvedVersion); + } + + throw err; + } } // @@ -76,6 +102,7 @@ export async function getNode( // console.log('Extracting ...'); let extPath: string; + info = info || ({} as INodeVersionInfo); // satisfy compiler, never null when reaches here if (osPlat == 'win32') { let _7zPath = path.join(__dirname, '..', 'externals', '7zr.exe'); extPath = await tc.extract7z(downloadPath, undefined, _7zPath); @@ -117,13 +144,13 @@ export async function getNode( async function getInfoFromManifest( versionSpec: string, stable: boolean, - token: string + auth: string | undefined ): Promise { let info: INodeVersionInfo | null = null; const releases = await tc.getManifestFromRepo( 'actions', 'node-versions', - token + auth ); console.log(`matching ${versionSpec}...`); const rel = await tc.findFromManifest(versionSpec, stable, releases); @@ -133,7 +160,6 @@ async function getInfoFromManifest( info.resolvedVersion = rel.version; info.downloadUrl = rel.files[0].download_url; info.fileName = rel.files[0].filename; - info.token = token; } return info; @@ -145,7 +171,6 @@ async function getInfoFromDist( let osPlat: string = os.platform(); let osArch: string = translateArchToDistUrl(os.arch()); - let info: INodeVersionInfo | null = null; let version: string; version = await queryDistForMatch(versionSpec); diff --git a/src/main.ts b/src/main.ts index 6a3486c0..ab89421f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,6 +2,7 @@ import * as core from '@actions/core'; import * as installer from './installer'; import * as auth from './authutil'; import * as path from 'path'; +import {URL} from 'url'; export async function run() { try { @@ -17,8 +18,9 @@ export async function run() { console.log(`version: ${version}`); if (version) { let token = core.getInput('token'); + let auth = !token || isGhes() ? undefined : `token ${token}`; let stable = (core.getInput('stable') || 'true').toUpperCase() === 'TRUE'; - await installer.getNode(version, stable, token); + await installer.getNode(version, stable, auth); } const registryUrl: string = core.getInput('registry-url'); @@ -39,3 +41,10 @@ export async function run() { core.setFailed(error.message); } } + +function isGhes(): boolean { + const ghUrl = new URL( + process.env['GITHUB_SERVER_URL'] || 'https://github.com' + ); + return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM'; +}