UNPKG

@knapsack-pro/core

Version:

Knapsack Pro Core library splits tests across CI nodes and makes sure that tests will run in optimal time on each CI node. This library gives core features like communication with KnapsackPro.com API. This library is a dependency for other projects specif

201 lines (200 loc) 8.54 kB
import { spawnSync, execSync } from 'child_process'; import { CIEnvConfig, isCI, detectCI } from './index.js'; import { KnapsackProLogger } from '../knapsack-pro-logger.js'; import * as Urls from '../urls.js'; function logLevel() { if (process.env.KNAPSACK_PRO_LOG_LEVEL) { return process.env.KNAPSACK_PRO_LOG_LEVEL; } return 'info'; } const knapsackProLogger = new KnapsackProLogger(logLevel()); const mask = (string) => { const regexp = /(?<=\w{2})[a-zA-Z]/g; const maskingChar = '*'; return string.replace(regexp, maskingChar); }; export class KnapsackProEnvConfig { static $fixedQueueSplit; static $knapsackProLogger = knapsackProLogger; static get endpoint() { if (process.env.KNAPSACK_PRO_ENDPOINT) { return process.env.KNAPSACK_PRO_ENDPOINT; } return 'https://api.knapsackpro.com'; } static get testSuiteToken() { if (process.env.KNAPSACK_PRO_TEST_SUITE_TOKEN) { return process.env.KNAPSACK_PRO_TEST_SUITE_TOKEN; } throw new Error(`Please set test suite API token in CI environment variables. Please check README for the Knapsack Pro client library.`); } static get fixedQueueSplit() { if (this.$fixedQueueSplit !== undefined) { return this.$fixedQueueSplit; } const envValue = this.envFor('KNAPSACK_PRO_FIXED_QUEUE_SPLIT', 'fixedQueueSplit'); this.$fixedQueueSplit = this.parseBoolean(envValue, 'KNAPSACK_PRO_FIXED_QUEUE_SPLIT'); if (!('KNAPSACK_PRO_FIXED_QUEUE_SPLIT' in process.env)) { this.$knapsackProLogger.info(`KNAPSACK_PRO_FIXED_QUEUE_SPLIT is not set. Using default value: ${this.$fixedQueueSplit}. Learn more at ${Urls.FIXED_QUEUE_SPLIT}`); } return this.$fixedQueueSplit; } static parseBoolean(value, name) { if (typeof value === 'boolean') { return value; } switch (value.toLowerCase()) { case 'true': return true; case 'false': return false; default: throw new Error(`'${value}' is not a valid value for ${name}. Use either 'true' or 'false'.`); } } static logOverwrittenEnv(envName, envValue, ciEnvValue) { this.$knapsackProLogger.info(`You have set the environment variable ${envName} to ${envValue} which could be automatically determined from the CI environment as ${ciEnvValue}.`); } static envFor(knapsackEnvName, ciEnvFunction) { const knapsackEnvValue = process.env[knapsackEnvName]; const ciEnvValue = CIEnvConfig[ciEnvFunction]; if (knapsackEnvValue !== undefined && ciEnvValue !== undefined && knapsackEnvValue !== ciEnvValue.toString()) { this.logOverwrittenEnv(knapsackEnvName, knapsackEnvValue, ciEnvValue.toString()); } return knapsackEnvValue !== undefined ? knapsackEnvValue : ciEnvValue; } static get ciNodeTotal() { const envValue = this.envFor('KNAPSACK_PRO_CI_NODE_TOTAL', 'ciNodeTotal'); if (envValue) { return parseInt(envValue, 10); } throw new Error('Undefined number of total CI nodes! Please set KNAPSACK_PRO_CI_NODE_TOTAL environment variable.'); } static get ciNodeIndex() { const envValue = this.envFor('KNAPSACK_PRO_CI_NODE_INDEX', 'ciNodeIndex'); if (envValue) { return parseInt(envValue, 10); } throw new Error('Undefined CI node index! Please set KNAPSACK_PRO_CI_NODE_INDEX environment variable.'); } static get ciNodeBuildId() { const envValue = this.envFor('KNAPSACK_PRO_CI_NODE_BUILD_ID', 'ciNodeBuildId'); if (envValue) { return envValue; } throw new Error(`Missing environment variable KNAPSACK_PRO_CI_NODE_BUILD_ID. Read more at ${Urls.KNAPSACK_PRO_CI_NODE_BUILD_ID}`); } static get ciNodeRetryCount() { const envValue = this.envFor('KNAPSACK_PRO_CI_NODE_RETRY_COUNT', 'ciNodeRetryCount'); if (envValue) { return parseInt(envValue, 10); } return 0; } static get commitHash() { const envValue = this.envFor('KNAPSACK_PRO_COMMIT_HASH', 'commitHash'); if (envValue) { return envValue; } const gitProcess = spawnSync('git', ['rev-parse', 'HEAD']); if (gitProcess.status === 0) { const gitCommitHash = gitProcess.stdout.toString().trim(); process.env.KNAPSACK_PRO_COMMIT_HASH = gitCommitHash; return gitCommitHash; } if (gitProcess.stderr === null) { knapsackProLogger.error('We tried to detect commit hash using git but it failed. Please ensure you have have git installed or set KNAPSACK_PRO_COMMIT_HASH environment variable.'); } else { const gitErrorMessage = gitProcess.stderr.toString(); knapsackProLogger.error('There was error in detecting commit hash using git installed on the machine:'); knapsackProLogger.error(gitErrorMessage); } throw new Error('Undefined commit hash! Please set KNAPSACK_PRO_COMMIT_HASH environment variable.'); } static get branch() { const envValue = this.envFor('KNAPSACK_PRO_BRANCH', 'branch'); if (envValue) { return envValue; } const gitProcess = spawnSync('git', ['rev-parse', '--abbrev-ref', 'HEAD']); if (gitProcess.status === 0) { const gitBranch = gitProcess.stdout.toString().trim(); process.env.KNAPSACK_PRO_BRANCH = gitBranch; return gitBranch; } if (gitProcess.stderr === null) { knapsackProLogger.error('We tried to detect branch name using git but it failed. Please ensure you have have git installed or set KNAPSACK_PRO_BRANCH environment variable.'); } else { const gitErrorMessage = gitProcess.stderr.toString(); knapsackProLogger.error('There was error in detecting branch name using git installed on the machine:'); knapsackProLogger.error(gitErrorMessage); } throw new Error('Undefined branch name! Please set KNAPSACK_PRO_BRANCH environment variable.'); } static get logLevel() { return logLevel(); } static get testFileListSourceFile() { return process.env.KNAPSACK_PRO_TEST_FILE_LIST_SOURCE_FILE; } static get maskedUserSeat() { const envValue = this.envFor('KNAPSACK_PRO_USER_SEAT', 'userSeat'); if (envValue) { return mask(envValue); } return undefined; } } const $buildAuthor = (command) => { try { const author = command().toString().trim(); return mask(author); } catch { return 'no git <no.git@example.com>'; } }; const gitBuildAuthor = () => execSync('git log --format="%aN <%aE>" -1 2>/dev/null'); export const buildAuthor = (command = gitBuildAuthor) => $buildAuthor(command); const gitIsShallowRepository = () => execSync(`git rev-parse --is-shallow-repository 2>/dev/null`); const $isShallowRepository = (command) => command().toString().trim() === 'true'; export const isShallowRepository = (command = gitIsShallowRepository) => $isShallowRepository(command); const $commitAuthors = (command) => { try { return command() .toString() .split('\n') .filter((line) => line !== '') .map((line) => line.trim()) .map((line) => line.split('\t')) .map(([commits, author]) => ({ commits: parseInt(commits, 10), author: mask(author), })); } catch { return []; } }; const gitCommitAuthors = () => { if (isCI() && isShallowRepository()) { const gitFetchShallowSinceCommand = 'git fetch --shallow-since "one month ago" --quiet 2>/dev/null'; try { execSync(gitFetchShallowSinceCommand, { timeout: 5000, }); } catch (error) { knapsackProLogger.debug(`Skip the \`${gitFetchShallowSinceCommand}\` command because it took too long. Error: ${error.message}`); } } return execSync(`git log --since "one month ago" 2>/dev/null | git shortlog --summary --email 2>/dev/null`); }; export const commitAuthors = (command = gitCommitAuthors) => $commitAuthors(command); export const ciProvider = () => detectCI().ciProvider;