From 3fdcc138f38897590d7f6d83163b4957bfd0f7f8 Mon Sep 17 00:00:00 2001 From: Luke Watts Date: Tue, 9 Apr 2024 12:33:55 +0200 Subject: [PATCH] fix: support declaring version (#193) --- README.md | 2 +- package-lock.json | 118 ++++++++++++++---- package.json | 5 +- snykTask/src/__tests__/install/index.test.ts | 122 +++++++++++++++++-- snykTask/src/index.ts | 7 +- snykTask/src/install/index.ts | 47 ++++--- snykTask/src/lib/sanitize-version-input.ts | 16 +++ snykTask/src/task-args.ts | 19 +-- snykTask/src/types.ts | 1 - snykTask/task.json | 10 +- snykTask/tsconfig.json | 3 +- 11 files changed, 278 insertions(+), 72 deletions(-) create mode 100644 snykTask/src/lib/sanitize-version-input.ts delete mode 100644 snykTask/src/types.ts diff --git a/README.md b/README.md index 4bc62b84..15c2ac1a 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ This extension requires that Node.js and npm be installed on the build agent. Th | testDirectory | Alternate working directory. For example, if you want to test a manifest file in a directory other than the root of your repo, you would put in relative path to that directory. | no | none | string | | ignoreUnknownCA | Use to ignore unknown or self-signed certificates. This might be useful in for self-hosted build agents with unusual network configurations or for Snyk on-prem installs configured with a self-signed certificate. | no | false | boolean | | additionalArguments | Additional Snyk CLI arguments to be passed in. Refer to the Snyk CLI help page for information on additional arguments. | no | none | string | -| distributionChannel | Select distribution channel for Snyk binaries. 'Stable' is for stable releases, whilst 'Preview' is for access to the latest features. | no | Stable | string | +| distributionChannel | Declare version for Snyk binaries. 'Stable' is for the current stable releases, whilst 'Preview' is for access to the latest features. You can also declare a specific version such as '1.1287.0' | no | Stable | string | ## Usage Examples diff --git a/package-lock.json b/package-lock.json index 5603c4a2..ffbf4954 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "azure-pipelines-task-lib": "^3.4.0", "jquery": "^3.4.1", + "semver": "^7.6.0", "vss-web-extension-sdk": "^5.141.0" }, "devDependencies": { @@ -29,11 +30,13 @@ "fs-extra": "^9.1.0", "jest": "^26.4.2", "mock-fs": "^4.10.4", + "nock": "^13.5.4", "prettier": "^2.3.1", "semantic-release": "^17.0.4", "tfx-cli": "^0.7.11", "ts-jest": "^26.3.0", - "typescript": "^4.5.2" + "typescript": "^4.5.2", + "uuid": "^9.0.1" } }, "node_modules/@ampproject/remapping": { @@ -2715,6 +2718,15 @@ "semver": "bin/semver" } }, + "node_modules/azure-pipelines-task-lib/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/babel-jest": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", @@ -9552,6 +9564,20 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node_modules/nock": { + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.4.tgz", + "integrity": "sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">= 10.13" + } + }, "node_modules/node-emoji": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", @@ -13399,6 +13425,15 @@ "node": ">= 6" } }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -14472,10 +14507,9 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -14523,7 +14557,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -14534,8 +14567,7 @@ "node_modules/semver/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/set-blocking": { "version": "2.0.0", @@ -15695,6 +15727,16 @@ "node": ">=0.4.0" } }, + "node_modules/tfx-cli/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/then-request": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", @@ -16447,12 +16489,16 @@ "dev": true }, "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { - "uuid": "bin/uuid" + "uuid": "dist/bin/uuid" } }, "node_modules/v8-compile-cache": { @@ -19034,6 +19080,11 @@ "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" } } }, @@ -24250,6 +24301,17 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "nock": { + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.4.tgz", + "integrity": "sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + } + }, "node-emoji": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", @@ -27039,6 +27101,12 @@ "sisteransi": "^1.0.5" } }, + "propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -27855,10 +27923,9 @@ } }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "requires": { "lru-cache": "^6.0.0" }, @@ -27867,7 +27934,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "requires": { "yallist": "^4.0.0" } @@ -27875,8 +27941,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } }, @@ -28832,6 +28897,12 @@ "requires": { "os-tmpdir": "~1.0.0" } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true } } }, @@ -29424,9 +29495,10 @@ } }, "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true }, "v8-compile-cache": { "version": "2.4.0", diff --git a/package.json b/package.json index 10808aec..fe50ca8a 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "dependencies": { "azure-pipelines-task-lib": "^3.4.0", "jquery": "^3.4.1", + "semver": "^7.6.0", "vss-web-extension-sdk": "^5.141.0" }, "devDependencies": { @@ -46,10 +47,12 @@ "fs-extra": "^9.1.0", "jest": "^26.4.2", "mock-fs": "^4.10.4", + "nock": "^13.5.4", "prettier": "^2.3.1", "semantic-release": "^17.0.4", "tfx-cli": "^0.7.11", "ts-jest": "^26.3.0", - "typescript": "^4.5.2" + "typescript": "^4.5.2", + "uuid": "^9.0.1" } } diff --git a/snykTask/src/__tests__/install/index.test.ts b/snykTask/src/__tests__/install/index.test.ts index 1a2ebf51..16b43288 100644 --- a/snykTask/src/__tests__/install/index.test.ts +++ b/snykTask/src/__tests__/install/index.test.ts @@ -14,8 +14,12 @@ * limitations under the License. */ -import { getSnykDownloadInfo } from '../../install'; +import { downloadExecutable, getSnykDownloadInfo } from '../../install'; import { Platform } from 'azure-pipelines-task-lib/task'; +import * as nock from 'nock'; +import * as os from 'os'; +import * as path from 'path'; +import * as uuid from 'uuid/v4'; describe('getSnykDownloadInfo', () => { it('retrieves the correct download info for Linux', () => { @@ -64,7 +68,7 @@ describe('getSnykDownloadInfo', () => { }); it('retrieves the correct download info a preview release', () => { - const dlInfo = getSnykDownloadInfo(Platform.MacOS, 'preview'); + const dlInfo = getSnykDownloadInfo(Platform.MacOS, 'preview '); expect(dlInfo).toEqual({ snyk: { filename: 'snyk-macos', @@ -78,11 +82,38 @@ describe('getSnykDownloadInfo', () => { }); }); - it('ignores invalid distribution channels', () => { - const dlInfo = getSnykDownloadInfo( - Platform.MacOS, - 'invalid-channel' as any, - ); + it('retrieves the correct download info for a valid semver', () => { + const dlInfo = getSnykDownloadInfo(Platform.MacOS, '1.1287.0'); + expect(dlInfo).toEqual({ + snyk: { + filename: 'snyk-macos', + downloadUrl: 'https://static.snyk.io/cli/v1.1287.0/snyk-macos', + }, + snykToHtml: { + filename: 'snyk-to-html-macos', + downloadUrl: + 'https://static.snyk.io/snyk-to-html/latest/snyk-to-html-macos', + }, + }); + }); + + it('retrieves the correct download info for a valid semver and sanitizes input', () => { + const dlInfo = getSnykDownloadInfo(Platform.MacOS, 'v1.1287.0 '); + expect(dlInfo).toEqual({ + snyk: { + filename: 'snyk-macos', + downloadUrl: 'https://static.snyk.io/cli/v1.1287.0/snyk-macos', + }, + snykToHtml: { + filename: 'snyk-to-html-macos', + downloadUrl: + 'https://static.snyk.io/snyk-to-html/latest/snyk-to-html-macos', + }, + }); + }); + + it('ignores invalid versions', () => { + const dlInfo = getSnykDownloadInfo(Platform.MacOS, 'invalid-channel'); expect(dlInfo).toEqual({ snyk: { filename: 'snyk-macos', @@ -96,3 +127,80 @@ describe('getSnykDownloadInfo', () => { }); }); }); + +describe('downloadExecutable', () => { + // Define a mock Executable object for testing + const mockExecutable = { + filename: 'test-file.exe', + downloadUrl: 'https://example.com/test-file.exe', + }; + + let mockConsoleError: jest.SpyInstance; + + beforeAll(() => { + // Mock console.error to prevent logging during tests + mockConsoleError = jest.spyOn(console, 'error').mockImplementation(); + }); + + beforeEach(() => { + // Clear any existing mock server configuration + nock.cleanAll(); + mockConsoleError.mockClear(); + }); + + afterAll(() => { + // Clean up any remaining nock interceptors + nock.cleanAll(); + }); + + jest.setTimeout(30_000); + it('gives up after all retries fail with 500 errors with meaningful error', async () => { + // Mock the server to always respond with 500 errors + const fileName = `test-file-${uuid()}.exe`; + nock('https://example.com') + .get('/' + fileName) + .reply(500); + + const targetDirectory = path.join(os.tmpdir()); + + await downloadExecutable( + targetDirectory, + { + filename: fileName, + downloadUrl: 'https://example.com/' + fileName, + }, + 1, + ); + + // Assert that the file was not created + const calls = mockConsoleError.mock.calls; + expect(mockConsoleError).toBeCalledTimes(2); + expect(calls[0]).toEqual([`Download of ${fileName} failed: HTTP 500`]); + expect(calls[1]).toEqual([`All retries failed for ${fileName}: HTTP 500`]); + }); + + it('gives up after all retries fail with 404 errors with meaningful error', async () => { + // Mock the server to always respond with 404 errors + const fileName = `test-file-${uuid()}.exe`; + nock('https://example.com') + .get('/' + fileName) + .reply(404); + + const targetDirectory = path.join(os.tmpdir()); + + await downloadExecutable( + targetDirectory, + { + filename: fileName, + downloadUrl: 'https://example.com/' + fileName, + }, + 1, + ); + + // Assert that the file was not created + const calls = mockConsoleError.mock.calls; + expect(mockConsoleError).toBeCalledTimes(2); + expect(calls[0]).toEqual([`Download of ${fileName} failed: HTTP 404`]); + expect(calls[1]).toEqual([`All retries failed for ${fileName}: HTTP 404`]); + }); +}); diff --git a/snykTask/src/index.ts b/snykTask/src/index.ts index 47d45233..93d6b9f0 100644 --- a/snykTask/src/index.ts +++ b/snykTask/src/index.ts @@ -35,7 +35,7 @@ import { import * as fs from 'fs'; import * as path from 'path'; import { getSnykDownloadInfo, downloadExecutable } from './install'; -import { CliDistributionChannel } from './types'; +import { sanitizeVersionInput } from './lib/sanitize-version-input'; class SnykError extends Error { constructor(message?: string) { @@ -97,8 +97,9 @@ function parseInputArgs(): TaskArgs { taskArgs.failOnThreshold = tl.getInput('failOnThreshold', false) || Severity.LOW; taskArgs.ignoreUnknownCA = tl.getBoolInput('ignoreUnknownCA', false); - taskArgs.distributionChannel = (tl.getInput('distributionChannel', false) || - 'stable') as CliDistributionChannel; + taskArgs.distributionChannel = sanitizeVersionInput( + tl.getInput('distributionChannel', false), + ); if (isDebugMode()) { logAllTaskArgs(taskArgs); diff --git a/snykTask/src/install/index.ts b/snykTask/src/install/index.ts index 79cedde2..f72875ea 100644 --- a/snykTask/src/install/index.ts +++ b/snykTask/src/install/index.ts @@ -18,7 +18,7 @@ import { Platform } from 'azure-pipelines-task-lib/task'; import * as fs from 'fs'; import * as path from 'path'; import * as https from 'https'; -import { CliDistributionChannel } from '../types'; +import { sanitizeVersionInput } from '../lib/sanitize-version-input'; export type Executable = { filename: string; @@ -32,9 +32,10 @@ export type SnykDownloads = { export function getSnykDownloadInfo( platform: Platform, - distributionChannel: CliDistributionChannel = 'stable', + versionString: string = 'stable', ): SnykDownloads { const baseUrl = 'https://static.snyk.io'; + const distributionChannel = sanitizeVersionInput(versionString); const filenameSuffixes: Record = { [Platform.Linux]: 'linux', @@ -42,12 +43,6 @@ export function getSnykDownloadInfo( [Platform.MacOS]: 'macos', }; - const validDistributionChannels = ['stable', 'preview']; - - if (!validDistributionChannels.includes(distributionChannel)) { - distributionChannel = 'stable'; - } - return { snyk: { filename: `snyk-${filenameSuffixes[platform]}`, @@ -82,16 +77,40 @@ export async function downloadExecutable( // Wrapping the download in a function for easy retrying const doDownload = () => new Promise((resolve, reject) => { - https.get(executable.downloadUrl, (response) => { - fileWriter.on('close', () => resolve()); - response.on('error', (err) => { + https + .get(executable.downloadUrl, (response) => { + const isResponseError = response.statusCode !== 200; + + response.on('error', (err) => { + console.error( + `Download of ${executable.filename} failed: ${err.message}`, + ); + reject(err); + }); + + if (response.statusCode !== 200) { + fileWriter.close(); + } + + fileWriter.on('close', () => { + console.log( + `File.close ${executable.filename} saved to ${filePath}`, + ); + if (isResponseError) { + reject(new Error(`HTTP ${response.statusCode}`)); + } else { + resolve(); + } + }); + + response.pipe(fileWriter); + }) + .on('error', (err) => { console.error( - `Download of ${executable.filename} failed: ${err.message}`, + `Request for ${executable.filename} failed: ${err.message}`, ); reject(err); }); - response.pipe(fileWriter); - }); }); // Try to download the file, retry up to `maxRetries` times if the attempt fails diff --git a/snykTask/src/lib/sanitize-version-input.ts b/snykTask/src/lib/sanitize-version-input.ts new file mode 100644 index 00000000..ab7d73aa --- /dev/null +++ b/snykTask/src/lib/sanitize-version-input.ts @@ -0,0 +1,16 @@ +import * as semver from 'semver'; + +export function sanitizeVersionInput(versionString: string = ''): string { + const version = versionString.toLowerCase().trim(); + const validDistributionChannels = ['stable', 'preview']; + + if (semver.valid(semver.clean(version))) { + return `v${semver.clean(version)}`; + } + + if (validDistributionChannels.includes(version)) { + return version; + } + + return 'stable'; +} diff --git a/snykTask/src/task-args.ts b/snykTask/src/task-args.ts index 96f9f10a..eafc649e 100644 --- a/snykTask/src/task-args.ts +++ b/snykTask/src/task-args.ts @@ -16,7 +16,7 @@ import * as tl from 'azure-pipelines-task-lib'; import { Severity, TestType, testTypeSeverityThreshold } from './task-lib'; -import { CliDistributionChannel } from './types'; + export type MonitorWhen = 'never' | 'noIssuesFound' | 'always'; class TaskArgs { testType: string | undefined = 'app'; @@ -42,10 +42,10 @@ class TaskArgs { delayAfterReportGenerationSeconds: number = 0; /** - * The distribution channel to use for the Snyk CLI. - * Defaults to 'stable', but can be set to 'preview' for early access to new features. + * The cli version to use + * Defaults to 'stable', but can be set to 'preview' or a specific version such as '1.1287.0' */ - distributionChannel: CliDistributionChannel | undefined = 'stable'; + distributionChannel: string = 'stable'; // the params here are the ones which are mandatory constructor(params: { failOnIssues: boolean }) { @@ -118,16 +118,7 @@ class TaskArgs { return this.projectName; } - public getDistributionChannel(): CliDistributionChannel { - if (!this.distributionChannel) { - this.distributionChannel = 'stable'; - } - - const validDistributionChannels = ['stable', 'preview']; - if (!validDistributionChannels.includes(this.distributionChannel)) { - this.distributionChannel = 'stable'; - } - + public getDistributionChannel(): string { return this.distributionChannel; } diff --git a/snykTask/src/types.ts b/snykTask/src/types.ts deleted file mode 100644 index 8fedd5be..00000000 --- a/snykTask/src/types.ts +++ /dev/null @@ -1 +0,0 @@ -export type CliDistributionChannel = 'stable' | 'preview'; diff --git a/snykTask/task.json b/snykTask/task.json index 76defc85..c0c44ba6 100644 --- a/snykTask/task.json +++ b/snykTask/task.json @@ -165,15 +165,11 @@ }, { "name": "distributionChannel", - "label": "CLI Distribution channel", - "type": "pickList", - "options": { - "stable": "Stable", - "preview": "Preview" - }, + "label": "Distribution channel", + "type": "string", "required": false, "defaultValue": "stable", - "helpMarkDown": "Defaults to 'stable', but can be set to 'preview' for early access to new features", + "helpMarkDown": "Defaults to 'stable', but can be set to 'preview' or a specific version such as '1.1287.0'", "groupName": "advanced" } ], diff --git a/snykTask/tsconfig.json b/snykTask/tsconfig.json index 6bd2ab56..1c808793 100644 --- a/snykTask/tsconfig.json +++ b/snykTask/tsconfig.json @@ -16,5 +16,6 @@ "inlineSourceMap": true /* Emit a single file with source maps instead of having a separate file. */, "inlineSources": true /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */, "useUnknownInCatchVariables": false - } + }, + "exclude": ["**/__tests__/**"] }