UNPKG

@nx/cypress

Version:

The Nx Plugin for Cypress contains executors and generators allowing your workspace to use the powerful Cypress integration testing capabilities.

141 lines (140 loc) • 6.99 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = disableWebpackCtJustInTimeCompile; const devkit_1 = require("@nx/devkit"); const internal_1 = require("@nx/js/internal"); const tsquery_1 = require("@phenomnomnominal/tsquery"); const config_1 = require("../../utils/config"); const migrations_1 = require("../../utils/migrations"); // @nx/remix component testing uses the vite dev server, so justInTimeCompile // (webpack only) does not apply even though the preset takes no bundler option. const NX_VITE_CT_PRESET = '@nx/remix/plugins/component-testing'; let ts; async function disableWebpackCtJustInTimeCompile(tree) { let wereProjectsMigrated = false; for await (const { cypressConfigPath } of (0, migrations_1.cypressProjectConfigs)(tree)) { if (!tree.exists(cypressConfigPath)) { continue; } const contents = tree.read(cypressConfigPath, 'utf-8'); const config = (0, config_1.resolveCypressConfigObject)(contents); if (!config) { continue; } const component = (0, migrations_1.getObjectProperty)(config, 'component'); if (!component || !isWebpackComponentTesting(component) || setsJustInTimeCompile(component)) { continue; } tree.write(cypressConfigPath, addJustInTimeCompile(contents, component)); wereProjectsMigrated = true; } if (wereProjectsMigrated) { await (0, devkit_1.formatFiles)(tree); } } function isWebpackComponentTesting(component) { ts ??= (0, internal_1.ensureTypescript)(); // A vite bundler (inline devServer or preset options) opts out. if (getComponentProperty(component, 'bundler').some((property) => ts.isStringLiteral(property.initializer) && property.initializer.text === 'vite')) { return false; } // Preset-based config: trust it only when `component` actually calls // nxComponentTestingPreset, so a stale/unused preset import doesn't count. if (usesNxComponentTestingPreset(component)) { // @nx/remix is the only vite-based Nx CT preset; resolve the import bound // to the call so an unrelated remix reference elsewhere can't misclassify. return getComponentTestingPresetImport(component) !== NX_VITE_CT_PRESET; } // Hand-written config: migrate only when it has an inline devServer framework. return hasInlineDevServerFramework(component); } function usesNxComponentTestingPreset(component) { ts ??= (0, internal_1.ensureTypescript)(); return (0, tsquery_1.query)(component, 'CallExpression').some((call) => ts.isIdentifier(call.expression) && call.expression.text === 'nxComponentTestingPreset'); } function hasInlineDevServerFramework(component) { ts ??= (0, internal_1.ensureTypescript)(); if (!ts.isObjectLiteralExpression(component.initializer)) { return false; } const devServer = (0, migrations_1.getObjectProperty)(component.initializer, 'devServer'); if (!devServer || !ts.isObjectLiteralExpression(devServer.initializer)) { return false; } return !!(0, migrations_1.getObjectProperty)(devServer.initializer, 'framework'); } function getComponentProperty(component, name) { ts ??= (0, internal_1.ensureTypescript)(); return (0, tsquery_1.query)(component, 'PropertyAssignment').filter((property) => ts.isIdentifier(property.name) && property.name.text === name); } // Resolves the module specifier that binds the `nxComponentTestingPreset` // identifier used inside `component`, covering both ESM `import` and CJS // `require` cypress configs. Returns null when no such binding is found. function getComponentTestingPresetImport(component) { ts ??= (0, internal_1.ensureTypescript)(); for (const statement of component.getSourceFile().statements) { if (ts.isImportDeclaration(statement) && statement.importClause) { const namedBindings = statement.importClause.namedBindings; if (namedBindings && ts.isNamedImports(namedBindings) && ts.isStringLiteral(statement.moduleSpecifier) && namedBindings.elements.some((element) => element.name.text === 'nxComponentTestingPreset')) { return statement.moduleSpecifier.text; } } if (ts.isVariableStatement(statement)) { for (const declaration of statement.declarationList.declarations) { const initializer = declaration.initializer; if (!initializer || !ts.isCallExpression(initializer) || !ts.isIdentifier(initializer.expression) || initializer.expression.text !== 'require' || !ts.isObjectBindingPattern(declaration.name)) { continue; } const moduleSpecifier = initializer.arguments[0]; if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier) && declaration.name.elements.some((element) => ts.isIdentifier(element.name) && element.name.text === 'nxComponentTestingPreset')) { return moduleSpecifier.text; } } } } return null; } function setsJustInTimeCompile(component) { return getComponentProperty(component, 'justInTimeCompile').length > 0; } function addJustInTimeCompile(contents, component) { ts ??= (0, internal_1.ensureTypescript)(); const indent = ' '; const comment = config_1.JIT_COMPILE_DISABLE_COMMENT.map((line) => `${indent}${line}`).join('\n'); const newProperty = `${comment}\n${indent}justInTimeCompile: false,`; const initializer = component.initializer; // Object literal: insert the property after the last existing one, leaving // the surrounding source (comments, formatting) untouched. `component` is // gated to be non-empty before we reach here. if (ts.isObjectLiteralExpression(initializer)) { const properties = initializer.properties; const lastProperty = properties[properties.length - 1]; let insertAt = lastProperty.getEnd(); if (properties.hasTrailingComma) { // Insert after the existing trailing comma, not before it. const commaIndex = contents.indexOf(',', insertAt); if (commaIndex !== -1) { insertAt = commaIndex + 1; } } const separator = properties.hasTrailingComma ? '' : ','; return `${contents.slice(0, insertAt)}${separator}\n${newProperty}${contents.slice(insertAt)}`; } // Preset call (or any other expression): wrap it in an object and spread. const componentValue = `{\n${indent}...${initializer.getText()},\n${newProperty}\n }`; return `${contents.slice(0, component.getStart())}component: ${componentValue}${contents.slice(component.getEnd())}`; }