From f0af0f6835097dad1373b357544bdf8612df561e Mon Sep 17 00:00:00 2001 From: janssen <118828444+janssen-tiobe@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:10:18 +0200 Subject: [PATCH 01/13] RM-35685: Added create project functionality to server mode Changes: - Updated @tiobe/http-client and @tiobe/install-tics dependencies - Added createProject input - Fixed tests broken due to adding the input --- action.yaml | 6 +- dist/index.js | 1272 +++++++++++++--------- package-lock.json | 179 +-- package.json | 4 +- src/analysis/qserver.ts | 7 +- src/configuration/tics-cli.ts | 21 +- src/configuration/tics.ts | 9 + src/helper/response.ts | 2 +- src/viewer/http-client.ts | 2 +- src/viewer/interfaces.d.ts | 2 +- src/viewer/project.ts | 52 + src/viewer/qserver.ts | 4 + test/integration/configuration.test.ts | 1 + test/integration/httpclient.test.ts | 3 +- test/integration/octokit.test.ts | 1 + test/unit/configuration/tics-cli.test.ts | 4 +- test/unit/configuration/tics.test.ts | 35 +- test/unit/helper/response.test.ts | 2 +- 18 files changed, 955 insertions(+), 651 deletions(-) create mode 100644 src/viewer/project.ts diff --git a/action.yaml b/action.yaml index 8c9b28da..47df1cc7 100644 --- a/action.yaml +++ b/action.yaml @@ -10,7 +10,7 @@ inputs: required: false default: client project: - description: Name of the TICS project. + description: Name of the TICS project. In client mode this will default to project `auto`, in server mode it will use the repository name. required: false default: auto githubToken: @@ -29,6 +29,10 @@ inputs: calc: description: Comma-separated list of metrics to be calculated. GATE metric is supported for TICS Viewer versions higher than 2022.2.x. required: false + createProject: + description: Create the project in the TICS Viewer if it does not exist already (only in server mode). + required: false + default: false nocalc: description: Comma-separated list of metrics not to be calculated. required: false diff --git a/dist/index.js b/dist/index.js index 0467c460..475ddd69 100644 --- a/dist/index.js +++ b/dist/index.js @@ -804,6 +804,7 @@ const summary_1 = __nccwpck_require__(69651); const config_1 = __nccwpck_require__(34151); const output_1 = __nccwpck_require__(63761); const analyzer_1 = __nccwpck_require__(24928); +const project_1 = __nccwpck_require__(17210); const qserver_1 = __nccwpck_require__(18179); const analysis_result_1 = __nccwpck_require__(79472); /** @@ -811,6 +812,9 @@ const analysis_result_1 = __nccwpck_require__(79472); * @returns Verdict for a QServer run. */ async function qServerAnalysis() { + if (config_1.ticsConfig.createProject) { + await (0, project_1.createProject)(); + } const oldDate = await (0, qserver_1.getLastQServerRunDate)(); const analysis = await (0, analyzer_1.runTicsAnalyzer)(''); const newDate = await (0, qserver_1.getLastQServerRunDate)(); @@ -1190,6 +1194,7 @@ exports.CliOptions = exports.TicsCli = void 0; const core_1 = __nccwpck_require__(37484); const tics_1 = __nccwpck_require__(5232); const logger_1 = __nccwpck_require__(66113); +const config_1 = __nccwpck_require__(34151); class TicsCli { project; branchname; @@ -1203,7 +1208,7 @@ class TicsCli { tmpdir; additionalFlags; constructor(mode) { - this.project = (0, core_1.getInput)('project'); + this.project = this.getProject((0, core_1.getInput)('project'), mode); this.branchname = (0, core_1.getInput)('branchname'); this.branchdir = (0, core_1.getInput)('branchdir'); this.cdtoken = (0, core_1.getInput)('cdtoken'); @@ -1223,6 +1228,16 @@ class TicsCli { this.branchdir = process.env.GITHUB_WORKSPACE; } } + getProject(input, mode) { + // validate project + if (mode === tics_1.Mode.QSERVER) { + if (input === 'auto') { + logger_1.logger.info(`Parameter 'project' is not set, using the repository name (${config_1.githubConfig.reponame}) instead.`); + return config_1.githubConfig.reponame; + } + } + return input; + } /** * Get the calc option or the default if not set by the user * @returns the calc option set by user or thedefault. @@ -1243,12 +1258,6 @@ class TicsCli { * @throws error if project auto is used incorrectly. */ validateCliOptions(cli, mode) { - // validate project - if (mode === tics_1.Mode.QSERVER) { - if (cli.project === 'auto') { - throw Error(`Running TICS with project 'auto' is not possible with QServer`); - } - } for (const option of exports.CliOptions) { const key = option.action; if (cli[key] !== '' && !option.modes.includes(mode)) { @@ -1341,6 +1350,7 @@ class TicsConfiguration { mode; ticsAuthToken; trustStrategy; + createProject; /** * The URL pointing to the "cfg" API endpoint of the TICS Viewer. Is used for running TICS. */ @@ -1365,6 +1375,7 @@ class TicsConfiguration { this.ticsAuthToken = (0, core_1.getInput)('ticsAuthToken'); this.baseUrl = (0, install_tics_1.getBaseUrl)(this.viewerUrl).href; this.displayUrl = this.validateAndGetDisplayUrl((0, core_1.getInput)('displayUrl')); + this.createProject = this.validateAndGetCreateProject((0, core_1.getBooleanInput)('createProject')); this.setVariables(); } /** @@ -1459,6 +1470,12 @@ class TicsConfiguration { } throw Error(`Parameter 'hostnameVerification' should be '1'/'true' or '0'/'false'. Input given is '${input}'`); } + validateAndGetCreateProject(input) { + if (this.mode !== Mode.QSERVER && input) { + throw Error(`Parameter 'createProject' can only be used in server mode`); + } + return input; + } /** * Set all environment variables TICS needs to run in the GitHub setting. */ @@ -2130,28 +2147,28 @@ async function getChangedFilesOfPullRequestQL() { }; let response; try { - response = await octokit_1.octokit.graphql.paginate(`query changedFiles($owner: String!, $repo: String!, $pull_number: Int!, $per_page: Int!, $cursor: String) { - rateLimit { - remaining - } - repository(owner: $owner, name: $repo) { - pullRequest(number: $pull_number) { - files(first: $per_page, after: $cursor) { - totalCount - nodes { - path - changeType - additions - deletions - viewerViewedState - } - pageInfo { - hasNextPage - endCursor - } - } - } - } + response = await octokit_1.octokit.graphql.paginate(`query changedFiles($owner: String!, $repo: String!, $pull_number: Int!, $per_page: Int!, $cursor: String) { + rateLimit { + remaining + } + repository(owner: $owner, name: $repo) { + pullRequest(number: $pull_number) { + files(first: $per_page, after: $cursor) { + totalCount + nodes { + path + changeType + additions + deletions + viewerViewedState + } + pageInfo { + hasNextPage + endCursor + } + } + } + } }`, params); logger_1.logger.debug(JSON.stringify(response)); } @@ -2543,7 +2560,7 @@ exports.handleOctokitError = handleOctokitError; exports.getRetryErrorMessage = getRetryErrorMessage; exports.getRetryMessage = getRetryMessage; const request_error_1 = __nccwpck_require__(93708); -const retry_1 = __nccwpck_require__(72344); +const http_client_1 = __nccwpck_require__(22338); function handleOctokitError(error) { let message = 'reason unkown'; if (error instanceof Error) { @@ -2560,7 +2577,7 @@ function getRetryErrorMessage(error) { let message = error; if (error instanceof Error) { message = error.message; - if (error instanceof retry_1.RequestError) { + if (error instanceof http_client_1.RequestError) { if (error.retryCount > 0) { message += ` (retried ${error.retryCount.toString()} times)`; } @@ -3074,19 +3091,16 @@ function findAnnotationInList(list, annotation) { /***/ }), /***/ 80939: -/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.httpClient = void 0; -const http_client_1 = __importDefault(__nccwpck_require__(22338)); +const http_client_1 = __nccwpck_require__(22338); const proxy_agent_1 = __nccwpck_require__(75273); const config_1 = __nccwpck_require__(34151); -exports.httpClient = new http_client_1.default(true, { +exports.httpClient = new http_client_1.HttpClient(true, { authToken: config_1.ticsConfig.ticsAuthToken, xRequestWithTics: true, retry: { @@ -3097,6 +3111,65 @@ exports.httpClient = new http_client_1.default(true, { }, new proxy_agent_1.ProxyAgent()); +/***/ }), + +/***/ 17210: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.createProject = createProject; +const config_1 = __nccwpck_require__(34151); +const logger_1 = __nccwpck_require__(66113); +const response_1 = __nccwpck_require__(93590); +const url_1 = __nccwpck_require__(71112); +const http_client_1 = __nccwpck_require__(80939); +/** + * Gets the date of the last QServer run the viewer knows of. + * @throws Error if project cannot be created or does not exist. + */ +async function createProject() { + const createProjectUrl = (0, url_1.joinUrl)(config_1.ticsConfig.baseUrl, `api/public/v1/fapi/Project`); + const branchName = getBranchName(); + const body = { + projectName: config_1.ticsCli.project, + branchName: branchName, + branchDir: config_1.ticsCli.branchdir, + calculate: true, + visible: true, + renameTo: { + branchName: branchName + } + }; + try { + logger_1.logger.header('Creating/updating the TICS project'); + logger_1.logger.debug(`With ${createProjectUrl}`); + await http_client_1.httpClient.put(createProjectUrl, JSON.stringify(body)); + } + catch (error) { + const message = (0, response_1.getRetryErrorMessage)(error); + throw Error(`There was an error creating the project: ${message}`); + } +} +/** + * Get the branchname of the project to create. + * If branchdir is not set, it will try to get the default branch or else 'main'. + */ +function getBranchName() { + if (config_1.ticsCli.branchname) { + return config_1.ticsCli.branchname; + } + if (process.env.GITHUB_BASE_REF) { + return process.env.GITHUB_BASE_REF; + } + if (process.env.GITHUB_REF_NAME) { + return process.env.GITHUB_REF_NAME; + } + return 'main'; +} + + /***/ }), /***/ 18179: @@ -3127,6 +3200,10 @@ async function getLastQServerRunDate() { if (response.data.data.length === 0) { throw Error('Request returned empty array'); } + if (!response.data.data[0].value) { + // return -1 for projects that haven't run yet + return -1; + } return response.data.data[0].value / 1000; } catch (error) { @@ -48419,7 +48496,7 @@ exports.ReflectionTypeCheck = ReflectionTypeCheck; /***/ }), -/***/ 22338: +/***/ 43419: /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -48440,26 +48517,36 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : 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 __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.HttpClient = void 0; const node_fetch_1 = __importStar(__nccwpck_require__(26705)); -const retry_1 = __importStar(__nccwpck_require__(72344)); +const retry_1 = __importDefault(__nccwpck_require__(72344)); +const interfaces_1 = __nccwpck_require__(75152); class HttpClient { + agent; + ci; + defaultHeaders; + customFetch; /** * @param ci If this http client is used in a ci environment. * @param options (Optional) options to use for the HttpClient. @@ -48469,75 +48556,141 @@ class HttpClient { this.ci = ci; this.agent = agent; this.defaultHeaders = new node_fetch_1.Headers(); - if (options === null || options === void 0 ? void 0 : options.authToken) { + if (options?.authToken) { this.defaultHeaders.set('authorization', `Basic ${options.authToken}`); } - if (options === null || options === void 0 ? void 0 : options.xRequestWithTics) { + if (options?.xRequestWithTics) { this.defaultHeaders.set('x-requested-with', 'TICS'); } - this.customFetch = (0, retry_1.default)(node_fetch_1.default, options === null || options === void 0 ? void 0 : options.retry); + this.customFetch = (0, retry_1.default)(node_fetch_1.default, options?.retry); } /** * Executes a GET request to the given url. * @param url api url to perform a GET request for. */ - get(url, headers) { - return __awaiter(this, void 0, void 0, function* () { - let fixedHeaders = this.defaultHeaders; - headers === null || headers === void 0 ? void 0 : headers.forEach((value, key) => { - fixedHeaders.append(key, value); - }); - const requestInit = { - agent: this.agent, - headers: fixedHeaders - }; - const response = yield this.customFetch(url, requestInit); - switch (response.status) { - case 200: - const text = yield response.text(); - try { - const result = { - status: response.status, - retryCount: response.retryCount, - data: JSON.parse(text) - }; - return result; - } - catch (error) { - throw new retry_1.RequestError(`${error}: ${text}`, response.status, response.retryCount); - } - case 302: - throw new retry_1.RequestError(`HTTP request failed with status ${response.status}. Please check if the given ticsConfiguration is correct.`, response.status, response.retryCount); - case 400: - throw new retry_1.RequestError(`HTTP request failed with status ${response.status}. ${(yield response.json()).alertMessages[0].header}`, response.status, response.retryCount); - case 401: - let authUrl = url.split('/api/')[0]; - authUrl += this.ci ? '/Administration.html#page=authToken' : '/UserSettings.html#page=authToken'; - throw new retry_1.RequestError(`HTTP request failed with status ${response.status}. Please provide a valid TICS authentication token. See ${authUrl}`, response.status, response.retryCount); - case 403: - throw new retry_1.RequestError(`HTTP request failed with status ${response.status}. Forbidden call: ${url}`, response.status, response.retryCount); - case 404: - throw new retry_1.RequestError(`HTTP request failed with status ${response.status}. Please check if the given ticsConfiguration is correct.`, response.status, response.retryCount); - default: - throw new retry_1.RequestError(`HTTP request failed with status ${response.status}: ${response.statusText}`, response.status, response.retryCount); - } + async get(url, headers) { + return this.request(url, 'GET', undefined, headers); + } + /** + * Executes a DELETE request. + * @param url api url to perform a DELETE request for. + */ + async delete(url, headers) { + return this.request(url, 'DELETE', undefined, headers); + } + /** + * Executes a POST request. + * @param url api url to perform a POST request for. + * @param body The JSON body to send. + */ + async post(url, body, headers) { + return this.request(url, 'POST', body, headers); + } + /** + * Executes a PUT request. + * @param url api url to perform a PUT request for. + * @param body The JSON body to send. + */ + async put(url, body, headers) { + return this.request(url, 'PUT', body, headers); + } + /** + * Private helper to handle the request logic and response parsing. + */ + async request(url, method, body, headers) { + const fixedHeaders = new node_fetch_1.Headers(this.defaultHeaders); + // Set JSON content type for POST/PUT if a body is present + if (body) { + fixedHeaders.set('content-type', 'application/json'); + } + headers?.forEach((value, key) => { + fixedHeaders.append(key, value); }); + const requestInit = { + method: method, + agent: this.agent, + headers: fixedHeaders, + body: body, + }; + const response = await this.customFetch(url, requestInit); + return this.handleResponse(url, response); + } + async handleResponse(url, response) { + switch (response.status) { + case 200: + case 201: { + const text = await response.text(); + try { + const result = { + status: response.status, + retryCount: response.retryCount, + data: JSON.parse(text), + }; + return result; + } + catch (error) { + throw new interfaces_1.RequestError(`${error}: ${text}`, response.status, response.retryCount); + } + } + case 302: + throw new interfaces_1.RequestError(`HTTP request failed with status ${response.status.toString()}. Please check if the given ticsConfiguration is correct.`, response.status, response.retryCount); + case 400: + throw new interfaces_1.RequestError(`HTTP request failed with status ${response.status.toString()}. ${(await response.json()).alertMessages[0].header}`, response.status, response.retryCount); + case 401: { + let authUrl = url.split('/api/')[0]; + authUrl += this.ci ? '/Administration.html#page=authToken' : '/UserSettings.html#page=authToken'; + throw new interfaces_1.RequestError(`HTTP request failed with status ${response.status.toString()}. Please provide a valid TICS authentication token. See ${authUrl}`, response.status, response.retryCount); + } + case 403: + throw new interfaces_1.RequestError(`HTTP request failed with status ${response.status.toString()}. Forbidden call: ${url}`, response.status, response.retryCount); + case 404: + throw new interfaces_1.RequestError(`HTTP request failed with status ${response.status.toString()}. Please check if the given ticsConfiguration is correct.`, response.status, response.retryCount); + default: + throw new interfaces_1.RequestError(`HTTP request failed with status ${response.status.toString()}: ${response.statusText}`, response.status, response.retryCount); + } } } -exports["default"] = HttpClient; +exports.HttpClient = HttpClient; +//# sourceMappingURL=client.js.map + +/***/ }), + +/***/ 22338: +/***/ (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 __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +__exportStar(__nccwpck_require__(43419), exports); +__exportStar(__nccwpck_require__(75152), exports); //# sourceMappingURL=index.js.map /***/ }), -/***/ 72344: +/***/ 75152: /***/ ((__unused_webpack_module, exports) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.RequestError = void 0; -exports.fetchBuilder = fetchBuilder; class RequestError extends Error { + status; + retryCount; constructor(message, status, retryCount) { super(message); this.name = 'RequestError'; @@ -48546,8 +48699,21 @@ class RequestError extends Error { } } exports.RequestError = RequestError; +//# sourceMappingURL=interfaces.js.map + +/***/ }), + +/***/ 72344: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.fetchBuilder = fetchBuilder; +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +const interfaces_1 = __nccwpck_require__(75152); function sanitize(params, defaults) { - const result = Object.assign(Object.assign({}, defaults), params); + const result = { ...defaults, ...params }; if (typeof result.retries === 'undefined') { result.retries = defaults.retries; } @@ -48565,21 +48731,20 @@ function fetchBuilder(fetchFunc, params = {}) { return function (input, init) { const frp = sanitize({ // TICS -no-unsafe-assignment - retries: init === null || init === void 0 ? void 0 : init.retries, - retryDelay: init === null || init === void 0 ? void 0 : init.retryDelay, - retryOn: init === null || init === void 0 ? void 0 : init.retryOn + retries: init?.retries, + retryDelay: init?.retryDelay, + retryOn: init?.retryOn, // TICS +no-unsafe-assignment }, defaults); const retryDelayFn = typeof frp.retryDelay === 'function' ? frp.retryDelay : () => frp.retryDelay; const retryOnFn = typeof frp.retryOn === 'function' ? frp.retryOn - : (attempt, retries, error, response) => (!!error || !response || frp.retryOn.indexOf(response.status) !== -1) && attempt < retries; + : (attempt, retries, error, response) => (!!error || !response || frp.retryOn.includes(response.status)) && attempt < retries; return new Promise(function (resolve, reject) { const extendedFetch = function (attempt) { fetchFunc(input, init) .then(function (response) { if (retryOnFn(attempt, frp.retries, null, response)) { - // eslint-disable-next-line @typescript-eslint/no-use-before-define retry(attempt, null, response); } else { @@ -48588,13 +48753,13 @@ function fetchBuilder(fetchFunc, params = {}) { resolve(responseWithRetryCount); } }) + // eslint-disable-next-line @typescript-eslint/use-unknown-in-catch-callback-variable .catch(function (error) { if (retryOnFn(attempt, frp.retries, error, null)) { - // eslint-disable-next-line @typescript-eslint/no-use-before-define retry(attempt, error, null); } else { - reject(new RequestError(error.message, 0, attempt)); + reject(new interfaces_1.RequestError(error.message, 0, attempt)); } }); }; @@ -48617,6 +48782,32 @@ exports["default"] = fetchBuilder; "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 __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +__exportStar(__nccwpck_require__(85861), exports); +__exportStar(__nccwpck_require__(65446), exports); +//# sourceMappingURL=index.js.map + +/***/ }), + +/***/ 65446: +/***/ (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); @@ -48633,30 +48824,28 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : 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 }; -}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); Object.defineProperty(exports, "__esModule", ({ value: true })); exports.InstallTics = void 0; exports.getBaseUrl = getBaseUrl; const os = __importStar(__nccwpck_require__(70857)); -const http_client_1 = __importDefault(__nccwpck_require__(22338)); +const http_client_1 = __nccwpck_require__(22338); var Platform; (function (Platform) { Platform["aix"] = "aix"; @@ -48684,13 +48873,14 @@ function getBaseUrl(url) { try { return new URL(url.split(apiMarker)[0]); } - catch (error) { + catch { throw Error('URL could not be parsed.'); } } throw Error('Incorrect TICS Viewer url was given.'); } class InstallTics { + httpClient; /** * @param ci If this http client is used in a ci environment. * @param httpClient (Optional) Give your own HttpClient to use. @@ -48701,53 +48891,49 @@ class InstallTics { this.httpClient = httpClient; } else { - this.httpClient = new http_client_1.default(ci, options === null || options === void 0 ? void 0 : options.httpClientOptions, options === null || options === void 0 ? void 0 : options.agent); + this.httpClient = new http_client_1.HttpClient(ci, options?.httpClientOptions, options?.agent); } } /** * Retrieves the install command from the configured TICS Viewer. Uses environment variable TICSTRUSTSTRATEGY. * @param url TICS configuration url. */ - getInstallCommand(url) { - return __awaiter(this, void 0, void 0, function* () { - const platform = Platform[os.platform()]; - const installTicsUrl = yield this.getInstallTicsUrl(url, platform); - switch (platform) { - case Platform.linux: - return this.linuxInstall(installTicsUrl); - case Platform.win32: - return this.windowsInstall(installTicsUrl); - default: - throw Error(`No install command found for platform: ${platform}.`); - } - }); + async getInstallCommand(url) { + const platform = Platform[os.platform()]; + const installTicsUrl = await this.getInstallTicsUrl(url, platform); + switch (platform) { + case Platform.linux: + return this.linuxInstall(installTicsUrl); + case Platform.win32: + return this.windowsInstall(installTicsUrl); + default: + throw Error(`No install command found for platform: ${platform}.`); + } } /** * Returns the TIOBE web base url. * @param url TICS configuration url. * @param platform os to retrieve the install url for. */ - getInstallTicsUrl(url, platform) { - return __awaiter(this, void 0, void 0, function* () { - const baseUrl = getBaseUrl(url).toString(); - const installTicsUrl = new URL(url); - installTicsUrl.searchParams.append('platform', platform); - installTicsUrl.searchParams.append('url', baseUrl); - try { - const response = yield this.httpClient.get(installTicsUrl.href); - if (response.data.links.installTics) { - return baseUrl + response.data.links.installTics; - } - throw Error(`Install url could not be retrieved from ${installTicsUrl.href}.`); + async getInstallTicsUrl(url, platform) { + const baseUrl = getBaseUrl(url).toString(); + const installTicsUrl = new URL(url); + installTicsUrl.searchParams.append('platform', platform); + installTicsUrl.searchParams.append('url', baseUrl); + try { + const response = await this.httpClient.get(installTicsUrl.href); + if (response.data.links.installTics) { + return baseUrl + response.data.links.installTics; } - catch (err) { - let message; - if (err instanceof Error) { - message = err.message; - } - throw Error(message); + throw Error(`Install url could not be retrieved from ${installTicsUrl.href}.`); + } + catch (err) { + let message; + if (err instanceof Error) { + message = err.message; } - }); + throw Error(message, { cause: err }); + } } /** * Returns the install command for Linux. @@ -48773,7 +48959,17 @@ class InstallTics { } } exports.InstallTics = InstallTics; -//# sourceMappingURL=index.js.map +//# sourceMappingURL=install.js.map + +/***/ }), + +/***/ 85861: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +//# sourceMappingURL=interfaces.js.map /***/ }), @@ -52784,7 +52980,7 @@ function expand(str, isTop) { var y = numeric(n[1]); var width = Math.max(n[0].length, n[1].length) var incr = n.length == 3 - ? Math.abs(numeric(n[2])) + ? Math.max(Math.abs(numeric(n[2])), 1) : 1; var test = lte; var reverse = y < x; @@ -52838,7 +53034,6 @@ function expand(str, isTop) { } - /***/ }), /***/ 99392: @@ -124423,7 +124618,7 @@ function expand(str, isTop) { var y = numeric(n[1]); var width = Math.max(n[0].length, n[1].length) var incr = n.length == 3 - ? Math.abs(numeric(n[2])) + ? Math.max(Math.abs(numeric(n[2])), 1) : 1; var test = lte; var reverse = y < x; @@ -124477,7 +124672,6 @@ function expand(str, isTop) { } - /***/ }), /***/ 63669: @@ -202033,6 +202227,378 @@ function cleanEscapedString(input) { } +/***/ }), + +/***/ 27633: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +exports.parseISO = parseISO; +var _index = __nccwpck_require__(46104); + +var _index2 = __nccwpck_require__(25848); +var _index3 = __nccwpck_require__(44826); + +/** + * The {@link parseISO} function options. + */ + +/** + * @name parseISO + * @category Common Helpers + * @summary Parse ISO string + * + * @description + * Parse the given string in ISO 8601 format and return an instance of Date. + * + * Function accepts complete ISO 8601 formats as well as partial implementations. + * ISO 8601: http://en.wikipedia.org/wiki/ISO_8601 + * + * If the argument isn't a string, the function cannot parse the string or + * the values are invalid, it returns Invalid Date. + * + * @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc). + * @typeParam ResultDate - The result `Date` type, it is the type returned from the context function if it is passed, or inferred from the arguments. + * + * @param argument - The value to convert + * @param options - An object with options + * + * @returns The parsed date in the local time zone + * + * @example + * // Convert string '2014-02-11T11:30:30' to date: + * const result = parseISO('2014-02-11T11:30:30') + * //=> Tue Feb 11 2014 11:30:30 + * + * @example + * // Convert string '+02014101' to date, + * // if the additional number of digits in the extended year format is 1: + * const result = parseISO('+02014101', { additionalDigits: 1 }) + * //=> Fri Apr 11 2014 00:00:00 + */ +function parseISO(argument, options) { + const invalidDate = () => (0, _index2.constructFrom)(options?.in, NaN); + + const additionalDigits = options?.additionalDigits ?? 2; + const dateStrings = splitDateString(argument); + + let date; + if (dateStrings.date) { + const parseYearResult = parseYear(dateStrings.date, additionalDigits); + date = parseDate(parseYearResult.restDateString, parseYearResult.year); + } + + if (!date || isNaN(+date)) return invalidDate(); + + const timestamp = +date; + let time = 0; + let offset; + + if (dateStrings.time) { + time = parseTime(dateStrings.time); + if (isNaN(time)) return invalidDate(); + } + + if (dateStrings.timezone) { + offset = parseTimezone(dateStrings.timezone); + if (isNaN(offset)) return invalidDate(); + } else { + const tmpDate = new Date(timestamp + time); + const result = (0, _index3.toDate)(0, options?.in); + result.setFullYear( + tmpDate.getUTCFullYear(), + tmpDate.getUTCMonth(), + tmpDate.getUTCDate(), + ); + result.setHours( + tmpDate.getUTCHours(), + tmpDate.getUTCMinutes(), + tmpDate.getUTCSeconds(), + tmpDate.getUTCMilliseconds(), + ); + return result; + } + + return (0, _index3.toDate)(timestamp + time + offset, options?.in); +} + +const patterns = { + dateTimeDelimiter: /[T ]/, + timeZoneDelimiter: /[Z ]/i, + timezone: /([Z+-].*)$/, +}; + +const dateRegex = + /^-?(?:(\d{3})|(\d{2})(?:-?(\d{2}))?|W(\d{2})(?:-?(\d{1}))?|)$/; +const timeRegex = + /^(\d{2}(?:[.,]\d*)?)(?::?(\d{2}(?:[.,]\d*)?))?(?::?(\d{2}(?:[.,]\d*)?))?$/; +const timezoneRegex = /^([+-])(\d{2})(?::?(\d{2}))?$/; + +function splitDateString(dateString) { + const dateStrings = {}; + const array = dateString.split(patterns.dateTimeDelimiter); + let timeString; + + // The regex match should only return at maximum two array elements. + // [date], [time], or [date, time]. + if (array.length > 2) { + return dateStrings; + } + + if (/:/.test(array[0])) { + timeString = array[0]; + } else { + dateStrings.date = array[0]; + timeString = array[1]; + if (patterns.timeZoneDelimiter.test(dateStrings.date)) { + dateStrings.date = dateString.split(patterns.timeZoneDelimiter)[0]; + timeString = dateString.substr( + dateStrings.date.length, + dateString.length, + ); + } + } + + if (timeString) { + const token = patterns.timezone.exec(timeString); + if (token) { + dateStrings.time = timeString.replace(token[1], ""); + dateStrings.timezone = token[1]; + } else { + dateStrings.time = timeString; + } + } + + return dateStrings; +} + +function parseYear(dateString, additionalDigits) { + const regex = new RegExp( + "^(?:(\\d{4}|[+-]\\d{" + + (4 + additionalDigits) + + "})|(\\d{2}|[+-]\\d{" + + (2 + additionalDigits) + + "})$)", + ); + + const captures = dateString.match(regex); + // Invalid ISO-formatted year + if (!captures) return { year: NaN, restDateString: "" }; + + const year = captures[1] ? parseInt(captures[1]) : null; + const century = captures[2] ? parseInt(captures[2]) : null; + + // either year or century is null, not both + return { + year: century === null ? year : century * 100, + restDateString: dateString.slice((captures[1] || captures[2]).length), + }; +} + +function parseDate(dateString, year) { + // Invalid ISO-formatted year + if (year === null) return new Date(NaN); + + const captures = dateString.match(dateRegex); + // Invalid ISO-formatted string + if (!captures) return new Date(NaN); + + const isWeekDate = !!captures[4]; + const dayOfYear = parseDateUnit(captures[1]); + const month = parseDateUnit(captures[2]) - 1; + const day = parseDateUnit(captures[3]); + const week = parseDateUnit(captures[4]); + const dayOfWeek = parseDateUnit(captures[5]) - 1; + + if (isWeekDate) { + if (!validateWeekDate(year, week, dayOfWeek)) { + return new Date(NaN); + } + return dayOfISOWeekYear(year, week, dayOfWeek); + } else { + const date = new Date(0); + if ( + !validateDate(year, month, day) || + !validateDayOfYearDate(year, dayOfYear) + ) { + return new Date(NaN); + } + date.setUTCFullYear(year, month, Math.max(dayOfYear, day)); + return date; + } +} + +function parseDateUnit(value) { + return value ? parseInt(value) : 1; +} + +function parseTime(timeString) { + const captures = timeString.match(timeRegex); + if (!captures) return NaN; // Invalid ISO-formatted time + + const hours = parseTimeUnit(captures[1]); + const minutes = parseTimeUnit(captures[2]); + const seconds = parseTimeUnit(captures[3]); + + if (!validateTime(hours, minutes, seconds)) { + return NaN; + } + + return ( + hours * _index.millisecondsInHour + + minutes * _index.millisecondsInMinute + + seconds * 1000 + ); +} + +function parseTimeUnit(value) { + return (value && parseFloat(value.replace(",", "."))) || 0; +} + +function parseTimezone(timezoneString) { + if (timezoneString === "Z") return 0; + + const captures = timezoneString.match(timezoneRegex); + if (!captures) return 0; + + const sign = captures[1] === "+" ? -1 : 1; + const hours = parseInt(captures[2]); + const minutes = (captures[3] && parseInt(captures[3])) || 0; + + if (!validateTimezone(hours, minutes)) { + return NaN; + } + + return ( + sign * + (hours * _index.millisecondsInHour + minutes * _index.millisecondsInMinute) + ); +} + +function dayOfISOWeekYear(isoWeekYear, week, day) { + const date = new Date(0); + date.setUTCFullYear(isoWeekYear, 0, 4); + const fourthOfJanuaryDay = date.getUTCDay() || 7; + const diff = (week - 1) * 7 + day + 1 - fourthOfJanuaryDay; + date.setUTCDate(date.getUTCDate() + diff); + return date; +} + +// Validation functions + +// February is null to handle the leap year (using ||) +const daysInMonths = [31, null, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + +function isLeapYearIndex(year) { + return year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0); +} + +function validateDate(year, month, date) { + return ( + month >= 0 && + month <= 11 && + date >= 1 && + date <= (daysInMonths[month] || (isLeapYearIndex(year) ? 29 : 28)) + ); +} + +function validateDayOfYearDate(year, dayOfYear) { + return dayOfYear >= 1 && dayOfYear <= (isLeapYearIndex(year) ? 366 : 365); +} + +function validateWeekDate(_year, week, day) { + return week >= 1 && week <= 53 && day >= 0 && day <= 6; +} + +function validateTime(hours, minutes, seconds) { + if (hours === 24) { + return minutes === 0 && seconds === 0; + } + + return ( + seconds >= 0 && + seconds < 60 && + minutes >= 0 && + minutes < 60 && + hours >= 0 && + hours < 25 + ); +} + +function validateTimezone(_hours, minutes) { + return minutes >= 0 && minutes <= 59; +} + + +/***/ }), + +/***/ 26380: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +exports.parseJSON = parseJSON; +var _index = __nccwpck_require__(44826); + +/** + * The {@link parseJSON} function options. + */ + +/** + * Converts a complete ISO date string in UTC time, the typical format for transmitting + * a date in JSON, to a JavaScript `Date` instance. + * + * This is a minimal implementation for converting dates retrieved from a JSON API to + * a `Date` instance which can be used with other functions in the `date-fns` library. + * The following formats are supported: + * + * - `2000-03-15T05:20:10.123Z`: The output of `.toISOString()` and `JSON.stringify(new Date())` + * - `2000-03-15T05:20:10Z`: Without milliseconds + * - `2000-03-15T05:20:10+00:00`: With a zero offset, the default JSON encoded format in some other languages + * - `2000-03-15T05:20:10+05:45`: With a positive or negative offset, the default JSON encoded format in some other languages + * - `2000-03-15T05:20:10+0000`: With a zero offset without a colon + * - `2000-03-15T05:20:10`: Without a trailing 'Z' symbol + * - `2000-03-15T05:20:10.1234567`: Up to 7 digits in milliseconds field. Only first 3 are taken into account since JS does not allow fractional milliseconds + * - `2000-03-15 05:20:10`: With a space instead of a 'T' separator for APIs returning a SQL date without reformatting + * + * For convenience and ease of use these other input types are also supported + * via [toDate](https://date-fns.org/docs/toDate): + * + * - A `Date` instance will be cloned + * - A `number` will be treated as a timestamp + * + * Any other input type or invalid date strings will return an `Invalid Date`. + * + * @typeParam ResultDate - The result `Date` type, it is the type returned from the context function if it is passed, or inferred from the arguments. + * + * @param dateStr - A fully formed ISO8601 date string to convert + * @param options - An object with options + * + * @returns The parsed date in the local time zone + */ +function parseJSON(dateStr, options) { + const parts = dateStr.match( + /(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})(?:\.(\d{0,7}))?(?:Z|(.)(\d{2}):?(\d{2})?)?/, + ); + + if (!parts) return (0, _index.toDate)(NaN, options?.in); + + return (0, _index.toDate)( + Date.UTC( + +parts[1], + +parts[2] - 1, + +parts[3], + +parts[4] - (+parts[9] || 0) * (parts[8] == "-" ? -1 : 1), + +parts[5] - (+parts[10] || 0) * (parts[8] == "-" ? -1 : 1), + +parts[6], + +((parts[7] || "0") + "00").substring(0, 3), + ), + options?.in, + ); +} + + /***/ }), /***/ 29861: @@ -204565,378 +205131,6 @@ function isLeapYearIndex(year) { } -/***/ }), - -/***/ 27633: -/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { - -"use strict"; - -exports.parseISO = parseISO; -var _index = __nccwpck_require__(46104); - -var _index2 = __nccwpck_require__(25848); -var _index3 = __nccwpck_require__(44826); - -/** - * The {@link parseISO} function options. - */ - -/** - * @name parseISO - * @category Common Helpers - * @summary Parse ISO string - * - * @description - * Parse the given string in ISO 8601 format and return an instance of Date. - * - * Function accepts complete ISO 8601 formats as well as partial implementations. - * ISO 8601: http://en.wikipedia.org/wiki/ISO_8601 - * - * If the argument isn't a string, the function cannot parse the string or - * the values are invalid, it returns Invalid Date. - * - * @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc). - * @typeParam ResultDate - The result `Date` type, it is the type returned from the context function if it is passed, or inferred from the arguments. - * - * @param argument - The value to convert - * @param options - An object with options - * - * @returns The parsed date in the local time zone - * - * @example - * // Convert string '2014-02-11T11:30:30' to date: - * const result = parseISO('2014-02-11T11:30:30') - * //=> Tue Feb 11 2014 11:30:30 - * - * @example - * // Convert string '+02014101' to date, - * // if the additional number of digits in the extended year format is 1: - * const result = parseISO('+02014101', { additionalDigits: 1 }) - * //=> Fri Apr 11 2014 00:00:00 - */ -function parseISO(argument, options) { - const invalidDate = () => (0, _index2.constructFrom)(options?.in, NaN); - - const additionalDigits = options?.additionalDigits ?? 2; - const dateStrings = splitDateString(argument); - - let date; - if (dateStrings.date) { - const parseYearResult = parseYear(dateStrings.date, additionalDigits); - date = parseDate(parseYearResult.restDateString, parseYearResult.year); - } - - if (!date || isNaN(+date)) return invalidDate(); - - const timestamp = +date; - let time = 0; - let offset; - - if (dateStrings.time) { - time = parseTime(dateStrings.time); - if (isNaN(time)) return invalidDate(); - } - - if (dateStrings.timezone) { - offset = parseTimezone(dateStrings.timezone); - if (isNaN(offset)) return invalidDate(); - } else { - const tmpDate = new Date(timestamp + time); - const result = (0, _index3.toDate)(0, options?.in); - result.setFullYear( - tmpDate.getUTCFullYear(), - tmpDate.getUTCMonth(), - tmpDate.getUTCDate(), - ); - result.setHours( - tmpDate.getUTCHours(), - tmpDate.getUTCMinutes(), - tmpDate.getUTCSeconds(), - tmpDate.getUTCMilliseconds(), - ); - return result; - } - - return (0, _index3.toDate)(timestamp + time + offset, options?.in); -} - -const patterns = { - dateTimeDelimiter: /[T ]/, - timeZoneDelimiter: /[Z ]/i, - timezone: /([Z+-].*)$/, -}; - -const dateRegex = - /^-?(?:(\d{3})|(\d{2})(?:-?(\d{2}))?|W(\d{2})(?:-?(\d{1}))?|)$/; -const timeRegex = - /^(\d{2}(?:[.,]\d*)?)(?::?(\d{2}(?:[.,]\d*)?))?(?::?(\d{2}(?:[.,]\d*)?))?$/; -const timezoneRegex = /^([+-])(\d{2})(?::?(\d{2}))?$/; - -function splitDateString(dateString) { - const dateStrings = {}; - const array = dateString.split(patterns.dateTimeDelimiter); - let timeString; - - // The regex match should only return at maximum two array elements. - // [date], [time], or [date, time]. - if (array.length > 2) { - return dateStrings; - } - - if (/:/.test(array[0])) { - timeString = array[0]; - } else { - dateStrings.date = array[0]; - timeString = array[1]; - if (patterns.timeZoneDelimiter.test(dateStrings.date)) { - dateStrings.date = dateString.split(patterns.timeZoneDelimiter)[0]; - timeString = dateString.substr( - dateStrings.date.length, - dateString.length, - ); - } - } - - if (timeString) { - const token = patterns.timezone.exec(timeString); - if (token) { - dateStrings.time = timeString.replace(token[1], ""); - dateStrings.timezone = token[1]; - } else { - dateStrings.time = timeString; - } - } - - return dateStrings; -} - -function parseYear(dateString, additionalDigits) { - const regex = new RegExp( - "^(?:(\\d{4}|[+-]\\d{" + - (4 + additionalDigits) + - "})|(\\d{2}|[+-]\\d{" + - (2 + additionalDigits) + - "})$)", - ); - - const captures = dateString.match(regex); - // Invalid ISO-formatted year - if (!captures) return { year: NaN, restDateString: "" }; - - const year = captures[1] ? parseInt(captures[1]) : null; - const century = captures[2] ? parseInt(captures[2]) : null; - - // either year or century is null, not both - return { - year: century === null ? year : century * 100, - restDateString: dateString.slice((captures[1] || captures[2]).length), - }; -} - -function parseDate(dateString, year) { - // Invalid ISO-formatted year - if (year === null) return new Date(NaN); - - const captures = dateString.match(dateRegex); - // Invalid ISO-formatted string - if (!captures) return new Date(NaN); - - const isWeekDate = !!captures[4]; - const dayOfYear = parseDateUnit(captures[1]); - const month = parseDateUnit(captures[2]) - 1; - const day = parseDateUnit(captures[3]); - const week = parseDateUnit(captures[4]); - const dayOfWeek = parseDateUnit(captures[5]) - 1; - - if (isWeekDate) { - if (!validateWeekDate(year, week, dayOfWeek)) { - return new Date(NaN); - } - return dayOfISOWeekYear(year, week, dayOfWeek); - } else { - const date = new Date(0); - if ( - !validateDate(year, month, day) || - !validateDayOfYearDate(year, dayOfYear) - ) { - return new Date(NaN); - } - date.setUTCFullYear(year, month, Math.max(dayOfYear, day)); - return date; - } -} - -function parseDateUnit(value) { - return value ? parseInt(value) : 1; -} - -function parseTime(timeString) { - const captures = timeString.match(timeRegex); - if (!captures) return NaN; // Invalid ISO-formatted time - - const hours = parseTimeUnit(captures[1]); - const minutes = parseTimeUnit(captures[2]); - const seconds = parseTimeUnit(captures[3]); - - if (!validateTime(hours, minutes, seconds)) { - return NaN; - } - - return ( - hours * _index.millisecondsInHour + - minutes * _index.millisecondsInMinute + - seconds * 1000 - ); -} - -function parseTimeUnit(value) { - return (value && parseFloat(value.replace(",", "."))) || 0; -} - -function parseTimezone(timezoneString) { - if (timezoneString === "Z") return 0; - - const captures = timezoneString.match(timezoneRegex); - if (!captures) return 0; - - const sign = captures[1] === "+" ? -1 : 1; - const hours = parseInt(captures[2]); - const minutes = (captures[3] && parseInt(captures[3])) || 0; - - if (!validateTimezone(hours, minutes)) { - return NaN; - } - - return ( - sign * - (hours * _index.millisecondsInHour + minutes * _index.millisecondsInMinute) - ); -} - -function dayOfISOWeekYear(isoWeekYear, week, day) { - const date = new Date(0); - date.setUTCFullYear(isoWeekYear, 0, 4); - const fourthOfJanuaryDay = date.getUTCDay() || 7; - const diff = (week - 1) * 7 + day + 1 - fourthOfJanuaryDay; - date.setUTCDate(date.getUTCDate() + diff); - return date; -} - -// Validation functions - -// February is null to handle the leap year (using ||) -const daysInMonths = [31, null, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - -function isLeapYearIndex(year) { - return year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0); -} - -function validateDate(year, month, date) { - return ( - month >= 0 && - month <= 11 && - date >= 1 && - date <= (daysInMonths[month] || (isLeapYearIndex(year) ? 29 : 28)) - ); -} - -function validateDayOfYearDate(year, dayOfYear) { - return dayOfYear >= 1 && dayOfYear <= (isLeapYearIndex(year) ? 366 : 365); -} - -function validateWeekDate(_year, week, day) { - return week >= 1 && week <= 53 && day >= 0 && day <= 6; -} - -function validateTime(hours, minutes, seconds) { - if (hours === 24) { - return minutes === 0 && seconds === 0; - } - - return ( - seconds >= 0 && - seconds < 60 && - minutes >= 0 && - minutes < 60 && - hours >= 0 && - hours < 25 - ); -} - -function validateTimezone(_hours, minutes) { - return minutes >= 0 && minutes <= 59; -} - - -/***/ }), - -/***/ 26380: -/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { - -"use strict"; - -exports.parseJSON = parseJSON; -var _index = __nccwpck_require__(44826); - -/** - * The {@link parseJSON} function options. - */ - -/** - * Converts a complete ISO date string in UTC time, the typical format for transmitting - * a date in JSON, to a JavaScript `Date` instance. - * - * This is a minimal implementation for converting dates retrieved from a JSON API to - * a `Date` instance which can be used with other functions in the `date-fns` library. - * The following formats are supported: - * - * - `2000-03-15T05:20:10.123Z`: The output of `.toISOString()` and `JSON.stringify(new Date())` - * - `2000-03-15T05:20:10Z`: Without milliseconds - * - `2000-03-15T05:20:10+00:00`: With a zero offset, the default JSON encoded format in some other languages - * - `2000-03-15T05:20:10+05:45`: With a positive or negative offset, the default JSON encoded format in some other languages - * - `2000-03-15T05:20:10+0000`: With a zero offset without a colon - * - `2000-03-15T05:20:10`: Without a trailing 'Z' symbol - * - `2000-03-15T05:20:10.1234567`: Up to 7 digits in milliseconds field. Only first 3 are taken into account since JS does not allow fractional milliseconds - * - `2000-03-15 05:20:10`: With a space instead of a 'T' separator for APIs returning a SQL date without reformatting - * - * For convenience and ease of use these other input types are also supported - * via [toDate](https://date-fns.org/docs/toDate): - * - * - A `Date` instance will be cloned - * - A `number` will be treated as a timestamp - * - * Any other input type or invalid date strings will return an `Invalid Date`. - * - * @typeParam ResultDate - The result `Date` type, it is the type returned from the context function if it is passed, or inferred from the arguments. - * - * @param dateStr - A fully formed ISO8601 date string to convert - * @param options - An object with options - * - * @returns The parsed date in the local time zone - */ -function parseJSON(dateStr, options) { - const parts = dateStr.match( - /(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})(?:\.(\d{0,7}))?(?:Z|(.)(\d{2}):?(\d{2})?)?/, - ); - - if (!parts) return (0, _index.toDate)(NaN, options?.in); - - return (0, _index.toDate)( - Date.UTC( - +parts[1], - +parts[2] - 1, - +parts[3], - +parts[4] - (+parts[9] || 0) * (parts[8] == "-" ? -1 : 1), - +parts[5] - (+parts[10] || 0) * (parts[8] == "-" ? -1 : 1), - +parts[6], - +((parts[7] || "0") + "00").substring(0, 3), - ), - options?.in, - ); -} - - /***/ }), /***/ 80144: diff --git a/package-lock.json b/package-lock.json index 10995189..83ac4791 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,8 +17,8 @@ "@octokit/plugin-paginate-graphql": "^4.0.1", "@octokit/plugin-retry": "^6.1.0", "@octokit/request-error": "^5.1.1", - "@tiobe/http-client": "^0.5.0", - "@tiobe/install-tics": "^0.6.0", + "@tiobe/http-client": "^1.1.0", + "@tiobe/install-tics": "^0.6.2", "canonical-path": "^1.0.0", "date-fns": "^4.1.0", "lodash": "^4.17.23", @@ -1481,9 +1481,9 @@ } }, "node_modules/@jest/console/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -1891,9 +1891,9 @@ } }, "node_modules/@jest/core/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -2220,9 +2220,9 @@ } }, "node_modules/@jest/reporters/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -2391,9 +2391,9 @@ } }, "node_modules/@jest/reporters/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -2740,9 +2740,9 @@ } }, "node_modules/@jest/test-sequencer/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -3204,22 +3204,21 @@ } }, "node_modules/@tiobe/http-client": { - "version": "0.5.0", - "resolved": "https://artifacts.tiobe.com/repository/npm/@tiobe/http-client/-/http-client-0.5.0.tgz", - "integrity": "sha512-TT9e4NFvPDumOMwHoXFAWx5haGWL5a3rjoezRSXwr27L/DQUJLGSGx9BoWu/xwVtmwr4ebc8DnLLNZ0U6jVehw==", + "version": "1.1.0", + "resolved": "https://artifacts.tiobe.com/repository/npm/@tiobe/http-client/-/http-client-1.1.0.tgz", + "integrity": "sha512-ViyIb0kZXPU7DizhWFHGrn+b3v4V/ycQGsWNBPGDRW5+MxXq71GK+ucEBptI2nnkjRSCCtwq+9WwaVrUVVqwtA==", "license": "MIT", "dependencies": { - "@types/node-fetch": "^2.6.11", "node-fetch": "2.7.0" } }, "node_modules/@tiobe/install-tics": { - "version": "0.6.0", - "resolved": "https://artifacts.tiobe.com/repository/npm/@tiobe/install-tics/-/install-tics-0.6.0.tgz", - "integrity": "sha512-dbA1T4CJQkLn83u1SUxOzbMqb0PlceA34MEncwZo/h5VP3XAYGJvSG9JnxN2Kl/DWdtxHzz5UXeW4UqxMORWgg==", + "version": "0.6.2", + "resolved": "https://artifacts.tiobe.com/repository/npm/@tiobe/install-tics/-/install-tics-0.6.2.tgz", + "integrity": "sha512-MaHCC4ULIkdLjl8Gw90lrim+VZ89LtQUfd9sS+p2j3cLJ76GEIXkceXa62nz76uDGPWDO1Jf83kLzUossG+Nrg==", "license": "MIT", "dependencies": { - "@tiobe/http-client": "^0.5.0" + "@tiobe/http-client": "^1.1.0" } }, "node_modules/@tootallnate/quickjs-emscripten": { @@ -3541,9 +3540,9 @@ } }, "node_modules/@types/jest/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -3826,9 +3825,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -4375,9 +4374,10 @@ } }, "node_modules/archiver-utils/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -4776,9 +4776,9 @@ } }, "node_modules/babel-jest/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -4972,10 +4972,11 @@ "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6374,9 +6375,9 @@ } }, "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6893,9 +6894,9 @@ } }, "node_modules/jest-changed-files/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -7330,9 +7331,9 @@ } }, "node_modules/jest-circus/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -7507,9 +7508,9 @@ } }, "node_modules/jest-cli/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -7624,9 +7625,9 @@ } }, "node_modules/jest-config/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -7715,9 +7716,9 @@ } }, "node_modules/jest-config/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -7874,9 +7875,9 @@ } }, "node_modules/jest-each/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -8087,9 +8088,9 @@ } }, "node_modules/jest-environment-node/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -8655,9 +8656,9 @@ } }, "node_modules/jest-resolve-dependencies/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -8851,9 +8852,9 @@ } }, "node_modules/jest-resolve/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -9196,9 +9197,9 @@ } }, "node_modules/jest-runner/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -9487,9 +9488,9 @@ } }, "node_modules/jest-runtime/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -9756,9 +9757,9 @@ } }, "node_modules/jest-runtime/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -10066,9 +10067,9 @@ } }, "node_modules/jest-watcher/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -10807,10 +10808,11 @@ "dev": true }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -11093,9 +11095,10 @@ } }, "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } diff --git a/package.json b/package.json index d21aa670..234a18d5 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,8 @@ "@octokit/plugin-paginate-graphql": "^4.0.1", "@octokit/plugin-retry": "^6.1.0", "@octokit/request-error": "^5.1.1", - "@tiobe/http-client": "^0.5.0", - "@tiobe/install-tics": "^0.6.0", + "@tiobe/http-client": "^1.1.0", + "@tiobe/install-tics": "^0.6.2", "canonical-path": "^1.0.0", "date-fns": "^4.1.0", "lodash": "^4.17.23", diff --git a/src/analysis/qserver.ts b/src/analysis/qserver.ts index 6fc73c70..3cfa6f80 100644 --- a/src/analysis/qserver.ts +++ b/src/analysis/qserver.ts @@ -1,10 +1,11 @@ import { decorateAction } from '../action/decorate/action'; import { postToConversation } from '../action/decorate/pull-request'; import { createErrorSummaryBody, createNothingAnalyzedSummaryBody } from '../action/decorate/summary'; -import { githubConfig } from '../configuration/config'; +import { githubConfig, ticsConfig } from '../configuration/config'; import { createAndSetOutput } from '../github/output'; import { AnalysisResult, Verdict } from '../helper/interfaces'; import { runTicsAnalyzer } from '../tics/analyzer'; +import { createProject } from '../viewer/project'; import { getLastQServerRunDate } from '../viewer/qserver'; import { getAnalysisResult } from './qserver/analysis-result'; @@ -13,6 +14,10 @@ import { getAnalysisResult } from './qserver/analysis-result'; * @returns Verdict for a QServer run. */ export async function qServerAnalysis(): Promise { + if (ticsConfig.createProject) { + await createProject(); + } + const oldDate = await getLastQServerRunDate(); const analysis = await runTicsAnalyzer(''); diff --git a/src/configuration/tics-cli.ts b/src/configuration/tics-cli.ts index fcf080f1..733b97ad 100644 --- a/src/configuration/tics-cli.ts +++ b/src/configuration/tics-cli.ts @@ -3,6 +3,7 @@ import { getInput } from '@actions/core'; import { CliOption } from './interfaces'; import { Mode } from './tics'; import { logger } from '../helper/logger'; +import { githubConfig } from './config'; export class TicsCli { readonly project: string; @@ -18,7 +19,7 @@ export class TicsCli { readonly additionalFlags: string; constructor(mode: Mode) { - this.project = getInput('project'); + this.project = this.getProject(getInput('project'), mode); this.branchname = getInput('branchname'); this.branchdir = getInput('branchdir'); this.cdtoken = getInput('cdtoken'); @@ -41,6 +42,17 @@ export class TicsCli { } } + private getProject(input: string, mode: Mode): string { + // validate project + if (mode === Mode.QSERVER) { + if (input === 'auto') { + logger.info(`Parameter 'project' is not set, using the repository name (${githubConfig.reponame}) instead.`); + return githubConfig.reponame; + } + } + return input; + } + /** * Get the calc option or the default if not set by the user * @returns the calc option set by user or thedefault. @@ -60,13 +72,6 @@ export class TicsCli { * @throws error if project auto is used incorrectly. */ private validateCliOptions(cli: TicsCli, mode: Mode) { - // validate project - if (mode === Mode.QSERVER) { - if (cli.project === 'auto') { - throw Error(`Running TICS with project 'auto' is not possible with QServer`); - } - } - for (const option of CliOptions) { const key = option.action as keyof TicsCli; if (cli[key] !== '' && !option.modes.includes(mode)) { diff --git a/src/configuration/tics.ts b/src/configuration/tics.ts index 0f0bbd10..e4238eeb 100644 --- a/src/configuration/tics.ts +++ b/src/configuration/tics.ts @@ -26,6 +26,7 @@ export class TicsConfiguration { readonly mode: Mode; readonly ticsAuthToken: string; readonly trustStrategy: TrustStrategy; + readonly createProject: boolean; /** * The URL pointing to the "cfg" API endpoint of the TICS Viewer. Is used for running TICS. @@ -52,6 +53,7 @@ export class TicsConfiguration { this.ticsAuthToken = getInput('ticsAuthToken'); this.baseUrl = getBaseUrl(this.viewerUrl).href; this.displayUrl = this.validateAndGetDisplayUrl(getInput('displayUrl')); + this.createProject = this.validateAndGetCreateProject(getBooleanInput('createProject')); this.setVariables(); } @@ -151,6 +153,13 @@ export class TicsConfiguration { throw Error(`Parameter 'hostnameVerification' should be '1'/'true' or '0'/'false'. Input given is '${input}'`); } + private validateAndGetCreateProject(input: boolean): boolean { + if (this.mode !== Mode.QSERVER && input) { + throw Error(`Parameter 'createProject' can only be used in server mode`); + } + return input; + } + /** * Set all environment variables TICS needs to run in the GitHub setting. */ diff --git a/src/helper/response.ts b/src/helper/response.ts index 5809617a..7e88b260 100644 --- a/src/helper/response.ts +++ b/src/helper/response.ts @@ -1,6 +1,6 @@ import { RequestError as OctokitError } from '@octokit/request-error'; import { ClientResponse } from '@tiobe/http-client'; -import { RequestError as TicsError } from '@tiobe/http-client/lib/retry'; +import { RequestError as TicsError } from '@tiobe/http-client'; export function handleOctokitError(error: unknown): string { let message = 'reason unkown'; diff --git a/src/viewer/http-client.ts b/src/viewer/http-client.ts index f200b7a2..44c15e84 100644 --- a/src/viewer/http-client.ts +++ b/src/viewer/http-client.ts @@ -1,4 +1,4 @@ -import HttpClient from '@tiobe/http-client'; +import { HttpClient } from '@tiobe/http-client'; import { ProxyAgent } from 'proxy-agent'; import { ticsConfig, actionConfig } from '../configuration/config'; diff --git a/src/viewer/interfaces.d.ts b/src/viewer/interfaces.d.ts index a58946e3..9cab6e8f 100644 --- a/src/viewer/interfaces.d.ts +++ b/src/viewer/interfaces.d.ts @@ -170,7 +170,7 @@ export interface RunDateResponse { messages: string[]; coverage: number; status: string; - value: number; + value: number | null; }[]; dates: string; metrics: { diff --git a/src/viewer/project.ts b/src/viewer/project.ts new file mode 100644 index 00000000..4cf0e91c --- /dev/null +++ b/src/viewer/project.ts @@ -0,0 +1,52 @@ +import { ticsCli, ticsConfig } from '../configuration/config'; +import { logger } from '../helper/logger'; +import { getRetryErrorMessage } from '../helper/response'; +import { joinUrl } from '../helper/url'; +import { httpClient } from './http-client'; + +/** + * Gets the date of the last QServer run the viewer knows of. + * @throws Error if project cannot be created or does not exist. + */ +export async function createProject(): Promise { + const createProjectUrl = joinUrl(ticsConfig.baseUrl, `api/public/v1/fapi/Project`); + const branchName = getBranchName(); + const body = { + projectName: ticsCli.project, + branchName: branchName, + branchDir: ticsCli.branchdir, + calculate: true, + visible: true, + renameTo: { + branchName: branchName + } + }; + try { + logger.header('Creating/updating the TICS project'); + logger.debug(`With ${createProjectUrl}`); + await httpClient.put(createProjectUrl, JSON.stringify(body)); + } catch (error: unknown) { + const message = getRetryErrorMessage(error); + throw Error(`There was an error creating the project: ${message}`); + } +} + +/** + * Get the branchname of the project to create. + * If branchdir is not set, it will try to get the default branch or else 'main'. + */ +function getBranchName(): string { + if (ticsCli.branchname) { + return ticsCli.branchname; + } + + if (process.env.GITHUB_BASE_REF) { + return process.env.GITHUB_BASE_REF; + } + + if (process.env.GITHUB_REF_NAME) { + return process.env.GITHUB_REF_NAME; + } + + return 'main'; +} diff --git a/src/viewer/qserver.ts b/src/viewer/qserver.ts index 358f705a..f08a9ab5 100644 --- a/src/viewer/qserver.ts +++ b/src/viewer/qserver.ts @@ -21,6 +21,10 @@ export async function getLastQServerRunDate(): Promise { if (response.data.data.length === 0) { throw Error('Request returned empty array'); } + if (!response.data.data[0].value) { + // return -1 for projects that haven't run yet + return -1; + } return response.data.data[0].value / 1000; } catch (error: unknown) { const message = getRetryErrorMessage(error); diff --git a/test/integration/configuration.test.ts b/test/integration/configuration.test.ts index 38f29522..87808eb6 100644 --- a/test/integration/configuration.test.ts +++ b/test/integration/configuration.test.ts @@ -12,6 +12,7 @@ process.env.INPUT_POSTTOCONVERSATION = 'false'; process.env.INPUT_PULLREQUESTAPPROVAL = 'false'; process.env.INPUT_SHOWBLOCKINGAFTER = 'true'; process.env.INPUT_TRUSTSTRATEGY = 'strict'; +process.env.INPUT_CREATEPROJECT = 'false'; beforeEach(() => { jest.resetModules(); diff --git a/test/integration/httpclient.test.ts b/test/integration/httpclient.test.ts index 05e2ce34..eae724ee 100644 --- a/test/integration/httpclient.test.ts +++ b/test/integration/httpclient.test.ts @@ -25,12 +25,13 @@ process.env.INPUT_POSTTOCONVERSATION = 'false'; process.env.INPUT_PULLREQUESTAPPROVAL = 'false'; process.env.INPUT_SHOWBLOCKINGAFTER = 'true'; process.env.INPUT_TRUSTSTRATEGY = 'strict'; +process.env.INPUT_CREATEPROJECT = 'false'; // mock before importing httpClient jest.spyOn(process.stdout, 'write').mockImplementation((): any => {}); import { httpClient } from '../../src/viewer/http-client'; -import HttpClient from '@tiobe/http-client'; +import { HttpClient } from '@tiobe/http-client'; import { ProxyAgent } from 'proxy-agent'; describe('@actions/http-client (using http_proxy)', () => { diff --git a/test/integration/octokit.test.ts b/test/integration/octokit.test.ts index 1ec883b9..14039c6d 100644 --- a/test/integration/octokit.test.ts +++ b/test/integration/octokit.test.ts @@ -27,6 +27,7 @@ process.env.INPUT_POSTTOCONVERSATION = 'false'; process.env.INPUT_PULLREQUESTAPPROVAL = 'false'; process.env.INPUT_SHOWBLOCKINGAFTER = 'true'; process.env.INPUT_TRUSTSTRATEGY = 'strict'; +process.env.INPUT_CREATEPROJECT = 'false'; // mock before importing octokit jest.spyOn(process.stdout, 'write').mockImplementation((): any => {}); diff --git a/test/unit/configuration/tics-cli.test.ts b/test/unit/configuration/tics-cli.test.ts index 3068d2ca..d38229d9 100644 --- a/test/unit/configuration/tics-cli.test.ts +++ b/test/unit/configuration/tics-cli.test.ts @@ -76,7 +76,7 @@ describe('cli Configuration', () => { expect(cliServer).toMatchObject({ ...expectCli, branchdir: 'dir', project: 'project' }); }); - it('should throw error if mode is qserver and project is auto', () => { + it('should throw error if mode is qserver, project is auto and GITHUB_WORKSPACE is not available', () => { values = { project: 'auto' }; @@ -89,7 +89,7 @@ describe('cli Configuration', () => { } expect(error).toBeInstanceOf(Error); - expect(error.message).toContain("Running TICS with project 'auto' is not possible with QServer"); + expect(error.message).toContain('Parameter `branchdir` is not set and environment variable `GITHUB_WORKSPACE` is empty. TICSQServer cannot run.'); }); it('should add default branchdir if mode is qserver no branchdir is given', () => { diff --git a/test/unit/configuration/tics.test.ts b/test/unit/configuration/tics.test.ts index bcc80feb..e52603b1 100644 --- a/test/unit/configuration/tics.test.ts +++ b/test/unit/configuration/tics.test.ts @@ -16,7 +16,8 @@ describe('tICS Configuration', () => { viewerUrl: '', trustStrategy: 'strict', baseUrl: '', - displayUrl: '' + displayUrl: '', + createProject: false }; beforeEach(() => { @@ -383,6 +384,26 @@ describe('tICS Configuration', () => { expect(error.message).toContain("Parameter 'trustStrategy' should be one of 'strict', 'self-signed' or 'all'. Input given is 'self'"); }); }); + + describe('validate createProject', () => { + it('should throw if mode is client and createProject is true', () => { + values = { + ...values, + mode: 'client', + createProject: 'true' + }; + + let error: any; + try { + new TicsConfiguration(); + } catch (err) { + error = err; + } + + expect(error).toBeInstanceOf(Error); + expect(error.message).toEqual(`Parameter 'createProject' can only be used in server mode`); + }); + }); }); describe('environment tests', () => { @@ -396,7 +417,8 @@ describe('tICS Configuration', () => { ticsAuthToken: 'auth-token', viewerUrl: 'http://localhost/tiobeweb/TICS/api/cfg?name=default', trustStrategy: 'self-signed', - displayUrl: 'http://viewer.url' + displayUrl: 'http://viewer.url', + createProject: 'false' }; const ticsConfig = new TicsConfiguration(); @@ -411,7 +433,8 @@ describe('tICS Configuration', () => { viewerUrl: 'http://localhost/tiobeweb/TICS/api/cfg?name=default', trustStrategy: TrustStrategy.SELFSIGNED, baseUrl: 'http://localhost/tiobeweb/TICS', - displayUrl: 'http://viewer.url/' + displayUrl: 'http://viewer.url/', + createProject: false }); expect(process.env.TICSCI).toBe('1'); expect(process.env.TICSIDE).toBe('GITHUB'); @@ -431,7 +454,8 @@ describe('tICS Configuration', () => { ticsAuthToken: 'auth-token', viewerUrl: 'http://localhost/tiobeweb/TICS/api/cfg?name=default', trustStrategy: 'self-signed', - displayUrl: 'http://viewer.url' + displayUrl: 'http://viewer.url', + createProject: 'true' }; const ticsConfig = new TicsConfiguration(); @@ -446,7 +470,8 @@ describe('tICS Configuration', () => { viewerUrl: 'http://localhost/tiobeweb/TICS/api/cfg?name=default', trustStrategy: TrustStrategy.SELFSIGNED, baseUrl: 'http://localhost/tiobeweb/TICS', - displayUrl: 'http://viewer.url/' + displayUrl: 'http://viewer.url/', + createProject: true }); expect(process.env.TICSCI).toBe('1'); diff --git a/test/unit/helper/response.test.ts b/test/unit/helper/response.test.ts index f592a92a..b27fbb62 100644 --- a/test/unit/helper/response.test.ts +++ b/test/unit/helper/response.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from '@jest/globals'; import { RequestError } from '@octokit/request-error'; -import { RequestError as TicsError } from '@tiobe/http-client/lib/retry'; +import { RequestError as TicsError } from '@tiobe/http-client'; import { ClientResponse } from '@tiobe/http-client'; import { getRetryErrorMessage, getRetryMessage, handleOctokitError } from '../../../src/helper/response'; From bf59b140a46fd854833fb45cec7f5bc1fa9dc9c3 Mon Sep 17 00:00:00 2001 From: janssen <118828444+janssen-tiobe@users.noreply.github.com> Date: Mon, 30 Mar 2026 17:49:16 +0200 Subject: [PATCH 02/13] RM-35685: Fixed the tics-cli test failing in a github environment --- test/unit/configuration/tics-cli.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/unit/configuration/tics-cli.test.ts b/test/unit/configuration/tics-cli.test.ts index d38229d9..a9015019 100644 --- a/test/unit/configuration/tics-cli.test.ts +++ b/test/unit/configuration/tics-cli.test.ts @@ -81,6 +81,8 @@ describe('cli Configuration', () => { project: 'auto' }; + const GITHUB_WORKSPACE = process.env.GITHUB_WORKSPACE; + delete process.env.GITHUB_WORKSPACE; let error: any; try { new TicsCli(Mode.QSERVER); @@ -88,6 +90,7 @@ describe('cli Configuration', () => { error = err; } + process.env.GITHUB_WORKSPACE = GITHUB_WORKSPACE; expect(error).toBeInstanceOf(Error); expect(error.message).toContain('Parameter `branchdir` is not set and environment variable `GITHUB_WORKSPACE` is empty. TICSQServer cannot run.'); }); From e1dd9774d0af74d8eb00b4c57fbdccafaeb05a2d Mon Sep 17 00:00:00 2001 From: janssen <118828444+janssen-tiobe@users.noreply.github.com> Date: Tue, 31 Mar 2026 18:07:00 +0200 Subject: [PATCH 03/13] RM-35685: Added tests for code added --- dist/index.js | 2 +- src/viewer/project.ts | 2 +- test/.setup/mock.ts | 8 +- test/unit/analysis/qserver.test.ts | 3 + test/unit/viewer/project.test.ts | 114 +++++++++++++++++++++++++++++ test/unit/viewer/qserver.test.ts | 8 ++ test/unit/viewer/version.test.ts | 14 ++++ 7 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 test/unit/viewer/project.test.ts diff --git a/dist/index.js b/dist/index.js index 475ddd69..c25ff7ac 100644 --- a/dist/index.js +++ b/dist/index.js @@ -3130,7 +3130,7 @@ const http_client_1 = __nccwpck_require__(80939); * @throws Error if project cannot be created or does not exist. */ async function createProject() { - const createProjectUrl = (0, url_1.joinUrl)(config_1.ticsConfig.baseUrl, `api/public/v1/fapi/Project`); + const createProjectUrl = (0, url_1.joinUrl)(config_1.ticsConfig.baseUrl, 'api/public/v1/fapi/Project'); const branchName = getBranchName(); const body = { projectName: config_1.ticsCli.project, diff --git a/src/viewer/project.ts b/src/viewer/project.ts index 4cf0e91c..05f3876a 100644 --- a/src/viewer/project.ts +++ b/src/viewer/project.ts @@ -9,7 +9,7 @@ import { httpClient } from './http-client'; * @throws Error if project cannot be created or does not exist. */ export async function createProject(): Promise { - const createProjectUrl = joinUrl(ticsConfig.baseUrl, `api/public/v1/fapi/Project`); + const createProjectUrl = joinUrl(ticsConfig.baseUrl, 'api/public/v1/fapi/Project'); const branchName = getBranchName(); const body = { projectName: ticsCli.project, diff --git a/test/.setup/mock.ts b/test/.setup/mock.ts index ab55ccf2..0565ce1c 100644 --- a/test/.setup/mock.ts +++ b/test/.setup/mock.ts @@ -51,7 +51,8 @@ export const ticsConfigMock = { viewerUrl: '', trustStrategy: 'strict', baseUrl: '', - displayUrl: '' + displayUrl: '', + createProject: false }; export const actionConfigMock = { @@ -94,7 +95,10 @@ jest.mock('../../src/configuration/config', () => { jest.mock('../../src/viewer/http-client', () => { return { httpClient: { - get: jest.fn() + get: jest.fn(), + delete: jest.fn(), + post: jest.fn(), + put: jest.fn() } }; }); diff --git a/test/unit/analysis/qserver.test.ts b/test/unit/analysis/qserver.test.ts index 2111790d..c4e7fcbf 100644 --- a/test/unit/analysis/qserver.test.ts +++ b/test/unit/analysis/qserver.test.ts @@ -5,6 +5,7 @@ import * as pull_request from '../../../src/action/decorate/pull-request'; import * as qserver from '../../../src/analysis/qserver/analysis-result'; import * as summary from '../../../src/action/decorate/summary'; import * as viewer from '../../../src/viewer/qserver'; +import * as project from '../../../src/viewer/project'; import { githubConfigMock, ticsConfigMock } from '../../.setup/mock'; import { @@ -35,6 +36,7 @@ describe('setFailed checks (QServer)', () => { jest.spyOn(action, 'decorateAction'); jest.spyOn(summary, 'createNothingAnalyzedSummaryBody').mockResolvedValue('body'); + jest.spyOn(project, 'createProject').mockResolvedValue(); }); afterEach(() => { @@ -166,6 +168,7 @@ describe('setFailed checks (QServer)', () => { }); it('should return passing verdict if getAnalysisResult returns passing Quality Gate', async () => { + ticsConfigMock.createProject = true; spyGetLastQServerRunDate.mockResolvedValueOnce(123456000); spyGetLastQServerRunDate.mockResolvedValueOnce(123457000); spyAnalyzer.mockResolvedValue(analysisPassed); diff --git a/test/unit/viewer/project.test.ts b/test/unit/viewer/project.test.ts new file mode 100644 index 00000000..761af20f --- /dev/null +++ b/test/unit/viewer/project.test.ts @@ -0,0 +1,114 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { httpClient } from '../../../src/viewer/http-client'; +import { createProject } from '../../../src/viewer/project'; +import { ticsCliMock, ticsConfigMock } from '../../.setup/mock'; +import { SpiedFunction } from 'jest-mock'; + +describe('createProject', () => { + let putSpy: SpiedFunction; + + beforeAll(() => { + ticsConfigMock.baseUrl = 'http://base.url'; + }); + + beforeEach(() => { + putSpy = jest.spyOn(httpClient, 'put'); + jest.clearAllMocks(); + }); + + it('should pass creating a project using default branchdir', async () => { + putSpy.mockResolvedValue(''); + + await createProject(); + + expect(putSpy).toHaveBeenCalledWith( + 'http://base.url/api/public/v1/fapi/Project', + JSON.stringify({ + projectName: '', + branchName: 'main', + branchDir: '', + calculate: true, + visible: true, + renameTo: { branchName: 'main' } + }) + ); + }); + + it('should pass creating a project using branchname given by input', async () => { + ticsCliMock.branchname = 'branch'; + putSpy.mockResolvedValue(''); + + await createProject(); + + expect(putSpy).toHaveBeenCalledWith( + 'http://base.url/api/public/v1/fapi/Project', + JSON.stringify({ + projectName: '', + branchName: 'branch', + branchDir: '', + calculate: true, + visible: true, + renameTo: { branchName: 'branch' } + }) + ); + ticsCliMock.branchname = ''; + }); + + it('should pass creating a project using branchname given by environment GITHUB_BASE_REF', async () => { + const GITHUB_BASE_REF = process.env.GITHUB_BASE_REF; + process.env.GITHUB_BASE_REF = 'branch'; + putSpy.mockResolvedValue(''); + + await createProject(); + + expect(putSpy).toHaveBeenCalledWith( + 'http://base.url/api/public/v1/fapi/Project', + JSON.stringify({ + projectName: '', + branchName: 'branch', + branchDir: '', + calculate: true, + visible: true, + renameTo: { branchName: 'branch' } + }) + ); + process.env.GITHUB_BASE_REF = GITHUB_BASE_REF; + }); + + it('should pass creating a project using branchname given by environment GITHUB_REF_NAME', async () => { + const GITHUB_BASE_REF = process.env.GITHUB_BASE_REF; + process.env.GITHUB_BASE_REF = ''; + const GITHUB_REF_NAME = process.env.GITHUB_REF_NAME; + process.env.GITHUB_REF_NAME = 'branch'; + putSpy.mockResolvedValue(''); + + await createProject(); + + expect(putSpy).toHaveBeenCalledWith( + 'http://base.url/api/public/v1/fapi/Project', + JSON.stringify({ + projectName: '', + branchName: 'branch', + branchDir: '', + calculate: true, + visible: true, + renameTo: { branchName: 'branch' } + }) + ); + process.env.GITHUB_BASE_REF = GITHUB_BASE_REF; + process.env.GITHUB_BASE_REF = GITHUB_REF_NAME; + }); + + it('should throw error when viewer returns error', async () => { + putSpy.mockRejectedValue(Error()); + + let error: any; + try { + await createProject(); + } catch (err) { + error = err; + } + + expect(error).toBeInstanceOf(Error); + }); +}); diff --git a/test/unit/viewer/qserver.test.ts b/test/unit/viewer/qserver.test.ts index caf92aba..cdf93b7c 100644 --- a/test/unit/viewer/qserver.test.ts +++ b/test/unit/viewer/qserver.test.ts @@ -26,6 +26,14 @@ describe('getQualityGate', () => { expect(error).toBeInstanceOf(Error); }); + it('should return -1 on project that has not run yet', async () => { + (jest.spyOn(httpClient, 'get') as any).mockResolvedValue({ data: { data: [{ value: undefined }] } }); + + const response = await getLastQServerRunDate(); + + expect(response).toBe(-1); + }); + it('should throw error on faulty get in getQualityGate', async () => { jest.spyOn(httpClient, 'get').mockRejectedValue(Error()); diff --git a/test/unit/viewer/version.test.ts b/test/unit/viewer/version.test.ts index a5b9693e..4d4fcb89 100644 --- a/test/unit/viewer/version.test.ts +++ b/test/unit/viewer/version.test.ts @@ -60,4 +60,18 @@ describe('getViewerVersion', () => { expect(response).toBeTruthy(); }); + + it('should throw viewer returns unparsable version', async () => { + jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { version: null }, retryCount: 0, status: 200 }); + + let error: any; + try { + await viewerVersion.viewerSupports(ViewerFeature.GITHUB_ACTION); + } catch (err) { + error = err; + } + + expect(error).toBeInstanceOf(Error); + expect(error.message).toStrictEqual('Could not compute version received by the viewer, got: null.'); + }); }); From b6080cb6eb87b980f66d7fed5013ef159496f241 Mon Sep 17 00:00:00 2001 From: janssen <118828444+janssen-tiobe@users.noreply.github.com> Date: Tue, 31 Mar 2026 22:33:48 +0200 Subject: [PATCH 04/13] RM-35685: Fixed running project tests in github environment --- test/unit/viewer/project.test.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/unit/viewer/project.test.ts b/test/unit/viewer/project.test.ts index 761af20f..dc77212b 100644 --- a/test/unit/viewer/project.test.ts +++ b/test/unit/viewer/project.test.ts @@ -17,6 +17,10 @@ describe('createProject', () => { }); it('should pass creating a project using default branchdir', async () => { + const GITHUB_BASE_REF = process.env.GITHUB_BASE_REF; + process.env.GITHUB_BASE_REF = ''; + const GITHUB_REF_NAME = process.env.GITHUB_REF_NAME; + process.env.GITHUB_REF_NAME = ''; putSpy.mockResolvedValue(''); await createProject(); @@ -32,6 +36,8 @@ describe('createProject', () => { renameTo: { branchName: 'main' } }) ); + process.env.GITHUB_BASE_REF = GITHUB_BASE_REF; + process.env.GITHUB_BASE_REF = GITHUB_REF_NAME; }); it('should pass creating a project using branchname given by input', async () => { @@ -79,7 +85,7 @@ describe('createProject', () => { const GITHUB_BASE_REF = process.env.GITHUB_BASE_REF; process.env.GITHUB_BASE_REF = ''; const GITHUB_REF_NAME = process.env.GITHUB_REF_NAME; - process.env.GITHUB_REF_NAME = 'branch'; + process.env.GITHUB_REF_NAME = 'branches'; putSpy.mockResolvedValue(''); await createProject(); @@ -88,11 +94,11 @@ describe('createProject', () => { 'http://base.url/api/public/v1/fapi/Project', JSON.stringify({ projectName: '', - branchName: 'branch', + branchName: 'branches', branchDir: '', calculate: true, visible: true, - renameTo: { branchName: 'branch' } + renameTo: { branchName: 'branches' } }) ); process.env.GITHUB_BASE_REF = GITHUB_BASE_REF; From 990c227eefd99cfb8ac951dc85e0527c3f2fa239 Mon Sep 17 00:00:00 2001 From: janssen <118828444+janssen-tiobe@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:23:36 +0200 Subject: [PATCH 05/13] RM-35685: Let the viewer decide on the branchname for the project to create --- dist/index.js | 28 ++-------- src/viewer/project.ts | 33 ++--------- test/unit/viewer/project.test.ts | 94 +++++++++----------------------- 3 files changed, 38 insertions(+), 117 deletions(-) diff --git a/dist/index.js b/dist/index.js index c25ff7ac..53f89045 100644 --- a/dist/index.js +++ b/dist/index.js @@ -3131,43 +3131,25 @@ const http_client_1 = __nccwpck_require__(80939); */ async function createProject() { const createProjectUrl = (0, url_1.joinUrl)(config_1.ticsConfig.baseUrl, 'api/public/v1/fapi/Project'); - const branchName = getBranchName(); const body = { projectName: config_1.ticsCli.project, - branchName: branchName, branchDir: config_1.ticsCli.branchdir, calculate: true, - visible: true, - renameTo: { - branchName: branchName - } + visible: true }; try { logger_1.logger.header('Creating/updating the TICS project'); logger_1.logger.debug(`With ${createProjectUrl}`); - await http_client_1.httpClient.put(createProjectUrl, JSON.stringify(body)); + const response = await http_client_1.httpClient.put(createProjectUrl, JSON.stringify(body)); + if (response.data.alertMessages.length > 0) { + logger_1.logger.info(response.data.alertMessages[0].header); + } } catch (error) { const message = (0, response_1.getRetryErrorMessage)(error); throw Error(`There was an error creating the project: ${message}`); } } -/** - * Get the branchname of the project to create. - * If branchdir is not set, it will try to get the default branch or else 'main'. - */ -function getBranchName() { - if (config_1.ticsCli.branchname) { - return config_1.ticsCli.branchname; - } - if (process.env.GITHUB_BASE_REF) { - return process.env.GITHUB_BASE_REF; - } - if (process.env.GITHUB_REF_NAME) { - return process.env.GITHUB_REF_NAME; - } - return 'main'; -} /***/ }), diff --git a/src/viewer/project.ts b/src/viewer/project.ts index 05f3876a..3a5d7553 100644 --- a/src/viewer/project.ts +++ b/src/viewer/project.ts @@ -1,3 +1,4 @@ +import { HttpBadRequestResponse } from '@tiobe/http-client'; import { ticsCli, ticsConfig } from '../configuration/config'; import { logger } from '../helper/logger'; import { getRetryErrorMessage } from '../helper/response'; @@ -10,43 +11,21 @@ import { httpClient } from './http-client'; */ export async function createProject(): Promise { const createProjectUrl = joinUrl(ticsConfig.baseUrl, 'api/public/v1/fapi/Project'); - const branchName = getBranchName(); const body = { projectName: ticsCli.project, - branchName: branchName, branchDir: ticsCli.branchdir, calculate: true, - visible: true, - renameTo: { - branchName: branchName - } + visible: true }; try { logger.header('Creating/updating the TICS project'); logger.debug(`With ${createProjectUrl}`); - await httpClient.put(createProjectUrl, JSON.stringify(body)); + const response = await httpClient.put(createProjectUrl, JSON.stringify(body)); + if (response.data.alertMessages.length > 0) { + logger.info(response.data.alertMessages[0].header); + } } catch (error: unknown) { const message = getRetryErrorMessage(error); throw Error(`There was an error creating the project: ${message}`); } } - -/** - * Get the branchname of the project to create. - * If branchdir is not set, it will try to get the default branch or else 'main'. - */ -function getBranchName(): string { - if (ticsCli.branchname) { - return ticsCli.branchname; - } - - if (process.env.GITHUB_BASE_REF) { - return process.env.GITHUB_BASE_REF; - } - - if (process.env.GITHUB_REF_NAME) { - return process.env.GITHUB_REF_NAME; - } - - return 'main'; -} diff --git a/test/unit/viewer/project.test.ts b/test/unit/viewer/project.test.ts index dc77212b..59cb504e 100644 --- a/test/unit/viewer/project.test.ts +++ b/test/unit/viewer/project.test.ts @@ -3,9 +3,11 @@ import { httpClient } from '../../../src/viewer/http-client'; import { createProject } from '../../../src/viewer/project'; import { ticsCliMock, ticsConfigMock } from '../../.setup/mock'; import { SpiedFunction } from 'jest-mock'; +import { logger } from '../../../src/helper/logger'; describe('createProject', () => { let putSpy: SpiedFunction; + let infoSpy: SpiedFunction; beforeAll(() => { ticsConfigMock.baseUrl = 'http://base.url'; @@ -13,96 +15,54 @@ describe('createProject', () => { beforeEach(() => { putSpy = jest.spyOn(httpClient, 'put'); + infoSpy = jest.spyOn(logger, 'info'); jest.clearAllMocks(); }); - it('should pass creating a project using default branchdir', async () => { - const GITHUB_BASE_REF = process.env.GITHUB_BASE_REF; - process.env.GITHUB_BASE_REF = ''; - const GITHUB_REF_NAME = process.env.GITHUB_REF_NAME; - process.env.GITHUB_REF_NAME = ''; - putSpy.mockResolvedValue(''); + it('should pass creating a project and log message if returned', async () => { + ticsCliMock.project = 'create-project'; + ticsCliMock.branchdir = '.'; + putSpy.mockResolvedValue({ + data: { + alertMessages: [ + { + header: `Created database created (took 4s, dbversion: 143), Added project 'PROJECTS => created' to configuration"` + } + ] + } + }); await createProject(); expect(putSpy).toHaveBeenCalledWith( 'http://base.url/api/public/v1/fapi/Project', JSON.stringify({ - projectName: '', - branchName: 'main', - branchDir: '', + projectName: 'create-project', + branchDir: '.', calculate: true, - visible: true, - renameTo: { branchName: 'main' } + visible: true }) ); - process.env.GITHUB_BASE_REF = GITHUB_BASE_REF; - process.env.GITHUB_BASE_REF = GITHUB_REF_NAME; + expect(infoSpy).toHaveBeenCalledWith(`Created database created (took 4s, dbversion: 143), Added project 'PROJECTS => created' to configuration"`); }); - it('should pass creating a project using branchname given by input', async () => { - ticsCliMock.branchname = 'branch'; - putSpy.mockResolvedValue(''); + it('should pass creating a project and not log message if not returned', async () => { + ticsCliMock.project = 'create-project'; + ticsCliMock.branchdir = '.'; + putSpy.mockResolvedValue({ data: { alertMessages: [] } }); await createProject(); expect(putSpy).toHaveBeenCalledWith( 'http://base.url/api/public/v1/fapi/Project', JSON.stringify({ - projectName: '', - branchName: 'branch', - branchDir: '', + projectName: 'create-project', + branchDir: '.', calculate: true, - visible: true, - renameTo: { branchName: 'branch' } + visible: true }) ); - ticsCliMock.branchname = ''; - }); - - it('should pass creating a project using branchname given by environment GITHUB_BASE_REF', async () => { - const GITHUB_BASE_REF = process.env.GITHUB_BASE_REF; - process.env.GITHUB_BASE_REF = 'branch'; - putSpy.mockResolvedValue(''); - - await createProject(); - - expect(putSpy).toHaveBeenCalledWith( - 'http://base.url/api/public/v1/fapi/Project', - JSON.stringify({ - projectName: '', - branchName: 'branch', - branchDir: '', - calculate: true, - visible: true, - renameTo: { branchName: 'branch' } - }) - ); - process.env.GITHUB_BASE_REF = GITHUB_BASE_REF; - }); - - it('should pass creating a project using branchname given by environment GITHUB_REF_NAME', async () => { - const GITHUB_BASE_REF = process.env.GITHUB_BASE_REF; - process.env.GITHUB_BASE_REF = ''; - const GITHUB_REF_NAME = process.env.GITHUB_REF_NAME; - process.env.GITHUB_REF_NAME = 'branches'; - putSpy.mockResolvedValue(''); - - await createProject(); - - expect(putSpy).toHaveBeenCalledWith( - 'http://base.url/api/public/v1/fapi/Project', - JSON.stringify({ - projectName: '', - branchName: 'branches', - branchDir: '', - calculate: true, - visible: true, - renameTo: { branchName: 'branches' } - }) - ); - process.env.GITHUB_BASE_REF = GITHUB_BASE_REF; - process.env.GITHUB_BASE_REF = GITHUB_REF_NAME; + expect(infoSpy).toHaveBeenCalledTimes(0); }); it('should throw error when viewer returns error', async () => { From 52047dd8605be4465832378aa2955d61ed9a2038 Mon Sep 17 00:00:00 2001 From: janssen <118828444+janssen-tiobe@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:46:29 +0200 Subject: [PATCH 06/13] RM-35685: Getting the configuration from input `viewerUrl` and using it the project creation call --- dist/index.js | 16 ++++++++++------ src/configuration/tics.ts | 18 ++++++++++++------ src/viewer/project.ts | 2 +- test/.setup/mock.ts | 1 + test/unit/configuration/tics.test.ts | 9 +++++++++ test/unit/viewer/project.test.ts | 6 ++++-- 6 files changed, 37 insertions(+), 15 deletions(-) diff --git a/dist/index.js b/dist/index.js index 53f89045..1cef6d46 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1355,6 +1355,7 @@ class TicsConfiguration { * The URL pointing to the "cfg" API endpoint of the TICS Viewer. Is used for running TICS. */ viewerUrl; + configuration; /** * Derived of the viewerUrl. Is used for performing API calls. */ @@ -1365,7 +1366,9 @@ class TicsConfiguration { */ displayUrl; constructor() { - this.viewerUrl = this.validateAndGetViewerUrl((0, core_1.getInput)('viewerUrl', { required: true })); + const viewerUrlAndConfiguration = this.validateAndGetViewerUrlAndConfiguration((0, core_1.getInput)('viewerUrl', { required: true })); + this.viewerUrl = viewerUrlAndConfiguration.href; + this.configuration = viewerUrlAndConfiguration.configuration; this.mode = this.validateAndGetMode((0, core_1.getInput)('mode')); this.githubToken = (0, core_1.getInput)('githubToken'); this.installTics = (0, core_1.getBooleanInput)('installTics'); @@ -1383,18 +1386,19 @@ class TicsConfiguration { * @returns the input if it is correct. * @throws error if the input is incorrect. */ - validateAndGetViewerUrl(url) { + validateAndGetViewerUrlAndConfiguration(url) { const uri = this.validateAndGetUrl(url, 'viewerUrl'); if (uri.protocol !== 'http:' && uri.protocol !== 'https:') { throw Error(`Parameter 'viewerUrl' is missing the protocol (http(s)://)`); } - else if (!uri.pathname.endsWith('/api/cfg')) { + if (!uri.pathname.endsWith('/api/cfg')) { throw Error(`Parameter 'viewerUrl' is missing path /api/cfg`); } - else if (!uri.searchParams.has('name') || uri.searchParams.get('name') === '') { + const cfgParam = uri.searchParams.get('name'); + if (!cfgParam || cfgParam === '') { throw Error(`Parameter 'viewerUrl' is missing the configuration. (eg: /cfg?name=default)`); } - return uri.href; + return { href: uri.href, configuration: cfgParam }; } /** * Validates if the given input is a valid url and returns it. @@ -3130,7 +3134,7 @@ const http_client_1 = __nccwpck_require__(80939); * @throws Error if project cannot be created or does not exist. */ async function createProject() { - const createProjectUrl = (0, url_1.joinUrl)(config_1.ticsConfig.baseUrl, 'api/public/v1/fapi/Project'); + const createProjectUrl = (0, url_1.joinUrl)(config_1.ticsConfig.baseUrl, `api/public/v1/fapi/Project?cfg=${config_1.ticsConfig.configuration}`); const body = { projectName: config_1.ticsCli.project, branchDir: config_1.ticsCli.branchdir, diff --git a/src/configuration/tics.ts b/src/configuration/tics.ts index e4238eeb..0789482f 100644 --- a/src/configuration/tics.ts +++ b/src/configuration/tics.ts @@ -32,6 +32,7 @@ export class TicsConfiguration { * The URL pointing to the "cfg" API endpoint of the TICS Viewer. Is used for running TICS. */ readonly viewerUrl: string; + readonly configuration: string; /** * Derived of the viewerUrl. Is used for performing API calls. */ @@ -43,7 +44,10 @@ export class TicsConfiguration { readonly displayUrl: string; constructor() { - this.viewerUrl = this.validateAndGetViewerUrl(getInput('viewerUrl', { required: true })); + const viewerUrlAndConfiguration = this.validateAndGetViewerUrlAndConfiguration(getInput('viewerUrl', { required: true })); + this.viewerUrl = viewerUrlAndConfiguration.href; + this.configuration = viewerUrlAndConfiguration.configuration; + this.mode = this.validateAndGetMode(getInput('mode')); this.githubToken = getInput('githubToken'); this.installTics = getBooleanInput('installTics'); @@ -63,18 +67,20 @@ export class TicsConfiguration { * @returns the input if it is correct. * @throws error if the input is incorrect. */ - private validateAndGetViewerUrl(url: string): string { + private validateAndGetViewerUrlAndConfiguration(url: string): { href: string; configuration: string } { const uri = this.validateAndGetUrl(url, 'viewerUrl'); if (uri.protocol !== 'http:' && uri.protocol !== 'https:') { throw Error(`Parameter 'viewerUrl' is missing the protocol (http(s)://)`); - } else if (!uri.pathname.endsWith('/api/cfg')) { + } + if (!uri.pathname.endsWith('/api/cfg')) { throw Error(`Parameter 'viewerUrl' is missing path /api/cfg`); - } else if (!uri.searchParams.has('name') || uri.searchParams.get('name') === '') { + } + const cfgParam = uri.searchParams.get('name'); + if (!cfgParam || cfgParam === '') { throw Error(`Parameter 'viewerUrl' is missing the configuration. (eg: /cfg?name=default)`); } - - return uri.href; + return { href: uri.href, configuration: cfgParam }; } /** diff --git a/src/viewer/project.ts b/src/viewer/project.ts index 3a5d7553..920068d0 100644 --- a/src/viewer/project.ts +++ b/src/viewer/project.ts @@ -10,7 +10,7 @@ import { httpClient } from './http-client'; * @throws Error if project cannot be created or does not exist. */ export async function createProject(): Promise { - const createProjectUrl = joinUrl(ticsConfig.baseUrl, 'api/public/v1/fapi/Project'); + const createProjectUrl = joinUrl(ticsConfig.baseUrl, `api/public/v1/fapi/Project?cfg=${ticsConfig.configuration}`); const body = { projectName: ticsCli.project, branchDir: ticsCli.branchdir, diff --git a/test/.setup/mock.ts b/test/.setup/mock.ts index 0565ce1c..826702e6 100644 --- a/test/.setup/mock.ts +++ b/test/.setup/mock.ts @@ -49,6 +49,7 @@ export const ticsConfigMock = { installTics: false, mode: 'client', viewerUrl: '', + configuration: '', trustStrategy: 'strict', baseUrl: '', displayUrl: '', diff --git a/test/unit/configuration/tics.test.ts b/test/unit/configuration/tics.test.ts index e52603b1..ab3bc6e7 100644 --- a/test/unit/configuration/tics.test.ts +++ b/test/unit/configuration/tics.test.ts @@ -14,6 +14,7 @@ describe('tICS Configuration', () => { mode: '', ticsAuthToken: '', viewerUrl: '', + configuration: '', trustStrategy: 'strict', baseUrl: '', displayUrl: '', @@ -157,6 +158,7 @@ describe('tICS Configuration', () => { expect(ticsConfig).toMatchObject({ ...expectDefault, viewerUrl: 'http://test.com/tiobeweb/TICS/api/cfg?name=asdf', + configuration: 'asdf', mode: Mode.CLIENT, baseUrl: 'http://test.com/tiobeweb/TICS', displayUrl: 'http://test.com/tiobeweb/TICS' @@ -166,6 +168,7 @@ describe('tICS Configuration', () => { it('should return correct https URL and set base- and displayUrl', () => { values = { viewerUrl: 'https://test.com/tiobeweb/TICS/api/cfg?name=asdf', + configuration: 'asdf', mode: 'client' }; @@ -174,6 +177,7 @@ describe('tICS Configuration', () => { expect(ticsConfig).toMatchObject({ ...expectDefault, viewerUrl: 'https://test.com/tiobeweb/TICS/api/cfg?name=asdf', + configuration: 'asdf', mode: Mode.CLIENT, baseUrl: 'https://test.com/tiobeweb/TICS', displayUrl: 'https://test.com/tiobeweb/TICS' @@ -183,6 +187,7 @@ describe('tICS Configuration', () => { it('should throw error if incorrect displayUrl', () => { values = { viewerUrl: 'https://test.com/tiobeweb/TICS/api/cfg?name=asdf', + configuration: 'asdf', mode: 'client', displayUrl: 'localhost' }; @@ -201,6 +206,7 @@ describe('tICS Configuration', () => { it('should return different displayUrl from baseUrl', () => { values = { viewerUrl: 'https://test.com/tiobeweb/TICS/api/cfg?name=asdf', + configuration: 'asdf', mode: 'client', displayUrl: 'http://viewer.url' }; @@ -210,6 +216,7 @@ describe('tICS Configuration', () => { expect(ticsConfig).toMatchObject({ ...expectDefault, viewerUrl: 'https://test.com/tiobeweb/TICS/api/cfg?name=asdf', + configuration: 'asdf', mode: Mode.CLIENT, baseUrl: 'https://test.com/tiobeweb/TICS', displayUrl: 'http://viewer.url/' @@ -221,12 +228,14 @@ describe('tICS Configuration', () => { beforeEach(() => { values = { viewerUrl: 'https://test.com/tiobeweb/TICS/api/cfg?name=asdf', + configuration: 'asdf', mode: 'client' }; expectDefault = { ...expectDefault, viewerUrl: 'https://test.com/tiobeweb/TICS/api/cfg?name=asdf', + configuration: 'asdf', mode: Mode.CLIENT, baseUrl: 'https://test.com/tiobeweb/TICS', displayUrl: 'https://test.com/tiobeweb/TICS' diff --git a/test/unit/viewer/project.test.ts b/test/unit/viewer/project.test.ts index 59cb504e..379379bd 100644 --- a/test/unit/viewer/project.test.ts +++ b/test/unit/viewer/project.test.ts @@ -20,6 +20,7 @@ describe('createProject', () => { }); it('should pass creating a project and log message if returned', async () => { + ticsConfigMock.configuration = 'default'; ticsCliMock.project = 'create-project'; ticsCliMock.branchdir = '.'; putSpy.mockResolvedValue({ @@ -35,7 +36,7 @@ describe('createProject', () => { await createProject(); expect(putSpy).toHaveBeenCalledWith( - 'http://base.url/api/public/v1/fapi/Project', + 'http://base.url/api/public/v1/fapi/Project?cfg=default', JSON.stringify({ projectName: 'create-project', branchDir: '.', @@ -47,6 +48,7 @@ describe('createProject', () => { }); it('should pass creating a project and not log message if not returned', async () => { + ticsConfigMock.configuration = 'default'; ticsCliMock.project = 'create-project'; ticsCliMock.branchdir = '.'; putSpy.mockResolvedValue({ data: { alertMessages: [] } }); @@ -54,7 +56,7 @@ describe('createProject', () => { await createProject(); expect(putSpy).toHaveBeenCalledWith( - 'http://base.url/api/public/v1/fapi/Project', + 'http://base.url/api/public/v1/fapi/Project?cfg=default', JSON.stringify({ projectName: 'create-project', branchDir: '.', From b8efb7bea4816262a159f92a2d8704b432d03281 Mon Sep 17 00:00:00 2001 From: janssen <118828444+janssen-tiobe@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:57:12 +0200 Subject: [PATCH 07/13] RM-35685: Fixed circular imports of calling config in tics-cli and tics-cli in config --- dist/index.js | 13 ++++----- src/configuration/config.ts | 2 +- src/configuration/tics-cli.ts | 11 ++++--- test/unit/configuration/tics-cli.test.ts | 37 +++++++++++++++--------- 4 files changed, 36 insertions(+), 27 deletions(-) diff --git a/dist/index.js b/dist/index.js index 1cef6d46..28d8cd5e 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1039,7 +1039,7 @@ const github_1 = __nccwpck_require__(54406); exports.actionConfig = new action_1.ActionConfiguration(); exports.githubConfig = new github_1.GithubConfig(); exports.ticsConfig = new tics_1.TicsConfiguration(); -exports.ticsCli = new tics_cli_1.TicsCli(exports.ticsConfig.mode); +exports.ticsCli = new tics_cli_1.TicsCli(exports.ticsConfig.mode, exports.githubConfig.reponame); /***/ }), @@ -1194,7 +1194,6 @@ exports.CliOptions = exports.TicsCli = void 0; const core_1 = __nccwpck_require__(37484); const tics_1 = __nccwpck_require__(5232); const logger_1 = __nccwpck_require__(66113); -const config_1 = __nccwpck_require__(34151); class TicsCli { project; branchname; @@ -1207,8 +1206,8 @@ class TicsCli { recalc; tmpdir; additionalFlags; - constructor(mode) { - this.project = this.getProject((0, core_1.getInput)('project'), mode); + constructor(mode, repoName) { + this.project = this.getProject((0, core_1.getInput)('project'), mode, repoName); this.branchname = (0, core_1.getInput)('branchname'); this.branchdir = (0, core_1.getInput)('branchdir'); this.cdtoken = (0, core_1.getInput)('cdtoken'); @@ -1228,12 +1227,12 @@ class TicsCli { this.branchdir = process.env.GITHUB_WORKSPACE; } } - getProject(input, mode) { + getProject(input, mode, repoName) { // validate project if (mode === tics_1.Mode.QSERVER) { if (input === 'auto') { - logger_1.logger.info(`Parameter 'project' is not set, using the repository name (${config_1.githubConfig.reponame}) instead.`); - return config_1.githubConfig.reponame; + logger_1.logger.info(`Parameter 'project' is not set, using the repository name (${repoName}) instead.`); + return repoName; } } return input; diff --git a/src/configuration/config.ts b/src/configuration/config.ts index ea488b20..a39d7c5d 100644 --- a/src/configuration/config.ts +++ b/src/configuration/config.ts @@ -7,4 +7,4 @@ export const actionConfig = new ActionConfiguration(); export const githubConfig = new GithubConfig(); export const ticsConfig = new TicsConfiguration(); -export const ticsCli = new TicsCli(ticsConfig.mode); +export const ticsCli = new TicsCli(ticsConfig.mode, githubConfig.reponame); diff --git a/src/configuration/tics-cli.ts b/src/configuration/tics-cli.ts index 733b97ad..02dcb969 100644 --- a/src/configuration/tics-cli.ts +++ b/src/configuration/tics-cli.ts @@ -3,7 +3,6 @@ import { getInput } from '@actions/core'; import { CliOption } from './interfaces'; import { Mode } from './tics'; import { logger } from '../helper/logger'; -import { githubConfig } from './config'; export class TicsCli { readonly project: string; @@ -18,8 +17,8 @@ export class TicsCli { readonly tmpdir: string; readonly additionalFlags: string; - constructor(mode: Mode) { - this.project = this.getProject(getInput('project'), mode); + constructor(mode: Mode, repoName: string) { + this.project = this.getProject(getInput('project'), mode, repoName); this.branchname = getInput('branchname'); this.branchdir = getInput('branchdir'); this.cdtoken = getInput('cdtoken'); @@ -42,12 +41,12 @@ export class TicsCli { } } - private getProject(input: string, mode: Mode): string { + private getProject(input: string, mode: Mode, repoName: string): string { // validate project if (mode === Mode.QSERVER) { if (input === 'auto') { - logger.info(`Parameter 'project' is not set, using the repository name (${githubConfig.reponame}) instead.`); - return githubConfig.reponame; + logger.info(`Parameter 'project' is not set, using the repository name (${repoName}) instead.`); + return repoName; } } return input; diff --git a/test/unit/configuration/tics-cli.test.ts b/test/unit/configuration/tics-cli.test.ts index a9015019..43f7fbe1 100644 --- a/test/unit/configuration/tics-cli.test.ts +++ b/test/unit/configuration/tics-cli.test.ts @@ -46,8 +46,8 @@ describe('cli Configuration', () => { project: 'auto' }; - const cliClient = new TicsCli(Mode.CLIENT); - const cliDiagnostic = new TicsCli(Mode.DIAGNOSTIC); + const cliClient = new TicsCli(Mode.CLIENT, 'repoName'); + const cliDiagnostic = new TicsCli(Mode.DIAGNOSTIC, 'repoName'); expect(cliClient).toMatchObject({ ...expectCli, project: 'auto', calc: 'GATE' }); expect(cliDiagnostic).toMatchObject({ ...expectCli, project: 'auto' }); @@ -58,8 +58,8 @@ describe('cli Configuration', () => { project: 'project' }; - const cliClient = new TicsCli(Mode.CLIENT); - const cliDiagnostic = new TicsCli(Mode.DIAGNOSTIC); + const cliClient = new TicsCli(Mode.CLIENT, 'repoName'); + const cliDiagnostic = new TicsCli(Mode.DIAGNOSTIC, 'repoName'); expect(cliClient).toMatchObject({ ...expectCli, project: 'project', calc: 'GATE' }); expect(cliDiagnostic).toMatchObject({ ...expectCli, project: 'project' }); @@ -71,21 +71,32 @@ describe('cli Configuration', () => { branchdir: 'dir' }; - const cliServer = new TicsCli(Mode.QSERVER); + const cliServer = new TicsCli(Mode.QSERVER, 'repoName'); expect(cliServer).toMatchObject({ ...expectCli, branchdir: 'dir', project: 'project' }); }); - it('should throw error if mode is qserver, project is auto and GITHUB_WORKSPACE is not available', () => { + it('should use repository name when mode is qserver and project is auto', () => { values = { - project: 'auto' + project: 'auto', + branchdir: 'dir' }; + const cliServer = new TicsCli(Mode.QSERVER, 'repoName'); + + expect(cliServer).toMatchObject({ ...expectCli, branchdir: 'dir', project: 'repoName' }); + }); + + it('should throw error if mode is qserver, branchdir is not given and GITHUB_WORKSPACE is not available', () => { const GITHUB_WORKSPACE = process.env.GITHUB_WORKSPACE; delete process.env.GITHUB_WORKSPACE; + values = { + branchdir: '' + }; + let error: any; try { - new TicsCli(Mode.QSERVER); + new TicsCli(Mode.QSERVER, 'repoName'); } catch (err) { error = err; } @@ -100,7 +111,7 @@ describe('cli Configuration', () => { project: 'project' }; process.env.GITHUB_WORKSPACE = '/workspace/project'; - const cliServer = new TicsCli(Mode.QSERVER); + const cliServer = new TicsCli(Mode.QSERVER, 'repoName'); expect(cliServer).toMatchObject({ project: 'project', branchdir: '/workspace/project' }); }); @@ -113,7 +124,7 @@ describe('cli Configuration', () => { let error: any; try { - new TicsCli(Mode.QSERVER); + new TicsCli(Mode.QSERVER, 'repoName'); } catch (err) { error = err; } @@ -137,7 +148,7 @@ describe('cli Configuration', () => { additionalFlags: '-log 9' }; - const cliServer = new TicsCli(Mode.QSERVER); + const cliServer = new TicsCli(Mode.QSERVER, 'repoName'); expect(cliServer).toMatchObject({ project: 'project', @@ -173,7 +184,7 @@ describe('cli Configuration', () => { additionalFlags: '-log 9' }; - const cliClient = new TicsCli(Mode.CLIENT); + const cliClient = new TicsCli(Mode.CLIENT, 'repoName'); expect(cliClient).toMatchObject({ project: 'project', @@ -208,7 +219,7 @@ describe('cli Configuration', () => { additionalFlags: '-log 9' }; - const cliDiagnostic = new TicsCli(Mode.DIAGNOSTIC); + const cliDiagnostic = new TicsCli(Mode.DIAGNOSTIC, 'repoName'); expect(cliDiagnostic).toMatchObject({ project: 'project', From c2e150f131d4709b389e61aeecb9e913801ccc97 Mon Sep 17 00:00:00 2001 From: janssen <118828444+janssen-tiobe@users.noreply.github.com> Date: Wed, 1 Apr 2026 15:30:25 +0200 Subject: [PATCH 08/13] RM-35685: Passing branchdir again, but only using override if branchname is set --- dist/index.js | 7 +++++++ src/viewer/project.ts | 8 ++++++++ test/unit/viewer/project.test.ts | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/dist/index.js b/dist/index.js index 28d8cd5e..32bf880a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -3137,6 +3137,7 @@ async function createProject() { const body = { projectName: config_1.ticsCli.project, branchDir: config_1.ticsCli.branchdir, + branchName: getBranchName(), calculate: true, visible: true }; @@ -3153,6 +3154,12 @@ async function createProject() { throw Error(`There was an error creating the project: ${message}`); } } +function getBranchName() { + if (config_1.ticsCli.branchname) { + return config_1.ticsCli.branchname; + } + return 'main'; +} /***/ }), diff --git a/src/viewer/project.ts b/src/viewer/project.ts index 920068d0..ab363b76 100644 --- a/src/viewer/project.ts +++ b/src/viewer/project.ts @@ -14,6 +14,7 @@ export async function createProject(): Promise { const body = { projectName: ticsCli.project, branchDir: ticsCli.branchdir, + branchName: getBranchName(), calculate: true, visible: true }; @@ -29,3 +30,10 @@ export async function createProject(): Promise { throw Error(`There was an error creating the project: ${message}`); } } + +function getBranchName() { + if (ticsCli.branchname) { + return ticsCli.branchname; + } + return 'main'; +} diff --git a/test/unit/viewer/project.test.ts b/test/unit/viewer/project.test.ts index 379379bd..5edf8dab 100644 --- a/test/unit/viewer/project.test.ts +++ b/test/unit/viewer/project.test.ts @@ -40,6 +40,36 @@ describe('createProject', () => { JSON.stringify({ projectName: 'create-project', branchDir: '.', + branchName: 'main', + calculate: true, + visible: true + }) + ); + expect(infoSpy).toHaveBeenCalledWith(`Created database created (took 4s, dbversion: 143), Added project 'PROJECTS => created' to configuration"`); + }); + + it('should pass creating a project using branchname given by input', async () => { + ticsConfigMock.configuration = 'default'; + ticsCliMock.project = 'create-project'; + ticsCliMock.branchname = 'branch'; + putSpy.mockResolvedValue({ + data: { + alertMessages: [ + { + header: `Created database created (took 4s, dbversion: 143), Added project 'PROJECTS => created' to configuration"` + } + ] + } + }); + + await createProject(); + + expect(putSpy).toHaveBeenCalledWith( + 'http://base.url/api/public/v1/fapi/Project?cfg=default', + JSON.stringify({ + projectName: 'create-project', + branchDir: '.', + branchName: 'branch', calculate: true, visible: true }) @@ -51,6 +81,7 @@ describe('createProject', () => { ticsConfigMock.configuration = 'default'; ticsCliMock.project = 'create-project'; ticsCliMock.branchdir = '.'; + ticsCliMock.branchname = ''; putSpy.mockResolvedValue({ data: { alertMessages: [] } }); await createProject(); @@ -60,6 +91,7 @@ describe('createProject', () => { JSON.stringify({ projectName: 'create-project', branchDir: '.', + branchName: 'main', calculate: true, visible: true }) From a0a067d699d0ceaba004e5dae38687254971c5e1 Mon Sep 17 00:00:00 2001 From: janssen <118828444+janssen-tiobe@users.noreply.github.com> Date: Wed, 1 Apr 2026 16:29:34 +0200 Subject: [PATCH 09/13] RM-35685: Made input project mandatory again in qserver mode. --- README.md | 19 +++++---- action.yaml | 2 +- dist/index.js | 15 +++---- src/configuration/config.ts | 2 +- src/configuration/tics-cli.ts | 14 ++++--- test/unit/configuration/tics-cli.test.ts | 52 +++++++++++++++++------- 6 files changed, 66 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 6f2de36f..fe8adbcb 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ The following options allow to instrument TICS Client more specifically: | `branchname` | Name of the branch in TICS. | - | | `codetype` | Allows you to pick which specific types of code you want to analyze with the TICS client. Options are `PRODUCTION`, `TESTCODE`, `EXTERNAL` and `GENERATED`. | `PRODUCTION` | | `excludeMovedFiles` | Exclude moved files from analysis even if there are modifications in the file. | `false` | -| `showAnnotationSeverity` | Show TICS violations with at least the specified severity in the changed files window (will also show up in `outputs.annotations`). Options are `blocking`, `blocking-after` or `issue`. This feature requires TICS Viewer 2025.1.8 or later. | `blocking-after` | +| `showAnnotationSeverity` | Show TICS violations with at least the specified severity in the changed files window (will also show up in `outputs.annotations`). Options are `blocking`, `blocking-after` or `issue`. This feature requires TICS Viewer 2025.1.8 or later. | `blocking-after` | | `tmpdir` | Location to store debug information. | - | #### Deprecated parameters @@ -154,15 +154,16 @@ The following inputs are recommended or required for this action: The following options allow to instrument TICSQServer more specifically: -| Input | Description | Default | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `calc` | Comma-separated list of [metrics](https://ticsdocumentation.tiobe.com/latest/docs/index.html#doc=user/clientoptions.html%23MetricAliases) to be calculated. | `ALL` | -| `recalc` | Comma-separated list of [metrics](https://ticsdocumentation.tiobe.com/latest/docs/index.html#doc=user/clientoptions.html%23MetricAliases) to be recalculated. | - | -| `nocalc` | Comma-separated list of [metrics](https://ticsdocumentation.tiobe.com/latest/docs/index.html#doc=user/clientoptions.html%23MetricAliases) to not be calculated. | - | -| `norecalc` | Comma-separated list of [metrics](https://ticsdocumentation.tiobe.com/latest/docs/index.html#doc=user/clientoptions.html%23MetricAliases) to not be recalculated. | - | -| `branchname` | Name of the branch in TICS. | - | +| Input | Description | Default | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `calc` | Comma-separated list of [metrics](https://ticsdocumentation.tiobe.com/latest/docs/index.html#doc=user/clientoptions.html%23MetricAliases) to be calculated. | `ALL` | +| `recalc` | Comma-separated list of [metrics](https://ticsdocumentation.tiobe.com/latest/docs/index.html#doc=user/clientoptions.html%23MetricAliases) to be recalculated. | - | +| `nocalc` | Comma-separated list of [metrics](https://ticsdocumentation.tiobe.com/latest/docs/index.html#doc=user/clientoptions.html%23MetricAliases) to not be calculated. | - | +| `norecalc` | Comma-separated list of [metrics](https://ticsdocumentation.tiobe.com/latest/docs/index.html#doc=user/clientoptions.html%23MetricAliases) to not be recalculated. | - | +| `branchname` | Name of the branch in TICS. | - | +| `createProject` | Create the project in the TICS Viewer if it does not exist already. | `false` | | `showAnnotationSeverity` | Show TICS violations with at least the specified severity in the changed files window (will also show up in `outputs.annotations`). Options are `blocking`, `blocking-after` or `issue`. This feature requires TICS Viewer 2025.1.8 or later. | `blocking-after` | -| `tmpdir` | Location to store debug information. | - | +| `tmpdir` | Location to store debug information. | - | #### Deprecated parameters diff --git a/action.yaml b/action.yaml index 47df1cc7..1b4e6af1 100644 --- a/action.yaml +++ b/action.yaml @@ -10,7 +10,7 @@ inputs: required: false default: client project: - description: Name of the TICS project. In client mode this will default to project `auto`, in server mode it will use the repository name. + description: Name of the TICS project. In client mode this will default to project `auto`, in server mode it is mandatory. required: false default: auto githubToken: diff --git a/dist/index.js b/dist/index.js index 32bf880a..af53ccde 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1039,7 +1039,7 @@ const github_1 = __nccwpck_require__(54406); exports.actionConfig = new action_1.ActionConfiguration(); exports.githubConfig = new github_1.GithubConfig(); exports.ticsConfig = new tics_1.TicsConfiguration(); -exports.ticsCli = new tics_cli_1.TicsCli(exports.ticsConfig.mode, exports.githubConfig.reponame); +exports.ticsCli = new tics_cli_1.TicsCli(exports.ticsConfig.mode); /***/ }), @@ -1206,8 +1206,8 @@ class TicsCli { recalc; tmpdir; additionalFlags; - constructor(mode, repoName) { - this.project = this.getProject((0, core_1.getInput)('project'), mode, repoName); + constructor(mode) { + this.project = this.getProject((0, core_1.getInput)('project'), mode); this.branchname = (0, core_1.getInput)('branchname'); this.branchdir = (0, core_1.getInput)('branchdir'); this.cdtoken = (0, core_1.getInput)('cdtoken'); @@ -1227,12 +1227,13 @@ class TicsCli { this.branchdir = process.env.GITHUB_WORKSPACE; } } - getProject(input, mode, repoName) { - // validate project + getProject(input, mode) { + if (input === '') { + throw Error('Parameter `project` is emtpy, TICS cannot run without it.'); + } if (mode === tics_1.Mode.QSERVER) { if (input === 'auto') { - logger_1.logger.info(`Parameter 'project' is not set, using the repository name (${repoName}) instead.`); - return repoName; + throw Error(`Running TICS with project 'auto' is not possible with QServer`); } } return input; diff --git a/src/configuration/config.ts b/src/configuration/config.ts index a39d7c5d..ea488b20 100644 --- a/src/configuration/config.ts +++ b/src/configuration/config.ts @@ -7,4 +7,4 @@ export const actionConfig = new ActionConfiguration(); export const githubConfig = new GithubConfig(); export const ticsConfig = new TicsConfiguration(); -export const ticsCli = new TicsCli(ticsConfig.mode, githubConfig.reponame); +export const ticsCli = new TicsCli(ticsConfig.mode); diff --git a/src/configuration/tics-cli.ts b/src/configuration/tics-cli.ts index 02dcb969..950c0f63 100644 --- a/src/configuration/tics-cli.ts +++ b/src/configuration/tics-cli.ts @@ -17,8 +17,8 @@ export class TicsCli { readonly tmpdir: string; readonly additionalFlags: string; - constructor(mode: Mode, repoName: string) { - this.project = this.getProject(getInput('project'), mode, repoName); + constructor(mode: Mode) { + this.project = this.getProject(getInput('project'), mode); this.branchname = getInput('branchname'); this.branchdir = getInput('branchdir'); this.cdtoken = getInput('cdtoken'); @@ -41,12 +41,14 @@ export class TicsCli { } } - private getProject(input: string, mode: Mode, repoName: string): string { - // validate project + private getProject(input: string, mode: Mode): string { + if (input === '') { + throw Error('Parameter `project` is emtpy, TICS cannot run without it.'); + } + if (mode === Mode.QSERVER) { if (input === 'auto') { - logger.info(`Parameter 'project' is not set, using the repository name (${repoName}) instead.`); - return repoName; + throw Error(`Running TICS with project 'auto' is not possible with QServer`); } } return input; diff --git a/test/unit/configuration/tics-cli.test.ts b/test/unit/configuration/tics-cli.test.ts index 43f7fbe1..9b366f88 100644 --- a/test/unit/configuration/tics-cli.test.ts +++ b/test/unit/configuration/tics-cli.test.ts @@ -46,8 +46,8 @@ describe('cli Configuration', () => { project: 'auto' }; - const cliClient = new TicsCli(Mode.CLIENT, 'repoName'); - const cliDiagnostic = new TicsCli(Mode.DIAGNOSTIC, 'repoName'); + const cliClient = new TicsCli(Mode.CLIENT); + const cliDiagnostic = new TicsCli(Mode.DIAGNOSTIC); expect(cliClient).toMatchObject({ ...expectCli, project: 'auto', calc: 'GATE' }); expect(cliDiagnostic).toMatchObject({ ...expectCli, project: 'auto' }); @@ -58,8 +58,8 @@ describe('cli Configuration', () => { project: 'project' }; - const cliClient = new TicsCli(Mode.CLIENT, 'repoName'); - const cliDiagnostic = new TicsCli(Mode.DIAGNOSTIC, 'repoName'); + const cliClient = new TicsCli(Mode.CLIENT); + const cliDiagnostic = new TicsCli(Mode.DIAGNOSTIC); expect(cliClient).toMatchObject({ ...expectCli, project: 'project', calc: 'GATE' }); expect(cliDiagnostic).toMatchObject({ ...expectCli, project: 'project' }); @@ -71,32 +71,56 @@ describe('cli Configuration', () => { branchdir: 'dir' }; - const cliServer = new TicsCli(Mode.QSERVER, 'repoName'); + const cliServer = new TicsCli(Mode.QSERVER); expect(cliServer).toMatchObject({ ...expectCli, branchdir: 'dir', project: 'project' }); }); - it('should use repository name when mode is qserver and project is auto', () => { + it('should pass when mode is qserver and project is empty', () => { + values = { + project: '', + branchdir: 'dir' + }; + + let error: any; + try { + new TicsCli(Mode.QSERVER); + } catch (err) { + error = err; + } + + expect(error).toBeInstanceOf(Error); + expect(error.message).toContain('Parameter `project` is emtpy, TICS cannot run without it.'); + }); + + it('should pass when mode is qserver and project is not given', () => { values = { project: 'auto', branchdir: 'dir' }; - const cliServer = new TicsCli(Mode.QSERVER, 'repoName'); + let error: any; + try { + new TicsCli(Mode.QSERVER); + } catch (err) { + error = err; + } - expect(cliServer).toMatchObject({ ...expectCli, branchdir: 'dir', project: 'repoName' }); + expect(error).toBeInstanceOf(Error); + expect(error.message).toContain(`Running TICS with project 'auto' is not possible with QServer`); }); it('should throw error if mode is qserver, branchdir is not given and GITHUB_WORKSPACE is not available', () => { const GITHUB_WORKSPACE = process.env.GITHUB_WORKSPACE; delete process.env.GITHUB_WORKSPACE; values = { + project: 'project', branchdir: '' }; let error: any; try { - new TicsCli(Mode.QSERVER, 'repoName'); + new TicsCli(Mode.QSERVER); } catch (err) { error = err; } @@ -111,7 +135,7 @@ describe('cli Configuration', () => { project: 'project' }; process.env.GITHUB_WORKSPACE = '/workspace/project'; - const cliServer = new TicsCli(Mode.QSERVER, 'repoName'); + const cliServer = new TicsCli(Mode.QSERVER); expect(cliServer).toMatchObject({ project: 'project', branchdir: '/workspace/project' }); }); @@ -124,7 +148,7 @@ describe('cli Configuration', () => { let error: any; try { - new TicsCli(Mode.QSERVER, 'repoName'); + new TicsCli(Mode.QSERVER); } catch (err) { error = err; } @@ -148,7 +172,7 @@ describe('cli Configuration', () => { additionalFlags: '-log 9' }; - const cliServer = new TicsCli(Mode.QSERVER, 'repoName'); + const cliServer = new TicsCli(Mode.QSERVER); expect(cliServer).toMatchObject({ project: 'project', @@ -184,7 +208,7 @@ describe('cli Configuration', () => { additionalFlags: '-log 9' }; - const cliClient = new TicsCli(Mode.CLIENT, 'repoName'); + const cliClient = new TicsCli(Mode.CLIENT); expect(cliClient).toMatchObject({ project: 'project', @@ -219,7 +243,7 @@ describe('cli Configuration', () => { additionalFlags: '-log 9' }; - const cliDiagnostic = new TicsCli(Mode.DIAGNOSTIC, 'repoName'); + const cliDiagnostic = new TicsCli(Mode.DIAGNOSTIC); expect(cliDiagnostic).toMatchObject({ project: 'project', From 8bad447b60af12f8bbb1bdfb72797ef90b4c596f Mon Sep 17 00:00:00 2001 From: janssen <118828444+janssen-tiobe@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:34 +0200 Subject: [PATCH 10/13] RM-35685: Fixed wrong description of function, logging all messages retrieved from the viewer in project creation, small code improvement. --- dist/index.js | 14 ++++++-------- src/configuration/tics-cli.ts | 7 ++----- src/viewer/project.ts | 8 ++++---- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/dist/index.js b/dist/index.js index af53ccde..4724ae8a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1231,10 +1231,8 @@ class TicsCli { if (input === '') { throw Error('Parameter `project` is emtpy, TICS cannot run without it.'); } - if (mode === tics_1.Mode.QSERVER) { - if (input === 'auto') { - throw Error(`Running TICS with project 'auto' is not possible with QServer`); - } + if (input === 'auto' && mode === tics_1.Mode.QSERVER) { + throw Error(`Running TICS with project 'auto' is not possible with QServer`); } return input; } @@ -3130,8 +3128,8 @@ const response_1 = __nccwpck_require__(93590); const url_1 = __nccwpck_require__(71112); const http_client_1 = __nccwpck_require__(80939); /** - * Gets the date of the last QServer run the viewer knows of. - * @throws Error if project cannot be created or does not exist. + * Creates a project in the viewer if it does not exist. + * @throws Error if project cannot be created. */ async function createProject() { const createProjectUrl = (0, url_1.joinUrl)(config_1.ticsConfig.baseUrl, `api/public/v1/fapi/Project?cfg=${config_1.ticsConfig.configuration}`); @@ -3146,8 +3144,8 @@ async function createProject() { logger_1.logger.header('Creating/updating the TICS project'); logger_1.logger.debug(`With ${createProjectUrl}`); const response = await http_client_1.httpClient.put(createProjectUrl, JSON.stringify(body)); - if (response.data.alertMessages.length > 0) { - logger_1.logger.info(response.data.alertMessages[0].header); + for (const message of response.data.alertMessages) { + logger_1.logger.info(message.header); } } catch (error) { diff --git a/src/configuration/tics-cli.ts b/src/configuration/tics-cli.ts index 950c0f63..9c501434 100644 --- a/src/configuration/tics-cli.ts +++ b/src/configuration/tics-cli.ts @@ -45,11 +45,8 @@ export class TicsCli { if (input === '') { throw Error('Parameter `project` is emtpy, TICS cannot run without it.'); } - - if (mode === Mode.QSERVER) { - if (input === 'auto') { - throw Error(`Running TICS with project 'auto' is not possible with QServer`); - } + if (input === 'auto' && mode === Mode.QSERVER) { + throw Error(`Running TICS with project 'auto' is not possible with QServer`); } return input; } diff --git a/src/viewer/project.ts b/src/viewer/project.ts index ab363b76..9f8fb25a 100644 --- a/src/viewer/project.ts +++ b/src/viewer/project.ts @@ -6,8 +6,8 @@ import { joinUrl } from '../helper/url'; import { httpClient } from './http-client'; /** - * Gets the date of the last QServer run the viewer knows of. - * @throws Error if project cannot be created or does not exist. + * Creates a project in the viewer if it does not exist. + * @throws Error if project cannot be created. */ export async function createProject(): Promise { const createProjectUrl = joinUrl(ticsConfig.baseUrl, `api/public/v1/fapi/Project?cfg=${ticsConfig.configuration}`); @@ -22,8 +22,8 @@ export async function createProject(): Promise { logger.header('Creating/updating the TICS project'); logger.debug(`With ${createProjectUrl}`); const response = await httpClient.put(createProjectUrl, JSON.stringify(body)); - if (response.data.alertMessages.length > 0) { - logger.info(response.data.alertMessages[0].header); + for (const message of response.data.alertMessages) { + logger.info(message.header); } } catch (error: unknown) { const message = getRetryErrorMessage(error); From 864573ce5ea1eff3204efb2a146302dd387c0883 Mon Sep 17 00:00:00 2001 From: janssen <118828444+janssen-tiobe@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:38:19 +0200 Subject: [PATCH 11/13] RM-35685: Added scmtool information to project creation, removed SemVer dependency and implemented own version check --- README.md | 2 +- dist/index.js | 2820 +----------------------------- dist/licenses.txt | 19 - package-lock.json | 17 +- package.json | 4 +- src/main.ts | 2 + src/viewer/interfaces.d.ts | 2 +- src/viewer/project.ts | 8 +- src/viewer/version.ts | 52 +- test/unit/main.test.ts | 19 + test/unit/viewer/project.test.ts | 18 +- test/unit/viewer/version.test.ts | 10 +- 12 files changed, 171 insertions(+), 2802 deletions(-) diff --git a/README.md b/README.md index fe8adbcb..63bc47b7 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ The following options allow to instrument TICSQServer more specifically: | `nocalc` | Comma-separated list of [metrics](https://ticsdocumentation.tiobe.com/latest/docs/index.html#doc=user/clientoptions.html%23MetricAliases) to not be calculated. | - | | `norecalc` | Comma-separated list of [metrics](https://ticsdocumentation.tiobe.com/latest/docs/index.html#doc=user/clientoptions.html%23MetricAliases) to not be recalculated. | - | | `branchname` | Name of the branch in TICS. | - | -| `createProject` | Create the project in the TICS Viewer if it does not exist already. | `false` | +| `createProject` | Create the project in the TICS Viewer if it does not exist already (requires a viewer of version 2026.1.2 or higher). | `false` | | `showAnnotationSeverity` | Show TICS violations with at least the specified severity in the changed files window (will also show up in `outputs.annotations`). Options are `blocking`, `blocking-after` or `issue`. This feature requires TICS Viewer 2025.1.8 or later. | `blocking-after` | | `tmpdir` | Location to store debug information. | - | diff --git a/dist/index.js b/dist/index.js index 4724ae8a..08463a64 100644 --- a/dist/index.js +++ b/dist/index.js @@ -3138,7 +3138,11 @@ async function createProject() { branchDir: config_1.ticsCli.branchdir, branchName: getBranchName(), calculate: true, - visible: true + visible: true, + scmTool: { + name: 'Git', + db: config_1.githubConfig.reponame + } }; try { logger_1.logger.header('Creating/updating the TICS project'); @@ -3282,33 +3286,26 @@ const logger_1 = __nccwpck_require__(66113); const url_1 = __nccwpck_require__(71112); const http_client_1 = __nccwpck_require__(80939); const config_1 = __nccwpck_require__(34151); -const semver_1 = __nccwpck_require__(62088); var ViewerFeature; (function (ViewerFeature) { - ViewerFeature["GITHUB_ACTION"] = ">=2022.4.0"; - ViewerFeature["NEW_ANNOTATIONS"] = ">=2025.1.8"; + ViewerFeature["GITHUB_ACTION"] = "2022.4.0"; + ViewerFeature["NEW_ANNOTATIONS"] = "2025.1.8"; + ViewerFeature["PROJECT_CREATION"] = "2026.1.2.54221"; })(ViewerFeature || (exports.ViewerFeature = ViewerFeature = {})); class ViewerVersion { viewerVersion; - /** - * Gets the version of the TICS viewer used. - * @returns Version of the used TICS viewer. - */ async viewerSupports(feature) { if (this.viewerVersion !== undefined) { - logger_1.logger.debug(`Getting version from cache: ${this.viewerVersion.version}`); - return (0, semver_1.satisfies)(this.viewerVersion, feature); + logger_1.logger.debug(`Getting version from cache: ${this.viewerVersion}`); + return this.satisfies(this.viewerVersion, feature); } const viewerVersion = await this.fetchViewerVersion(); - const cleanVersion = (0, semver_1.coerce)(viewerVersion.version); - if (cleanVersion !== null) { - logger_1.logger.info(`Found viewer with version: ${cleanVersion.version}`); - this.viewerVersion = cleanVersion; - } - else { - throw Error(`Could not compute version received by the viewer, got: ${viewerVersion.version}.`); + if (!viewerVersion.version) { + throw Error(`Viewer returned empty version.`); } - return (0, semver_1.satisfies)(cleanVersion, feature); + this.viewerVersion = viewerVersion.version; + logger_1.logger.info(`Found viewer with version: ${this.viewerVersion}`); + return this.satisfies(this.viewerVersion, feature); } async fetchViewerVersion() { const getViewerVersionUrl = (0, url_1.joinUrl)(config_1.ticsConfig.baseUrl, '/api/v1/version'); @@ -3325,6 +3322,27 @@ class ViewerVersion { throw Error(`There was an error retrieving the Viewer version: ${message}`); } } + /** + * Checks if version given is at least as high as the minimum version given. + */ + satisfies(version, minimum) { + const v = this.parseVersion(version); + const m = this.parseVersion(minimum); + const len = Math.max(v.length, m.length); + for (let i = 0; i < len; i++) { + const a = v[i] ?? 0; + const b = m[i] ?? 0; + if (a !== b) + return a > b; + } + return true; + } + parseVersion(version) { + return version + .replace(/^[^\d]+|[^\d]+$/g, '') + .split('.') + .map(Number); + } } exports.viewerVersion = new ViewerVersion(); @@ -95222,7 +95240,7 @@ module.exports = setCacheAdd; * @name has * @memberOf SetCache * @param {*} value The value to search for. - * @returns {number} Returns `true` if `value` is found, else `false`. + * @returns {boolean} Returns `true` if `value` is found, else `false`. */ function setCacheHas(value) { return this.__data__.has(value); @@ -96153,7 +96171,7 @@ module.exports = keysIn; var undefined; /** Used as the semantic version number. */ - var VERSION = '4.17.23'; + var VERSION = '4.18.1'; /** Used as the size to enable large array optimizations. */ var LARGE_ARRAY_SIZE = 200; @@ -96161,7 +96179,8 @@ module.exports = keysIn; /** Error message constants. */ var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://npms.io/search?q=ponyfill.', FUNC_ERROR_TEXT = 'Expected a function', - INVALID_TEMPL_VAR_ERROR_TEXT = 'Invalid `variable` option passed into `_.template`'; + INVALID_TEMPL_VAR_ERROR_TEXT = 'Invalid `variable` option passed into `_.template`', + INVALID_TEMPL_IMPORTS_ERROR_TEXT = 'Invalid `imports` option passed into `_.template`'; /** Used to stand-in for `undefined` hash values. */ var HASH_UNDEFINED = '__lodash_hash_undefined__'; @@ -97893,6 +97912,10 @@ module.exports = keysIn; * embedded Ruby (ERB) as well as ES2015 template strings. Change the * following template settings to use alternative delimiters. * + * **Security:** See + * [threat model](https://github.com/lodash/lodash/blob/main/threat-model.md) + * — `_.template` is insecure and will be removed in v5. + * * @static * @memberOf _ * @type {Object} @@ -98441,7 +98464,7 @@ module.exports = keysIn; * @name has * @memberOf SetCache * @param {*} value The value to search for. - * @returns {number} Returns `true` if `value` is found, else `false`. + * @returns {boolean} Returns `true` if `value` is found, else `false`. */ function setCacheHas(value) { return this.__data__.has(value); @@ -100512,7 +100535,9 @@ module.exports = keysIn; function baseUnset(object, path) { path = castPath(path, object); - // Prevent prototype pollution, see: https://github.com/lodash/lodash/security/advisories/GHSA-xxjr-mmjv-4gpg + // Prevent prototype pollution: + // https://github.com/lodash/lodash/security/advisories/GHSA-xxjr-mmjv-4gpg + // https://github.com/lodash/lodash/security/advisories/GHSA-f23m-r3pf-42rh var index = -1, length = path.length; @@ -100520,32 +100545,17 @@ module.exports = keysIn; return true; } - var isRootPrimitive = object == null || (typeof object !== 'object' && typeof object !== 'function'); - while (++index < length) { - var key = path[index]; - - // skip non-string keys (e.g., Symbols, numbers) - if (typeof key !== 'string') { - continue; - } + var key = toKey(path[index]); // Always block "__proto__" anywhere in the path if it's not expected if (key === '__proto__' && !hasOwnProperty.call(object, '__proto__')) { return false; } - // Block "constructor.prototype" chains - if (key === 'constructor' && - (index + 1) < length && - typeof path[index + 1] === 'string' && - path[index + 1] === 'prototype') { - - // Allow ONLY when the path starts at a primitive root, e.g., _.unset(0, 'constructor.prototype.a') - if (isRootPrimitive && index === 0) { - continue; - } - + // Block constructor/prototype as non-terminal traversal keys to prevent + // escaping the object graph into built-in constructors and prototypes. + if ((key === 'constructor' || key === 'prototype') && index < length - 1) { return false; } } @@ -103102,7 +103112,7 @@ module.exports = keysIn; /** * Creates an array with all falsey values removed. The values `false`, `null`, - * `0`, `""`, `undefined`, and `NaN` are falsey. + * `0`, `-0`, `0n`, `""`, `undefined`, and `NaN` are falsy. * * @static * @memberOf _ @@ -103641,7 +103651,7 @@ module.exports = keysIn; while (++index < length) { var pair = pairs[index]; - result[pair[0]] = pair[1]; + baseAssignValue(result, pair[0], pair[1]); } return result; } @@ -110301,6 +110311,8 @@ module.exports = keysIn; * **Note:** JavaScript follows the IEEE-754 standard for resolving * floating-point values which can produce unexpected results. * + * **Note:** If `lower` is greater than `upper`, the values are swapped. + * * @static * @memberOf _ * @since 0.7.0 @@ -110314,9 +110326,16 @@ module.exports = keysIn; * _.random(0, 5); * // => an integer between 0 and 5 * + * // when lower is greater than upper the values are swapped + * _.random(5, 0); + * // => an integer between 0 and 5 + * * _.random(5); * // => also an integer between 0 and 5 * + * _.random(-5); + * // => an integer between -5 and 0 + * * _.random(5, true); * // => a floating-point number between 0 and 5 * @@ -110918,6 +110937,10 @@ module.exports = keysIn; * properties may be accessed as free variables in the template. If a setting * object is given, it takes precedence over `_.templateSettings` values. * + * **Security:** `_.template` is insecure and should not be used. It will be + * removed in Lodash v5. Avoid untrusted input. See + * [threat model](https://github.com/lodash/lodash/blob/main/threat-model.md). + * * **Note:** In the development build `_.template` utilizes * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) * for easier debugging. @@ -111025,12 +111048,18 @@ module.exports = keysIn; options = undefined; } string = toString(string); - options = assignInWith({}, options, settings, customDefaultsAssignIn); + options = assignWith({}, options, settings, customDefaultsAssignIn); - var imports = assignInWith({}, options.imports, settings.imports, customDefaultsAssignIn), + var imports = assignWith({}, options.imports, settings.imports, customDefaultsAssignIn), importsKeys = keys(imports), importsValues = baseValues(imports, importsKeys); + arrayEach(importsKeys, function(key) { + if (reForbiddenIdentifierChars.test(key)) { + throw new Error(INVALID_TEMPL_IMPORTS_ERROR_TEXT); + } + }); + var isEscaping, isEvaluating, index = 0, @@ -125749,2702 +125778,6 @@ SafeBuffer.allocUnsafeSlow = function (size) { } -/***/ }), - -/***/ 89379: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const ANY = Symbol('SemVer ANY') -// hoisted class for cyclic dependency -class Comparator { - static get ANY () { - return ANY - } - - constructor (comp, options) { - options = parseOptions(options) - - if (comp instanceof Comparator) { - if (comp.loose === !!options.loose) { - return comp - } else { - comp = comp.value - } - } - - comp = comp.trim().split(/\s+/).join(' ') - debug('comparator', comp, options) - this.options = options - this.loose = !!options.loose - this.parse(comp) - - if (this.semver === ANY) { - this.value = '' - } else { - this.value = this.operator + this.semver.version - } - - debug('comp', this) - } - - parse (comp) { - const r = this.options.loose ? re[t.COMPARATORLOOSE] : re[t.COMPARATOR] - const m = comp.match(r) - - if (!m) { - throw new TypeError(`Invalid comparator: ${comp}`) - } - - this.operator = m[1] !== undefined ? m[1] : '' - if (this.operator === '=') { - this.operator = '' - } - - // if it literally is just '>' or '' then allow anything. - if (!m[2]) { - this.semver = ANY - } else { - this.semver = new SemVer(m[2], this.options.loose) - } - } - - toString () { - return this.value - } - - test (version) { - debug('Comparator.test', version, this.options.loose) - - if (this.semver === ANY || version === ANY) { - return true - } - - if (typeof version === 'string') { - try { - version = new SemVer(version, this.options) - } catch (er) { - return false - } - } - - return cmp(version, this.operator, this.semver, this.options) - } - - intersects (comp, options) { - if (!(comp instanceof Comparator)) { - throw new TypeError('a Comparator is required') - } - - if (this.operator === '') { - if (this.value === '') { - return true - } - return new Range(comp.value, options).test(this.value) - } else if (comp.operator === '') { - if (comp.value === '') { - return true - } - return new Range(this.value, options).test(comp.semver) - } - - options = parseOptions(options) - - // Special cases where nothing can possibly be lower - if (options.includePrerelease && - (this.value === '<0.0.0-0' || comp.value === '<0.0.0-0')) { - return false - } - if (!options.includePrerelease && - (this.value.startsWith('<0.0.0') || comp.value.startsWith('<0.0.0'))) { - return false - } - - // Same direction increasing (> or >=) - if (this.operator.startsWith('>') && comp.operator.startsWith('>')) { - return true - } - // Same direction decreasing (< or <=) - if (this.operator.startsWith('<') && comp.operator.startsWith('<')) { - return true - } - // same SemVer and both sides are inclusive (<= or >=) - if ( - (this.semver.version === comp.semver.version) && - this.operator.includes('=') && comp.operator.includes('=')) { - return true - } - // opposite directions less than - if (cmp(this.semver, '<', comp.semver, options) && - this.operator.startsWith('>') && comp.operator.startsWith('<')) { - return true - } - // opposite directions greater than - if (cmp(this.semver, '>', comp.semver, options) && - this.operator.startsWith('<') && comp.operator.startsWith('>')) { - return true - } - return false - } -} - -module.exports = Comparator - -const parseOptions = __nccwpck_require__(70356) -const { safeRe: re, t } = __nccwpck_require__(95471) -const cmp = __nccwpck_require__(28646) -const debug = __nccwpck_require__(1159) -const SemVer = __nccwpck_require__(7163) -const Range = __nccwpck_require__(96782) - - -/***/ }), - -/***/ 96782: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const SPACE_CHARACTERS = /\s+/g - -// hoisted class for cyclic dependency -class Range { - constructor (range, options) { - options = parseOptions(options) - - if (range instanceof Range) { - if ( - range.loose === !!options.loose && - range.includePrerelease === !!options.includePrerelease - ) { - return range - } else { - return new Range(range.raw, options) - } - } - - if (range instanceof Comparator) { - // just put it in the set and return - this.raw = range.value - this.set = [[range]] - this.formatted = undefined - return this - } - - this.options = options - this.loose = !!options.loose - this.includePrerelease = !!options.includePrerelease - - // First reduce all whitespace as much as possible so we do not have to rely - // on potentially slow regexes like \s*. This is then stored and used for - // future error messages as well. - this.raw = range.trim().replace(SPACE_CHARACTERS, ' ') - - // First, split on || - this.set = this.raw - .split('||') - // map the range to a 2d array of comparators - .map(r => this.parseRange(r.trim())) - // throw out any comparator lists that are empty - // this generally means that it was not a valid range, which is allowed - // in loose mode, but will still throw if the WHOLE range is invalid. - .filter(c => c.length) - - if (!this.set.length) { - throw new TypeError(`Invalid SemVer Range: ${this.raw}`) - } - - // if we have any that are not the null set, throw out null sets. - if (this.set.length > 1) { - // keep the first one, in case they're all null sets - const first = this.set[0] - this.set = this.set.filter(c => !isNullSet(c[0])) - if (this.set.length === 0) { - this.set = [first] - } else if (this.set.length > 1) { - // if we have any that are *, then the range is just * - for (const c of this.set) { - if (c.length === 1 && isAny(c[0])) { - this.set = [c] - break - } - } - } - } - - this.formatted = undefined - } - - get range () { - if (this.formatted === undefined) { - this.formatted = '' - for (let i = 0; i < this.set.length; i++) { - if (i > 0) { - this.formatted += '||' - } - const comps = this.set[i] - for (let k = 0; k < comps.length; k++) { - if (k > 0) { - this.formatted += ' ' - } - this.formatted += comps[k].toString().trim() - } - } - } - return this.formatted - } - - format () { - return this.range - } - - toString () { - return this.range - } - - parseRange (range) { - // memoize range parsing for performance. - // this is a very hot path, and fully deterministic. - const memoOpts = - (this.options.includePrerelease && FLAG_INCLUDE_PRERELEASE) | - (this.options.loose && FLAG_LOOSE) - const memoKey = memoOpts + ':' + range - const cached = cache.get(memoKey) - if (cached) { - return cached - } - - const loose = this.options.loose - // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` - const hr = loose ? re[t.HYPHENRANGELOOSE] : re[t.HYPHENRANGE] - range = range.replace(hr, hyphenReplace(this.options.includePrerelease)) - debug('hyphen replace', range) - - // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` - range = range.replace(re[t.COMPARATORTRIM], comparatorTrimReplace) - debug('comparator trim', range) - - // `~ 1.2.3` => `~1.2.3` - range = range.replace(re[t.TILDETRIM], tildeTrimReplace) - debug('tilde trim', range) - - // `^ 1.2.3` => `^1.2.3` - range = range.replace(re[t.CARETTRIM], caretTrimReplace) - debug('caret trim', range) - - // At this point, the range is completely trimmed and - // ready to be split into comparators. - - let rangeList = range - .split(' ') - .map(comp => parseComparator(comp, this.options)) - .join(' ') - .split(/\s+/) - // >=0.0.0 is equivalent to * - .map(comp => replaceGTE0(comp, this.options)) - - if (loose) { - // in loose mode, throw out any that are not valid comparators - rangeList = rangeList.filter(comp => { - debug('loose invalid filter', comp, this.options) - return !!comp.match(re[t.COMPARATORLOOSE]) - }) - } - debug('range list', rangeList) - - // if any comparators are the null set, then replace with JUST null set - // if more than one comparator, remove any * comparators - // also, don't include the same comparator more than once - const rangeMap = new Map() - const comparators = rangeList.map(comp => new Comparator(comp, this.options)) - for (const comp of comparators) { - if (isNullSet(comp)) { - return [comp] - } - rangeMap.set(comp.value, comp) - } - if (rangeMap.size > 1 && rangeMap.has('')) { - rangeMap.delete('') - } - - const result = [...rangeMap.values()] - cache.set(memoKey, result) - return result - } - - intersects (range, options) { - if (!(range instanceof Range)) { - throw new TypeError('a Range is required') - } - - return this.set.some((thisComparators) => { - return ( - isSatisfiable(thisComparators, options) && - range.set.some((rangeComparators) => { - return ( - isSatisfiable(rangeComparators, options) && - thisComparators.every((thisComparator) => { - return rangeComparators.every((rangeComparator) => { - return thisComparator.intersects(rangeComparator, options) - }) - }) - ) - }) - ) - }) - } - - // if ANY of the sets match ALL of its comparators, then pass - test (version) { - if (!version) { - return false - } - - if (typeof version === 'string') { - try { - version = new SemVer(version, this.options) - } catch (er) { - return false - } - } - - for (let i = 0; i < this.set.length; i++) { - if (testSet(this.set[i], version, this.options)) { - return true - } - } - return false - } -} - -module.exports = Range - -const LRU = __nccwpck_require__(61383) -const cache = new LRU() - -const parseOptions = __nccwpck_require__(70356) -const Comparator = __nccwpck_require__(89379) -const debug = __nccwpck_require__(1159) -const SemVer = __nccwpck_require__(7163) -const { - safeRe: re, - t, - comparatorTrimReplace, - tildeTrimReplace, - caretTrimReplace, -} = __nccwpck_require__(95471) -const { FLAG_INCLUDE_PRERELEASE, FLAG_LOOSE } = __nccwpck_require__(45101) - -const isNullSet = c => c.value === '<0.0.0-0' -const isAny = c => c.value === '' - -// take a set of comparators and determine whether there -// exists a version which can satisfy it -const isSatisfiable = (comparators, options) => { - let result = true - const remainingComparators = comparators.slice() - let testComparator = remainingComparators.pop() - - while (result && remainingComparators.length) { - result = remainingComparators.every((otherComparator) => { - return testComparator.intersects(otherComparator, options) - }) - - testComparator = remainingComparators.pop() - } - - return result -} - -// comprised of xranges, tildes, stars, and gtlt's at this point. -// already replaced the hyphen ranges -// turn into a set of JUST comparators. -const parseComparator = (comp, options) => { - debug('comp', comp, options) - comp = replaceCarets(comp, options) - debug('caret', comp) - comp = replaceTildes(comp, options) - debug('tildes', comp) - comp = replaceXRanges(comp, options) - debug('xrange', comp) - comp = replaceStars(comp, options) - debug('stars', comp) - return comp -} - -const isX = id => !id || id.toLowerCase() === 'x' || id === '*' - -// ~, ~> --> * (any, kinda silly) -// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0-0 -// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0-0 -// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0-0 -// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0-0 -// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0-0 -// ~0.0.1 --> >=0.0.1 <0.1.0-0 -const replaceTildes = (comp, options) => { - return comp - .trim() - .split(/\s+/) - .map((c) => replaceTilde(c, options)) - .join(' ') -} - -const replaceTilde = (comp, options) => { - const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE] - return comp.replace(r, (_, M, m, p, pr) => { - debug('tilde', comp, _, M, m, p, pr) - let ret - - if (isX(M)) { - ret = '' - } else if (isX(m)) { - ret = `>=${M}.0.0 <${+M + 1}.0.0-0` - } else if (isX(p)) { - // ~1.2 == >=1.2.0 <1.3.0-0 - ret = `>=${M}.${m}.0 <${M}.${+m + 1}.0-0` - } else if (pr) { - debug('replaceTilde pr', pr) - ret = `>=${M}.${m}.${p}-${pr - } <${M}.${+m + 1}.0-0` - } else { - // ~1.2.3 == >=1.2.3 <1.3.0-0 - ret = `>=${M}.${m}.${p - } <${M}.${+m + 1}.0-0` - } - - debug('tilde return', ret) - return ret - }) -} - -// ^ --> * (any, kinda silly) -// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0-0 -// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0-0 -// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0-0 -// ^1.2.3 --> >=1.2.3 <2.0.0-0 -// ^1.2.0 --> >=1.2.0 <2.0.0-0 -// ^0.0.1 --> >=0.0.1 <0.0.2-0 -// ^0.1.0 --> >=0.1.0 <0.2.0-0 -const replaceCarets = (comp, options) => { - return comp - .trim() - .split(/\s+/) - .map((c) => replaceCaret(c, options)) - .join(' ') -} - -const replaceCaret = (comp, options) => { - debug('caret', comp, options) - const r = options.loose ? re[t.CARETLOOSE] : re[t.CARET] - const z = options.includePrerelease ? '-0' : '' - return comp.replace(r, (_, M, m, p, pr) => { - debug('caret', comp, _, M, m, p, pr) - let ret - - if (isX(M)) { - ret = '' - } else if (isX(m)) { - ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0` - } else if (isX(p)) { - if (M === '0') { - ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0` - } else { - ret = `>=${M}.${m}.0${z} <${+M + 1}.0.0-0` - } - } else if (pr) { - debug('replaceCaret pr', pr) - if (M === '0') { - if (m === '0') { - ret = `>=${M}.${m}.${p}-${pr - } <${M}.${m}.${+p + 1}-0` - } else { - ret = `>=${M}.${m}.${p}-${pr - } <${M}.${+m + 1}.0-0` - } - } else { - ret = `>=${M}.${m}.${p}-${pr - } <${+M + 1}.0.0-0` - } - } else { - debug('no pr') - if (M === '0') { - if (m === '0') { - ret = `>=${M}.${m}.${p - }${z} <${M}.${m}.${+p + 1}-0` - } else { - ret = `>=${M}.${m}.${p - }${z} <${M}.${+m + 1}.0-0` - } - } else { - ret = `>=${M}.${m}.${p - } <${+M + 1}.0.0-0` - } - } - - debug('caret return', ret) - return ret - }) -} - -const replaceXRanges = (comp, options) => { - debug('replaceXRanges', comp, options) - return comp - .split(/\s+/) - .map((c) => replaceXRange(c, options)) - .join(' ') -} - -const replaceXRange = (comp, options) => { - comp = comp.trim() - const r = options.loose ? re[t.XRANGELOOSE] : re[t.XRANGE] - return comp.replace(r, (ret, gtlt, M, m, p, pr) => { - debug('xRange', comp, ret, gtlt, M, m, p, pr) - const xM = isX(M) - const xm = xM || isX(m) - const xp = xm || isX(p) - const anyX = xp - - if (gtlt === '=' && anyX) { - gtlt = '' - } - - // if we're including prereleases in the match, then we need - // to fix this to -0, the lowest possible prerelease value - pr = options.includePrerelease ? '-0' : '' - - if (xM) { - if (gtlt === '>' || gtlt === '<') { - // nothing is allowed - ret = '<0.0.0-0' - } else { - // nothing is forbidden - ret = '*' - } - } else if (gtlt && anyX) { - // we know patch is an x, because we have any x at all. - // replace X with 0 - if (xm) { - m = 0 - } - p = 0 - - if (gtlt === '>') { - // >1 => >=2.0.0 - // >1.2 => >=1.3.0 - gtlt = '>=' - if (xm) { - M = +M + 1 - m = 0 - p = 0 - } else { - m = +m + 1 - p = 0 - } - } else if (gtlt === '<=') { - // <=0.7.x is actually <0.8.0, since any 0.7.x should - // pass. Similarly, <=7.x is actually <8.0.0, etc. - gtlt = '<' - if (xm) { - M = +M + 1 - } else { - m = +m + 1 - } - } - - if (gtlt === '<') { - pr = '-0' - } - - ret = `${gtlt + M}.${m}.${p}${pr}` - } else if (xm) { - ret = `>=${M}.0.0${pr} <${+M + 1}.0.0-0` - } else if (xp) { - ret = `>=${M}.${m}.0${pr - } <${M}.${+m + 1}.0-0` - } - - debug('xRange return', ret) - - return ret - }) -} - -// Because * is AND-ed with everything else in the comparator, -// and '' means "any version", just remove the *s entirely. -const replaceStars = (comp, options) => { - debug('replaceStars', comp, options) - // Looseness is ignored here. star is always as loose as it gets! - return comp - .trim() - .replace(re[t.STAR], '') -} - -const replaceGTE0 = (comp, options) => { - debug('replaceGTE0', comp, options) - return comp - .trim() - .replace(re[options.includePrerelease ? t.GTE0PRE : t.GTE0], '') -} - -// This function is passed to string.replace(re[t.HYPHENRANGE]) -// M, m, patch, prerelease, build -// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5 -// 1.2.3 - 3.4 => >=1.2.0 <3.5.0-0 Any 3.4.x will do -// 1.2 - 3.4 => >=1.2.0 <3.5.0-0 -// TODO build? -const hyphenReplace = incPr => ($0, - from, fM, fm, fp, fpr, fb, - to, tM, tm, tp, tpr) => { - if (isX(fM)) { - from = '' - } else if (isX(fm)) { - from = `>=${fM}.0.0${incPr ? '-0' : ''}` - } else if (isX(fp)) { - from = `>=${fM}.${fm}.0${incPr ? '-0' : ''}` - } else if (fpr) { - from = `>=${from}` - } else { - from = `>=${from}${incPr ? '-0' : ''}` - } - - if (isX(tM)) { - to = '' - } else if (isX(tm)) { - to = `<${+tM + 1}.0.0-0` - } else if (isX(tp)) { - to = `<${tM}.${+tm + 1}.0-0` - } else if (tpr) { - to = `<=${tM}.${tm}.${tp}-${tpr}` - } else if (incPr) { - to = `<${tM}.${tm}.${+tp + 1}-0` - } else { - to = `<=${to}` - } - - return `${from} ${to}`.trim() -} - -const testSet = (set, version, options) => { - for (let i = 0; i < set.length; i++) { - if (!set[i].test(version)) { - return false - } - } - - if (version.prerelease.length && !options.includePrerelease) { - // Find the set of versions that are allowed to have prereleases - // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0 - // That should allow `1.2.3-pr.2` to pass. - // However, `1.2.4-alpha.notready` should NOT be allowed, - // even though it's within the range set by the comparators. - for (let i = 0; i < set.length; i++) { - debug(set[i].semver) - if (set[i].semver === Comparator.ANY) { - continue - } - - if (set[i].semver.prerelease.length > 0) { - const allowed = set[i].semver - if (allowed.major === version.major && - allowed.minor === version.minor && - allowed.patch === version.patch) { - return true - } - } - } - - // Version has a -pre, but it's not one of the ones we like. - return false - } - - return true -} - - -/***/ }), - -/***/ 7163: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const debug = __nccwpck_require__(1159) -const { MAX_LENGTH, MAX_SAFE_INTEGER } = __nccwpck_require__(45101) -const { safeRe: re, t } = __nccwpck_require__(95471) - -const parseOptions = __nccwpck_require__(70356) -const { compareIdentifiers } = __nccwpck_require__(73348) -class SemVer { - constructor (version, options) { - options = parseOptions(options) - - if (version instanceof SemVer) { - if (version.loose === !!options.loose && - version.includePrerelease === !!options.includePrerelease) { - return version - } else { - version = version.version - } - } else if (typeof version !== 'string') { - throw new TypeError(`Invalid version. Must be a string. Got type "${typeof version}".`) - } - - if (version.length > MAX_LENGTH) { - throw new TypeError( - `version is longer than ${MAX_LENGTH} characters` - ) - } - - debug('SemVer', version, options) - this.options = options - this.loose = !!options.loose - // this isn't actually relevant for versions, but keep it so that we - // don't run into trouble passing this.options around. - this.includePrerelease = !!options.includePrerelease - - const m = version.trim().match(options.loose ? re[t.LOOSE] : re[t.FULL]) - - if (!m) { - throw new TypeError(`Invalid Version: ${version}`) - } - - this.raw = version - - // these are actually numbers - this.major = +m[1] - this.minor = +m[2] - this.patch = +m[3] - - if (this.major > MAX_SAFE_INTEGER || this.major < 0) { - throw new TypeError('Invalid major version') - } - - if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) { - throw new TypeError('Invalid minor version') - } - - if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) { - throw new TypeError('Invalid patch version') - } - - // numberify any prerelease numeric ids - if (!m[4]) { - this.prerelease = [] - } else { - this.prerelease = m[4].split('.').map((id) => { - if (/^[0-9]+$/.test(id)) { - const num = +id - if (num >= 0 && num < MAX_SAFE_INTEGER) { - return num - } - } - return id - }) - } - - this.build = m[5] ? m[5].split('.') : [] - this.format() - } - - format () { - this.version = `${this.major}.${this.minor}.${this.patch}` - if (this.prerelease.length) { - this.version += `-${this.prerelease.join('.')}` - } - return this.version - } - - toString () { - return this.version - } - - compare (other) { - debug('SemVer.compare', this.version, this.options, other) - if (!(other instanceof SemVer)) { - if (typeof other === 'string' && other === this.version) { - return 0 - } - other = new SemVer(other, this.options) - } - - if (other.version === this.version) { - return 0 - } - - return this.compareMain(other) || this.comparePre(other) - } - - compareMain (other) { - if (!(other instanceof SemVer)) { - other = new SemVer(other, this.options) - } - - return ( - compareIdentifiers(this.major, other.major) || - compareIdentifiers(this.minor, other.minor) || - compareIdentifiers(this.patch, other.patch) - ) - } - - comparePre (other) { - if (!(other instanceof SemVer)) { - other = new SemVer(other, this.options) - } - - // NOT having a prerelease is > having one - if (this.prerelease.length && !other.prerelease.length) { - return -1 - } else if (!this.prerelease.length && other.prerelease.length) { - return 1 - } else if (!this.prerelease.length && !other.prerelease.length) { - return 0 - } - - let i = 0 - do { - const a = this.prerelease[i] - const b = other.prerelease[i] - debug('prerelease compare', i, a, b) - if (a === undefined && b === undefined) { - return 0 - } else if (b === undefined) { - return 1 - } else if (a === undefined) { - return -1 - } else if (a === b) { - continue - } else { - return compareIdentifiers(a, b) - } - } while (++i) - } - - compareBuild (other) { - if (!(other instanceof SemVer)) { - other = new SemVer(other, this.options) - } - - let i = 0 - do { - const a = this.build[i] - const b = other.build[i] - debug('build compare', i, a, b) - if (a === undefined && b === undefined) { - return 0 - } else if (b === undefined) { - return 1 - } else if (a === undefined) { - return -1 - } else if (a === b) { - continue - } else { - return compareIdentifiers(a, b) - } - } while (++i) - } - - // preminor will bump the version up to the next minor release, and immediately - // down to pre-release. premajor and prepatch work the same way. - inc (release, identifier, identifierBase) { - if (release.startsWith('pre')) { - if (!identifier && identifierBase === false) { - throw new Error('invalid increment argument: identifier is empty') - } - // Avoid an invalid semver results - if (identifier) { - const match = `-${identifier}`.match(this.options.loose ? re[t.PRERELEASELOOSE] : re[t.PRERELEASE]) - if (!match || match[1] !== identifier) { - throw new Error(`invalid identifier: ${identifier}`) - } - } - } - - switch (release) { - case 'premajor': - this.prerelease.length = 0 - this.patch = 0 - this.minor = 0 - this.major++ - this.inc('pre', identifier, identifierBase) - break - case 'preminor': - this.prerelease.length = 0 - this.patch = 0 - this.minor++ - this.inc('pre', identifier, identifierBase) - break - case 'prepatch': - // If this is already a prerelease, it will bump to the next version - // drop any prereleases that might already exist, since they are not - // relevant at this point. - this.prerelease.length = 0 - this.inc('patch', identifier, identifierBase) - this.inc('pre', identifier, identifierBase) - break - // If the input is a non-prerelease version, this acts the same as - // prepatch. - case 'prerelease': - if (this.prerelease.length === 0) { - this.inc('patch', identifier, identifierBase) - } - this.inc('pre', identifier, identifierBase) - break - case 'release': - if (this.prerelease.length === 0) { - throw new Error(`version ${this.raw} is not a prerelease`) - } - this.prerelease.length = 0 - break - - case 'major': - // If this is a pre-major version, bump up to the same major version. - // Otherwise increment major. - // 1.0.0-5 bumps to 1.0.0 - // 1.1.0 bumps to 2.0.0 - if ( - this.minor !== 0 || - this.patch !== 0 || - this.prerelease.length === 0 - ) { - this.major++ - } - this.minor = 0 - this.patch = 0 - this.prerelease = [] - break - case 'minor': - // If this is a pre-minor version, bump up to the same minor version. - // Otherwise increment minor. - // 1.2.0-5 bumps to 1.2.0 - // 1.2.1 bumps to 1.3.0 - if (this.patch !== 0 || this.prerelease.length === 0) { - this.minor++ - } - this.patch = 0 - this.prerelease = [] - break - case 'patch': - // If this is not a pre-release version, it will increment the patch. - // If it is a pre-release it will bump up to the same patch version. - // 1.2.0-5 patches to 1.2.0 - // 1.2.0 patches to 1.2.1 - if (this.prerelease.length === 0) { - this.patch++ - } - this.prerelease = [] - break - // This probably shouldn't be used publicly. - // 1.0.0 'pre' would become 1.0.0-0 which is the wrong direction. - case 'pre': { - const base = Number(identifierBase) ? 1 : 0 - - if (this.prerelease.length === 0) { - this.prerelease = [base] - } else { - let i = this.prerelease.length - while (--i >= 0) { - if (typeof this.prerelease[i] === 'number') { - this.prerelease[i]++ - i = -2 - } - } - if (i === -1) { - // didn't increment anything - if (identifier === this.prerelease.join('.') && identifierBase === false) { - throw new Error('invalid increment argument: identifier already exists') - } - this.prerelease.push(base) - } - } - if (identifier) { - // 1.2.0-beta.1 bumps to 1.2.0-beta.2, - // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 - let prerelease = [identifier, base] - if (identifierBase === false) { - prerelease = [identifier] - } - if (compareIdentifiers(this.prerelease[0], identifier) === 0) { - if (isNaN(this.prerelease[1])) { - this.prerelease = prerelease - } - } else { - this.prerelease = prerelease - } - } - break - } - default: - throw new Error(`invalid increment argument: ${release}`) - } - this.raw = this.format() - if (this.build.length) { - this.raw += `+${this.build.join('.')}` - } - return this - } -} - -module.exports = SemVer - - -/***/ }), - -/***/ 1799: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const parse = __nccwpck_require__(16353) -const clean = (version, options) => { - const s = parse(version.trim().replace(/^[=v]+/, ''), options) - return s ? s.version : null -} -module.exports = clean - - -/***/ }), - -/***/ 28646: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const eq = __nccwpck_require__(55082) -const neq = __nccwpck_require__(4974) -const gt = __nccwpck_require__(16599) -const gte = __nccwpck_require__(41236) -const lt = __nccwpck_require__(3872) -const lte = __nccwpck_require__(56717) - -const cmp = (a, op, b, loose) => { - switch (op) { - case '===': - if (typeof a === 'object') { - a = a.version - } - if (typeof b === 'object') { - b = b.version - } - return a === b - - case '!==': - if (typeof a === 'object') { - a = a.version - } - if (typeof b === 'object') { - b = b.version - } - return a !== b - - case '': - case '=': - case '==': - return eq(a, b, loose) - - case '!=': - return neq(a, b, loose) - - case '>': - return gt(a, b, loose) - - case '>=': - return gte(a, b, loose) - - case '<': - return lt(a, b, loose) - - case '<=': - return lte(a, b, loose) - - default: - throw new TypeError(`Invalid operator: ${op}`) - } -} -module.exports = cmp - - -/***/ }), - -/***/ 35385: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const SemVer = __nccwpck_require__(7163) -const parse = __nccwpck_require__(16353) -const { safeRe: re, t } = __nccwpck_require__(95471) - -const coerce = (version, options) => { - if (version instanceof SemVer) { - return version - } - - if (typeof version === 'number') { - version = String(version) - } - - if (typeof version !== 'string') { - return null - } - - options = options || {} - - let match = null - if (!options.rtl) { - match = version.match(options.includePrerelease ? re[t.COERCEFULL] : re[t.COERCE]) - } else { - // Find the right-most coercible string that does not share - // a terminus with a more left-ward coercible string. - // Eg, '1.2.3.4' wants to coerce '2.3.4', not '3.4' or '4' - // With includePrerelease option set, '1.2.3.4-rc' wants to coerce '2.3.4-rc', not '2.3.4' - // - // Walk through the string checking with a /g regexp - // Manually set the index so as to pick up overlapping matches. - // Stop when we get a match that ends at the string end, since no - // coercible string can be more right-ward without the same terminus. - const coerceRtlRegex = options.includePrerelease ? re[t.COERCERTLFULL] : re[t.COERCERTL] - let next - while ((next = coerceRtlRegex.exec(version)) && - (!match || match.index + match[0].length !== version.length) - ) { - if (!match || - next.index + next[0].length !== match.index + match[0].length) { - match = next - } - coerceRtlRegex.lastIndex = next.index + next[1].length + next[2].length - } - // leave it in a clean state - coerceRtlRegex.lastIndex = -1 - } - - if (match === null) { - return null - } - - const major = match[2] - const minor = match[3] || '0' - const patch = match[4] || '0' - const prerelease = options.includePrerelease && match[5] ? `-${match[5]}` : '' - const build = options.includePrerelease && match[6] ? `+${match[6]}` : '' - - return parse(`${major}.${minor}.${patch}${prerelease}${build}`, options) -} -module.exports = coerce - - -/***/ }), - -/***/ 37648: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const SemVer = __nccwpck_require__(7163) -const compareBuild = (a, b, loose) => { - const versionA = new SemVer(a, loose) - const versionB = new SemVer(b, loose) - return versionA.compare(versionB) || versionA.compareBuild(versionB) -} -module.exports = compareBuild - - -/***/ }), - -/***/ 56874: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const compare = __nccwpck_require__(78469) -const compareLoose = (a, b) => compare(a, b, true) -module.exports = compareLoose - - -/***/ }), - -/***/ 78469: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const SemVer = __nccwpck_require__(7163) -const compare = (a, b, loose) => - new SemVer(a, loose).compare(new SemVer(b, loose)) - -module.exports = compare - - -/***/ }), - -/***/ 70711: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const parse = __nccwpck_require__(16353) - -const diff = (version1, version2) => { - const v1 = parse(version1, null, true) - const v2 = parse(version2, null, true) - const comparison = v1.compare(v2) - - if (comparison === 0) { - return null - } - - const v1Higher = comparison > 0 - const highVersion = v1Higher ? v1 : v2 - const lowVersion = v1Higher ? v2 : v1 - const highHasPre = !!highVersion.prerelease.length - const lowHasPre = !!lowVersion.prerelease.length - - if (lowHasPre && !highHasPre) { - // Going from prerelease -> no prerelease requires some special casing - - // If the low version has only a major, then it will always be a major - // Some examples: - // 1.0.0-1 -> 1.0.0 - // 1.0.0-1 -> 1.1.1 - // 1.0.0-1 -> 2.0.0 - if (!lowVersion.patch && !lowVersion.minor) { - return 'major' - } - - // If the main part has no difference - if (lowVersion.compareMain(highVersion) === 0) { - if (lowVersion.minor && !lowVersion.patch) { - return 'minor' - } - return 'patch' - } - } - - // add the `pre` prefix if we are going to a prerelease version - const prefix = highHasPre ? 'pre' : '' - - if (v1.major !== v2.major) { - return prefix + 'major' - } - - if (v1.minor !== v2.minor) { - return prefix + 'minor' - } - - if (v1.patch !== v2.patch) { - return prefix + 'patch' - } - - // high and low are preleases - return 'prerelease' -} - -module.exports = diff - - -/***/ }), - -/***/ 55082: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const compare = __nccwpck_require__(78469) -const eq = (a, b, loose) => compare(a, b, loose) === 0 -module.exports = eq - - -/***/ }), - -/***/ 16599: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const compare = __nccwpck_require__(78469) -const gt = (a, b, loose) => compare(a, b, loose) > 0 -module.exports = gt - - -/***/ }), - -/***/ 41236: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const compare = __nccwpck_require__(78469) -const gte = (a, b, loose) => compare(a, b, loose) >= 0 -module.exports = gte - - -/***/ }), - -/***/ 62338: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const SemVer = __nccwpck_require__(7163) - -const inc = (version, release, options, identifier, identifierBase) => { - if (typeof (options) === 'string') { - identifierBase = identifier - identifier = options - options = undefined - } - - try { - return new SemVer( - version instanceof SemVer ? version.version : version, - options - ).inc(release, identifier, identifierBase).version - } catch (er) { - return null - } -} -module.exports = inc - - -/***/ }), - -/***/ 3872: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const compare = __nccwpck_require__(78469) -const lt = (a, b, loose) => compare(a, b, loose) < 0 -module.exports = lt - - -/***/ }), - -/***/ 56717: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const compare = __nccwpck_require__(78469) -const lte = (a, b, loose) => compare(a, b, loose) <= 0 -module.exports = lte - - -/***/ }), - -/***/ 68511: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const SemVer = __nccwpck_require__(7163) -const major = (a, loose) => new SemVer(a, loose).major -module.exports = major - - -/***/ }), - -/***/ 32603: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const SemVer = __nccwpck_require__(7163) -const minor = (a, loose) => new SemVer(a, loose).minor -module.exports = minor - - -/***/ }), - -/***/ 4974: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const compare = __nccwpck_require__(78469) -const neq = (a, b, loose) => compare(a, b, loose) !== 0 -module.exports = neq - - -/***/ }), - -/***/ 16353: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const SemVer = __nccwpck_require__(7163) -const parse = (version, options, throwErrors = false) => { - if (version instanceof SemVer) { - return version - } - try { - return new SemVer(version, options) - } catch (er) { - if (!throwErrors) { - return null - } - throw er - } -} - -module.exports = parse - - -/***/ }), - -/***/ 48756: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const SemVer = __nccwpck_require__(7163) -const patch = (a, loose) => new SemVer(a, loose).patch -module.exports = patch - - -/***/ }), - -/***/ 15714: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const parse = __nccwpck_require__(16353) -const prerelease = (version, options) => { - const parsed = parse(version, options) - return (parsed && parsed.prerelease.length) ? parsed.prerelease : null -} -module.exports = prerelease - - -/***/ }), - -/***/ 32173: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const compare = __nccwpck_require__(78469) -const rcompare = (a, b, loose) => compare(b, a, loose) -module.exports = rcompare - - -/***/ }), - -/***/ 87192: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const compareBuild = __nccwpck_require__(37648) -const rsort = (list, loose) => list.sort((a, b) => compareBuild(b, a, loose)) -module.exports = rsort - - -/***/ }), - -/***/ 68011: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const Range = __nccwpck_require__(96782) -const satisfies = (version, range, options) => { - try { - range = new Range(range, options) - } catch (er) { - return false - } - return range.test(version) -} -module.exports = satisfies - - -/***/ }), - -/***/ 29872: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const compareBuild = __nccwpck_require__(37648) -const sort = (list, loose) => list.sort((a, b) => compareBuild(a, b, loose)) -module.exports = sort - - -/***/ }), - -/***/ 58780: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const parse = __nccwpck_require__(16353) -const valid = (version, options) => { - const v = parse(version, options) - return v ? v.version : null -} -module.exports = valid - - -/***/ }), - -/***/ 62088: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -// just pre-load all the stuff that index.js lazily exports -const internalRe = __nccwpck_require__(95471) -const constants = __nccwpck_require__(45101) -const SemVer = __nccwpck_require__(7163) -const identifiers = __nccwpck_require__(73348) -const parse = __nccwpck_require__(16353) -const valid = __nccwpck_require__(58780) -const clean = __nccwpck_require__(1799) -const inc = __nccwpck_require__(62338) -const diff = __nccwpck_require__(70711) -const major = __nccwpck_require__(68511) -const minor = __nccwpck_require__(32603) -const patch = __nccwpck_require__(48756) -const prerelease = __nccwpck_require__(15714) -const compare = __nccwpck_require__(78469) -const rcompare = __nccwpck_require__(32173) -const compareLoose = __nccwpck_require__(56874) -const compareBuild = __nccwpck_require__(37648) -const sort = __nccwpck_require__(29872) -const rsort = __nccwpck_require__(87192) -const gt = __nccwpck_require__(16599) -const lt = __nccwpck_require__(3872) -const eq = __nccwpck_require__(55082) -const neq = __nccwpck_require__(4974) -const gte = __nccwpck_require__(41236) -const lte = __nccwpck_require__(56717) -const cmp = __nccwpck_require__(28646) -const coerce = __nccwpck_require__(35385) -const Comparator = __nccwpck_require__(89379) -const Range = __nccwpck_require__(96782) -const satisfies = __nccwpck_require__(68011) -const toComparators = __nccwpck_require__(54750) -const maxSatisfying = __nccwpck_require__(73193) -const minSatisfying = __nccwpck_require__(68595) -const minVersion = __nccwpck_require__(51866) -const validRange = __nccwpck_require__(64737) -const outside = __nccwpck_require__(10280) -const gtr = __nccwpck_require__(12276) -const ltr = __nccwpck_require__(15213) -const intersects = __nccwpck_require__(23465) -const simplifyRange = __nccwpck_require__(82028) -const subset = __nccwpck_require__(61489) -module.exports = { - parse, - valid, - clean, - inc, - diff, - major, - minor, - patch, - prerelease, - compare, - rcompare, - compareLoose, - compareBuild, - sort, - rsort, - gt, - lt, - eq, - neq, - gte, - lte, - cmp, - coerce, - Comparator, - Range, - satisfies, - toComparators, - maxSatisfying, - minSatisfying, - minVersion, - validRange, - outside, - gtr, - ltr, - intersects, - simplifyRange, - subset, - SemVer, - re: internalRe.re, - src: internalRe.src, - tokens: internalRe.t, - SEMVER_SPEC_VERSION: constants.SEMVER_SPEC_VERSION, - RELEASE_TYPES: constants.RELEASE_TYPES, - compareIdentifiers: identifiers.compareIdentifiers, - rcompareIdentifiers: identifiers.rcompareIdentifiers, -} - - -/***/ }), - -/***/ 45101: -/***/ ((module) => { - -"use strict"; - - -// Note: this is the semver.org version of the spec that it implements -// Not necessarily the package version of this code. -const SEMVER_SPEC_VERSION = '2.0.0' - -const MAX_LENGTH = 256 -const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || -/* istanbul ignore next */ 9007199254740991 - -// Max safe segment length for coercion. -const MAX_SAFE_COMPONENT_LENGTH = 16 - -// Max safe length for a build identifier. The max length minus 6 characters for -// the shortest version with a build 0.0.0+BUILD. -const MAX_SAFE_BUILD_LENGTH = MAX_LENGTH - 6 - -const RELEASE_TYPES = [ - 'major', - 'premajor', - 'minor', - 'preminor', - 'patch', - 'prepatch', - 'prerelease', -] - -module.exports = { - MAX_LENGTH, - MAX_SAFE_COMPONENT_LENGTH, - MAX_SAFE_BUILD_LENGTH, - MAX_SAFE_INTEGER, - RELEASE_TYPES, - SEMVER_SPEC_VERSION, - FLAG_INCLUDE_PRERELEASE: 0b001, - FLAG_LOOSE: 0b010, -} - - -/***/ }), - -/***/ 1159: -/***/ ((module) => { - -"use strict"; - - -const debug = ( - typeof process === 'object' && - process.env && - process.env.NODE_DEBUG && - /\bsemver\b/i.test(process.env.NODE_DEBUG) -) ? (...args) => console.error('SEMVER', ...args) - : () => {} - -module.exports = debug - - -/***/ }), - -/***/ 73348: -/***/ ((module) => { - -"use strict"; - - -const numeric = /^[0-9]+$/ -const compareIdentifiers = (a, b) => { - const anum = numeric.test(a) - const bnum = numeric.test(b) - - if (anum && bnum) { - a = +a - b = +b - } - - return a === b ? 0 - : (anum && !bnum) ? -1 - : (bnum && !anum) ? 1 - : a < b ? -1 - : 1 -} - -const rcompareIdentifiers = (a, b) => compareIdentifiers(b, a) - -module.exports = { - compareIdentifiers, - rcompareIdentifiers, -} - - -/***/ }), - -/***/ 61383: -/***/ ((module) => { - -"use strict"; - - -class LRUCache { - constructor () { - this.max = 1000 - this.map = new Map() - } - - get (key) { - const value = this.map.get(key) - if (value === undefined) { - return undefined - } else { - // Remove the key from the map and add it to the end - this.map.delete(key) - this.map.set(key, value) - return value - } - } - - delete (key) { - return this.map.delete(key) - } - - set (key, value) { - const deleted = this.delete(key) - - if (!deleted && value !== undefined) { - // If cache is full, delete the least recently used item - if (this.map.size >= this.max) { - const firstKey = this.map.keys().next().value - this.delete(firstKey) - } - - this.map.set(key, value) - } - - return this - } -} - -module.exports = LRUCache - - -/***/ }), - -/***/ 70356: -/***/ ((module) => { - -"use strict"; - - -// parse out just the options we care about -const looseOption = Object.freeze({ loose: true }) -const emptyOpts = Object.freeze({ }) -const parseOptions = options => { - if (!options) { - return emptyOpts - } - - if (typeof options !== 'object') { - return looseOption - } - - return options -} -module.exports = parseOptions - - -/***/ }), - -/***/ 95471: -/***/ ((module, exports, __nccwpck_require__) => { - -"use strict"; - - -const { - MAX_SAFE_COMPONENT_LENGTH, - MAX_SAFE_BUILD_LENGTH, - MAX_LENGTH, -} = __nccwpck_require__(45101) -const debug = __nccwpck_require__(1159) -exports = module.exports = {} - -// The actual regexps go on exports.re -const re = exports.re = [] -const safeRe = exports.safeRe = [] -const src = exports.src = [] -const safeSrc = exports.safeSrc = [] -const t = exports.t = {} -let R = 0 - -const LETTERDASHNUMBER = '[a-zA-Z0-9-]' - -// Replace some greedy regex tokens to prevent regex dos issues. These regex are -// used internally via the safeRe object since all inputs in this library get -// normalized first to trim and collapse all extra whitespace. The original -// regexes are exported for userland consumption and lower level usage. A -// future breaking change could export the safer regex only with a note that -// all input should have extra whitespace removed. -const safeRegexReplacements = [ - ['\\s', 1], - ['\\d', MAX_LENGTH], - [LETTERDASHNUMBER, MAX_SAFE_BUILD_LENGTH], -] - -const makeSafeRegex = (value) => { - for (const [token, max] of safeRegexReplacements) { - value = value - .split(`${token}*`).join(`${token}{0,${max}}`) - .split(`${token}+`).join(`${token}{1,${max}}`) - } - return value -} - -const createToken = (name, value, isGlobal) => { - const safe = makeSafeRegex(value) - const index = R++ - debug(name, index, value) - t[name] = index - src[index] = value - safeSrc[index] = safe - re[index] = new RegExp(value, isGlobal ? 'g' : undefined) - safeRe[index] = new RegExp(safe, isGlobal ? 'g' : undefined) -} - -// The following Regular Expressions can be used for tokenizing, -// validating, and parsing SemVer version strings. - -// ## Numeric Identifier -// A single `0`, or a non-zero digit followed by zero or more digits. - -createToken('NUMERICIDENTIFIER', '0|[1-9]\\d*') -createToken('NUMERICIDENTIFIERLOOSE', '\\d+') - -// ## Non-numeric Identifier -// Zero or more digits, followed by a letter or hyphen, and then zero or -// more letters, digits, or hyphens. - -createToken('NONNUMERICIDENTIFIER', `\\d*[a-zA-Z-]${LETTERDASHNUMBER}*`) - -// ## Main Version -// Three dot-separated numeric identifiers. - -createToken('MAINVERSION', `(${src[t.NUMERICIDENTIFIER]})\\.` + - `(${src[t.NUMERICIDENTIFIER]})\\.` + - `(${src[t.NUMERICIDENTIFIER]})`) - -createToken('MAINVERSIONLOOSE', `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + - `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + - `(${src[t.NUMERICIDENTIFIERLOOSE]})`) - -// ## Pre-release Version Identifier -// A numeric identifier, or a non-numeric identifier. -// Non-numberic identifiers include numberic identifiers but can be longer. -// Therefore non-numberic identifiers must go first. - -createToken('PRERELEASEIDENTIFIER', `(?:${src[t.NONNUMERICIDENTIFIER] -}|${src[t.NUMERICIDENTIFIER]})`) - -createToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NONNUMERICIDENTIFIER] -}|${src[t.NUMERICIDENTIFIERLOOSE]})`) - -// ## Pre-release Version -// Hyphen, followed by one or more dot-separated pre-release version -// identifiers. - -createToken('PRERELEASE', `(?:-(${src[t.PRERELEASEIDENTIFIER] -}(?:\\.${src[t.PRERELEASEIDENTIFIER]})*))`) - -createToken('PRERELEASELOOSE', `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE] -}(?:\\.${src[t.PRERELEASEIDENTIFIERLOOSE]})*))`) - -// ## Build Metadata Identifier -// Any combination of digits, letters, or hyphens. - -createToken('BUILDIDENTIFIER', `${LETTERDASHNUMBER}+`) - -// ## Build Metadata -// Plus sign, followed by one or more period-separated build metadata -// identifiers. - -createToken('BUILD', `(?:\\+(${src[t.BUILDIDENTIFIER] -}(?:\\.${src[t.BUILDIDENTIFIER]})*))`) - -// ## Full Version String -// A main version, followed optionally by a pre-release version and -// build metadata. - -// Note that the only major, minor, patch, and pre-release sections of -// the version string are capturing groups. The build metadata is not a -// capturing group, because it should not ever be used in version -// comparison. - -createToken('FULLPLAIN', `v?${src[t.MAINVERSION] -}${src[t.PRERELEASE]}?${ - src[t.BUILD]}?`) - -createToken('FULL', `^${src[t.FULLPLAIN]}$`) - -// like full, but allows v1.2.3 and =1.2.3, which people do sometimes. -// also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty -// common in the npm registry. -createToken('LOOSEPLAIN', `[v=\\s]*${src[t.MAINVERSIONLOOSE] -}${src[t.PRERELEASELOOSE]}?${ - src[t.BUILD]}?`) - -createToken('LOOSE', `^${src[t.LOOSEPLAIN]}$`) - -createToken('GTLT', '((?:<|>)?=?)') - -// Something like "2.*" or "1.2.x". -// Note that "x.x" is a valid xRange identifer, meaning "any version" -// Only the first item is strictly required. -createToken('XRANGEIDENTIFIERLOOSE', `${src[t.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`) -createToken('XRANGEIDENTIFIER', `${src[t.NUMERICIDENTIFIER]}|x|X|\\*`) - -createToken('XRANGEPLAIN', `[v=\\s]*(${src[t.XRANGEIDENTIFIER]})` + - `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + - `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + - `(?:${src[t.PRERELEASE]})?${ - src[t.BUILD]}?` + - `)?)?`) - -createToken('XRANGEPLAINLOOSE', `[v=\\s]*(${src[t.XRANGEIDENTIFIERLOOSE]})` + - `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + - `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + - `(?:${src[t.PRERELEASELOOSE]})?${ - src[t.BUILD]}?` + - `)?)?`) - -createToken('XRANGE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAIN]}$`) -createToken('XRANGELOOSE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAINLOOSE]}$`) - -// Coercion. -// Extract anything that could conceivably be a part of a valid semver -createToken('COERCEPLAIN', `${'(^|[^\\d])' + - '(\\d{1,'}${MAX_SAFE_COMPONENT_LENGTH}})` + - `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + - `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?`) -createToken('COERCE', `${src[t.COERCEPLAIN]}(?:$|[^\\d])`) -createToken('COERCEFULL', src[t.COERCEPLAIN] + - `(?:${src[t.PRERELEASE]})?` + - `(?:${src[t.BUILD]})?` + - `(?:$|[^\\d])`) -createToken('COERCERTL', src[t.COERCE], true) -createToken('COERCERTLFULL', src[t.COERCEFULL], true) - -// Tilde ranges. -// Meaning is "reasonably at or greater than" -createToken('LONETILDE', '(?:~>?)') - -createToken('TILDETRIM', `(\\s*)${src[t.LONETILDE]}\\s+`, true) -exports.tildeTrimReplace = '$1~' - -createToken('TILDE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAIN]}$`) -createToken('TILDELOOSE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAINLOOSE]}$`) - -// Caret ranges. -// Meaning is "at least and backwards compatible with" -createToken('LONECARET', '(?:\\^)') - -createToken('CARETTRIM', `(\\s*)${src[t.LONECARET]}\\s+`, true) -exports.caretTrimReplace = '$1^' - -createToken('CARET', `^${src[t.LONECARET]}${src[t.XRANGEPLAIN]}$`) -createToken('CARETLOOSE', `^${src[t.LONECARET]}${src[t.XRANGEPLAINLOOSE]}$`) - -// A simple gt/lt/eq thing, or just "" to indicate "any version" -createToken('COMPARATORLOOSE', `^${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]})$|^$`) -createToken('COMPARATOR', `^${src[t.GTLT]}\\s*(${src[t.FULLPLAIN]})$|^$`) - -// An expression to strip any whitespace between the gtlt and the thing -// it modifies, so that `> 1.2.3` ==> `>1.2.3` -createToken('COMPARATORTRIM', `(\\s*)${src[t.GTLT] -}\\s*(${src[t.LOOSEPLAIN]}|${src[t.XRANGEPLAIN]})`, true) -exports.comparatorTrimReplace = '$1$2$3' - -// Something like `1.2.3 - 1.2.4` -// Note that these all use the loose form, because they'll be -// checked against either the strict or loose comparator form -// later. -createToken('HYPHENRANGE', `^\\s*(${src[t.XRANGEPLAIN]})` + - `\\s+-\\s+` + - `(${src[t.XRANGEPLAIN]})` + - `\\s*$`) - -createToken('HYPHENRANGELOOSE', `^\\s*(${src[t.XRANGEPLAINLOOSE]})` + - `\\s+-\\s+` + - `(${src[t.XRANGEPLAINLOOSE]})` + - `\\s*$`) - -// Star ranges basically just allow anything at all. -createToken('STAR', '(<|>)?=?\\s*\\*') -// >=0.0.0 is like a star -createToken('GTE0', '^\\s*>=\\s*0\\.0\\.0\\s*$') -createToken('GTE0PRE', '^\\s*>=\\s*0\\.0\\.0-0\\s*$') - - -/***/ }), - -/***/ 12276: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -// Determine if version is greater than all the versions possible in the range. -const outside = __nccwpck_require__(10280) -const gtr = (version, range, options) => outside(version, range, '>', options) -module.exports = gtr - - -/***/ }), - -/***/ 23465: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const Range = __nccwpck_require__(96782) -const intersects = (r1, r2, options) => { - r1 = new Range(r1, options) - r2 = new Range(r2, options) - return r1.intersects(r2, options) -} -module.exports = intersects - - -/***/ }), - -/***/ 15213: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const outside = __nccwpck_require__(10280) -// Determine if version is less than all the versions possible in the range -const ltr = (version, range, options) => outside(version, range, '<', options) -module.exports = ltr - - -/***/ }), - -/***/ 73193: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const SemVer = __nccwpck_require__(7163) -const Range = __nccwpck_require__(96782) - -const maxSatisfying = (versions, range, options) => { - let max = null - let maxSV = null - let rangeObj = null - try { - rangeObj = new Range(range, options) - } catch (er) { - return null - } - versions.forEach((v) => { - if (rangeObj.test(v)) { - // satisfies(v, range, options) - if (!max || maxSV.compare(v) === -1) { - // compare(max, v, true) - max = v - maxSV = new SemVer(max, options) - } - } - }) - return max -} -module.exports = maxSatisfying - - -/***/ }), - -/***/ 68595: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const SemVer = __nccwpck_require__(7163) -const Range = __nccwpck_require__(96782) -const minSatisfying = (versions, range, options) => { - let min = null - let minSV = null - let rangeObj = null - try { - rangeObj = new Range(range, options) - } catch (er) { - return null - } - versions.forEach((v) => { - if (rangeObj.test(v)) { - // satisfies(v, range, options) - if (!min || minSV.compare(v) === 1) { - // compare(min, v, true) - min = v - minSV = new SemVer(min, options) - } - } - }) - return min -} -module.exports = minSatisfying - - -/***/ }), - -/***/ 51866: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const SemVer = __nccwpck_require__(7163) -const Range = __nccwpck_require__(96782) -const gt = __nccwpck_require__(16599) - -const minVersion = (range, loose) => { - range = new Range(range, loose) - - let minver = new SemVer('0.0.0') - if (range.test(minver)) { - return minver - } - - minver = new SemVer('0.0.0-0') - if (range.test(minver)) { - return minver - } - - minver = null - for (let i = 0; i < range.set.length; ++i) { - const comparators = range.set[i] - - let setMin = null - comparators.forEach((comparator) => { - // Clone to avoid manipulating the comparator's semver object. - const compver = new SemVer(comparator.semver.version) - switch (comparator.operator) { - case '>': - if (compver.prerelease.length === 0) { - compver.patch++ - } else { - compver.prerelease.push(0) - } - compver.raw = compver.format() - /* fallthrough */ - case '': - case '>=': - if (!setMin || gt(compver, setMin)) { - setMin = compver - } - break - case '<': - case '<=': - /* Ignore maximum versions */ - break - /* istanbul ignore next */ - default: - throw new Error(`Unexpected operation: ${comparator.operator}`) - } - }) - if (setMin && (!minver || gt(minver, setMin))) { - minver = setMin - } - } - - if (minver && range.test(minver)) { - return minver - } - - return null -} -module.exports = minVersion - - -/***/ }), - -/***/ 10280: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const SemVer = __nccwpck_require__(7163) -const Comparator = __nccwpck_require__(89379) -const { ANY } = Comparator -const Range = __nccwpck_require__(96782) -const satisfies = __nccwpck_require__(68011) -const gt = __nccwpck_require__(16599) -const lt = __nccwpck_require__(3872) -const lte = __nccwpck_require__(56717) -const gte = __nccwpck_require__(41236) - -const outside = (version, range, hilo, options) => { - version = new SemVer(version, options) - range = new Range(range, options) - - let gtfn, ltefn, ltfn, comp, ecomp - switch (hilo) { - case '>': - gtfn = gt - ltefn = lte - ltfn = lt - comp = '>' - ecomp = '>=' - break - case '<': - gtfn = lt - ltefn = gte - ltfn = gt - comp = '<' - ecomp = '<=' - break - default: - throw new TypeError('Must provide a hilo val of "<" or ">"') - } - - // If it satisfies the range it is not outside - if (satisfies(version, range, options)) { - return false - } - - // From now on, variable terms are as if we're in "gtr" mode. - // but note that everything is flipped for the "ltr" function. - - for (let i = 0; i < range.set.length; ++i) { - const comparators = range.set[i] - - let high = null - let low = null - - comparators.forEach((comparator) => { - if (comparator.semver === ANY) { - comparator = new Comparator('>=0.0.0') - } - high = high || comparator - low = low || comparator - if (gtfn(comparator.semver, high.semver, options)) { - high = comparator - } else if (ltfn(comparator.semver, low.semver, options)) { - low = comparator - } - }) - - // If the edge version comparator has a operator then our version - // isn't outside it - if (high.operator === comp || high.operator === ecomp) { - return false - } - - // If the lowest version comparator has an operator and our version - // is less than it then it isn't higher than the range - if ((!low.operator || low.operator === comp) && - ltefn(version, low.semver)) { - return false - } else if (low.operator === ecomp && ltfn(version, low.semver)) { - return false - } - } - return true -} - -module.exports = outside - - -/***/ }), - -/***/ 82028: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -// given a set of versions and a range, create a "simplified" range -// that includes the same versions that the original range does -// If the original range is shorter than the simplified one, return that. -const satisfies = __nccwpck_require__(68011) -const compare = __nccwpck_require__(78469) -module.exports = (versions, range, options) => { - const set = [] - let first = null - let prev = null - const v = versions.sort((a, b) => compare(a, b, options)) - for (const version of v) { - const included = satisfies(version, range, options) - if (included) { - prev = version - if (!first) { - first = version - } - } else { - if (prev) { - set.push([first, prev]) - } - prev = null - first = null - } - } - if (first) { - set.push([first, null]) - } - - const ranges = [] - for (const [min, max] of set) { - if (min === max) { - ranges.push(min) - } else if (!max && min === v[0]) { - ranges.push('*') - } else if (!max) { - ranges.push(`>=${min}`) - } else if (min === v[0]) { - ranges.push(`<=${max}`) - } else { - ranges.push(`${min} - ${max}`) - } - } - const simplified = ranges.join(' || ') - const original = typeof range.raw === 'string' ? range.raw : String(range) - return simplified.length < original.length ? simplified : range -} - - -/***/ }), - -/***/ 61489: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const Range = __nccwpck_require__(96782) -const Comparator = __nccwpck_require__(89379) -const { ANY } = Comparator -const satisfies = __nccwpck_require__(68011) -const compare = __nccwpck_require__(78469) - -// Complex range `r1 || r2 || ...` is a subset of `R1 || R2 || ...` iff: -// - Every simple range `r1, r2, ...` is a null set, OR -// - Every simple range `r1, r2, ...` which is not a null set is a subset of -// some `R1, R2, ...` -// -// Simple range `c1 c2 ...` is a subset of simple range `C1 C2 ...` iff: -// - If c is only the ANY comparator -// - If C is only the ANY comparator, return true -// - Else if in prerelease mode, return false -// - else replace c with `[>=0.0.0]` -// - If C is only the ANY comparator -// - if in prerelease mode, return true -// - else replace C with `[>=0.0.0]` -// - Let EQ be the set of = comparators in c -// - If EQ is more than one, return true (null set) -// - Let GT be the highest > or >= comparator in c -// - Let LT be the lowest < or <= comparator in c -// - If GT and LT, and GT.semver > LT.semver, return true (null set) -// - If any C is a = range, and GT or LT are set, return false -// - If EQ -// - If GT, and EQ does not satisfy GT, return true (null set) -// - If LT, and EQ does not satisfy LT, return true (null set) -// - If EQ satisfies every C, return true -// - Else return false -// - If GT -// - If GT.semver is lower than any > or >= comp in C, return false -// - If GT is >=, and GT.semver does not satisfy every C, return false -// - If GT.semver has a prerelease, and not in prerelease mode -// - If no C has a prerelease and the GT.semver tuple, return false -// - If LT -// - If LT.semver is greater than any < or <= comp in C, return false -// - If LT is <=, and LT.semver does not satisfy every C, return false -// - If GT.semver has a prerelease, and not in prerelease mode -// - If no C has a prerelease and the LT.semver tuple, return false -// - Else return true - -const subset = (sub, dom, options = {}) => { - if (sub === dom) { - return true - } - - sub = new Range(sub, options) - dom = new Range(dom, options) - let sawNonNull = false - - OUTER: for (const simpleSub of sub.set) { - for (const simpleDom of dom.set) { - const isSub = simpleSubset(simpleSub, simpleDom, options) - sawNonNull = sawNonNull || isSub !== null - if (isSub) { - continue OUTER - } - } - // the null set is a subset of everything, but null simple ranges in - // a complex range should be ignored. so if we saw a non-null range, - // then we know this isn't a subset, but if EVERY simple range was null, - // then it is a subset. - if (sawNonNull) { - return false - } - } - return true -} - -const minimumVersionWithPreRelease = [new Comparator('>=0.0.0-0')] -const minimumVersion = [new Comparator('>=0.0.0')] - -const simpleSubset = (sub, dom, options) => { - if (sub === dom) { - return true - } - - if (sub.length === 1 && sub[0].semver === ANY) { - if (dom.length === 1 && dom[0].semver === ANY) { - return true - } else if (options.includePrerelease) { - sub = minimumVersionWithPreRelease - } else { - sub = minimumVersion - } - } - - if (dom.length === 1 && dom[0].semver === ANY) { - if (options.includePrerelease) { - return true - } else { - dom = minimumVersion - } - } - - const eqSet = new Set() - let gt, lt - for (const c of sub) { - if (c.operator === '>' || c.operator === '>=') { - gt = higherGT(gt, c, options) - } else if (c.operator === '<' || c.operator === '<=') { - lt = lowerLT(lt, c, options) - } else { - eqSet.add(c.semver) - } - } - - if (eqSet.size > 1) { - return null - } - - let gtltComp - if (gt && lt) { - gtltComp = compare(gt.semver, lt.semver, options) - if (gtltComp > 0) { - return null - } else if (gtltComp === 0 && (gt.operator !== '>=' || lt.operator !== '<=')) { - return null - } - } - - // will iterate one or zero times - for (const eq of eqSet) { - if (gt && !satisfies(eq, String(gt), options)) { - return null - } - - if (lt && !satisfies(eq, String(lt), options)) { - return null - } - - for (const c of dom) { - if (!satisfies(eq, String(c), options)) { - return false - } - } - - return true - } - - let higher, lower - let hasDomLT, hasDomGT - // if the subset has a prerelease, we need a comparator in the superset - // with the same tuple and a prerelease, or it's not a subset - let needDomLTPre = lt && - !options.includePrerelease && - lt.semver.prerelease.length ? lt.semver : false - let needDomGTPre = gt && - !options.includePrerelease && - gt.semver.prerelease.length ? gt.semver : false - // exception: <1.2.3-0 is the same as <1.2.3 - if (needDomLTPre && needDomLTPre.prerelease.length === 1 && - lt.operator === '<' && needDomLTPre.prerelease[0] === 0) { - needDomLTPre = false - } - - for (const c of dom) { - hasDomGT = hasDomGT || c.operator === '>' || c.operator === '>=' - hasDomLT = hasDomLT || c.operator === '<' || c.operator === '<=' - if (gt) { - if (needDomGTPre) { - if (c.semver.prerelease && c.semver.prerelease.length && - c.semver.major === needDomGTPre.major && - c.semver.minor === needDomGTPre.minor && - c.semver.patch === needDomGTPre.patch) { - needDomGTPre = false - } - } - if (c.operator === '>' || c.operator === '>=') { - higher = higherGT(gt, c, options) - if (higher === c && higher !== gt) { - return false - } - } else if (gt.operator === '>=' && !satisfies(gt.semver, String(c), options)) { - return false - } - } - if (lt) { - if (needDomLTPre) { - if (c.semver.prerelease && c.semver.prerelease.length && - c.semver.major === needDomLTPre.major && - c.semver.minor === needDomLTPre.minor && - c.semver.patch === needDomLTPre.patch) { - needDomLTPre = false - } - } - if (c.operator === '<' || c.operator === '<=') { - lower = lowerLT(lt, c, options) - if (lower === c && lower !== lt) { - return false - } - } else if (lt.operator === '<=' && !satisfies(lt.semver, String(c), options)) { - return false - } - } - if (!c.operator && (lt || gt) && gtltComp !== 0) { - return false - } - } - - // if there was a < or >, and nothing in the dom, then must be false - // UNLESS it was limited by another range in the other direction. - // Eg, >1.0.0 <1.0.1 is still a subset of <2.0.0 - if (gt && hasDomLT && !lt && gtltComp !== 0) { - return false - } - - if (lt && hasDomGT && !gt && gtltComp !== 0) { - return false - } - - // we needed a prerelease range in a specific tuple, but didn't get one - // then this isn't a subset. eg >=1.2.3-pre is not a subset of >=1.0.0, - // because it includes prereleases in the 1.2.3 tuple - if (needDomGTPre || needDomLTPre) { - return false - } - - return true -} - -// >=1.2.3 is lower than >1.2.3 -const higherGT = (a, b, options) => { - if (!a) { - return b - } - const comp = compare(a.semver, b.semver, options) - return comp > 0 ? a - : comp < 0 ? b - : b.operator === '>' && a.operator === '>=' ? b - : a -} - -// <=1.2.3 is higher than <1.2.3 -const lowerLT = (a, b, options) => { - if (!a) { - return b - } - const comp = compare(a.semver, b.semver, options) - return comp < 0 ? a - : comp > 0 ? b - : b.operator === '<' && a.operator === '<=' ? b - : a -} - -module.exports = subset - - -/***/ }), - -/***/ 54750: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const Range = __nccwpck_require__(96782) - -// Mostly just for testing and legacy API reasons -const toComparators = (range, options) => - new Range(range, options).set - .map(comp => comp.map(c => c.value).join(' ').trim().split(' ')) - -module.exports = toComparators - - -/***/ }), - -/***/ 64737: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -const Range = __nccwpck_require__(96782) -const validRange = (range, options) => { - try { - // Return '*' instead of '' so that truthiness works. - // This will throw if it's invalid anyway - return new Range(range, options).range || '*' - } catch (er) { - return null - } -} -module.exports = validRange - - /***/ }), /***/ 67290: @@ -208328,6 +205661,9 @@ async function meetsPrerequisites() { if (!(await version_1.viewerVersion.viewerSupports(version_1.ViewerFeature.GITHUB_ACTION))) { throw Error(`Minimum required TICS Viewer version is 2022.4.0.`); } + else if (config_1.ticsConfig.createProject && !(await version_1.viewerVersion.viewerSupports(version_1.ViewerFeature.PROJECT_CREATION))) { + throw Error(`Minimum required TICS Viewer version for creating a project is 2026.1.2.54221.`); + } else if (config_1.ticsConfig.mode === tics_1.Mode.DIAGNOSTIC) { /* No need for checked out repository. */ } diff --git a/dist/licenses.txt b/dist/licenses.txt index dec5250c..e5aa0e39 100644 --- a/dist/licenses.txt +++ b/dist/licenses.txt @@ -3321,25 +3321,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -semver -ISC -The ISC License - -Copyright (c) Isaac Z. Schlueter and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM 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. - - smart-buffer MIT The MIT License (MIT) diff --git a/package-lock.json b/package-lock.json index 83ac4791..a794d550 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,8 +23,7 @@ "date-fns": "^4.1.0", "lodash": "^4.17.23", "node-fetch": "^2.7.0", - "proxy-agent": "^6.5.0", - "semver": "^7.6.3" + "proxy-agent": "^6.5.0" }, "devDependencies": { "@eslint/js": "^9.10.0", @@ -34,7 +33,6 @@ "@types/jest": "^30.0.0", "@types/lodash": "^4.17.16", "@types/node": "^22.5.5", - "@types/semver": "^7.5.8", "@vercel/ncc": "^0.38.4", "async-listen": "^3.0.1", "eslint": "^9.39.1", @@ -3597,12 +3595,6 @@ "form-data": "^4.0.0" } }, - "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", - "dev": true - }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -10322,9 +10314,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash.memoize": { @@ -11226,6 +11218,7 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" diff --git a/package.json b/package.json index 234a18d5..862258ed 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,7 @@ "date-fns": "^4.1.0", "lodash": "^4.17.23", "node-fetch": "^2.7.0", - "proxy-agent": "^6.5.0", - "semver": "^7.6.3" + "proxy-agent": "^6.5.0" }, "devDependencies": { "@eslint/js": "^9.10.0", @@ -52,7 +51,6 @@ "@types/jest": "^30.0.0", "@types/lodash": "^4.17.16", "@types/node": "^22.5.5", - "@types/semver": "^7.5.8", "@vercel/ncc": "^0.38.4", "async-listen": "^3.0.1", "eslint": "^9.39.1", diff --git a/src/main.ts b/src/main.ts index d1eb0ae6..0a0ca6f1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -57,6 +57,8 @@ export async function main(): Promise { async function meetsPrerequisites(): Promise { if (!(await viewerVersion.viewerSupports(ViewerFeature.GITHUB_ACTION))) { throw Error(`Minimum required TICS Viewer version is 2022.4.0.`); + } else if (ticsConfig.createProject && !(await viewerVersion.viewerSupports(ViewerFeature.PROJECT_CREATION))) { + throw Error(`Minimum required TICS Viewer version for creating a project is 2026.1.2.54221.`); } else if (ticsConfig.mode === Mode.DIAGNOSTIC) { /* No need for checked out repository. */ } else if (ticsConfig.mode === Mode.CLIENT && !githubConfig.event.isPullRequest && ticsConfig.filelist === '') { diff --git a/src/viewer/interfaces.d.ts b/src/viewer/interfaces.d.ts index 9cab6e8f..b997e79d 100644 --- a/src/viewer/interfaces.d.ts +++ b/src/viewer/interfaces.d.ts @@ -157,7 +157,7 @@ export interface Annotation { export interface VersionResponse { buildTime?: string; revision?: string; - version: string; + version?: string; fullVersion?: string; project?: string; dbversion?: string; diff --git a/src/viewer/project.ts b/src/viewer/project.ts index 9f8fb25a..98715ccf 100644 --- a/src/viewer/project.ts +++ b/src/viewer/project.ts @@ -1,5 +1,5 @@ import { HttpBadRequestResponse } from '@tiobe/http-client'; -import { ticsCli, ticsConfig } from '../configuration/config'; +import { githubConfig, ticsCli, ticsConfig } from '../configuration/config'; import { logger } from '../helper/logger'; import { getRetryErrorMessage } from '../helper/response'; import { joinUrl } from '../helper/url'; @@ -16,7 +16,11 @@ export async function createProject(): Promise { branchDir: ticsCli.branchdir, branchName: getBranchName(), calculate: true, - visible: true + visible: true, + scmTool: { + name: 'Git', + db: githubConfig.reponame + } }; try { logger.header('Creating/updating the TICS project'); diff --git a/src/viewer/version.ts b/src/viewer/version.ts index 1dc676c4..03fdd72c 100644 --- a/src/viewer/version.ts +++ b/src/viewer/version.ts @@ -4,37 +4,30 @@ import { logger } from '../helper/logger'; import { joinUrl } from '../helper/url'; import { httpClient } from './http-client'; import { ticsConfig } from '../configuration/config'; -import { coerce, satisfies, SemVer } from 'semver'; export enum ViewerFeature { - GITHUB_ACTION = '>=2022.4.0', - NEW_ANNOTATIONS = '>=2025.1.8' + GITHUB_ACTION = '2022.4.0', + NEW_ANNOTATIONS = '2025.1.8', + PROJECT_CREATION = '2026.1.2.54221' } class ViewerVersion { - private viewerVersion?: SemVer; + private viewerVersion?: string; - /** - * Gets the version of the TICS viewer used. - * @returns Version of the used TICS viewer. - */ async viewerSupports(feature: ViewerFeature): Promise { if (this.viewerVersion !== undefined) { - logger.debug(`Getting version from cache: ${this.viewerVersion.version}`); - return satisfies(this.viewerVersion, feature); + logger.debug(`Getting version from cache: ${this.viewerVersion}`); + return this.satisfies(this.viewerVersion, feature); } const viewerVersion = await this.fetchViewerVersion(); - const cleanVersion = coerce(viewerVersion.version); - - if (cleanVersion !== null) { - logger.info(`Found viewer with version: ${cleanVersion.version}`); - this.viewerVersion = cleanVersion; - } else { - throw Error(`Could not compute version received by the viewer, got: ${viewerVersion.version}.`); + if (!viewerVersion.version) { + throw Error(`Viewer returned empty version.`); } + this.viewerVersion = viewerVersion.version; + logger.info(`Found viewer with version: ${this.viewerVersion}`); - return satisfies(cleanVersion, feature); + return this.satisfies(this.viewerVersion, feature); } private async fetchViewerVersion(): Promise { @@ -52,5 +45,28 @@ class ViewerVersion { throw Error(`There was an error retrieving the Viewer version: ${message}`); } } + + /** + * Checks if version given is at least as high as the minimum version given. + */ + private satisfies(version: string, minimum: string): boolean { + const v = this.parseVersion(version); + const m = this.parseVersion(minimum); + const len = Math.max(v.length, m.length); + + for (let i = 0; i < len; i++) { + const a = v[i] ?? 0; + const b = m[i] ?? 0; + if (a !== b) return a > b; + } + return true; + } + + private parseVersion(version: string): number[] { + return version + .replace(/^[^\d]+|[^\d]+$/g, '') + .split('.') + .map(Number); + } } export const viewerVersion = new ViewerVersion(); diff --git a/test/unit/main.test.ts b/test/unit/main.test.ts index 422ae1c3..f88c9d80 100644 --- a/test/unit/main.test.ts +++ b/test/unit/main.test.ts @@ -43,6 +43,25 @@ describe('meetsPrerequisites', () => { expect((error as Error).message).toEqual(expect.stringContaining('Minimum required TICS Viewer version is 2022.4.0.')); }); + it('should throw error if viewer version is too low for project creation', async () => { + ticsConfigMock.createProject = true; + viewerVersionSpy.mockResolvedValueOnce(true); // satisfies viewer version + viewerVersionSpy.mockResolvedValueOnce(false); + + let error: any; + try { + await main(); + } catch (err) { + error = err; + } + + expect(error).toBeInstanceOf(Error); + expect((error as Error).message).toEqual( + expect.stringContaining('Minimum required TICS Viewer version for creating a project is 2026.1.2.54221.') + ); + ticsConfigMock.createProject = false; + }); + it('should throw error on mode Client when it is not a pull request and no filelist is given', async () => { viewerVersionSpy.mockResolvedValue(true); githubConfigMock.event = GithubEvent.WORKFLOW_RUN; diff --git a/test/unit/viewer/project.test.ts b/test/unit/viewer/project.test.ts index 5edf8dab..51753b1e 100644 --- a/test/unit/viewer/project.test.ts +++ b/test/unit/viewer/project.test.ts @@ -42,7 +42,11 @@ describe('createProject', () => { branchDir: '.', branchName: 'main', calculate: true, - visible: true + visible: true, + scmTool: { + name: 'Git', + db: 'test' + } }) ); expect(infoSpy).toHaveBeenCalledWith(`Created database created (took 4s, dbversion: 143), Added project 'PROJECTS => created' to configuration"`); @@ -71,7 +75,11 @@ describe('createProject', () => { branchDir: '.', branchName: 'branch', calculate: true, - visible: true + visible: true, + scmTool: { + name: 'Git', + db: 'test' + } }) ); expect(infoSpy).toHaveBeenCalledWith(`Created database created (took 4s, dbversion: 143), Added project 'PROJECTS => created' to configuration"`); @@ -93,7 +101,11 @@ describe('createProject', () => { branchDir: '.', branchName: 'main', calculate: true, - visible: true + visible: true, + scmTool: { + name: 'Git', + db: 'test' + } }) ); expect(infoSpy).toHaveBeenCalledTimes(0); diff --git a/test/unit/viewer/version.test.ts b/test/unit/viewer/version.test.ts index 4d4fcb89..c52f7e18 100644 --- a/test/unit/viewer/version.test.ts +++ b/test/unit/viewer/version.test.ts @@ -61,6 +61,14 @@ describe('getViewerVersion', () => { expect(response).toBeTruthy(); }); + it('should return true if viewer version is insufficient with reversion', async () => { + jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { version: '2026.1.2.54220' }, retryCount: 0, status: 200 }); + + const response = await viewerVersion.viewerSupports(ViewerFeature.PROJECT_CREATION); + + expect(response).toBeFalsy(); + }); + it('should throw viewer returns unparsable version', async () => { jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { version: null }, retryCount: 0, status: 200 }); @@ -72,6 +80,6 @@ describe('getViewerVersion', () => { } expect(error).toBeInstanceOf(Error); - expect(error.message).toStrictEqual('Could not compute version received by the viewer, got: null.'); + expect(error.message).toStrictEqual('Viewer returned empty version.'); }); }); From 66342d8f2d33de1476e6d3458fa58234ba7690d1 Mon Sep 17 00:00:00 2001 From: janssen <118828444+janssen-tiobe@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:44:20 +0200 Subject: [PATCH 12/13] RM-35685: Fixed wrong naming of tests, also added some to test revision checks --- test/unit/viewer/version.test.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/test/unit/viewer/version.test.ts b/test/unit/viewer/version.test.ts index c52f7e18..795f7a5a 100644 --- a/test/unit/viewer/version.test.ts +++ b/test/unit/viewer/version.test.ts @@ -61,7 +61,7 @@ describe('getViewerVersion', () => { expect(response).toBeTruthy(); }); - it('should return true if viewer version is insufficient with reversion', async () => { + it('should return false if viewer version is insufficient with reversion', async () => { jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { version: '2026.1.2.54220' }, retryCount: 0, status: 200 }); const response = await viewerVersion.viewerSupports(ViewerFeature.PROJECT_CREATION); @@ -69,6 +69,22 @@ describe('getViewerVersion', () => { expect(response).toBeFalsy(); }); + it('should return true if viewer version is sufficient with reversion', async () => { + jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { version: '2026.1.2.54222' }, retryCount: 0, status: 200 }); + + const response = await viewerVersion.viewerSupports(ViewerFeature.PROJECT_CREATION); + + expect(response).toBeTruthy(); + }); + + it('should return true if viewer version is sufficient with no reversion', async () => { + jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { version: '2026.1.3' }, retryCount: 0, status: 200 }); + + const response = await viewerVersion.viewerSupports(ViewerFeature.PROJECT_CREATION); + + expect(response).toBeTruthy(); + }); + it('should throw viewer returns unparsable version', async () => { jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { version: null }, retryCount: 0, status: 200 }); From 6c53baa823ff2b7648a078a0631422fb41b932b4 Mon Sep 17 00:00:00 2001 From: janssen <118828444+janssen-tiobe@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:35:55 +0200 Subject: [PATCH 13/13] RM-35685: Now using the full version returned by the viewer (including revision). --- dist/index.js | 4 ++-- src/viewer/version.ts | 4 ++-- test/unit/viewer/version.test.ts | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/dist/index.js b/dist/index.js index 08463a64..01b4fedc 100644 --- a/dist/index.js +++ b/dist/index.js @@ -3300,10 +3300,10 @@ class ViewerVersion { return this.satisfies(this.viewerVersion, feature); } const viewerVersion = await this.fetchViewerVersion(); - if (!viewerVersion.version) { + if (!viewerVersion.fullVersion) { throw Error(`Viewer returned empty version.`); } - this.viewerVersion = viewerVersion.version; + this.viewerVersion = viewerVersion.fullVersion; logger_1.logger.info(`Found viewer with version: ${this.viewerVersion}`); return this.satisfies(this.viewerVersion, feature); } diff --git a/src/viewer/version.ts b/src/viewer/version.ts index 03fdd72c..25bc83ef 100644 --- a/src/viewer/version.ts +++ b/src/viewer/version.ts @@ -21,10 +21,10 @@ class ViewerVersion { } const viewerVersion = await this.fetchViewerVersion(); - if (!viewerVersion.version) { + if (!viewerVersion.fullVersion) { throw Error(`Viewer returned empty version.`); } - this.viewerVersion = viewerVersion.version; + this.viewerVersion = viewerVersion.fullVersion; logger.info(`Found viewer with version: ${this.viewerVersion}`); return this.satisfies(this.viewerVersion, feature); diff --git a/test/unit/viewer/version.test.ts b/test/unit/viewer/version.test.ts index 795f7a5a..c5fea61c 100644 --- a/test/unit/viewer/version.test.ts +++ b/test/unit/viewer/version.test.ts @@ -25,12 +25,12 @@ describe('getViewerVersion', () => { }); it('should return no github action support if version < 2022.4.0', async () => { - jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { version: '2022.0.0' }, retryCount: 0, status: 200 }); + jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { fullVersion: '2022.0.0' }, retryCount: 0, status: 200 }); const response1 = await viewerVersion.viewerSupports(ViewerFeature.GITHUB_ACTION); // check if response is cached - jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { version: '2023.1.0' }, retryCount: 0, status: 200 }); + jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { fullVersion: '2023.1.0' }, retryCount: 0, status: 200 }); const response2 = await viewerVersion.viewerSupports(ViewerFeature.GITHUB_ACTION); expect(response1).toBeFalsy(); @@ -38,7 +38,7 @@ describe('getViewerVersion', () => { }); it('should return github action support if version >= 2022.4.0', async () => { - jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { version: '2023.1.0' }, retryCount: 0, status: 200 }); + jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { fullVersion: '2023.1.0' }, retryCount: 0, status: 200 }); const response = await viewerVersion.viewerSupports(ViewerFeature.GITHUB_ACTION); @@ -46,7 +46,7 @@ describe('getViewerVersion', () => { }); it('should return false if viewer version is too low with prefix character', async () => { - jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { version: 'r2022.1.0' }, retryCount: 0, status: 200 }); + jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { fullVersion: 'r2022.1.0' }, retryCount: 0, status: 200 }); const response = await viewerVersion.viewerSupports(ViewerFeature.GITHUB_ACTION); @@ -54,7 +54,7 @@ describe('getViewerVersion', () => { }); it('should return true if viewer version is sufficient with prefix character', async () => { - jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { version: 'r2025.1.0' }, retryCount: 0, status: 200 }); + jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { fullVersion: 'r2025.1.0' }, retryCount: 0, status: 200 }); const response = await viewerVersion.viewerSupports(ViewerFeature.GITHUB_ACTION); @@ -62,7 +62,7 @@ describe('getViewerVersion', () => { }); it('should return false if viewer version is insufficient with reversion', async () => { - jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { version: '2026.1.2.54220' }, retryCount: 0, status: 200 }); + jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { fullVersion: '2026.1.2.54220' }, retryCount: 0, status: 200 }); const response = await viewerVersion.viewerSupports(ViewerFeature.PROJECT_CREATION); @@ -70,7 +70,7 @@ describe('getViewerVersion', () => { }); it('should return true if viewer version is sufficient with reversion', async () => { - jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { version: '2026.1.2.54222' }, retryCount: 0, status: 200 }); + jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { fullVersion: '2026.1.2.54222' }, retryCount: 0, status: 200 }); const response = await viewerVersion.viewerSupports(ViewerFeature.PROJECT_CREATION); @@ -78,7 +78,7 @@ describe('getViewerVersion', () => { }); it('should return true if viewer version is sufficient with no reversion', async () => { - jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { version: '2026.1.3' }, retryCount: 0, status: 200 }); + jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { fullVersion: '2026.1.3' }, retryCount: 0, status: 200 }); const response = await viewerVersion.viewerSupports(ViewerFeature.PROJECT_CREATION); @@ -86,7 +86,7 @@ describe('getViewerVersion', () => { }); it('should throw viewer returns unparsable version', async () => { - jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { version: null }, retryCount: 0, status: 200 }); + jest.spyOn(httpClient, 'get').mockResolvedValueOnce({ data: { fullVersion: null }, retryCount: 0, status: 200 }); let error: any; try {