| #!/usr/bin/env node | ||
| export {}; |
+60
| #!/usr/bin/env node | ||
| "use strict"; | ||
| 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 }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| const yargs_1 = __importDefault(require("yargs/yargs")); | ||
| const helpers_1 = require("yargs/helpers"); | ||
| const merge_1 = require("./merge"); | ||
| const flaky_1 = require("./flaky"); | ||
| const argv = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv)) | ||
| .command('merge <directory>', 'Merge CTRF reports into a single report', yargs => { | ||
| return yargs | ||
| .positional('directory', { | ||
| describe: 'Directory of the CTRF reports', | ||
| type: 'string', | ||
| demandOption: true, | ||
| }) | ||
| .option('output', { | ||
| alias: 'o', | ||
| describe: 'Output file path (default: ctrf-report.json)', | ||
| type: 'string', | ||
| default: 'ctrf-report.json', | ||
| }) | ||
| .option('output-dir', { | ||
| alias: 'd', | ||
| describe: 'Output directory for merged report', | ||
| type: 'string', | ||
| hidden: true, | ||
| deprecated: 'Use --output with a path instead', | ||
| }) | ||
| .option('keep-reports', { | ||
| alias: 'k', | ||
| describe: 'Keep existing reports after merging', | ||
| type: 'boolean', | ||
| default: false, | ||
| }); | ||
| }, (argv) => __awaiter(void 0, void 0, void 0, function* () { | ||
| yield (0, merge_1.mergeReports)(argv.directory, argv.output, argv['keep-reports'], argv['output-dir']); | ||
| })) | ||
| .command('flaky <file>', 'Identify flaky tests from a CTRF report file', yargs => { | ||
| return yargs.positional('file', { | ||
| describe: 'CTRF report file', | ||
| type: 'string', | ||
| demandOption: true, | ||
| }); | ||
| }, (argv) => __awaiter(void 0, void 0, void 0, function* () { | ||
| yield (0, flaky_1.identifyFlakyTests)(argv.file); | ||
| })) | ||
| .help() | ||
| .demandCommand(1, 'You need at least one command before moving on').argv; |
| export declare function identifyFlakyTests(filePath: string): Promise<void>; |
| "use strict"; | ||
| 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 }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.identifyFlakyTests = void 0; | ||
| const fs_1 = __importDefault(require("fs")); | ||
| const path_1 = __importDefault(require("path")); | ||
| function identifyFlakyTests(filePath) { | ||
| return __awaiter(this, void 0, void 0, function* () { | ||
| try { | ||
| const resolvedFilePath = path_1.default.resolve(filePath); | ||
| if (!fs_1.default.existsSync(resolvedFilePath)) { | ||
| console.error(`The file ${resolvedFilePath} does not exist.`); | ||
| return; | ||
| } | ||
| const fileContent = fs_1.default.readFileSync(resolvedFilePath, 'utf8'); | ||
| const report = JSON.parse(fileContent); | ||
| const flakyTests = report.results.tests.filter((test) => test.flaky === true); | ||
| if (flakyTests.length > 0) { | ||
| console.log(`Found ${flakyTests.length} flaky test(s):`); | ||
| flakyTests.forEach((test) => { | ||
| console.log(`- Test Name: ${test.name}, Retries: ${test.retries}`); | ||
| }); | ||
| } | ||
| else { | ||
| console.log(`No flaky tests found in ${resolvedFilePath}.`); | ||
| } | ||
| } | ||
| catch (error) { | ||
| console.error('Error identifying flaky tests:', error); | ||
| } | ||
| }); | ||
| } | ||
| exports.identifyFlakyTests = identifyFlakyTests; |
| export { mergeReports } from './methods/merge-reports'; | ||
| export { readReportsFromDirectory } from './methods/read-reports'; | ||
| export { readReportsFromGlobPattern } from './methods/read-reports'; |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.readReportsFromGlobPattern = exports.readReportsFromDirectory = exports.mergeReports = void 0; | ||
| var merge_reports_1 = require("./methods/merge-reports"); | ||
| Object.defineProperty(exports, "mergeReports", { enumerable: true, get: function () { return merge_reports_1.mergeReports; } }); | ||
| var read_reports_1 = require("./methods/read-reports"); | ||
| Object.defineProperty(exports, "readReportsFromDirectory", { enumerable: true, get: function () { return read_reports_1.readReportsFromDirectory; } }); | ||
| var read_reports_2 = require("./methods/read-reports"); | ||
| Object.defineProperty(exports, "readReportsFromGlobPattern", { enumerable: true, get: function () { return read_reports_2.readReportsFromGlobPattern; } }); |
| export declare function mergeReports(directory: string, output: string, keepReports: boolean, outputDir?: string): Promise<void>; |
+118
| "use strict"; | ||
| 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 }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.mergeReports = void 0; | ||
| const fs_1 = __importDefault(require("fs")); | ||
| const path_1 = __importDefault(require("path")); | ||
| function mergeReports(directory, output, keepReports, outputDir) { | ||
| return __awaiter(this, void 0, void 0, function* () { | ||
| try { | ||
| const directoryPath = path_1.default.resolve(directory); | ||
| let outputPath; | ||
| if (outputDir) { | ||
| console.warn('Warning: --output-dir is deprecated. Use --output with a path instead.'); | ||
| const outputFileName = output; | ||
| const resolvedOutputDir = path_1.default.resolve(outputDir); | ||
| outputPath = path_1.default.join(resolvedOutputDir, outputFileName); | ||
| } | ||
| else if (output.includes('/') || output.includes('\\')) { | ||
| const resolvedPath = path_1.default.resolve(output); | ||
| const endsWithSeparator = output.endsWith('/') || output.endsWith('\\'); | ||
| const isExistingDirectory = fs_1.default.existsSync(resolvedPath) && fs_1.default.statSync(resolvedPath).isDirectory(); | ||
| const hasNoExtension = path_1.default.extname(output) === ''; | ||
| if (endsWithSeparator || isExistingDirectory || hasNoExtension) { | ||
| outputPath = path_1.default.join(resolvedPath, 'ctrf-report.json'); | ||
| } | ||
| else { | ||
| outputPath = resolvedPath; | ||
| } | ||
| } | ||
| else { | ||
| outputPath = path_1.default.join(directoryPath, output); | ||
| } | ||
| console.log('Merging CTRF reports...'); | ||
| const files = fs_1.default.readdirSync(directoryPath); | ||
| files.forEach(file => { | ||
| console.log('Found file:', file); | ||
| }); | ||
| const ctrfReportFiles = files.filter(file => { | ||
| try { | ||
| if (path_1.default.extname(file) !== '.json') { | ||
| console.log(`Skipping non-CTRF file: ${file}`); | ||
| return false; | ||
| } | ||
| const filePath = path_1.default.join(directoryPath, file); | ||
| const fileContent = fs_1.default.readFileSync(filePath, 'utf8'); | ||
| const jsonData = JSON.parse(fileContent); | ||
| if (!Object.prototype.hasOwnProperty.call(jsonData, 'results')) { | ||
| console.log(`Skipping non-CTRF file: ${file}`); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| catch (error) { | ||
| console.error(`Error reading JSON file '${file}':`, error); | ||
| return false; | ||
| } | ||
| }); | ||
| if (ctrfReportFiles.length === 0) { | ||
| console.log('No CTRF reports found in the specified directory.'); | ||
| return; | ||
| } | ||
| const outputDirPath = path_1.default.dirname(outputPath); | ||
| if (!fs_1.default.existsSync(outputDirPath)) { | ||
| fs_1.default.mkdirSync(outputDirPath, { recursive: true }); | ||
| console.log(`Created output directory: ${outputDirPath}`); | ||
| } | ||
| const mergedReport = ctrfReportFiles | ||
| .map(file => { | ||
| console.log('Merging report:', file); | ||
| const filePath = path_1.default.join(directoryPath, file); | ||
| const fileContent = fs_1.default.readFileSync(filePath, 'utf8'); | ||
| return JSON.parse(fileContent); | ||
| }) | ||
| .reduce((acc, curr) => { | ||
| if (!acc.results) { | ||
| return curr; | ||
| } | ||
| acc.results.summary.tests += curr.results.summary.tests; | ||
| acc.results.summary.passed += curr.results.summary.passed; | ||
| acc.results.summary.failed += curr.results.summary.failed; | ||
| acc.results.summary.skipped += curr.results.summary.skipped; | ||
| acc.results.summary.pending += curr.results.summary.pending; | ||
| acc.results.summary.other += curr.results.summary.other; | ||
| acc.results.tests.push(...curr.results.tests); | ||
| acc.results.summary.start = Math.min(acc.results.summary.start, curr.results.summary.start); | ||
| acc.results.summary.stop = Math.max(acc.results.summary.stop, curr.results.summary.stop); | ||
| return acc; | ||
| }, { results: null }); | ||
| fs_1.default.writeFileSync(outputPath, JSON.stringify(mergedReport, null, 2)); | ||
| if (!keepReports) { | ||
| const outputFileName = path_1.default.basename(outputPath); | ||
| ctrfReportFiles.forEach(file => { | ||
| const filePath = path_1.default.join(directoryPath, file); | ||
| if (file !== outputFileName) { | ||
| fs_1.default.unlinkSync(filePath); | ||
| } | ||
| }); | ||
| } | ||
| console.log('CTRF reports merged successfully.'); | ||
| console.log(`Merged report saved to: ${outputPath}`); | ||
| } | ||
| catch (error) { | ||
| console.error('Error merging CTRF reports:', error); | ||
| } | ||
| }); | ||
| } | ||
| exports.mergeReports = mergeReports; |
| import { CtrfReport } from '../../types/ctrf'; | ||
| /** | ||
| * Merges multiple CTRF reports into a single report. | ||
| * | ||
| * @param reports Array of CTRF report objects to be merged. | ||
| * @returns The merged CTRF report object. | ||
| */ | ||
| export declare function mergeReports(reports: CtrfReport[]): CtrfReport; |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.mergeReports = void 0; | ||
| /** | ||
| * Merges multiple CTRF reports into a single report. | ||
| * | ||
| * @param reports Array of CTRF report objects to be merged. | ||
| * @returns The merged CTRF report object. | ||
| */ | ||
| function mergeReports(reports) { | ||
| if (!reports || reports.length === 0) { | ||
| throw new Error('No reports provided for merging.'); | ||
| } | ||
| const mergedReport = { | ||
| results: { | ||
| tool: reports[0].results.tool, | ||
| summary: initializeEmptySummary(), | ||
| tests: [], | ||
| }, | ||
| }; | ||
| reports.forEach(report => { | ||
| const { summary, tests, environment, extra } = report.results; | ||
| mergedReport.results.summary.tests += summary.tests; | ||
| mergedReport.results.summary.passed += summary.passed; | ||
| mergedReport.results.summary.failed += summary.failed; | ||
| mergedReport.results.summary.skipped += summary.skipped; | ||
| mergedReport.results.summary.pending += summary.pending; | ||
| mergedReport.results.summary.other += summary.other; | ||
| if (summary.suites !== undefined) { | ||
| mergedReport.results.summary.suites = | ||
| (mergedReport.results.summary.suites || 0) + summary.suites; | ||
| } | ||
| mergedReport.results.summary.start = Math.min(mergedReport.results.summary.start, summary.start); | ||
| mergedReport.results.summary.stop = Math.max(mergedReport.results.summary.stop, summary.stop); | ||
| mergedReport.results.tests.push(...tests); | ||
| if (environment) { | ||
| mergedReport.results.environment = Object.assign(Object.assign({}, mergedReport.results.environment), environment); | ||
| } | ||
| if (extra) { | ||
| mergedReport.results.extra = Object.assign(Object.assign({}, mergedReport.results.extra), extra); | ||
| } | ||
| }); | ||
| return mergedReport; | ||
| } | ||
| exports.mergeReports = mergeReports; | ||
| /** | ||
| * Initializes an empty summary object. | ||
| * | ||
| * @returns An empty Summary object. | ||
| */ | ||
| function initializeEmptySummary() { | ||
| return { | ||
| tests: 0, | ||
| passed: 0, | ||
| failed: 0, | ||
| skipped: 0, | ||
| pending: 0, | ||
| other: 0, | ||
| start: Number.MAX_SAFE_INTEGER, | ||
| stop: 0, | ||
| }; | ||
| } |
| import { CtrfReport } from '../../types/ctrf'; | ||
| /** | ||
| * Reads a single CTRF report file from a specified path. | ||
| * | ||
| * @param filePath Path to the JSON file containing the CTRF report. | ||
| * @returns The parsed `CtrfReport` object. | ||
| * @throws If the file does not exist, is not a valid JSON, or does not conform to the `CtrfReport` structure. | ||
| */ | ||
| export declare function readSingleReport(filePath: string): CtrfReport; | ||
| /** | ||
| * Reads all CTRF report files from a given directory. | ||
| * | ||
| * @param directory Path to the directory containing JSON files. | ||
| * @returns An array of parsed `CtrfReport` objects. | ||
| * @throws If the directory does not exist or no valid CTRF reports are found. | ||
| */ | ||
| export declare function readReportsFromDirectory(directoryPath: string): CtrfReport[]; | ||
| /** | ||
| * Reads all CTRF report files matching a glob pattern. | ||
| * | ||
| * @param pattern The glob pattern to match files (e.g., ctrf/*.json). | ||
| * @returns An array of parsed `CtrfReport` objects. | ||
| * @throws If no valid CTRF reports are found. | ||
| */ | ||
| export declare function readReportsFromGlobPattern(pattern: string): CtrfReport[]; |
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.readReportsFromGlobPattern = exports.readReportsFromDirectory = exports.readSingleReport = void 0; | ||
| // TO BE REMOVED, WILL USE THE CTRF LIBRARY INSTEAD | ||
| const fs_1 = __importDefault(require("fs")); | ||
| const path_1 = __importDefault(require("path")); | ||
| const glob_1 = require("glob"); | ||
| /** | ||
| * Reads a single CTRF report file from a specified path. | ||
| * | ||
| * @param filePath Path to the JSON file containing the CTRF report. | ||
| * @returns The parsed `CtrfReport` object. | ||
| * @throws If the file does not exist, is not a valid JSON, or does not conform to the `CtrfReport` structure. | ||
| */ | ||
| function readSingleReport(filePath) { | ||
| if (!fs_1.default.existsSync(filePath)) { | ||
| throw new Error(`JSON file not found: ${filePath}`); | ||
| } | ||
| const resolvedPath = path_1.default.resolve(filePath); | ||
| if (!fs_1.default.existsSync(resolvedPath)) { | ||
| throw new Error(`The file '${resolvedPath}' does not exist.`); | ||
| } | ||
| try { | ||
| const content = fs_1.default.readFileSync(resolvedPath, 'utf8'); | ||
| const parsed = JSON.parse(content); | ||
| if (!isCtrfReport(parsed)) { | ||
| throw new Error(`The file '${resolvedPath}' is not a valid CTRF report.`); | ||
| } | ||
| return parsed; | ||
| } | ||
| catch (error) { | ||
| const errorMessage = error.message || 'Unknown error'; | ||
| throw new Error(`Failed to read or parse the file '${resolvedPath}': ${errorMessage}`); | ||
| } | ||
| } | ||
| exports.readSingleReport = readSingleReport; | ||
| /** | ||
| * Reads all CTRF report files from a given directory. | ||
| * | ||
| * @param directory Path to the directory containing JSON files. | ||
| * @returns An array of parsed `CtrfReport` objects. | ||
| * @throws If the directory does not exist or no valid CTRF reports are found. | ||
| */ | ||
| function readReportsFromDirectory(directoryPath) { | ||
| directoryPath = path_1.default.resolve(directoryPath); | ||
| if (!fs_1.default.existsSync(directoryPath)) { | ||
| throw new Error(`The directory '${directoryPath}' does not exist.`); | ||
| } | ||
| const files = fs_1.default.readdirSync(directoryPath); | ||
| const reports = files | ||
| .filter(file => path_1.default.extname(file) === '.json') | ||
| .map(file => { | ||
| const filePath = path_1.default.join(directoryPath, file); | ||
| try { | ||
| const content = fs_1.default.readFileSync(filePath, 'utf8'); | ||
| const parsed = JSON.parse(content); | ||
| if (!isCtrfReport(parsed)) { | ||
| console.warn(`Skipping invalid CTRF report file: ${file}`); | ||
| return null; | ||
| } | ||
| return parsed; | ||
| } | ||
| catch (error) { | ||
| console.warn(`Failed to read or parse file '${file}':`, error); | ||
| return null; | ||
| } | ||
| }) | ||
| .filter((report) => report !== null); | ||
| if (reports.length === 0) { | ||
| throw new Error(`No valid CTRF reports found in the directory '${directoryPath}'.`); | ||
| } | ||
| return reports; | ||
| } | ||
| exports.readReportsFromDirectory = readReportsFromDirectory; | ||
| /** | ||
| * Reads all CTRF report files matching a glob pattern. | ||
| * | ||
| * @param pattern The glob pattern to match files (e.g., ctrf/*.json). | ||
| * @returns An array of parsed `CtrfReport` objects. | ||
| * @throws If no valid CTRF reports are found. | ||
| */ | ||
| function readReportsFromGlobPattern(pattern) { | ||
| const files = glob_1.glob.sync(pattern); | ||
| if (files.length === 0) { | ||
| throw new Error(`No files found matching the pattern '${pattern}'.`); | ||
| } | ||
| const reports = files | ||
| .map(file => { | ||
| try { | ||
| const content = fs_1.default.readFileSync(file, 'utf8'); | ||
| const parsed = JSON.parse(content); | ||
| if (!isCtrfReport(parsed)) { | ||
| console.warn(`Skipping invalid CTRF report file: ${file}`); | ||
| return null; | ||
| } | ||
| return parsed; | ||
| } | ||
| catch (error) { | ||
| console.warn(`Failed to read or parse file '${file}':`, error); | ||
| return null; | ||
| } | ||
| }) | ||
| .filter((report) => report !== null); | ||
| if (reports.length === 0) { | ||
| throw new Error(`No valid CTRF reports found matching the pattern '${pattern}'.`); | ||
| } | ||
| return reports; | ||
| } | ||
| exports.readReportsFromGlobPattern = readReportsFromGlobPattern; | ||
| /** | ||
| * Checks if an object conforms to the `CtrfReport` structure. | ||
| * | ||
| * @param obj The object to validate. | ||
| * @returns `true` if the object matches the `CtrfReport` type; otherwise, `false`. | ||
| */ | ||
| function isCtrfReport(obj) { | ||
| return (obj && | ||
| typeof obj === 'object' && | ||
| obj.results && | ||
| Array.isArray(obj.results.tests) && | ||
| typeof obj.results.summary === 'object' && | ||
| typeof obj.results.tool === 'object'); | ||
| } |
+2
-2
| { | ||
| "name": "ctrf-cli", | ||
| "version": "0.0.3", | ||
| "version": "0.0.4", | ||
| "description": "Various CTRF utilities available from the command line", | ||
| "main": "dist/index.js", | ||
| "bin": { | ||
| "ctrf": "dist/cli.js" | ||
| "ctrf-cli": "dist/cli.js" | ||
| }, | ||
@@ -9,0 +9,0 @@ "scripts": { |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Empty package
Supply chain riskPackage does not contain any code. It may be removed, is name squatting, or the result of a faulty package publish.
Found 1 instance in 1 package
27332
301.53%15
400%459
Infinity%3
Infinity%