Fix misc generation issues related to the new TS solution setup: - Improve Cypress config default formatting (when no prettier) - Remove leftover compiler options from `tsconfig.json` files - Do not add TS path mappings - Update `outDir` for `typecheck` tasks to be `out-tsc/...` - Generate Nx configuration in `package.json` files for e2e test runner projects - Fix issue with `@nx/js:library` and `--bundler=vite` - Other smaller changes <!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
187 lines
5.3 KiB
TypeScript
187 lines
5.3 KiB
TypeScript
import { glob, joinPathFragments, type Tree } from '@nx/devkit';
|
|
import type {
|
|
InterfaceDeclaration,
|
|
MethodSignature,
|
|
ObjectLiteralExpression,
|
|
PropertyAssignment,
|
|
PropertySignature,
|
|
} from 'typescript';
|
|
import type {
|
|
NxComponentTestingOptions,
|
|
NxCypressE2EPresetOptions,
|
|
} from '../../plugins/cypress-preset';
|
|
|
|
export const CYPRESS_CONFIG_FILE_NAME_PATTERN =
|
|
'cypress.config.{js,ts,mjs,cjs}';
|
|
|
|
const TS_QUERY_COMMON_JS_EXPORT_SELECTOR =
|
|
'BinaryExpression:has(Identifier[name="module"]):has(Identifier[name="exports"])';
|
|
const TS_QUERY_EXPORT_CONFIG_PREFIX = `:matches(ExportAssignment, ${TS_QUERY_COMMON_JS_EXPORT_SELECTOR}) `;
|
|
|
|
export async function addDefaultE2EConfig(
|
|
cyConfigContents: string,
|
|
options: NxCypressE2EPresetOptions,
|
|
baseUrl: string
|
|
) {
|
|
if (!cyConfigContents) {
|
|
throw new Error('The passed in cypress config file is empty!');
|
|
}
|
|
const { tsquery } = await import('@phenomnomnominal/tsquery');
|
|
|
|
const isCommonJS =
|
|
tsquery.query(cyConfigContents, TS_QUERY_COMMON_JS_EXPORT_SELECTOR).length >
|
|
0;
|
|
const testingTypeConfig = tsquery.query<PropertyAssignment>(
|
|
cyConfigContents,
|
|
`${TS_QUERY_EXPORT_CONFIG_PREFIX} PropertyAssignment:has(Identifier[name="e2e"])`
|
|
);
|
|
|
|
let updatedConfigContents = cyConfigContents;
|
|
|
|
if (testingTypeConfig.length === 0) {
|
|
const configValue = `nxE2EPreset(__filename, ${JSON.stringify(
|
|
options,
|
|
null,
|
|
2
|
|
)
|
|
.split('\n')
|
|
.join('\n ')})`;
|
|
|
|
updatedConfigContents = tsquery.replace(
|
|
cyConfigContents,
|
|
`${TS_QUERY_EXPORT_CONFIG_PREFIX} ObjectLiteralExpression:first-child`,
|
|
(node: ObjectLiteralExpression) => {
|
|
let baseUrlContents = baseUrl ? `,\n baseUrl: '${baseUrl}'` : '';
|
|
if (node.properties.length > 0) {
|
|
return `{
|
|
${node.properties.map((p) => p.getText()).join(',\n')},
|
|
e2e: {
|
|
...${configValue}${baseUrlContents}
|
|
}
|
|
}`;
|
|
}
|
|
return `{
|
|
e2e: {
|
|
...${configValue}${baseUrlContents}
|
|
}
|
|
}`;
|
|
}
|
|
);
|
|
|
|
return isCommonJS
|
|
? `const { nxE2EPreset } = require('@nx/cypress/plugins/cypress-preset');
|
|
${updatedConfigContents}`
|
|
: `import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
|
${updatedConfigContents}`;
|
|
}
|
|
return updatedConfigContents;
|
|
}
|
|
|
|
/**
|
|
* Adds the nxComponentTestingPreset to the cypress config file
|
|
* Make sure after calling this the correct import statement is addeda
|
|
* to bring in the nxComponentTestingPreset function
|
|
**/
|
|
export async function addDefaultCTConfig(
|
|
cyConfigContents: string,
|
|
options: NxComponentTestingOptions = {}
|
|
) {
|
|
if (!cyConfigContents) {
|
|
throw new Error('The passed in cypress config file is empty!');
|
|
}
|
|
const { tsquery } = await import('@phenomnomnominal/tsquery');
|
|
|
|
const testingTypeConfig = tsquery.query<PropertyAssignment>(
|
|
cyConfigContents,
|
|
`${TS_QUERY_EXPORT_CONFIG_PREFIX} PropertyAssignment:has(Identifier[name="component"])`
|
|
);
|
|
|
|
let updatedConfigContents = cyConfigContents;
|
|
|
|
if (testingTypeConfig.length === 0) {
|
|
let configValue = 'nxComponentTestingPreset(__filename)';
|
|
if (options) {
|
|
if (options.bundler !== 'vite') {
|
|
// vite is the default bundler, so we don't need to set it
|
|
delete options.bundler;
|
|
}
|
|
|
|
if (Object.keys(options).length) {
|
|
configValue = `nxComponentTestingPreset(__filename, ${JSON.stringify(
|
|
options
|
|
)})`;
|
|
}
|
|
}
|
|
|
|
updatedConfigContents = tsquery.replace(
|
|
cyConfigContents,
|
|
`${TS_QUERY_EXPORT_CONFIG_PREFIX} ObjectLiteralExpression:first-child`,
|
|
(node: ObjectLiteralExpression) => {
|
|
if (node.properties.length > 0) {
|
|
return `{
|
|
${node.properties.map((p) => p.getText()).join(',\n')},
|
|
component: ${configValue}
|
|
}`;
|
|
}
|
|
return `{
|
|
component: ${configValue}
|
|
}`;
|
|
}
|
|
);
|
|
}
|
|
return updatedConfigContents;
|
|
}
|
|
|
|
/**
|
|
* Adds the mount command for Cypress
|
|
* Make sure after calling this the correct import statement is added
|
|
* to bring in the correct mount from cypress.
|
|
**/
|
|
export async function addMountDefinition(cmpCommandFileContents: string) {
|
|
if (!cmpCommandFileContents) {
|
|
throw new Error('The passed in cypress component file is empty!');
|
|
}
|
|
const { tsquery } = await import('@phenomnomnominal/tsquery');
|
|
const hasMountCommand =
|
|
tsquery.query<MethodSignature | PropertySignature>(
|
|
cmpCommandFileContents,
|
|
'CallExpression StringLiteral[value="mount"]'
|
|
)?.length > 0;
|
|
|
|
if (hasMountCommand) {
|
|
return cmpCommandFileContents;
|
|
}
|
|
|
|
const mountCommand = `Cypress.Commands.add('mount', mount);`;
|
|
|
|
const updatedInterface = tsquery.replace(
|
|
cmpCommandFileContents,
|
|
'InterfaceDeclaration',
|
|
(node: InterfaceDeclaration) => {
|
|
return `interface ${node.name.getText()}${
|
|
node.typeParameters
|
|
? `<${node.typeParameters.map((p) => p.getText()).join(', ')}>`
|
|
: ''
|
|
} {
|
|
${node.members.map((m) => m.getText()).join('\n ')}
|
|
mount: typeof mount;
|
|
}`;
|
|
}
|
|
);
|
|
return `${updatedInterface}\n${mountCommand}`;
|
|
}
|
|
|
|
export function getProjectCypressConfigPath(
|
|
tree: Tree,
|
|
projectRoot: string
|
|
): string {
|
|
const cypressConfigPaths = glob(tree, [
|
|
joinPathFragments(projectRoot, CYPRESS_CONFIG_FILE_NAME_PATTERN),
|
|
]);
|
|
if (cypressConfigPaths.length === 0) {
|
|
throw new Error(`Could not find a cypress config file in ${projectRoot}.`);
|
|
}
|
|
|
|
return cypressConfigPaths[0];
|
|
}
|