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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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);