feat(misc): v19 cleanup for Nx plugins (#23104)
This PR removes deprecated code that's been slated for removal in Nx 19 - mentioned as `TODO(v19)` comments. ## Breaking Changes - **CNW:** `create-nx-workspace` no longer support `--preset=empty` and `--preset=core`, use `--preset=apps` and `--preset=npm` respectively. Deprecated in Nx 15.9. - **Next.js:** `NX_` environment variables are no longer bundled into Next.js apps, use `NEXT_PUBLIC` instead. Deprecated in Nx 16.8. - **Webpack, Storybook, Esbuild:** `NX_` environment variables are no longer bundled into browser bundles, use `NX_PUBLIC` instead. This removes the possibility of intentional bundling of `NX_` variables. Deprecated in Nx 18. - **Cypress:** `cypressComponentConfiguration` generator removed from `@nx/cypress`, use `configurationGenerator`instead. Deprecated in Nx 16.8. - **Cypress:** `cypressProjectGenerator` generator removed from `@nx/cypress`, use `configurationGenerator` instead. Deprecated in Nx 15.9. - **Expo:** `withNxWebpack` removed from `@nx/expo`, use [metro bundler](https://docs.expo.dev/guides/customizing-metro/) (https://docs.expo.dev/guides/customizing-metro/) in app.json instead. There is a migration to handle this in Nx 19. Deprecated in Nx 15.8. ## Deferred to v20 - **JS:** `classProperties.loose` option removed from `@nx/js/babel` preset, use `loose` instead. Deprecated in Nx 17.0. - **ESLint:** Low priority task to "deviations from @typescript-eslint/recommended" for our lint rules. @JamesHenry will look at this later before Nx 20, but it is unimportant. - **React:** component testing does not work with Project Crystal, and we need the executor + built-in webpack configs to run CT. Will do a follow-up on this after Nx 19 release. Related issue: https://github.com/nrwl/nx/issues/21546 - **Next.js:** `withStylus` removal from `@nx/next`, use SASS instead. It hasn't worked, but we kept the file to throw an error when used. Deprecated in Nx 17.0. - **Next.js**: `@nx/next:component` and `@nx/next:page` generators to not derive the `components` and `app`/`pages` directory. Use `nx g @nx/next:component apps/myapp/components/button` instead. Deprecated in Nx 17.0. - **Webpack:** `isolatedConfig` option removal from `@nx/webpack:webpack` executor. There is a migration to handle this in Nx 19. Deprecated in in Nx 17.2. - **Angular:** `executeWebpackDevServerBuilder` removal from `@nx/angular/executors`, use `executeDevServerBuilder` instead. Deprecated in Nx 17.0.
This commit is contained in:
parent
35f0618347
commit
2e621f324c
@ -145,7 +145,7 @@ Prefix to use for Angular component and directive selectors.
|
||||
|
||||
Type: `string`
|
||||
|
||||
Customizes the initial content of your workspace. Default presets include: ["apps", "empty", "core", "npm", "ts", "web-components", "angular-monorepo", "angular-standalone", "react-monorepo", "react-standalone", "vue-monorepo", "vue-standalone", "nuxt", "nuxt-standalone", "next", "nextjs-standalone", "remix-monorepo", "remix-standalone", "react-native", "expo", "nest", "express", "react", "vue", "angular", "node-standalone", "node-monorepo", "ts-standalone"]. To build your own see https://nx.dev/extending-nx/recipes/create-preset
|
||||
Customizes the initial content of your workspace. Default presets include: ["apps", "npm", "ts", "web-components", "angular-monorepo", "angular-standalone", "react-monorepo", "react-standalone", "vue-monorepo", "vue-standalone", "nuxt", "nuxt-standalone", "next", "nextjs-standalone", "remix-monorepo", "remix-standalone", "react-native", "expo", "nest", "express", "react", "vue", "angular", "node-standalone", "node-monorepo", "ts-standalone"]. To build your own see https://nx.dev/extending-nx/recipes/create-preset
|
||||
|
||||
### routing
|
||||
|
||||
|
||||
@ -7017,14 +7017,6 @@
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "cypress-project",
|
||||
"path": "/nx-api/cypress/generators/cypress-project",
|
||||
"name": "cypress-project",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "configuration",
|
||||
"path": "/nx-api/cypress/generators/configuration",
|
||||
|
||||
@ -510,15 +510,6 @@
|
||||
"path": "/nx-api/cypress/generators/init",
|
||||
"type": "generator"
|
||||
},
|
||||
"/nx-api/cypress/generators/cypress-project": {
|
||||
"description": "Add a Cypress E2E Project.",
|
||||
"file": "generated/packages/cypress/generators/cypress-project.json",
|
||||
"hidden": true,
|
||||
"name": "cypress-project",
|
||||
"originalFilePath": "/packages/cypress/src/generators/cypress-project/schema.json",
|
||||
"path": "/nx-api/cypress/generators/cypress-project",
|
||||
"type": "generator"
|
||||
},
|
||||
"/nx-api/cypress/generators/configuration": {
|
||||
"description": "Add a Cypress E2E Configuration to an existing project.",
|
||||
"file": "generated/packages/cypress/generators/configuration.json",
|
||||
|
||||
@ -502,15 +502,6 @@
|
||||
"path": "cypress/generators/init",
|
||||
"type": "generator"
|
||||
},
|
||||
{
|
||||
"description": "Add a Cypress E2E Project.",
|
||||
"file": "generated/packages/cypress/generators/cypress-project.json",
|
||||
"hidden": true,
|
||||
"name": "cypress-project",
|
||||
"originalFilePath": "/packages/cypress/src/generators/cypress-project/schema.json",
|
||||
"path": "cypress/generators/cypress-project",
|
||||
"type": "generator"
|
||||
},
|
||||
{
|
||||
"description": "Add a Cypress E2E Configuration to an existing project.",
|
||||
"file": "generated/packages/cypress/generators/configuration.json",
|
||||
|
||||
@ -1,84 +0,0 @@
|
||||
{
|
||||
"name": "cypress-project",
|
||||
"factory": "./src/generators/cypress-project/cypress-project#cypressProjectGeneratorInternal",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "NxCypressProjectGeneratorSchema",
|
||||
"cli": "nx",
|
||||
"title": "Create Cypress Configuration for the workspace",
|
||||
"description": "Create Cypress Configuration for the workspace.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The name of the frontend project to test.",
|
||||
"$default": { "$source": "projectName" },
|
||||
"x-priority": "important"
|
||||
},
|
||||
"baseUrl": {
|
||||
"type": "string",
|
||||
"description": "The address (with the port) which your application is running on."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the E2E Project.",
|
||||
"$default": { "$source": "argv", "index": 0 },
|
||||
"x-prompt": "What name would you like to use for the e2e project?"
|
||||
},
|
||||
"directory": {
|
||||
"type": "string",
|
||||
"description": "A directory where the project is placed.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"projectNameAndRootFormat": {
|
||||
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
|
||||
"type": "string",
|
||||
"enum": ["as-provided", "derived"]
|
||||
},
|
||||
"linter": {
|
||||
"description": "The tool to use for running lint checks.",
|
||||
"type": "string",
|
||||
"enum": ["eslint", "none"],
|
||||
"default": "eslint"
|
||||
},
|
||||
"js": {
|
||||
"description": "Generate JavaScript files rather than TypeScript files.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"skipFormat": {
|
||||
"description": "Skip formatting files.",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"x-priority": "internal"
|
||||
},
|
||||
"setParserOptionsProject": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.",
|
||||
"default": false
|
||||
},
|
||||
"skipPackageJson": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Do not add dependencies to `package.json`.",
|
||||
"x-priority": "internal"
|
||||
},
|
||||
"bundler": {
|
||||
"description": "The Cypress bundler to use.",
|
||||
"type": "string",
|
||||
"enum": ["vite", "webpack", "none"],
|
||||
"x-prompt": "Which Cypress bundler do you want to use?",
|
||||
"default": "webpack"
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"examplesFile": "Adding Cypress to an existing application requires two options. The name of the e2e app to create and what project that e2e app is for.\n\n```bash\nnx g configuration --name=my-app-e2e --project=my-app\n```\n\nWhen providing `--project` option, the generator will look for the `serve` target in that given project. This allows the [cypress executor](/packages/cypress/executors/cypress) to spin up the project and start the cypress runner.\n\nIf you prefer to not have the project served automatically, you can provide a `--base-url` argument in place of `--project`\n\n```bash\nnx g configuration --name=my-app-e2e --base-url=http://localhost:1234\n```\n\n{% callout type=\"note\" title=\"What about API Projects?\" %}\nYou can also run the `configuration` generator against API projects like a [Nest API](/packages/nest/generators/application#@nx/nest:application).\nIf there is a URL to visit then you can test it with Cypress!\n{% /callout %}\n\n## Using Cypress with Vite.js\n\nNow, you can generate your Cypress project with Vite.js as the bundler:\n\n```bash\nnx g configuration --name=my-app-e2e --project=my-app --bundler=vite\n```\n\nThis generator will pass the `bundler` information (`bundler: 'vite'`) to our `nxE2EPreset`, in your project's `cypress.config.ts` file (eg. `my-app-e2e/cypress.config.ts`).\n\n### Customizing the Vite.js configuration\n\nThe `nxE2EPreset` will then use the `bundler` information to generate the correct settings for your Cypress project to use Vite.js. In the background, the way this works is that it's using a custom Vite preprocessor for your files, that's called on the `file:preprocessor` event. If you want to customize this behaviour, you can do so like this in your project's `cypress.config.ts` file:\n\n```ts\nimport { defineConfig } from 'cypress';\nimport { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';\n\nconst config = nxE2EPreset(__filename, { bundler: 'vite' });\nexport default defineConfig({\n e2e: {\n ...config,\n async setupNodeEvents(on, config) {\n // Ensure that `@nx/cypress` events are set up.\n await config.setupNodeEvents(on, config);\n\n // Your settings here\n },\n },\n});\n```\n",
|
||||
"presets": []
|
||||
},
|
||||
"description": "Add a Cypress E2E Project.",
|
||||
"hidden": true,
|
||||
"implementation": "/packages/cypress/src/generators/cypress-project/cypress-project#cypressProjectGeneratorInternal.ts",
|
||||
"aliases": [],
|
||||
"path": "/packages/cypress/src/generators/cypress-project/schema.json",
|
||||
"type": "generator"
|
||||
}
|
||||
@ -145,7 +145,7 @@ Prefix to use for Angular component and directive selectors.
|
||||
|
||||
Type: `string`
|
||||
|
||||
Customizes the initial content of your workspace. Default presets include: ["apps", "empty", "core", "npm", "ts", "web-components", "angular-monorepo", "angular-standalone", "react-monorepo", "react-standalone", "vue-monorepo", "vue-standalone", "nuxt", "nuxt-standalone", "next", "nextjs-standalone", "remix-monorepo", "remix-standalone", "react-native", "expo", "nest", "express", "react", "vue", "angular", "node-standalone", "node-monorepo", "ts-standalone"]. To build your own see https://nx.dev/extending-nx/recipes/create-preset
|
||||
Customizes the initial content of your workspace. Default presets include: ["apps", "npm", "ts", "web-components", "angular-monorepo", "angular-standalone", "react-monorepo", "react-standalone", "vue-monorepo", "vue-standalone", "nuxt", "nuxt-standalone", "next", "nextjs-standalone", "remix-monorepo", "remix-standalone", "react-native", "expo", "nest", "express", "react", "vue", "angular", "node-standalone", "node-monorepo", "ts-standalone"]. To build your own see https://nx.dev/extending-nx/recipes/create-preset
|
||||
|
||||
### routing
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -368,7 +368,6 @@
|
||||
- [cypress](/nx-api/cypress/executors/cypress)
|
||||
- [generators](/nx-api/cypress/generators)
|
||||
- [init](/nx-api/cypress/generators/init)
|
||||
- [cypress-project](/nx-api/cypress/generators/cypress-project)
|
||||
- [configuration](/nx-api/cypress/generators/configuration)
|
||||
- [component-configuration](/nx-api/cypress/generators/component-configuration)
|
||||
- [migrate-to-cypress-11](/nx-api/cypress/generators/migrate-to-cypress-11)
|
||||
|
||||
@ -154,10 +154,10 @@ export default defineConfig({
|
||||
async () => {
|
||||
const appName = uniq('next-cy-app');
|
||||
runCLI(
|
||||
`generate @nx/next:app ${appName} --e2eTestRunner=none --no-interactive`
|
||||
`generate @nx/next:app ${appName} --directory=apps/${appName} --e2eTestRunner=none --no-interactive --projectNameAndRootFormat=as-provided`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/next:component btn --project=${appName} --no-interactive`
|
||||
`generate @nx/next:component btn --project=${appName} --directory=apps/${appName}/components --nameAndDirectoryFormat=as-provided --no-interactive`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/next:cypress-component-configuration --project=${appName} --generate-tests --no-interactive`
|
||||
|
||||
@ -118,10 +118,10 @@ function addBabelSupport(path: string) {
|
||||
|
||||
function createAppWithCt(appName: string) {
|
||||
runCLI(
|
||||
`generate @nx/next:app ${appName} --no-interactive --appDir=false --src=false`
|
||||
`generate @nx/next:app ${appName} --directory=apps/${appName} --no-interactive --appDir=false --src=false --projectNameAndRootFormat=as-provided`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/next:component button --project=${appName} --directory=components --flat --no-interactive`
|
||||
`generate @nx/next:component button --project=${appName} --directory=apps/${appName}/components --nameAndDirectoryFormat=as-provided --no-interactive`
|
||||
);
|
||||
createFile(
|
||||
`apps/${appName}/public/data.json`,
|
||||
|
||||
@ -244,7 +244,7 @@ describe('index.html interpolation (legacy)', () => {
|
||||
</head>
|
||||
<body>
|
||||
<div id='root'></div>
|
||||
<div>Nx Variable: %NX_VARIABLE%</div>
|
||||
<div>Nx Variable: %NX_PUBLIC_VARIABLE%</div>
|
||||
<div>Some other variable: %SOME_OTHER_VARIABLE%</div>
|
||||
<div>Deploy Url: %DEPLOY_URL%</div>
|
||||
</body>
|
||||
@ -252,7 +252,7 @@ describe('index.html interpolation (legacy)', () => {
|
||||
`;
|
||||
const envFilePath = `apps/${appName}/.env`;
|
||||
const envFileContents = `
|
||||
NX_VARIABLE=foo
|
||||
NX_PUBLIC_VARIABLE=foo
|
||||
SOME_OTHER_VARIABLE=bar
|
||||
}`;
|
||||
|
||||
|
||||
@ -243,39 +243,39 @@ describe('CLI - Environment Variables', () => {
|
||||
//test if the Nx CLI loads root .env vars
|
||||
updateFile(
|
||||
`.env`,
|
||||
'NX_WS_BASE=ws-base\nNX_SHARED_ENV=shared-in-workspace-base'
|
||||
'NX_PUBLIC_WS_BASE=ws-base\nNX_PUBLIC_SHARED_ENV=shared-in-workspace-base'
|
||||
);
|
||||
updateFile(
|
||||
`.env.local`,
|
||||
'NX_WS_ENV_LOCAL=ws-env-local\nNX_SHARED_ENV=shared-in-workspace-env-local'
|
||||
'NX_PUBLIC_WS_ENV_LOCAL=ws-env-local\nNX_PUBLIC_SHARED_ENV=shared-in-workspace-env-local'
|
||||
);
|
||||
updateFile(
|
||||
`.local.env`,
|
||||
'NX_WS_LOCAL_ENV=ws-local-env\nNX_SHARED_ENV=shared-in-workspace-local-env'
|
||||
'NX_PUBLIC_WS_LOCAL_ENV=ws-local-env\nNX_PUBLIC_SHARED_ENV=shared-in-workspace-local-env'
|
||||
);
|
||||
updateFile(
|
||||
`apps/${appName}/.env`,
|
||||
'NX_APP_BASE=app-base\nNX_SHARED_ENV=shared-in-app-base'
|
||||
'NX_PUBLIC_APP_BASE=app-base\nNX_PUBLIC_SHARED_ENV=shared-in-app-base'
|
||||
);
|
||||
updateFile(
|
||||
`apps/${appName}/.env.local`,
|
||||
'NX_APP_ENV_LOCAL=app-env-local\nNX_SHARED_ENV=shared-in-app-env-local'
|
||||
'NX_PUBLIC_APP_ENV_LOCAL=app-env-local\nNX_PUBLIC_SHARED_ENV=shared-in-app-env-local'
|
||||
);
|
||||
updateFile(
|
||||
`apps/${appName}/.local.env`,
|
||||
'NX_APP_LOCAL_ENV=app-local-env\nNX_SHARED_ENV=shared-in-app-local-env'
|
||||
'NX_PUBLIC_APP_LOCAL_ENV=app-local-env\nNX_PUBLIC_SHARED_ENV=shared-in-app-local-env'
|
||||
);
|
||||
const main = `apps/${appName}/src/main.ts`;
|
||||
const newCode = `
|
||||
const envVars = [process.env.NODE_ENV, process.env.NX_WS_BASE, process.env.NX_WS_ENV_LOCAL, process.env.NX_WS_LOCAL_ENV, process.env.NX_APP_BASE, process.env.NX_APP_ENV_LOCAL, process.env.NX_APP_LOCAL_ENV, process.env.NX_SHARED_ENV];
|
||||
const envVars = [process.env.NODE_ENV, process.env.NX_PUBLIC_WS_BASE, process.env.NX_PUBLIC_WS_ENV_LOCAL, process.env.NX_PUBLIC_WS_LOCAL_ENV, process.env.NX_PUBLIC_APP_BASE, process.env.NX_PUBLIC_APP_ENV_LOCAL, process.env.NX_PUBLIC_APP_LOCAL_ENV, process.env.NX_PUBLIC_SHARED_ENV];
|
||||
const nodeEnv = process.env.NODE_ENV;
|
||||
const nxWsBase = process.env.NX_WS_BASE;
|
||||
const nxWsEnvLocal = process.env.NX_WS_ENV_LOCAL;
|
||||
const nxWsLocalEnv = process.env.NX_WS_LOCAL_ENV;
|
||||
const nxAppBase = process.env.NX_APP_BASE;
|
||||
const nxAppEnvLocal = process.env.NX_APP_ENV_LOCAL;
|
||||
const nxAppLocalEnv = process.env.NX_APP_LOCAL_ENV;
|
||||
const nxSharedEnv = process.env.NX_SHARED_ENV;
|
||||
const nxWsBase = process.env.NX_PUBLIC_WS_BASE;
|
||||
const nxWsEnvLocal = process.env.NX_PUBLIC_WS_ENV_LOCAL;
|
||||
const nxWsLocalEnv = process.env.NX_PUBLIC_WS_LOCAL_ENV;
|
||||
const nxAppBase = process.env.NX_PUBLIC_APP_BASE;
|
||||
const nxAppEnvLocal = process.env.NX_PUBLIC_APP_ENV_LOCAL;
|
||||
const nxAppLocalEnv = process.env.NX_PUBLIC_APP_LOCAL_ENV;
|
||||
const nxSharedEnv = process.env.NX_PUBLIC_SHARED_ENV;
|
||||
`;
|
||||
|
||||
runCLI(
|
||||
@ -290,18 +290,18 @@ describe('CLI - Environment Variables', () => {
|
||||
|
||||
updateFile(
|
||||
`apps/${appName2}/.env`,
|
||||
'NX_APP_BASE=app2-base\nNX_SHARED_ENV=shared2-in-app-base'
|
||||
'NX_PUBLIC_APP_BASE=app2-base\nNX_PUBLIC_SHARED_ENV=shared2-in-app-base'
|
||||
);
|
||||
updateFile(
|
||||
`apps/${appName2}/.env.local`,
|
||||
'NX_APP_ENV_LOCAL=app2-env-local\nNX_SHARED_ENV=shared2-in-app-env-local'
|
||||
'NX_PUBLIC_APP_ENV_LOCAL=app2-env-local\nNX_PUBLIC_SHARED_ENV=shared2-in-app-env-local'
|
||||
);
|
||||
updateFile(
|
||||
`apps/${appName2}/.local.env`,
|
||||
'NX_APP_LOCAL_ENV=app2-local-env\nNX_SHARED_ENV=shared2-in-app-local-env'
|
||||
'NX_PUBLIC_APP_LOCAL_ENV=app2-local-env\nNX_PUBLIC_SHARED_ENV=shared2-in-app-local-env'
|
||||
);
|
||||
const main2 = `apps/${appName2}/src/main.ts`;
|
||||
const newCode2 = `const envVars = [process.env.NODE_ENV, process.env.NX_WS_BASE, process.env.NX_WS_ENV_LOCAL, process.env.NX_WS_LOCAL_ENV, process.env.NX_APP_BASE, process.env.NX_APP_ENV_LOCAL, process.env.NX_APP_LOCAL_ENV, process.env.NX_SHARED_ENV];`;
|
||||
const newCode2 = `const envVars = [process.env.NODE_ENV, process.env.NX_PUBLIC_WS_BASE, process.env.NX_PUBLIC_WS_ENV_LOCAL, process.env.NX_PUBLIC_WS_LOCAL_ENV, process.env.NX_PUBLIC_APP_BASE, process.env.NX_PUBLIC_APP_ENV_LOCAL, process.env.NX_PUBLIC_APP_LOCAL_ENV, process.env.NX_PUBLIC_SHARED_ENV];`;
|
||||
|
||||
runCLI(
|
||||
`generate @nx/web:app ${appName2} --bundler=webpack --no-interactive --compiler=babel`
|
||||
@ -361,14 +361,14 @@ describe('index.html interpolation', () => {
|
||||
</head>
|
||||
<body>
|
||||
<div id='root'></div>
|
||||
<div>Nx Variable: %NX_VARIABLE%</div>
|
||||
<div>Nx Variable: %NX_PUBLIC_VARIABLE%</div>
|
||||
<div>Some other variable: %SOME_OTHER_VARIABLE%</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
const envFilePath = `apps/${appName}/.env`;
|
||||
const envFileContents = `
|
||||
NX_VARIABLE=foo
|
||||
NX_PUBLIC_VARIABLE=foo
|
||||
SOME_OTHER_VARIABLE=bar
|
||||
}`;
|
||||
|
||||
|
||||
@ -164,22 +164,18 @@ describe('Webpack Plugin', () => {
|
||||
expect(output).toMatch(/Hello/);
|
||||
}, 500_000);
|
||||
|
||||
it('should bundle in non-sensitive NX_ environment variables', () => {
|
||||
it('should bundle in NX_PUBLIC_ environment variables', () => {
|
||||
const appName = uniq('app');
|
||||
runCLI(`generate @nx/web:app ${appName} --bundler webpack`);
|
||||
updateFile(
|
||||
`apps/${appName}/src/main.ts`,
|
||||
`
|
||||
console.log(process.env['NX_CLOUD_ENCRYPTION_KEY']);
|
||||
console.log(process.env['NX_CLOUD_ACCESS_TOKEN']);
|
||||
console.log(process.env['NX_PUBLIC_TEST']);
|
||||
`
|
||||
);
|
||||
|
||||
runCLI(`build ${appName}`, {
|
||||
env: {
|
||||
NX_CLOUD_ENCRYPTION_KEY: 'secret',
|
||||
NX_CLOUD_ACCESS_TOKEN: 'secret',
|
||||
NX_PUBLIC_TEST: 'foobar',
|
||||
},
|
||||
});
|
||||
@ -188,7 +184,6 @@ describe('Webpack Plugin', () => {
|
||||
f.startsWith('main.')
|
||||
);
|
||||
const content = readFile(`dist/apps/${appName}/${mainFile}`);
|
||||
expect(content).not.toMatch(/secret/);
|
||||
expect(content).toMatch(/foobar/);
|
||||
});
|
||||
|
||||
|
||||
@ -95,10 +95,6 @@ const pages: Array<{ title: string; path: string }> = [
|
||||
},
|
||||
{ title: '@nx/cypress', path: '/packages/cypress' },
|
||||
{ title: '@nx/cypress:init', path: '/packages/cypress/generators/init' },
|
||||
{
|
||||
title: '@nx/cypress:cypress-project',
|
||||
path: '/packages/cypress/generators/cypress-project',
|
||||
},
|
||||
{
|
||||
title: '@nx/cypress:cypress',
|
||||
path: '/packages/cypress/executors/cypress',
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export * from '@nx/expo/plugins/with-nx-webpack';
|
||||
@ -12,9 +12,9 @@ export * from './src/executors/extract-i18n/extract-i18n.impl';
|
||||
import { executeDevServerBuilder } from './src/builders/dev-server/dev-server.impl';
|
||||
|
||||
export {
|
||||
// TODO(v19): remove this alias
|
||||
// TODO(v20): remove this alias
|
||||
/**
|
||||
* @deprecated Use executeDevServerBuilder instead. It will be removed in Nx v19.
|
||||
* @deprecated Use executeDevServerBuilder instead. It will be removed in Nx v20.
|
||||
*/
|
||||
executeDevServerBuilder as executeWebpackDevServerBuilder,
|
||||
executeDevServerBuilder,
|
||||
|
||||
@ -196,14 +196,7 @@ export const commandsObject: yargs.Argv<Arguments> = yargs
|
||||
throw error;
|
||||
});
|
||||
},
|
||||
[
|
||||
normalizeArgsMiddleware,
|
||||
normalizeAndWarnOnDeprecatedPreset({
|
||||
// TODO(v19): Remove Empty and Core presets
|
||||
[Preset.Core]: Preset.NPM,
|
||||
[Preset.Empty]: Preset.Apps,
|
||||
}),
|
||||
] as yargs.MiddlewareFunction<{}>[]
|
||||
[normalizeArgsMiddleware] as yargs.MiddlewareFunction<{}>[]
|
||||
)
|
||||
.help('help', chalk.dim`Show help`)
|
||||
.updateLocale(yargsDecorator)
|
||||
@ -248,28 +241,6 @@ async function main(parsedArgs: yargs.Arguments<Arguments>) {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeAndWarnOnDeprecatedPreset(
|
||||
deprecatedPresets: Partial<Record<Preset, Preset>>
|
||||
): (argv: yargs.Arguments<Arguments>) => Promise<void> {
|
||||
return async (args: yargs.Arguments<Arguments>): Promise<void> => {
|
||||
if (!args.preset) return;
|
||||
if (deprecatedPresets[args.preset]) {
|
||||
output.addVerticalSeparator();
|
||||
output.note({
|
||||
title: `The "${args.preset}" preset is deprecated.`,
|
||||
bodyLines: [
|
||||
`The "${
|
||||
args.preset
|
||||
}" preset will be removed in a future Nx release. Use the "${
|
||||
deprecatedPresets[args.preset]
|
||||
}" preset instead.`,
|
||||
],
|
||||
});
|
||||
args.preset = deprecatedPresets[args.preset] as Preset;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to normalize the arguments passed to the command.
|
||||
* It would:
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
export enum Preset {
|
||||
Apps = 'apps',
|
||||
Empty = 'empty', // same as apps, deprecated
|
||||
Core = 'core', // same as npm, deprecated
|
||||
NPM = 'npm',
|
||||
TS = 'ts',
|
||||
WebComponents = 'web-components',
|
||||
|
||||
@ -9,12 +9,6 @@
|
||||
"aliases": ["ng-add"],
|
||||
"hidden": true
|
||||
},
|
||||
"cypress-project": {
|
||||
"factory": "./src/generators/cypress-project/cypress-project#cypressProjectGeneratorInternal",
|
||||
"schema": "./src/generators/cypress-project/schema.json",
|
||||
"description": "Add a Cypress E2E Project.",
|
||||
"hidden": true
|
||||
},
|
||||
"configuration": {
|
||||
"aliases": ["cypress-e2e-configuration", "e2e", "e2e-config"],
|
||||
"factory": "./src/generators/configuration/configuration#configurationGeneratorInternal",
|
||||
|
||||
@ -1,17 +1,4 @@
|
||||
import { configurationGenerator } from './src/generators/configuration/configuration';
|
||||
import { componentConfigurationGenerator } from './src/generators/component-configuration/component-configuration';
|
||||
import { cypressProjectGenerator as _cypressProjectGenerator } from './src/generators/cypress-project/cypress-project';
|
||||
|
||||
export { configurationGenerator, componentConfigurationGenerator };
|
||||
|
||||
// Maintain backwards compatibility with the old names in case community plugins used them.
|
||||
// TODO(v19): Remove old name
|
||||
/** @deprecated Use `configurationGenerator` instead. It will be removed in Nx 19. */
|
||||
export const cypressComponentConfiguration = componentConfigurationGenerator;
|
||||
|
||||
export { configurationGenerator as cypressE2EConfigurationGenerator };
|
||||
// TODO(v19): Remove project generator
|
||||
/** @deprecated Add a new project and call `configurationGenerator` instead. It will be removed in Nx 19. */
|
||||
export const cypressProjectGenerator = _cypressProjectGenerator;
|
||||
export { configurationGenerator } from './src/generators/configuration/configuration';
|
||||
export { componentConfigurationGenerator } from './src/generators/component-configuration/component-configuration';
|
||||
export { cypressInitGenerator } from './src/generators/init/init';
|
||||
export { migrateCypressProject } from './src/generators/migrate-to-cypress-11/migrate-to-cypress-11';
|
||||
|
||||
@ -39,7 +39,6 @@
|
||||
"@nx/js": "file:../js",
|
||||
"@phenomnomnominal/tsquery": "~5.0.1",
|
||||
"detect-port": "^1.5.1",
|
||||
"semver": "^7.5.3",
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@ -1,240 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Cypress Project < v7 --linter eslint should add eslint-plugin-cypress 1`] = `
|
||||
{
|
||||
"extends": [
|
||||
"plugin:cypress/recommended",
|
||||
"../.eslintrc.json",
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"!**/*",
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.ts",
|
||||
"*.tsx",
|
||||
"*.js",
|
||||
"*.jsx",
|
||||
],
|
||||
"rules": {},
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"src/plugins/index.js",
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"no-undef": "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Cypress Project < v7 nested should update configuration 1`] = `
|
||||
{
|
||||
"e2e": {
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "my-dir-my-app:serve:production",
|
||||
},
|
||||
},
|
||||
"executor": "@nx/cypress:cypress",
|
||||
"options": {
|
||||
"cypressConfig": "my-dir/my-app-e2e/cypress.json",
|
||||
"devServerTarget": "my-dir-my-app:serve",
|
||||
"testingType": "e2e",
|
||||
"tsConfig": "my-dir/my-app-e2e/tsconfig.json",
|
||||
},
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Cypress Project < v7 project with directory in its name should set right path names in \`cypress.json\` 1`] = `
|
||||
"{
|
||||
"fileServerFolder": ".",
|
||||
"fixturesFolder": "./src/fixtures",
|
||||
"integrationFolder": "./src/integration",
|
||||
"modifyObstructiveCode": false,
|
||||
"supportFile": "./src/support/index.ts",
|
||||
"pluginsFile": "./src/plugins/index",
|
||||
"video": true,
|
||||
"videosFolder": "../../dist/cypress/my-dir/my-app-e2e/videos",
|
||||
"screenshotsFolder": "../../dist/cypress/my-dir/my-app-e2e/screenshots",
|
||||
"chromeWebSecurity": false
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`Cypress Project < v7 project with directory in its name should update configuration 1`] = `
|
||||
{
|
||||
"e2e": {
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "my-dir-my-app:serve:production",
|
||||
},
|
||||
},
|
||||
"executor": "@nx/cypress:cypress",
|
||||
"options": {
|
||||
"cypressConfig": "my-dir/my-app-e2e/cypress.json",
|
||||
"devServerTarget": "my-dir-my-app:serve",
|
||||
"testingType": "e2e",
|
||||
"tsConfig": "my-dir/my-app-e2e/tsconfig.json",
|
||||
},
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Cypress Project < v7 should update project configuration (baseUrl) 1`] = `
|
||||
{
|
||||
"e2e": {
|
||||
"executor": "@nx/cypress:cypress",
|
||||
"options": {
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"cypressConfig": "my-app-e2e/cypress.json",
|
||||
"testingType": "e2e",
|
||||
"tsConfig": "my-app-e2e/tsconfig.json",
|
||||
},
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Cypress Project < v7 should update project configuration 1`] = `
|
||||
{
|
||||
"e2e": {
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "my-app:serve:production",
|
||||
},
|
||||
},
|
||||
"executor": "@nx/cypress:cypress",
|
||||
"options": {
|
||||
"cypressConfig": "my-app-e2e/cypress.json",
|
||||
"devServerTarget": "my-app:serve",
|
||||
"testingType": "e2e",
|
||||
"tsConfig": "my-app-e2e/tsconfig.json",
|
||||
},
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Cypress Project < v7 should update target configurations 1`] = `
|
||||
{
|
||||
"e2e": {
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "my-app:serve:production",
|
||||
},
|
||||
},
|
||||
"executor": "@nx/cypress:cypress",
|
||||
"options": {
|
||||
"cypressConfig": "my-app-e2e/cypress.json",
|
||||
"devServerTarget": "my-app:serve:development",
|
||||
"testingType": "e2e",
|
||||
"tsConfig": "my-app-e2e/tsconfig.json",
|
||||
},
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Cypress Project > v10 for bundler:vite should pass the bundler info to nxE2EPreset in \`cypress.config.ts\` 1`] = `
|
||||
"import { defineConfig } from 'cypress';
|
||||
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: nxE2EPreset(__dirname, {
|
||||
bundler: 'vite',
|
||||
}),
|
||||
});
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`Cypress Project > v10 nested should set right path names in \`cypress.config.ts\` 1`] = `
|
||||
"import { defineConfig } from 'cypress';
|
||||
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: nxE2EPreset(__dirname),
|
||||
});
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`Cypress Project > v10 nested should set right path names in \`tsconfig.e2e.json\` 1`] = `
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"sourceMap": false,
|
||||
"types": [
|
||||
"cypress",
|
||||
"node",
|
||||
],
|
||||
},
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.js",
|
||||
"cypress.config.ts",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Cypress Project > v10 should set right path names in \`cypress.config.ts\` 1`] = `
|
||||
"import { defineConfig } from 'cypress';
|
||||
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: nxE2EPreset(__dirname),
|
||||
});
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`Cypress Project > v10 should set right path names in \`tsconfig.e2e.json\` 1`] = `
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"outDir": "../dist/out-tsc",
|
||||
"sourceMap": false,
|
||||
"types": [
|
||||
"cypress",
|
||||
"node",
|
||||
],
|
||||
},
|
||||
"extends": "../tsconfig.base.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.js",
|
||||
"cypress.config.ts",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Cypress Project > v10 should update configuration when eslint is passed 1`] = `
|
||||
"{
|
||||
"extends": ["plugin:cypress/recommended", "../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
"
|
||||
`;
|
||||
@ -1,602 +0,0 @@
|
||||
import 'nx/src/internal-testing-utils/mock-project-graph';
|
||||
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
readJson,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
updateProjectConfiguration,
|
||||
} from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { cypressProjectGenerator } from './cypress-project';
|
||||
import { Schema } from './schema';
|
||||
import { Linter } from '@nx/eslint';
|
||||
import { installedCypressVersion } from '../../utils/cypress-version';
|
||||
import { cypressInitGenerator } from '../init/init';
|
||||
|
||||
jest.mock('../../utils/cypress-version');
|
||||
jest.mock('../init/init');
|
||||
describe('Cypress Project', () => {
|
||||
let tree: Tree;
|
||||
const defaultOptions: Omit<Schema, 'name' | 'project'> = {
|
||||
linter: Linter.EsLint,
|
||||
};
|
||||
let mockedInstalledCypressVersion: jest.Mock<
|
||||
ReturnType<typeof installedCypressVersion>
|
||||
> = installedCypressVersion as never;
|
||||
let mockInitCypress: jest.Mock<ReturnType<typeof cypressInitGenerator>> =
|
||||
cypressInitGenerator as never;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
|
||||
addProjectConfiguration(tree, 'my-app', {
|
||||
root: 'my-app',
|
||||
targets: {
|
||||
serve: {
|
||||
executor: 'serve-executor',
|
||||
options: {},
|
||||
configurations: {
|
||||
production: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
addProjectConfiguration(tree, 'my-dir-my-app', {
|
||||
root: 'my-dir/my-app',
|
||||
targets: {
|
||||
serve: {
|
||||
executor: 'serve-executor',
|
||||
options: {},
|
||||
configurations: {
|
||||
production: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
afterEach(() => jest.clearAllMocks());
|
||||
|
||||
it('should call init if cypress is not installed', async () => {
|
||||
mockedInstalledCypressVersion.mockReturnValue(null);
|
||||
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
expect(mockInitCypress).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call not init if cypress is installed', async () => {
|
||||
mockedInstalledCypressVersion.mockReturnValue(10);
|
||||
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
expect(mockInitCypress).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('> v10', () => {
|
||||
beforeEach(() => {
|
||||
mockedInstalledCypressVersion.mockReturnValue(10);
|
||||
});
|
||||
|
||||
it('should generate files for v10 and above', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
expect(tree.exists('my-app-e2e/cypress.config.ts')).toBeTruthy();
|
||||
expect(tree.exists('my-app-e2e/src/fixtures/example.json')).toBeTruthy();
|
||||
expect(tree.exists('my-app-e2e/src/e2e/app.cy.ts')).toBeTruthy();
|
||||
expect(tree.exists('my-app-e2e/src/support/app.po.ts')).toBeTruthy();
|
||||
expect(tree.exists('my-app-e2e/src/support/commands.ts')).toBeTruthy();
|
||||
expect(tree.exists('my-app-e2e/src/support/e2e.ts')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should update configuration when eslint is passed', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
linter: Linter.EsLint,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
expect(tree.read('my-app-e2e/.eslintrc.json', 'utf-8')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not add lint target when "none" is passed', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
linter: Linter.None,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const project = readProjectConfiguration(tree, 'my-app-e2e');
|
||||
expect(project.targets.lint).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should update tags and implicit dependencies', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
linter: Linter.EsLint,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const project = readProjectConfiguration(tree, 'my-app-e2e');
|
||||
expect(project.tags).toEqual([]);
|
||||
expect(project.implicitDependencies).toEqual(['my-app']);
|
||||
});
|
||||
|
||||
it('should set right path names in `cypress.config.ts`', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const cypressConfig = tree.read('my-app-e2e/cypress.config.ts', 'utf-8');
|
||||
expect(cypressConfig).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should set right path names in `tsconfig.e2e.json`', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const tsconfigJson = readJson(tree, 'my-app-e2e/tsconfig.json');
|
||||
expect(tsconfigJson).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should extend from tsconfig.base.json', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const tsConfig = readJson(tree, 'my-app-e2e/tsconfig.json');
|
||||
expect(tsConfig.extends).toBe('../tsconfig.base.json');
|
||||
});
|
||||
|
||||
it('should support a root tsconfig.json instead of tsconfig.base.json', async () => {
|
||||
tree.rename('tsconfig.base.json', 'tsconfig.json');
|
||||
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const tsConfig = readJson(tree, 'my-app-e2e/tsconfig.json');
|
||||
expect(tsConfig.extends).toBe('../tsconfig.json');
|
||||
});
|
||||
|
||||
describe('for bundler:vite', () => {
|
||||
it('should pass the bundler info to nxE2EPreset in `cypress.config.ts`', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
bundler: 'vite',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const cypressConfig = tree.read(
|
||||
'my-app-e2e/cypress.config.ts',
|
||||
'utf-8'
|
||||
);
|
||||
expect(cypressConfig).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('nested', () => {
|
||||
it('should set right path names in `cypress.config.ts`', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-dir-my-app',
|
||||
directory: 'my-dir/my-app-e2e',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const cypressConfig = tree.read(
|
||||
'my-dir/my-app-e2e/cypress.config.ts',
|
||||
'utf-8'
|
||||
);
|
||||
expect(cypressConfig).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should set right path names in `tsconfig.e2e.json`', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-dir-my-app',
|
||||
directory: 'my-dir/my-app-e2e',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const tsconfigJson = readJson(tree, 'my-dir/my-app-e2e/tsconfig.json');
|
||||
expect(tsconfigJson).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should extend from tsconfig.base.json', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
directory: 'my-dir/my-app-e2e',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const tsConfig = readJson(tree, 'my-dir/my-app-e2e/tsconfig.json');
|
||||
expect(tsConfig.extends).toBe('../../tsconfig.base.json');
|
||||
});
|
||||
|
||||
it('should support a root tsconfig.json instead of tsconfig.base.json', async () => {
|
||||
tree.rename('tsconfig.base.json', 'tsconfig.json');
|
||||
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
directory: 'my-dir/my-app-e2e',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const tsConfig = readJson(tree, 'my-dir/my-app-e2e/tsconfig.json');
|
||||
expect(tsConfig.extends).toBe('../../tsconfig.json');
|
||||
});
|
||||
|
||||
describe('root project', () => {
|
||||
it('should generate in option.name when root project detected', async () => {
|
||||
addProjectConfiguration(tree, 'root', { root: '.' });
|
||||
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'e2e-tests',
|
||||
baseUrl: 'http://localhost:1234',
|
||||
project: 'root',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
expect(tree.listChanges().map((c) => c.path)).toEqual(
|
||||
expect.arrayContaining([
|
||||
'e2e-tests/cypress.config.ts',
|
||||
'e2e-tests/src/e2e/app.cy.ts',
|
||||
'e2e-tests/src/fixtures/example.json',
|
||||
'e2e-tests/src/support/app.po.ts',
|
||||
'e2e-tests/src/support/commands.ts',
|
||||
'e2e-tests/src/support/e2e.ts',
|
||||
'e2e-tests/tsconfig.json',
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should not generate a root project when the passed in project is not the root project', async () => {
|
||||
addProjectConfiguration(tree, 'root', { root: '.' });
|
||||
addProjectConfiguration(tree, 'my-cool-app', { root: 'my-cool-app' });
|
||||
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'e2e-tests',
|
||||
baseUrl: 'http://localhost:1234',
|
||||
project: 'my-cool-app',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
expect(tree.listChanges().map((c) => c.path)).toEqual(
|
||||
expect.arrayContaining([
|
||||
'e2e-tests/cypress.config.ts',
|
||||
'e2e-tests/src/e2e/app.cy.ts',
|
||||
'e2e-tests/src/fixtures/example.json',
|
||||
'e2e-tests/src/support/app.po.ts',
|
||||
'e2e-tests/src/support/commands.ts',
|
||||
'e2e-tests/src/support/e2e.ts',
|
||||
'e2e-tests/tsconfig.json',
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('--project', () => {
|
||||
describe('none', () => {
|
||||
it('should not add any implicit dependencies', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'my-app-e2e',
|
||||
baseUrl: 'http://localhost:7788',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-app-e2e');
|
||||
expect(projectConfig.implicitDependencies).not.toBeDefined();
|
||||
expect(projectConfig.tags).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw an error when --project does not have targets', async () => {
|
||||
const projectConf = readProjectConfiguration(tree, 'my-app');
|
||||
delete projectConf.targets;
|
||||
updateProjectConfiguration(tree, 'my-app', projectConf);
|
||||
|
||||
await cypressProjectGenerator(tree, {
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
linter: Linter.EsLint,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-app-e2e');
|
||||
expect(projectConfig.targets['e2e'].options.devServerTarget).toEqual(
|
||||
'my-app:serve'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate in the correct folder', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'one-two-other-e2e',
|
||||
project: 'my-app',
|
||||
directory: 'one/two/other-e2e',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const project = readProjectConfiguration(tree, 'one-two-other-e2e');
|
||||
expect(project).toBeDefined();
|
||||
[
|
||||
'one/two/other-e2e/cypress.config.ts',
|
||||
'one/two/other-e2e/src/e2e/app.cy.ts',
|
||||
].forEach((path) => expect(tree.exists(path)).toBeTruthy());
|
||||
});
|
||||
|
||||
it('should generate in the correct folder when --project-name-and-root-format=derived', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'other-e2e',
|
||||
project: 'my-app',
|
||||
directory: 'one/two',
|
||||
projectNameAndRootFormat: 'derived',
|
||||
});
|
||||
|
||||
const project = readProjectConfiguration(tree, 'one-two-other-e2e');
|
||||
expect(project).toBeDefined();
|
||||
[
|
||||
'apps/one/two/other-e2e/cypress.config.ts',
|
||||
'apps/one/two/other-e2e/src/e2e/app.cy.ts',
|
||||
].forEach((path) => expect(tree.exists(path)).toBeTruthy());
|
||||
});
|
||||
|
||||
describe('serve-static', () => {
|
||||
it('should configure Cypress with ci configuration if serve-static is found', async () => {
|
||||
const appConfig = readProjectConfiguration(tree, 'my-app');
|
||||
appConfig.targets['serve-static'] = {
|
||||
executor: 'serve-static-executor',
|
||||
options: {},
|
||||
configurations: {
|
||||
production: {},
|
||||
},
|
||||
};
|
||||
updateProjectConfiguration(tree, 'my-app', appConfig);
|
||||
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const e2eConfig = readProjectConfiguration(tree, 'my-app-e2e');
|
||||
expect(e2eConfig.targets.e2e).toMatchObject({
|
||||
options: {
|
||||
devServerTarget: 'my-app:serve',
|
||||
},
|
||||
configurations: {
|
||||
production: { devServerTarget: 'my-app:serve:production' },
|
||||
ci: { devServerTarget: 'my-app:serve-static' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should not configure Cypress with ci configuration if serve-static is not found', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const e2eConfig = readProjectConfiguration(tree, 'my-app-e2e');
|
||||
expect(e2eConfig.targets.e2e.configurations.ci).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('v9 - v7', () => {
|
||||
beforeEach(() => {
|
||||
mockedInstalledCypressVersion.mockReturnValue(9);
|
||||
});
|
||||
|
||||
it('should generate files', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
expect(tree.exists('my-app-e2e/cypress.json')).toBeTruthy();
|
||||
expect(tree.exists('my-app-e2e/src/fixtures/example.json')).toBeTruthy();
|
||||
expect(
|
||||
tree.exists('my-app-e2e/src/integration/app.spec.ts')
|
||||
).toBeTruthy();
|
||||
expect(tree.exists('my-app-e2e/src/support/app.po.ts')).toBeTruthy();
|
||||
expect(tree.exists('my-app-e2e/src/support/commands.ts')).toBeTruthy();
|
||||
expect(tree.exists('my-app-e2e/src/support/index.ts')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('< v7', () => {
|
||||
beforeEach(() => {
|
||||
mockedInstalledCypressVersion.mockReturnValue(6);
|
||||
});
|
||||
|
||||
it('should generate a plugin file if cypress is below version 7', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
addPlugin: false,
|
||||
});
|
||||
|
||||
expect(tree.exists('my-app-e2e/src/plugins/index.js')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should update project configuration', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
linter: Linter.EsLint,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
addPlugin: false,
|
||||
});
|
||||
|
||||
const project = readProjectConfiguration(tree, 'my-app-e2e');
|
||||
expect(project.root).toEqual('my-app-e2e');
|
||||
expect(project.targets).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should update project configuration (baseUrl)', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
baseUrl: 'http://localhost:3000',
|
||||
linter: Linter.EsLint,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
addPlugin: false,
|
||||
});
|
||||
|
||||
const project = readProjectConfiguration(tree, 'my-app-e2e');
|
||||
expect(project.root).toEqual('my-app-e2e');
|
||||
expect(project.targets).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should update target configurations', async () => {
|
||||
const originalProject = readProjectConfiguration(tree, 'my-app');
|
||||
originalProject.targets.serve.defaultConfiguration = 'development';
|
||||
originalProject.targets.serve.configurations.development = {};
|
||||
updateProjectConfiguration(tree, 'my-app', originalProject);
|
||||
|
||||
await cypressProjectGenerator(tree, {
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
linter: Linter.EsLint,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
addPlugin: false,
|
||||
});
|
||||
|
||||
const project = readProjectConfiguration(tree, 'my-app-e2e');
|
||||
expect(project.root).toEqual('my-app-e2e');
|
||||
expect(project.targets).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('nested', () => {
|
||||
it('should update configuration', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
name: 'my-dir-my-app-e2e',
|
||||
project: 'my-dir-my-app',
|
||||
directory: 'my-dir/my-app-e2e',
|
||||
linter: Linter.EsLint,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
addPlugin: false,
|
||||
});
|
||||
|
||||
const projectConfig = readProjectConfiguration(
|
||||
tree,
|
||||
'my-dir-my-app-e2e'
|
||||
);
|
||||
expect(projectConfig).toBeDefined();
|
||||
expect(projectConfig.targets).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('--linter', () => {
|
||||
describe('eslint', () => {
|
||||
it('should add eslint-plugin-cypress', async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
name: 'my-app-e2e',
|
||||
project: 'my-app',
|
||||
linter: Linter.EsLint,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
addPlugin: false,
|
||||
});
|
||||
|
||||
const packageJson = readJson(tree, 'package.json');
|
||||
expect(
|
||||
packageJson.devDependencies['eslint-plugin-cypress']
|
||||
).toBeTruthy();
|
||||
const eslintrcJson = readJson(tree, 'my-app-e2e/.eslintrc.json');
|
||||
expect(eslintrcJson).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('project with directory in its name', () => {
|
||||
beforeEach(async () => {
|
||||
await cypressProjectGenerator(tree, {
|
||||
name: 'my-dir-my-app-e2e',
|
||||
project: 'my-dir-my-app',
|
||||
directory: 'my-dir/my-app-e2e',
|
||||
linter: Linter.EsLint,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
addPlugin: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should update configuration', async () => {
|
||||
const projectConfig = readProjectConfiguration(
|
||||
tree,
|
||||
'my-dir-my-app-e2e'
|
||||
);
|
||||
expect(projectConfig).toBeDefined();
|
||||
expect(projectConfig.targets).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should update nx.json', async () => {
|
||||
const project = readProjectConfiguration(tree, 'my-dir-my-app-e2e');
|
||||
expect(project.tags).toEqual([]);
|
||||
expect(project.implicitDependencies).toEqual(['my-dir-my-app']);
|
||||
});
|
||||
|
||||
it('should set right path names in `cypress.json`', async () => {
|
||||
const cypressConfig = tree.read(
|
||||
'my-dir/my-app-e2e/cypress.json',
|
||||
'utf-8'
|
||||
);
|
||||
expect(cypressConfig).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,297 +0,0 @@
|
||||
import {
|
||||
addDependenciesToPackageJson,
|
||||
addProjectConfiguration,
|
||||
formatFiles,
|
||||
generateFiles,
|
||||
GeneratorCallback,
|
||||
getProjects,
|
||||
joinPathFragments,
|
||||
logger,
|
||||
offsetFromRoot,
|
||||
ProjectConfiguration,
|
||||
readProjectConfiguration,
|
||||
runTasksInSerial,
|
||||
stripIndents,
|
||||
toJS,
|
||||
Tree,
|
||||
updateJson,
|
||||
} from '@nx/devkit';
|
||||
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||
import { checkAndCleanWithSemver } from '@nx/devkit/src/utils/semver';
|
||||
import {
|
||||
getRelativePathToRootTsConfig,
|
||||
initGenerator as jsInitGenerator,
|
||||
} from '@nx/js';
|
||||
import { Linter } from '@nx/eslint';
|
||||
import { join } from 'path';
|
||||
import { major } from 'semver';
|
||||
import { addLinterToCyProject } from '../../utils/add-linter';
|
||||
import { installedCypressVersion } from '../../utils/cypress-version';
|
||||
import {
|
||||
cypressVersion,
|
||||
typesNodeVersion,
|
||||
viteVersion,
|
||||
} from '../../utils/versions';
|
||||
import { cypressInitGenerator } from '../init/init';
|
||||
import { Schema } from './schema';
|
||||
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
||||
|
||||
export interface CypressProjectSchema extends Schema {
|
||||
projectName: string;
|
||||
projectRoot: string;
|
||||
rootProject: boolean;
|
||||
}
|
||||
|
||||
function createFiles(tree: Tree, options: CypressProjectSchema) {
|
||||
// if not installed or >v10 use v10 folder
|
||||
// else use v9 folder
|
||||
const cypressVersion = installedCypressVersion();
|
||||
const cypressFiles =
|
||||
cypressVersion && cypressVersion < 10 ? 'v9-and-under' : 'v10-and-after';
|
||||
|
||||
generateFiles(
|
||||
tree,
|
||||
join(__dirname, './files', cypressFiles),
|
||||
options.projectRoot,
|
||||
{
|
||||
tmpl: '',
|
||||
...options,
|
||||
project: options.project || 'Project',
|
||||
ext: options.js ? 'js' : 'ts',
|
||||
offsetFromRoot: offsetFromRoot(options.projectRoot),
|
||||
rootTsConfigPath: getRelativePathToRootTsConfig(
|
||||
tree,
|
||||
options.projectRoot
|
||||
),
|
||||
bundler: options.bundler,
|
||||
}
|
||||
);
|
||||
|
||||
if (cypressVersion && cypressVersion < 7) {
|
||||
updateJson(tree, join(options.projectRoot, 'cypress.json'), (json) => {
|
||||
json.pluginsFile = './src/plugins/index';
|
||||
return json;
|
||||
});
|
||||
} else if (cypressVersion < 10) {
|
||||
const pluginPath = join(options.projectRoot, 'src/plugins/index.js');
|
||||
if (tree.exists(pluginPath)) {
|
||||
tree.delete(pluginPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.js) {
|
||||
toJS(tree);
|
||||
}
|
||||
}
|
||||
|
||||
function addProject(tree: Tree, options: CypressProjectSchema) {
|
||||
let e2eProjectConfig: ProjectConfiguration;
|
||||
|
||||
const detectedCypressVersion =
|
||||
installedCypressVersion() ??
|
||||
major(checkAndCleanWithSemver('cypress', cypressVersion));
|
||||
|
||||
const cypressConfig =
|
||||
detectedCypressVersion < 10 ? 'cypress.json' : 'cypress.config.ts';
|
||||
|
||||
if (options.baseUrl) {
|
||||
e2eProjectConfig = {
|
||||
root: options.projectRoot,
|
||||
sourceRoot: joinPathFragments(options.projectRoot, 'src'),
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
e2e: {
|
||||
executor: '@nx/cypress:cypress',
|
||||
options: {
|
||||
cypressConfig: joinPathFragments(
|
||||
options.projectRoot,
|
||||
cypressConfig
|
||||
),
|
||||
baseUrl: options.baseUrl,
|
||||
testingType: 'e2e',
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: [],
|
||||
implicitDependencies: options.project ? [options.project] : undefined,
|
||||
};
|
||||
} else if (options.project) {
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
|
||||
if (!project.targets) {
|
||||
logger.warn(stripIndents`
|
||||
NOTE: Project, "${options.project}", does not have any targets defined and a baseUrl was not provided. Nx will use
|
||||
"${options.project}:serve" as the devServerTarget. But you may need to define this target within the project, "${options.project}".
|
||||
`);
|
||||
}
|
||||
const devServerTarget =
|
||||
project.targets?.serve && project.targets?.serve?.defaultConfiguration
|
||||
? `${options.project}:serve:${project.targets.serve.defaultConfiguration}`
|
||||
: `${options.project}:serve`;
|
||||
e2eProjectConfig = {
|
||||
root: options.projectRoot,
|
||||
sourceRoot: joinPathFragments(options.projectRoot, 'src'),
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
e2e: {
|
||||
executor: '@nx/cypress:cypress',
|
||||
options: {
|
||||
cypressConfig: joinPathFragments(
|
||||
options.projectRoot,
|
||||
cypressConfig
|
||||
),
|
||||
devServerTarget,
|
||||
testingType: 'e2e',
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
devServerTarget: `${options.project}:serve:production`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: [],
|
||||
implicitDependencies: options.project ? [options.project] : undefined,
|
||||
};
|
||||
if (project.targets?.['serve-static']) {
|
||||
e2eProjectConfig.targets.e2e.configurations.ci = {
|
||||
devServerTarget: `${options.project}:serve-static`,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Either project or baseUrl should be specified.`);
|
||||
}
|
||||
|
||||
if (detectedCypressVersion < 7) {
|
||||
e2eProjectConfig.targets.e2e.options.tsConfig = joinPathFragments(
|
||||
options.projectRoot,
|
||||
'tsconfig.json'
|
||||
);
|
||||
}
|
||||
addProjectConfiguration(tree, options.projectName, e2eProjectConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use cypressE2EConfigurationGenerator instead
|
||||
**/
|
||||
export async function cypressProjectGenerator(host: Tree, schema: Schema) {
|
||||
return await cypressProjectGeneratorInternal(host, {
|
||||
projectNameAndRootFormat: 'derived',
|
||||
addPlugin: false,
|
||||
...schema,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use cypressE2EConfigurationGenerator instead
|
||||
**/
|
||||
export async function cypressProjectGeneratorInternal(
|
||||
host: Tree,
|
||||
schema: Schema
|
||||
) {
|
||||
const options = await normalizeOptions(host, schema);
|
||||
const tasks: GeneratorCallback[] = [];
|
||||
const cypressVersion = installedCypressVersion();
|
||||
// if there is an installed cypress version, then we don't call
|
||||
// init since we want to keep the existing version that is installed
|
||||
if (!cypressVersion) {
|
||||
tasks.push(await jsInitGenerator(host, { ...options, skipFormat: true }));
|
||||
tasks.push(
|
||||
await cypressInitGenerator(host, {
|
||||
...options,
|
||||
skipFormat: true,
|
||||
addPlugin: options.addPlugin,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
createFiles(host, options);
|
||||
addProject(host, options);
|
||||
const installTask = await addLinterToCyProject(host, {
|
||||
...options,
|
||||
cypressDir: 'src',
|
||||
linter: schema.linter,
|
||||
project: options.projectName,
|
||||
overwriteExisting: true,
|
||||
});
|
||||
tasks.push(installTask);
|
||||
|
||||
if (!options.skipPackageJson) {
|
||||
tasks.push(ensureDependencies(host, options));
|
||||
}
|
||||
|
||||
if (!options.skipFormat) {
|
||||
await formatFiles(host);
|
||||
}
|
||||
return runTasksInSerial(...tasks);
|
||||
}
|
||||
|
||||
function ensureDependencies(tree: Tree, options: CypressProjectSchema) {
|
||||
const devDependencies: Record<string, string> = {
|
||||
'@types/node': typesNodeVersion,
|
||||
};
|
||||
|
||||
if (options.bundler === 'vite') {
|
||||
devDependencies['vite'] = viteVersion;
|
||||
}
|
||||
|
||||
return runTasksInSerial(
|
||||
...[
|
||||
addDependenciesToPackageJson(tree, {}, devDependencies),
|
||||
() => {
|
||||
logShowProjectCommand(options.projectName);
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
async function normalizeOptions(
|
||||
host: Tree,
|
||||
options: Schema
|
||||
): Promise<CypressProjectSchema> {
|
||||
let maybeRootProject: ProjectConfiguration;
|
||||
let isRootProject = false;
|
||||
|
||||
const projects = getProjects(host);
|
||||
// nx will set the project option for generators when ran within a project.
|
||||
// since the root project will always be set for standalone projects we can just check it here.
|
||||
if (options.project) {
|
||||
maybeRootProject = projects.get(options.project);
|
||||
}
|
||||
|
||||
if (
|
||||
maybeRootProject?.root === '.' ||
|
||||
// should still check to see if we are in a standalone based workspace
|
||||
(!maybeRootProject &&
|
||||
Array.from(projects.values()).some((config) => config.root === '.'))
|
||||
) {
|
||||
isRootProject = true;
|
||||
}
|
||||
|
||||
let { projectName, projectRoot } = await determineProjectNameAndRootOptions(
|
||||
host,
|
||||
{
|
||||
name: options.name,
|
||||
projectType: 'application',
|
||||
directory: isRootProject ? options.name : options.directory,
|
||||
projectNameAndRootFormat: isRootProject
|
||||
? 'as-provided'
|
||||
: options.projectNameAndRootFormat,
|
||||
callingGenerator: '@nx/cypress:cypress-project',
|
||||
}
|
||||
);
|
||||
|
||||
options.linter = options.linter || Linter.EsLint;
|
||||
options.bundler = options.bundler || 'webpack';
|
||||
options.addPlugin ??= process.env.NX_ADD_PLUGINS !== 'false';
|
||||
|
||||
return {
|
||||
...options,
|
||||
// other generators depend on the rootProject flag down stream
|
||||
rootProject: isRootProject,
|
||||
projectName,
|
||||
projectRoot,
|
||||
};
|
||||
}
|
||||
|
||||
export default cypressProjectGenerator;
|
||||
@ -1,10 +0,0 @@
|
||||
import { defineConfig } from 'cypress';
|
||||
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: nxE2EPreset(__dirname<% if (bundler === 'vite'){ %>,
|
||||
{
|
||||
bundler: 'vite'
|
||||
}
|
||||
<% } %>)
|
||||
});
|
||||
@ -1,13 +0,0 @@
|
||||
import { getGreeting } from '../support/app.po';
|
||||
|
||||
describe('<%= project %>', () => {
|
||||
beforeEach(() => cy.visit('/'));
|
||||
|
||||
it('should display welcome message', () => {
|
||||
// Custom command example, see `../support/commands.ts` file
|
||||
cy.login('my-email@something.com', 'myPassword');
|
||||
|
||||
// Function helper example, see `../support/app.po.ts` file
|
||||
getGreeting().contains('Welcome <%= project %>');
|
||||
});
|
||||
});
|
||||
@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io"
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export const getGreeting = () => cy.get('h1');
|
||||
@ -1,33 +0,0 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
<% if (linter === 'eslint') { %>
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace<% } %>
|
||||
declare namespace Cypress {<% if (linter === 'eslint') { %>
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars<% } %>
|
||||
interface Chainable<Subject> {
|
||||
login(email: string, password: string): void;
|
||||
}
|
||||
}
|
||||
//
|
||||
// -- This is a parent command --
|
||||
Cypress.Commands.add('login', (email, password) => {
|
||||
console.log('Custom command example: Login', email, password);
|
||||
});
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
@ -1,17 +0,0 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands';
|
||||
@ -1,10 +0,0 @@
|
||||
{
|
||||
"extends": "<%= rootTsConfigPath %>",
|
||||
"compilerOptions": {
|
||||
"sourceMap": false,
|
||||
"outDir": "<%= offsetFromRoot %>dist/out-tsc",
|
||||
"allowJs": true,
|
||||
"types": ["cypress", "node"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "cypress.config.ts"]
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
{
|
||||
"fileServerFolder": ".",
|
||||
"fixturesFolder": "./src/fixtures",
|
||||
"integrationFolder": "./src/integration",
|
||||
"modifyObstructiveCode": false,
|
||||
"supportFile": "./src/support/index.<%= ext %>",
|
||||
"pluginsFile": false,
|
||||
"video": true,
|
||||
"videosFolder": "<%= offsetFromRoot %>dist/cypress/<%= projectRoot %>/videos",
|
||||
"screenshotsFolder": "<%= offsetFromRoot %>dist/cypress/<%= projectRoot %>/screenshots",
|
||||
"chromeWebSecurity": false
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io"
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
import { getGreeting } from '../support/app.po';
|
||||
|
||||
describe('<%= project %>', () => {
|
||||
beforeEach(() => cy.visit('/'));
|
||||
|
||||
it('should display welcome message', () => {
|
||||
// Custom command example, see `../support/commands.ts` file
|
||||
cy.login('my-email@something.com', 'myPassword');
|
||||
|
||||
// Function helper example, see `../support/app.po.ts` file
|
||||
getGreeting().contains('Welcome <%= project %>');
|
||||
});
|
||||
});
|
||||
@ -1,22 +0,0 @@
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
const { preprocessTypescript } = require('@nx/cypress/plugins/preprocessor');
|
||||
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
|
||||
// Preprocess Typescript file using Nx helper
|
||||
on('file:preprocessor', preprocessTypescript(config));
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
export const getGreeting = () => cy.get('h1');
|
||||
@ -1,33 +0,0 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
<% if (linter === 'eslint') { %>
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace<% } %>
|
||||
declare namespace Cypress {<% if (linter === 'eslint') { %>
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars<% } %>
|
||||
interface Chainable<Subject> {
|
||||
login(email: string, password: string): void;
|
||||
}
|
||||
}
|
||||
//
|
||||
// -- This is a parent command --
|
||||
Cypress.Commands.add('login', (email, password) => {
|
||||
console.log('Custom command example: Login', email, password);
|
||||
});
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
@ -1,17 +0,0 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.ts is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.ts using ES2015 syntax:
|
||||
import './commands';
|
||||
@ -1,10 +0,0 @@
|
||||
{
|
||||
"extends": "<%= offsetFromRoot %>tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"sourceMap": false,
|
||||
"outDir": "<%= offsetFromRoot %>dist/out-tsc",
|
||||
"allowJs": true,
|
||||
"types": ["cypress", "node"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js"]
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
import type { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||
import type { Linter } from '@nx/eslint';
|
||||
|
||||
export interface Schema {
|
||||
project?: string;
|
||||
baseUrl?: string;
|
||||
name: string;
|
||||
directory?: string;
|
||||
projectNameAndRootFormat?: ProjectNameAndRootFormat;
|
||||
linter?: Linter;
|
||||
js?: boolean;
|
||||
skipFormat?: boolean;
|
||||
setParserOptionsProject?: boolean;
|
||||
skipPackageJson?: boolean;
|
||||
bundler?: 'webpack' | 'vite' | 'none';
|
||||
addPlugin?: boolean;
|
||||
}
|
||||
@ -1,78 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "NxCypressProjectGeneratorSchema",
|
||||
"cli": "nx",
|
||||
"title": "Create Cypress Configuration for the workspace",
|
||||
"description": "Create Cypress Configuration for the workspace.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The name of the frontend project to test.",
|
||||
"$default": {
|
||||
"$source": "projectName"
|
||||
},
|
||||
"x-priority": "important"
|
||||
},
|
||||
"baseUrl": {
|
||||
"type": "string",
|
||||
"description": "The address (with the port) which your application is running on."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the E2E Project.",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
},
|
||||
"x-prompt": "What name would you like to use for the e2e project?"
|
||||
},
|
||||
"directory": {
|
||||
"type": "string",
|
||||
"description": "A directory where the project is placed.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"projectNameAndRootFormat": {
|
||||
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
|
||||
"type": "string",
|
||||
"enum": ["as-provided", "derived"]
|
||||
},
|
||||
"linter": {
|
||||
"description": "The tool to use for running lint checks.",
|
||||
"type": "string",
|
||||
"enum": ["eslint", "none"],
|
||||
"default": "eslint"
|
||||
},
|
||||
"js": {
|
||||
"description": "Generate JavaScript files rather than TypeScript files.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"skipFormat": {
|
||||
"description": "Skip formatting files.",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"x-priority": "internal"
|
||||
},
|
||||
"setParserOptionsProject": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.",
|
||||
"default": false
|
||||
},
|
||||
"skipPackageJson": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Do not add dependencies to `package.json`.",
|
||||
"x-priority": "internal"
|
||||
},
|
||||
"bundler": {
|
||||
"description": "The Cypress bundler to use.",
|
||||
"type": "string",
|
||||
"enum": ["vite", "webpack", "none"],
|
||||
"x-prompt": "Which Cypress bundler do you want to use?",
|
||||
"default": "webpack"
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"examplesFile": "../../../docs/cypress-project-examples.md"
|
||||
}
|
||||
@ -155,11 +155,6 @@ export default defineConfig({
|
||||
exports[`convertToCypressTen convertCypressProject should infer targets with --all flag 2`] = `
|
||||
{
|
||||
"e2e": {
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "app:serve:production",
|
||||
},
|
||||
},
|
||||
"executor": "@nx/cypress:cypress",
|
||||
"options": {
|
||||
"cypressConfig": "app-e2e/cypress.config.ts",
|
||||
@ -168,11 +163,6 @@ exports[`convertToCypressTen convertCypressProject should infer targets with --a
|
||||
},
|
||||
},
|
||||
"e2e-custom": {
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "app:serve:production",
|
||||
},
|
||||
},
|
||||
"executor": "@nx/cypress:cypress",
|
||||
"options": {
|
||||
"cypressConfig": "app-e2e/cypress.config.ts",
|
||||
@ -212,11 +202,6 @@ export default defineConfig({
|
||||
exports[`convertToCypressTen convertCypressProject should not break when an invalid target is passed in 2`] = `
|
||||
{
|
||||
"e2e": {
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "app:serve:production",
|
||||
},
|
||||
},
|
||||
"executor": "@nx/cypress:cypress",
|
||||
"options": {
|
||||
"cypressConfig": "app-e2e/cypress.config.ts",
|
||||
@ -225,11 +210,6 @@ exports[`convertToCypressTen convertCypressProject should not break when an inva
|
||||
},
|
||||
},
|
||||
"e2e-custom": {
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "app:serve:production",
|
||||
},
|
||||
},
|
||||
"executor": "@nx/cypress:cypress",
|
||||
"options": {
|
||||
"cypressConfig": "app-e2e/cypress.config.ts",
|
||||
|
||||
@ -8,10 +8,11 @@ import {
|
||||
Tree,
|
||||
updateJson,
|
||||
updateProjectConfiguration,
|
||||
writeJson,
|
||||
} from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { installedCypressVersion } from '../../utils/cypress-version';
|
||||
import { cypressProjectGenerator } from '../cypress-project/cypress-project';
|
||||
import { configurationGenerator } from '../configuration/configuration';
|
||||
import {
|
||||
createSupportFileImport,
|
||||
updateImports,
|
||||
@ -51,11 +52,23 @@ describe('convertToCypressTen', () => {
|
||||
});
|
||||
mockedInstalledCypressVersion.mockReturnValue(9);
|
||||
|
||||
await cypressProjectGenerator(tree, {
|
||||
name: 'app-e2e',
|
||||
addProjectConfiguration(tree, 'app-e2e', {
|
||||
projectType: 'application',
|
||||
root: 'app-e2e',
|
||||
sourceRoot: 'app-e2e/src',
|
||||
targets: {},
|
||||
implicitDependencies: ['app'],
|
||||
tags: [],
|
||||
});
|
||||
|
||||
writeJson(tree, 'app-e2e/tsconfig.json', {
|
||||
include: ['src/**/*.ts', 'src/**/*.js'],
|
||||
});
|
||||
|
||||
await configurationGenerator(tree, {
|
||||
skipFormat: true,
|
||||
project: 'app',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
project: 'app-e2e',
|
||||
devServerTarget: 'app:serve',
|
||||
addPlugin: false,
|
||||
});
|
||||
});
|
||||
@ -296,12 +309,26 @@ describe('convertToCypressTen', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await cypressProjectGenerator(tree, {
|
||||
name: 'app-two-e2e',
|
||||
skipFormat: true,
|
||||
project: 'app-two',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
addProjectConfiguration(tree, 'app-two-e2e', {
|
||||
projectType: 'application',
|
||||
root: 'app-two-e2e',
|
||||
sourceRoot: 'app-two-e2e/src',
|
||||
targets: {},
|
||||
implicitDependencies: ['app-two'],
|
||||
tags: [],
|
||||
});
|
||||
|
||||
writeJson(tree, 'app-two-e2e/tsconfig.json', {
|
||||
include: ['src/**/*.ts', 'src/**/*.js'],
|
||||
});
|
||||
|
||||
await configurationGenerator(tree, {
|
||||
skipFormat: true,
|
||||
project: 'app-two-e2e',
|
||||
devServerTarget: 'app-two:serve',
|
||||
addPlugin: false,
|
||||
});
|
||||
|
||||
const appOneProjectConfig = readProjectConfiguration(tree, 'app-e2e');
|
||||
appOneProjectConfig.targets['e2e'].options.cypressConfig = 'cypress.json';
|
||||
updateProjectConfiguration(tree, 'app-e2e', appOneProjectConfig);
|
||||
@ -338,11 +365,6 @@ describe('convertToCypressTen', () => {
|
||||
devServerTarget: 'app:serve',
|
||||
testingType: 'e2e',
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
devServerTarget: 'app:serve:production',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(readJson(tree, 'app-two-e2e/tsconfig.json').include).toEqual([
|
||||
'src/**/*.ts',
|
||||
@ -360,11 +382,6 @@ describe('convertToCypressTen', () => {
|
||||
devServerTarget: 'app-two:serve',
|
||||
testingType: 'e2e',
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
devServerTarget: 'app-two:serve:production',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,15 +1,8 @@
|
||||
// Prevent sensitive keys from being bundled when source code uses entire `process.env` object rather than individual keys (e.g. `process.env.NX_FOO`).
|
||||
// TODO(v19): Only env vars prefixed with NX_PUBLIC should be bundled. This is a breaking change so we won't do it in v18.
|
||||
const excludedKeys = ['NX_CLOUD_ACCESS_TOKEN', 'NX_CLOUD_ENCRYPTION_KEY'];
|
||||
|
||||
export function getClientEnvironment(): Record<string, string> {
|
||||
const NX_APP = /^NX_/i;
|
||||
const nxPublicKeyRegex = /^NX_PUBLIC_/i;
|
||||
|
||||
return Object.keys(process.env)
|
||||
.filter(
|
||||
(key) =>
|
||||
!excludedKeys.includes(key) && (NX_APP.test(key) || key === 'NODE_ENV')
|
||||
)
|
||||
.filter((key) => nxPublicKeyRegex.test(key) || key === 'NODE_ENV')
|
||||
.reduce((env, key) => {
|
||||
env[`process.env.${key}`] = JSON.stringify(process.env[key]);
|
||||
return env;
|
||||
|
||||
@ -55,7 +55,7 @@ export default {
|
||||
* previously defined v5 of `@typescript-eslint`. v6 of `@typescript-eslint`
|
||||
* changed how configurations are defined.
|
||||
*
|
||||
* TODO(v19): re-evalute these deviations from @typescript-eslint/recommended in v19 of Nx
|
||||
* TODO(v20): re-evalute these deviations from @typescript-eslint/recommended in v19 of Nx
|
||||
*/
|
||||
'no-extra-semi': 'off',
|
||||
'@typescript-eslint/no-extra-semi': 'error',
|
||||
|
||||
@ -38,7 +38,7 @@ export default {
|
||||
* previously defined v5 of `@typescript-eslint`. v6 of `@typescript-eslint`
|
||||
* changed how configurations are defined.
|
||||
*
|
||||
* TODO(v19): re-evalute these deviations from @typescript-eslint/recommended in v19 of Nx
|
||||
* TODO(v20): re-evalute these deviations from @typescript-eslint/recommended in v19 of Nx
|
||||
*/
|
||||
'no-extra-semi': 'off',
|
||||
'@typescript-eslint/no-extra-semi': 'error',
|
||||
|
||||
@ -65,6 +65,12 @@
|
||||
"version": "18.0.0-beta.0",
|
||||
"description": "Remove the offset from the outputDir of the export target",
|
||||
"implementation": "./src/migrations/update-18-0-0/change-outputDir-export-target"
|
||||
},
|
||||
"update-19-0-0-change-webpack-to-metro": {
|
||||
"version": "19.0.0-beta.9",
|
||||
"cli": "nx",
|
||||
"description": "Change webpack to metro in expo projects",
|
||||
"factory": "./src/migrations/update-19-0-0/change-webpack-to-metro"
|
||||
}
|
||||
},
|
||||
"packageJsonUpdates": {
|
||||
|
||||
@ -35,7 +35,6 @@
|
||||
"node-fetch": "^2.6.7",
|
||||
"tslib": "^2.3.0",
|
||||
"tsconfig-paths": "^4.1.2",
|
||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
||||
"@nx/jest": "file:../jest",
|
||||
"@nx/js": "file:../js",
|
||||
"@nx/eslint": "file:../eslint",
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin';
|
||||
import { resolve } from 'path';
|
||||
|
||||
/**
|
||||
* @deprecated TODO(v19) use bundler: 'metro' instead, will be removed in v19
|
||||
* This function add additional rules to expo's webpack config to make expo web working
|
||||
*/
|
||||
export async function withNxWebpack(config) {
|
||||
// add additional rule to load files under libs
|
||||
const rules = config.module.rules.find((rule) =>
|
||||
Array.isArray(rule.oneOf)
|
||||
)?.oneOf;
|
||||
if (rules) {
|
||||
rules.push({
|
||||
test: /\.(mjs|[jt]sx?)$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: require.resolve('@nx/webpack/src/utils/web-babel-loader.js'),
|
||||
options: {
|
||||
presets: [
|
||||
[
|
||||
'@nx/react/babel',
|
||||
{
|
||||
runtime: 'automatic',
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (!config.resolve) {
|
||||
config.resolve = {};
|
||||
}
|
||||
if (!config.resolve.plugins) {
|
||||
config.resolve.plugins = [];
|
||||
}
|
||||
const extensions = ['.ts', '.tsx', '.mjs', '.js', '.jsx'];
|
||||
const tsConfigPath = resolve(__dirname, 'tsconfig.json');
|
||||
config.resolve.plugins.push(
|
||||
new TsconfigPathsPlugin({
|
||||
configFile: tsConfigPath,
|
||||
extensions,
|
||||
})
|
||||
);
|
||||
config.resolve.fallback = {
|
||||
...config.resolve.fallback,
|
||||
crypto: require.resolve('crypto-browserify'),
|
||||
stream: require.resolve('stream-browserify'),
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
getProjects,
|
||||
readJson,
|
||||
Tree,
|
||||
} from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import update from './change-webpack-to-metro';
|
||||
|
||||
describe('change-webpack-to-metro', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(async () => {
|
||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
addProjectConfiguration(tree, 'product', {
|
||||
root: 'apps/product',
|
||||
sourceRoot: 'apps/product/src',
|
||||
targets: {
|
||||
start: {
|
||||
executor: '@nx/expo:start',
|
||||
},
|
||||
'export-web': {
|
||||
executor: '@nx/expo:export',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
tree.write(
|
||||
`apps/product/app.json`,
|
||||
JSON.stringify({
|
||||
expo: {
|
||||
web: {
|
||||
bundler: 'webpack',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it(`should update project.json with target export-web and change app.json`, async () => {
|
||||
await update(tree);
|
||||
|
||||
getProjects(tree).forEach((project) => {
|
||||
expect(project.targets['export-web']).toEqual({
|
||||
executor: '@nx/expo:export',
|
||||
options: {
|
||||
bundler: 'metro',
|
||||
},
|
||||
});
|
||||
|
||||
expect(project.targets['web']).toBeUndefined();
|
||||
const appJson = readJson(tree, `apps/product/app.json`);
|
||||
expect(appJson.expo.web.bundler).toEqual('metro');
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,38 @@
|
||||
import {
|
||||
Tree,
|
||||
formatFiles,
|
||||
getProjects,
|
||||
updateProjectConfiguration,
|
||||
updateJson,
|
||||
} from '@nx/devkit';
|
||||
|
||||
/**
|
||||
* Migration:
|
||||
* - change target 'export-web'
|
||||
* - change bundler to 'metro'
|
||||
*/
|
||||
export default async function update(tree: Tree) {
|
||||
const projects = getProjects(tree);
|
||||
|
||||
for (const [name, config] of projects.entries()) {
|
||||
if (config.targets?.['start']?.executor === '@nx/expo:start') {
|
||||
if (config.targets['web']) {
|
||||
delete config.targets['web'];
|
||||
}
|
||||
if (config.targets['export-web']) {
|
||||
config.targets['export-web'].options.bundler = 'metro';
|
||||
}
|
||||
|
||||
updateJson(tree, `${config.root}/app.json`, (appJson) => {
|
||||
if (appJson.expo?.web) {
|
||||
appJson.expo.web.bundler = 'metro';
|
||||
}
|
||||
return appJson;
|
||||
});
|
||||
}
|
||||
|
||||
updateProjectConfiguration(tree, name, config);
|
||||
}
|
||||
|
||||
await formatFiles(tree);
|
||||
}
|
||||
@ -37,7 +37,7 @@ module.exports = function (api: any, options: NxWebBabelPresetOptions = {}) {
|
||||
|
||||
// Determine settings for `@babel//babel-plugin-transform-class-properties`,
|
||||
// so that we can sync the `loose` option with `@babel/preset-env`.
|
||||
// TODO(v19): Remove classProperties since it's no longer needed, now that the class props transform is in preset-env.
|
||||
// TODO(v20): Remove classProperties since it's no longer needed, now that the class props transform is in preset-env.
|
||||
const loose = options.classProperties?.loose ?? options.loose ?? true;
|
||||
if (options.classProperties) {
|
||||
logger.warn(
|
||||
|
||||
@ -327,12 +327,7 @@ export function getNextConfig(
|
||||
}
|
||||
|
||||
/**
|
||||
* 5. Add env variables prefixed with NX_
|
||||
*/
|
||||
addNxEnvVariables(config);
|
||||
|
||||
/**
|
||||
* 6. Add SVGR support if option is on.
|
||||
* 5. Add SVGR support if option is on.
|
||||
*/
|
||||
|
||||
// Default SVGR support to be on for projects.
|
||||
@ -393,40 +388,6 @@ export function getNextConfig(
|
||||
};
|
||||
}
|
||||
|
||||
// Prevent sensitive keys from being bundled when source code uses entire `process.env` object rather than individual keys (e.g. `process.env.NX_FOO`).
|
||||
// TODO(v19): BREAKING: Only support NEXT_PUBLIC_ env vars and ignore NX_ vars since this is a standard Next.js feature.
|
||||
const excludedKeys = ['NX_CLOUD_ACCESS_TOKEN', 'NX_CLOUD_ENCRYPTION_KEY'];
|
||||
|
||||
function getNxEnvironmentVariables() {
|
||||
return Object.keys(process.env)
|
||||
.filter((env) => !excludedKeys.includes(env) && /^NX_/i.test(env))
|
||||
.reduce((env, key) => {
|
||||
env[key] = process.env[key];
|
||||
return env;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO(v19)
|
||||
* @deprecated Use Next.js 9.4+ built-in support for environment variables. Reference https://nextjs.org/docs/pages/api-reference/next-config-js/env
|
||||
*/
|
||||
function addNxEnvVariables(config: any) {
|
||||
const maybeDefinePlugin = config.plugins?.find((plugin) => {
|
||||
return plugin.definitions?.['process.env.NODE_ENV'];
|
||||
});
|
||||
|
||||
if (maybeDefinePlugin) {
|
||||
const env = getNxEnvironmentVariables();
|
||||
|
||||
Object.entries(env)
|
||||
.map(([name, value]) => [`process.env.${name}`, `"${value}"`])
|
||||
.filter(([name]) => !maybeDefinePlugin.definitions[name])
|
||||
.forEach(
|
||||
([name, value]) => (maybeDefinePlugin.definitions[name] = value)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function getAliasForProject(
|
||||
node: ProjectGraphProjectNode,
|
||||
paths: Record<string, string[]>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { NextConfigFn } from '../src/utils/config';
|
||||
import { WithNxOptions } from './with-nx';
|
||||
|
||||
// TODO(v19): Remove file, it is here until users migrate over to SASS manually.
|
||||
// TODO(v20): Remove file, it is here until users migrate over to SASS manually.
|
||||
export function withStylus(
|
||||
configOrFn: WithNxOptions | NextConfigFn
|
||||
): NextConfigFn {
|
||||
|
||||
@ -36,7 +36,7 @@ interface Schema {
|
||||
skipFormat?: boolean;
|
||||
}
|
||||
|
||||
// TODO(v19): Remove this logic once we no longer derive directory.
|
||||
// TODO(v20): Remove this logic once we no longer derive directory.
|
||||
function maybeGetDerivedDirectory(host: Tree, options: Schema): string {
|
||||
if (!options.project) return options.directory;
|
||||
const workspace = getProjects(host);
|
||||
|
||||
@ -56,7 +56,7 @@ async function normalizeOptions(host: Tree, options: Schema) {
|
||||
|
||||
if (options.project) {
|
||||
// Legacy behavior, detect app vs page router from specified project.
|
||||
// TODO(v19): remove this logic
|
||||
// TODO(v20): remove this logic
|
||||
const project = readProjectConfiguration(host, options.project);
|
||||
// app/ is a reserved folder in nextjs so it is safe to check it's existence
|
||||
isAppRouter =
|
||||
|
||||
@ -283,7 +283,10 @@ function buildTargetWebpack(
|
||||
|
||||
return async () => {
|
||||
customWebpack = await customWebpack;
|
||||
// TODO(v19): Once webpackConfig is always set in @nx/webpack:webpack and isolatedConfig is removed, we no longer need this default.
|
||||
// TODO(v20): Component testing need to be agnostic of the underlying executor. With Crystal, we're not using `@nx/webpack:webpack` by default.
|
||||
// We need to decouple CT from the build target of the app, we just care about bundler config (e.g. webpack.config.js).
|
||||
// The generated setup should support both Webpack and Vite as documented here: https://docs.cypress.io/guides/component-testing/react/overview
|
||||
// Related issue: https://github.com/nrwl/nx/issues/21546
|
||||
const configure = composePluginsSync(withNx(), withWeb());
|
||||
const defaultWebpack = configure(
|
||||
{},
|
||||
|
||||
@ -18,24 +18,16 @@ import { mergePlugins } from './merge-plugins';
|
||||
import { withReact } from '../with-react';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
// Prevent sensitive keys from being bundled when source code uses entire `process.env` object rather than individual keys (e.g. `process.env.NX_FOO`).
|
||||
// TODO(v19): BREAKING: Only env vars prefixed with NX_PUBLIC should be bundled. This is a breaking change so we won't do it in v18.
|
||||
const excludedKeys = ['NX_CLOUD_ACCESS_TOKEN', 'NX_CLOUD_ENCRYPTION_KEY'];
|
||||
|
||||
// This is shamelessly taken from CRA and modified for NX use
|
||||
// https://github.com/facebook/create-react-app/blob/4784997f0682e75eb32a897b4ffe34d735912e6c/packages/react-scripts/config/env.js#L71
|
||||
function getClientEnvironment(mode) {
|
||||
// Grab NODE_ENV and NX_* and STORYBOOK_* environment variables and prepare them to be
|
||||
// injected into the application via DefinePlugin in webpack configuration.
|
||||
const NX_PREFIX = /^NX_/i;
|
||||
const NX_PREFIX = /^NX_PUBLIC_/i;
|
||||
const STORYBOOK_PREFIX = /^STORYBOOK_/i;
|
||||
|
||||
const raw = Object.keys(process.env)
|
||||
.filter(
|
||||
(key) =>
|
||||
!excludedKeys.includes(key) &&
|
||||
(NX_PREFIX.test(key) || STORYBOOK_PREFIX.test(key))
|
||||
)
|
||||
.filter((key) => NX_PREFIX.test(key) || STORYBOOK_PREFIX.test(key))
|
||||
.reduce(
|
||||
(env, key) => {
|
||||
env[key] = process.env[key];
|
||||
|
||||
@ -5,22 +5,18 @@ description: Examples and a short guide on how to use the @nx/webpack:webpack bu
|
||||
|
||||
`project.json`:
|
||||
|
||||
```json
|
||||
```json5
|
||||
//...
|
||||
"my-app": {
|
||||
"targets": {
|
||||
//...
|
||||
"build": {
|
||||
"executor": "@nx/webpack:webpack",
|
||||
//...
|
||||
//...
|
||||
"options": {
|
||||
...
|
||||
},
|
||||
//...
|
||||
}
|
||||
},
|
||||
}
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/webpack:webpack",
|
||||
"options": {
|
||||
"webpackConfig": "apps/my-app/webpack.config.js"
|
||||
}
|
||||
},
|
||||
//...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -40,21 +36,19 @@ Copying from the [Babel documentation](https://babeljs.io/docs/config-files#root
|
||||
|
||||
Setting `babelUpwardRootMode` to `true` in your `project.json` will set `rootMode` option to `upward` in the Babel config. You may want the `upward` mode in a monorepo when projects must apply their individual `.babelrc` file. We recommend that you don't set it at all, so it will use the default to `false` as the `upward` mode brings additional complexity to the build process.
|
||||
|
||||
```json
|
||||
```json5
|
||||
//...
|
||||
"my-app": {
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/webpack:webpack",
|
||||
"options": {
|
||||
"babelUpwardRootMode": true,
|
||||
//...
|
||||
},
|
||||
//...
|
||||
"webpackConfig": "apps/my-app/webpack.config.js",
|
||||
"babelUpwardRootMode": true
|
||||
}
|
||||
},
|
||||
//...
|
||||
},
|
||||
//...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -94,23 +88,19 @@ In workspace above, if `demo` imports `a` and `b`, it will apply the config `lib
|
||||
|
||||
If you have a custom Babel config file (i.e. not `.babelrc`), you can use the `configFile` option as follows:
|
||||
|
||||
```json
|
||||
```json5
|
||||
//...
|
||||
"my-app": {
|
||||
"targets": {
|
||||
//...
|
||||
"build": {
|
||||
"executor": "@nx/webpack:webpack",
|
||||
//...
|
||||
"options": {
|
||||
//...
|
||||
"babelConfig": "apps/my-app/.babelrc.custom.json",
|
||||
},
|
||||
"configurations": {
|
||||
...
|
||||
}
|
||||
},
|
||||
}
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/webpack:webpack",
|
||||
"options": {
|
||||
"webpackConfig": "apps/my-app/webpack.config.js",
|
||||
"babelConfig": "apps/my-app/.babelrc.custom.json",
|
||||
}
|
||||
},
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -133,21 +123,15 @@ Set `isolatedConfig` to `true` in your `project.json` file in the `build` target
|
||||
```json
|
||||
//...
|
||||
"my-app": {
|
||||
"targets": {
|
||||
//...
|
||||
"build": {
|
||||
"executor": "@nx/webpack:webpack",
|
||||
//...
|
||||
"options": {
|
||||
//...
|
||||
"webpackConfig": "apps/my-app/webpack.config.js",
|
||||
"isolatedConfig": true
|
||||
},
|
||||
"configurations": {
|
||||
...
|
||||
}
|
||||
},
|
||||
}
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/webpack:webpack",
|
||||
"options": {
|
||||
"webpackConfig": "apps/my-app/webpack.config.js",
|
||||
"isolatedConfig": true
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -47,7 +47,7 @@ export interface WebpackExecutorOptions {
|
||||
extractLicenses?: boolean;
|
||||
fileReplacements?: FileReplacement[];
|
||||
generatePackageJson?: boolean;
|
||||
// TODO(v19): Remove this option
|
||||
// TODO(v20): Remove this option
|
||||
/** @deprecated set webpackConfig and provide an explicit webpack.config.js file (See: https://nx.dev/recipes/webpack/webpack-config-setup) */
|
||||
isolatedConfig?: boolean;
|
||||
standardWebpackConfigFunction?: boolean;
|
||||
|
||||
@ -1,14 +1,10 @@
|
||||
// Prevent sensitive keys from being bundled when source code uses entire `process.env` object rather than individual keys (e.g. `process.env.NX_FOO`).
|
||||
// TODO(v19): Only env vars prefixed with NX_PUBLIC should be bundled. This is a breaking change so we won't do it in v18.
|
||||
const excludedKeys = ['NX_CLOUD_ACCESS_TOKEN', 'NX_CLOUD_ENCRYPTION_KEY'];
|
||||
|
||||
export function getClientEnvironment(mode?: string) {
|
||||
// Grab NODE_ENV and NX_* environment variables and prepare them to be
|
||||
// Grab NODE_ENV and NX_PUBLIC_* environment variables and prepare them to be
|
||||
// injected into the application via DefinePlugin in webpack configuration.
|
||||
const NX_APP = /^NX_/i;
|
||||
const nxPublicKeyRegex = /^NX_PUBLIC_/i;
|
||||
|
||||
const raw = Object.keys(process.env)
|
||||
.filter((key) => !excludedKeys.includes(key) && NX_APP.test(key))
|
||||
.filter((key) => nxPublicKeyRegex.test(key))
|
||||
.reduce(
|
||||
(env, key) => {
|
||||
env[key] = process.env[key];
|
||||
|
||||
@ -2,7 +2,7 @@ import { interpolateEnvironmentVariablesToIndex } from './interpolate-env-variab
|
||||
|
||||
describe('interpolateEnvironmentVariablesToIndex()', () => {
|
||||
const envDefaults = {
|
||||
NX_VARIABLE: 'foo',
|
||||
NX_PUBLIC_VARIABLE: 'foo',
|
||||
SOME_OTHER_VARIABLE: 'bar',
|
||||
DEPLOY_URL: 'baz',
|
||||
};
|
||||
@ -14,7 +14,7 @@ describe('interpolateEnvironmentVariablesToIndex()', () => {
|
||||
|
||||
test('default env variables', () => {
|
||||
const content = `
|
||||
<div>Nx Variable: %NX_VARIABLE%</div>
|
||||
<div>Nx Variable: %NX_PUBLIC_VARIABLE%</div>
|
||||
<div>Some other variable: %SOME_OTHER_VARIABLE%</div>
|
||||
<div>Deploy Url: %DEPLOY_URL%</div>
|
||||
`;
|
||||
@ -28,7 +28,7 @@ describe('interpolateEnvironmentVariablesToIndex()', () => {
|
||||
|
||||
test('Deploy url set as option overrides DEPLOY_URL env variable', () => {
|
||||
const content = `
|
||||
<div>Nx Variable: %NX_VARIABLE%</div>
|
||||
<div>Nx Variable: %NX_PUBLIC_VARIABLE%</div>
|
||||
<div>Some other variable: %SOME_OTHER_VARIABLE%</div>
|
||||
<div>Deploy Url: %DEPLOY_URL%</div>
|
||||
`;
|
||||
@ -45,7 +45,7 @@ describe('interpolateEnvironmentVariablesToIndex()', () => {
|
||||
test('No deploy url provided via either option', () => {
|
||||
delete process.env.DEPLOY_URL;
|
||||
const content = `
|
||||
<div>Nx Variable: %NX_VARIABLE%</div>
|
||||
<div>Nx Variable: %NX_PUBLIC_VARIABLE%</div>
|
||||
<div>Some other variable: %SOME_OTHER_VARIABLE%</div>
|
||||
<div>Deploy Url: %DEPLOY_URL%</div>
|
||||
`;
|
||||
@ -60,7 +60,7 @@ describe('interpolateEnvironmentVariablesToIndex()', () => {
|
||||
test('NX_ prefixed option present in index.html, but not present in process.env', () => {
|
||||
delete process.env.DEPLOY_URL;
|
||||
const content = `
|
||||
<div>Nx Variable: %NX_VARIABLE%</div>
|
||||
<div>Nx Variable: %NX_PUBLIC_VARIABLE%</div>
|
||||
<div>Some other variable: %SOME_OTHER_VARIABLE%</div>
|
||||
<div>Some other nx_variable: %NX_SOME_OTHER_VARIABLE%</div>
|
||||
<div>Deploy Url: %DEPLOY_URL%</div>
|
||||
|
||||
@ -6,14 +6,10 @@ export function interpolateEnvironmentVariablesToIndex(
|
||||
return interpolateEnvironmentVariables(contents, environmentVariables as any);
|
||||
}
|
||||
|
||||
const NX_PREFIX = /^NX_/i;
|
||||
|
||||
// Prevent sensitive keys from being bundled when source code uses entire `process.env` object rather than individual keys (e.g. `process.env.NX_FOO`).
|
||||
// TODO(v19): Only env vars prefixed with NX_PUBLIC should be bundled. This is a breaking change so we won't do it in v18.
|
||||
const excludedKeys = ['NX_CLOUD_ACCESS_TOKEN', 'NX_CLOUD_ENCRYPTION_KEY'];
|
||||
const NX_PREFIX = /^NX_PUBLIC_/i;
|
||||
|
||||
function isNxEnvironmentKey(x: string): boolean {
|
||||
return !excludedKeys.includes(x) && NX_PREFIX.test(x);
|
||||
return NX_PREFIX.test(x);
|
||||
}
|
||||
|
||||
function getClientEnvironment(deployUrl: string) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user