From 98ef5c7f7ad51752afe6bc503e5ae1856b5de7bf Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Fri, 23 Jun 2023 23:13:39 +0200 Subject: [PATCH] Use cache instead of artifacts --- dist/index.js | 1681 ++++++++----------- src/classes/actions-cache/http-responses.ts | 19 + src/classes/actions-cache/index.ts | 234 +++ src/classes/actions-cache/retry.ts | 127 ++ src/classes/issue.ts | 2 +- src/classes/issues-processor.ts | 8 +- src/classes/state.ts | 49 +- src/main.ts | 2 +- src/services/state.service.ts | 5 +- 9 files changed, 1155 insertions(+), 972 deletions(-) create mode 100644 src/classes/actions-cache/http-responses.ts create mode 100644 src/classes/actions-cache/index.ts create mode 100644 src/classes/actions-cache/retry.ts diff --git a/dist/index.js b/dist/index.js index 685f4224e..6bf1776aa 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,6 +1,336 @@ /******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ +/***/ 7233: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.isServerErrorStatusCode = exports.isSuccessStatusCode = void 0; +const isSuccessStatusCode = (statusCode) => { + if (!statusCode) { + return false; + } + return statusCode >= 200 && statusCode < 300; +}; +exports.isSuccessStatusCode = isSuccessStatusCode; +function isServerErrorStatusCode(statusCode) { + if (!statusCode) { + return true; + } + return statusCode >= 500; +} +exports.isServerErrorStatusCode = isServerErrorStatusCode; + + +/***/ }), + +/***/ 7985: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +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 __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.saveFileAsActionsCache = void 0; +const core = __importStar(__nccwpck_require__(2186)); +const fs_1 = __importDefault(__nccwpck_require__(7147)); +const http_client_1 = __nccwpck_require__(6255); +const auth_1 = __nccwpck_require__(5526); +const cache_1 = __nccwpck_require__(7799); +const http_responses_1 = __nccwpck_require__(7233); +const retry_1 = __nccwpck_require__(3910); +const github_1 = __nccwpck_require__(5438); +const plugin_retry_1 = __nccwpck_require__(6298); +const uploadChunk = (httpClient, resourceUrl, openStream, start, end) => __awaiter(void 0, void 0, void 0, function* () { + // Format: `bytes start-end/filesize + // start and end are inclusive + // filesize can be * + // For a 200 byte chunk starting at byte 0: + // Content-Range: bytes 0-199/* + const contentRange = `bytes ${start}-${end}/*`; + core.debug(`Uploading chunk of size ${end - start + 1} bytes at offset ${start} with content range: ${contentRange}`); + const additionalHeaders = { + 'Content-Type': 'application/octet-stream', + 'Content-Range': contentRange + }; + const uploadChunkResponse = yield (0, retry_1.retryHttpClientResponse)(`uploadChunk (start: ${start}, end: ${end})`, () => __awaiter(void 0, void 0, void 0, function* () { + return httpClient.sendStream('PATCH', resourceUrl, openStream(), additionalHeaders); + })); + if (!(0, http_responses_1.isSuccessStatusCode)(uploadChunkResponse.message.statusCode)) { + throw new Error(`Cache service responded with ${uploadChunkResponse.message.statusCode} during upload chunk.`); + } +}); +const getCacheApiUrl = (resource) => { + const baseUrl = process.env['ACTIONS_CACHE_URL'] || ''; + if (!baseUrl) { + throw new Error('Cache Service Url not found, unable to restore cache.'); + } + const url = `${baseUrl}_apis/artifactcache/${resource}`; + core.debug(`Resource Url: ${url}`); + return url; +}; +const createAcceptHeader = (type, apiVersion) => `${type};api-version=${apiVersion}`; +const getRequestOptions = () => ({ + headers: { + Accept: createAcceptHeader('application/json', '6.0-preview.1') + } +}); +const createHttpClient = () => { + const token = process.env['ACTIONS_RUNTIME_TOKEN'] || ''; + const bearerCredentialHandler = new auth_1.BearerCredentialHandler(token); + return new http_client_1.HttpClient('actions/cache', [bearerCredentialHandler], getRequestOptions()); +}; +const uploadFile = (httpClient, cacheId, filePath, fileSize) => __awaiter(void 0, void 0, void 0, function* () { + // Upload Chunks + const resourceUrl = getCacheApiUrl(`caches/${cacheId.toString()}`); + const fd = fs_1.default.openSync(filePath, 'r'); + try { + const chunkSize = fileSize; + const start = 0; + const end = chunkSize - 1; + yield uploadChunk(httpClient, resourceUrl, () => fs_1.default + .createReadStream(filePath, { + fd, + start, + end, + autoClose: false + }) + .on('error', error => { + throw new Error(`Cache upload failed because file read failed with ${error.message}`); + }), start, end); + } + finally { + fs_1.default.closeSync(fd); + } + return; +}); +const CACHE_KEY = '_state'; +const resetCacheWithOctokit = () => __awaiter(void 0, void 0, void 0, function* () { + const token = core.getInput('repo-token'); + const client = (0, github_1.getOctokit)(token, undefined, plugin_retry_1.retry); + const repo = process.env['GITHUB_REPOSITORY']; + const result = yield client.request(`DELETE /repos/${repo}/actions/caches?key=${CACHE_KEY}`); + console.log('============> delete'); + console.log(result); +}); +const resetCache = (httpClient) => __awaiter(void 0, void 0, void 0, function* () { + yield (0, retry_1.retryTypedResponse)('resetCache', () => __awaiter(void 0, void 0, void 0, function* () { + const result = yield httpClient.del(getCacheApiUrl(`caches?key=${CACHE_KEY}`)); + console.log('============> delete'); + console.log(result.message); + return result.message; + })); +}); +const reserveCache = (httpClient, fileSize) => __awaiter(void 0, void 0, void 0, function* () { + var _a, _b, _c, _d; + const reserveCacheRequest = { + key: CACHE_KEY, + version: new Date().getDate().toString(), + cacheSize: fileSize + }; + const response = (yield (0, retry_1.retryTypedResponse)('reserveCache', () => __awaiter(void 0, void 0, void 0, function* () { + return httpClient.postJson(getCacheApiUrl('caches'), reserveCacheRequest); + }))); + if ((response === null || response === void 0 ? void 0 : response.statusCode) === 400) + throw new Error((_b = (_a = response === null || response === void 0 ? void 0 : response.error) === null || _a === void 0 ? void 0 : _a.message) !== null && _b !== void 0 ? _b : `Cache size of ~${Math.round(fileSize / (1024 * 1024))} MB (${fileSize} B) is over the data cap limit, not saving cache.`); + const cacheId = (_c = response === null || response === void 0 ? void 0 : response.result) === null || _c === void 0 ? void 0 : _c.cacheId; + if (cacheId === undefined) + throw new cache_1.ReserveCacheError(`Unable to reserve cache with key ${CACHE_KEY}, another job may be creating this cache. More details: ${(_d = response === null || response === void 0 ? void 0 : response.error) === null || _d === void 0 ? void 0 : _d.message}`); + return cacheId; +}); +const commitCache = (httpClient, cacheId, filesize) => __awaiter(void 0, void 0, void 0, function* () { + const commitCacheRequest = { size: filesize }; + const response = (yield (0, retry_1.retryTypedResponse)('commitCache', () => __awaiter(void 0, void 0, void 0, function* () { + return httpClient.postJson(getCacheApiUrl(`caches/${cacheId.toString()}`), commitCacheRequest); + }))); + if (!(0, http_responses_1.isSuccessStatusCode)(response.statusCode)) { + throw new Error(`Cache service responded with ${response.statusCode} during commit cache.`); + } +}); +const saveFileAsActionsCache = (filePath) => __awaiter(void 0, void 0, void 0, function* () { + try { + yield resetCacheWithOctokit(); + const httpClient = createHttpClient(); + // await resetCache(httpClient); + const fileSize = fs_1.default.statSync(filePath).size; + const cacheId = yield reserveCache(httpClient, fileSize); + yield uploadFile(httpClient, cacheId, filePath, fileSize); + yield commitCache(httpClient, cacheId, fileSize); + } + catch (error) { + const typedError = error; + if (typedError.name === cache_1.ValidationError.name) { + throw error; + } + else if (typedError.name === cache_1.ReserveCacheError.name) { + core.info(`Failed to save: ${typedError.message}`); + } + else { + core.warning(`Failed to save: ${typedError.message}`); + } + } +}); +exports.saveFileAsActionsCache = saveFileAsActionsCache; + + +/***/ }), + +/***/ 3910: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +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()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.retryTypedResponse = exports.retryHttpClientResponse = void 0; +const http_client_1 = __nccwpck_require__(6255); +const http_responses_1 = __nccwpck_require__(7233); +const core = __importStar(__nccwpck_require__(2186)); +const isRetryableStatusCode = (statusCode) => { + if (!statusCode) { + return false; + } + const retryableStatusCodes = [ + http_client_1.HttpCodes.BadGateway, + http_client_1.HttpCodes.ServiceUnavailable, + http_client_1.HttpCodes.GatewayTimeout + ]; + return retryableStatusCodes.includes(statusCode); +}; +const sleep = (milliseconds) => new Promise(resolve => setTimeout(resolve, milliseconds)); +// The default number of retry attempts. +const DefaultRetryAttempts = 2; +// The default delay in milliseconds between retry attempts. +const DefaultRetryDelay = 5000; +const retry = (name, method, getStatusCode, maxAttempts = DefaultRetryAttempts, delay = DefaultRetryDelay, onError = undefined) => __awaiter(void 0, void 0, void 0, function* () { + let errorMessage = ''; + let attempt = 1; + while (attempt <= maxAttempts) { + let response = undefined; + let statusCode = undefined; + let isRetryable = false; + try { + response = yield method(); + } + catch (error) { + if (onError) { + response = onError(error); + } + isRetryable = true; + errorMessage = error.message; + } + if (response) { + statusCode = getStatusCode(response); + if (!(0, http_responses_1.isServerErrorStatusCode)(statusCode)) { + return response; + } + } + if (statusCode) { + isRetryable = isRetryableStatusCode(statusCode); + errorMessage = `Cache service responded with ${statusCode}`; + } + core.debug(`${name} - Attempt ${attempt} of ${maxAttempts} failed with error: ${errorMessage}`); + if (!isRetryable) { + core.debug(`${name} - Error is not retryable`); + break; + } + yield sleep(delay); + attempt++; + } + throw Error(`${name} failed: ${errorMessage}`); +}); +const retryHttpClientResponse = (name, method, maxAttempts = DefaultRetryAttempts, delay = DefaultRetryDelay) => __awaiter(void 0, void 0, void 0, function* () { + return yield retry(name, method, (response) => response.message.statusCode, maxAttempts, delay); +}); +exports.retryHttpClientResponse = retryHttpClientResponse; +const retryTypedResponse = (name, method, maxAttempts = DefaultRetryAttempts, delay = DefaultRetryDelay) => retry(name, method, (response) => response.statusCode, maxAttempts, delay, +// If the error object contains the statusCode property, extract it and return +// an TypedResponse so it can be processed by the retry logic. +(error) => { + if (error instanceof http_client_1.HttpClientError) { + return { + statusCode: error.statusCode, + result: null, + headers: {}, + error + }; + } + else { + return undefined; + } +}); +exports.retryTypedResponse = retryTypedResponse; + + +/***/ }), + /***/ 7236: /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { @@ -273,7 +603,8 @@ const is_labeled_1 = __nccwpck_require__(6792); const is_pull_request_1 = __nccwpck_require__(5400); const operations_1 = __nccwpck_require__(7957); class Issue { - constructor(options, issue) { + constructor(options, issue // | Readonly + ) { this.operations = new operations_1.Operations(); this._options = options; this.title = issue.title; @@ -686,7 +1017,11 @@ class IssuesProcessor { page }); (_a = this.statistics) === null || _a === void 0 ? void 0 : _a.incrementFetchedItemsCount(issueResult.data.length); - return issueResult.data.map((issue) => new issue_1.Issue(this.options, issue)); + // state_reason is incompatible - oktokit dependency conflict? + // return issueResult.data.map((issue: Readonly): Issue => { + return issueResult.data.map((issue) => { + return new issue_1.Issue(this.options, issue); + }); } catch (error) { throw Error(`Getting issues was blocked by the error: ${error.message}`); @@ -1558,13 +1893,13 @@ exports.State = void 0; const os_1 = __importDefault(__nccwpck_require__(2037)); const fs_1 = __importDefault(__nccwpck_require__(7147)); const path_1 = __importDefault(__nccwpck_require__(1017)); -// import * as artifact from '@actions/artifact'; const core = __importStar(__nccwpck_require__(2186)); -const cache_1 = __nccwpck_require__(7799); +const actions_cache_1 = __nccwpck_require__(7985); class State { - constructor() { + constructor(options) { + this.options = options; this.processedIssuesIDs = new Set(); - this.debug = core.getInput('debug-only') === 'true'; + this.debug = options.debugOnly; } isIssueProcessed(issue) { return this.processedIssuesIDs.has(issue.number); @@ -1584,16 +1919,17 @@ class State { return; } const serialized = Array.from(this.processedIssuesIDs).join('|'); - const tmpDir = os_1.default.tmpdir(); + // const tmpDir = path.join(os.tmpdir(),crypto.randomBytes(8).readBigUInt64LE(0).toString()); + const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'state-')); + // const tmpDir = path.join(os.tmpdir(), 'state'); + // fs.mkdirSync(tmpDir, {recursive: true}); const file = path_1.default.join(tmpDir, State.STATE_FILE // crypto.randomBytes(8).readBigUInt64LE(0).toString() ); fs_1.default.writeFileSync(file, serialized); - core.debug(`Persisting state includes info about ${this.processedIssuesIDs.size} issue(s)`); - // const artifactClient = artifact.create(); + core.debug(`Persisting state includes info about ${this.processedIssuesIDs.size} issue(s) --> ${serialized}`); try { - (0, cache_1.saveCache)([tmpDir], State.ARTIFACT_NAME); - // await artifactClient.uploadArtifact(State.ARTIFACT_NAME, [file], tmpDir); + yield (0, actions_cache_1.saveFileAsActionsCache)(file); } catch (error) { core.warning(`Persisting the state was not successful due to "${error.message || 'unknown reason'}"`); @@ -1603,7 +1939,10 @@ class State { rehydrate() { return __awaiter(this, void 0, void 0, function* () { this.reset(); - const tmpDir = os_1.default.tmpdir(); + // const tmpDir = os.mkdtemp() os.tmpdir(); + // const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'state-')); + const tmpDir = path_1.default.join(os_1.default.tmpdir(), 'state'); + fs_1.default.mkdirSync(tmpDir, { recursive: true }); // const artifactClient = artifact.create(); try { /* @@ -1612,7 +1951,7 @@ class State { tmpDir ); */ - yield (0, cache_1.restoreCache)([tmpDir], State.ARTIFACT_NAME); + // await restoreCache([tmpDir], State.ARTIFACT_NAME); /* const downloadedFiles = fs.readdirSync(downloadResponse.downloadPath); if (downloadedFiles.length === 0) { @@ -1621,15 +1960,20 @@ class State { ); } */ - const serialized = fs_1.default.readFileSync(path_1.default.join(tmpDir, State.ARTIFACT_NAME), { encoding: 'utf8' }); + if (!fs_1.default.existsSync(path_1.default.join(tmpDir, State.STATE_FILE))) { + throw Error('There is no state persisted, probably because of the very first run or previous run failed'); + } + const serialized = fs_1.default.readFileSync(path_1.default.join(tmpDir, State.STATE_FILE), { + encoding: 'utf8' + }); if (serialized.length === 0) return; const issueIDs = serialized .split('|') - .map(parseInt) + .map(id => parseInt(id)) .filter(i => !isNaN(i)); this.processedIssuesIDs = new Set(issueIDs); - core.debug(`Rehydrated state includes info about ${issueIDs.length} issue(s)`); + core.debug(`Rehydrated state includes info about ${issueIDs.length} issue(s) <-- ${serialized}, ${JSON.stringify(issueIDs)}-${JSON.stringify(serialized.split('|'))}`); } catch (error) { core.warning(`Rehydrating the state was not successful due to "${error.message || 'unknown reason'}"`); @@ -1637,9 +1981,9 @@ class State { }); } } +exports.State = State; State.ARTIFACT_NAME = '_state'; State.STATE_FILE = 'state.txt'; -exports.State = State; /***/ }), @@ -2069,7 +2413,7 @@ var Option; Option["IgnorePrUpdates"] = "ignore-pr-updates"; Option["ExemptDraftPr"] = "exempt-draft-pr"; Option["CloseIssueReason"] = "close-issue-reason"; -})(Option = exports.Option || (exports.Option = {})); +})(Option || (exports.Option = Option = {})); /***/ }), @@ -2334,7 +2678,7 @@ function _run() { return __awaiter(this, void 0, void 0, function* () { try { const args = _getAndValidateArgs(); - const state = state_service_1.StateService.getState(); + const state = state_service_1.StateService.getState(args); yield state.rehydrate(); const issueProcessor = new issues_processor_1.IssuesProcessor(args, state); yield issueProcessor.processIssues(); @@ -2529,8 +2873,8 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.StateService = void 0; const state_1 = __nccwpck_require__(1776); class StateService { - static getState() { - return new state_1.State(); + static getState(options) { + return new state_1.State(options); } } exports.StateService = StateService; @@ -6431,8 +6775,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.OidcClient = void 0; -const http_client_1 = __nccwpck_require__(1404); -const auth_1 = __nccwpck_require__(6758); +const http_client_1 = __nccwpck_require__(6255); +const auth_1 = __nccwpck_require__(5526); const core_1 = __nccwpck_require__(2186); class OidcClient { static createHttpClient(allowRetry = true, maxRetry = 10) { @@ -6901,775 +7245,7 @@ exports.toCommandProperties = toCommandProperties; /***/ }), -/***/ 6758: -/***/ (function(__unused_webpack_module, exports) { - -"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()); - }); -}; -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.PersonalAccessTokenCredentialHandler = exports.BearerCredentialHandler = exports.BasicCredentialHandler = void 0; -class BasicCredentialHandler { - constructor(username, password) { - this.username = username; - this.password = password; - } - prepareRequest(options) { - if (!options.headers) { - throw Error('The request has no headers'); - } - options.headers['Authorization'] = `Basic ${Buffer.from(`${this.username}:${this.password}`).toString('base64')}`; - } - // This handler cannot handle 401 - canHandleAuthentication() { - return false; - } - handleAuthentication() { - return __awaiter(this, void 0, void 0, function* () { - throw new Error('not implemented'); - }); - } -} -exports.BasicCredentialHandler = BasicCredentialHandler; -class BearerCredentialHandler { - constructor(token) { - this.token = token; - } - // currently implements pre-authorization - // TODO: support preAuth = false where it hooks on 401 - prepareRequest(options) { - if (!options.headers) { - throw Error('The request has no headers'); - } - options.headers['Authorization'] = `Bearer ${this.token}`; - } - // This handler cannot handle 401 - canHandleAuthentication() { - return false; - } - handleAuthentication() { - return __awaiter(this, void 0, void 0, function* () { - throw new Error('not implemented'); - }); - } -} -exports.BearerCredentialHandler = BearerCredentialHandler; -class PersonalAccessTokenCredentialHandler { - constructor(token) { - this.token = token; - } - // currently implements pre-authorization - // TODO: support preAuth = false where it hooks on 401 - prepareRequest(options) { - if (!options.headers) { - throw Error('The request has no headers'); - } - options.headers['Authorization'] = `Basic ${Buffer.from(`PAT:${this.token}`).toString('base64')}`; - } - // This handler cannot handle 401 - canHandleAuthentication() { - return false; - } - handleAuthentication() { - return __awaiter(this, void 0, void 0, function* () { - throw new Error('not implemented'); - }); - } -} -exports.PersonalAccessTokenCredentialHandler = PersonalAccessTokenCredentialHandler; -//# sourceMappingURL=auth.js.map - -/***/ }), - -/***/ 1404: -/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { - -"use strict"; - -/* eslint-disable @typescript-eslint/no-explicit-any */ -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -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()); - }); -}; -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.HttpClient = exports.isHttps = exports.HttpClientResponse = exports.HttpClientError = exports.getProxyUrl = exports.MediaTypes = exports.Headers = exports.HttpCodes = void 0; -const http = __importStar(__nccwpck_require__(3685)); -const https = __importStar(__nccwpck_require__(5687)); -const pm = __importStar(__nccwpck_require__(2843)); -const tunnel = __importStar(__nccwpck_require__(4294)); -var HttpCodes; -(function (HttpCodes) { - HttpCodes[HttpCodes["OK"] = 200] = "OK"; - HttpCodes[HttpCodes["MultipleChoices"] = 300] = "MultipleChoices"; - HttpCodes[HttpCodes["MovedPermanently"] = 301] = "MovedPermanently"; - HttpCodes[HttpCodes["ResourceMoved"] = 302] = "ResourceMoved"; - HttpCodes[HttpCodes["SeeOther"] = 303] = "SeeOther"; - HttpCodes[HttpCodes["NotModified"] = 304] = "NotModified"; - HttpCodes[HttpCodes["UseProxy"] = 305] = "UseProxy"; - HttpCodes[HttpCodes["SwitchProxy"] = 306] = "SwitchProxy"; - HttpCodes[HttpCodes["TemporaryRedirect"] = 307] = "TemporaryRedirect"; - HttpCodes[HttpCodes["PermanentRedirect"] = 308] = "PermanentRedirect"; - HttpCodes[HttpCodes["BadRequest"] = 400] = "BadRequest"; - HttpCodes[HttpCodes["Unauthorized"] = 401] = "Unauthorized"; - HttpCodes[HttpCodes["PaymentRequired"] = 402] = "PaymentRequired"; - HttpCodes[HttpCodes["Forbidden"] = 403] = "Forbidden"; - HttpCodes[HttpCodes["NotFound"] = 404] = "NotFound"; - HttpCodes[HttpCodes["MethodNotAllowed"] = 405] = "MethodNotAllowed"; - HttpCodes[HttpCodes["NotAcceptable"] = 406] = "NotAcceptable"; - HttpCodes[HttpCodes["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired"; - HttpCodes[HttpCodes["RequestTimeout"] = 408] = "RequestTimeout"; - HttpCodes[HttpCodes["Conflict"] = 409] = "Conflict"; - HttpCodes[HttpCodes["Gone"] = 410] = "Gone"; - HttpCodes[HttpCodes["TooManyRequests"] = 429] = "TooManyRequests"; - HttpCodes[HttpCodes["InternalServerError"] = 500] = "InternalServerError"; - HttpCodes[HttpCodes["NotImplemented"] = 501] = "NotImplemented"; - HttpCodes[HttpCodes["BadGateway"] = 502] = "BadGateway"; - HttpCodes[HttpCodes["ServiceUnavailable"] = 503] = "ServiceUnavailable"; - HttpCodes[HttpCodes["GatewayTimeout"] = 504] = "GatewayTimeout"; -})(HttpCodes = exports.HttpCodes || (exports.HttpCodes = {})); -var Headers; -(function (Headers) { - Headers["Accept"] = "accept"; - Headers["ContentType"] = "content-type"; -})(Headers = exports.Headers || (exports.Headers = {})); -var MediaTypes; -(function (MediaTypes) { - MediaTypes["ApplicationJson"] = "application/json"; -})(MediaTypes = exports.MediaTypes || (exports.MediaTypes = {})); -/** - * Returns the proxy URL, depending upon the supplied url and proxy environment variables. - * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com - */ -function getProxyUrl(serverUrl) { - const proxyUrl = pm.getProxyUrl(new URL(serverUrl)); - return proxyUrl ? proxyUrl.href : ''; -} -exports.getProxyUrl = getProxyUrl; -const HttpRedirectCodes = [ - HttpCodes.MovedPermanently, - HttpCodes.ResourceMoved, - HttpCodes.SeeOther, - HttpCodes.TemporaryRedirect, - HttpCodes.PermanentRedirect -]; -const HttpResponseRetryCodes = [ - HttpCodes.BadGateway, - HttpCodes.ServiceUnavailable, - HttpCodes.GatewayTimeout -]; -const RetryableHttpVerbs = ['OPTIONS', 'GET', 'DELETE', 'HEAD']; -const ExponentialBackoffCeiling = 10; -const ExponentialBackoffTimeSlice = 5; -class HttpClientError extends Error { - constructor(message, statusCode) { - super(message); - this.name = 'HttpClientError'; - this.statusCode = statusCode; - Object.setPrototypeOf(this, HttpClientError.prototype); - } -} -exports.HttpClientError = HttpClientError; -class HttpClientResponse { - constructor(message) { - this.message = message; - } - readBody() { - return __awaiter(this, void 0, void 0, function* () { - return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { - let output = Buffer.alloc(0); - this.message.on('data', (chunk) => { - output = Buffer.concat([output, chunk]); - }); - this.message.on('end', () => { - resolve(output.toString()); - }); - })); - }); - } -} -exports.HttpClientResponse = HttpClientResponse; -function isHttps(requestUrl) { - const parsedUrl = new URL(requestUrl); - return parsedUrl.protocol === 'https:'; -} -exports.isHttps = isHttps; -class HttpClient { - constructor(userAgent, handlers, requestOptions) { - this._ignoreSslError = false; - this._allowRedirects = true; - this._allowRedirectDowngrade = false; - this._maxRedirects = 50; - this._allowRetries = false; - this._maxRetries = 1; - this._keepAlive = false; - this._disposed = false; - this.userAgent = userAgent; - this.handlers = handlers || []; - this.requestOptions = requestOptions; - if (requestOptions) { - if (requestOptions.ignoreSslError != null) { - this._ignoreSslError = requestOptions.ignoreSslError; - } - this._socketTimeout = requestOptions.socketTimeout; - if (requestOptions.allowRedirects != null) { - this._allowRedirects = requestOptions.allowRedirects; - } - if (requestOptions.allowRedirectDowngrade != null) { - this._allowRedirectDowngrade = requestOptions.allowRedirectDowngrade; - } - if (requestOptions.maxRedirects != null) { - this._maxRedirects = Math.max(requestOptions.maxRedirects, 0); - } - if (requestOptions.keepAlive != null) { - this._keepAlive = requestOptions.keepAlive; - } - if (requestOptions.allowRetries != null) { - this._allowRetries = requestOptions.allowRetries; - } - if (requestOptions.maxRetries != null) { - this._maxRetries = requestOptions.maxRetries; - } - } - } - options(requestUrl, additionalHeaders) { - return __awaiter(this, void 0, void 0, function* () { - return this.request('OPTIONS', requestUrl, null, additionalHeaders || {}); - }); - } - get(requestUrl, additionalHeaders) { - return __awaiter(this, void 0, void 0, function* () { - return this.request('GET', requestUrl, null, additionalHeaders || {}); - }); - } - del(requestUrl, additionalHeaders) { - return __awaiter(this, void 0, void 0, function* () { - return this.request('DELETE', requestUrl, null, additionalHeaders || {}); - }); - } - post(requestUrl, data, additionalHeaders) { - return __awaiter(this, void 0, void 0, function* () { - return this.request('POST', requestUrl, data, additionalHeaders || {}); - }); - } - patch(requestUrl, data, additionalHeaders) { - return __awaiter(this, void 0, void 0, function* () { - return this.request('PATCH', requestUrl, data, additionalHeaders || {}); - }); - } - put(requestUrl, data, additionalHeaders) { - return __awaiter(this, void 0, void 0, function* () { - return this.request('PUT', requestUrl, data, additionalHeaders || {}); - }); - } - head(requestUrl, additionalHeaders) { - return __awaiter(this, void 0, void 0, function* () { - return this.request('HEAD', requestUrl, null, additionalHeaders || {}); - }); - } - sendStream(verb, requestUrl, stream, additionalHeaders) { - return __awaiter(this, void 0, void 0, function* () { - return this.request(verb, requestUrl, stream, additionalHeaders); - }); - } - /** - * Gets a typed object from an endpoint - * Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise - */ - getJson(requestUrl, additionalHeaders = {}) { - return __awaiter(this, void 0, void 0, function* () { - additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); - const res = yield this.get(requestUrl, additionalHeaders); - return this._processResponse(res, this.requestOptions); - }); - } - postJson(requestUrl, obj, additionalHeaders = {}) { - return __awaiter(this, void 0, void 0, function* () { - const data = JSON.stringify(obj, null, 2); - additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); - additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); - const res = yield this.post(requestUrl, data, additionalHeaders); - return this._processResponse(res, this.requestOptions); - }); - } - putJson(requestUrl, obj, additionalHeaders = {}) { - return __awaiter(this, void 0, void 0, function* () { - const data = JSON.stringify(obj, null, 2); - additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); - additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); - const res = yield this.put(requestUrl, data, additionalHeaders); - return this._processResponse(res, this.requestOptions); - }); - } - patchJson(requestUrl, obj, additionalHeaders = {}) { - return __awaiter(this, void 0, void 0, function* () { - const data = JSON.stringify(obj, null, 2); - additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); - additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); - const res = yield this.patch(requestUrl, data, additionalHeaders); - return this._processResponse(res, this.requestOptions); - }); - } - /** - * Makes a raw http request. - * All other methods such as get, post, patch, and request ultimately call this. - * Prefer get, del, post and patch - */ - request(verb, requestUrl, data, headers) { - return __awaiter(this, void 0, void 0, function* () { - if (this._disposed) { - throw new Error('Client has already been disposed.'); - } - const parsedUrl = new URL(requestUrl); - let info = this._prepareRequest(verb, parsedUrl, headers); - // Only perform retries on reads since writes may not be idempotent. - const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb) - ? this._maxRetries + 1 - : 1; - let numTries = 0; - let response; - do { - response = yield this.requestRaw(info, data); - // Check if it's an authentication challenge - if (response && - response.message && - response.message.statusCode === HttpCodes.Unauthorized) { - let authenticationHandler; - for (const handler of this.handlers) { - if (handler.canHandleAuthentication(response)) { - authenticationHandler = handler; - break; - } - } - if (authenticationHandler) { - return authenticationHandler.handleAuthentication(this, info, data); - } - else { - // We have received an unauthorized response but have no handlers to handle it. - // Let the response return to the caller. - return response; - } - } - let redirectsRemaining = this._maxRedirects; - while (response.message.statusCode && - HttpRedirectCodes.includes(response.message.statusCode) && - this._allowRedirects && - redirectsRemaining > 0) { - const redirectUrl = response.message.headers['location']; - if (!redirectUrl) { - // if there's no location to redirect to, we won't - break; - } - const parsedRedirectUrl = new URL(redirectUrl); - if (parsedUrl.protocol === 'https:' && - parsedUrl.protocol !== parsedRedirectUrl.protocol && - !this._allowRedirectDowngrade) { - throw new Error('Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.'); - } - // we need to finish reading the response before reassigning response - // which will leak the open socket. - yield response.readBody(); - // strip authorization header if redirected to a different hostname - if (parsedRedirectUrl.hostname !== parsedUrl.hostname) { - for (const header in headers) { - // header names are case insensitive - if (header.toLowerCase() === 'authorization') { - delete headers[header]; - } - } - } - // let's make the request with the new redirectUrl - info = this._prepareRequest(verb, parsedRedirectUrl, headers); - response = yield this.requestRaw(info, data); - redirectsRemaining--; - } - if (!response.message.statusCode || - !HttpResponseRetryCodes.includes(response.message.statusCode)) { - // If not a retry code, return immediately instead of retrying - return response; - } - numTries += 1; - if (numTries < maxTries) { - yield response.readBody(); - yield this._performExponentialBackoff(numTries); - } - } while (numTries < maxTries); - return response; - }); - } - /** - * Needs to be called if keepAlive is set to true in request options. - */ - dispose() { - if (this._agent) { - this._agent.destroy(); - } - this._disposed = true; - } - /** - * Raw request. - * @param info - * @param data - */ - requestRaw(info, data) { - return __awaiter(this, void 0, void 0, function* () { - return new Promise((resolve, reject) => { - function callbackForResult(err, res) { - if (err) { - reject(err); - } - else if (!res) { - // If `err` is not passed, then `res` must be passed. - reject(new Error('Unknown error')); - } - else { - resolve(res); - } - } - this.requestRawWithCallback(info, data, callbackForResult); - }); - }); - } - /** - * Raw request with callback. - * @param info - * @param data - * @param onResult - */ - requestRawWithCallback(info, data, onResult) { - if (typeof data === 'string') { - if (!info.options.headers) { - info.options.headers = {}; - } - info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8'); - } - let callbackCalled = false; - function handleResult(err, res) { - if (!callbackCalled) { - callbackCalled = true; - onResult(err, res); - } - } - const req = info.httpModule.request(info.options, (msg) => { - const res = new HttpClientResponse(msg); - handleResult(undefined, res); - }); - let socket; - req.on('socket', sock => { - socket = sock; - }); - // If we ever get disconnected, we want the socket to timeout eventually - req.setTimeout(this._socketTimeout || 3 * 60000, () => { - if (socket) { - socket.end(); - } - handleResult(new Error(`Request timeout: ${info.options.path}`)); - }); - req.on('error', function (err) { - // err has statusCode property - // res should have headers - handleResult(err); - }); - if (data && typeof data === 'string') { - req.write(data, 'utf8'); - } - if (data && typeof data !== 'string') { - data.on('close', function () { - req.end(); - }); - data.pipe(req); - } - else { - req.end(); - } - } - /** - * Gets an http agent. This function is useful when you need an http agent that handles - * routing through a proxy server - depending upon the url and proxy environment variables. - * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com - */ - getAgent(serverUrl) { - const parsedUrl = new URL(serverUrl); - return this._getAgent(parsedUrl); - } - _prepareRequest(method, requestUrl, headers) { - const info = {}; - info.parsedUrl = requestUrl; - const usingSsl = info.parsedUrl.protocol === 'https:'; - info.httpModule = usingSsl ? https : http; - const defaultPort = usingSsl ? 443 : 80; - info.options = {}; - info.options.host = info.parsedUrl.hostname; - info.options.port = info.parsedUrl.port - ? parseInt(info.parsedUrl.port) - : defaultPort; - info.options.path = - (info.parsedUrl.pathname || '') + (info.parsedUrl.search || ''); - info.options.method = method; - info.options.headers = this._mergeHeaders(headers); - if (this.userAgent != null) { - info.options.headers['user-agent'] = this.userAgent; - } - info.options.agent = this._getAgent(info.parsedUrl); - // gives handlers an opportunity to participate - if (this.handlers) { - for (const handler of this.handlers) { - handler.prepareRequest(info.options); - } - } - return info; - } - _mergeHeaders(headers) { - if (this.requestOptions && this.requestOptions.headers) { - return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers || {})); - } - return lowercaseKeys(headers || {}); - } - _getExistingOrDefaultHeader(additionalHeaders, header, _default) { - let clientHeader; - if (this.requestOptions && this.requestOptions.headers) { - clientHeader = lowercaseKeys(this.requestOptions.headers)[header]; - } - return additionalHeaders[header] || clientHeader || _default; - } - _getAgent(parsedUrl) { - let agent; - const proxyUrl = pm.getProxyUrl(parsedUrl); - const useProxy = proxyUrl && proxyUrl.hostname; - if (this._keepAlive && useProxy) { - agent = this._proxyAgent; - } - if (this._keepAlive && !useProxy) { - agent = this._agent; - } - // if agent is already assigned use that agent. - if (agent) { - return agent; - } - const usingSsl = parsedUrl.protocol === 'https:'; - let maxSockets = 100; - if (this.requestOptions) { - maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets; - } - // This is `useProxy` again, but we need to check `proxyURl` directly for TypeScripts's flow analysis. - if (proxyUrl && proxyUrl.hostname) { - const agentOptions = { - maxSockets, - keepAlive: this._keepAlive, - proxy: Object.assign(Object.assign({}, ((proxyUrl.username || proxyUrl.password) && { - proxyAuth: `${proxyUrl.username}:${proxyUrl.password}` - })), { host: proxyUrl.hostname, port: proxyUrl.port }) - }; - let tunnelAgent; - const overHttps = proxyUrl.protocol === 'https:'; - if (usingSsl) { - tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp; - } - else { - tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp; - } - agent = tunnelAgent(agentOptions); - this._proxyAgent = agent; - } - // if reusing agent across request and tunneling agent isn't assigned create a new agent - if (this._keepAlive && !agent) { - const options = { keepAlive: this._keepAlive, maxSockets }; - agent = usingSsl ? new https.Agent(options) : new http.Agent(options); - this._agent = agent; - } - // if not using private agent and tunnel agent isn't setup then use global agent - if (!agent) { - agent = usingSsl ? https.globalAgent : http.globalAgent; - } - if (usingSsl && this._ignoreSslError) { - // we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process - // http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options - // we have to cast it to any and change it directly - agent.options = Object.assign(agent.options || {}, { - rejectUnauthorized: false - }); - } - return agent; - } - _performExponentialBackoff(retryNumber) { - return __awaiter(this, void 0, void 0, function* () { - retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber); - const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber); - return new Promise(resolve => setTimeout(() => resolve(), ms)); - }); - } - _processResponse(res, options) { - return __awaiter(this, void 0, void 0, function* () { - return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { - const statusCode = res.message.statusCode || 0; - const response = { - statusCode, - result: null, - headers: {} - }; - // not found leads to null obj returned - if (statusCode === HttpCodes.NotFound) { - resolve(response); - } - // get the result from the body - function dateTimeDeserializer(key, value) { - if (typeof value === 'string') { - const a = new Date(value); - if (!isNaN(a.valueOf())) { - return a; - } - } - return value; - } - let obj; - let contents; - try { - contents = yield res.readBody(); - if (contents && contents.length > 0) { - if (options && options.deserializeDates) { - obj = JSON.parse(contents, dateTimeDeserializer); - } - else { - obj = JSON.parse(contents); - } - response.result = obj; - } - response.headers = res.message.headers; - } - catch (err) { - // Invalid resource (contents not json); leaving result obj null - } - // note that 3xx redirects are handled by the http layer. - if (statusCode > 299) { - let msg; - // if exception/error in body, attempt to get better error - if (obj && obj.message) { - msg = obj.message; - } - else if (contents && contents.length > 0) { - // it may be the case that the exception is in the body message as string - msg = contents; - } - else { - msg = `Failed request: (${statusCode})`; - } - const err = new HttpClientError(msg, statusCode); - err.result = response.result; - reject(err); - } - else { - resolve(response); - } - })); - }); - } -} -exports.HttpClient = HttpClient; -const lowercaseKeys = (obj) => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {}); -//# sourceMappingURL=index.js.map - -/***/ }), - -/***/ 2843: -/***/ ((__unused_webpack_module, exports) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.checkBypass = exports.getProxyUrl = void 0; -function getProxyUrl(reqUrl) { - const usingSsl = reqUrl.protocol === 'https:'; - if (checkBypass(reqUrl)) { - return undefined; - } - const proxyVar = (() => { - if (usingSsl) { - return process.env['https_proxy'] || process.env['HTTPS_PROXY']; - } - else { - return process.env['http_proxy'] || process.env['HTTP_PROXY']; - } - })(); - if (proxyVar) { - return new URL(proxyVar); - } - else { - return undefined; - } -} -exports.getProxyUrl = getProxyUrl; -function checkBypass(reqUrl) { - if (!reqUrl.hostname) { - return false; - } - const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || ''; - if (!noProxy) { - return false; - } - // Determine the request port - let reqPort; - if (reqUrl.port) { - reqPort = Number(reqUrl.port); - } - else if (reqUrl.protocol === 'http:') { - reqPort = 80; - } - else if (reqUrl.protocol === 'https:') { - reqPort = 443; - } - // Format the request hostname and hostname with port - const upperReqHosts = [reqUrl.hostname.toUpperCase()]; - if (typeof reqPort === 'number') { - upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`); - } - // Compare request host against noproxy - for (const upperNoProxyItem of noProxy - .split(',') - .map(x => x.trim().toUpperCase()) - .filter(x => x)) { - if (upperReqHosts.some(x => x === upperNoProxyItem)) { - return true; - } - } - return false; -} -exports.checkBypass = checkBypass; -//# sourceMappingURL=proxy.js.map - -/***/ }), - -/***/ 1514: +/***/ 1514: /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -11453,6 +11029,10 @@ function checkBypass(reqUrl) { if (!reqUrl.hostname) { return false; } + const reqHost = reqUrl.hostname; + if (isLoopbackAddress(reqHost)) { + return true; + } const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || ''; if (!noProxy) { return false; @@ -11478,13 +11058,24 @@ function checkBypass(reqUrl) { .split(',') .map(x => x.trim().toUpperCase()) .filter(x => x)) { - if (upperReqHosts.some(x => x === upperNoProxyItem)) { + if (upperNoProxyItem === '*' || + upperReqHosts.some(x => x === upperNoProxyItem || + x.endsWith(`.${upperNoProxyItem}`) || + (upperNoProxyItem.startsWith('.') && + x.endsWith(`${upperNoProxyItem}`)))) { return true; } } return false; } exports.checkBypass = checkBypass; +function isLoopbackAddress(host) { + const hostLower = host.toLowerCase(); + return (hostLower === 'localhost' || + hostLower.startsWith('127.') || + hostLower.startsWith('[::1]') || + hostLower.startsWith('[0:0:0:0:0:0:0:1]')); +} //# sourceMappingURL=proxy.js.map /***/ }), @@ -18470,7 +18061,7 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ -/* global global, define, System, Reflect, Promise */ +/* global global, define, Symbol, Reflect, Promise, SuppressedError */ var __extends; var __assign; var __rest; @@ -18500,6 +18091,8 @@ var __classPrivateFieldGet; var __classPrivateFieldSet; var __classPrivateFieldIn; var __createBinding; +var __addDisposableResource; +var __disposeResources; (function (factory) { var root = typeof global === "object" ? global : typeof self === "object" ? self : typeof this === "object" ? this : {}; if (typeof define === "function" && define.amd) { @@ -18796,6 +18389,53 @@ var __createBinding; return typeof state === "function" ? receiver === state : state.has(receiver); }; + __addDisposableResource = function (env, value, async) { + if (value !== null && value !== void 0) { + if (typeof value !== "object") throw new TypeError("Object expected."); + var dispose; + if (async) { + if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); + dispose = value[Symbol.asyncDispose]; + } + if (dispose === void 0) { + if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); + dispose = value[Symbol.dispose]; + } + if (typeof dispose !== "function") throw new TypeError("Object not disposable."); + env.stack.push({ value: value, dispose: dispose, async: async }); + } + else if (async) { + env.stack.push({ async: true }); + } + return value; + }; + + var _SuppressedError = typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { + var e = new Error(message); + return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; + }; + + __disposeResources = function (env) { + function fail(e) { + env.error = env.hasError ? new _SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; + env.hasError = true; + } + function next() { + while (env.stack.length) { + var rec = env.stack.pop(); + try { + var result = rec.dispose && rec.dispose.call(rec.value); + if (rec.async) return Promise.resolve(result).then(next, function(e) { fail(e); return next(); }); + } + catch (e) { + fail(e); + } + } + if (env.hasError) throw env.error; + } + return next(); + }; + exporter("__extends", __extends); exporter("__assign", __assign); exporter("__rest", __rest); @@ -18825,6 +18465,8 @@ var __createBinding; exporter("__classPrivateFieldGet", __classPrivateFieldGet); exporter("__classPrivateFieldSet", __classPrivateFieldSet); exporter("__classPrivateFieldIn", __classPrivateFieldIn); + exporter("__addDisposableResource", __addDisposableResource); + exporter("__disposeResources", __disposeResources); }); @@ -20128,7 +19770,7 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ -/* global global, define, System, Reflect, Promise */ +/* global global, define, Symbol, Reflect, Promise, SuppressedError */ var __extends; var __assign; var __rest; @@ -20158,6 +19800,8 @@ var __classPrivateFieldGet; var __classPrivateFieldSet; var __classPrivateFieldIn; var __createBinding; +var __addDisposableResource; +var __disposeResources; (function (factory) { var root = typeof global === "object" ? global : typeof self === "object" ? self : typeof this === "object" ? this : {}; if (typeof define === "function" && define.amd) { @@ -20454,6 +20098,53 @@ var __createBinding; return typeof state === "function" ? receiver === state : state.has(receiver); }; + __addDisposableResource = function (env, value, async) { + if (value !== null && value !== void 0) { + if (typeof value !== "object") throw new TypeError("Object expected."); + var dispose; + if (async) { + if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); + dispose = value[Symbol.asyncDispose]; + } + if (dispose === void 0) { + if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); + dispose = value[Symbol.dispose]; + } + if (typeof dispose !== "function") throw new TypeError("Object not disposable."); + env.stack.push({ value: value, dispose: dispose, async: async }); + } + else if (async) { + env.stack.push({ async: true }); + } + return value; + }; + + var _SuppressedError = typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { + var e = new Error(message); + return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; + }; + + __disposeResources = function (env) { + function fail(e) { + env.error = env.hasError ? new _SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; + env.hasError = true; + } + function next() { + while (env.stack.length) { + var rec = env.stack.pop(); + try { + var result = rec.dispose && rec.dispose.call(rec.value); + if (rec.async) return Promise.resolve(result).then(next, function(e) { fail(e); return next(); }); + } + catch (e) { + fail(e); + } + } + if (env.hasError) throw env.error; + } + return next(); + }; + exporter("__extends", __extends); exporter("__assign", __assign); exporter("__rest", __rest); @@ -20483,6 +20174,8 @@ var __createBinding; exporter("__classPrivateFieldGet", __classPrivateFieldGet); exporter("__classPrivateFieldSet", __classPrivateFieldSet); exporter("__classPrivateFieldIn", __classPrivateFieldIn); + exporter("__addDisposableResource", __addDisposableResource); + exporter("__disposeResources", __disposeResources); }); @@ -46358,7 +46051,7 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ -/* global global, define, System, Reflect, Promise */ +/* global global, define, Symbol, Reflect, Promise, SuppressedError */ var __extends; var __assign; var __rest; @@ -46388,6 +46081,8 @@ var __classPrivateFieldGet; var __classPrivateFieldSet; var __classPrivateFieldIn; var __createBinding; +var __addDisposableResource; +var __disposeResources; (function (factory) { var root = typeof global === "object" ? global : typeof self === "object" ? self : typeof this === "object" ? this : {}; if (typeof define === "function" && define.amd) { @@ -46684,6 +46379,53 @@ var __createBinding; return typeof state === "function" ? receiver === state : state.has(receiver); }; + __addDisposableResource = function (env, value, async) { + if (value !== null && value !== void 0) { + if (typeof value !== "object") throw new TypeError("Object expected."); + var dispose; + if (async) { + if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); + dispose = value[Symbol.asyncDispose]; + } + if (dispose === void 0) { + if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); + dispose = value[Symbol.dispose]; + } + if (typeof dispose !== "function") throw new TypeError("Object not disposable."); + env.stack.push({ value: value, dispose: dispose, async: async }); + } + else if (async) { + env.stack.push({ async: true }); + } + return value; + }; + + var _SuppressedError = typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { + var e = new Error(message); + return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; + }; + + __disposeResources = function (env) { + function fail(e) { + env.error = env.hasError ? new _SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; + env.hasError = true; + } + function next() { + while (env.stack.length) { + var rec = env.stack.pop(); + try { + var result = rec.dispose && rec.dispose.call(rec.value); + if (rec.async) return Promise.resolve(result).then(next, function(e) { fail(e); return next(); }); + } + catch (e) { + fail(e); + } + } + if (env.hasError) throw env.error; + } + return next(); + }; + exporter("__extends", __extends); exporter("__assign", __assign); exporter("__rest", __rest); @@ -46713,6 +46455,8 @@ var __createBinding; exporter("__classPrivateFieldGet", __classPrivateFieldGet); exporter("__classPrivateFieldSet", __classPrivateFieldSet); exporter("__classPrivateFieldIn", __classPrivateFieldIn); + exporter("__addDisposableResource", __addDisposableResource); + exporter("__disposeResources", __disposeResources); }); @@ -48047,54 +47791,83 @@ exports.restEndpointMethods = restEndpointMethods; /***/ }), /***/ 6298: -/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { "use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// pkg/dist-src/index.js +var dist_src_exports = {}; +__export(dist_src_exports, { + VERSION: () => VERSION, + retry: () => retry +}); +module.exports = __toCommonJS(dist_src_exports); -Object.defineProperty(exports, "__esModule", ({ value: true })); - -function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } - -var Bottleneck = _interopDefault(__nccwpck_require__(1174)); -var requestError = __nccwpck_require__(537); - -// @ts-ignore +// pkg/dist-src/error-request.js async function errorRequest(state, octokit, error, options) { if (!error.request || !error.request.request) { - // address https://github.com/octokit/plugin-retry.js/issues/8 throw error; } - // retry all >= 400 && not doNotRetry if (error.status >= 400 && !state.doNotRetry.includes(error.status)) { const retries = options.request.retries != null ? options.request.retries : state.retries; const retryAfter = Math.pow((options.request.retryCount || 0) + 1, 2); throw octokit.retry.retryRequest(error, retries, retryAfter); } - // Maybe eventually there will be more cases here throw error; } -// @ts-nocheck +// pkg/dist-src/wrap-request.js +var import_light = __toESM(__nccwpck_require__(1174)); +var import_request_error = __nccwpck_require__(537); async function wrapRequest(state, octokit, request, options) { - const limiter = new Bottleneck(); - limiter.on("failed", function (error, info) { + const limiter = new import_light.default(); + limiter.on("failed", function(error, info) { const maxRetries = ~~error.request.request.retries; const after = ~~error.request.request.retryAfter; options.request.retryCount = info.retryCount + 1; if (maxRetries > info.retryCount) { - // Returning a number instructs the limiter to retry - // the request after that number of milliseconds have passed return after * state.retryAfterBaseValue; } }); - return limiter.schedule(requestWithGraphqlErrorHandling.bind(null, state, octokit, request), options); + return limiter.schedule( + requestWithGraphqlErrorHandling.bind(null, state, octokit, request), + options + ); } async function requestWithGraphqlErrorHandling(state, octokit, request, options) { const response = await request(request, options); - if (response.data && response.data.errors && /Something went wrong while executing your query/.test(response.data.errors[0].message)) { - // simulate 500 request error for retry handling - const error = new requestError.RequestError(response.data.errors[0].message, 500, { + if (response.data && response.data.errors && /Something went wrong while executing your query/.test( + response.data.errors[0].message + )) { + const error = new import_request_error.RequestError(response.data.errors[0].message, 500, { request: options, response }); @@ -48103,14 +47876,18 @@ async function requestWithGraphqlErrorHandling(state, octokit, request, options) return response; } -const VERSION = "4.1.3"; +// pkg/dist-src/index.js +var VERSION = "4.1.6"; function retry(octokit, octokitOptions) { - const state = Object.assign({ - enabled: true, - retryAfterBaseValue: 1000, - doNotRetry: [400, 401, 403, 404, 422], - retries: 3 - }, octokitOptions.retry); + const state = Object.assign( + { + enabled: true, + retryAfterBaseValue: 1e3, + doNotRetry: [400, 401, 403, 404, 422], + retries: 3 + }, + octokitOptions.retry + ); if (state.enabled) { octokit.hook.error("request", errorRequest.bind(null, state, octokit)); octokit.hook.wrap("request", wrapRequest.bind(null, state, octokit)); @@ -48119,8 +47896,8 @@ function retry(octokit, octokitOptions) { retry: { retryRequest: (error, retries, retryAfter) => { error.request.request = Object.assign({}, error.request.request, { - retries: retries, - retryAfter: retryAfter + retries, + retryAfter }); return error; } @@ -48128,10 +47905,8 @@ function retry(octokit, octokitOptions) { }; } retry.VERSION = VERSION; - -exports.VERSION = VERSION; -exports.retry = retry; -//# sourceMappingURL=index.js.map +// Annotate the CommonJS export names for ESM import in node: +0 && (0); /***/ }), @@ -51731,6 +51506,9 @@ function range(a, b, str) { var i = ai; if (ai >= 0 && bi > 0) { + if(a===b) { + return [ai, bi]; + } begs = []; left = str.length; @@ -54058,6 +53836,22 @@ class Deprecation extends Error { exports.Deprecation = Deprecation; +/***/ }), + +/***/ 1621: +/***/ ((module) => { + +"use strict"; + + +module.exports = (flag, argv = process.argv) => { + const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--'); + const position = argv.indexOf(prefix + flag); + const terminatorPosition = argv.indexOf('--'); + return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition); +}; + + /***/ }), /***/ 3287: @@ -58955,129 +58749,14 @@ function onceStrict (fn) { /***/ }), -/***/ 8824: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - -const supportsColor = __nccwpck_require__(1507); -const hasFlag = __nccwpck_require__(7362); - -function parseVersion(versionString) { - if (/^\d{3,4}$/.test(versionString)) { - // Env var doesn't always use dots. example: 4601 => 46.1.0 - const m = /(\d{1,2})(\d{2})/.exec(versionString); - return { - major: 0, - minor: parseInt(m[1], 10), - patch: parseInt(m[2], 10) - }; - } - - const versions = (versionString || '').split('.').map(n => parseInt(n, 10)); - return { - major: versions[0], - minor: versions[1], - patch: versions[2] - }; -} - -function supportsHyperlink(stream) { - const {env} = process; - - if ('FORCE_HYPERLINK' in env) { - return !(env.FORCE_HYPERLINK.length > 0 && parseInt(env.FORCE_HYPERLINK, 10) === 0); - } - - if (hasFlag('no-hyperlink') || hasFlag('no-hyperlinks') || hasFlag('hyperlink=false') || hasFlag('hyperlink=never')) { - return false; - } - - if (hasFlag('hyperlink=true') || hasFlag('hyperlink=always')) { - return true; - } - - // If they specify no colors, they probably don't want hyperlinks. - if (!supportsColor.supportsColor(stream)) { - return false; - } - - if (stream && !stream.isTTY) { - return false; - } - - if (process.platform === 'win32') { - return false; - } - - if ('CI' in env) { - return false; - } - - if ('TEAMCITY_VERSION' in env) { - return false; - } - - if ('TERM_PROGRAM' in env) { - const version = parseVersion(env.TERM_PROGRAM_VERSION); - - switch (env.TERM_PROGRAM) { - case 'iTerm.app': - if (version.major === 3) { - return version.minor >= 1; - } - - return version.major > 3; - // No default - } - } - - if ('VTE_VERSION' in env) { - // 0.50.0 was supposed to support hyperlinks, but throws a segfault - if (env.VTE_VERSION === '0.50.0') { - return false; - } - - const version = parseVersion(env.VTE_VERSION); - return version.major > 0 || version.minor >= 50; - } - - return false; -} - -module.exports = { - supportsHyperlink, - stdout: supportsHyperlink(process.stdout), - stderr: supportsHyperlink(process.stderr) -}; - - -/***/ }), - -/***/ 7362: -/***/ ((module) => { - -"use strict"; - - -module.exports = (flag, argv = process.argv) => { - const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--'); - const position = argv.indexOf(prefix + flag); - const terminatorPosition = argv.indexOf('--'); - return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition); -}; - - -/***/ }), - -/***/ 1507: +/***/ 9318: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { "use strict"; const os = __nccwpck_require__(2037); const tty = __nccwpck_require__(6224); -const hasFlag = __nccwpck_require__(7362); +const hasFlag = __nccwpck_require__(1621); const {env} = process; @@ -59211,6 +58890,114 @@ module.exports = { }; +/***/ }), + +/***/ 8824: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + +const supportsColor = __nccwpck_require__(9318); +const hasFlag = __nccwpck_require__(1621); + +function parseVersion(versionString) { + if (/^\d{3,4}$/.test(versionString)) { + // Env var doesn't always use dots. example: 4601 => 46.1.0 + const m = /(\d{1,2})(\d{2})/.exec(versionString); + return { + major: 0, + minor: parseInt(m[1], 10), + patch: parseInt(m[2], 10) + }; + } + + const versions = (versionString || '').split('.').map(n => parseInt(n, 10)); + return { + major: versions[0], + minor: versions[1], + patch: versions[2] + }; +} + +function supportsHyperlink(stream) { + const {env} = process; + + if ('FORCE_HYPERLINK' in env) { + return !(env.FORCE_HYPERLINK.length > 0 && parseInt(env.FORCE_HYPERLINK, 10) === 0); + } + + if (hasFlag('no-hyperlink') || hasFlag('no-hyperlinks') || hasFlag('hyperlink=false') || hasFlag('hyperlink=never')) { + return false; + } + + if (hasFlag('hyperlink=true') || hasFlag('hyperlink=always')) { + return true; + } + + // Netlify does not run a TTY, it does not need `supportsColor` check + if ('NETLIFY' in env) { + return true; + } + + // If they specify no colors, they probably don't want hyperlinks. + if (!supportsColor.supportsColor(stream)) { + return false; + } + + if (stream && !stream.isTTY) { + return false; + } + + if (process.platform === 'win32') { + return false; + } + + if ('CI' in env) { + return false; + } + + if ('TEAMCITY_VERSION' in env) { + return false; + } + + if ('TERM_PROGRAM' in env) { + const version = parseVersion(env.TERM_PROGRAM_VERSION); + + switch (env.TERM_PROGRAM) { + case 'iTerm.app': + if (version.major === 3) { + return version.minor >= 1; + } + + return version.major > 3; + case 'WezTerm': + return version.major >= 20200620; + case 'vscode': + return version.major > 1 || version.major === 1 && version.minor >= 72; + // No default + } + } + + if ('VTE_VERSION' in env) { + // 0.50.0 was supposed to support hyperlinks, but throws a segfault + if (env.VTE_VERSION === '0.50.0') { + return false; + } + + const version = parseVersion(env.VTE_VERSION); + return version.major > 0 || version.minor >= 50; + } + + return false; +} + +module.exports = { + supportsHyperlink, + stdout: supportsHyperlink(process.stdout), + stderr: supportsHyperlink(process.stderr) +}; + + /***/ }), /***/ 1898: diff --git a/src/classes/actions-cache/http-responses.ts b/src/classes/actions-cache/http-responses.ts new file mode 100644 index 000000000..20ce4baa0 --- /dev/null +++ b/src/classes/actions-cache/http-responses.ts @@ -0,0 +1,19 @@ +import {TypedResponse} from '@actions/http-client/lib/interfaces'; +import {HttpClientError} from '@actions/http-client'; + +export const isSuccessStatusCode = (statusCode?: number): boolean => { + if (!statusCode) { + return false; + } + return statusCode >= 200 && statusCode < 300; +}; +export function isServerErrorStatusCode(statusCode?: number): boolean { + if (!statusCode) { + return true; + } + return statusCode >= 500; +} + +export interface TypedResponseWithError extends TypedResponse { + error?: HttpClientError; +} diff --git a/src/classes/actions-cache/index.ts b/src/classes/actions-cache/index.ts new file mode 100644 index 000000000..8155a6493 --- /dev/null +++ b/src/classes/actions-cache/index.ts @@ -0,0 +1,234 @@ +import * as core from '@actions/core'; +import fs from 'fs'; +import {HttpClient, HttpClientResponse} from '@actions/http-client'; +import {BearerCredentialHandler} from '@actions/http-client/lib/auth'; +import { + RequestOptions, + TypedResponse +} from '@actions/http-client/lib/interfaces'; +import {ReserveCacheError, ValidationError} from '@actions/cache'; +import {isSuccessStatusCode, TypedResponseWithError} from './http-responses'; +import {retryHttpClientResponse, retryTypedResponse} from './retry'; +import {IncomingMessage} from 'http'; +import {getOctokit} from '@actions/github'; +import {retry} from '@octokit/plugin-retry'; + +const uploadChunk = async ( + httpClient: HttpClient, + resourceUrl: string, + openStream: () => NodeJS.ReadableStream, + start: number, + end: number +): Promise => { + // Format: `bytes start-end/filesize + // start and end are inclusive + // filesize can be * + // For a 200 byte chunk starting at byte 0: + // Content-Range: bytes 0-199/* + const contentRange = `bytes ${start}-${end}/*`; + core.debug( + `Uploading chunk of size ${ + end - start + 1 + } bytes at offset ${start} with content range: ${contentRange}` + ); + const additionalHeaders = { + 'Content-Type': 'application/octet-stream', + 'Content-Range': contentRange + }; + + const uploadChunkResponse = await retryHttpClientResponse( + `uploadChunk (start: ${start}, end: ${end})`, + async () => + httpClient.sendStream( + 'PATCH', + resourceUrl, + openStream(), + additionalHeaders + ) + ); + + if (!isSuccessStatusCode(uploadChunkResponse.message.statusCode)) { + throw new Error( + `Cache service responded with ${uploadChunkResponse.message.statusCode} during upload chunk.` + ); + } +}; + +const getCacheApiUrl = (resource: string): string => { + const baseUrl: string = process.env['ACTIONS_CACHE_URL'] || ''; + if (!baseUrl) { + throw new Error('Cache Service Url not found, unable to restore cache.'); + } + + const url = `${baseUrl}_apis/artifactcache/${resource}`; + core.debug(`Resource Url: ${url}`); + return url; +}; + +const createAcceptHeader = (type: string, apiVersion: string): string => + `${type};api-version=${apiVersion}`; + +const getRequestOptions = (): RequestOptions => ({ + headers: { + Accept: createAcceptHeader('application/json', '6.0-preview.1') + } +}); + +const createHttpClient = (): HttpClient => { + const token = process.env['ACTIONS_RUNTIME_TOKEN'] || ''; + const bearerCredentialHandler = new BearerCredentialHandler(token); + + return new HttpClient( + 'actions/cache', + [bearerCredentialHandler], + getRequestOptions() + ); +}; + +const uploadFile = async ( + httpClient: HttpClient, + cacheId: number, + filePath: string, + fileSize: number +): Promise => { + // Upload Chunks + const resourceUrl = getCacheApiUrl(`caches/${cacheId.toString()}`); + const fd = fs.openSync(filePath, 'r'); + + try { + const chunkSize = fileSize; + const start = 0; + const end = chunkSize - 1; + + await uploadChunk( + httpClient, + resourceUrl, + () => + fs + .createReadStream(filePath, { + fd, + start, + end, + autoClose: false + }) + .on('error', error => { + throw new Error( + `Cache upload failed because file read failed with ${error.message}` + ); + }), + start, + end + ); + } finally { + fs.closeSync(fd); + } + return; +}; + +const CACHE_KEY = '_state'; + +const resetCacheWithOctokit = async (): Promise => { + const token = core.getInput('repo-token'); + const client = getOctokit(token, undefined, retry); + const repo = process.env['GITHUB_REPOSITORY']; + const result = await client.request( + `DELETE /repos/${repo}/actions/caches?key=${CACHE_KEY}` + ); + console.log('============> delete'); + console.log(result); +}; + +const resetCache = async (httpClient: HttpClient): Promise => { + await retryTypedResponse('resetCache', async () => { + const result = await httpClient.del( + getCacheApiUrl(`caches?key=${CACHE_KEY}`) + ); + console.log('============> delete'); + console.log(result.message); + return result.message as unknown as TypedResponseWithError; + }); +}; +interface ReserveCacheResponse { + cacheId: number; +} + +const reserveCache = async ( + httpClient: HttpClient, + fileSize: number +): Promise => { + const reserveCacheRequest = { + key: CACHE_KEY, + version: new Date().getDate().toString(), + cacheSize: fileSize + }; + const response = (await retryTypedResponse('reserveCache', async () => + httpClient.postJson( + getCacheApiUrl('caches'), + reserveCacheRequest + ) + )) as TypedResponseWithError; + + if (response?.statusCode === 400) + throw new Error( + response?.error?.message ?? + `Cache size of ~${Math.round( + fileSize / (1024 * 1024) + )} MB (${fileSize} B) is over the data cap limit, not saving cache.` + ); + + const cacheId = response?.result?.cacheId; + + if (cacheId === undefined) + throw new ReserveCacheError( + `Unable to reserve cache with key ${CACHE_KEY}, another job may be creating this cache. More details: ${response?.error?.message}` + ); + return cacheId; +}; + +interface CommitCacheRequest { + size: number; +} + +const commitCache = async ( + httpClient: HttpClient, + cacheId: number, + filesize: number +): Promise => { + const commitCacheRequest: CommitCacheRequest = {size: filesize}; + const response = (await retryTypedResponse('commitCache', async () => + httpClient.postJson( + getCacheApiUrl(`caches/${cacheId.toString()}`), + commitCacheRequest + ) + )) as TypedResponse; + if (!isSuccessStatusCode(response.statusCode)) { + throw new Error( + `Cache service responded with ${response.statusCode} during commit cache.` + ); + } +}; + +export const saveFileAsActionsCache = async (filePath: string) => { + try { + await resetCacheWithOctokit(); + const httpClient = createHttpClient(); + + // await resetCache(httpClient); + + const fileSize = fs.statSync(filePath).size; + const cacheId = await reserveCache(httpClient, fileSize); + + await uploadFile(httpClient, cacheId, filePath, fileSize); + + await commitCache(httpClient, cacheId, fileSize); + } catch (error) { + const typedError = error as Error; + if (typedError.name === ValidationError.name) { + throw error; + } else if (typedError.name === ReserveCacheError.name) { + core.info(`Failed to save: ${typedError.message}`); + } else { + core.warning(`Failed to save: ${typedError.message}`); + } + } +}; diff --git a/src/classes/actions-cache/retry.ts b/src/classes/actions-cache/retry.ts new file mode 100644 index 000000000..8efaacf9f --- /dev/null +++ b/src/classes/actions-cache/retry.ts @@ -0,0 +1,127 @@ +import { + HttpClientError, + HttpClientResponse, + HttpCodes +} from '@actions/http-client'; +import { + isServerErrorStatusCode, + TypedResponseWithError +} from './http-responses'; +import * as core from '@actions/core'; + +const isRetryableStatusCode = (statusCode?: number): boolean => { + if (!statusCode) { + return false; + } + const retryableStatusCodes = [ + HttpCodes.BadGateway, + HttpCodes.ServiceUnavailable, + HttpCodes.GatewayTimeout + ]; + return retryableStatusCodes.includes(statusCode); +}; + +const sleep = (milliseconds: number): Promise => + new Promise(resolve => setTimeout(resolve, milliseconds)); +// The default number of retry attempts. +const DefaultRetryAttempts = 2; +// The default delay in milliseconds between retry attempts. +const DefaultRetryDelay = 5000; + +const retry = async ( + name: string, + method: () => Promise, + getStatusCode: (arg0: T) => number | undefined, + maxAttempts = DefaultRetryAttempts, + delay = DefaultRetryDelay, + onError: ((arg0: Error) => T | undefined) | undefined = undefined +): Promise => { + let errorMessage = ''; + let attempt = 1; + + while (attempt <= maxAttempts) { + let response: T | undefined = undefined; + let statusCode: number | undefined = undefined; + let isRetryable = false; + + try { + response = await method(); + } catch (error) { + if (onError) { + response = onError(error); + } + + isRetryable = true; + errorMessage = error.message; + } + + if (response) { + statusCode = getStatusCode(response); + + if (!isServerErrorStatusCode(statusCode)) { + return response; + } + } + + if (statusCode) { + isRetryable = isRetryableStatusCode(statusCode); + errorMessage = `Cache service responded with ${statusCode}`; + } + + core.debug( + `${name} - Attempt ${attempt} of ${maxAttempts} failed with error: ${errorMessage}` + ); + + if (!isRetryable) { + core.debug(`${name} - Error is not retryable`); + break; + } + + await sleep(delay); + attempt++; + } + + throw Error(`${name} failed: ${errorMessage}`); +}; + +export const retryHttpClientResponse = async ( + name: string, + method: () => Promise, + maxAttempts = DefaultRetryAttempts, + delay = DefaultRetryDelay +): Promise => { + return await retry( + name, + method, + (response: HttpClientResponse) => response.message.statusCode, + maxAttempts, + delay + ); +}; +export const retryTypedResponse = ( + name: string, + method: () => Promise>, + maxAttempts = DefaultRetryAttempts, + delay = DefaultRetryDelay +): Promise> => + retry( + name, + method, + (response: TypedResponseWithError) => response.statusCode, + maxAttempts, + delay, + // If the error object contains the statusCode property, extract it and return + // an TypedResponse so it can be processed by the retry logic. + (error: Error) => { + if (error instanceof HttpClientError) { + return { + statusCode: error.statusCode, + result: null, + headers: {}, + error + }; + } else { + return undefined; + } + } + ); diff --git a/src/classes/issue.ts b/src/classes/issue.ts index b90631835..e754beb27 100644 --- a/src/classes/issue.ts +++ b/src/classes/issue.ts @@ -27,7 +27,7 @@ export class Issue implements IIssue { constructor( options: Readonly, - issue: Readonly | Readonly + issue: Readonly // | Readonly ) { this._options = options; this.title = issue.title; diff --git a/src/classes/issues-processor.ts b/src/classes/issues-processor.ts index eaade3892..af383dfb8 100644 --- a/src/classes/issues-processor.ts +++ b/src/classes/issues-processor.ts @@ -574,9 +574,11 @@ export class IssuesProcessor { }); this.statistics?.incrementFetchedItemsCount(issueResult.data.length); - return issueResult.data.map( - (issue: Readonly): Issue => new Issue(this.options, issue) - ); + // state_reason is incompatible - oktokit dependency conflict? + // return issueResult.data.map((issue: Readonly): Issue => { + return issueResult.data.map((issue): Issue => { + return new Issue(this.options, issue as Readonly); + }); } catch (error) { throw Error(`Getting issues was blocked by the error: ${error.message}`); } diff --git a/src/classes/state.ts b/src/classes/state.ts index ec03d8b2a..da0d8e7b0 100644 --- a/src/classes/state.ts +++ b/src/classes/state.ts @@ -1,23 +1,24 @@ import {Issue} from './issue'; import {IState} from '../interfaces/state'; import os from 'os'; -import crypto from 'crypto'; import fs from 'fs'; import path from 'path'; -// import * as artifact from '@actions/artifact'; import * as core from '@actions/core'; -import {restoreCache, saveCache} from '@actions/cache'; +import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options'; +import {saveFileAsActionsCache} from './actions-cache'; type IssueID = number; export class State implements IState { + private readonly options: IIssuesProcessorOptions; private processedIssuesIDs: Set; /** * @private don't mutate in the debug mode */ private readonly debug: boolean; - constructor() { + constructor(options: IIssuesProcessorOptions) { + this.options = options; this.processedIssuesIDs = new Set(); - this.debug = core.getInput('debug-only') === 'true'; + this.debug = options.debugOnly; } isIssueProcessed(issue: Issue) { @@ -42,7 +43,10 @@ export class State implements IState { const serialized = Array.from(this.processedIssuesIDs).join('|'); - const tmpDir = os.tmpdir(); + // const tmpDir = path.join(os.tmpdir(),crypto.randomBytes(8).readBigUInt64LE(0).toString()); + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'state-')); + // const tmpDir = path.join(os.tmpdir(), 'state'); + // fs.mkdirSync(tmpDir, {recursive: true}); const file = path.join( tmpDir, State.STATE_FILE @@ -51,12 +55,10 @@ export class State implements IState { fs.writeFileSync(file, serialized); core.debug( - `Persisting state includes info about ${this.processedIssuesIDs.size} issue(s)` + `Persisting state includes info about ${this.processedIssuesIDs.size} issue(s) --> ${serialized}` ); - // const artifactClient = artifact.create(); try { - saveCache([tmpDir], State.ARTIFACT_NAME); - // await artifactClient.uploadArtifact(State.ARTIFACT_NAME, [file], tmpDir); + await saveFileAsActionsCache(file); } catch (error) { core.warning( `Persisting the state was not successful due to "${ @@ -69,7 +71,10 @@ export class State implements IState { async rehydrate() { this.reset(); - const tmpDir = os.tmpdir(); + // const tmpDir = os.mkdtemp() os.tmpdir(); + // const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'state-')); + const tmpDir = path.join(os.tmpdir(), 'state'); + fs.mkdirSync(tmpDir, {recursive: true}); // const artifactClient = artifact.create(); try { /* @@ -78,7 +83,7 @@ export class State implements IState { tmpDir ); */ - await restoreCache([tmpDir], State.ARTIFACT_NAME); + // await restoreCache([tmpDir], State.ARTIFACT_NAME); /* const downloadedFiles = fs.readdirSync(downloadResponse.downloadPath); @@ -88,21 +93,29 @@ export class State implements IState { ); } */ - const serialized = fs.readFileSync( - path.join(tmpDir, State.ARTIFACT_NAME), - {encoding: 'utf8'} - ); + if (!fs.existsSync(path.join(tmpDir, State.STATE_FILE))) { + throw Error( + 'There is no state persisted, probably because of the very first run or previous run failed' + ); + } + const serialized = fs.readFileSync(path.join(tmpDir, State.STATE_FILE), { + encoding: 'utf8' + }); if (serialized.length === 0) return; const issueIDs = serialized .split('|') - .map(parseInt) + .map(id => parseInt(id)) .filter(i => !isNaN(i)); this.processedIssuesIDs = new Set(issueIDs); core.debug( - `Rehydrated state includes info about ${issueIDs.length} issue(s)` + `Rehydrated state includes info about ${ + issueIDs.length + } issue(s) <-- ${serialized}, ${JSON.stringify( + issueIDs + )}-${JSON.stringify(serialized.split('|'))}` ); } catch (error) { core.warning( diff --git a/src/main.ts b/src/main.ts index e17c680f0..cb32fa08d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,7 +9,7 @@ async function _run(): Promise { try { const args = _getAndValidateArgs(); - const state = StateService.getState(); + const state = StateService.getState(args); await state.rehydrate(); const issueProcessor: IssuesProcessor = new IssuesProcessor(args, state); diff --git a/src/services/state.service.ts b/src/services/state.service.ts index 00ab95466..a2b303c33 100644 --- a/src/services/state.service.ts +++ b/src/services/state.service.ts @@ -1,8 +1,9 @@ import {IState} from '../interfaces/state'; import {State} from '../classes/state'; +import {IIssuesProcessorOptions} from '../interfaces/issues-processor-options'; export class StateService { - static getState(): IState { - return new State(); + static getState(options: IIssuesProcessorOptions): IState { + return new State(options); } }