"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Cypress = void 0; const tslib_1 = require("tslib"); const debug_1 = tslib_1.__importDefault(require("debug")); const node_html_parser_1 = require("node-html-parser"); const fs_1 = tslib_1.__importDefault(require("fs")); const path_1 = tslib_1.__importDefault(require("path")); const debug = (0, debug_1.default)('cypress:vite-dev-server:plugins:cypress'); const INIT_FILEPATH = path_1.default.resolve(__dirname, '../../client/initCypressTests.js'); const HMR_DEPENDENCY_LOOKUP_MAX_ITERATION = 50; function getSpecsPathsSet(specs) { return new Set(specs.map((spec) => spec.absolute)); } const Cypress = (options, vite) => { let base = '/'; const projectRoot = options.cypressConfig.projectRoot; const supportFilePath = options.cypressConfig.supportFile ? path_1.default.resolve(projectRoot, options.cypressConfig.supportFile) : false; const devServerEvents = options.devServerEvents; const specs = options.specs; const indexHtmlFile = options.cypressConfig.indexHtmlFile; let specsPathsSet = getSpecsPathsSet(specs); // TODO: use async fs methods here // eslint-disable-next-line no-restricted-syntax let loader = fs_1.default.readFileSync(INIT_FILEPATH, 'utf8'); devServerEvents.on('dev-server:specs:changed', (specs) => { specsPathsSet = getSpecsPathsSet(specs); }); return { name: 'cypress:main', enforce: 'pre', configResolved(config) { base = config.base; }, async transformIndexHtml(html) { // it's possibe other plugins have modified the HTML // before we get to. For example vitejs/plugin-react will // add a preamble. We do our best to look at the HTML we // receive and inject it. // For now we just grab any ` ${indexHtmlContent.substring(endOfBody)} `; return newHtml; }, configureServer: async (server) => { server.middlewares.use(`${base}index.html`, async (req, res) => { let transformedIndexHtml = await server.transformIndexHtml(base, ''); const viteImport = ``; // If we're doing cy-in-cy, we need to be able to access the Cypress instance from the parent frame. if (process.env.CYPRESS_INTERNAL_VITE_OPEN_MODE_TESTING) { transformedIndexHtml = transformedIndexHtml.replace(viteImport, `${viteImport}`); } return res.end(transformedIndexHtml); }); }, handleHotUpdate: ({ server, file }) => { debug('handleHotUpdate - file', file); // If the user provided IndexHtml is changed, do a full-reload if (vite.normalizePath(file) === path_1.default.resolve(projectRoot, indexHtmlFile)) { server.ws.send({ type: 'full-reload', }); return; } // get the graph node for the file that just got updated let moduleImporters = server.moduleGraph.fileToModulesMap.get(file); let iterationNumber = 0; const exploredFiles = new Set(); // until we reached a point where the current module is imported by no other while (moduleImporters === null || moduleImporters === void 0 ? void 0 : moduleImporters.size) { if (iterationNumber > HMR_DEPENDENCY_LOOKUP_MAX_ITERATION) { debug(`max hmr iteration reached: ${HMR_DEPENDENCY_LOOKUP_MAX_ITERATION}; Rerun will not happen on this file change.`); return; } // as soon as we find one of the specs, we trigger the re-run of tests for (const mod of moduleImporters.values()) { debug('handleHotUpdate - mod.file', mod.file); if (mod.file === supportFilePath) { debug('handleHotUpdate - support compile success'); devServerEvents.emit('dev-server:compile:success'); // if we update support we know we have to re-run it all // no need to check further return; } if (mod.file && specsPathsSet.has(mod.file)) { debug('handleHotUpdate - spec compile success', mod.file); devServerEvents.emit('dev-server:compile:success', { specFile: mod.file }); // if we find one spec, does not mean we are done yet, // there could be other spec files to re-run // see https://github.com/cypress-io/cypress/issues/17691 } } // get all the modules that import the current one moduleImporters = getImporters(moduleImporters, exploredFiles); iterationNumber += 1; } return; }, }; }; exports.Cypress = Cypress; /** * Gets all the modules that import the set of modules passed in parameters * @param modules the set of module whose dependents to return * @param alreadyExploredFiles set of files that have already been looked at and should be avoided in case of circular dependency * @returns a set of ModuleMode that import directly the current modules */ function getImporters(modules, alreadyExploredFiles) { const allImporters = new Set(); modules.forEach((m) => { if (m.file && !alreadyExploredFiles.has(m.file)) { alreadyExploredFiles.add(m.file); m.importers.forEach((imp) => { allImporters.add(imp); }); } }); return allImporters; }