From ce6f339dbddc1d2154453f7ad393bf54ab6457d4 Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Mon, 9 Feb 2026 10:32:38 -0500 Subject: [PATCH 01/17] partial --- lib/{common-utils.js => common-utils.ts} | 46 +- lib/{plugins.js => plugins.ts} | 9 +- lib/support-utils.js | 171 ------- lib/support-utils.ts | 155 ++++++ lib/{support.js => support.ts} | 65 +-- lib/{task-utils.js => task-utils.ts} | 197 ++++---- lib/task.d.ts | 6 - lib/{task.js => task.ts} | 93 ++-- lib/{use-babelrc.js => use-babelrc.ts} | 7 +- middleware/{express.js => express.ts} | 7 +- middleware/hapi.js | 14 - middleware/hapi.ts | 17 + middleware/{nextjs.js => nextjs.ts} | 6 +- package-lock.json | 440 +++++++++++++++--- package.json | 30 +- test/lib/{combine.test.js => combine.test.ts} | 2 +- .../{filtering.test.js => filtering.test.ts} | 88 ++-- ...paths.test.js => fix-source-paths.test.ts} | 2 +- test/lib/{merge.test.js => merge.test.ts} | 23 +- tsconfig.json | 32 ++ vitest.config.js => vitest.config.ts | 2 +- 21 files changed, 889 insertions(+), 523 deletions(-) rename lib/{common-utils.js => common-utils.ts} (52%) rename lib/{plugins.js => plugins.ts} (54%) delete mode 100644 lib/support-utils.js create mode 100644 lib/support-utils.ts rename lib/{support.js => support.ts} (82%) rename lib/{task-utils.js => task-utils.ts} (59%) delete mode 100644 lib/task.d.ts rename lib/{task.js => task.ts} (71%) rename lib/{use-babelrc.js => use-babelrc.ts} (54%) rename middleware/{express.js => express.ts} (52%) delete mode 100644 middleware/hapi.js create mode 100644 middleware/hapi.ts rename middleware/{nextjs.js => nextjs.ts} (79%) rename test/lib/{combine.test.js => combine.test.ts} (96%) rename test/lib/{filtering.test.js => filtering.test.ts} (84%) rename test/lib/{fix-source-paths.test.js => fix-source-paths.test.ts} (94%) rename test/lib/{merge.test.js => merge.test.ts} (79%) create mode 100644 tsconfig.json rename vitest.config.js => vitest.config.ts (74%) diff --git a/lib/common-utils.js b/lib/common-utils.ts similarity index 52% rename from lib/common-utils.js rename to lib/common-utils.ts index 573f7adab..4e9eb62a0 100644 --- a/lib/common-utils.js +++ b/lib/common-utils.ts @@ -1,13 +1,23 @@ -// @ts-check -function stringToArray(prop, obj) { +/// + +export interface NycOptions { + 'report-dir'?: string + reporter?: string | string[] + extension?: string | string[] + exclude?: string | string[] + excludeAfterRemap?: boolean + all?: boolean + include?: string | string[] + [key: string]: unknown +} + +function stringToArray(prop: string, obj: Record): void { if (typeof obj[prop] === 'string') { obj[prop] = [obj[prop]] } - - return obj } -function combineNycOptions(...options) { +export function combineNycOptions(...options: NycOptions[]): NycOptions { // last option wins const nycOptions = Object.assign({}, ...options) @@ -19,20 +29,30 @@ function combineNycOptions(...options) { return nycOptions } -const defaultNycOptions = { +export const defaultNycOptions: NycOptions = { 'report-dir': './coverage', reporter: ['lcov', 'clover', 'json', 'json-summary'], extension: ['.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx'], excludeAfterRemap: false } +export interface FileCoveragePlaceholder { + path: string + statementMap: Record + fnMap: Record + branchMap: Record + s: Record + f: Record + b: Record +} + /** * Returns an object with placeholder properties for files we * do not have coverage yet. The result can go into the coverage object * - * @param {string} fullPath Filename + * @param fullPath Filename */ -const fileCoveragePlaceholder = (fullPath) => { +export function fileCoveragePlaceholder(fullPath: string): FileCoveragePlaceholder { return { path: fullPath, statementMap: {}, @@ -44,7 +64,7 @@ const fileCoveragePlaceholder = (fullPath) => { } } -const isPlaceholder = (entry) => { +function isPlaceholder(entry: FileCoveragePlaceholder | { hash?: string }): boolean { // when the file has been instrumented, its entry has "hash" property return !('hash' in entry) } @@ -53,7 +73,7 @@ const isPlaceholder = (entry) => { * Given a coverage object with potential placeholder entries * inserted instead of covered files, removes them. Modifies the object in place */ -const removePlaceholders = (coverage) => { +export function removePlaceholders(coverage: Record): void { Object.keys(coverage).forEach((key) => { if (isPlaceholder(coverage[key])) { delete coverage[key] @@ -61,9 +81,3 @@ const removePlaceholders = (coverage) => { }) } -module.exports = { - combineNycOptions, - defaultNycOptions, - fileCoveragePlaceholder, - removePlaceholders -} diff --git a/lib/plugins.js b/lib/plugins.ts similarity index 54% rename from lib/plugins.js rename to lib/plugins.ts index 80e5e67b0..1c8b5d99f 100644 --- a/lib/plugins.js +++ b/lib/plugins.ts @@ -1,3 +1,7 @@ +/// +/// +import registerCodeCoverageTasks from './task' + // common Cypress plugin file you can point at to have the // code coverage tasks registered correctly. From your "cypress.json" file // { @@ -5,8 +9,9 @@ // "supportFile": "@cypress/code-coverage/support" // } // -module.exports = (on, config) => { - require('./task')(on, config) +export default function plugins(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions): Cypress.PluginConfigOptions { + registerCodeCoverageTasks(on, config) // IMPORTANT to return the config object with any changes return config } + diff --git a/lib/support-utils.js b/lib/support-utils.js deleted file mode 100644 index c4fbe1c52..000000000 --- a/lib/support-utils.js +++ /dev/null @@ -1,171 +0,0 @@ -// @ts-check -// helper functions that are safe to use in the browser -// from support.js file - no file system access - -/** excludes files that shouldn't be in code coverage report */ -const filterFilesFromCoverage = ( - totalCoverage, - config = Cypress.config, - expose = Cypress.expose, - spec = Cypress.spec -) => { - const withoutSpecs = filterSpecsFromCoverage( - totalCoverage, - config, - expose, - spec - ) - const appCoverageOnly = filterSupportFilesFromCoverage(withoutSpecs, config) - return appCoverageOnly -} - -/** - * remove coverage for the spec files themselves, - * only keep "external" application source file coverage - */ -const filterSpecsFromCoverage = ( - totalCoverage, - config = Cypress.config, - expose = Cypress.expose, - spec = Cypress.spec -) => { - const testFilePatterns = getCypressExcludePatterns(config, expose, spec) - - const isTestFile = (_, filePath) => { - const workingDir = spec.absolute.replace(spec.relative, '') - const filename = filePath.replace(workingDir, '') - const matchedPattern = testFilePatterns.some((specPattern) => - Cypress.minimatch(filename, specPattern, { debug: false }) - ) - const matchedEndOfPath = testFilePatterns.some((specPattern) => - filename.endsWith(specPattern) - ) - return matchedPattern || matchedEndOfPath - } - - const coverage = Cypress._.omitBy(totalCoverage, isTestFile) - return coverage -} - -/** - * Reads Cypress config and exclude patterns and combines them into one array - * @param {*} config - * @param {*} expose - * @returns string[] - */ -function getCypressExcludePatterns(config, expose, spec) { - let testFilePatterns = [] - - const testFilePattern = config('specPattern') || config('testFiles') - const excludePattern = expose().codeCoverage && expose().codeCoverage.exclude - - if (Array.isArray(testFilePattern)) { - testFilePatterns = testFilePattern - } else { - testFilePatterns = [testFilePattern] - } - - // combine test files pattern and exclude patterns into single exclude pattern - if (Array.isArray(excludePattern)) { - testFilePatterns = [...testFilePatterns, ...excludePattern] - } else if (excludePattern) { - testFilePatterns = [...testFilePatterns, excludePattern] - } - - // Cypress { - if (pattern === '**/*.*') { - testFilePatterns[idx] = `${integrationFolder}/${pattern}`.replace( - '//', - '/' - ) - } - }) - } - - return testFilePatterns -} - -/** - * Removes support file from the coverage object. - * If there are more files loaded from support folder, also removes them - */ -const filterSupportFilesFromCoverage = ( - totalCoverage, - config = Cypress.config -) => { - const integrationFolder = config('integrationFolder') - - /** - * Cypress v10 doesn't have an integrationFolder config value any more, so we nope out here if its undefined. - * Instead, we rely on the new exclude option logic done in the getCypressExcludePatterns function. - */ - if (!integrationFolder) { - return totalCoverage - } - - const supportFile = config('supportFile') - - /** @type {string} Cypress run-time config has the support folder string */ - // @ts-ignore - const supportFolder = config('supportFolder') - - const isSupportFile = (filename) => filename === supportFile - - let coverage = Cypress._.omitBy(totalCoverage, (fileCoverage, filename) => - isSupportFile(filename) - ) - - // check the edge case - // if we have files from support folder AND the support folder is not same - // as the integration, or its prefix (this might remove all app source files) - // then remove all files from the support folder - if (!integrationFolder.startsWith(supportFolder)) { - // remove all covered files from support folder - coverage = Cypress._.omitBy(totalCoverage, (fileCoverage, filename) => - filename.startsWith(supportFolder) - ) - } - return coverage -} - -/** - * Replace source-map's path by the corresponding absolute file path - * (coverage report wouldn't work with source-map path being relative - * or containing Webpack loaders and query parameters) - */ -function fixSourcePaths(coverage) { - Object.values(coverage).forEach((file) => { - const { path: absolutePath, inputSourceMap } = file - if (!inputSourceMap) return - const fileName = /([^\/\\]+)$/.exec(absolutePath)[1] - if (!fileName) return - - if (inputSourceMap.sourceRoot) inputSourceMap.sourceRoot = '' - inputSourceMap.sources = inputSourceMap.sources.map((source) => - source.includes(fileName) ? absolutePath : source - ) - }) -} - -/** - * Validates and returns the configured batch size for - * sending coverage to the backend - */ -function getSendCoverageBatchSize() { - const batchSize = Cypress.expose('sendCoverageBatchSize') - const parsedBatchSize = parseInt(batchSize) - const isValid = !isNaN(parsedBatchSize) && parsedBatchSize > 0 - return isValid ? parsedBatchSize : null -} - -module.exports = { - fixSourcePaths, - filterFilesFromCoverage, - getSendCoverageBatchSize -} diff --git a/lib/support-utils.ts b/lib/support-utils.ts new file mode 100644 index 000000000..a7ed554e5 --- /dev/null +++ b/lib/support-utils.ts @@ -0,0 +1,155 @@ +/// +/// + +// helper functions that are safe to use in the browser +// from support.js file - no file system access + +export interface CoverageObject { + [key: string]: { + path?: string + inputSourceMap?: { + sources: string[] + sourceRoot?: string + } + [key: string]: unknown + } +} + +/** excludes files that shouldn't be in code coverage report */ +export function filterFilesFromCoverage( + totalCoverage: CoverageObject, + config: typeof Cypress.config = Cypress.config, + expose: typeof Cypress.expose = Cypress.expose, + spec: typeof Cypress.spec = Cypress.spec +): CoverageObject { + const withoutSpecs = filterSpecsFromCoverage( + totalCoverage, + config, + expose, + spec + ) + const appCoverageOnly = filterSupportFilesFromCoverage(withoutSpecs, config) + return appCoverageOnly +} + +/** + * remove coverage for the spec files themselves, + * only keep "external" application source file coverage + */ +function filterSpecsFromCoverage( + totalCoverage: CoverageObject, + config: typeof Cypress.config = Cypress.config, + expose: typeof Cypress.expose = Cypress.expose, + spec: typeof Cypress.spec = Cypress.spec +): CoverageObject { + const testFilePatterns = getCypressExcludePatterns(config, expose, spec) + + const isTestFile = (_: unknown, filePath: string): boolean => { + const workingDir = spec.absolute.replace(spec.relative, '') + const filename = filePath.replace(workingDir, '') + const matchedPattern = testFilePatterns.some((specPattern) => + Cypress.minimatch(filename, specPattern, { debug: false }) + ) + const matchedEndOfPath = testFilePatterns.some((specPattern) => + filename.endsWith(specPattern) + ) + return matchedPattern || matchedEndOfPath + } + + const coverage = Cypress._.omitBy(totalCoverage, isTestFile) + return coverage +} + +/** + * Reads Cypress config and exclude patterns and combines them into one array + */ +function getCypressExcludePatterns( + config: typeof Cypress.config, + expose: typeof Cypress.expose, + spec: typeof Cypress.spec +): string[] { + const testFilePattern = config('specPattern') + const codeCoverageConfig = expose().codeCoverage as { exclude?: string | string[] } | undefined + const excludePattern = codeCoverageConfig?.exclude + + let testFilePatterns: string[] = [] + if (Array.isArray(testFilePattern)) { + // Filter out any undefined values and ensure all are strings + for (const p of testFilePattern) { + if (p !== undefined && typeof p === 'string') { + testFilePatterns.push(p) + } + } + } else if (testFilePattern && typeof testFilePattern === 'string') { + testFilePatterns = [testFilePattern] + } + + // combine test files pattern and exclude patterns into single exclude pattern + if (Array.isArray(excludePattern)) { + // Filter out any undefined values and ensure all are strings + for (const p of excludePattern) { + if (p !== undefined && typeof p === 'string') { + testFilePatterns.push(p) + } + } + } else if (typeof excludePattern === 'string') { + testFilePatterns.push(excludePattern) + } + + return testFilePatterns +} + +/** + * Removes support file from the coverage object. + * Support files are now handled via the exclude patterns in getCypressExcludePatterns. + */ +function filterSupportFilesFromCoverage( + totalCoverage: CoverageObject, + config: typeof Cypress.config = Cypress.config +): CoverageObject { + const supportFile = config('supportFile') as string | undefined + + if (!supportFile) { + return totalCoverage + } + + const isSupportFile = (filename: string): boolean => filename === supportFile + + const coverage = Cypress._.omitBy(totalCoverage, (_fileCoverage, filename) => + isSupportFile(filename) + ) + + return coverage +} + +/** + * Replace source-map's path by the corresponding absolute file path + * (coverage report wouldn't work with source-map path being relative + * or containing Webpack loaders and query parameters) + */ +export function fixSourcePaths(coverage: CoverageObject): void { + Object.values(coverage).forEach((file) => { + const { path: absolutePath, inputSourceMap } = file + if (!inputSourceMap || !absolutePath) return + const fileNameMatch = /([^\/\\]+)$/.exec(absolutePath) + if (!fileNameMatch) return + const fileName = fileNameMatch[1] + + if (inputSourceMap.sourceRoot) inputSourceMap.sourceRoot = '' + inputSourceMap.sources = inputSourceMap.sources.map((source) => + source.includes(fileName) ? absolutePath : source + ) + }) +} + +/** + * Validates and returns the configured batch size for + * sending coverage to the backend + */ +export function getSendCoverageBatchSize(): number | null { + const batchSize = Cypress.expose('sendCoverageBatchSize') + const parsedBatchSize = parseInt(String(batchSize), 10) + const isValid = !isNaN(parsedBatchSize) && parsedBatchSize > 0 + return isValid ? parsedBatchSize : null +} + diff --git a/lib/support.js b/lib/support.ts similarity index 82% rename from lib/support.js rename to lib/support.ts index 7c27b217b..6b38139bb 100644 --- a/lib/support.js +++ b/lib/support.ts @@ -1,23 +1,28 @@ /// -// @ts-check - -const dayjs = require('dayjs') -var duration = require('dayjs/plugin/duration') -const { +/// +import type { CoverageObject } from './support-utils' +import dayjs from 'dayjs' +import duration from 'dayjs/plugin/duration' +import { filterFilesFromCoverage, getSendCoverageBatchSize -} = require('./support-utils') +} from './support-utils' dayjs.extend(duration) +interface WindowCoverageObject { + coverage: unknown + pathname: string +} + /** * Sends collected code coverage object to the backend code * via "cy.task". */ -const sendCoverage = (coverage, pathname = '/') => { +function sendCoverage(coverage: unknown, pathname = '/'): void { logMessage(`Saving code coverage for **${pathname}**`) - const totalCoverage = filterFilesFromCoverage(coverage) + const totalCoverage = filterFilesFromCoverage(coverage as CoverageObject) const batchSize = getSendCoverageBatchSize() const keys = Object.keys(totalCoverage) @@ -35,12 +40,12 @@ const sendCoverage = (coverage, pathname = '/') => { * Sends collected code coverage object to the backend code * in batches via "cy.task". */ -const sendBatchCoverage = (totalCoverage, batchSize) => { +function sendBatchCoverage(totalCoverage: Record, batchSize: number): void { const keys = Object.keys(totalCoverage) for (let i = 0; i < keys.length; i += batchSize) { const batchKeys = keys.slice(i, i + batchSize) - const batchCoverage = {} + const batchCoverage: Record = {} batchKeys.forEach((key) => { batchCoverage[key] = totalCoverage[key] @@ -55,19 +60,19 @@ const sendBatchCoverage = (totalCoverage, batchSize) => { /** * Consistently logs the given string to the Command Log * so the user knows the log message is coming from this plugin. - * @param {string} s Message to log. + * @param s Message to log. */ -const logMessage = (s) => { +function logMessage(s: string): void { cy.log(`${s} \`[@cypress/code-coverage]\``) } -const registerHooks = () => { - let windowCoverageObjects +function registerHooks(): void { + let windowCoverageObjects: WindowCoverageObject[] = [] - const hasE2ECoverage = () => Boolean(windowCoverageObjects.length) + const hasE2ECoverage = (): boolean => Boolean(windowCoverageObjects.length) - // @ts-ignore - const hasUnitTestCoverage = () => Boolean(window.__coverage__) + // @ts-ignore - __coverage__ is a global added by instrumentation + const hasUnitTestCoverage = (): boolean => Boolean((window as typeof window & { __coverage__?: unknown }).__coverage__) before(() => { // we need to reset the coverage when running @@ -81,7 +86,7 @@ const registerHooks = () => { cy.task( 'resetCoverage', { - // @ts-ignore + // @ts-ignore - isInteractive is a runtime property isInteractive: Cypress.config('isInteractive') }, { log: false } @@ -95,20 +100,23 @@ const registerHooks = () => { // to let the user know the coverage has been collected windowCoverageObjects = [] - const saveCoverageObject = (win) => { + const saveCoverageObject = (win: Window): void => { // if the application code has been instrumented, then the app iframe "win.__coverage__" will be available, // in addition, accessing win.__coverage__ can throw when testing cross-origin code, // because we don't control the cross-origin code, we can safely return - let applicationSourceCoverage + let applicationSourceCoverage: unknown try { // Note that we are purposefully not supporting the optional chaining syntax here to // support a wide range of projects (some of which are not set up to support the optional // chaining syntax due to current Cypress limitations). See: // https://github.com/cypress-io/cypress/issues/20753 if (win) { - applicationSourceCoverage = win.__coverage__ + // @ts-ignore - __coverage__ is added by instrumentation + applicationSourceCoverage = (win as typeof win & { __coverage__?: unknown }).__coverage__ } - } catch {} + } catch { + // ignore cross-origin errors + } if (!applicationSourceCoverage) { return @@ -174,9 +182,9 @@ const registerHooks = () => { // there might be server-side code coverage information // we should grab it once after all tests finish - // @ts-ignore - const baseUrl = Cypress.config('baseUrl') || cy.state('window').origin - // @ts-ignore + // @ts-ignore - state is a runtime property + const baseUrl = Cypress.config('baseUrl') || (cy.state('window') as Window).origin + // @ts-ignore - proxyUrl is a runtime property const runningEndToEndTests = baseUrl !== Cypress.config('proxyUrl') const expectFrontendCoverageOnly = Cypress._.get( Cypress.expose('codeCoverage'), @@ -199,7 +207,7 @@ const registerHooks = () => { 'url', '/__coverage__' ) - function captureCoverage(url, suffix = '') { + function captureCoverage(url: string, suffix = ''): void { cy.request({ url, log: false, @@ -247,8 +255,8 @@ const registerHooks = () => { // then we will have unit test coverage // NOTE: spec iframe is NOT reset between the tests, so we can grab // the coverage information only once after all tests have finished - // @ts-ignore - const unitTestCoverage = window.__coverage__ + // @ts-ignore - __coverage__ is added by instrumentation + const unitTestCoverage = (window as typeof window & { __coverage__?: unknown }).__coverage__ if (unitTestCoverage) { sendCoverage(unitTestCoverage, 'unit') } @@ -300,3 +308,4 @@ if (exposedValues.coverage === false) { } else { registerHooks() } + diff --git a/lib/task-utils.js b/lib/task-utils.ts similarity index 59% rename from lib/task-utils.js rename to lib/task-utils.ts index dc413f1a2..837f3a82d 100644 --- a/lib/task-utils.js +++ b/lib/task-utils.ts @@ -1,74 +1,86 @@ // helper functions to use from "task.js" plugins code // that need access to the file system -// @ts-check -/// -const { readFileSync, writeFileSync, existsSync } = require('fs') -const { isAbsolute, resolve, join } = require('path') -const debug = require('debug')('code-coverage') -const chalk = require('chalk') -const tinyglobby = require('tinyglobby') -const yaml = require('js-yaml') -const { +/// +import { readFileSync, writeFileSync, existsSync } from 'fs' +import { isAbsolute, resolve, join } from 'path' +import debug from 'debug' +import chalk from 'chalk' +import { globSync } from 'tinyglobby' +import yaml from 'js-yaml' +import { combineNycOptions, defaultNycOptions, - fileCoveragePlaceholder -} = require('./common-utils') + fileCoveragePlaceholder, + type NycOptions +} from './common-utils' -function readNycOptions(workingDirectory) { +const log = debug('code-coverage') + +export interface CoverageEntry { + path: string + hash?: string + [key: string]: unknown +} + +export interface CoverageMap { + [key: string]: CoverageEntry +} + +export function readNycOptions(workingDirectory: string): NycOptions { const pkgFilename = join(workingDirectory, 'package.json') const pkg = existsSync(pkgFilename) ? JSON.parse(readFileSync(pkgFilename, 'utf8')) : {} - const pkgNycOptions = pkg.nyc || {} + const pkgNycOptions: NycOptions = pkg.nyc || {} const nycrcFilename = join(workingDirectory, '.nycrc') - const nycrc = existsSync(nycrcFilename) + const nycrc: NycOptions = existsSync(nycrcFilename) ? JSON.parse(readFileSync(nycrcFilename, 'utf8')) : {} const nycrcJsonFilename = join(workingDirectory, '.nycrc.json') - const nycrcJson = existsSync(nycrcJsonFilename) + const nycrcJson: NycOptions = existsSync(nycrcJsonFilename) ? JSON.parse(readFileSync(nycrcJsonFilename, 'utf8')) : {} const nycrcYamlFilename = join(workingDirectory, '.nycrc.yaml') - let nycrcYaml = {} + let nycrcYaml: NycOptions = {} if (existsSync(nycrcYamlFilename)) { try { - nycrcYaml = yaml.safeLoad(readFileSync(nycrcYamlFilename, 'utf8')) + nycrcYaml = yaml.load(readFileSync(nycrcYamlFilename, 'utf8')) as NycOptions } catch (error) { - throw new Error(`Failed to load .nycrc.yaml: ${error.message}`) + throw new Error(`Failed to load .nycrc.yaml: ${error instanceof Error ? error.message : String(error)}`) } } const nycrcYmlFilename = join(workingDirectory, '.nycrc.yml') - let nycrcYml = {} + let nycrcYml: NycOptions = {} if (existsSync(nycrcYmlFilename)) { try { - nycrcYml = yaml.safeLoad(readFileSync(nycrcYmlFilename, 'utf8')) + nycrcYml = yaml.load(readFileSync(nycrcYmlFilename, 'utf8')) as NycOptions } catch (error) { - throw new Error(`Failed to load .nycrc.yml: ${error.message}`) + throw new Error(`Failed to load .nycrc.yml: ${error instanceof Error ? error.message : String(error)}`) } } const nycConfigFilename = join(workingDirectory, 'nyc.config.js') - let nycConfig = {} + let nycConfig: NycOptions = {} if (existsSync(nycConfigFilename)) { try { - nycConfig = require(nycConfigFilename) + nycConfig = require(nycConfigFilename) as NycOptions } catch (error) { - throw new Error(`Failed to load nyc.config.js: ${error.message}`) + throw new Error(`Failed to load nyc.config.js: ${error instanceof Error ? error.message : String(error)}`) } } const nycConfigCommonJsFilename = join(workingDirectory, 'nyc.config.cjs') - let nycConfigCommonJs = {} + let nycConfigCommonJs: NycOptions = {} if (existsSync(nycConfigCommonJsFilename)) { try { - nycConfigCommonJs = require(nycConfigCommonJsFilename) + nycConfigCommonJs = require(nycConfigCommonJsFilename) as NycOptions } catch (error) { - throw new Error(`Failed to load nyc.config.cjs: ${error.message}`) + throw new Error(`Failed to load nyc.config.cjs: ${error instanceof Error ? error.message : String(error)}`) } } @@ -82,26 +94,26 @@ function readNycOptions(workingDirectory) { nycConfigCommonJs, pkgNycOptions ) - debug('combined NYC options %o', nycOptions) + log('combined NYC options %o', nycOptions) return nycOptions } -function checkAllPathsNotFound(nycFilename) { - const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8')) +export function checkAllPathsNotFound(nycFilename: string): boolean | undefined { + const nycCoverage: CoverageMap = JSON.parse(readFileSync(nycFilename, 'utf8')) const coverageKeys = Object.keys(nycCoverage) if (!coverageKeys.length) { - debug('⚠️ file %s has no coverage information', nycFilename) + log('⚠️ file %s has no coverage information', nycFilename) return } - const allFilesAreMissing = coverageKeys.every((key, k) => { + const allFilesAreMissing = coverageKeys.every((key) => { const coverage = nycCoverage[key] return !existsSync(coverage.path) }) - debug( + log( 'in file %s all files are not found? %o', nycFilename, allFilesAreMissing @@ -112,8 +124,8 @@ function checkAllPathsNotFound(nycFilename) { /** * A small debug utility to inspect paths saved in NYC output JSON file */ -function showNycInfo(nycFilename) { - const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8')) +export function showNycInfo(nycFilename: string): void { + const nycCoverage: CoverageMap = JSON.parse(readFileSync(nycFilename, 'utf8')) const coverageKeys = Object.keys(nycCoverage) if (!coverageKeys.length) { @@ -129,7 +141,7 @@ function showNycInfo(nycFilename) { ) return } - debug('NYC file %s has %d key(s)', nycFilename, coverageKeys.length) + log('NYC file %s has %d key(s)', nycFilename, coverageKeys.length) const maxPrintKeys = 3 const showKeys = coverageKeys.slice(0, maxPrintKeys) @@ -140,7 +152,7 @@ function showNycInfo(nycFilename) { // printing a few found keys and file paths from the coverage file // will make debugging any problems much much easier if (k < maxPrintKeys) { - debug('%d key %s file path %s', k + 1, key, coverage.path) + log('%d key %s file path %s', k + 1, key, coverage.path) } }) } @@ -150,29 +162,29 @@ function showNycInfo(nycFilename) { * and if the file is relative, and exists, changes its path to * be absolute. */ -function resolveRelativePaths(nycFilename) { - const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8')) +export function resolveRelativePaths(nycFilename: string): void { + const nycCoverage: CoverageMap = JSON.parse(readFileSync(nycFilename, 'utf8')) const coverageKeys = Object.keys(nycCoverage) if (!coverageKeys.length) { - debug('⚠️ file %s has no coverage information', nycFilename) + log('⚠️ file %s has no coverage information', nycFilename) return } - debug('NYC file %s has %d key(s)', nycFilename, coverageKeys.length) + log('NYC file %s has %d key(s)', nycFilename, coverageKeys.length) - let changed + let changed = false - coverageKeys.forEach((key, k) => { + coverageKeys.forEach((key) => { const coverage = nycCoverage[key] if (!coverage.path) { - debug('key %s does not have path', key) + log('key %s does not have path', key) return } if (!isAbsolute(coverage.path)) { if (existsSync(coverage.path)) { - debug('resolving path %s', coverage.path) + log('resolving path %s', coverage.path) coverage.path = resolve(coverage.path) changed = true } @@ -181,13 +193,13 @@ function resolveRelativePaths(nycFilename) { // path is absolute, let's check if it exists if (!existsSync(coverage.path)) { - debug('⚠️ cannot find file %s with hash %s', coverage.path, coverage.hash) + log('⚠️ cannot find file %s with hash %s', coverage.path, coverage.hash) } }) if (changed) { - debug('resolveRelativePaths saving updated file %s', nycFilename) - debug('there are %d keys in the file', coverageKeys.length) + log('resolveRelativePaths saving updated file %s', nycFilename) + log('there are %d keys in the file', coverageKeys.length) writeFileSync( nycFilename, JSON.stringify(nycCoverage, null, 2) + '\n', @@ -197,12 +209,12 @@ function resolveRelativePaths(nycFilename) { } /** - * @param {string[]} filepaths - * @returns {string | undefined} common prefix that corresponds to current folder + * @param filepaths + * @returns common prefix that corresponds to current folder */ -function findCommonRoot(filepaths) { +export function findCommonRoot(filepaths: string[]): string | undefined { if (!filepaths.length) { - debug('cannot find common root without any files') + log('cannot find common root without any files') return } @@ -210,19 +222,19 @@ function findCommonRoot(filepaths) { const splitParts = filepaths.map((name) => name.split('/')) const lengths = splitParts.map((arr) => arr.length) const shortestLength = Math.min.apply(null, lengths) - debug('shorted file path has %d parts', shortestLength) + log('shorted file path has %d parts', shortestLength) const cwd = process.cwd() - let commonPrefix = [] - let foundCurrentFolder + const commonPrefix: string[] = [] + let foundCurrentFolder: string | undefined for (let k = 0; k < shortestLength; k += 1) { const part = splitParts[0][k] const prefix = commonPrefix.concat(part).join('/') - debug('testing prefix %o', prefix) + log('testing prefix %o', prefix) const allFilesStart = filepaths.every((name) => name.startsWith(prefix)) if (!allFilesStart) { - debug('stopped at non-common prefix %s', prefix) + log('stopped at non-common prefix %s', prefix) break } @@ -231,13 +243,13 @@ function findCommonRoot(filepaths) { const removedPrefixNames = filepaths.map((filepath) => filepath.slice(prefix.length) ) - debug('removedPrefix %o', removedPrefixNames) + log('removedPrefix %o', removedPrefixNames) const foundAllPaths = removedPrefixNames.every((filepath) => existsSync(join(cwd, filepath)) ) - debug('all files found at %s? %o', prefix, foundAllPaths) + log('all files found at %s? %o', prefix, foundAllPaths) if (foundAllPaths) { - debug('found prefix that matches current folder: %s', prefix) + log('found prefix that matches current folder: %s', prefix) foundCurrentFolder = prefix break } @@ -246,23 +258,23 @@ function findCommonRoot(filepaths) { return foundCurrentFolder } -function tryFindingLocalFiles(nycFilename) { - const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8')) +export function tryFindingLocalFiles(nycFilename: string): void { + const nycCoverage: CoverageMap = JSON.parse(readFileSync(nycFilename, 'utf8')) const coverageKeys = Object.keys(nycCoverage) const filenames = coverageKeys.map((key) => nycCoverage[key].path) const commonFolder = findCommonRoot(filenames) if (!commonFolder) { - debug('could not find common folder %s', commonFolder) + log('could not find common folder %s', commonFolder) return } const cwd = process.cwd() - debug( + log( 'found common folder %s that matches current working directory %s', commonFolder, cwd ) const length = commonFolder.length - let changed + let changed = false coverageKeys.forEach((key) => { const from = nycCoverage[key].path @@ -270,14 +282,14 @@ function tryFindingLocalFiles(nycFilename) { const to = join(cwd, from.slice(length)) // ? Do we need to replace the "key" in the coverage object or can we just replace the "path"? nycCoverage[key].path = to - debug('replaced %s -> %s', from, to) + log('replaced %s -> %s', from, to) changed = true } }) if (changed) { - debug('tryFindingLocalFiles saving updated file %s', nycFilename) - debug('there are %d keys in the file', coverageKeys.length) + log('tryFindingLocalFiles saving updated file %s', nycFilename) + log('there are %d keys in the file', coverageKeys.length) writeFileSync( nycFilename, JSON.stringify(nycCoverage, null, 2) + '\n', @@ -290,8 +302,8 @@ function tryFindingLocalFiles(nycFilename) { * Tries to find source files to be included in the final coverage report * using NYC options: extension list, include and exclude. */ -function findSourceFiles(nycOptions) { - debug('include all files options: %o', { +function findSourceFiles(nycOptions: NycOptions): string[] { + log('include all files options: %o', { all: nycOptions.all, include: nycOptions.include, exclude: nycOptions.exclude, @@ -306,13 +318,13 @@ function findSourceFiles(nycOptions) { return [] } - let patterns = [] + const patterns: string[] = [] if (Array.isArray(nycOptions.include)) { - patterns = patterns.concat(nycOptions.include) + patterns.push(...nycOptions.include) } else if (typeof nycOptions.include === 'string') { patterns.push(nycOptions.include) } else { - debug('using default list of extensions') + log('using default list of extensions') nycOptions.extension.forEach((extension) => { patterns.push('**/*' + extension) }) @@ -320,7 +332,7 @@ function findSourceFiles(nycOptions) { if (Array.isArray(nycOptions.exclude)) { const negated = nycOptions.exclude.map((s) => '!' + s) - patterns = patterns.concat(negated) + patterns.push(...negated) } else if (typeof nycOptions.exclude === 'string') { patterns.push('!' + nycOptions.exclude) } @@ -328,11 +340,12 @@ function findSourceFiles(nycOptions) { // https://github.com/istanbuljs/nyc#including-files-within-node_modules patterns.push('!**/node_modules/**') - debug('searching files to include using patterns %o', patterns) + log('searching files to include using patterns %o', patterns) - const allFiles = tinyglobby.globSync(patterns, { absolute: true }) + const allFiles = globSync(patterns, { absolute: true }) return allFiles } + /** * If the website or unit tests did not load ALL files we need to * include, then we should include the missing files ourselves @@ -340,42 +353,42 @@ function findSourceFiles(nycOptions) { * * @see https://github.com/cypress-io/code-coverage/issues/207 */ -function includeAllFiles(nycFilename, nycOptions) { +export function includeAllFiles(nycFilename: string, nycOptions: NycOptions): void { if (!nycOptions.all) { - debug('NYC "all" option is not set, skipping including all files') + log('NYC "all" option is not set, skipping including all files') return } const allFiles = findSourceFiles(nycOptions) - if (debug.enabled) { - debug('found %d file(s)', allFiles.length) + if (log.enabled) { + log('found %d file(s)', allFiles.length) console.error(allFiles.join('\n')) } if (!allFiles.length) { - debug('no files found, hoping for the best') + log('no files found, hoping for the best') return } - const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8')) + const nycCoverage: CoverageMap = JSON.parse(readFileSync(nycFilename, 'utf8')) const coverageKeys = Object.keys(nycCoverage) const coveredPaths = coverageKeys.map((key) => nycCoverage[key].path.replace(/\\/g, '/') ) - debug('coverage has %d record(s)', coveredPaths.length) + log('coverage has %d record(s)', coveredPaths.length) // report on first couple of entries - if (debug.enabled) { + if (log.enabled) { console.error('coverage has the following first paths') console.error(coveredPaths.slice(0, 4).join('\n')) } - let changed + let changed = false allFiles.forEach((fullPath) => { if (coveredPaths.includes(fullPath)) { // all good, this file exists in coverage object return } - debug('adding empty coverage for file %s', fullPath) + log('adding empty coverage for file %s', fullPath) changed = true // insert placeholder object for now const placeholder = fileCoveragePlaceholder(fullPath) @@ -383,8 +396,8 @@ function includeAllFiles(nycFilename, nycOptions) { }) if (changed) { - debug('includeAllFiles saving updated file %s', nycFilename) - debug('there are %d keys in the file', Object.keys(nycCoverage).length) + log('includeAllFiles saving updated file %s', nycFilename) + log('there are %d keys in the file', Object.keys(nycCoverage).length) writeFileSync( nycFilename, @@ -394,11 +407,3 @@ function includeAllFiles(nycFilename, nycOptions) { } } -module.exports = { - showNycInfo, - resolveRelativePaths, - checkAllPathsNotFound, - tryFindingLocalFiles, - readNycOptions, - includeAllFiles -} diff --git a/lib/task.d.ts b/lib/task.d.ts deleted file mode 100644 index 8ff8ef8f6..000000000 --- a/lib/task.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -/// - -export default function registerCodeCoverageTasks( - on: Cypress.PluginEvents, - config: Cypress.PluginConfigOptions, -): void; diff --git a/lib/task.js b/lib/task.ts similarity index 71% rename from lib/task.js rename to lib/task.ts index e82b39ee6..b1e5266ca 100644 --- a/lib/task.js +++ b/lib/task.ts @@ -1,19 +1,20 @@ -// @ts-check -const istanbul = require('istanbul-lib-coverage') -const { join, resolve } = require('path') -const { existsSync, mkdirSync, readFileSync, writeFileSync } = require('fs') -const execa = require('execa') -const { +/// +import { createCoverageMap, CoverageMap as IstanbulCoverageMap } from 'istanbul-lib-coverage' +import { join, resolve } from 'path' +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs' +import { execa } from 'execa' +import { showNycInfo, resolveRelativePaths, checkAllPathsNotFound, tryFindingLocalFiles, readNycOptions, includeAllFiles -} = require('./task-utils') -const { fixSourcePaths } = require('./support-utils') +} from './task-utils' +import { fixSourcePaths } from './support-utils' +import debug from 'debug' -const debug = require('debug')('code-coverage') +const log = debug('code-coverage') // these are standard folder and file names used by NYC tools const processWorkingDirectory = process.cwd() @@ -40,7 +41,7 @@ const nycReportOptions = (function getNycOption() { } if (nycReportOptions['temp-dir']) { - nycReportOptions['temp-dir'] = resolve(nycReportOptions['temp-dir']) + nycReportOptions['temp-dir'] = resolve(nycReportOptions['temp-dir'] as string) } else { nycReportOptions['temp-dir'] = join(processWorkingDirectory, '.nyc_output') } @@ -48,7 +49,7 @@ const nycReportOptions = (function getNycOption() { nycReportOptions.tempDir = nycReportOptions['temp-dir'] if (nycReportOptions['report-dir']) { - nycReportOptions['report-dir'] = resolve(nycReportOptions['report-dir']) + nycReportOptions['report-dir'] = resolve(nycReportOptions['report-dir'] as string) } // seems nyc API really is using camel cased version nycReportOptions.reportDir = nycReportOptions['report-dir'] @@ -56,35 +57,35 @@ const nycReportOptions = (function getNycOption() { return nycReportOptions })() -const nycFilename = join(nycReportOptions['temp-dir'], 'out.json') +const nycFilename = join(nycReportOptions['temp-dir'] as string, 'out.json') -let coverageMap = (() => { +let coverageMap: IstanbulCoverageMap = (() => { const previousCoverage = existsSync(nycFilename) ? JSON.parse(readFileSync(nycFilename, 'utf8')) : {} - return istanbul.createCoverageMap(previousCoverage) + return createCoverageMap(previousCoverage) })() -function saveCoverage(coverage) { - if (!existsSync(nycReportOptions.tempDir)) { - mkdirSync(nycReportOptions.tempDir, { recursive: true }) - debug('created folder %s for output coverage', nycReportOptions.tempDir) +function saveCoverage(coverage: unknown): void { + if (!existsSync(nycReportOptions.tempDir as string)) { + mkdirSync(nycReportOptions.tempDir as string, { recursive: true }) + log('created folder %s for output coverage', nycReportOptions.tempDir) } writeFileSync(nycFilename, JSON.stringify(coverage, null, 2)) } -function maybePrintFinalCoverageFiles(folder) { +function maybePrintFinalCoverageFiles(folder: string): void { const jsonReportFilename = join(folder, 'coverage-final.json') if (!existsSync(jsonReportFilename)) { - debug('Did not find final coverage file %s', jsonReportFilename) + log('Did not find final coverage file %s', jsonReportFilename) return } - debug('Final coverage in %s', jsonReportFilename) - const finalCoverage = JSON.parse(readFileSync(jsonReportFilename, 'utf8')) + log('Final coverage in %s', jsonReportFilename) + const finalCoverage: Record }> = JSON.parse(readFileSync(jsonReportFilename, 'utf8')) const finalCoverageKeys = Object.keys(finalCoverage) - debug( + log( 'There are %d key(s) in %s', finalCoverageKeys.length, jsonReportFilename @@ -105,7 +106,7 @@ function maybePrintFinalCoverageFiles(folder) { const allCovered = coveredStatements === totalStatements const coverageStatus = hasStatements ? (allCovered ? '✅' : '⚠️') : '❓' - debug( + log( '%s %s statements covered %d/%d', coverageStatus, key, @@ -115,6 +116,10 @@ function maybePrintFinalCoverageFiles(folder) { }) } +interface TaskParams { + isInteractive?: boolean +} + const tasks = { /** * Clears accumulated code coverage information. @@ -125,10 +130,10 @@ const tasks = { * - runs EACH spec separately, so we cannot reset the coverage * or we will lose the coverage from previous specs. */ - resetCoverage({ isInteractive }) { + resetCoverage({ isInteractive }: TaskParams): null { if (isInteractive) { - debug('reset code coverage in interactive mode') - coverageMap = istanbul.createCoverageMap({}) + log('reset code coverage in interactive mode') + coverageMap = createCoverageMap({}) saveCoverage(coverageMap) } /* @@ -145,12 +150,12 @@ const tasks = { * Combines coverage information from single test * with previously collected coverage. * - * @param {string} sentCoverage Stringified coverage object sent by the test runner - * @returns {null} Nothing is returned from this task + * @param sentCoverage Stringified coverage object sent by the test runner + * @returns Nothing is returned from this task */ - combineCoverage(sentCoverage) { + combineCoverage(sentCoverage: string): null { const coverage = JSON.parse(sentCoverage) - debug('parsed sent coverage') + log('parsed sent coverage') fixSourcePaths(coverage) @@ -163,7 +168,7 @@ const tasks = { * Saves coverage information as a JSON file and calls * NPM script to generate HTML report */ - coverageReport() { + coverageReport(): Promise | null { saveCoverage(coverageMap) if (!existsSync(nycFilename)) { console.warn('Cannot find coverage file %s', nycFilename) @@ -181,30 +186,30 @@ const tasks = { resolveRelativePaths(nycFilename) if (customNycReportScript) { - debug( + log( 'saving coverage report using script "%s" from package.json, command: %s', DEFAULT_CUSTOM_COVERAGE_SCRIPT_NAME, customNycReportScript ) - debug('current working directory is %s', process.cwd()) + log('current working directory is %s', process.cwd()) return execa('npm', ['run', DEFAULT_CUSTOM_COVERAGE_SCRIPT_NAME], { stdio: 'inherit' - }) + }).then(() => null) } if (nycReportOptions.all) { - debug('nyc needs to report on all included files') + log('nyc needs to report on all included files') includeAllFiles(nycFilename, nycReportOptions) } - debug('calling NYC reporter with options %o', nycReportOptions) - debug('current working directory is %s', process.cwd()) + log('calling NYC reporter with options %o', nycReportOptions) + log('current working directory is %s', process.cwd()) const NYC = require('nyc') const nyc = new NYC(nycReportOptions) - const returnReportFolder = () => { - const reportFolder = nycReportOptions['report-dir'] - debug( + const returnReportFolder = (): string => { + const reportFolder = nycReportOptions['report-dir'] as string + log( 'after reporting, returning the report folder name %s', reportFolder ) @@ -231,7 +236,10 @@ const tasks = { } ``` */ -function registerCodeCoverageTasks(on, config) { +export default function registerCodeCoverageTasks( + on: Cypress.PluginEvents, + config: Cypress.PluginConfigOptions +): Cypress.PluginConfigOptions { on('task', tasks) // set a variable to let the hooks running in the browser @@ -241,4 +249,3 @@ function registerCodeCoverageTasks(on, config) { return config } -module.exports = registerCodeCoverageTasks diff --git a/lib/use-babelrc.js b/lib/use-babelrc.ts similarity index 54% rename from lib/use-babelrc.js rename to lib/use-babelrc.ts index 6f967221d..4bde282f2 100644 --- a/lib/use-babelrc.js +++ b/lib/use-babelrc.ts @@ -1,5 +1,8 @@ -const webpackPreprocessor = require('@cypress/webpack-preprocessor') +/// +import webpackPreprocessor from '@cypress/webpack-preprocessor' + const defaults = webpackPreprocessor.defaultOptions // remove presets so the babelrc file will be used delete defaults.webpackOptions.module.rules[0].use[0].options.presets -module.exports = webpackPreprocessor(defaults) +export default webpackPreprocessor(defaults) + diff --git a/middleware/express.js b/middleware/express.ts similarity index 52% rename from middleware/express.js rename to middleware/express.ts index 07f884ff2..60e840e99 100644 --- a/middleware/express.js +++ b/middleware/express.ts @@ -1,10 +1,13 @@ +import type { Express } from 'express' + // for Express.js -module.exports = app => { +export default function expressMiddleware(app: Express): void { // expose "GET __coverage__" endpoint that just returns // global coverage information (if the application has been instrumented) app.get('/__coverage__', (req, res) => { res.json({ - coverage: global.__coverage__ || null + coverage: (global as typeof globalThis & { __coverage__?: unknown }).__coverage__ || null }) }) } + diff --git a/middleware/hapi.js b/middleware/hapi.js deleted file mode 100644 index 159dd9728..000000000 --- a/middleware/hapi.js +++ /dev/null @@ -1,14 +0,0 @@ -// for Hapi.js -module.exports = server => { - // expose "GET __coverage__" endpoint that just returns - // global coverage information (if the application has been instrumented) - - // https://hapijs.com/tutorials/routing?lang=en_US - server.route({ - method: 'GET', - path: '/__coverage__', - handler () { - return { coverage: global.__coverage__ } - } - }) -} diff --git a/middleware/hapi.ts b/middleware/hapi.ts new file mode 100644 index 000000000..38594eef8 --- /dev/null +++ b/middleware/hapi.ts @@ -0,0 +1,17 @@ +import type { Server } from 'hapi' +// for Hapi.js +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export default function hapiMiddleware(server: Server): void { + // expose "GET __coverage__" endpoint that just returns + // global coverage information (if the application has been instrumented) + + // https://hapijs.com/tutorials/routing?lang=en_US + server.route({ + method: 'GET', + path: '/__coverage__', + handler() { + return { coverage: (global as typeof globalThis & { __coverage__?: unknown }).__coverage__ || null } + } + }) +} + diff --git a/middleware/nextjs.js b/middleware/nextjs.ts similarity index 79% rename from middleware/nextjs.js rename to middleware/nextjs.ts index 00d1657ac..4d3fdddc8 100644 --- a/middleware/nextjs.js +++ b/middleware/nextjs.ts @@ -20,9 +20,11 @@ * @see https://nextjs.org/docs#api-routes * @see https://github.com/cypress-io/code-coverage */ -module.exports = function returnCodeCoverageNext (req, res) { + +export default function returnCodeCoverageNext(req: any, res: any): void { // only GET is supported res.status(200).json({ - coverage: global.__coverage__ || null + coverage: (global as typeof globalThis & { __coverage__?: unknown }).__coverage__ || null }) } + diff --git a/package-lock.json b/package-lock.json index 0443098a7..22b1772b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,10 @@ "@babel/core": "^7.16.0", "@babel/preset-env": "^7.16.11", "@cypress/code-coverage": "file:.", + "@types/express": "^5.0.6", + "@types/hapi": "^18.0.15", + "@types/next": "^8.0.7", + "@types/node": "^20.19.33", "@vitest/coverage-v8": "^4.0.0", "babel-loader": "^9.1.3", "babel-plugin-istanbul": "6.1.1", @@ -39,6 +43,7 @@ "semantic-release": "17.4.7", "serve": "14.2.4", "start-server-and-test": "2.0.12", + "typescript": "^5.9.3", "vitest": "^4.0.0", "webpack": "^5.68.0", "webpack-cli": "^5.1.4" @@ -1578,23 +1583,6 @@ "node": ">= 6" } }, - "node_modules/@cypress/request/node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/@cypress/request/node_modules/http-signature": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", @@ -7302,6 +7290,24 @@ "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", "dev": true }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/boom": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@types/boom/-/boom-7.3.5.tgz", + "integrity": "sha512-jBS0kU2s9W2sx+ILEyO4kxqIYLllqcUXTaVrBctvGptZ+4X3TWkkgY9+AmxdMPKrgiDDdLcfsaQCTu7bniLvgw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/cacheable-request": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", @@ -7314,6 +7320,13 @@ "@types/responselike": "*" } }, + "node_modules/@types/catbox": { + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/@types/catbox/-/catbox-10.0.9.tgz", + "integrity": "sha512-4qXm1SmZurBMNFc/536+7gfbOlN43fWyoo4O0bdLqtpDK/cpuCYnEDou0Cl4naaMwuJ19rEwnuscR7tetGnTDA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -7330,6 +7343,16 @@ "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -7362,12 +7385,71 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/hapi": { + "version": "18.0.15", + "resolved": "https://registry.npmjs.org/@types/hapi/-/hapi-18.0.15.tgz", + "integrity": "sha512-n7fXlpRAptQ+6N0JdQun+K4co2v3Wv5+KOFYLFV1xL8HEJ+EX8hqvbBmQg+uL3OkzwQZHvgCtHiBSdmCCDlrog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/boom": "*", + "@types/catbox": "*", + "@types/iron": "*", + "@types/mimos": "*", + "@types/node": "*", + "@types/podium": "*", + "@types/shot": "*", + "joi": "^17.3.0" + } + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", "dev": true }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/iron": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@types/iron/-/iron-5.0.5.tgz", + "integrity": "sha512-ndu2RvRJ5LWsSVF0kBMJe9qnNcFcAO9eYwzr2P4FOU6m5ypRrbdiX+d8x4GNG7lIn1mKShyQf3M08CIX4wPsEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -7383,19 +7465,73 @@ "@types/node": "*" } }, + "node_modules/@types/mime-db": { + "version": "1.43.6", + "resolved": "https://registry.npmjs.org/@types/mime-db/-/mime-db-1.43.6.tgz", + "integrity": "sha512-r2cqxAt/Eo5yWBOQie1lyM1JZFCiORa5xtLlhSZI0w8RJggBPKw8c4g/fgQCzWydaDR5bL4imnmix2d1n52iBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mimos": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/mimos/-/mimos-3.0.6.tgz", + "integrity": "sha512-pQlYu/Q1e5F5lyu7ATW4J2cyPOfjhRHZgAepZlKBbHqqAjshteHtNLqBXgx7KV5GjXjPLXWUvbzWaGwmVFPaYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime-db": "*" + } + }, "node_modules/@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, + "node_modules/@types/next": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@types/next/-/next-8.0.7.tgz", + "integrity": "sha512-I/Gcj1YfOFmpBBX5XgBP1t1wKcFS0TGk8ytW99ujjvCp8U31QuKqM3fvvGb7+Hf1CJt3BAAgzGT0aCigqO5opQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/next-server": "*", + "@types/node": "*", + "@types/node-fetch": "*", + "@types/react": "*" + } + }, + "node_modules/@types/next-server": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/next-server/-/next-server-8.1.2.tgz", + "integrity": "sha512-Fm4QhAxwDlC9AHiGy23Lhv7DeTTt1O1s7tnAsyVOLPjePmYXPZVbOCrxd2oRHZnIIYWw41JelLbq4hN1B5idlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/next": "*", + "@types/node": "*", + "@types/react": "*", + "@types/react-loadable": "*" + } + }, "node_modules/@types/node": { - "version": "25.2.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.2.tgz", - "integrity": "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==", + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "@types/node": "*", + "form-data": "^4.0.4" } }, "node_modules/@types/normalize-package-data": { @@ -7410,6 +7546,48 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/podium": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/podium/-/podium-1.0.4.tgz", + "integrity": "sha512-HuG5/hRHs9PxuXXlNFXPy7mHMnBD6Z4riED2SFGwjs+RcszJUkxLgYHQpoiDpYrhLv7sHk9WDyswybD6aNYkig==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", + "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-loadable": { + "version": "5.5.11", + "resolved": "https://registry.npmjs.org/@types/react-loadable/-/react-loadable-5.5.11.tgz", + "integrity": "sha512-/tq2IJ853MoIFRBmqVOxnGsRRjER5TmEKzsZtaAkiXAWoDeKgR/QNOT1vd9k0p9h/F616X21cpNh3hu4RutzRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*", + "@types/webpack": "^4" + } + }, "node_modules/@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -7425,6 +7603,37 @@ "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==", "dev": true }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@types/shot": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/shot/-/shot-4.0.5.tgz", + "integrity": "sha512-4wiGdy1sXfpcFgF1VAouUaSl6zjWn0v2NarNgviAJYm3BeiODywERnRtIC5YtNIbfh3UfShfCRD9yyeWAD7R0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/sinonjs__fake-timers": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", @@ -7437,6 +7646,20 @@ "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", "dev": true }, + "node_modules/@types/source-list-map": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.6.tgz", + "integrity": "sha512-5JcVt1u5HDmlXkwOD2nslZVllBBc7HDuOICfiZah2Z0is8M8g+ddAEawbmd3VjedfDHBzxCaXLs07QEmb7y54g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tapable": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.12.tgz", + "integrity": "sha512-bTHG8fcxEqv1M9+TD14P8ok8hjxoOCkfKc8XXLaaD05kI7ohpeI956jtDOD3XHKBQrlyPughUtzm1jtVhHpA5Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/tmp": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.6.tgz", @@ -7444,6 +7667,53 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/uglify-js": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.5.tgz", + "integrity": "sha512-TU+fZFBTBcXj/GpDpDaBmgWk/gn96kMZ+uocaFUlV2f8a6WdMzzI44QBCmGcCiYR0Y6ZlNRiyUyKKt5nl/lbzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "^0.6.1" + } + }, + "node_modules/@types/webpack": { + "version": "4.41.40", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.40.tgz", + "integrity": "sha512-u6kMFSBM9HcoTpUXnL6mt2HSzftqb3JgYV6oxIgL2dl6sX6aCa5k6SOkzv5DuZjBTPUE/dJltKtwwuqrkZHpfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tapable": "^1", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "anymatch": "^3.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/@types/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-4nZOdMwSPHZ4pTEZzSp0AsTM4K7Qmu40UKW4tJDiOVs20UzYF9l+qUe4s0ftfN0pin06n+5cWWDJXH+sbhAiDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.7.3" + } + }, + "node_modules/@types/webpack-sources/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/@types/yauzl": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", @@ -7945,6 +8215,20 @@ "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", "dev": true }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/append-transform": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", @@ -8148,22 +8432,6 @@ "proxy-from-env": "^1.1.0" } }, - "node_modules/axios/node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/axios/node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -9952,6 +10220,13 @@ "node": ">=8" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cypress": { "version": "15.10.0", "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.10.0.tgz", @@ -10308,16 +10583,6 @@ "node": ">=4.0" } }, - "node_modules/degenerator/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/del": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", @@ -11194,6 +11459,23 @@ "node": "*" } }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -11650,15 +11932,6 @@ "uglify-js": "^3.1.4" } }, - "node_modules/handlebars/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -12462,14 +12735,6 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/istanbul-reports": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", @@ -13962,6 +14227,16 @@ "node": ">=10" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/npm": { "version": "7.21.1", "resolved": "https://registry.npmjs.org/npm/-/npm-7.21.1.tgz", @@ -19642,6 +19917,15 @@ "node": ">= 14" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -19661,14 +19945,6 @@ "source-map": "^0.6.0" } }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/spawn-error-forwarder": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", @@ -20584,6 +20860,20 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/uglify-js": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.1.tgz", @@ -20598,9 +20888,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { diff --git a/package.json b/package.json index 096fcd223..46b6ca060 100644 --- a/package.json +++ b/package.json @@ -3,15 +3,17 @@ "version": "0.0.0-development", "description": "Saves the code coverage collected during Cypress tests", "exports": { - "./task": "./lib/task.js", - "./use-babelrc": "./lib/use-babelrc.js", - "./plugins": "./lib/plugins.js", - "./support": "./lib/support.js", - "./middleware/express": "./middleware/express.js", - "./middleware/hapi": "./middleware/hapi.js", - "./middleware/nextjs": "./middleware/nextjs.js" + "./task": "./dist/lib/task.js", + "./use-babelrc": "./dist/lib/use-babelrc.js", + "./plugins": "./dist/lib/plugins.js", + "./support": "./dist/lib/support.js", + "./middleware/express": "./dist/middleware/express.js", + "./middleware/hapi": "./dist/middleware/hapi.js", + "./middleware/nextjs": "./dist/middleware/nextjs.js" }, "scripts": { + "build": "tsc", + "prepublishOnly": "npm run build", "start": "parcel serve cypress/index.html", "cy:open": "cypress open", "dev": "start-test 1234 cy:open", @@ -21,8 +23,8 @@ "coverage": "vitest run --coverage", "report:coverage": "nyc report --reporter=html", "dev:no:coverage": "start-test 1234 'cypress open --expose coverage=false'", - "format": "prettier --write '*.js'", - "format:check": "prettier --check '*.js'", + "format": "prettier --write '*.{js,ts}'", + "format:check": "prettier --check '*.{js,ts}'", "check:markdown": "find *.md -exec npx markdown-link-check {} \\;", "effective:config": "circleci config process .circleci/config.yml | sed /^#/d", "apps:test:coverage:verify": "./scripts/test-apps-and-verify-coverage.sh" @@ -55,9 +57,8 @@ }, "homepage": "https://github.com/cypress-io/code-coverage#readme", "files": [ - "*.d.ts", - "*.js", - "middleware" + "dist", + "*.d.ts" ], "publishConfig": { "access": "public" @@ -78,6 +79,10 @@ "@babel/core": "^7.16.0", "@babel/preset-env": "^7.16.11", "@cypress/code-coverage": "file:.", + "@types/express": "^5.0.6", + "@types/hapi": "^18.0.15", + "@types/next": "^8.0.7", + "@types/node": "^20.19.33", "@vitest/coverage-v8": "^4.0.0", "babel-loader": "^9.1.3", "babel-plugin-istanbul": "6.1.1", @@ -94,6 +99,7 @@ "semantic-release": "17.4.7", "serve": "14.2.4", "start-server-and-test": "2.0.12", + "typescript": "^5.9.3", "vitest": "^4.0.0", "webpack": "^5.68.0", "webpack-cli": "^5.1.4" diff --git a/test/lib/combine.test.js b/test/lib/combine.test.ts similarity index 96% rename from test/lib/combine.test.js rename to test/lib/combine.test.ts index 15776741d..0598cd3f6 100644 --- a/test/lib/combine.test.js +++ b/test/lib/combine.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest' -const { combineNycOptions, defaultNycOptions } = require('../../lib/common-utils') +import { combineNycOptions, defaultNycOptions } from '../../lib/common-utils' describe('Combine NYC options', () => { it('overrides defaults', () => { diff --git a/test/lib/filtering.test.js b/test/lib/filtering.test.ts similarity index 84% rename from test/lib/filtering.test.js rename to test/lib/filtering.test.ts index 94abb00ad..454757087 100644 --- a/test/lib/filtering.test.js +++ b/test/lib/filtering.test.ts @@ -3,6 +3,16 @@ import { minimatch } from 'minimatch' import _ from 'lodash' // Mock Cypress globals before importing support-utils +declare global { + // eslint-disable-next-line no-var + var Cypress: { + minimatch: typeof minimatch + _: { + omitBy: typeof _.omitBy + } + } +} + global.Cypress = { minimatch, _: { @@ -10,7 +20,7 @@ global.Cypress = { } } -const { filterFilesFromCoverage } = require('../../lib/support-utils') +import { filterFilesFromCoverage } from '../../lib/support-utils' describe('minimatch', () => { it('string matches', () => { @@ -30,17 +40,17 @@ describe('minimatch', () => { describe('filtering specs', () => { describe('using integrationFolder and testFiles in Cypress < v10', () => { - let config - let expose - let spec + let config: ReturnType + let expose: ReturnType + let spec: { absolute: string; relative: string } beforeEach(() => { - const configValues = { + const configValues: Record = { integrationFolder: '/user/app/cypress/integration', supportFile: '/user/app/cypress/support/index.js', supportFolder: '/user/app/cypress/support' } - config = vi.fn((key) => { + config = vi.fn((key: string) => { return configValues[key] }) @@ -53,7 +63,7 @@ describe('filtering specs', () => { }) it('filters list of specs by single string', () => { - config.mockImplementation((key) => { + config.mockImplementation((key: string) => { if (key === 'testFiles') return 'specA.js' if (key === 'integrationFolder') return '/user/app/cypress/integration' if (key === 'supportFile') return '/user/app/cypress/support/index.js' @@ -64,14 +74,14 @@ describe('filtering specs', () => { '/user/app/cypress/integration/specA.js': {}, '/user/app/cypress/integration/specB.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config, expose, spec) + const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) expect(result).toEqual({ '/user/app/cypress/integration/specB.js': {} }) }) it('filters list of specs by single string in array', () => { - config.mockImplementation((key) => { + config.mockImplementation((key: string) => { if (key === 'testFiles') return ['codeA.js'] if (key === 'integrationFolder') return '/user/app/cypress/integration' if (key === 'supportFile') return '/user/app/cypress/support/index.js' @@ -82,14 +92,14 @@ describe('filtering specs', () => { '/user/app/src/codeA.js': {}, '/user/app/src/codeB.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config, expose, spec) + const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) expect(result).toEqual({ '/user/app/src/codeB.js': {} }) }) it('filters list of specs by pattern', () => { - config.mockImplementation((key) => { + config.mockImplementation((key: string) => { if (key === 'testFiles') return ['**/*B.js'] if (key === 'integrationFolder') return '/user/app/cypress/integration' if (key === 'supportFile') return '/user/app/cypress/support/index.js' @@ -101,14 +111,14 @@ describe('filtering specs', () => { '/user/app/src/codeA.js': {}, '/user/app/src/codeB.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config, expose, spec) + const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) expect(result).toEqual({ '/user/app/src/codeA.js': {} }) }) it('filters list of specs by pattern and single spec', () => { - config.mockImplementation((key) => { + config.mockImplementation((key: string) => { if (key === 'testFiles') return ['**/*B.js', 'codeA.js'] if (key === 'integrationFolder') return '/user/app/cypress/integration' if (key === 'supportFile') return '/user/app/cypress/support/index.js' @@ -120,12 +130,12 @@ describe('filtering specs', () => { '/user/app/src/codeA.js': {}, '/user/app/src/codeB.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config, expose, spec) + const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) expect(result).toEqual({}) }) it('filters specs from integration folder', () => { - config.mockImplementation((key) => { + config.mockImplementation((key: string) => { if (key === 'testFiles') return '**/*.*' // default pattern if (key === 'integrationFolder') return '/user/app/cypress/integration' if (key === 'supportFile') return '/user/app/cypress/support/index.js' @@ -140,7 +150,7 @@ describe('filtering specs', () => { '/user/app/cypress/integration/spec1.js': {}, '/user/app/cypress/integration/spec2.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config, expose, spec) + const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) expect(result).toEqual({ '/user/app/src/codeA.js': {}, '/user/app/src/codeB.js': {} @@ -148,7 +158,7 @@ describe('filtering specs', () => { }) it('filters list of specs when testFiles specifies folder', () => { - config.mockImplementation((key) => { + config.mockImplementation((key: string) => { if (key === 'testFiles') return ['cypress/integration/**.*'] if (key === 'integrationFolder') return '/user/app/cypress/integration' if (key === 'supportFile') return '/user/app/cypress/support/index.js' @@ -162,14 +172,14 @@ describe('filtering specs', () => { // This file should be included in coverage 'src/my-code.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config, expose, spec) + const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) expect(result).toEqual({ 'src/my-code.js': {} }) }) it('filters files out of cypress support directory', () => { - config.mockImplementation((key) => { + config.mockImplementation((key: string) => { if (key === 'testFiles') return ['**/*.*'] // default pattern if (key === 'integrationFolder') return '/user/app/cypress/integration' if (key === 'supportFile') return '/user/app/cypress/support/index.js' @@ -183,7 +193,7 @@ describe('filtering specs', () => { // This file should be included in coverage 'src/my-code.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config, expose, spec) + const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) expect(result).toEqual({ 'src/my-code.js': {} }) @@ -191,9 +201,9 @@ describe('filtering specs', () => { }) describe('using codeCoverage.exclude and specPattern in Cypress >= v10', () => { - let config - let expose - let spec + let config: ReturnType + let expose: ReturnType + let spec: { absolute: string; relative: string } beforeEach(() => { config = vi.fn() @@ -212,7 +222,7 @@ describe('filtering specs', () => { }) it('filters list of specs by single string', () => { - config.mockImplementation((key) => { + config.mockImplementation((key: string) => { if (key === 'specPattern') return 'specA.cy.js' return undefined }) @@ -220,14 +230,14 @@ describe('filtering specs', () => { '/user/app/src/specA.cy.js': {}, '/user/app/src/specB.cy.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config, expose, spec) + const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) expect(result).toEqual({ '/user/app/src/specB.cy.js': {} }) }) it('filters list of specs by single string in array', () => { - config.mockImplementation((key) => { + config.mockImplementation((key: string) => { if (key === 'specPattern') return ['specA.cy.js'] return undefined }) @@ -235,14 +245,14 @@ describe('filtering specs', () => { '/user/app/src/specA.cy.js': {}, '/user/app/src/specB.cy.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config, expose, spec) + const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) expect(result).toEqual({ '/user/app/src/specB.cy.js': {} }) }) it('filters out file in codeCoverage.exclude', () => { - config.mockImplementation((key) => { + config.mockImplementation((key: string) => { if (key === 'specPattern') return ['**/*.cy.js'] return undefined }) @@ -253,7 +263,7 @@ describe('filtering specs', () => { '/user/app/src/codeA.js': {}, '/user/app/src/codeB.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config, expose, spec) + const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) expect(result).toEqual({ '/user/app/src/codeA.js': {}, '/user/app/src/codeB.js': {} @@ -261,7 +271,7 @@ describe('filtering specs', () => { }) it('filters list of specs by pattern', () => { - config.mockImplementation((key) => { + config.mockImplementation((key: string) => { if (key === 'specPattern') return ['**/*B.js'] return undefined }) @@ -270,14 +280,14 @@ describe('filtering specs', () => { '/user/app/src/codeA.js': {}, '/user/app/src/codeB.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config, expose, spec) + const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) expect(result).toEqual({ '/user/app/src/codeA.js': {} }) }) it('filters list of specs by pattern and single spec', () => { - config.mockImplementation((key) => { + config.mockImplementation((key: string) => { if (key === 'specPattern') return ['**/*B.js', 'codeA.js'] return undefined }) @@ -286,12 +296,12 @@ describe('filtering specs', () => { '/user/app/src/codeA.js': {}, '/user/app/src/codeB.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config, expose, spec) + const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) expect(result).toEqual({}) }) it('filters list of specs in integration folder', () => { - config.mockImplementation((key) => { + config.mockImplementation((key: string) => { if (key === 'specPattern') return '**/*.cy.{js,jsx,ts,tsx}' // default pattern return undefined }) @@ -303,7 +313,7 @@ describe('filtering specs', () => { '/user/app/cypress/integration/spec1.js': {}, '/user/app/cypress/integration/spec2.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config, expose, spec) + const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) expect(result).toEqual({ '/user/app/src/codeA.js': {}, '/user/app/src/codeB.js': {} @@ -311,7 +321,7 @@ describe('filtering specs', () => { }) it('filters list of specs when specPattern specifies folder', () => { - config.mockImplementation((key) => { + config.mockImplementation((key: string) => { if (key === 'specPattern') return ['src/**/*.cy.js'] return undefined }) @@ -322,7 +332,7 @@ describe('filtering specs', () => { // This file should be included in coverage 'src/my-code.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config, expose, spec) + const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) expect(result).toEqual({ 'src/my-code.js': {} }) @@ -336,7 +346,7 @@ describe('filtering specs', () => { } }) - config.mockImplementation((key) => { + config.mockImplementation((key: string) => { if (key === 'specPattern') return ['src/**/*.cy.js'] return undefined }) @@ -348,7 +358,7 @@ describe('filtering specs', () => { '/user/app/cypress/c.js': {}, 'src/my-code.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config, expose, spec) + const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) expect(result).toEqual({ '/user/app/cypress/c.js': {}, 'src/my-code.js': {} diff --git a/test/lib/fix-source-paths.test.js b/test/lib/fix-source-paths.test.ts similarity index 94% rename from test/lib/fix-source-paths.test.js rename to test/lib/fix-source-paths.test.ts index 75f320abb..47f3e5506 100644 --- a/test/lib/fix-source-paths.test.js +++ b/test/lib/fix-source-paths.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest' -const { fixSourcePaths } = require('../../lib/support-utils') +import { fixSourcePaths } from '../../lib/support-utils' describe('fixSourcePaths', () => { it('fixes webpack loader source-map pathes', () => { diff --git a/test/lib/merge.test.js b/test/lib/merge.test.ts similarity index 79% rename from test/lib/merge.test.js rename to test/lib/merge.test.ts index 0f7018ce3..f1dce7a17 100644 --- a/test/lib/merge.test.js +++ b/test/lib/merge.test.ts @@ -1,17 +1,16 @@ import { describe, it, expect, beforeAll } from 'vitest' import _ from 'lodash' -const istanbul = require('istanbul-lib-coverage') -const coverage = require('./__fixtures__/coverage.json') -const { +import { createCoverageMap } from 'istanbul-lib-coverage' +import coverage from './__fixtures__/coverage.json' +import { fileCoveragePlaceholder, removePlaceholders -} = require('../../lib/common-utils') +} from '../../lib/common-utils' /** * Extracts just the data from the coverage map object - * @param {*} cm */ -const coverageMapToCoverage = (cm) => { +const coverageMapToCoverage = (cm: ReturnType) => { return JSON.parse(JSON.stringify(cm)) } @@ -23,8 +22,8 @@ describe('merging coverage', () => { }) it('combines an empty coverage object', () => { - const previous = istanbul.createCoverageMap({}) - const coverageMap = istanbul.createCoverageMap(previous) + const previous = createCoverageMap({}) + const coverageMap = createCoverageMap(previous) coverageMap.merge(_.cloneDeep(coverage)) const merged = coverageMapToCoverage(coverageMap) @@ -33,8 +32,8 @@ describe('merging coverage', () => { }) it('combines the same full coverage twice', () => { - const previous = istanbul.createCoverageMap(_.cloneDeep(coverage)) - const coverageMap = istanbul.createCoverageMap(previous) + const previous = createCoverageMap(_.cloneDeep(coverage)) + const coverageMap = createCoverageMap(previous) coverageMap.merge(_.cloneDeep(coverage)) const merged = coverageMapToCoverage(coverageMap) @@ -57,8 +56,8 @@ describe('merging coverage', () => { }) // now lets merge full info - const previous = istanbul.createCoverageMap(coverageWithPlaceHolder) - const coverageMap = istanbul.createCoverageMap(previous) + const previous = createCoverageMap(coverageWithPlaceHolder) + const coverageMap = createCoverageMap(previous) coverageMap.merge(coverage) const merged = coverageMapToCoverage(coverageMap) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..701739022 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "CommonJS", + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": ".", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "moduleResolution": "node", + "types": ["node", "cypress"] + }, + "include": [ + "lib/**/*", + "middleware/**/*", + "test/**/*", + "vitest.config.ts" + ], + "exclude": [ + "test-apps/**/*", + "node_modules", + "dist", + "coverage" + ] +} + diff --git a/vitest.config.js b/vitest.config.ts similarity index 74% rename from vitest.config.js rename to vitest.config.ts index b281a9482..7a5a6d587 100644 --- a/vitest.config.js +++ b/vitest.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ test: { coverage: { provider: 'v8', - include: ['common-utils.js', 'support-utils.js'], + include: ['lib/common-utils.ts', 'lib/support-utils.ts'], thresholds: { lines: 80 } From ec2fcf224ee03937ffb374d31c02fa24470d314d Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Mon, 9 Feb 2026 10:52:38 -0500 Subject: [PATCH 02/17] fix type errors --- lib/task-utils.ts | 2 + lib/task.ts | 2 +- lib/use-babelrc.ts | 14 ++- package-lock.json | 55 ++++++++++- package.json | 7 +- test/lib/filtering.test.ts | 190 ++----------------------------------- tsconfig.json | 2 - 7 files changed, 78 insertions(+), 194 deletions(-) diff --git a/lib/task-utils.ts b/lib/task-utils.ts index 837f3a82d..31907df14 100644 --- a/lib/task-utils.ts +++ b/lib/task-utils.ts @@ -392,6 +392,8 @@ export function includeAllFiles(nycFilename: string, nycOptions: NycOptions): vo changed = true // insert placeholder object for now const placeholder = fileCoveragePlaceholder(fullPath) + // TODO: fix placeholder shape + // @ts-expect-error - placeholder doesn't match internal types yet nycCoverage[fullPath] = placeholder }) diff --git a/lib/task.ts b/lib/task.ts index b1e5266ca..7b1f13a9d 100644 --- a/lib/task.ts +++ b/lib/task.ts @@ -2,7 +2,7 @@ import { createCoverageMap, CoverageMap as IstanbulCoverageMap } from 'istanbul-lib-coverage' import { join, resolve } from 'path' import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs' -import { execa } from 'execa' +import execa from 'execa' import { showNycInfo, resolveRelativePaths, diff --git a/lib/use-babelrc.ts b/lib/use-babelrc.ts index 4bde282f2..179a53a91 100644 --- a/lib/use-babelrc.ts +++ b/lib/use-babelrc.ts @@ -1,8 +1,16 @@ /// +/// import webpackPreprocessor from '@cypress/webpack-preprocessor' const defaults = webpackPreprocessor.defaultOptions -// remove presets so the babelrc file will be used -delete defaults.webpackOptions.module.rules[0].use[0].options.presets -export default webpackPreprocessor(defaults) +// remove presets so the babelrc file will be used +// @ts-ignore - we know that the use property exists +if (defaults.webpackOptions?.module?.rules?.[0]?.use?.[0]?.options?.presets) { + // @ts-ignore - we know that the use property exists + delete defaults.webpackOptions.module.rules[0].use[0].options.presets +} +// @ts-ignore - we know that the use property exists +const preprocessor = webpackPreprocessor(defaults) +// Type assertion to avoid exposing private FileEvent type +export default preprocessor as (file: Cypress.FileObject) => string | Promise diff --git a/package-lock.json b/package-lock.json index 22b1772b8..e4e68c4f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,6 @@ "debug": "4.4.0", "execa": "4.1.0", "istanbul-lib-coverage": "^3.0.0", - "js-yaml": "4.1.0", "nyc": "^17.1.0", "tinyglobby": "^0.2.14" }, @@ -23,8 +22,12 @@ "@babel/core": "^7.16.0", "@babel/preset-env": "^7.16.11", "@cypress/code-coverage": "file:.", + "@types/debug": "^4.1.12", "@types/express": "^5.0.6", "@types/hapi": "^18.0.15", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/js-yaml": "^4.0.9", + "@types/lodash": "^4.17.23", "@types/next": "^8.0.7", "@types/node": "^20.19.33", "@vitest/coverage-v8": "^4.0.0", @@ -34,6 +37,7 @@ "console-log-div": "0.6.3", "cypress": "^15.10.0", "express": "^4.18.2", + "js-yaml": "^4.1.1", "lodash": "4.17.21", "markdown-link-check": "3.13.6", "minimatch": "^10.0.0", @@ -7353,6 +7357,16 @@ "@types/node": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -7450,6 +7464,20 @@ "@types/node": "*" } }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -7465,6 +7493,13 @@ "@types/node": "*" } }, + "node_modules/@types/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mime-db": { "version": "1.43.6", "resolved": "https://registry.npmjs.org/@types/mime-db/-/mime-db-1.43.6.tgz", @@ -7488,6 +7523,13 @@ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/next": { "version": "8.0.7", "resolved": "https://registry.npmjs.org/@types/next/-/next-8.0.7.tgz", @@ -12832,9 +12874,11 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -12845,7 +12889,8 @@ "node_modules/js-yaml/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "node_modules/jsbn": { "version": "0.1.1", diff --git a/package.json b/package.json index 46b6ca060..251c456b9 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "./middleware/nextjs": "./dist/middleware/nextjs.js" }, "scripts": { + "prebuild": "rimraf dist", "build": "tsc", "prepublishOnly": "npm run build", "start": "parcel serve cypress/index.html", @@ -71,7 +72,6 @@ "debug": "4.4.0", "execa": "4.1.0", "istanbul-lib-coverage": "^3.0.0", - "js-yaml": "4.1.0", "nyc": "^17.1.0", "tinyglobby": "^0.2.14" }, @@ -79,8 +79,12 @@ "@babel/core": "^7.16.0", "@babel/preset-env": "^7.16.11", "@cypress/code-coverage": "file:.", + "@types/debug": "^4.1.12", "@types/express": "^5.0.6", "@types/hapi": "^18.0.15", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/js-yaml": "^4.0.9", + "@types/lodash": "^4.17.23", "@types/next": "^8.0.7", "@types/node": "^20.19.33", "@vitest/coverage-v8": "^4.0.0", @@ -90,6 +94,7 @@ "console-log-div": "0.6.3", "cypress": "^15.10.0", "express": "^4.18.2", + "js-yaml": "^4.1.1", "lodash": "4.17.21", "markdown-link-check": "3.13.6", "minimatch": "^10.0.0", diff --git a/test/lib/filtering.test.ts b/test/lib/filtering.test.ts index 454757087..d7e7b0e9f 100644 --- a/test/lib/filtering.test.ts +++ b/test/lib/filtering.test.ts @@ -2,25 +2,12 @@ import { describe, it, expect, beforeEach, vi } from 'vitest' import { minimatch } from 'minimatch' import _ from 'lodash' -// Mock Cypress globals before importing support-utils -declare global { - // eslint-disable-next-line no-var - var Cypress: { - minimatch: typeof minimatch - _: { - omitBy: typeof _.omitBy - } - } -} +import { filterFilesFromCoverage } from '../../lib/support-utils' -global.Cypress = { +vi.stubGlobal('Cypress', { minimatch, - _: { - omitBy: _.omitBy - } -} - -import { filterFilesFromCoverage } from '../../lib/support-utils' + _, +}) describe('minimatch', () => { it('string matches', () => { @@ -39,168 +26,7 @@ describe('minimatch', () => { }) describe('filtering specs', () => { - describe('using integrationFolder and testFiles in Cypress < v10', () => { - let config: ReturnType - let expose: ReturnType - let spec: { absolute: string; relative: string } - - beforeEach(() => { - const configValues: Record = { - integrationFolder: '/user/app/cypress/integration', - supportFile: '/user/app/cypress/support/index.js', - supportFolder: '/user/app/cypress/support' - } - config = vi.fn((key: string) => { - return configValues[key] - }) - - expose = vi.fn().mockReturnValue({}) - - spec = { - absolute: '/user/app/cypress/integration/test.cy.js', - relative: 'cypress/integration/test.cy.js' - } - }) - - it('filters list of specs by single string', () => { - config.mockImplementation((key: string) => { - if (key === 'testFiles') return 'specA.js' - if (key === 'integrationFolder') return '/user/app/cypress/integration' - if (key === 'supportFile') return '/user/app/cypress/support/index.js' - if (key === 'supportFolder') return '/user/app/cypress/support' - return undefined - }) - const totalCoverage = { - '/user/app/cypress/integration/specA.js': {}, - '/user/app/cypress/integration/specB.js': {} - } - const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) - expect(result).toEqual({ - '/user/app/cypress/integration/specB.js': {} - }) - }) - - it('filters list of specs by single string in array', () => { - config.mockImplementation((key: string) => { - if (key === 'testFiles') return ['codeA.js'] - if (key === 'integrationFolder') return '/user/app/cypress/integration' - if (key === 'supportFile') return '/user/app/cypress/support/index.js' - if (key === 'supportFolder') return '/user/app/cypress/support' - return undefined - }) - const totalCoverage = { - '/user/app/src/codeA.js': {}, - '/user/app/src/codeB.js': {} - } - const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) - expect(result).toEqual({ - '/user/app/src/codeB.js': {} - }) - }) - - it('filters list of specs by pattern', () => { - config.mockImplementation((key: string) => { - if (key === 'testFiles') return ['**/*B.js'] - if (key === 'integrationFolder') return '/user/app/cypress/integration' - if (key === 'supportFile') return '/user/app/cypress/support/index.js' - if (key === 'supportFolder') return '/user/app/cypress/support' - return undefined - }) - - const totalCoverage = { - '/user/app/src/codeA.js': {}, - '/user/app/src/codeB.js': {} - } - const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) - expect(result).toEqual({ - '/user/app/src/codeA.js': {} - }) - }) - - it('filters list of specs by pattern and single spec', () => { - config.mockImplementation((key: string) => { - if (key === 'testFiles') return ['**/*B.js', 'codeA.js'] - if (key === 'integrationFolder') return '/user/app/cypress/integration' - if (key === 'supportFile') return '/user/app/cypress/support/index.js' - if (key === 'supportFolder') return '/user/app/cypress/support' - return undefined - }) - - const totalCoverage = { - '/user/app/src/codeA.js': {}, - '/user/app/src/codeB.js': {} - } - const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) - expect(result).toEqual({}) - }) - - it('filters specs from integration folder', () => { - config.mockImplementation((key: string) => { - if (key === 'testFiles') return '**/*.*' // default pattern - if (key === 'integrationFolder') return '/user/app/cypress/integration' - if (key === 'supportFile') return '/user/app/cypress/support/index.js' - if (key === 'supportFolder') return '/user/app/cypress/support' - return undefined - }) - - const totalCoverage = { - '/user/app/src/codeA.js': {}, - '/user/app/src/codeB.js': {}, - // these files should be removed - '/user/app/cypress/integration/spec1.js': {}, - '/user/app/cypress/integration/spec2.js': {} - } - const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) - expect(result).toEqual({ - '/user/app/src/codeA.js': {}, - '/user/app/src/codeB.js': {} - }) - }) - - it('filters list of specs when testFiles specifies folder', () => { - config.mockImplementation((key: string) => { - if (key === 'testFiles') return ['cypress/integration/**.*'] - if (key === 'integrationFolder') return '/user/app/cypress/integration' - if (key === 'supportFile') return '/user/app/cypress/support/index.js' - if (key === 'supportFolder') return '/user/app/cypress/support' - return undefined - }) - - const totalCoverage = { - '/user/app/cypress/integration/specA.js': {}, - '/user/app/cypress/integration/specB.js': {}, - // This file should be included in coverage - 'src/my-code.js': {} - } - const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) - expect(result).toEqual({ - 'src/my-code.js': {} - }) - }) - - it('filters files out of cypress support directory', () => { - config.mockImplementation((key: string) => { - if (key === 'testFiles') return ['**/*.*'] // default pattern - if (key === 'integrationFolder') return '/user/app/cypress/integration' - if (key === 'supportFile') return '/user/app/cypress/support/index.js' - if (key === 'supportFolder') return '/user/app/cypress/support' - return undefined - }) - const totalCoverage = { - '/user/app/cypress/support/index.js': {}, - '/user/app/cypress/support/command.js': {}, - '/user/app/cypress/integration/spec.js': {}, - // This file should be included in coverage - 'src/my-code.js': {} - } - const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) - expect(result).toEqual({ - 'src/my-code.js': {} - }) - }) - }) - - describe('using codeCoverage.exclude and specPattern in Cypress >= v10', () => { + describe('using codeCoverage.exclude and specPattern', () => { let config: ReturnType let expose: ReturnType let spec: { absolute: string; relative: string } @@ -300,7 +126,7 @@ describe('filtering specs', () => { expect(result).toEqual({}) }) - it('filters list of specs in integration folder', () => { + it('filters list of specs matching specPattern', () => { config.mockImplementation((key: string) => { if (key === 'specPattern') return '**/*.cy.{js,jsx,ts,tsx}' // default pattern return undefined @@ -310,8 +136,8 @@ describe('filtering specs', () => { '/user/app/src/codeA.js': {}, '/user/app/src/codeB.js': {}, // these files should be removed - '/user/app/cypress/integration/spec1.js': {}, - '/user/app/cypress/integration/spec2.js': {} + '/user/app/cypress/integration/spec1.cy.js': {}, + '/user/app/cypress/integration/spec2.cy.js': {} } const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) expect(result).toEqual({ diff --git a/tsconfig.json b/tsconfig.json index 701739022..5bd153ce6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,8 +19,6 @@ "include": [ "lib/**/*", "middleware/**/*", - "test/**/*", - "vitest.config.ts" ], "exclude": [ "test-apps/**/*", From f7042db0a4fe8b49bc90d4d7bc48e7ced03fde7e Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Mon, 9 Feb 2026 11:18:59 -0500 Subject: [PATCH 03/17] updates circleci to build ts, consolidates matrix job, renames jobs and params for grokability --- .circleci/config.yml | 63 ++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 094d3bd4d..f73be6f21 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -68,6 +68,18 @@ jobs: command: npm i -D check-code-coverage && npm run coverage:check-files working_directory: test-apps/all-files + build-dist: + docker: + - image: cimg/node:22.14.0 + steps: + - attach_workspace: + at: ~/ + - run: npm run build + - persist_to_workspace: + paths: + - dist + root: ~/ + publish: description: Publishes the new version of the plugin to NPM docker: @@ -85,36 +97,36 @@ jobs: at: ~/ - run: npm run semantic-release - cyrun: + verify-test-apps: docker: - image: cypress/base:22.14.0 parameters: - jobname: + app: type: string steps: - attach_workspace: at: ~/ - run: - working_directory: test-apps/<< parameters.jobname >> + working_directory: test-apps/<< parameters.app >> command: npm i - run: command: npm run test - working_directory: test-apps/<< parameters.jobname >> + working_directory: test-apps/<< parameters.app >> - store_artifacts: - path: test-apps/<< parameters.jobname >>/coverage + path: test-apps/<< parameters.app >>/coverage - run: name: Verify Code Coverage command: npm run coverage:verify - working_directory: test-apps/<< parameters.jobname >> + working_directory: test-apps/<< parameters.app >> - run: name: Check code coverage files 📈 # we will check the final coverage report # to make sure it only has files we are interested in # because there are files covered at 0 in the report command: npm run coverage:check-files - working_directory: test-apps/<< parameters.jobname >> + working_directory: test-apps/<< parameters.app >> - test-code-coverage-plugin: + unit-test: docker: - image: cypress/base:24.11.0 steps: @@ -133,17 +145,16 @@ workflows: requires: - install_and_persist - - test-code-coverage-plugin: + - unit-test: requires: - install_and_persist - - cyrun: - name: test-<< matrix.jobname>> + - verify-test-apps: requires: - - install_and_persist + - lint matrix: parameters: - jobname: + app: - all-files - backend - batch-send-coverage @@ -161,6 +172,12 @@ workflows: - use-webpack - redirect - windows_test + - build-dist: + requires: + - lint + - unit-test + - verify-test-apps + - windows_test - publish: context: org-npm-credentials filters: @@ -171,22 +188,4 @@ workflows: - next - dev requires: - - lint - - test-code-coverage-plugin - - test-all-files - - test-backend - - test-batch-send-coverage - - test-before-all-visit - - test-before-each-visit - - test-exclude-files - - test-frontend - - test-fullstack - - test-multiple-backends - - test-one-spec - - test-same-folder - - test-support-files - - test-ts-example - - test-unit-tests-js - - test-use-webpack - - test-redirect - - windows_test + - build-dist From c6f693ac7ace82962e020ed6bd9cd250dc47cf6e Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Mon, 9 Feb 2026 11:53:48 -0500 Subject: [PATCH 04/17] fix ts in tests regarding placeholder data --- lib/common-utils.ts | 1 + lib/task-utils.ts | 9 +++---- test/lib/merge.test.ts | 59 +++++++++++++++++++++--------------------- 3 files changed, 34 insertions(+), 35 deletions(-) diff --git a/lib/common-utils.ts b/lib/common-utils.ts index 4e9eb62a0..981ed6d62 100644 --- a/lib/common-utils.ts +++ b/lib/common-utils.ts @@ -1,5 +1,6 @@ /// + export interface NycOptions { 'report-dir'?: string reporter?: string | string[] diff --git a/lib/task-utils.ts b/lib/task-utils.ts index 31907df14..d55beea60 100644 --- a/lib/task-utils.ts +++ b/lib/task-utils.ts @@ -14,7 +14,7 @@ import { fileCoveragePlaceholder, type NycOptions } from './common-utils' - +import type { CoverageMapData, FileCoverageData } from 'istanbul-lib-coverage' const log = debug('code-coverage') export interface CoverageEntry { @@ -369,7 +369,7 @@ export function includeAllFiles(nycFilename: string, nycOptions: NycOptions): vo return } - const nycCoverage: CoverageMap = JSON.parse(readFileSync(nycFilename, 'utf8')) + const nycCoverage: CoverageMapData = JSON.parse(readFileSync(nycFilename, 'utf8')) const coverageKeys = Object.keys(nycCoverage) const coveredPaths = coverageKeys.map((key) => nycCoverage[key].path.replace(/\\/g, '/') @@ -392,9 +392,8 @@ export function includeAllFiles(nycFilename: string, nycOptions: NycOptions): vo changed = true // insert placeholder object for now const placeholder = fileCoveragePlaceholder(fullPath) - // TODO: fix placeholder shape - // @ts-expect-error - placeholder doesn't match internal types yet - nycCoverage[fullPath] = placeholder + + nycCoverage[fullPath] = placeholder as FileCoverageData }) if (changed) { diff --git a/test/lib/merge.test.ts b/test/lib/merge.test.ts index f1dce7a17..05a00b74c 100644 --- a/test/lib/merge.test.ts +++ b/test/lib/merge.test.ts @@ -1,44 +1,46 @@ import { describe, it, expect, beforeAll } from 'vitest' import _ from 'lodash' -import { createCoverageMap } from 'istanbul-lib-coverage' +import { createCoverageMap, CoverageMapData, FileCoverageData } from 'istanbul-lib-coverage' import coverage from './__fixtures__/coverage.json' import { fileCoveragePlaceholder, - removePlaceholders + removePlaceholders, + type FileCoveragePlaceholder } from '../../lib/common-utils' -/** - * Extracts just the data from the coverage map object - */ -const coverageMapToCoverage = (cm: ReturnType) => { - return JSON.parse(JSON.stringify(cm)) + +// CoverageMapData apparently has a getter that attempts to write to a property that only has a getter +// this getter is accessed during expect(), so we need to normalize the data to avoid it +function normalizeCoverageData(coverageData: CoverageMapData): CoverageMapData { + return JSON.parse(JSON.stringify(coverageData)) } describe('merging coverage', () => { const filename = '/src/index.js' + const coverageFixture = coverage satisfies CoverageMapData beforeAll(() => { - expect(coverage).toHaveProperty(filename) + expect(coverageFixture).toHaveProperty(filename) }) it('combines an empty coverage object', () => { const previous = createCoverageMap({}) const coverageMap = createCoverageMap(previous) - coverageMap.merge(_.cloneDeep(coverage)) + coverageMap.merge(coverageFixture) - const merged = coverageMapToCoverage(coverageMap) + const mergedData = normalizeCoverageData(coverageMap.data) - expect(merged).toEqual(coverage) + expect(mergedData).toEqual(coverageFixture) }) it('combines the same full coverage twice', () => { - const previous = createCoverageMap(_.cloneDeep(coverage)) + const previous = createCoverageMap(_.cloneDeep(coverageFixture)) const coverageMap = createCoverageMap(previous) - coverageMap.merge(_.cloneDeep(coverage)) + coverageMap.merge(_.cloneDeep(coverageFixture)) - const merged = coverageMapToCoverage(coverageMap) + const merged = normalizeCoverageData(coverageMap.data) // it is almost the same - only the statement count has been doubled - const expected = _.cloneDeep(coverage) + const expected = _.cloneDeep(coverageFixture) expected[filename].s[0] = 2 expect(merged).toEqual(expected) }) @@ -46,10 +48,10 @@ describe('merging coverage', () => { // This test is skipped in the vitest migration. It is testing internals // of nyc that have changed with nyc 17. This needs fixed before we can // finalize v4 of @cypress/code-coverage. - it.skip('does not merge correctly placeholders', () => { - const coverageWithPlaceHolder = _.cloneDeep(coverage) + it('does not merge correctly placeholders', () => { + const coverageWithPlaceHolder = _.cloneDeep(coverageFixture) const placeholder = fileCoveragePlaceholder(filename) - coverageWithPlaceHolder[filename] = placeholder + ;(coverageWithPlaceHolder as Record)[filename] = placeholder expect(coverageWithPlaceHolder).toEqual({ [filename]: placeholder @@ -58,28 +60,25 @@ describe('merging coverage', () => { // now lets merge full info const previous = createCoverageMap(coverageWithPlaceHolder) const coverageMap = createCoverageMap(previous) - coverageMap.merge(coverage) + coverageMap.merge(coverageFixture) - const merged = coverageMapToCoverage(coverageMap) - const expected = _.cloneDeep(coverage) + const merged = normalizeCoverageData(coverageMap.data) + const expected = _.cloneDeep(coverageFixture) // the merge against the placeholder without valid statement map - // removes the statement map and sets the counter to null - expected[filename].s = { 0: null } - expected[filename].statementMap = {} - // and no hashes :( - delete expected[filename].hash - delete expected[filename]._coverageSchema + // has no hashes + delete (expected[filename] as any).hash + delete (expected[filename] as any)._coverageSchema expect(merged).toEqual(expected) }) it('removes placeholders', () => { - const inputCoverage = _.cloneDeep(coverage) + const inputCoverage = _.cloneDeep(coverageFixture) removePlaceholders(inputCoverage) - expect(inputCoverage).toEqual(coverage) + expect(inputCoverage).toEqual(coverageFixture) // add placeholder const placeholder = fileCoveragePlaceholder(filename) - inputCoverage[filename] = placeholder + ;(inputCoverage as Record)[filename] = placeholder removePlaceholders(inputCoverage) expect(inputCoverage).toEqual({}) From aa2983d1ff8ef00073d6efd3b091d070e2c01c5e Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Mon, 9 Feb 2026 12:24:41 -0500 Subject: [PATCH 05/17] correct job dependencies --- .circleci/config.yml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f73be6f21..f31819de1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -144,14 +144,16 @@ workflows: - lint: requires: - install_and_persist - - unit-test: requires: - install_and_persist - - - verify-test-apps: + - build-dist: requires: - lint + - unit-test + - verify-test-apps: + requires: + - build-dist matrix: parameters: app: @@ -171,13 +173,9 @@ workflows: - unit-tests-js - use-webpack - redirect - - windows_test - - build-dist: + - windows_test: requires: - - lint - - unit-test - - verify-test-apps - - windows_test + - build-dist - publish: context: org-npm-credentials filters: @@ -188,4 +186,5 @@ workflows: - next - dev requires: - - build-dist + - verify-test-apps + - windows_test From 44a9bcf94934bd68d0bd563a1a268efeebb8a30a Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Mon, 9 Feb 2026 12:25:37 -0500 Subject: [PATCH 06/17] adds an esm test app --- .circleci/config.yml | 1 + test-apps/esm-example/.babelrc | 4 ++++ test-apps/esm-example/.nycrc | 7 ++++++ test-apps/esm-example/README.md | 23 +++++++++++++++++++ test-apps/esm-example/cypress.config.js | 14 +++++++++++ test-apps/esm-example/cypress.config.mjs | 14 +++++++++++ test-apps/esm-example/cypress/e2e/spec.cy.js | 13 +++++++++++ .../esm-example/cypress/plugins/index.js | 6 +++++ .../esm-example/cypress/plugins/index.mjs | 6 +++++ .../esm-example/cypress/support/commands.js | 2 ++ test-apps/esm-example/cypress/support/e2e.js | 3 +++ test-apps/esm-example/cypress/support/e2e.mjs | 2 ++ test-apps/esm-example/index.html | 17 ++++++++++++++ test-apps/esm-example/main.js | 4 ++++ test-apps/esm-example/not-covered.js | 5 ++++ test-apps/esm-example/package.json | 20 ++++++++++++++++ test-apps/esm-example/second.js | 2 ++ 17 files changed, 143 insertions(+) create mode 100644 test-apps/esm-example/.babelrc create mode 100644 test-apps/esm-example/.nycrc create mode 100644 test-apps/esm-example/README.md create mode 100644 test-apps/esm-example/cypress.config.js create mode 100644 test-apps/esm-example/cypress.config.mjs create mode 100644 test-apps/esm-example/cypress/e2e/spec.cy.js create mode 100644 test-apps/esm-example/cypress/plugins/index.js create mode 100644 test-apps/esm-example/cypress/plugins/index.mjs create mode 100644 test-apps/esm-example/cypress/support/commands.js create mode 100644 test-apps/esm-example/cypress/support/e2e.js create mode 100644 test-apps/esm-example/cypress/support/e2e.mjs create mode 100644 test-apps/esm-example/index.html create mode 100644 test-apps/esm-example/main.js create mode 100644 test-apps/esm-example/not-covered.js create mode 100644 test-apps/esm-example/package.json create mode 100644 test-apps/esm-example/second.js diff --git a/.circleci/config.yml b/.circleci/config.yml index f31819de1..a539be276 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -162,6 +162,7 @@ workflows: - batch-send-coverage - before-all-visit - before-each-visit + - esm-example - exclude-files - frontend - fullstack diff --git a/test-apps/esm-example/.babelrc b/test-apps/esm-example/.babelrc new file mode 100644 index 000000000..e7884b674 --- /dev/null +++ b/test-apps/esm-example/.babelrc @@ -0,0 +1,4 @@ +{ + "plugins": ["istanbul"] +} + diff --git a/test-apps/esm-example/.nycrc b/test-apps/esm-example/.nycrc new file mode 100644 index 000000000..0cf95e28f --- /dev/null +++ b/test-apps/esm-example/.nycrc @@ -0,0 +1,7 @@ +{ + "all": true, + "include": [ + "*.js" + ] +} + diff --git a/test-apps/esm-example/README.md b/test-apps/esm-example/README.md new file mode 100644 index 000000000..3862df1d8 --- /dev/null +++ b/test-apps/esm-example/README.md @@ -0,0 +1,23 @@ +# ESM Example + +This test app demonstrates using `@cypress/code-coverage` with ES Module syntax (`import`/`export`). + +## Key differences from CommonJS examples: + +1. **package.json**: Uses `"type": "module"` to enable ES modules (all `.js` files are treated as ES modules) +2. **cypress.config.js**: Uses `import`/`export` syntax instead of `require`/`module.exports` +3. **cypress/plugins/index.js**: Uses ES module `import` syntax to import the code coverage plugin: + ```javascript + import codecov from '@cypress/code-coverage/plugins' + ``` +4. **cypress/support/e2e.js**: Uses ES module `import` syntax for support files: + ```javascript + import '@cypress/code-coverage/support' + ``` + +## Usage + +```bash +npm install +npm test +``` diff --git a/test-apps/esm-example/cypress.config.js b/test-apps/esm-example/cypress.config.js new file mode 100644 index 000000000..48f095d7a --- /dev/null +++ b/test-apps/esm-example/cypress.config.js @@ -0,0 +1,14 @@ +import { defineConfig } from 'cypress' +import codecov from '@cypress/code-coverage/plugins' + +export default defineConfig({ + allowCypressEnv: false, + fixturesFolder: false, + e2e: { + setupNodeEvents(on, config) { + return codecov(on, config) + }, + baseUrl: 'http://localhost:1234' + } +}) + diff --git a/test-apps/esm-example/cypress.config.mjs b/test-apps/esm-example/cypress.config.mjs new file mode 100644 index 000000000..48f095d7a --- /dev/null +++ b/test-apps/esm-example/cypress.config.mjs @@ -0,0 +1,14 @@ +import { defineConfig } from 'cypress' +import codecov from '@cypress/code-coverage/plugins' + +export default defineConfig({ + allowCypressEnv: false, + fixturesFolder: false, + e2e: { + setupNodeEvents(on, config) { + return codecov(on, config) + }, + baseUrl: 'http://localhost:1234' + } +}) + diff --git a/test-apps/esm-example/cypress/e2e/spec.cy.js b/test-apps/esm-example/cypress/e2e/spec.cy.js new file mode 100644 index 000000000..945485e6e --- /dev/null +++ b/test-apps/esm-example/cypress/e2e/spec.cy.js @@ -0,0 +1,13 @@ +/// +it('works', () => { + cy.visit('/') + cy.contains('Page body') + + cy.window() + .invoke('reverse', 'super') + .should('equal', 'repus') + + // application's code should be instrumented + cy.window().should('have.property', '__coverage__') +}) + diff --git a/test-apps/esm-example/cypress/plugins/index.js b/test-apps/esm-example/cypress/plugins/index.js new file mode 100644 index 000000000..47c517d7e --- /dev/null +++ b/test-apps/esm-example/cypress/plugins/index.js @@ -0,0 +1,6 @@ +import codecov from '@cypress/code-coverage/plugins' + +export default (on, config) => { + return codecov(on, config) +} + diff --git a/test-apps/esm-example/cypress/plugins/index.mjs b/test-apps/esm-example/cypress/plugins/index.mjs new file mode 100644 index 000000000..47c517d7e --- /dev/null +++ b/test-apps/esm-example/cypress/plugins/index.mjs @@ -0,0 +1,6 @@ +import codecov from '@cypress/code-coverage/plugins' + +export default (on, config) => { + return codecov(on, config) +} + diff --git a/test-apps/esm-example/cypress/support/commands.js b/test-apps/esm-example/cypress/support/commands.js new file mode 100644 index 000000000..78ceeefb8 --- /dev/null +++ b/test-apps/esm-example/cypress/support/commands.js @@ -0,0 +1,2 @@ +// Custom commands can be added here if needed + diff --git a/test-apps/esm-example/cypress/support/e2e.js b/test-apps/esm-example/cypress/support/e2e.js new file mode 100644 index 000000000..6419f3e4d --- /dev/null +++ b/test-apps/esm-example/cypress/support/e2e.js @@ -0,0 +1,3 @@ +import './commands' +import '@cypress/code-coverage/support' + diff --git a/test-apps/esm-example/cypress/support/e2e.mjs b/test-apps/esm-example/cypress/support/e2e.mjs new file mode 100644 index 000000000..71316ef0e --- /dev/null +++ b/test-apps/esm-example/cypress/support/e2e.mjs @@ -0,0 +1,2 @@ +import '@cypress/code-coverage/support' + diff --git a/test-apps/esm-example/index.html b/test-apps/esm-example/index.html new file mode 100644 index 000000000..993f0c189 --- /dev/null +++ b/test-apps/esm-example/index.html @@ -0,0 +1,17 @@ + + Page body + + + + diff --git a/test-apps/esm-example/main.js b/test-apps/esm-example/main.js new file mode 100644 index 000000000..a25288ac3 --- /dev/null +++ b/test-apps/esm-example/main.js @@ -0,0 +1,4 @@ +window.add = (a, b) => a + b + +window.sub = (a, b) => a - b + diff --git a/test-apps/esm-example/not-covered.js b/test-apps/esm-example/not-covered.js new file mode 100644 index 000000000..a2425b198 --- /dev/null +++ b/test-apps/esm-example/not-covered.js @@ -0,0 +1,5 @@ +// This file is not covered by tests +window.notCovered = () => { + return 'not covered' +} + diff --git a/test-apps/esm-example/package.json b/test-apps/esm-example/package.json new file mode 100644 index 000000000..94ade313b --- /dev/null +++ b/test-apps/esm-example/package.json @@ -0,0 +1,20 @@ +{ + "name": "example-esm", + "description": "ES Module example using import syntax", + "private": true, + "type": "module", + "scripts": { + "cy:run": "cypress run", + "start": "parcel serve index.html", + "start:windows": "npx bin-up parcel serve index.html", + "pretest": "rimraf .nyc_output .cache coverage dist", + "test": "start-test 1234 cy:run", + "coverage:verify": "npx nyc report --check-coverage true --lines 100", + "coverage:check-files": "check-coverage main.js && check-coverage second.js && check-coverage not-covered.js && check-coverage cypress.config.js && only-covered --from coverage/coverage-final.json main.js second.js not-covered.js cypress.config.js" + }, + "devDependencies": { + "@babel/core": "^7.22.15", + "babel-plugin-istanbul": "6.1.1" + } +} + diff --git a/test-apps/esm-example/second.js b/test-apps/esm-example/second.js new file mode 100644 index 000000000..f141778dd --- /dev/null +++ b/test-apps/esm-example/second.js @@ -0,0 +1,2 @@ +window.reverse = (str) => str.split('').reverse().join('') + From 47ff4dfb67449e5bf91f229b45619ed4e0bd66b6 Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Mon, 9 Feb 2026 12:26:01 -0500 Subject: [PATCH 07/17] fix exports to work correctly with esm --- lib/plugins.ts | 4 ++-- lib/task.ts | 2 +- lib/use-babelrc.ts | 2 +- middleware/express.ts | 2 +- middleware/hapi.ts | 2 +- middleware/nextjs.ts | 2 +- tsconfig.json | 7 ------- 7 files changed, 7 insertions(+), 14 deletions(-) diff --git a/lib/plugins.ts b/lib/plugins.ts index 1c8b5d99f..11df22226 100644 --- a/lib/plugins.ts +++ b/lib/plugins.ts @@ -1,6 +1,6 @@ /// /// -import registerCodeCoverageTasks from './task' +import registerCodeCoverageTasks = require('./task') // common Cypress plugin file you can point at to have the // code coverage tasks registered correctly. From your "cypress.json" file @@ -9,7 +9,7 @@ import registerCodeCoverageTasks from './task' // "supportFile": "@cypress/code-coverage/support" // } // -export default function plugins(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions): Cypress.PluginConfigOptions { +export = function plugins(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions): Cypress.PluginConfigOptions { registerCodeCoverageTasks(on, config) // IMPORTANT to return the config object with any changes return config diff --git a/lib/task.ts b/lib/task.ts index 7b1f13a9d..e682f83a5 100644 --- a/lib/task.ts +++ b/lib/task.ts @@ -236,7 +236,7 @@ const tasks = { } ``` */ -export default function registerCodeCoverageTasks( +export = function registerCodeCoverageTasks( on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions ): Cypress.PluginConfigOptions { diff --git a/lib/use-babelrc.ts b/lib/use-babelrc.ts index 179a53a91..1046209af 100644 --- a/lib/use-babelrc.ts +++ b/lib/use-babelrc.ts @@ -12,5 +12,5 @@ if (defaults.webpackOptions?.module?.rules?.[0]?.use?.[0]?.options?.presets) { // @ts-ignore - we know that the use property exists const preprocessor = webpackPreprocessor(defaults) // Type assertion to avoid exposing private FileEvent type -export default preprocessor as (file: Cypress.FileObject) => string | Promise +export = preprocessor as (file: Cypress.FileObject) => string | Promise diff --git a/middleware/express.ts b/middleware/express.ts index 60e840e99..bd8ab88e7 100644 --- a/middleware/express.ts +++ b/middleware/express.ts @@ -1,7 +1,7 @@ import type { Express } from 'express' // for Express.js -export default function expressMiddleware(app: Express): void { +export = function expressMiddleware(app: Express): void { // expose "GET __coverage__" endpoint that just returns // global coverage information (if the application has been instrumented) app.get('/__coverage__', (req, res) => { diff --git a/middleware/hapi.ts b/middleware/hapi.ts index 38594eef8..0b47a88c4 100644 --- a/middleware/hapi.ts +++ b/middleware/hapi.ts @@ -1,7 +1,7 @@ import type { Server } from 'hapi' // for Hapi.js // eslint-disable-next-line @typescript-eslint/no-explicit-any -export default function hapiMiddleware(server: Server): void { +export = function hapiMiddleware(server: Server): void { // expose "GET __coverage__" endpoint that just returns // global coverage information (if the application has been instrumented) diff --git a/middleware/nextjs.ts b/middleware/nextjs.ts index 4d3fdddc8..ff370d54e 100644 --- a/middleware/nextjs.ts +++ b/middleware/nextjs.ts @@ -21,7 +21,7 @@ * @see https://github.com/cypress-io/code-coverage */ -export default function returnCodeCoverageNext(req: any, res: any): void { +export = function returnCodeCoverageNext(req: any, res: any): void { // only GET is supported res.status(200).json({ coverage: (global as typeof globalThis & { __coverage__?: unknown }).__coverage__ || null diff --git a/tsconfig.json b/tsconfig.json index 5bd153ce6..610b38d43 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,18 +13,11 @@ "declaration": true, "declarationMap": true, "sourceMap": true, - "moduleResolution": "node", "types": ["node", "cypress"] }, "include": [ "lib/**/*", "middleware/**/*", ], - "exclude": [ - "test-apps/**/*", - "node_modules", - "dist", - "coverage" - ] } From 6b3b1100c6f18ba5d6c27face71762af50106f21 Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Mon, 9 Feb 2026 12:30:06 -0500 Subject: [PATCH 08/17] correct prettier args --- lib/common-utils.ts | 14 +++--- lib/plugins.ts | 6 ++- lib/support-utils.ts | 5 ++- lib/support.ts | 20 ++++++--- lib/task-utils.ts | 40 +++++++++++------ lib/task.ts | 28 ++++++------ lib/use-babelrc.ts | 3 +- middleware/express.ts | 5 ++- middleware/hapi.ts | 7 ++- middleware/nextjs.ts | 5 ++- package.json | 4 +- test/lib/combine.test.ts | 1 - test/lib/filtering.test.ts | 73 +++++++++++++++++++++++-------- test/lib/fix-source-paths.test.ts | 1 - test/lib/merge.test.ts | 24 +++++++--- 15 files changed, 156 insertions(+), 80 deletions(-) diff --git a/lib/common-utils.ts b/lib/common-utils.ts index 981ed6d62..0deb6164d 100644 --- a/lib/common-utils.ts +++ b/lib/common-utils.ts @@ -1,6 +1,5 @@ /// - export interface NycOptions { 'report-dir'?: string reporter?: string | string[] @@ -53,7 +52,9 @@ export interface FileCoveragePlaceholder { * * @param fullPath Filename */ -export function fileCoveragePlaceholder(fullPath: string): FileCoveragePlaceholder { +export function fileCoveragePlaceholder( + fullPath: string +): FileCoveragePlaceholder { return { path: fullPath, statementMap: {}, @@ -65,7 +66,9 @@ export function fileCoveragePlaceholder(fullPath: string): FileCoveragePlacehold } } -function isPlaceholder(entry: FileCoveragePlaceholder | { hash?: string }): boolean { +function isPlaceholder( + entry: FileCoveragePlaceholder | { hash?: string } +): boolean { // when the file has been instrumented, its entry has "hash" property return !('hash' in entry) } @@ -74,11 +77,12 @@ function isPlaceholder(entry: FileCoveragePlaceholder | { hash?: string }): bool * Given a coverage object with potential placeholder entries * inserted instead of covered files, removes them. Modifies the object in place */ -export function removePlaceholders(coverage: Record): void { +export function removePlaceholders( + coverage: Record +): void { Object.keys(coverage).forEach((key) => { if (isPlaceholder(coverage[key])) { delete coverage[key] } }) } - diff --git a/lib/plugins.ts b/lib/plugins.ts index 11df22226..760fdd393 100644 --- a/lib/plugins.ts +++ b/lib/plugins.ts @@ -9,9 +9,11 @@ import registerCodeCoverageTasks = require('./task') // "supportFile": "@cypress/code-coverage/support" // } // -export = function plugins(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions): Cypress.PluginConfigOptions { +export = function plugins( + on: Cypress.PluginEvents, + config: Cypress.PluginConfigOptions +): Cypress.PluginConfigOptions { registerCodeCoverageTasks(on, config) // IMPORTANT to return the config object with any changes return config } - diff --git a/lib/support-utils.ts b/lib/support-utils.ts index a7ed554e5..a7cf8c851 100644 --- a/lib/support-utils.ts +++ b/lib/support-utils.ts @@ -69,7 +69,9 @@ function getCypressExcludePatterns( spec: typeof Cypress.spec ): string[] { const testFilePattern = config('specPattern') - const codeCoverageConfig = expose().codeCoverage as { exclude?: string | string[] } | undefined + const codeCoverageConfig = expose().codeCoverage as + | { exclude?: string | string[] } + | undefined const excludePattern = codeCoverageConfig?.exclude let testFilePatterns: string[] = [] @@ -152,4 +154,3 @@ export function getSendCoverageBatchSize(): number | null { const isValid = !isNaN(parsedBatchSize) && parsedBatchSize > 0 return isValid ? parsedBatchSize : null } - diff --git a/lib/support.ts b/lib/support.ts index 6b38139bb..2707dc3de 100644 --- a/lib/support.ts +++ b/lib/support.ts @@ -40,7 +40,10 @@ function sendCoverage(coverage: unknown, pathname = '/'): void { * Sends collected code coverage object to the backend code * in batches via "cy.task". */ -function sendBatchCoverage(totalCoverage: Record, batchSize: number): void { +function sendBatchCoverage( + totalCoverage: Record, + batchSize: number +): void { const keys = Object.keys(totalCoverage) for (let i = 0; i < keys.length; i += batchSize) { @@ -72,7 +75,8 @@ function registerHooks(): void { const hasE2ECoverage = (): boolean => Boolean(windowCoverageObjects.length) // @ts-ignore - __coverage__ is a global added by instrumentation - const hasUnitTestCoverage = (): boolean => Boolean((window as typeof window & { __coverage__?: unknown }).__coverage__) + const hasUnitTestCoverage = (): boolean => + Boolean((window as typeof window & { __coverage__?: unknown }).__coverage__) before(() => { // we need to reset the coverage when running @@ -112,7 +116,9 @@ function registerHooks(): void { // https://github.com/cypress-io/cypress/issues/20753 if (win) { // @ts-ignore - __coverage__ is added by instrumentation - applicationSourceCoverage = (win as typeof win & { __coverage__?: unknown }).__coverage__ + applicationSourceCoverage = ( + win as typeof win & { __coverage__?: unknown } + ).__coverage__ } } catch { // ignore cross-origin errors @@ -183,7 +189,8 @@ function registerHooks(): void { // there might be server-side code coverage information // we should grab it once after all tests finish // @ts-ignore - state is a runtime property - const baseUrl = Cypress.config('baseUrl') || (cy.state('window') as Window).origin + const baseUrl = + Cypress.config('baseUrl') || (cy.state('window') as Window).origin // @ts-ignore - proxyUrl is a runtime property const runningEndToEndTests = baseUrl !== Cypress.config('proxyUrl') const expectFrontendCoverageOnly = Cypress._.get( @@ -256,7 +263,9 @@ function registerHooks(): void { // NOTE: spec iframe is NOT reset between the tests, so we can grab // the coverage information only once after all tests have finished // @ts-ignore - __coverage__ is added by instrumentation - const unitTestCoverage = (window as typeof window & { __coverage__?: unknown }).__coverage__ + const unitTestCoverage = ( + window as typeof window & { __coverage__?: unknown } + ).__coverage__ if (unitTestCoverage) { sendCoverage(unitTestCoverage, 'unit') } @@ -308,4 +317,3 @@ if (exposedValues.coverage === false) { } else { registerHooks() } - diff --git a/lib/task-utils.ts b/lib/task-utils.ts index d55beea60..9d7b4bd4f 100644 --- a/lib/task-utils.ts +++ b/lib/task-utils.ts @@ -48,9 +48,13 @@ export function readNycOptions(workingDirectory: string): NycOptions { let nycrcYaml: NycOptions = {} if (existsSync(nycrcYamlFilename)) { try { - nycrcYaml = yaml.load(readFileSync(nycrcYamlFilename, 'utf8')) as NycOptions + nycrcYaml = yaml.load( + readFileSync(nycrcYamlFilename, 'utf8') + ) as NycOptions } catch (error) { - throw new Error(`Failed to load .nycrc.yaml: ${error instanceof Error ? error.message : String(error)}`) + throw new Error( + `Failed to load .nycrc.yaml: ${error instanceof Error ? error.message : String(error)}` + ) } } @@ -60,7 +64,9 @@ export function readNycOptions(workingDirectory: string): NycOptions { try { nycrcYml = yaml.load(readFileSync(nycrcYmlFilename, 'utf8')) as NycOptions } catch (error) { - throw new Error(`Failed to load .nycrc.yml: ${error instanceof Error ? error.message : String(error)}`) + throw new Error( + `Failed to load .nycrc.yml: ${error instanceof Error ? error.message : String(error)}` + ) } } @@ -70,7 +76,9 @@ export function readNycOptions(workingDirectory: string): NycOptions { try { nycConfig = require(nycConfigFilename) as NycOptions } catch (error) { - throw new Error(`Failed to load nyc.config.js: ${error instanceof Error ? error.message : String(error)}`) + throw new Error( + `Failed to load nyc.config.js: ${error instanceof Error ? error.message : String(error)}` + ) } } @@ -80,7 +88,9 @@ export function readNycOptions(workingDirectory: string): NycOptions { try { nycConfigCommonJs = require(nycConfigCommonJsFilename) as NycOptions } catch (error) { - throw new Error(`Failed to load nyc.config.cjs: ${error instanceof Error ? error.message : String(error)}`) + throw new Error( + `Failed to load nyc.config.cjs: ${error instanceof Error ? error.message : String(error)}` + ) } } @@ -99,7 +109,9 @@ export function readNycOptions(workingDirectory: string): NycOptions { return nycOptions } -export function checkAllPathsNotFound(nycFilename: string): boolean | undefined { +export function checkAllPathsNotFound( + nycFilename: string +): boolean | undefined { const nycCoverage: CoverageMap = JSON.parse(readFileSync(nycFilename, 'utf8')) const coverageKeys = Object.keys(nycCoverage) @@ -113,11 +125,7 @@ export function checkAllPathsNotFound(nycFilename: string): boolean | undefined return !existsSync(coverage.path) }) - log( - 'in file %s all files are not found? %o', - nycFilename, - allFilesAreMissing - ) + log('in file %s all files are not found? %o', nycFilename, allFilesAreMissing) return allFilesAreMissing } @@ -353,7 +361,10 @@ function findSourceFiles(nycOptions: NycOptions): string[] { * * @see https://github.com/cypress-io/code-coverage/issues/207 */ -export function includeAllFiles(nycFilename: string, nycOptions: NycOptions): void { +export function includeAllFiles( + nycFilename: string, + nycOptions: NycOptions +): void { if (!nycOptions.all) { log('NYC "all" option is not set, skipping including all files') return @@ -369,7 +380,9 @@ export function includeAllFiles(nycFilename: string, nycOptions: NycOptions): vo return } - const nycCoverage: CoverageMapData = JSON.parse(readFileSync(nycFilename, 'utf8')) + const nycCoverage: CoverageMapData = JSON.parse( + readFileSync(nycFilename, 'utf8') + ) const coverageKeys = Object.keys(nycCoverage) const coveredPaths = coverageKeys.map((key) => nycCoverage[key].path.replace(/\\/g, '/') @@ -407,4 +420,3 @@ export function includeAllFiles(nycFilename: string, nycOptions: NycOptions): vo ) } } - diff --git a/lib/task.ts b/lib/task.ts index e682f83a5..3f080d991 100644 --- a/lib/task.ts +++ b/lib/task.ts @@ -1,5 +1,8 @@ /// -import { createCoverageMap, CoverageMap as IstanbulCoverageMap } from 'istanbul-lib-coverage' +import { + createCoverageMap, + CoverageMap as IstanbulCoverageMap +} from 'istanbul-lib-coverage' import { join, resolve } from 'path' import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs' import execa from 'execa' @@ -41,7 +44,9 @@ const nycReportOptions = (function getNycOption() { } if (nycReportOptions['temp-dir']) { - nycReportOptions['temp-dir'] = resolve(nycReportOptions['temp-dir'] as string) + nycReportOptions['temp-dir'] = resolve( + nycReportOptions['temp-dir'] as string + ) } else { nycReportOptions['temp-dir'] = join(processWorkingDirectory, '.nyc_output') } @@ -49,7 +54,9 @@ const nycReportOptions = (function getNycOption() { nycReportOptions.tempDir = nycReportOptions['temp-dir'] if (nycReportOptions['report-dir']) { - nycReportOptions['report-dir'] = resolve(nycReportOptions['report-dir'] as string) + nycReportOptions['report-dir'] = resolve( + nycReportOptions['report-dir'] as string + ) } // seems nyc API really is using camel cased version nycReportOptions.reportDir = nycReportOptions['report-dir'] @@ -83,13 +90,10 @@ function maybePrintFinalCoverageFiles(folder: string): void { } log('Final coverage in %s', jsonReportFilename) - const finalCoverage: Record }> = JSON.parse(readFileSync(jsonReportFilename, 'utf8')) + const finalCoverage: Record }> = + JSON.parse(readFileSync(jsonReportFilename, 'utf8')) const finalCoverageKeys = Object.keys(finalCoverage) - log( - 'There are %d key(s) in %s', - finalCoverageKeys.length, - jsonReportFilename - ) + log('There are %d key(s) in %s', finalCoverageKeys.length, jsonReportFilename) finalCoverageKeys.forEach((key) => { const s = finalCoverage[key].s || {} @@ -209,10 +213,7 @@ const tasks = { const returnReportFolder = (): string => { const reportFolder = nycReportOptions['report-dir'] as string - log( - 'after reporting, returning the report folder name %s', - reportFolder - ) + log('after reporting, returning the report folder name %s', reportFolder) maybePrintFinalCoverageFiles(reportFolder) @@ -248,4 +249,3 @@ export = function registerCodeCoverageTasks( return config } - diff --git a/lib/use-babelrc.ts b/lib/use-babelrc.ts index 1046209af..271782ebc 100644 --- a/lib/use-babelrc.ts +++ b/lib/use-babelrc.ts @@ -3,7 +3,7 @@ import webpackPreprocessor from '@cypress/webpack-preprocessor' const defaults = webpackPreprocessor.defaultOptions -// remove presets so the babelrc file will be used +// remove presets so the babelrc file will be used // @ts-ignore - we know that the use property exists if (defaults.webpackOptions?.module?.rules?.[0]?.use?.[0]?.options?.presets) { // @ts-ignore - we know that the use property exists @@ -13,4 +13,3 @@ if (defaults.webpackOptions?.module?.rules?.[0]?.use?.[0]?.options?.presets) { const preprocessor = webpackPreprocessor(defaults) // Type assertion to avoid exposing private FileEvent type export = preprocessor as (file: Cypress.FileObject) => string | Promise - diff --git a/middleware/express.ts b/middleware/express.ts index bd8ab88e7..c94b4c344 100644 --- a/middleware/express.ts +++ b/middleware/express.ts @@ -6,8 +6,9 @@ export = function expressMiddleware(app: Express): void { // global coverage information (if the application has been instrumented) app.get('/__coverage__', (req, res) => { res.json({ - coverage: (global as typeof globalThis & { __coverage__?: unknown }).__coverage__ || null + coverage: + (global as typeof globalThis & { __coverage__?: unknown }) + .__coverage__ || null }) }) } - diff --git a/middleware/hapi.ts b/middleware/hapi.ts index 0b47a88c4..58b434a51 100644 --- a/middleware/hapi.ts +++ b/middleware/hapi.ts @@ -10,8 +10,11 @@ export = function hapiMiddleware(server: Server): void { method: 'GET', path: '/__coverage__', handler() { - return { coverage: (global as typeof globalThis & { __coverage__?: unknown }).__coverage__ || null } + return { + coverage: + (global as typeof globalThis & { __coverage__?: unknown }) + .__coverage__ || null + } } }) } - diff --git a/middleware/nextjs.ts b/middleware/nextjs.ts index ff370d54e..76d2c7db6 100644 --- a/middleware/nextjs.ts +++ b/middleware/nextjs.ts @@ -24,7 +24,8 @@ export = function returnCodeCoverageNext(req: any, res: any): void { // only GET is supported res.status(200).json({ - coverage: (global as typeof globalThis & { __coverage__?: unknown }).__coverage__ || null + coverage: + (global as typeof globalThis & { __coverage__?: unknown }).__coverage__ || + null }) } - diff --git a/package.json b/package.json index 251c456b9..a77df396e 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,8 @@ "coverage": "vitest run --coverage", "report:coverage": "nyc report --reporter=html", "dev:no:coverage": "start-test 1234 'cypress open --expose coverage=false'", - "format": "prettier --write '*.{js,ts}'", - "format:check": "prettier --check '*.{js,ts}'", + "format": "prettier --write '{lib,middleware,test}/**/*.{js,ts}'", + "format:check": "prettier --check '{lib,middleware,test}/**/*.{js,ts}'", "check:markdown": "find *.md -exec npx markdown-link-check {} \\;", "effective:config": "circleci config process .circleci/config.yml | sed /^#/d", "apps:test:coverage:verify": "./scripts/test-apps-and-verify-coverage.sh" diff --git a/test/lib/combine.test.ts b/test/lib/combine.test.ts index 0598cd3f6..e62f27082 100644 --- a/test/lib/combine.test.ts +++ b/test/lib/combine.test.ts @@ -94,4 +94,3 @@ describe('Combine NYC options', () => { }) }) }) - diff --git a/test/lib/filtering.test.ts b/test/lib/filtering.test.ts index d7e7b0e9f..b6947a065 100644 --- a/test/lib/filtering.test.ts +++ b/test/lib/filtering.test.ts @@ -6,22 +6,18 @@ import { filterFilesFromCoverage } from '../../lib/support-utils' vi.stubGlobal('Cypress', { minimatch, - _, + _ }) describe('minimatch', () => { it('string matches', () => { - expect( - minimatch('/user/app/src/codeA.js', '/user/app/src/codeA.js') - ).toBe(true) + expect(minimatch('/user/app/src/codeA.js', '/user/app/src/codeA.js')).toBe( + true + ) - expect( - minimatch('/user/app/src/codeA.js', 'codeA.js') - ).toBe(false) + expect(minimatch('/user/app/src/codeA.js', 'codeA.js')).toBe(false) - expect( - minimatch('/user/app/src/codeA.js', '**/codeA.js') - ).toBe(true) + expect(minimatch('/user/app/src/codeA.js', '**/codeA.js')).toBe(true) }) }) @@ -56,7 +52,12 @@ describe('filtering specs', () => { '/user/app/src/specA.cy.js': {}, '/user/app/src/specB.cy.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) + const result = filterFilesFromCoverage( + totalCoverage, + config as any, + expose as any, + spec as any + ) expect(result).toEqual({ '/user/app/src/specB.cy.js': {} }) @@ -71,7 +72,12 @@ describe('filtering specs', () => { '/user/app/src/specA.cy.js': {}, '/user/app/src/specB.cy.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) + const result = filterFilesFromCoverage( + totalCoverage, + config as any, + expose as any, + spec as any + ) expect(result).toEqual({ '/user/app/src/specB.cy.js': {} }) @@ -89,7 +95,12 @@ describe('filtering specs', () => { '/user/app/src/codeA.js': {}, '/user/app/src/codeB.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) + const result = filterFilesFromCoverage( + totalCoverage, + config as any, + expose as any, + spec as any + ) expect(result).toEqual({ '/user/app/src/codeA.js': {}, '/user/app/src/codeB.js': {} @@ -106,7 +117,12 @@ describe('filtering specs', () => { '/user/app/src/codeA.js': {}, '/user/app/src/codeB.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) + const result = filterFilesFromCoverage( + totalCoverage, + config as any, + expose as any, + spec as any + ) expect(result).toEqual({ '/user/app/src/codeA.js': {} }) @@ -122,7 +138,12 @@ describe('filtering specs', () => { '/user/app/src/codeA.js': {}, '/user/app/src/codeB.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) + const result = filterFilesFromCoverage( + totalCoverage, + config as any, + expose as any, + spec as any + ) expect(result).toEqual({}) }) @@ -139,7 +160,12 @@ describe('filtering specs', () => { '/user/app/cypress/integration/spec1.cy.js': {}, '/user/app/cypress/integration/spec2.cy.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) + const result = filterFilesFromCoverage( + totalCoverage, + config as any, + expose as any, + spec as any + ) expect(result).toEqual({ '/user/app/src/codeA.js': {}, '/user/app/src/codeB.js': {} @@ -158,7 +184,12 @@ describe('filtering specs', () => { // This file should be included in coverage 'src/my-code.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) + const result = filterFilesFromCoverage( + totalCoverage, + config as any, + expose as any, + spec as any + ) expect(result).toEqual({ 'src/my-code.js': {} }) @@ -184,7 +215,12 @@ describe('filtering specs', () => { '/user/app/cypress/c.js': {}, 'src/my-code.js': {} } - const result = filterFilesFromCoverage(totalCoverage, config as any, expose as any, spec as any) + const result = filterFilesFromCoverage( + totalCoverage, + config as any, + expose as any, + spec as any + ) expect(result).toEqual({ '/user/app/cypress/c.js': {}, 'src/my-code.js': {} @@ -192,4 +228,3 @@ describe('filtering specs', () => { }) }) }) - diff --git a/test/lib/fix-source-paths.test.ts b/test/lib/fix-source-paths.test.ts index 47f3e5506..f9ed36cff 100644 --- a/test/lib/fix-source-paths.test.ts +++ b/test/lib/fix-source-paths.test.ts @@ -35,4 +35,3 @@ describe('fixSourcePaths', () => { }) }) }) - diff --git a/test/lib/merge.test.ts b/test/lib/merge.test.ts index 05a00b74c..7f934e1ac 100644 --- a/test/lib/merge.test.ts +++ b/test/lib/merge.test.ts @@ -1,6 +1,10 @@ import { describe, it, expect, beforeAll } from 'vitest' import _ from 'lodash' -import { createCoverageMap, CoverageMapData, FileCoverageData } from 'istanbul-lib-coverage' +import { + createCoverageMap, + CoverageMapData, + FileCoverageData +} from 'istanbul-lib-coverage' import coverage from './__fixtures__/coverage.json' import { fileCoveragePlaceholder, @@ -8,7 +12,6 @@ import { type FileCoveragePlaceholder } from '../../lib/common-utils' - // CoverageMapData apparently has a getter that attempts to write to a property that only has a getter // this getter is accessed during expect(), so we need to normalize the data to avoid it function normalizeCoverageData(coverageData: CoverageMapData): CoverageMapData { @@ -51,7 +54,12 @@ describe('merging coverage', () => { it('does not merge correctly placeholders', () => { const coverageWithPlaceHolder = _.cloneDeep(coverageFixture) const placeholder = fileCoveragePlaceholder(filename) - ;(coverageWithPlaceHolder as Record)[filename] = placeholder + ;( + coverageWithPlaceHolder as Record< + string, + FileCoveragePlaceholder | FileCoverageData + > + )[filename] = placeholder expect(coverageWithPlaceHolder).toEqual({ [filename]: placeholder @@ -65,7 +73,7 @@ describe('merging coverage', () => { const merged = normalizeCoverageData(coverageMap.data) const expected = _.cloneDeep(coverageFixture) // the merge against the placeholder without valid statement map - // has no hashes + // has no hashes delete (expected[filename] as any).hash delete (expected[filename] as any)._coverageSchema expect(merged).toEqual(expected) @@ -78,10 +86,14 @@ describe('merging coverage', () => { // add placeholder const placeholder = fileCoveragePlaceholder(filename) - ;(inputCoverage as Record)[filename] = placeholder + ;( + inputCoverage as Record< + string, + FileCoveragePlaceholder | FileCoverageData + > + )[filename] = placeholder removePlaceholders(inputCoverage) expect(inputCoverage).toEqual({}) }) }) - From 828b6c372ba0d7118ab0f46d8f9b01557804b3ac Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Mon, 9 Feb 2026 12:33:31 -0500 Subject: [PATCH 09/17] fix persist --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a539be276..d936383a5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -77,7 +77,7 @@ jobs: - run: npm run build - persist_to_workspace: paths: - - dist + - project/dist root: ~/ publish: From 18a3c6de3f77b0566c39df778c776e362c3951db Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Mon, 9 Feb 2026 12:40:19 -0500 Subject: [PATCH 10/17] fix ts ignore --- lib/support.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/support.ts b/lib/support.ts index 2707dc3de..ef73a29e1 100644 --- a/lib/support.ts +++ b/lib/support.ts @@ -188,8 +188,9 @@ function registerHooks(): void { // there might be server-side code coverage information // we should grab it once after all tests finish - // @ts-ignore - state is a runtime property + const baseUrl = + // @ts-ignore - state is a runtime property Cypress.config('baseUrl') || (cy.state('window') as Window).origin // @ts-ignore - proxyUrl is a runtime property const runningEndToEndTests = baseUrl !== Cypress.config('proxyUrl') From 31d36efe45621d03f0205d289ba53bc4dfe0c375 Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Mon, 9 Feb 2026 13:07:50 -0500 Subject: [PATCH 11/17] rm conflicting cy cfg in esm test app --- test-apps/esm-example/cypress.config.mjs | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 test-apps/esm-example/cypress.config.mjs diff --git a/test-apps/esm-example/cypress.config.mjs b/test-apps/esm-example/cypress.config.mjs deleted file mode 100644 index 48f095d7a..000000000 --- a/test-apps/esm-example/cypress.config.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from 'cypress' -import codecov from '@cypress/code-coverage/plugins' - -export default defineConfig({ - allowCypressEnv: false, - fixturesFolder: false, - e2e: { - setupNodeEvents(on, config) { - return codecov(on, config) - }, - baseUrl: 'http://localhost:1234' - } -}) - From 4b0d59c7923c006f3c785d4af944306c339f10d8 Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Mon, 9 Feb 2026 13:12:49 -0500 Subject: [PATCH 12/17] windows build needs to happen separately --- .circleci/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index d936383a5..ebdbaec57 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -48,6 +48,9 @@ jobs: - run: name: Install deps for code coverage command: npm ci + - run: + name: Build project + command: npm run build - cypress/run-tests: # no-workspace: true start-command: npm run start:windows --prefix test-apps/all-files From acb081696d3aec74fd50e16a203ae5dbd0cdf505 Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Mon, 9 Feb 2026 13:38:27 -0500 Subject: [PATCH 13/17] ensure test app runs npm i before cy in windows --- .circleci/config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index ebdbaec57..79c9e9605 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -51,6 +51,10 @@ jobs: - run: name: Build project command: npm run build + - run: + name: Install test app deps + command: npm i + working_directory: test-apps/all-files - cypress/run-tests: # no-workspace: true start-command: npm run start:windows --prefix test-apps/all-files From ab0999a49204deabde3f2415cddb331e95babbad Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Tue, 10 Feb 2026 09:46:36 -0500 Subject: [PATCH 14/17] js-yaml back to deps --- package-lock.json | 6 ++---- package.json | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index e4e68c4f5..d6badb24f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "debug": "4.4.0", "execa": "4.1.0", "istanbul-lib-coverage": "^3.0.0", + "js-yaml": "^4.1.1", "nyc": "^17.1.0", "tinyglobby": "^0.2.14" }, @@ -37,7 +38,6 @@ "console-log-div": "0.6.3", "cypress": "^15.10.0", "express": "^4.18.2", - "js-yaml": "^4.1.1", "lodash": "4.17.21", "markdown-link-check": "3.13.6", "minimatch": "^10.0.0", @@ -12877,7 +12877,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -12889,8 +12888,7 @@ "node_modules/js-yaml/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/jsbn": { "version": "0.1.1", diff --git a/package.json b/package.json index a77df396e..8304496ce 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "debug": "4.4.0", "execa": "4.1.0", "istanbul-lib-coverage": "^3.0.0", + "js-yaml": "^4.1.1", "nyc": "^17.1.0", "tinyglobby": "^0.2.14" }, @@ -94,7 +95,6 @@ "console-log-div": "0.6.3", "cypress": "^15.10.0", "express": "^4.18.2", - "js-yaml": "^4.1.1", "lodash": "4.17.21", "markdown-link-check": "3.13.6", "minimatch": "^10.0.0", From b657ab27e832c6c18a819cea4a9d3627a87daf50 Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Tue, 10 Feb 2026 11:18:36 -0500 Subject: [PATCH 15/17] rm unused param for spec in getCypressExcludePatterns --- lib/support-utils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/support-utils.ts b/lib/support-utils.ts index a7cf8c851..48e7f0e6f 100644 --- a/lib/support-utils.ts +++ b/lib/support-utils.ts @@ -42,7 +42,7 @@ function filterSpecsFromCoverage( expose: typeof Cypress.expose = Cypress.expose, spec: typeof Cypress.spec = Cypress.spec ): CoverageObject { - const testFilePatterns = getCypressExcludePatterns(config, expose, spec) + const testFilePatterns = getCypressExcludePatterns(config, expose) const isTestFile = (_: unknown, filePath: string): boolean => { const workingDir = spec.absolute.replace(spec.relative, '') @@ -66,7 +66,6 @@ function filterSpecsFromCoverage( function getCypressExcludePatterns( config: typeof Cypress.config, expose: typeof Cypress.expose, - spec: typeof Cypress.spec ): string[] { const testFilePattern = config('specPattern') const codeCoverageConfig = expose().codeCoverage as From f195a0674fc0f983158868b81812e6d3b87d1654 Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Tue, 10 Feb 2026 11:22:45 -0500 Subject: [PATCH 16/17] add esm-example to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 50bcd8937..dd8c06469 100644 --- a/README.md +++ b/README.md @@ -432,6 +432,7 @@ Full examples we use for testing in this repository: - [test-apps/before-each-visit](test-apps/before-each-visit) checks if code coverage correctly keeps track of code when doing `cy.visit` before each test - [test-apps/one-spec](test-apps/one-spec) confirms that coverage is collected and filtered correctly if the user only executes a single Cypress test - [test-apps/ts-example](test-apps/ts-example) uses Babel + Parcel to instrument and serve TypeScript file +- [test-apps/esm-example](test-apps/esm-example) demonstrates using ES module syntax (`import`/`export`) with the plugin - [test-apps/use-webpack](test-apps/use-webpack) shows Webpack build with source maps and Babel - [test-apps/unit-tests-js](test-apps/unit-tests-js) runs just the unit tests and reports code coverage (JavaScript source code) - [test-apps/unit-tests-ts](test-apps/ts-example) runs just the unit tests and reports code coverage (TypeScript source code) From 5969aab9991ca59fdc867ba6059e8155f18306d2 Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Tue, 10 Feb 2026 11:58:33 -0500 Subject: [PATCH 17/17] format --- lib/support-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/support-utils.ts b/lib/support-utils.ts index 48e7f0e6f..e556605e3 100644 --- a/lib/support-utils.ts +++ b/lib/support-utils.ts @@ -65,7 +65,7 @@ function filterSpecsFromCoverage( */ function getCypressExcludePatterns( config: typeof Cypress.config, - expose: typeof Cypress.expose, + expose: typeof Cypress.expose ): string[] { const testFilePattern = config('specPattern') const codeCoverageConfig = expose().codeCoverage as