From 1c5c1375b3817ad821719597effe8e3d6f764930 Mon Sep 17 00:00:00 2001 From: eric sciple Date: Wed, 5 Feb 2020 11:20:33 -0500 Subject: [PATCH] consume getJson function for better error messages (#110) --- .github/workflows/workflow.yml | 1 - __tests__/verify-no-unstaged-changes.sh | 2 +- dist/index.js | 121 +++++++++++++++++++++++- package-lock.json | 26 ++--- package.json | 7 +- src/installer.ts | 9 +- 6 files changed, 135 insertions(+), 31 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 623a4831..945b0541 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -22,7 +22,6 @@ jobs: - run: npm ci - run: npm run build - run: npm run format-check - - run: npm run pack - run: npm test - name: Verify no unstaged changes if: runner.os != 'windows' diff --git a/__tests__/verify-no-unstaged-changes.sh b/__tests__/verify-no-unstaged-changes.sh index 9fe6173a..f3260e3b 100755 --- a/__tests__/verify-no-unstaged-changes.sh +++ b/__tests__/verify-no-unstaged-changes.sh @@ -12,6 +12,6 @@ if [[ "$(git status --porcelain)" != "" ]]; then echo ---------------------------------------- echo Troubleshooting echo ---------------------------------------- - echo "::error::Unstaged changes detected. Locally try running: git clean -ffdx && npm ci && npm run all" + echo "::error::Unstaged changes detected. Locally try running: git clean -ffdx && npm ci && npm run pre-checkin" exit 1 fi diff --git a/dist/index.js b/dist/index.js index dee34cfa..65782bda 100644 --- a/dist/index.js +++ b/dist/index.js @@ -10602,6 +10602,15 @@ var HttpCodes; 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 @@ -10700,6 +10709,36 @@ class HttpClient { sendStream(verb, requestUrl, stream, additionalHeaders) { 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 + */ + async getJson(requestUrl, additionalHeaders = {}) { + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + let res = await this.get(requestUrl, additionalHeaders); + return this._processResponse(res, this.requestOptions); + } + async postJson(requestUrl, obj, additionalHeaders = {}) { + let 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); + let res = await this.post(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + } + async putJson(requestUrl, obj, additionalHeaders = {}) { + let 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); + let res = await this.put(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + } + async patchJson(requestUrl, obj, additionalHeaders = {}) { + let 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); + let res = await 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. @@ -10883,6 +10922,14 @@ class HttpClient { } return lowercaseKeys(headers || {}); } + _getExistingOrDefaultHeader(additionalHeaders, header, _default) { + const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => (c[k.toLowerCase()] = obj[k], c), {}); + let clientHeader; + if (this.requestOptions && this.requestOptions.headers) { + clientHeader = lowercaseKeys(this.requestOptions.headers)[header]; + } + return additionalHeaders[header] || clientHeader || _default; + } _getAgent(parsedUrl) { let agent; let proxyUrl = pm.getProxyUrl(parsedUrl); @@ -10950,6 +10997,73 @@ class HttpClient { const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber); return new Promise(resolve => setTimeout(() => resolve(), ms)); } + static dateTimeDeserializer(key, value) { + if (typeof value === 'string') { + let a = new Date(value); + if (!isNaN(a.valueOf())) { + return a; + } + } + return value; + } + async _processResponse(res, options) { + return new Promise(async (resolve, reject) => { + const statusCode = res.message.statusCode; + const response = { + statusCode: statusCode, + result: null, + headers: {} + }; + // not found leads to null obj returned + if (statusCode == HttpCodes.NotFound) { + resolve(response); + } + let obj; + let contents; + // get the result from the body + try { + contents = await res.readBody(); + if (contents && contents.length > 0) { + if (options && options.deserializeDates) { + obj = JSON.parse(contents, HttpClient.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 + ")"; + } + let err = new Error(msg); + // attach statusCode and body obj (if available) to the error object + err['statusCode'] = statusCode; + if (response.result) { + err['result'] = response.result; + } + reject(err); + } + else { + resolve(response); + } + }); + } } exports.HttpClient = HttpClient; @@ -11979,7 +12093,6 @@ var __importStar = (this && this.__importStar) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); // Load tempDirectory before it gets wiped by tool-cache let tempDirectory = process.env['RUNNER_TEMPDIRECTORY'] || ''; -const assert = __importStar(__webpack_require__(357)); const core = __importStar(__webpack_require__(470)); const hc = __importStar(__webpack_require__(539)); const io = __importStar(__webpack_require__(1)); @@ -12070,10 +12183,8 @@ function queryLatestMatch(versionSpec) { allowRetries: true, maxRetries: 3 }); - let response = yield httpClient.get(dataUrl); - assert.ok(response.message.statusCode === 200, `Unexpected HTTP status code '${response.message.statusCode}'`); - let body = yield response.readBody(); - let nodeVersions = JSON.parse(body); + let response = yield httpClient.getJson(dataUrl); + let nodeVersions = response.result || []; nodeVersions.forEach((nodeVersion) => { // ensure this version supports your os and platform if (nodeVersion.files.indexOf(dataFileName) >= 0) { diff --git a/package-lock.json b/package-lock.json index cfcd232b..29fbac4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,9 +27,9 @@ } }, "@actions/http-client": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.3.tgz", - "integrity": "sha512-wFwh1U4adB/Zsk4cc9kVqaBOHoknhp/pJQk+aWTocbAZWpIl4Zx/At83WFRLXvxB+5HVTWOACM6qjULMZfQSfw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.6.tgz", + "integrity": "sha512-LGmio4w98UyGX33b/W6V6Nx/sQHRXZ859YlMkn36wPsXPB82u8xTVlA/Dq2DXrm6lEq9RVmisRJa1c+HETAIJA==", "requires": { "tunnel": "0.0.6" } @@ -1258,9 +1258,9 @@ } }, "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, "optional": true }, @@ -2450,9 +2450,9 @@ "dev": true }, "handlebars": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", - "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.3.tgz", + "integrity": "sha512-SRGwSYuNfx8DwHD/6InAPzD6RgeruWLT+B8e8a7gGs8FWgHzlExpTFMEq2IA6QpAfOClpKHy6+8IqTjeBCu6Kg==", "dev": true, "requires": { "neo-async": "^2.6.0", @@ -5085,13 +5085,13 @@ "dev": true }, "uglify-js": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", - "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.7.tgz", + "integrity": "sha512-FeSU+hi7ULYy6mn8PKio/tXsdSXN35lm4KgV2asx00kzrLU9Pi3oAslcJT70Jdj7PHX29gGUPOT6+lXGBbemhA==", "dev": true, "optional": true, "requires": { - "commander": "~2.20.0", + "commander": "~2.20.3", "source-map": "~0.6.1" } }, diff --git a/package.json b/package.json index c73d9f35..fd0541fe 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,11 @@ "description": "setup node action", "main": "lib/setup-node.js", "scripts": { - "build": "tsc", + "build": "tsc && ncc build", "format": "prettier --write **/*.ts", "format-check": "prettier --check **/*.ts", - "pack": "ncc build", "test": "jest", - "all": "npm run build && npm run format && npm run pack && npm test" + "pre-checkin": "npm run format && npm run build && npm test" }, "repository": { "type": "git", @@ -26,7 +25,7 @@ "dependencies": { "@actions/core": "^1.2.2", "@actions/github": "^1.1.0", - "@actions/http-client": "^1.0.3", + "@actions/http-client": "^1.0.6", "@actions/io": "^1.0.2", "@actions/tool-cache": "^1.3.1", "semver": "^6.1.1" diff --git a/src/installer.ts b/src/installer.ts index ebaec403..25c908a7 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -105,13 +105,8 @@ async function queryLatestMatch(versionSpec: string): Promise { allowRetries: true, maxRetries: 3 }); - let response = await httpClient.get(dataUrl); - assert.ok( - response.message.statusCode === 200, - `Unexpected HTTP status code '${response.message.statusCode}'` - ); - let body = await response.readBody(); - let nodeVersions = JSON.parse(body) as INodeVersion[]; + let response = await httpClient.getJson(dataUrl); + let nodeVersions = response.result || []; nodeVersions.forEach((nodeVersion: INodeVersion) => { // ensure this version supports your os and platform if (nodeVersion.files.indexOf(dataFileName) >= 0) {