Compare commits

...

27 Commits

Author SHA1 Message Date
Craigory Coppola 548745799c fix(core): update pty version to add windows specific flags (#22711)
(cherry picked from commit 32144bb702)
2024-04-08 17:28:28 -04:00
Nicholas Cunningham cb05dbbae7 fix(nextjs): Adding tailwind should work when creating an app OOTB (#22709)
(cherry picked from commit 50d89c7d5a)
2024-04-08 17:28:25 -04:00
Leosvel Pérez Espinosa 91a2dfc0ae fix(angular): fix dynamic module federation generation (#22724)
(cherry picked from commit 18a2878de6)
2024-04-08 17:28:23 -04:00
Jack Hsu b9f9ac102e fix(js): propagate error from child process to @nx/js:node executor (#22705)
(cherry picked from commit a9e554ff79)
2024-04-05 17:58:34 -04:00
Jack Hsu 600fc0177d fix(bundling): print errors from rollup build (#22707)
(cherry picked from commit 8a9e2fd543)
2024-04-05 17:58:33 -04:00
Emily Xiong 4188589251 fix(core): not passing props of run-commands to underlying command (#22595)
(cherry picked from commit 9d4891c323)
2024-04-05 17:58:30 -04:00
Craigory Coppola e29478638c fix(core): errors from create dependencies should show properly (#22695)
(cherry picked from commit b9ecf1ab5a)
2024-04-05 17:58:29 -04:00
Jack Hsu b91fdc0c38 fix(webpack): remove url-loader from dependencies since it is replaced by asset modules (#22698)
(cherry picked from commit 900db73c1d)
2024-04-05 17:58:29 -04:00
Colum Ferry 99ed18a270 fix(module-federation): serve dynamic remotes statically in their own processes (#22688)
(cherry picked from commit 640c61dd1a)
2024-04-05 17:58:21 -04:00
Colum Ferry e4c4697f69 fix(js): append target when generating tmp tsconfig to prevent conflicts #21396 (#22671)
(cherry picked from commit 1c6118e140)
2024-04-05 17:58:20 -04:00
Colum Ferry 313414030e feat(core): list crystal plugins with nx report (#22649)
(cherry picked from commit 123932527a)
2024-04-05 17:58:19 -04:00
Emily Xiong 7132551b19 fix(core): write terminal output to cache folder (#22673)
(cherry picked from commit c51a084eea)
2024-04-05 17:57:48 -04:00
Miroslav Jonaš 6a143b41f9 fix(misc): fix optional branch tracking on ci pipeline (#22652)
(cherry picked from commit 3b182e4650)
2024-04-05 17:57:47 -04:00
Jack Hsu 1b80d01cf9 fix(webpack): support standard webpack config with @nx/webpack:dev-server (#22660)
(cherry picked from commit 4fdf862700)
2024-04-05 17:57:46 -04:00
Nicholas Cunningham 042212eebd fix(nx-dev): Update urls that are 404 (#22653)
(cherry picked from commit 92a2cc775d)
2024-04-05 17:57:46 -04:00
Colum Ferry d9883f5e91 fix(testing): app generators should create correct e2e config at generation time (#22565)
(cherry picked from commit 7f00927655)
2024-04-05 17:57:42 -04:00
Colum Ferry 9a3c4fad39 fix(vite): ensure cache is created correctly for separate vite and vitest config files #22244 (#22618)
(cherry picked from commit 2149fa292d)
2024-04-05 17:57:41 -04:00
Jack Hsu f6f2b3a9d9 fix(webpack): bring back previous SVG and SVGR behavior for React projects (#22628)
(cherry picked from commit 270788e47b)
2024-04-05 17:57:38 -04:00
Austin Fahsl 9dd97c43a1 fix(release): respect root .npmrc registry settings for publishing
(cherry picked from commit 12afa20210)
2024-04-05 17:57:37 -04:00
Altan Stalker 66e254ca26 fix(js): update jest snapshot after vite-plugin-dts bump (#22621)
(cherry picked from commit 902da5db58)
2024-04-05 17:57:36 -04:00
Leosvel Pérez Espinosa 30cfb2b1dd fix(testing): fix playwright executor uiPort option schema (#22610)
(cherry picked from commit 153096574a)
2024-04-05 17:57:32 -04:00
Jason Jean 1e335e1aed fix(core): do not assume workspace inputs cause all projects to be af… (#22573)
(cherry picked from commit 9419bad776)
2024-04-02 14:12:02 -04:00
Colum Ferry 78f5c62f3e feat(vite): migrate to latest vite-plugin-dts (#22614)
(cherry picked from commit 9e13c8537a)
2024-04-02 14:12:01 -04:00
Leosvel Pérez Espinosa b224d653b3 fix(angular): fix @nx/angular/src/utils entry point (#22609)
(cherry picked from commit 814d773623)
2024-04-02 14:11:56 -04:00
Leosvel Pérez Espinosa 0006cd1ce2 fix(angular): respect skipPackageJson correctly in library generator (#22608)
(cherry picked from commit 5d5e88df90)
2024-04-02 14:11:56 -04:00
Leosvel Pérez Espinosa e1f1396532 fix(angular): prevent false positive validation due to option default value in dev-server executor (#22606)
(cherry picked from commit 2aed31fc0a)
2024-04-02 14:11:55 -04:00
arekkubaczkowski be25f33ab1 fix(react-native): storybook relative paths (#22031)
Co-authored-by: Emily Xiong <xiongemi@gmail.com>
(cherry picked from commit 120cde6d52)
2024-04-02 14:11:55 -04:00
136 changed files with 2411 additions and 481 deletions
Generated
+2 -2
View File
@@ -483,7 +483,7 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "filedescriptor"
version = "0.8.3"
source = "git+https://github.com/cammisuli/wezterm#3db3f2d05f7188af9c7527214605f18332fac06d"
source = "git+https://github.com/cammisuli/wezterm?rev=b538ee29e1e89eeb4832fb35ae095564dce34c29#b538ee29e1e89eeb4832fb35ae095564dce34c29"
dependencies = [
"libc",
"thiserror",
@@ -1660,7 +1660,7 @@ dependencies = [
[[package]]
name = "portable-pty"
version = "0.8.1"
source = "git+https://github.com/cammisuli/wezterm#3db3f2d05f7188af9c7527214605f18332fac06d"
source = "git+https://github.com/cammisuli/wezterm?rev=b538ee29e1e89eeb4832fb35ae095564dce34c29#b538ee29e1e89eeb4832fb35ae095564dce34c29"
dependencies = [
"anyhow",
"bitflags 1.3.2",
+24
View File
@@ -2300,6 +2300,14 @@
"children": [],
"disableCollapsible": false
},
{
"name": "Configure Custom Registries",
"path": "/recipes/nx-release/configure-custom-registries",
"id": "configure-custom-registries",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Publish in CI/CD",
"path": "/recipes/nx-release/publish-in-ci-cd",
@@ -4140,6 +4148,14 @@
"children": [],
"disableCollapsible": false
},
{
"name": "Configure Custom Registries",
"path": "/recipes/nx-release/configure-custom-registries",
"id": "configure-custom-registries",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Publish in CI/CD",
"path": "/recipes/nx-release/publish-in-ci-cd",
@@ -4207,6 +4223,14 @@
"children": [],
"disableCollapsible": false
},
{
"name": "Configure Custom Registries",
"path": "/recipes/nx-release/configure-custom-registries",
"id": "configure-custom-registries",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Publish in CI/CD",
"path": "/recipes/nx-release/publish-in-ci-cd",
+33
View File
@@ -3145,6 +3145,17 @@
"path": "/recipes/nx-release/automatically-version-with-conventional-commits",
"tags": ["nx-release"]
},
{
"id": "configure-custom-registries",
"name": "Configure Custom Registries",
"description": "",
"mediaImage": "",
"file": "shared/recipes/nx-release/configure-custom-registries",
"itemList": [],
"isExternal": false,
"path": "/recipes/nx-release/configure-custom-registries",
"tags": ["nx-release"]
},
{
"id": "publish-in-ci-cd",
"name": "Publish in CI/CD",
@@ -5668,6 +5679,17 @@
"path": "/recipes/nx-release/automatically-version-with-conventional-commits",
"tags": ["nx-release"]
},
{
"id": "configure-custom-registries",
"name": "Configure Custom Registries",
"description": "",
"mediaImage": "",
"file": "shared/recipes/nx-release/configure-custom-registries",
"itemList": [],
"isExternal": false,
"path": "/recipes/nx-release/configure-custom-registries",
"tags": ["nx-release"]
},
{
"id": "publish-in-ci-cd",
"name": "Publish in CI/CD",
@@ -5761,6 +5783,17 @@
"path": "/recipes/nx-release/automatically-version-with-conventional-commits",
"tags": ["nx-release"]
},
"/recipes/nx-release/configure-custom-registries": {
"id": "configure-custom-registries",
"name": "Configure Custom Registries",
"description": "",
"mediaImage": "",
"file": "shared/recipes/nx-release/configure-custom-registries",
"itemList": [],
"isExternal": false,
"path": "/recipes/nx-release/configure-custom-registries",
"tags": ["nx-release"]
},
"/recipes/nx-release/publish-in-ci-cd": {
"id": "publish-in-ci-cd",
"name": "Publish in CI/CD",
+7
View File
@@ -532,6 +532,13 @@
"name": "Automatically Version with Conventional Commits",
"path": "/recipes/nx-release/automatically-version-with-conventional-commits"
},
{
"description": "",
"file": "shared/recipes/nx-release/configure-custom-registries",
"id": "configure-custom-registries",
"name": "Configure Custom Registries",
"path": "/recipes/nx-release/configure-custom-registries"
},
{
"description": "",
"file": "shared/recipes/nx-release/publish-in-ci-cd",
@@ -51,7 +51,7 @@
"componentDir",
"componentFileName"
],
"examplesFile": "{% callout type=\"caution\" title=\"Can I use component testing?\" %}\nAngular component testing with Nx requires **Cypress version 10.7.0** and up.\n\nYou can migrate with to v10 via the [migrate-to-cypress-10 generator](/packages/cypress/generators/migrate-to-cypress-10).\n\nThis generator is for Cypress based component testing.\n\nIf you're wanting to create Cypress tests for a Storybook story, then check out the [component-cypress-spec generator docs](/packages/angular/generators/component-cypress-spec)\n\nIf you're wanting to create Storybook stories for a component, then check out the [stories generator docs](/packages/angular/generators/stories) or [component-story generator docs](/packages/angular/generators/component-cypress-spec)\n{% /callout %}\n\nThis generator is used to create a Cypress component test file for a given Angular component.\n\n```shell\nnx g @nx/angular:component-test --project=my-cool-angular-project --componentName=CoolBtnComponent --componentDir=src/cool-btn --componentFileName=cool-btn.component\n```\n\nTest file are generated with the `.cy.ts` suffix. this is to prevent colliding with any existing `.spec.` files contained in the project.\n\nIt's currently expected the generated `.cy.ts` file will live side by side with the component. It is also assumed the project is already setup for component testing. If it isn't, then you can run the [cypress-component-project generator](/packages/angular/generators/cypress-component-configuration) to set up the project for component testing.\n",
"examplesFile": "{% callout type=\"caution\" title=\"Can I use component testing?\" %}\nAngular component testing with Nx requires **Cypress version 10.7.0** and up.\n\nYou can migrate with to v11 via the [migrate-to-cypress-11 generator](/packages/cypress/generators/migrate-to-cypress-11).\n\nThis generator is for Cypress based component testing.\n\nIf you're wanting to create Cypress tests for a Storybook story, then check out the [component-cypress-spec generator docs](/packages/angular/generators/component-cypress-spec)\n\nIf you're wanting to create Storybook stories for a component, then check out the [stories generator docs](/packages/angular/generators/stories) or [component-story generator docs](/packages/angular/generators/component-cypress-spec)\n{% /callout %}\n\nThis generator is used to create a Cypress component test file for a given Angular component.\n\n```shell\nnx g @nx/angular:component-test --project=my-cool-angular-project --componentName=CoolBtnComponent --componentDir=src/cool-btn --componentFileName=cool-btn.component\n```\n\nTest file are generated with the `.cy.ts` suffix. this is to prevent colliding with any existing `.spec.` files contained in the project.\n\nIt's currently expected the generated `.cy.ts` file will live side by side with the component. It is also assumed the project is already setup for component testing. If it isn't, then you can run the [cypress-component-project generator](/packages/angular/generators/cypress-component-configuration) to set up the project for component testing.\n",
"presets": []
},
"description": "Creates a cypress component test file for a component.",
@@ -37,7 +37,7 @@
}
},
"required": ["project"],
"examplesFile": "{% callout type=\"caution\" title=\"Can I use component testing?\" %}\nAngular component testing with Nx requires **Cypress version 10.7.0** and up.\n\nYou can migrate with to v10 via the [migrate-to-cypress-10 generator](/packages/cypress/generators/migrate-to-cypress-10).\n\nThis generator is for Cypress based component testing.\n\nIf you want to test components via Storybook with Cypress, then check out the [storybook-configuration generator docs](/nx-api/angular/generators/storybook-configuration). However, this functionality is deprecated, and will be removed on Nx version 18.\n{% /callout %}\n\nThis generator is designed to get your Angular project up and running with Cypress Component Testing.\n\n```shell\nnx g @nx/angular:cypress-component-configuration --project=my-cool-angular-project\n```\n\nRunning this generator, adds the required files to the specified project with a preconfigured `cypress.config.ts` designed for Nx workspaces.\n\n```ts {% fileName=\"cypress.config.ts\" %}\nimport { defineConfig } from 'cypress';\nimport { nxComponentTestingPreset } from '@nx/angular/plugins/component-testing';\n\nexport default defineConfig({\n component: nxComponentTestingPreset(__filename),\n});\n```\n\nHere is an example on how to add custom options to the configuration\n\n```ts {% fileName=\"cypress.config.ts\" %}\nimport { defineConfig } from 'cypress';\nimport { nxComponentTestingPreset } from '@nx/angular/plugins/component-testing';\n\nexport default defineConfig({\n component: {\n ...nxComponentTestingPreset(__filename),\n // extra options here\n },\n});\n```\n\n## Specifying a Build Target\n\nComponent testing requires a _build target_ to correctly run the component test dev server. This option can be manually specified with `--build-target=some-angular-app:build`, but Nx will infer this usage from the [project graph](/concepts/mental-model#the-project-graph) if one isn't provided.\n\nFor Angular projects, the build target needs to be using the `@nx/angular:webpack-browser` or\n`@angular-devkit/build-angular:browser` executor.\nThe generator will throw an error if a build target can't be found and suggest passing one in manually.\n\nLetting Nx infer the build target by default\n\n```shell\nnx g @nx/angular:cypress-component-configuration --project=my-cool-angular-project\n```\n\nManually specifying the build target\n\n```shell\nnx g @nx/angular:cypress-component-configuration --project=my-cool-angular-project --build-target:some-angular-app:build --generate-tests\n```\n\n{% callout type=\"note\" title=\"Build Target with Configuration\" %}\nIf you're wanting to use a build target with a specific configuration. i.e. `my-app:build:production`,\nthen manually providing `--build-target=my-app:build:production` is the best way to do that.\n{% /callout %}\n\n## Auto Generating Tests\n\nYou can optionally use the `--generate-tests` flag to generate a test file for each component in your project.\n\n```shell\nnx g @nx/angular:cypress-component-configuration --project=my-cool-angular-project --generate-tests\n```\n\n## Running Component Tests\n\nA new `component-test` target will be added to the specified project to run your component tests.\n\n```shell\nnx g component-test my-cool-angular-project\n```\n\nHere is an example of the project configuration that is generated. The `--build-target` option is added as the `devServerTarget` which can be changed as needed.\n\n```json {% fileName=\"project.json\" %}\n{\n \"targets\" {\n \"component-test\": {\n \"executor\": \"@nx/cypress:cypress\",\n \"options\": {\n \"cypressConfig\": \"<path-to-project-root>/cypress.config.ts\",\n \"testingType\": \"component\",\n \"devServerTarget\": \"some-angular-app:build\",\n \"skipServe\": true\n }\n }\n }\n}\n```\n\n## What is bundled\n\nWhen the project being tested is a dependent of the specified `--build-target`, then **assets, scripts, and styles** are applied to the component being tested. You can determine if the project is dependent by using the [project graph](/features/explore-graph). If there is no link between the two projects, then the **assets, scripts, and styles** won't be included in the build; therefore, they will not be applied to the component. To have a link between projects, you can import from the project being tested into the specified `--build-target` project, or set the `--build-target` project to [implicitly depend](/reference/project-configuration#implicitdependencies) on the project being tested.\n\nNx also supports [React component testing](/packages/angular/generators/cypress-component-configuration).\n",
"examplesFile": "{% callout type=\"caution\" title=\"Can I use component testing?\" %}\nAngular component testing with Nx requires **Cypress version 10.7.0** and up.\n\nYou can migrate with to v11 via the [migrate-to-cypress-11 generator](/packages/cypress/generators/migrate-to-cypress-11).\n\nThis generator is for Cypress based component testing.\n\nIf you want to test components via Storybook with Cypress, then check out the [storybook-configuration generator docs](/nx-api/angular/generators/storybook-configuration). However, this functionality is deprecated, and will be removed on Nx version 18.\n{% /callout %}\n\nThis generator is designed to get your Angular project up and running with Cypress Component Testing.\n\n```shell\nnx g @nx/angular:cypress-component-configuration --project=my-cool-angular-project\n```\n\nRunning this generator, adds the required files to the specified project with a preconfigured `cypress.config.ts` designed for Nx workspaces.\n\n```ts {% fileName=\"cypress.config.ts\" %}\nimport { defineConfig } from 'cypress';\nimport { nxComponentTestingPreset } from '@nx/angular/plugins/component-testing';\n\nexport default defineConfig({\n component: nxComponentTestingPreset(__filename),\n});\n```\n\nHere is an example on how to add custom options to the configuration\n\n```ts {% fileName=\"cypress.config.ts\" %}\nimport { defineConfig } from 'cypress';\nimport { nxComponentTestingPreset } from '@nx/angular/plugins/component-testing';\n\nexport default defineConfig({\n component: {\n ...nxComponentTestingPreset(__filename),\n // extra options here\n },\n});\n```\n\n## Specifying a Build Target\n\nComponent testing requires a _build target_ to correctly run the component test dev server. This option can be manually specified with `--build-target=some-angular-app:build`, but Nx will infer this usage from the [project graph](/concepts/mental-model#the-project-graph) if one isn't provided.\n\nFor Angular projects, the build target needs to be using the `@nx/angular:webpack-browser` or\n`@angular-devkit/build-angular:browser` executor.\nThe generator will throw an error if a build target can't be found and suggest passing one in manually.\n\nLetting Nx infer the build target by default\n\n```shell\nnx g @nx/angular:cypress-component-configuration --project=my-cool-angular-project\n```\n\nManually specifying the build target\n\n```shell\nnx g @nx/angular:cypress-component-configuration --project=my-cool-angular-project --build-target:some-angular-app:build --generate-tests\n```\n\n{% callout type=\"note\" title=\"Build Target with Configuration\" %}\nIf you're wanting to use a build target with a specific configuration. i.e. `my-app:build:production`,\nthen manually providing `--build-target=my-app:build:production` is the best way to do that.\n{% /callout %}\n\n## Auto Generating Tests\n\nYou can optionally use the `--generate-tests` flag to generate a test file for each component in your project.\n\n```shell\nnx g @nx/angular:cypress-component-configuration --project=my-cool-angular-project --generate-tests\n```\n\n## Running Component Tests\n\nA new `component-test` target will be added to the specified project to run your component tests.\n\n```shell\nnx g component-test my-cool-angular-project\n```\n\nHere is an example of the project configuration that is generated. The `--build-target` option is added as the `devServerTarget` which can be changed as needed.\n\n```json {% fileName=\"project.json\" %}\n{\n \"targets\" {\n \"component-test\": {\n \"executor\": \"@nx/cypress:cypress\",\n \"options\": {\n \"cypressConfig\": \"<path-to-project-root>/cypress.config.ts\",\n \"testingType\": \"component\",\n \"devServerTarget\": \"some-angular-app:build\",\n \"skipServe\": true\n }\n }\n }\n}\n```\n\n## What is bundled\n\nWhen the project being tested is a dependent of the specified `--build-target`, then **assets, scripts, and styles** are applied to the component being tested. You can determine if the project is dependent by using the [project graph](/features/explore-graph). If there is no link between the two projects, then the **assets, scripts, and styles** won't be included in the build; therefore, they will not be applied to the component. To have a link between projects, you can import from the project being tested into the specified `--build-target` project, or set the `--build-target` project to [implicitly depend](/reference/project-configuration#implicitdependencies) on the project being tested.\n\nNx also supports [React component testing](/packages/angular/generators/cypress-component-configuration).\n",
"presets": []
},
"description": "Setup Cypress component testing for a project.",
@@ -64,7 +64,7 @@
}
},
"additionalProperties": false,
"examplesFile": "## Information\n\nThis generator is usually used as part of the process of migrating from an Angular CLI Workspace to an [Nx Integrated Workspace](/concepts/integrated-vs-package-based#integrated-repos) using `npx nx@latest init --integrated`.\n\nYou can read more about [migrating from Angular CLI to Nx here](/recipes/adopting-nx-angular).\n",
"examplesFile": "## Information\n\nThis generator is usually used as part of the process of migrating from an Angular CLI Workspace to an [Nx Integrated Workspace](/concepts/integrated-vs-package-based#integrated-repos) using `npx nx@latest init --integrated`.\n\nYou can read more about [migrating from Angular CLI to Nx here](/recipes/angular/migration/angular).\n",
"presets": []
},
"description": "Migrates an Angular CLI workspace to Nx or adds the Angular plugin to an Nx workspace.",
@@ -57,7 +57,7 @@
},
"additionalProperties": false,
"required": ["name"],
"examplesFile": "This generator will generate stories for all your components in your project. The stories will be generated using [Component Story Format 3 (CSF3)](https://storybook.js.org/blog/storybook-csf3-is-here/).\n\n```bash\nnx g @nx/angular:stories project-name\n```\n\nYou can read more about how this generator works, in the [Storybook for Angular overview page](/recipes/storybook/overview-angular#auto-generate-stories).\n\nWhen running this generator, you will be prompted to provide the following:\n\n- The `name` of the project you want to generate the configuration for.\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. You can read more about this in the [Nx Storybook interaction tests documentation page](/packages/storybook/documents/interaction-tests).\n\nYou must provide a `name` for the generator to work.\n\nBy default, this generator will also set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing). If you don't want to set up Storybook interaction tests, you can pass the `--interactionTests=false` option, but it's not recommended.\n\nThere are a number of other options available. Let's take a look at some examples.\n\n## Examples\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nx/angular:stories ui --ignorePaths=libs/ui/src/not-stories/**,**/**/src/**/*.other.*\n```\n\nThis will generate stories for all the components in the `ui` project, except for the ones in the `libs/ui/src/not-stories` directory, and also for components that their file name is of the pattern `*.other.*`.\n\nThis is useful if you have a project that contains components that are not meant to be used in isolation, but rather as part of a larger component.\n\nBy default, Nx will ignore the following paths:\n\n```text\n*.stories.ts, *.stories.tsx, *.stories.js, *.stories.jsx, *.stories.mdx\n```\n\nbut you can change this behaviour easily, as explained above.\n",
"examplesFile": "This generator will generate stories for all your components in your project. The stories will be generated using [Component Story Format 3 (CSF3)](https://storybook.js.org/blog/storybook-csf3-is-here/).\n\n```bash\nnx g @nx/angular:stories project-name\n```\n\nYou can read more about how this generator works, in the [Storybook for Angular overview page](/recipes/storybook/overview-angular#auto-generate-stories).\n\nWhen running this generator, you will be prompted to provide the following:\n\n- The `name` of the project you want to generate the configuration for.\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. You can read more about this in the [Nx Storybook interaction tests documentation page](/recipes/storybook/storybook-interaction-tests#setup-storybook-interaction-tests).\n\nYou must provide a `name` for the generator to work.\n\nBy default, this generator will also set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing). If you don't want to set up Storybook interaction tests, you can pass the `--interactionTests=false` option, but it's not recommended.\n\nThere are a number of other options available. Let's take a look at some examples.\n\n## Examples\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nx/angular:stories ui --ignorePaths=libs/ui/src/not-stories/**,**/**/src/**/*.other.*\n```\n\nThis will generate stories for all the components in the `ui` project, except for the ones in the `libs/ui/src/not-stories` directory, and also for components that their file name is of the pattern `*.other.*`.\n\nThis is useful if you have a project that contains components that are not meant to be used in isolation, but rather as part of a larger component.\n\nBy default, Nx will ignore the following paths:\n\n```text\n*.stories.ts, *.stories.tsx, *.stories.js, *.stories.jsx, *.stories.mdx\n```\n\nbut you can change this behaviour easily, as explained above.\n",
"presets": []
},
"description": "Creates stories/specs for all components declared in a project.",
@@ -91,7 +91,7 @@
},
"additionalProperties": false,
"required": ["project"],
"examplesFile": "This generator will set up Storybook for your **Angular** project. By default, starting Nx 16, Storybook v7 is used.\n\n```bash\nnx g @nx/angular:storybook-configuration project-name\n```\n\nYou can read more about how this generator works, in the [Storybook for Angular overview page](/recipes/storybook/overview-angular#generate-storybook-configuration-for-an-angular-project).\n\nWhen running this generator, you will be prompted to provide the following:\n\n- The `name` of the project you want to generate the configuration for.\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. Also, a `test-storybook` target will be generated in your project's `project.json`, with a command to invoke the [Storybook `test-runner`](https://storybook.js.org/docs/angular/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/packages/storybook/documents/interaction-tests).\n- Whether you want to `generateStories` for the components in your project. If you choose `yes`, a `.stories.ts` file will be generated next to each of your components in your project.\n\nYou must provide a `name` for the generator to work.\n\nBy default, this generator will also set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing). If you don't want to set up Storybook interaction tests, you can pass the `--interactionTests=false` option, but it's not recommended.\n\nThere are a number of other options available. Let's take a look at some examples.\n\n## Examples\n\n### Generate Storybook configuration\n\n```bash\nnx g @nx/angular:storybook-configuration ui\n```\n\nThis will generate Storybook configuration for the `ui` project using TypeScript for the Storybook configuration files (the files inside the `.storybook` directory, eg. `.storybook/main.ts`).\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nx/angular:storybook-configuration ui --generateStories=true --ignorePaths=libs/ui/src/not-stories/**,**/**/src/**/*.other.*,apps/my-app/**/*.something.ts\n```\n\nThis will generate a Storybook configuration for the `ui` project and generate stories for all components in the `libs/ui/src/lib` directory, except for the ones in the `libs/ui/src/not-stories` directory, and the ones in the `apps/my-app` directory that end with `.something.ts`, and also for components that their file name is of the pattern `*.other.*`.\n\nThis is useful if you have a project that contains components that are not meant to be used in isolation, but rather as part of a larger component.\n\nBy default, Nx will ignore the following paths:\n\n```text\n*.stories.ts, *.stories.tsx, *.stories.js, *.stories.jsx, *.stories.mdx\n```\n\nbut you can change this behaviour easily, as explained above.\n\n### Generate Storybook configuration using JavaScript\n\n```bash\nnx g @nx/angular:storybook-configuration ui --tsConfiguration=false\n```\n\nBy default, our generator generates TypeScript Storybook configuration files. You can choose to use JavaScript for the Storybook configuration files of your project (the files inside the `.storybook` directory, eg. `.storybook/main.js`).\n",
"examplesFile": "This generator will set up Storybook for your **Angular** project. By default, starting Nx 16, Storybook v7 is used.\n\n```bash\nnx g @nx/angular:storybook-configuration project-name\n```\n\nYou can read more about how this generator works, in the [Storybook for Angular overview page](/recipes/storybook/overview-angular#generate-storybook-configuration-for-an-angular-project).\n\nWhen running this generator, you will be prompted to provide the following:\n\n- The `name` of the project you want to generate the configuration for.\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. Also, a `test-storybook` target will be generated in your project's `project.json`, with a command to invoke the [Storybook `test-runner`](https://storybook.js.org/docs/angular/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/recipes/storybook/storybook-interaction-tests#setup-storybook-interaction-tests).\n- Whether you want to `generateStories` for the components in your project. If you choose `yes`, a `.stories.ts` file will be generated next to each of your components in your project.\n\nYou must provide a `name` for the generator to work.\n\nBy default, this generator will also set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing). If you don't want to set up Storybook interaction tests, you can pass the `--interactionTests=false` option, but it's not recommended.\n\nThere are a number of other options available. Let's take a look at some examples.\n\n## Examples\n\n### Generate Storybook configuration\n\n```bash\nnx g @nx/angular:storybook-configuration ui\n```\n\nThis will generate Storybook configuration for the `ui` project using TypeScript for the Storybook configuration files (the files inside the `.storybook` directory, eg. `.storybook/main.ts`).\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nx/angular:storybook-configuration ui --generateStories=true --ignorePaths=libs/ui/src/not-stories/**,**/**/src/**/*.other.*,apps/my-app/**/*.something.ts\n```\n\nThis will generate a Storybook configuration for the `ui` project and generate stories for all components in the `libs/ui/src/lib` directory, except for the ones in the `libs/ui/src/not-stories` directory, and the ones in the `apps/my-app` directory that end with `.something.ts`, and also for components that their file name is of the pattern `*.other.*`.\n\nThis is useful if you have a project that contains components that are not meant to be used in isolation, but rather as part of a larger component.\n\nBy default, Nx will ignore the following paths:\n\n```text\n*.stories.ts, *.stories.tsx, *.stories.js, *.stories.jsx, *.stories.mdx\n```\n\nbut you can change this behaviour easily, as explained above.\n\n### Generate Storybook configuration using JavaScript\n\n```bash\nnx g @nx/angular:storybook-configuration ui --tsConfiguration=false\n```\n\nBy default, our generator generates TypeScript Storybook configuration files. You can choose to use JavaScript for the Storybook configuration files of your project (the files inside the `.storybook` directory, eg. `.storybook/main.js`).\n",
"presets": []
},
"description": "Adds Storybook configuration to a project.",
@@ -156,7 +156,7 @@
},
"additionalProperties": true,
"required": ["cypressConfig"],
"examplesFile": "Depending on your testing type, the Cypress executor is configured in different ways. The following are sample configurations that are created via the [configuration](/packages/cypress/generators/configuration) and [cypress-component-configuration](/packages/cypress/generators/cypress-component-configuration) generators.\n\n{% tabs %}\n{% tab label=\"E2E Testing\" %}\n\n```json\n\"targets\": {\n \"e2e\": {\n \"executor\": \"@nx/cypress:cypress\",\n \"options\": {\n \"cypressConfig\": \"apps/app-e2e/cypres.config.ts\",\n \"devServerTarget\": \"my-react-app:serve\",\n \"testingType\": \"e2e\"\n }\n }\n}\n```\n\n{% callout type=\"note\" title=\"API Testing\" %}\nAPI testing with Cypress is the same setup as e2e testing. Just change which `devServerTarget` is used!\n{% /callout %}\n\n### Providing a Base URL\n\nIf `devServerTarget` is provided, the url returned from started the dev server will be passed to cypress as the `baseUrl` option.\n\nDefining a `baseUrl` in the executor options will override the inferred `baseUrl` from the `devServerTarget`.\n\nThe `baseUrl` defined in the Cypress config file is the last value used if no url is found in the `devServerTarget` or executor options.\n\n### Static Serving\n\nWhen running in CI it doesn't make sense to start up a dev server since there aren't changes to watch for.\n\nYou can use [`@nx/web:file-server`](/packages/web/executors/file-server) to serve the pre-built static files of your frontend project.\n\nIn some _frontend_ application, add a 'static-serve' target.\n\n```json\n\"serve-static\": {\n \"executor\": \"@nx/web:file-server\",\n \"options\":{\n \"buildTarget\": \"frontend:build\"\n }\n}\n```\n\nIn the _e2e_ application add a configuration to change `devServerTarget` to point to the `static-serve` from the _frontend_ application\n\n```json\n\"e2e\": {\n //...\n \"configurations\": {\n \"ci\": {\n \"devServerTarget\": \"frontend:serve-static\"\n }\n }\n}\n```\n\n{% callout type=\"note\" title=\"What about Node projects?\" %}\nThe same can be done for backend node apps with [`@nx/js:node` executor](/packages/js/executors/node)\n{% /callout %}\n\n```bash\nnx e2e my-app-e2e\n```\n\n{% /tab %}\n{% tab label=\"Component Testing\" %}\n\n{% callout type=\"note\" title=\"Cypress Component Testing\" %}\nWhen adding component testing to a project, it's best to use the framework specific generator, instead `cypress-component-project` directly.\n\n- [React component testing](/packages/react/generators/cypress-component-configuration)\n- [Angular component testing](/packages/angular/generators/cypress-component-configuration)\n\n{% /callout %}\n\n```json\n\"targets\": {\n \"component-test\": {\n \"executor\": \"@nx/cypress:cypress\",\n \"options\": {\n \"cypressConfig\": \"apps/app/cypres.config.ts\",\n \"devServerTarget\": \"my-react-app:build\",\n \"testingType\": \"component\",\n \"skipServe\": true\n }\n }\n}\n```\n\nIt's important `skipServe` is set to true. Nx doesn't need to run the `devServerTarget`, Cypress creates its own dev server for component testing.\nInstead, Nx needs to know what build target to create the correct configuration to pass to Cypress, which is why it's still used in component testing.\n\n{% /tab %}\n{% /tabs %}\n\n### Environment Variables\n\nUsing [executor configurations](recipes/executors/use-executor-configurations#use-executor-configurations) offers flexibility to set environment variables\n\n```json\n\"targets\": {\n \"e2e\": {\n \"executor\": \"@nx/cypress:cypress\",\n \"options\": {\n \"cypressConfig\": \"apps/app-e2e/cypres.config.ts\",\n \"devServerTarget\": \"my-react-app:serve\",\n \"testingType\": \"e2e\"\n },\n \"configurations\": {\n \"qa\": {\n \"env\": {\n \"API_URL\": \"https://api.qa.company.com\"\n }\n },\n \"dev\": {\n \"env\": {\n \"API_URL\": \"http://localhost:3333/api\"\n }\n }\n }\n }\n}\n```\n\nRead more on different ways to use [environment variables for cypress executor](/packages/cypress#environment-variables)\n"
"examplesFile": "Depending on your testing type, the Cypress executor is configured in different ways. The following are sample configurations that are created via the [configuration](/packages/cypress/generators/configuration) and [cypress-component-configuration](/packages/cypress/generators/cypress-component-configuration) generators.\n\n{% tabs %}\n{% tab label=\"E2E Testing\" %}\n\n```json\n\"targets\": {\n \"e2e\": {\n \"executor\": \"@nx/cypress:cypress\",\n \"options\": {\n \"cypressConfig\": \"apps/app-e2e/cypres.config.ts\",\n \"devServerTarget\": \"my-react-app:serve\",\n \"testingType\": \"e2e\"\n }\n }\n}\n```\n\n{% callout type=\"note\" title=\"API Testing\" %}\nAPI testing with Cypress is the same setup as e2e testing. Just change which `devServerTarget` is used!\n{% /callout %}\n\n### Providing a Base URL\n\nIf `devServerTarget` is provided, the url returned from started the dev server will be passed to cypress as the `baseUrl` option.\n\nDefining a `baseUrl` in the executor options will override the inferred `baseUrl` from the `devServerTarget`.\n\nThe `baseUrl` defined in the Cypress config file is the last value used if no url is found in the `devServerTarget` or executor options.\n\n### Static Serving\n\nWhen running in CI it doesn't make sense to start up a dev server since there aren't changes to watch for.\n\nYou can use [`@nx/web:file-server`](/packages/web/executors/file-server) to serve the pre-built static files of your frontend project.\n\nIn some _frontend_ application, add a 'static-serve' target.\n\n```json\n\"serve-static\": {\n \"executor\": \"@nx/web:file-server\",\n \"options\":{\n \"buildTarget\": \"frontend:build\"\n }\n}\n```\n\nIn the _e2e_ application add a configuration to change `devServerTarget` to point to the `static-serve` from the _frontend_ application\n\n```json\n\"e2e\": {\n //...\n \"configurations\": {\n \"ci\": {\n \"devServerTarget\": \"frontend:serve-static\"\n }\n }\n}\n```\n\n{% callout type=\"note\" title=\"What about Node projects?\" %}\nThe same can be done for backend node apps with [`@nx/js:node` executor](/packages/js/executors/node)\n{% /callout %}\n\n```bash\nnx e2e my-app-e2e\n```\n\n{% /tab %}\n{% tab label=\"Component Testing\" %}\n\n{% callout type=\"note\" title=\"Cypress Component Testing\" %}\nWhen adding component testing to a project, it's best to use the framework specific generator, instead `cypress-component-project` directly.\n\n- [React component testing](/packages/react/generators/cypress-component-configuration)\n- [Angular component testing](/packages/angular/generators/cypress-component-configuration)\n\n{% /callout %}\n\n```json\n\"targets\": {\n \"component-test\": {\n \"executor\": \"@nx/cypress:cypress\",\n \"options\": {\n \"cypressConfig\": \"apps/app/cypres.config.ts\",\n \"devServerTarget\": \"my-react-app:build\",\n \"testingType\": \"component\",\n \"skipServe\": true\n }\n }\n}\n```\n\nIt's important `skipServe` is set to true. Nx doesn't need to run the `devServerTarget`, Cypress creates its own dev server for component testing.\nInstead, Nx needs to know what build target to create the correct configuration to pass to Cypress, which is why it's still used in component testing.\n\n{% /tab %}\n{% /tabs %}\n\n### Environment Variables\n\nUsing [executor configurations](/concepts/executors-and-configurations#executors-and-configurations) offers flexibility to set environment variables\n\n```json\n\"targets\": {\n \"e2e\": {\n \"executor\": \"@nx/cypress:cypress\",\n \"options\": {\n \"cypressConfig\": \"apps/app-e2e/cypres.config.ts\",\n \"devServerTarget\": \"my-react-app:serve\",\n \"testingType\": \"e2e\"\n },\n \"configurations\": {\n \"qa\": {\n \"env\": {\n \"API_URL\": \"https://api.qa.company.com\"\n }\n },\n \"dev\": {\n \"env\": {\n \"API_URL\": \"http://localhost:3333/api\"\n }\n }\n }\n }\n}\n```\n\nRead more on different ways to use [environment variables for cypress executor](/packages/cypress#environment-variables)\n"
},
"description": "Run Cypress E2E tests.",
"aliases": [],
@@ -46,6 +46,10 @@
"value": "less",
"label": "LESS [ https://lesscss.org ]"
},
{
"value": "tailwind",
"label": "tailwind [ https://tailwindcss.com/ ]"
},
{
"value": "styled-components",
"label": "styled-components [ https://styled-components.com ]"
@@ -41,7 +41,7 @@
}
},
"required": ["project"],
"examplesFile": "{% callout type=\"caution\" title=\"Can I use component testing?\" %}\nNext component testing with Nx requires **Cypress version 10.7.0** and up.\n\nYou can migrate with to v10 via the [migrate-to-cypress-11 generator](/packages/cypress/generators/migrate-to-cypress-11).\n\nThis generator is for Cypress based component testing.\n\nIf you want to test components via Storybook with Cypress, then check out the [storybook-configuration generator docs](/nx-api/react/generators/storybook-configuration). However, this functionality is deprecated, and will be removed on Nx version 19.\n{% /callout %}\n\nThis generator is designed to get your Next project up and running with Cypress Component Testing.\n\n```shell\nnx g @nx/next:cypress-component-configuration --project=my-cool-next-project\n```\n\nRunning this generator, adds the required files to the specified project with a preconfigured `cypress.config.ts` designed for Nx workspaces.\n\n```ts {% fileName=\"cypress.config.ts\" %}\nimport { defineConfig } from 'cypress';\nimport { nxComponentTestingPreset } from '@nx/next/plugins/component-testing';\n\nexport default defineConfig({\n component: nxComponentTestingPreset(__filename),\n});\n```\n\nHere is an example on how to add custom options to the configuration\n\n```ts {% fileName=\"cypress.config.ts\" %}\nimport { defineConfig } from 'cypress';\nimport { nxComponentTestingPreset } from '@nx/next/plugins/component-testing';\n\nexport default defineConfig({\n component: {\n ...nxComponentTestingPreset(__filename),\n // extra options here\n },\n});\n```\n\n```shell\nnx g @nx/next:cypress-component-project --project=my-cool-next-project\n```\n\n## Auto Generating Tests\n\nYou can optionally use the `--generate-tests` flag to generate a test file for each component in your project.\n\n```shell\nnx g @nx/next:cypress-component-configuration --project=my-cool-next-project --generate-tests\n```\n\n## Running Component Tests\n\nA new `component-test` target will be added to the specified project to run your component tests.\n\n```shell\nnx g component-test my-cool-next-project\n```\n\nHere is an example of the project configuration that is generated.\n\n```json {% fileName=\"project.json\" %}\n{\n \"targets\" {\n \"component-test\": {\n \"executor\": \"@nx/cypress:cypress\",\n \"options\": {\n \"cypressConfig\": \"<path-to-project-root>/cypress.config.ts\",\n \"testingType\": \"component\",\n \"skipServe\": true\n }\n }\n }\n}\n```\n\nNx also supports [Angular component testing](/packages/angular/generators/cypress-component-configuration).\n",
"examplesFile": "{% callout type=\"caution\" title=\"Can I use component testing?\" %}\nNext component testing with Nx requires **Cypress version 10.7.0** and up.\n\nYou can migrate with to v11 via the [migrate-to-cypress-11 generator](/packages/cypress/generators/migrate-to-cypress-11).\n\nThis generator is for Cypress based component testing.\n\nIf you want to test components via Storybook with Cypress, then check out the [storybook-configuration generator docs](/nx-api/react/generators/storybook-configuration). However, this functionality is deprecated, and will be removed on Nx version 19.\n{% /callout %}\n\nThis generator is designed to get your Next project up and running with Cypress Component Testing.\n\n```shell\nnx g @nx/next:cypress-component-configuration --project=my-cool-next-project\n```\n\nRunning this generator, adds the required files to the specified project with a preconfigured `cypress.config.ts` designed for Nx workspaces.\n\n```ts {% fileName=\"cypress.config.ts\" %}\nimport { defineConfig } from 'cypress';\nimport { nxComponentTestingPreset } from '@nx/next/plugins/component-testing';\n\nexport default defineConfig({\n component: nxComponentTestingPreset(__filename),\n});\n```\n\nHere is an example on how to add custom options to the configuration\n\n```ts {% fileName=\"cypress.config.ts\" %}\nimport { defineConfig } from 'cypress';\nimport { nxComponentTestingPreset } from '@nx/next/plugins/component-testing';\n\nexport default defineConfig({\n component: {\n ...nxComponentTestingPreset(__filename),\n // extra options here\n },\n});\n```\n\n```shell\nnx g @nx/next:cypress-component-project --project=my-cool-next-project\n```\n\n## Auto Generating Tests\n\nYou can optionally use the `--generate-tests` flag to generate a test file for each component in your project.\n\n```shell\nnx g @nx/next:cypress-component-configuration --project=my-cool-next-project --generate-tests\n```\n\n## Running Component Tests\n\nA new `component-test` target will be added to the specified project to run your component tests.\n\n```shell\nnx g component-test my-cool-next-project\n```\n\nHere is an example of the project configuration that is generated.\n\n```json {% fileName=\"project.json\" %}\n{\n \"targets\" {\n \"component-test\": {\n \"executor\": \"@nx/cypress:cypress\",\n \"options\": {\n \"cypressConfig\": \"<path-to-project-root>/cypress.config.ts\",\n \"testingType\": \"component\",\n \"skipServe\": true\n }\n }\n }\n}\n```\n\nNx also supports [Angular component testing](/packages/angular/generators/cypress-component-configuration).\n",
"presets": []
},
"description": "cypress-component-configuration generator",
@@ -73,7 +73,7 @@
}
},
"required": ["project"],
"examplesFile": "---\ntitle: Storybook configuration generator examples\ndescription: This page contains examples for the @nx/nuxt:storybook-configuration generator.\n---\n\nThis generator calls the `@nx/vue:storybook-configuration` generator under the hood. It will set up Storybook for your **Nuxt** project.\n\n```bash\nnx g @nx/nuxt:storybook-configuration project-name\n```\n\nYou can read more about how this generator works, in the [Storybook for Vue overview page](/recipes/storybook/overview-vue#generate-storybook-configuration-for-a-vue-project).\n\nWhen running this generator, you will be prompted to provide the following:\n\n- The `name` of the project you want to generate the configuration for.\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/vue/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. Also, a `test-storybook` target will be generated in your project's `project.json`, with a command to invoke the [Storybook `test-runner`](https://storybook.js.org/docs/vue/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/packages/storybook/documents/interaction-tests)..\n- Whether you want to `generateStories` for the components in your project. If you choose `yes`, a `.stories.ts` file will be generated next to each of your components in your project.\n\nYou must provide a `name` for the generator to work.\n\nBy default, this generator will also set up [Storybook interaction tests](https://storybook.js.org/docs/vue/writing-tests/interaction-testing). If you don't want to set up Storybook interaction tests, you can pass the `--interactionTests=false` option, but it's not recommended.\n\nThere are a number of other options available. Let's take a look at some examples.\n\n## Examples\n\n### Generate Storybook configuration\n\n```bash\nnx g @nx/nuxt:storybook-configuration ui\n```\n\nThis will generate Storybook configuration for the `ui` project using TypeScript for the Storybook configuration files (the files inside the `.storybook` directory, eg. `.storybook/main.ts`).\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nx/nuxt:storybook-configuration ui --generateStories=true --ignorePaths=\"libs/ui/src/not-stories/**,**/**/src/**/*.other.*,apps/my-app/**/*.something.ts\"\n```\n\nThis will generate a Storybook configuration for the `ui` project and generate stories for all components in the `libs/ui/src/lib` directory, except for the ones in the `libs/ui/src/not-stories` directory, and the ones in the `apps/my-app` directory that end with `.something.ts`, and also for components that their file name is of the pattern `*.other.*`.\n\nThis is useful if you have a project that contains components that are not meant to be used in isolation, but rather as part of a larger component.\n\nBy default, Nx will ignore the following paths:\n\n```text\n*.stories.ts, *.stories.tsx, *.stories.js, *.stories.jsx, *.stories.mdx\n```\n\nbut you can change this behaviour easily, as explained above.\n\n### Generate stories using JavaScript instead of TypeScript\n\n```bash\nnx g @nx/nuxt:storybook-configuration ui --generateStories=true --js=true\n```\n\nThis will generate stories for all the components in the `ui` project using JavaScript instead of TypeScript. So, you will have `.stories.js` files next to your components.\n\n### Generate Storybook configuration using JavaScript\n\n```bash\nnx g @nx/nuxt:storybook-configuration ui --tsConfiguration=false\n```\n\nBy default, our generator generates TypeScript Storybook configuration files. You can choose to use JavaScript for the Storybook configuration files of your project (the files inside the `.storybook` directory, eg. `.storybook/main.js`).\n",
"examplesFile": "---\ntitle: Storybook configuration generator examples\ndescription: This page contains examples for the @nx/nuxt:storybook-configuration generator.\n---\n\nThis generator calls the `@nx/vue:storybook-configuration` generator under the hood. It will set up Storybook for your **Nuxt** project.\n\n```bash\nnx g @nx/nuxt:storybook-configuration project-name\n```\n\nYou can read more about how this generator works, in the [Storybook for Vue overview page](/recipes/storybook/overview-vue#generate-storybook-configuration-for-a-vue-project).\n\nWhen running this generator, you will be prompted to provide the following:\n\n- The `name` of the project you want to generate the configuration for.\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/vue/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. Also, a `test-storybook` target will be generated in your project's `project.json`, with a command to invoke the [Storybook `test-runner`](https://storybook.js.org/docs/vue/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/recipes/storybook/storybook-interaction-tests#setup-storybook-interaction-tests)..\n- Whether you want to `generateStories` for the components in your project. If you choose `yes`, a `.stories.ts` file will be generated next to each of your components in your project.\n\nYou must provide a `name` for the generator to work.\n\nBy default, this generator will also set up [Storybook interaction tests](https://storybook.js.org/docs/vue/writing-tests/interaction-testing). If you don't want to set up Storybook interaction tests, you can pass the `--interactionTests=false` option, but it's not recommended.\n\nThere are a number of other options available. Let's take a look at some examples.\n\n## Examples\n\n### Generate Storybook configuration\n\n```bash\nnx g @nx/nuxt:storybook-configuration ui\n```\n\nThis will generate Storybook configuration for the `ui` project using TypeScript for the Storybook configuration files (the files inside the `.storybook` directory, eg. `.storybook/main.ts`).\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nx/nuxt:storybook-configuration ui --generateStories=true --ignorePaths=\"libs/ui/src/not-stories/**,**/**/src/**/*.other.*,apps/my-app/**/*.something.ts\"\n```\n\nThis will generate a Storybook configuration for the `ui` project and generate stories for all components in the `libs/ui/src/lib` directory, except for the ones in the `libs/ui/src/not-stories` directory, and the ones in the `apps/my-app` directory that end with `.something.ts`, and also for components that their file name is of the pattern `*.other.*`.\n\nThis is useful if you have a project that contains components that are not meant to be used in isolation, but rather as part of a larger component.\n\nBy default, Nx will ignore the following paths:\n\n```text\n*.stories.ts, *.stories.tsx, *.stories.js, *.stories.jsx, *.stories.mdx\n```\n\nbut you can change this behaviour easily, as explained above.\n\n### Generate stories using JavaScript instead of TypeScript\n\n```bash\nnx g @nx/nuxt:storybook-configuration ui --generateStories=true --js=true\n```\n\nThis will generate stories for all the components in the `ui` project using JavaScript instead of TypeScript. So, you will have `.stories.js` files next to your components.\n\n### Generate Storybook configuration using JavaScript\n\n```bash\nnx g @nx/nuxt:storybook-configuration ui --tsConfiguration=false\n```\n\nBy default, our generator generates TypeScript Storybook configuration files. You can choose to use JavaScript for the Storybook configuration files of your project (the files inside the `.storybook` directory, eg. `.storybook/main.js`).\n",
"presets": []
},
"description": "Set up storybook for a Nuxt app.",
@@ -154,7 +154,7 @@
"description": "Host to serve UI on; specifying this option opens UI in a browser tab"
},
"uiPort": {
"type": "string",
"type": "number",
"description": "Port to serve UI on, 0 for any free port; specifying this option opens UI in a browser tab"
},
"skipInstall": {
@@ -108,6 +108,10 @@
"staticRemotesPort": {
"type": "number",
"description": "The port at which to serve the file-server for the static remotes."
},
"pathToManifestFile": {
"type": "string",
"description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic remote applications relative to the workspace root."
}
},
"presets": []
@@ -30,7 +30,7 @@
}
},
"required": ["project", "componentPath"],
"examplesFile": "{% callout type=\"caution\" title=\"Can I use component testing?\" %}\nReact component testing with Nx requires **Cypress version 10** and up.\n\nYou can migrate with to v10 via the [migrate-to-cypress-10 generator](/packages/cypress/generators/migrate-to-cypress-10).\n\nThis generator is for Cypress based component testing.\n\nIf you're wanting to create Cypress tests for a Storybook story, then check out the [component-cypress-spec generator docs](/packages/react/generators/component-cypress-spec)\n\nIf you're wanting to create Storybook stories for a component, then check out the [stories generator docs](/packages/react/generators/stories) or [component-story generator docs](/packages/react/generators/component-cypress-spec)\n\n{% /callout %}\n\nThis generator is used to create a Cypress component test file for a given React component.\n\n```shell\nnx g @nx/react:component-test --project=my-cool-react-project --componentPath=src/my-fancy-button.tsx\n```\n\nTest file are generated with the `.cy.` suffix. this is to prevent colliding with any existing `.spec.` files contained in the project.\n\nIt's currently expected the generated `.cy.` file will live side by side with the component. It is also assumed the project is already setup for component testing. If it isn't, then you can run the [cypress-component-project generator](/packages/react/generators/cypress-component-configuration) to set up the project for component testing.\n",
"examplesFile": "{% callout type=\"caution\" title=\"Can I use component testing?\" %}\nReact component testing with Nx requires **Cypress version 10** and up.\n\nYou can migrate with to v11 via the [migrate-to-cypress-11 generator](/packages/cypress/generators/migrate-to-cypress-11).\n\nThis generator is for Cypress based component testing.\n\nIf you're wanting to create Cypress tests for a Storybook story, then check out the [component-cypress-spec generator docs](/packages/react/generators/component-cypress-spec)\n\nIf you're wanting to create Storybook stories for a component, then check out the [stories generator docs](/packages/react/generators/stories) or [component-story generator docs](/packages/react/generators/component-cypress-spec)\n\n{% /callout %}\n\nThis generator is used to create a Cypress component test file for a given React component.\n\n```shell\nnx g @nx/react:component-test --project=my-cool-react-project --componentPath=src/my-fancy-button.tsx\n```\n\nTest file are generated with the `.cy.` suffix. this is to prevent colliding with any existing `.spec.` files contained in the project.\n\nIt's currently expected the generated `.cy.` file will live side by side with the component. It is also assumed the project is already setup for component testing. If it isn't, then you can run the [cypress-component-project generator](/packages/react/generators/cypress-component-configuration) to set up the project for component testing.\n",
"presets": []
},
"description": "Generate a Cypress component test for a React component",
File diff suppressed because one or more lines are too long
@@ -60,7 +60,7 @@
}
},
"required": ["project"],
"examplesFile": "This generator will generate stories for all your components in your project. The stories will be generated using [Component Story Format 3 (CSF3)](https://storybook.js.org/blog/storybook-csf3-is-here/).\n\n```bash\nnx g @nx/react:stories project-name\n```\n\nYou can read more about how this generator works, in the [Storybook for React overview page](/recipes/storybook/overview-react#auto-generate-stories).\n\nWhen running this generator, you will be prompted to provide the following:\n\n- The `name` of the project you want to generate the configuration for.\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. You can read more about this in the [Nx Storybook interaction tests documentation page](/packages/storybook/documents/interaction-tests)..\n\nYou must provide a `name` for the generator to work.\n\nBy default, this generator will also set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing). If you don't want to set up Storybook interaction tests, you can pass the `--interactionTests=false` option, but it's not recommended.\n\nThere are a number of other options available. Let's take a look at some examples.\n\n## Examples\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nx/react:stories --name=ui --ignorePaths=libs/ui/src/not-stories/**,**/**/src/**/*.other.*\n```\n\nThis will generate stories for all the components in the `ui` project, except for the ones in the `libs/ui/src/not-stories` directory, and also for components that their file name is of the pattern `*.other.*`.\n\nThis is useful if you have a project that contains components that are not meant to be used in isolation, but rather as part of a larger component.\n\nBy default, Nx will ignore the following paths:\n\n```text\n*.stories.ts, *.stories.tsx, *.stories.js, *.stories.jsx, *.stories.mdx\n```\n\nbut you can change this behaviour easily, as explained above.\n\n### Generate stories using JavaScript instead of TypeScript\n\n```bash\nnx g @nx/react:stories --name=ui --js=true\n```\n\nThis will generate stories for all the components in the `ui` project using JavaScript instead of TypeScript. So, you will have `.stories.js` files next to your components.\n",
"examplesFile": "This generator will generate stories for all your components in your project. The stories will be generated using [Component Story Format 3 (CSF3)](https://storybook.js.org/blog/storybook-csf3-is-here/).\n\n```bash\nnx g @nx/react:stories project-name\n```\n\nYou can read more about how this generator works, in the [Storybook for React overview page](/recipes/storybook/overview-react#auto-generate-stories).\n\nWhen running this generator, you will be prompted to provide the following:\n\n- The `name` of the project you want to generate the configuration for.\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. You can read more about this in the [Nx Storybook interaction tests documentation page](/recipes/storybook/storybook-interaction-tests#setup-storybook-interaction-tests)..\n\nYou must provide a `name` for the generator to work.\n\nBy default, this generator will also set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing). If you don't want to set up Storybook interaction tests, you can pass the `--interactionTests=false` option, but it's not recommended.\n\nThere are a number of other options available. Let's take a look at some examples.\n\n## Examples\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nx/react:stories --name=ui --ignorePaths=libs/ui/src/not-stories/**,**/**/src/**/*.other.*\n```\n\nThis will generate stories for all the components in the `ui` project, except for the ones in the `libs/ui/src/not-stories` directory, and also for components that their file name is of the pattern `*.other.*`.\n\nThis is useful if you have a project that contains components that are not meant to be used in isolation, but rather as part of a larger component.\n\nBy default, Nx will ignore the following paths:\n\n```text\n*.stories.ts, *.stories.tsx, *.stories.js, *.stories.jsx, *.stories.mdx\n```\n\nbut you can change this behaviour easily, as explained above.\n\n### Generate stories using JavaScript instead of TypeScript\n\n```bash\nnx g @nx/react:stories --name=ui --js=true\n```\n\nThis will generate stories for all the components in the `ui` project using JavaScript instead of TypeScript. So, you will have `.stories.js` files next to your components.\n",
"presets": []
},
"description": "Create stories/specs for all components declared in an app or library.",
@@ -88,7 +88,7 @@
}
},
"required": ["project"],
"examplesFile": "This generator will set up Storybook for your **React** project. You can also use this generator to generate Storybook configuration for your **Next.js** project. By default, starting Nx 16, Storybook v7 is used.\n\n```bash\nnx g @nx/react:storybook-configuration project-name\n```\n\nYou can read more about how this generator works, in the [Storybook for React overview page](/recipes/storybook/overview-react#generate-storybook-configuration-for-an-react-project).\n\nWhen running this generator, you will be prompted to provide the following:\n\n- The `name` of the project you want to generate the configuration for.\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/react/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. Also, a `test-storybook` target will be generated in your project's `project.json`, with a command to invoke the [Storybook `test-runner`](https://storybook.js.org/docs/react/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/packages/storybook/documents/interaction-tests)..\n- Whether you want to `generateStories` for the components in your project. If you choose `yes`, a `.stories.ts` file will be generated next to each of your components in your project.\n\nYou must provide a `name` for the generator to work.\n\nBy default, this generator will also set up [Storybook interaction tests](https://storybook.js.org/docs/react/writing-tests/interaction-testing). If you don't want to set up Storybook interaction tests, you can pass the `--interactionTests=false` option, but it's not recommended.\n\nThere are a number of other options available. Let's take a look at some examples.\n\n## Examples\n\n### Generate Storybook configuration\n\n```bash\nnx g @nx/react:storybook-configuration ui\n```\n\nThis will generate Storybook configuration for the `ui` project using TypeScript for the Storybook configuration files (the files inside the `.storybook` directory, eg. `.storybook/main.ts`).\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nx/react:storybook-configuration ui --generateStories=true --ignorePaths=libs/ui/src/not-stories/**,**/**/src/**/*.other.*,apps/my-app/**/*.something.ts\n```\n\nThis will generate a Storybook configuration for the `ui` project and generate stories for all components in the `libs/ui/src/lib` directory, except for the ones in the `libs/ui/src/not-stories` directory, and the ones in the `apps/my-app` directory that end with `.something.ts`, and also for components that their file name is of the pattern `*.other.*`.\n\nThis is useful if you have a project that contains components that are not meant to be used in isolation, but rather as part of a larger component.\n\nBy default, Nx will ignore the following paths:\n\n```text\n*.stories.ts, *.stories.tsx, *.stories.js, *.stories.jsx, *.stories.mdx\n```\n\nbut you can change this behaviour easily, as explained above.\n\n### Generate stories using JavaScript instead of TypeScript\n\n```bash\nnx g @nx/react:storybook-configuration ui --generateStories=true --js=true\n```\n\nThis will generate stories for all the components in the `ui` project using JavaScript instead of TypeScript. So, you will have `.stories.js` files next to your components.\n\n### Generate Storybook configuration using JavaScript\n\n```bash\nnx g @nx/react:storybook-configuration ui --tsConfiguration=false\n```\n\nBy default, our generator generates TypeScript Storybook configuration files. You can choose to use JavaScript for the Storybook configuration files of your project (the files inside the `.storybook` directory, eg. `.storybook/main.js`).\n",
"examplesFile": "This generator will set up Storybook for your **React** project. You can also use this generator to generate Storybook configuration for your **Next.js** project. By default, starting Nx 16, Storybook v7 is used.\n\n```bash\nnx g @nx/react:storybook-configuration project-name\n```\n\nYou can read more about how this generator works, in the [Storybook for React overview page](/recipes/storybook/overview-react#generate-storybook-configuration-for-an-react-project).\n\nWhen running this generator, you will be prompted to provide the following:\n\n- The `name` of the project you want to generate the configuration for.\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/react/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. Also, a `test-storybook` target will be generated in your project's `project.json`, with a command to invoke the [Storybook `test-runner`](https://storybook.js.org/docs/react/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/recipes/storybook/storybook-interaction-tests#setup-storybook-interaction-tests)..\n- Whether you want to `generateStories` for the components in your project. If you choose `yes`, a `.stories.ts` file will be generated next to each of your components in your project.\n\nYou must provide a `name` for the generator to work.\n\nBy default, this generator will also set up [Storybook interaction tests](https://storybook.js.org/docs/react/writing-tests/interaction-testing). If you don't want to set up Storybook interaction tests, you can pass the `--interactionTests=false` option, but it's not recommended.\n\nThere are a number of other options available. Let's take a look at some examples.\n\n## Examples\n\n### Generate Storybook configuration\n\n```bash\nnx g @nx/react:storybook-configuration ui\n```\n\nThis will generate Storybook configuration for the `ui` project using TypeScript for the Storybook configuration files (the files inside the `.storybook` directory, eg. `.storybook/main.ts`).\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nx/react:storybook-configuration ui --generateStories=true --ignorePaths=libs/ui/src/not-stories/**,**/**/src/**/*.other.*,apps/my-app/**/*.something.ts\n```\n\nThis will generate a Storybook configuration for the `ui` project and generate stories for all components in the `libs/ui/src/lib` directory, except for the ones in the `libs/ui/src/not-stories` directory, and the ones in the `apps/my-app` directory that end with `.something.ts`, and also for components that their file name is of the pattern `*.other.*`.\n\nThis is useful if you have a project that contains components that are not meant to be used in isolation, but rather as part of a larger component.\n\nBy default, Nx will ignore the following paths:\n\n```text\n*.stories.ts, *.stories.tsx, *.stories.js, *.stories.jsx, *.stories.mdx\n```\n\nbut you can change this behaviour easily, as explained above.\n\n### Generate stories using JavaScript instead of TypeScript\n\n```bash\nnx g @nx/react:storybook-configuration ui --generateStories=true --js=true\n```\n\nThis will generate stories for all the components in the `ui` project using JavaScript instead of TypeScript. So, you will have `.stories.js` files next to your components.\n\n### Generate Storybook configuration using JavaScript\n\n```bash\nnx g @nx/react:storybook-configuration ui --tsConfiguration=false\n```\n\nBy default, our generator generates TypeScript Storybook configuration files. You can choose to use JavaScript for the Storybook configuration files of your project (the files inside the `.storybook` directory, eg. `.storybook/main.js`).\n",
"presets": []
},
"description": "Set up storybook for a React app or library.",
@@ -97,7 +97,7 @@
}
},
"required": ["project", "uiFramework"],
"examplesFile": "---\ntitle: Storybook configuration generator examples\ndescription: This page contains examples for the @nx/storybook:configuration generator.\n---\n\nThis is a framework-agnostic generator for setting up Storybook configuration for a project.\n\n```bash\nnx g @nx/storybook:configuration\n```\n\n{% callout type=\"info\" title=\"Nx uses Storybook 7\" %}\nNx does not support Storybook v6 any more. So, Nx will configure your project to use Storybook v7. If you are not on Storybook 7 yet, please migrate. Please follow our [Storybook 7 migration generator](/packages/storybook/generators/migrate-7) guide.\n{% /callout %}\n\nIf you are using Angular, React, Next.js, Vue or React Native in your project, it's best to use the framework specific Storybook configuration generator:\n\n- [React Storybook Configuration Generator](/nx-api/react/generators/storybook-configuration) (React and Next.js projects)\n\n- [Angular Storybook Configuration Generator](/nx-api/angular/generators/storybook-configuration)\n\n- [React Native Storybook Configuration Generator](/nx-api/react-native/generators/storybook-configuration)\n\n- [Vue Storybook Configuration Generator](/nx-api/vue/generators/storybook-configuration)\n\nIf you are not using one of the framework-specific generators mentioned above, when running this generator you will be prompted to provide the following:\n\n- The `name` of the project you want to generate the configuration for.\n- The `uiFramework` you want to use. Supported values are:\n - `@storybook/angular`\n - `@storybook/html-webpack5`\n - `@storybook/nextjs`\n - `@storybook/preact-webpack5`\n - `@storybook/react-webpack5`\n - `@storybook/react-vite`\n - `@storybook/server-webpack5`\n - `@storybook/svelte-webpack5`\n - `@storybook/svelte-vite`\n - `@storybook/sveltekit`\n - `@storybook/vue-webpack5`\n - `@storybook/vue-vite`\n - `@storybook/vue3-webpack5`\n - `@storybook/vue3-vite`\n - `@storybook/web-components-webpack5`\n - `@storybook/web-components-vite`\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, all the necessary dependencies will be installed. Also, a `test-storybook` target will be generated in your project's `project.json`, with a command to invoke the [Storybook `test-runner`](https://storybook.js.org/docs/angular/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/packages/storybook/documents/interaction-tests).\n\nYou must provide a `name` and a `uiFramework` for the generator to work.\n\nYou can read more about how this generator works, in the [Storybook package overview page](/packages/storybook#generating-storybook-configuration).\n\n## Examples\n\n### Generate Storybook configuration using JavaScript\n\n```bash\nnx g @nx/storybook:configuration ui --uiFramework=@storybook/web-components-vite --tsConfiguration=false\n```\n\nBy default, our generator generates TypeScript Storybook configuration files. You can choose to use JavaScript for the Storybook configuration files of your project (the files inside the `.storybook` directory, eg. `.storybook/main.js`).\n",
"examplesFile": "---\ntitle: Storybook configuration generator examples\ndescription: This page contains examples for the @nx/storybook:configuration generator.\n---\n\nThis is a framework-agnostic generator for setting up Storybook configuration for a project.\n\n```bash\nnx g @nx/storybook:configuration\n```\n\n{% callout type=\"info\" title=\"Nx uses Storybook 7\" %}\nNx does not support Storybook v6 any more. So, Nx will configure your project to use Storybook v7. If you are not on Storybook 7 yet, please migrate. Please follow our [Storybook 7 migration generator](/packages/storybook/generators/migrate-7) guide.\n{% /callout %}\n\nIf you are using Angular, React, Next.js, Vue or React Native in your project, it's best to use the framework specific Storybook configuration generator:\n\n- [React Storybook Configuration Generator](/nx-api/react/generators/storybook-configuration) (React and Next.js projects)\n\n- [Angular Storybook Configuration Generator](/nx-api/angular/generators/storybook-configuration)\n\n- [React Native Storybook Configuration Generator](/nx-api/react-native/generators/storybook-configuration)\n\n- [Vue Storybook Configuration Generator](/nx-api/vue/generators/storybook-configuration)\n\nIf you are not using one of the framework-specific generators mentioned above, when running this generator you will be prompted to provide the following:\n\n- The `name` of the project you want to generate the configuration for.\n- The `uiFramework` you want to use. Supported values are:\n - `@storybook/angular`\n - `@storybook/html-webpack5`\n - `@storybook/nextjs`\n - `@storybook/preact-webpack5`\n - `@storybook/react-webpack5`\n - `@storybook/react-vite`\n - `@storybook/server-webpack5`\n - `@storybook/svelte-webpack5`\n - `@storybook/svelte-vite`\n - `@storybook/sveltekit`\n - `@storybook/vue-webpack5`\n - `@storybook/vue-vite`\n - `@storybook/vue3-webpack5`\n - `@storybook/vue3-vite`\n - `@storybook/web-components-webpack5`\n - `@storybook/web-components-vite`\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, all the necessary dependencies will be installed. Also, a `test-storybook` target will be generated in your project's `project.json`, with a command to invoke the [Storybook `test-runner`](https://storybook.js.org/docs/angular/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/recipes/storybook/storybook-interaction-tests#setup-storybook-interaction-tests).\n\nYou must provide a `name` and a `uiFramework` for the generator to work.\n\nYou can read more about how this generator works, in the [Storybook package overview page](/packages/storybook#generating-storybook-configuration).\n\n## Examples\n\n### Generate Storybook configuration using JavaScript\n\n```bash\nnx g @nx/storybook:configuration ui --uiFramework=@storybook/web-components-vite --tsConfiguration=false\n```\n\nBy default, our generator generates TypeScript Storybook configuration files. You can choose to use JavaScript for the Storybook configuration files of your project (the files inside the `.storybook` directory, eg. `.storybook/main.js`).\n",
"presets": []
},
"description": "Add Storybook configuration to a UI library or an application.",
@@ -60,7 +60,7 @@
}
},
"required": ["project"],
"examplesFile": "---\ntitle: Stories generator examples\ndescription: This page contains examples for the @nx/vue:stories generator.\n---\n\nThis generator will generate stories for all your components in your project. The stories will be generated using [Component Story Format 3 (CSF3)](https://storybook.js.org/blog/storybook-csf3-is-here/).\n\nYou can also use this generator to generate stories for your **Nuxt** project:\n\n```bash\nnx g @nx/vue:stories project-name\n```\n\nor\n\n```bash\nnx g @nx/nuxt:stories project-name\n```\n\nYou can read more about how this generator works, in the [Storybook for Vue overview page](/recipes/storybook/overview-vue#auto-generate-stories).\n\nWhen running this generator, you will be prompted to provide the following:\n\n- The `name` of the project you want to generate the configuration for.\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. You can read more about this in the [Nx Storybook interaction tests documentation page](/packages/storybook/documents/interaction-tests)..\n\nYou must provide a `name` for the generator to work.\n\nBy default, this generator will also set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing). If you don't want to set up Storybook interaction tests, you can pass the `--interactionTests=false` option, but it's not recommended.\n\nThere are a number of other options available. Let's take a look at some examples.\n\n## Examples\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nx/vue:stories --name=ui --ignorePaths=libs/ui/src/not-stories/**,**/**/src/**/*.other.*\n```\n\nThis will generate stories for all the components in the `ui` project, except for the ones in the `libs/ui/src/not-stories` directory, and also for components that their file name is of the pattern `*.other.*`.\n\nThis is useful if you have a project that contains components that are not meant to be used in isolation, but rather as part of a larger component.\n\nBy default, Nx will ignore the following paths:\n\n```text\n*.stories.ts, *.stories.tsx, *.stories.js, *.stories.jsx, *.stories.mdx\n```\n\nbut you can change this behaviour easily, as explained above.\n\n### Generate stories using JavaScript instead of TypeScript\n\n```bash\nnx g @nx/vue:stories --name=ui --js=true\n```\n\nThis will generate stories for all the components in the `ui` project using JavaScript instead of TypeScript. So, you will have `.stories.js` files next to your components.\n",
"examplesFile": "---\ntitle: Stories generator examples\ndescription: This page contains examples for the @nx/vue:stories generator.\n---\n\nThis generator will generate stories for all your components in your project. The stories will be generated using [Component Story Format 3 (CSF3)](https://storybook.js.org/blog/storybook-csf3-is-here/).\n\nYou can also use this generator to generate stories for your **Nuxt** project:\n\n```bash\nnx g @nx/vue:stories project-name\n```\n\nor\n\n```bash\nnx g @nx/nuxt:stories project-name\n```\n\nYou can read more about how this generator works, in the [Storybook for Vue overview page](/recipes/storybook/overview-vue#auto-generate-stories).\n\nWhen running this generator, you will be prompted to provide the following:\n\n- The `name` of the project you want to generate the configuration for.\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. You can read more about this in the [Nx Storybook interaction tests documentation page](/recipes/storybook/storybook-interaction-tests#setup-storybook-interaction-tests)..\n\nYou must provide a `name` for the generator to work.\n\nBy default, this generator will also set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing). If you don't want to set up Storybook interaction tests, you can pass the `--interactionTests=false` option, but it's not recommended.\n\nThere are a number of other options available. Let's take a look at some examples.\n\n## Examples\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nx/vue:stories --name=ui --ignorePaths=libs/ui/src/not-stories/**,**/**/src/**/*.other.*\n```\n\nThis will generate stories for all the components in the `ui` project, except for the ones in the `libs/ui/src/not-stories` directory, and also for components that their file name is of the pattern `*.other.*`.\n\nThis is useful if you have a project that contains components that are not meant to be used in isolation, but rather as part of a larger component.\n\nBy default, Nx will ignore the following paths:\n\n```text\n*.stories.ts, *.stories.tsx, *.stories.js, *.stories.jsx, *.stories.mdx\n```\n\nbut you can change this behaviour easily, as explained above.\n\n### Generate stories using JavaScript instead of TypeScript\n\n```bash\nnx g @nx/vue:stories --name=ui --js=true\n```\n\nThis will generate stories for all the components in the `ui` project using JavaScript instead of TypeScript. So, you will have `.stories.js` files next to your components.\n",
"presets": []
},
"description": "Create stories for all components declared in an app or library.",
@@ -73,7 +73,7 @@
}
},
"required": ["project"],
"examplesFile": "---\ntitle: Storybook configuration generator examples\ndescription: This page contains examples for the @nx/vue:storybook-configuration generator.\n---\n\nThis generator will set up Storybook for your **Vue** project. You can also use this generator to generate Storybook configuration for your **Nuxt** project.\n\n```bash\nnx g @nx/vue:storybook-configuration project-name\n```\n\nor\n\n```bash\nnx g @nx/nuxt:storybook-configuration project-name\n```\n\nYou can read more about how this generator works, in the [Storybook for Vue overview page](/recipes/storybook/overview-vue#generate-storybook-configuration-for-a-vue-project).\n\nWhen running this generator, you will be prompted to provide the following:\n\n- The `name` of the project you want to generate the configuration for.\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/vue/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. Also, a `test-storybook` target will be generated in your project's `project.json`, with a command to invoke the [Storybook `test-runner`](https://storybook.js.org/docs/vue/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/packages/storybook/documents/interaction-tests)..\n- Whether you want to `generateStories` for the components in your project. If you choose `yes`, a `.stories.ts` file will be generated next to each of your components in your project.\n\nYou must provide a `name` for the generator to work.\n\nBy default, this generator will also set up [Storybook interaction tests](https://storybook.js.org/docs/vue/writing-tests/interaction-testing). If you don't want to set up Storybook interaction tests, you can pass the `--interactionTests=false` option, but it's not recommended.\n\nThere are a number of other options available. Let's take a look at some examples.\n\n## Examples\n\n### Generate Storybook configuration\n\n```bash\nnx g @nx/vue:storybook-configuration ui\n```\n\nThis will generate Storybook configuration for the `ui` project using TypeScript for the Storybook configuration files (the files inside the `.storybook` directory, eg. `.storybook/main.ts`).\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nx/vue:storybook-configuration ui --generateStories=true --ignorePaths=libs/ui/src/not-stories/**,**/**/src/**/*.other.*,apps/my-app/**/*.something.ts\n```\n\nThis will generate a Storybook configuration for the `ui` project and generate stories for all components in the `libs/ui/src/lib` directory, except for the ones in the `libs/ui/src/not-stories` directory, and the ones in the `apps/my-app` directory that end with `.something.ts`, and also for components that their file name is of the pattern `*.other.*`.\n\nThis is useful if you have a project that contains components that are not meant to be used in isolation, but rather as part of a larger component.\n\nBy default, Nx will ignore the following paths:\n\n```text\n*.stories.ts, *.stories.tsx, *.stories.js, *.stories.jsx, *.stories.mdx\n```\n\nbut you can change this behaviour easily, as explained above.\n\n### Generate stories using JavaScript instead of TypeScript\n\n```bash\nnx g @nx/vue:storybook-configuration ui --generateStories=true --js=true\n```\n\nThis will generate stories for all the components in the `ui` project using JavaScript instead of TypeScript. So, you will have `.stories.js` files next to your components.\n\n### Generate Storybook configuration using JavaScript\n\n```bash\nnx g @nx/vue:storybook-configuration ui --tsConfiguration=false\n```\n\nBy default, our generator generates TypeScript Storybook configuration files. You can choose to use JavaScript for the Storybook configuration files of your project (the files inside the `.storybook` directory, eg. `.storybook/main.js`).\n",
"examplesFile": "---\ntitle: Storybook configuration generator examples\ndescription: This page contains examples for the @nx/vue:storybook-configuration generator.\n---\n\nThis generator will set up Storybook for your **Vue** project. You can also use this generator to generate Storybook configuration for your **Nuxt** project.\n\n```bash\nnx g @nx/vue:storybook-configuration project-name\n```\n\nor\n\n```bash\nnx g @nx/nuxt:storybook-configuration project-name\n```\n\nYou can read more about how this generator works, in the [Storybook for Vue overview page](/recipes/storybook/overview-vue#generate-storybook-configuration-for-a-vue-project).\n\nWhen running this generator, you will be prompted to provide the following:\n\n- The `name` of the project you want to generate the configuration for.\n- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/vue/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. Also, a `test-storybook` target will be generated in your project's `project.json`, with a command to invoke the [Storybook `test-runner`](https://storybook.js.org/docs/vue/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/recipes/storybook/storybook-interaction-tests#setup-storybook-interaction-tests)..\n- Whether you want to `generateStories` for the components in your project. If you choose `yes`, a `.stories.ts` file will be generated next to each of your components in your project.\n\nYou must provide a `name` for the generator to work.\n\nBy default, this generator will also set up [Storybook interaction tests](https://storybook.js.org/docs/vue/writing-tests/interaction-testing). If you don't want to set up Storybook interaction tests, you can pass the `--interactionTests=false` option, but it's not recommended.\n\nThere are a number of other options available. Let's take a look at some examples.\n\n## Examples\n\n### Generate Storybook configuration\n\n```bash\nnx g @nx/vue:storybook-configuration ui\n```\n\nThis will generate Storybook configuration for the `ui` project using TypeScript for the Storybook configuration files (the files inside the `.storybook` directory, eg. `.storybook/main.ts`).\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nx/vue:storybook-configuration ui --generateStories=true --ignorePaths=libs/ui/src/not-stories/**,**/**/src/**/*.other.*,apps/my-app/**/*.something.ts\n```\n\nThis will generate a Storybook configuration for the `ui` project and generate stories for all components in the `libs/ui/src/lib` directory, except for the ones in the `libs/ui/src/not-stories` directory, and the ones in the `apps/my-app` directory that end with `.something.ts`, and also for components that their file name is of the pattern `*.other.*`.\n\nThis is useful if you have a project that contains components that are not meant to be used in isolation, but rather as part of a larger component.\n\nBy default, Nx will ignore the following paths:\n\n```text\n*.stories.ts, *.stories.tsx, *.stories.js, *.stories.jsx, *.stories.mdx\n```\n\nbut you can change this behaviour easily, as explained above.\n\n### Generate stories using JavaScript instead of TypeScript\n\n```bash\nnx g @nx/vue:storybook-configuration ui --generateStories=true --js=true\n```\n\nThis will generate stories for all the components in the `ui` project using JavaScript instead of TypeScript. So, you will have `.stories.js` files next to your components.\n\n### Generate Storybook configuration using JavaScript\n\n```bash\nnx g @nx/vue:storybook-configuration ui --tsConfiguration=false\n```\n\nBy default, our generator generates TypeScript Storybook configuration files. You can choose to use JavaScript for the Storybook configuration files of your project (the files inside the `.storybook` directory, eg. `.storybook/main.js`).\n",
"presets": []
},
"description": "Set up storybook for a Vue app or library.",
+6
View File
@@ -1132,6 +1132,12 @@
"tags": ["nx-release"],
"file": "shared/recipes/nx-release/automatically-version-with-conventional-commits"
},
{
"name": "Configure Custom Registries",
"id": "configure-custom-registries",
"tags": ["nx-release"],
"file": "shared/recipes/nx-release/configure-custom-registries"
},
{
"name": "Publish in CI/CD",
"id": "publish-in-ci-cd",
+1 -1
View File
@@ -107,7 +107,7 @@ To enable the light runner feature, make sure you:
This release is also the first one to support ["Nx Agents"](https://nx.dev/ci/features/distribute-task-execution#managed-agents-seamless-configuration).
While currently experimental and disabled by default for on-prem users, we are looking for more on-prem workspaces to try it out with
so please reach out to your DPE contact or to [cloud-suppport@nrwl.io](cloud-support@nrwl.io) if you are interested in helping us shape this according to your needs!
so please reach out to your DPE contact or to [cloud-suppport@nrwl.io](mailto:cloud-support@nrwl.io) if you are interested in helping us shape this according to your needs!
##### Breaking changes - MongoDB migration
+3
View File
@@ -286,6 +286,8 @@ jobs:
- uses: nrwl/nx-set-shas@v3
# This line is needed for nx affected to work when CI is running on a PR
- run: git branch --track main origin/main
if: ${{ github.event_name == 'pull_request' }}
- run: pnpm nx affected -t lint test build --parallel=3
- run: pnpm nx affected -t e2e --parallel=1
```
@@ -463,6 +465,7 @@ jobs:
- uses: nrwl/nx-set-shas@v3
# This line is needed for nx affected to work when CI is running on a PR
- run: git branch --track main origin/main
if: ${{ github.event_name == 'pull_request' }}
- run: pnpm nx affected -t lint test build --parallel=3
- run: pnpm nx affected -t e2e-ci --parallel=1
- run: pnpm nx affected -t deploy --no-agents
+4 -2
View File
@@ -45,13 +45,15 @@ jobs:
echo "##vso[task.setvariable variable=BASE_SHA]$LAST_SHA"
fi
# Required for nx affected if we're on a branch
- script: git branch --track main origin/main
# This line enables distribution
# The "--stop-agents-after" is optional, but allows idle agents to shut down once the "e2e-ci" targets have been requested
# - script: npx nx-cloud start-ci-run --distribute-on="5 linux-medium-js" --stop-agents-after="e2e-ci"
- script: npm ci
# Required for nx affected if we're on a branch
- script: git branch --track main origin/main
condition: eq(variables['Build.Reason'], 'PullRequest')
- script: npx nx-cloud record -- nx format:check --base=$(BASE_SHA)
- script: npx nx affected --base=$(BASE_SHA) -t lint test build e2e-ci
```
@@ -33,8 +33,10 @@ jobs:
- run: npm ci
- uses: nrwl/nx-set-shas@v3
# This line is needed for nx affected to work when CI is running on a PR
- run: git branch --track main origin/main
if: ${{ github.event_name == 'pull_request' }}
- run: npx nx-cloud record -- nx format:check
- run: npx nx affected -t lint test build e2e-ci
@@ -6,14 +6,6 @@ Before continuing, make sure you have all your changes committed and have a clea
You can migrate an E2E project to v11 by running the following command:
{% callout type="note" title="Generator Naming Changes" %}
If your nx version is below v15.3.0, this migration is called `migrate-to-cypress-10`.
From v15.3.0+ this generator is called `migrate-to-cypress-11`.
As of nx v15.1.0, if your project was already using Cypress v10, then your project will be migrated to Cypress v11 via the [standard nx migration process](/features/automate-updating-dependencies)
{% /callout %}
```shell
nx g @nx/cypress:migrate-to-cypress-11
```
@@ -0,0 +1,109 @@
# Configure Custom Registries
To publish JavaScript packages, Nx Release uses the `npm` CLI under the hood, which defaults to publishing to the `npm` registry (`https://registry.npmjs.org/`). If you need to publish to a different registry, you can configure the registry in the `.npmrc` file in the root of your workspace or at the project level in the project configuration.
## Set the Registry in the Root .npmrc File
The easiest way to configure a custom registry is to set it in the `npm` configuration via the root `.npmrc` file. This file is located in the root of your workspace, and Nx Release will use it for publishing all projects. To set the registry, add the 'registry' property to your root `.npmrc` file:
```bash .npmrc
registry=https://my-custom-registry.com/
```
### Authenticate to the Registry in CI
To authenticate with a custom registry in CI, you can add authentication tokens to the `.npmrc` file:
```bash .npmrc
registry=https://my-custom-registry.com/
//my-custom-registry.com/:_authToken=<TOKEN>
```
See the [npm documentation](https://docs.npmjs.com/cli/v10/configuring-npm/npmrc#auth-related-configuration) for more information.
## Configure Multiple Registries
The recommended way to determine which registry packages are published to is by using [npm scopes](https://docs.npmjs.com/cli/v10/using-npm/scope). All packages with a name that starts with your scope will be published to the registry specified in the `.npmrc` file for that scope. Consider the following example:
```bash .npmrc
@my-scope:registry=https://my-custom-registry.com/
//my-custom-registry.com/:_authToken=<TOKEN>
@other-scope:registry=https://my-other-registry.com/
//my-other-registry.com/:_authToken=<OTHER_TOKEN>
registry=https://my-default-registry.com/
//my-default-registry.com/:_authToken=<DEFAULT_TOKEN>
```
With the above `.npmrc`, the following packages would be published to the specified registries:
- `@my-scope/pkg-1` -> `https://my-custom-registry.com/`
- `@other-scope/pkg-2` -> `https://my-other-registry.com/`
- `pkg-3` -> `https://my-default-registry.com/`
## Specify an Alternate Registry for a Single Package
In some cases, you may want to configure the registry on a per-package basis instead of by scope. This can be done by setting options in the project's configuration.
{% callout type="info" title="Authentication" %}
All registries set for specific packages must still have authentication tokens set in the root `.npmrc` file for publishing in CI. See [Authenticate to the Registry in CI](#authenticate-to-the-registry-in-ci) for an example.
{% /callout %}
### Set the Registry in the Project Configuration
The project configuration for Nx Release is in two parts - one for the version step and one for the publish step.
#### Update the Version Step
The version step of Nx Release is responsible for determining the new version of the package. If you have set the `version.generatorOptions.currentVersionResolver` to 'registry', then Nx Release will check the remote registry for the current version of the package.
**Note:** If you do not use the 'registry' current version resolver, then this step is not needed.
To set custom registry options for the current version lookup, add the registry and/or tag to the `currentVersionResolverMetadata` in the project configuration:
```json project.json
{
"name": "pkg-5",
"sourceRoot": "...",
"targets": {
...
},
"release": {
"version": {
"generatorOptions": {
"currentVersionResolverMetadata": {
"registry": "https://my-unique-registry.com/",
"tag": "next"
}
}
}
}
}
```
#### Update the Publish Step
The publish step of Nx Release is responsible for publishing the package to the registry. To set custom registry options for publishing, you can add the `registry` and/or `tag` options for the `nx-release-publish` target in the project configuration:
```json project.json
{
"name": "pkg-5",
"sourceRoot": "...",
"targets": {
...,
"nx-release-publish": {
"options": {
"registry": "https://my-unique-registry.com/",
"tag": "next"
}
}
}
}
```
### Set the Registry in the Package Manifest
{% callout type="caution" title="Caution" %}
It is not recommended to set the registry for a package in the 'publishConfig' property of its 'package.json' file. 'npm publish' will always prefer the registry from the 'publishConfig' over the '--registry' argument. Because of this, the '--registry' CLI and programmatic API options of Nx Release will no longer be able to override the registry for purposes such as publishing locally for end to end testing.
{% /callout %}
+1
View File
@@ -180,6 +180,7 @@
- [Get Started with Nx Release](/recipes/nx-release/get-started-with-nx-release)
- [Release Projects Independently](/recipes/nx-release/release-projects-independently)
- [Automatically Version with Conventional Commits](/recipes/nx-release/automatically-version-with-conventional-commits)
- [Configure Custom Registries](/recipes/nx-release/configure-custom-registries)
- [Publish in CI/CD](/recipes/nx-release/publish-in-ci-cd)
- [Automate GitHub Releases](/recipes/nx-release/automate-github-releases)
- [Publish Rust Crates](/recipes/nx-release/publish-rust-crates)
+1
View File
@@ -44,6 +44,7 @@ describe('js:node executor', () => {
const output = runCLI(`run ${esbuildLib}:run-node`, {
redirectStderr: true,
silenceError: true,
});
expect(output).toContain('Hello from my library!');
expect(output).toContain('This is an error');
+80
View File
@@ -0,0 +1,80 @@
import {
cleanupProject,
createFile,
listFiles,
newProject,
readFile,
runCLI,
runE2ETests,
uniq,
updateFile,
} from '@nx/e2e/utils';
describe('NextJs SVGR support', () => {
beforeAll(() => {
newProject({
packages: ['@nx/next'],
});
});
afterAll(() => cleanupProject());
it('should allow both SVG asset and SVGR component to be used', () => {
const appName = uniq('app');
runCLI(
`generate @nx/next:app ${appName} --no-interactive --appDir=true --src=true`
);
createFile(
`apps/${appName}/src/app/nx.svg`,
`
<svg version="1.1" width="300" height="200" xmlns="http://www.w3.org/2000/svg">
<text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG for app</text>
</svg>
`
);
updateFile(
`apps/${appName}/src/app/page.tsx`,
`
import Image from 'next/image';
import svgImg, { ReactComponent as Logo } from './nx.svg';
export default async function Index() {
return (
<>
<Image src={svgImg} alt="Alt for SVG img tag" />
<Logo />
</>
);
}
`
);
updateFile(
`apps/${appName}/next.config.js`,
`
const { composePlugins, withNx } = require('@nx/next');
const nextConfig = {
nx: {
svgr: true,
},
};
const plugins = [
withNx,
];
module.exports = composePlugins(...plugins)(nextConfig);
`
);
runCLI(`build ${appName}`);
const pageFile = readFile(`apps/${appName}/.next/server/app/page.js`);
const svgFile = listFiles(`apps/${appName}/.next/static/media`).find((f) =>
/nx\.[a-z0-9]+\.svg$/.test(f)
);
expect(`apps/${appName}/.next/static/chunks/app/${pageFile}`).toMatch(
/SVG for app/
);
expect(`apps/${appName}/.next/static/chunks/app/${pageFile}`).toMatch(
/Alt for SVG img tag/
);
expect(svgFile).toBeTruthy();
});
});
@@ -0,0 +1,65 @@
import {
cleanupProject,
createFile,
listFiles,
newProject,
readFile,
runCLI,
runCLIAsync,
uniq,
updateFile,
} from '@nx/e2e/utils';
describe('Build React applications and libraries with Vite', () => {
beforeAll(() => {
newProject({
packages: ['@nx/react'],
});
});
afterAll(() => {
cleanupProject();
});
// Regression test: https://github.com/nrwl/nx/issues/21773
it('should support SVGR and SVG asset in the same project', async () => {
const appName = uniq('app');
runCLI(
`generate @nx/react:app ${appName} --bundler=webpack --compiler=babel --unitTestRunner=none --no-interactive`
);
createFile(
`apps/${appName}/src/app/nx.svg`,
`
<svg version="1.1" width="300" height="200" xmlns="http://www.w3.org/2000/svg">
<text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG for app</text>
</svg>
`
);
updateFile(
`apps/${appName}/src/app/app.tsx`,
`
import svgImg, { ReactComponent as Logo } from './nx.svg';
export function App() {
return (
<>
<img src={svgImg} alt="Alt for SVG img tag" />
<Logo />
</>
);
}
export default App;
`
);
await runCLIAsync(`build ${appName}`);
const outFiles = listFiles(`dist/apps/${appName}`);
const mainFile = outFiles.find((f) => f.startsWith('main.'));
const mainContent = readFile(`dist/apps/${appName}/${mainFile}`);
const svgFile = outFiles.find((f) => f.endsWith('.svg'));
expect(mainContent).toMatch(/SVG for app/);
expect(mainContent).toMatch(/Alt for SVG img tag/);
expect(svgFile).toBeTruthy();
}, 300_000);
});
+430
View File
@@ -0,0 +1,430 @@
import { NxJsonConfiguration, ProjectConfiguration } from '@nx/devkit';
import {
cleanupProject,
createFile,
killProcessAndPorts,
newProject,
runCLI,
runCommandUntil,
uniq,
updateFile,
updateJson,
} from '@nx/e2e/utils';
import { execSync } from 'child_process';
import type { PackageJson } from 'nx/src/utils/package-json';
describe('nx release - custom npm registries', () => {
const verdaccioPort = 7191;
const customRegistryUrl = `http://localhost:${verdaccioPort}`;
const scope = 'scope';
beforeAll(async () => {
newProject({
unsetProjectNameAndRootFormat: false,
packages: ['@nx/js'],
});
}, 60000);
afterAll(() => cleanupProject());
it('should respect registry configuration for each package', async () => {
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
nxJson.release = {
projectsRelationship: 'independent',
};
return nxJson;
});
const e2eRegistryUrl = execSync('npm config get registry')
.toString()
.trim();
const npmrcEntries = [
`@${scope}:registry=http://scoped-registry.com`,
'tag=next',
// We can't test overriding the default registry in this file since our e2e tests override it anyway.
// Instead, we'll just assert that the e2e registry is used anytime we expect the default registry
];
createFile('.npmrc', npmrcEntries.join('\n'));
const scopedWithPublishConfig = newPackage('pkg-scoped-publish-config', {
scoped: true,
publishConfig: {
[`@${scope}:registry`]: 'http://publish-config-registry.com',
},
});
const publishResultScopedWithPublishConfig = runCLI(
`release publish -p ${scopedWithPublishConfig} --dry-run`
);
expect(publishResultScopedWithPublishConfig).toContain(
'Would publish to http://publish-config-registry.com with tag "next"'
);
const scopedWithPublishConfigAndProjectConfig = newPackage(
'pkg-scoped-publish-config-project-config',
{
scoped: true,
publishConfig: {
[`@${scope}:registry`]: 'http://publish-config-registry.com',
},
projectConfig: {
registry: 'http://ignored-registry.com',
tag: 'alpha',
},
}
);
const publishResultScopedWithPublishConfigAndProjectConfig = runCLI(
`release publish -p ${scopedWithPublishConfigAndProjectConfig} --dry-run`
);
expect(publishResultScopedWithPublishConfigAndProjectConfig).toContain(
'Would publish to http://publish-config-registry.com with tag "alpha"'
);
const publishResultScopedWithPublishConfigAndProjectConfigAndArg = runCLI(
`release publish -p ${scopedWithPublishConfigAndProjectConfig} --dry-run --registry=http://ignored-registry.com`
);
expect(
publishResultScopedWithPublishConfigAndProjectConfigAndArg
).toContain(
'Would publish to http://publish-config-registry.com with tag "alpha"'
);
const scopedNoOtherConfig = newPackage('pkg-scoped-no-other-config', {
scoped: true,
});
const publishResultScopedNoOtherConfig = runCLI(
`release publish -p ${scopedNoOtherConfig} --dry-run`
);
expect(publishResultScopedNoOtherConfig).toContain(
'Would publish to http://scoped-registry.com with tag "next"'
);
const publishResultScopedWithRegistryArg = runCLI(
`release publish -p ${scopedNoOtherConfig} --dry-run --registry=http://scope-override-registry.com`
);
expect(publishResultScopedWithRegistryArg).toContain(
'Would publish to http://scope-override-registry.com with tag "next"'
);
const noScopeWithPublishConfigAndProjectConfig = newPackage(
'pkg-no-scope-publish-config-project-config',
{
publishConfig: {
registry: 'http://publish-config-registry.com',
},
projectConfig: {
registry: 'http://ignored-registry.com',
tag: 'alpha',
},
}
);
const publishResultNoScopeWithPublishConfigAndProjectConfig = runCLI(
`release publish -p ${noScopeWithPublishConfigAndProjectConfig} --dry-run`
);
expect(publishResultNoScopeWithPublishConfigAndProjectConfig).toContain(
'Would publish to http://publish-config-registry.com with tag "alpha"'
);
const publishResultNoScopeWithPublishConfigAndProjectConfigAndArg = runCLI(
`release publish -p ${noScopeWithPublishConfigAndProjectConfig} --dry-run --registry=http://ignored-registry.com --tag=beta`
);
expect(
publishResultNoScopeWithPublishConfigAndProjectConfigAndArg
).toContain(
`Would publish to http://publish-config-registry.com with tag "beta"`
);
const noScopeNoOtherConfig = newPackage('pkg-no-scope-no-config', {});
const publishResultNoScope = runCLI(
`release publish -p ${noScopeNoOtherConfig} --dry-run`
);
expect(publishResultNoScope).toContain(
`Would publish to ${e2eRegistryUrl} with tag "next"`
);
const publishResultNoScopeWithRegistryArg = runCLI(
`release publish -p ${noScopeNoOtherConfig} --dry-run --registry=${customRegistryUrl} --tag=alpha`
);
expect(publishResultNoScopeWithRegistryArg).toContain(
`Would publish to ${customRegistryUrl} with tag "alpha"`
);
const scopeWithProjectConfig = newPackage('pkg-scope-project-config', {
scoped: true,
projectConfig: {
registry: 'http://scope-override-registry.com',
tag: 'alpha',
},
});
const publishResultScopedWithProjectConfig = runCLI(
`release publish -p ${scopeWithProjectConfig} --dry-run`
);
expect(publishResultScopedWithProjectConfig).toContain(
'Would publish to http://scope-override-registry.com with tag "alpha"'
);
const publishResultScopedWithProjectConfigAndArg = runCLI(
`release publish -p ${scopeWithProjectConfig} --dry-run --registry=http://scope-override-arg-registry.com --tag=prev`
);
expect(publishResultScopedWithProjectConfigAndArg).toContain(
'Would publish to http://scope-override-arg-registry.com with tag "prev"'
);
const noScopeWithProjectConfig = newPackage('pkg-no-scope-project-config', {
projectConfig: {
registry: 'http://default-override-registry.com',
tag: 'alpha',
},
});
const publishResultNoScopeWithProjectConfig = runCLI(
`release publish -p ${noScopeWithProjectConfig} --dry-run`
);
expect(publishResultNoScopeWithProjectConfig).toContain(
'Would publish to http://default-override-registry.com with tag "alpha"'
);
const publishResultNoScopeWithProjectConfigAndArg = runCLI(
`release publish -p ${noScopeWithProjectConfig} --dry-run --registry=http://default-override-arg-registry.com --tag=prev`
);
expect(publishResultNoScopeWithProjectConfigAndArg).toContain(
'Would publish to http://default-override-arg-registry.com with tag "prev"'
);
runCLI(`generate setup-verdaccio`);
const process = await runCommandUntil(
`local-registry @proj/source --port=${verdaccioPort}`,
(output) => output.includes(`warn --- http address`)
);
const npmrcEntries2 = [
`@${scope}:registry=${customRegistryUrl}`,
`registry=http://ignored-registry.com`,
'tag=next',
];
updateFile('.npmrc', npmrcEntries2.join('\n'));
const actualScopedWithPublishConfigAndProjectConfig = newPackage(
'pkg-actual-scoped-publish-config-project-config',
{
scoped: true,
publishConfig: {
[`@${scope}:registry`]: e2eRegistryUrl,
},
projectConfig: {
registry: 'http://ignored-registry.com',
tag: 'beta',
version: {
tag: 'alpha', // alpha tag will be passed via publish CLI arg to override the above 'beta'
},
},
}
);
const actualPublishResultScoped = runCLI(
`release publish -p ${actualScopedWithPublishConfigAndProjectConfig} --registry=http://ignored-registry.com --tag=alpha`
);
const actualScopedWithWrongPublishConfig = newPackage(
'pkg-actual-scoped-wrong-publish-config',
{
scoped: true,
publishConfig: {
// to properly override the registry for a scoped package, the key needs to include the scope
registry: 'http://ignored-registry.com',
},
projectConfig: {}, // this will still set the current version resolver to 'registry', it just won't set the registry url
}
);
const actualPublishResultScopedWithWrongPublishConfig = runCLI(
`release publish -p ${actualScopedWithWrongPublishConfig}`
);
const actualNoScopeWithProjectConfig = newPackage(
'pkg-actual-no-scope-project-config',
{
projectConfig: {
registry: customRegistryUrl,
tag: 'beta',
},
}
);
const actualPublishResultNoScopeWithProjectConfig = runCLI(
`release publish -p ${actualNoScopeWithProjectConfig}`
);
const actualScopedWithProjectConfig = newPackage(
'pkg-actual-scoped-project-config',
{
scoped: true,
projectConfig: {
registry: e2eRegistryUrl,
tag: 'prev',
},
}
);
const actualPublishResultScopedWithProjectConfig = runCLI(
`release publish -p ${actualScopedWithProjectConfig}`
);
const versionResult = runCLI(
`release version 999.9.9 -p "${actualScopedWithPublishConfigAndProjectConfig},${actualScopedWithWrongPublishConfig},${actualNoScopeWithProjectConfig},${actualScopedWithProjectConfig}" --dry-run`,
{ silenceError: true } // don't error on this command because the verdaccio process needs to be killed regardless before the test exits
);
await killProcessAndPorts(process.pid, verdaccioPort);
expect(actualPublishResultScoped).toContain(
`Published to ${e2eRegistryUrl} with tag "alpha"`
);
expect(actualPublishResultScopedWithWrongPublishConfig).toContain(
`Published to ${customRegistryUrl} with tag "next"`
);
expect(actualPublishResultNoScopeWithProjectConfig).toContain(
`Published to ${customRegistryUrl} with tag "beta"`
);
expect(actualPublishResultScopedWithProjectConfig).toContain(
`Published to ${e2eRegistryUrl} with tag "prev"`
);
expect(
versionResult.match(
new RegExp(
`Resolved the current version as 0.0.0 for tag "alpha" from registry ${e2eRegistryUrl}`,
'g'
)
).length
).toBe(1);
expect(
versionResult.match(
new RegExp(
`Resolved the current version as 0.0.0 for tag "next" from registry ${customRegistryUrl}`,
'g'
)
).length
).toBe(1);
expect(
versionResult.match(
new RegExp(
`Resolved the current version as 0.0.0 for tag "beta" from registry ${customRegistryUrl}`,
'g'
)
).length
).toBe(1);
expect(
versionResult.match(
new RegExp(
`Resolved the current version as 0.0.0 for tag "prev" from registry ${e2eRegistryUrl}`,
'g'
)
).length
).toBe(1);
}, 600000);
function newPackage(
name: string,
options: {
scoped?: true;
publishConfig?: Record<string, string>;
projectConfig?: {
registry?: string;
tag?: string;
version?: {
registry?: string;
tag?: string;
};
publish?: {
registry?: string;
tag?: string;
};
};
}
): string {
const projectName = uniq(name);
runCLI(`generate @nx/workspace:npm-package ${projectName}`);
let packageName = projectName;
if (options.scoped) {
packageName = `@${scope}/${projectName}`;
}
updateJson<PackageJson>(`${projectName}/package.json`, (json) => {
json.name = packageName;
if (options.publishConfig) {
json.publishConfig = options.publishConfig;
}
return json;
});
updateJson<ProjectConfiguration>(`${projectName}/project.json`, (json) => {
if (options.projectConfig) {
json.release = {
version: {
generatorOptions: {
currentVersionResolver: 'registry',
currentVersionResolverMetadata: {},
},
},
};
json.targets = {
...json.targets,
'nx-release-publish': {
options: {},
},
};
}
if (options.projectConfig?.registry) {
json.targets['nx-release-publish'].options.registry =
options.projectConfig.registry;
(
json.release.version.generatorOptions
.currentVersionResolverMetadata as Record<string, string>
).registry = options.projectConfig.registry;
}
if (options.projectConfig?.tag) {
json.targets['nx-release-publish'].options.tag =
options.projectConfig.tag;
(
json.release.version.generatorOptions
.currentVersionResolverMetadata as Record<string, string>
).tag = options.projectConfig.tag;
}
if (options.projectConfig?.publish?.registry) {
json.targets['nx-release-publish'].options.registry =
options.projectConfig.publish.registry;
}
if (options.projectConfig?.publish?.tag) {
json.targets['nx-release-publish'].options.tag =
options.projectConfig.publish.tag;
}
if (options.projectConfig?.version?.registry) {
(
json.release.version.generatorOptions
.currentVersionResolverMetadata as Record<string, string>
).registry = options.projectConfig.version.registry;
}
if (options.projectConfig?.version?.tag) {
(
json.release.version.generatorOptions
.currentVersionResolverMetadata as Record<string, string>
).tag = options.projectConfig.version.tag;
}
return json;
});
return projectName;
}
});
+1 -1
View File
@@ -213,7 +213,7 @@ export default App;
const results = runCLI(`build ${app} --buildLibsFromSource=false`);
expect(results).toContain('Successfully ran target build for project');
// this should be less modules than building from source
expect(results).toContain('38 modules transformed');
expect(results).toContain('40 modules transformed');
});
it('should build app from libs without package.json in lib', () => {
+44
View File
@@ -5,6 +5,7 @@ import {
newProject,
runCLI,
runCommandUntil,
runE2ETests,
uniq,
updateFile,
} from '@nx/e2e/utils';
@@ -102,4 +103,47 @@ describe('Webpack Plugin (legacy)', () => {
checkFilesExist(`dist/${appName}/styles.css`);
}).toThrow();
});
it('should support standard webpack config with executors', () => {
const appName = uniq('app');
runCLI(
`generate @nx/web:app ${appName} --bundler webpack --e2eTestRunner=playwright`
);
updateFile(
`${appName}/src/main.ts`,
`
document.querySelector('proj-root').innerHTML = '<h1>Welcome</h1>';
`
);
updateFile(
`${appName}/webpack.config.js`,
`
const { join } = require('path');
const {NxWebpackPlugin} = require('@nx/webpack');
module.exports = {
output: {
path: join(__dirname, '../dist/app9524918'),
},
plugins: [
new NxWebpackPlugin({
main: './src/main.ts',
compiler: 'tsc',
index: './src/index.html',
tsConfig: './tsconfig.app.json',
})
]
};
`
);
expect(() => {
runCLI(`build ${appName} --outputHashing none`);
}).not.toThrow();
if (runE2ETests()) {
expect(() => {
runCLI(`e2e ${appName}-e2e`);
}).not.toThrow();
}
});
});
@@ -39,7 +39,7 @@ export const ParameterView = (props: {
Deprecated
</span>
)}
{props.schema['x-priority'] === 'internal' && (
{(props.schema as any)['x-priority'] === 'internal' && (
<span
data-tooltip="Intended for use by other generators"
className="relative -top-0.5 inline-flex rounded-md bg-yellow-300 px-2 text-xs font-semibold uppercase leading-5 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100"
+1 -1
View File
@@ -87,7 +87,7 @@ export const pkg: ProcessedPackageMetadata = {
},
},
githubRoot: 'https://github.com/nrwl/nx-labs/tree/main/packages/rspack',
name: 'Rspack',
name: 'rspack',
packageName: '@nx/rspack',
path: '',
root: '',
+1 -1
View File
@@ -186,7 +186,7 @@ const guideUrls = {
'/migration/lerna-and-nx': 'https://lerna.js.org',
'/cypress/v10-migration-guide': '/cypress/v11-migration-guide',
'/cypress/generators/migrate-to-cypress-10':
'/cypress/generators/migrate-to-cypress-11',
'/packages/cypress/generators/migrate-to-cypress-11',
};
/**
@@ -1,7 +1,7 @@
{% callout type="caution" title="Can I use component testing?" %}
Angular component testing with Nx requires **Cypress version 10.7.0** and up.
You can migrate with to v10 via the [migrate-to-cypress-10 generator](/packages/cypress/generators/migrate-to-cypress-10).
You can migrate with to v11 via the [migrate-to-cypress-11 generator](/packages/cypress/generators/migrate-to-cypress-11).
This generator is for Cypress based component testing.
@@ -1,7 +1,7 @@
{% callout type="caution" title="Can I use component testing?" %}
Angular component testing with Nx requires **Cypress version 10.7.0** and up.
You can migrate with to v10 via the [migrate-to-cypress-10 generator](/packages/cypress/generators/migrate-to-cypress-10).
You can migrate with to v11 via the [migrate-to-cypress-11 generator](/packages/cypress/generators/migrate-to-cypress-11).
This generator is for Cypress based component testing.
+1 -1
View File
@@ -2,4 +2,4 @@
This generator is usually used as part of the process of migrating from an Angular CLI Workspace to an [Nx Integrated Workspace](/concepts/integrated-vs-package-based#integrated-repos) using `npx nx@latest init --integrated`.
You can read more about [migrating from Angular CLI to Nx here](/recipes/adopting-nx-angular).
You can read more about [migrating from Angular CLI to Nx here](/recipes/angular/migration/angular).
+1 -1
View File
@@ -9,7 +9,7 @@ You can read more about how this generator works, in the [Storybook for Angular
When running this generator, you will be prompted to provide the following:
- The `name` of the project you want to generate the configuration for.
- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. You can read more about this in the [Nx Storybook interaction tests documentation page](/packages/storybook/documents/interaction-tests).
- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. You can read more about this in the [Nx Storybook interaction tests documentation page](/recipes/storybook/storybook-interaction-tests#setup-storybook-interaction-tests).
You must provide a `name` for the generator to work.
@@ -9,7 +9,7 @@ You can read more about how this generator works, in the [Storybook for Angular
When running this generator, you will be prompted to provide the following:
- The `name` of the project you want to generate the configuration for.
- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. Also, a `test-storybook` target will be generated in your project's `project.json`, with a command to invoke the [Storybook `test-runner`](https://storybook.js.org/docs/angular/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/packages/storybook/documents/interaction-tests).
- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/angular/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. Also, a `test-storybook` target will be generated in your project's `project.json`, with a command to invoke the [Storybook `test-runner`](https://storybook.js.org/docs/angular/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/recipes/storybook/storybook-interaction-tests#setup-storybook-interaction-tests).
- Whether you want to `generateStories` for the components in your project. If you choose `yes`, a `.stories.ts` file will be generated next to each of your components in your project.
You must provide a `name` for the generator to work.
+1 -1
View File
@@ -23,7 +23,7 @@
"./executors": "./executors.js",
"./tailwind": "./tailwind.js",
"./module-federation": "./module-federation/index.js",
"./src/utils": "./src/utils/public-api.js",
"./src/utils": "./src/utils/index.js",
"./plugins/component-testing": "./plugins/component-testing.js",
"./src/generators/utils": "./src/generators/utils/index.js",
"./src/generators/move/move-impl": "./src/generators/move/move-impl.js",
@@ -7,7 +7,7 @@ export function validateOptions(options: Schema): void {
const { major: angularMajorVersion, version: angularVersion } =
getInstalledAngularVersionInfo();
if (lt(angularVersion, '16.1.0') && options.forceEsbuild !== undefined) {
if (lt(angularVersion, '16.1.0') && options.forceEsbuild) {
throw new Error(stripIndents`The "forceEsbuild" option is only supported in Angular >= 16.1.0. You are currently using "${angularVersion}".
You can resolve this error by removing the "forceEsbuild" option or by migrating to Angular 16.1.0.`);
}
@@ -5,41 +5,38 @@ import {
runExecutor,
} from '@nx/devkit';
export async function startDevRemotes(
remotes: {
remotePorts: any[];
staticRemotes: string[];
devRemotes: string[];
},
export async function startRemotes(
remotes: string[],
workspaceProjects: Record<string, ProjectConfiguration>,
options: Schema,
context: ExecutorContext
context: ExecutorContext,
target: 'serve' | 'serve-static' = 'serve'
) {
const devRemotesIters: AsyncIterable<{ success: boolean }>[] = [];
for (const app of remotes.devRemotes) {
if (!workspaceProjects[app].targets?.['serve']) {
throw new Error(`Could not find "serve" target in "${app}" project.`);
} else if (!workspaceProjects[app].targets?.['serve'].executor) {
const remoteIters: AsyncIterable<{ success: boolean }>[] = [];
for (const app of remotes) {
if (!workspaceProjects[app].targets?.[target]) {
throw new Error(`Could not find "${target}" target in "${app}" project.`);
} else if (!workspaceProjects[app].targets?.[target].executor) {
throw new Error(
`Could not find executor for "serve" target in "${app}" project.`
`Could not find executor for "${target}" target in "${app}" project.`
);
}
const [collection, executor] =
workspaceProjects[app].targets['serve'].executor.split(':');
workspaceProjects[app].targets[target].executor.split(':');
const isUsingModuleFederationDevServerExecutor = executor.includes(
'module-federation-dev-server'
);
devRemotesIters.push(
remoteIters.push(
await runExecutor(
{
project: app,
target: 'serve',
target,
configuration: context.configurationName,
},
{
verbose: options.verbose ?? false,
...(target === 'serve' ? { verbose: options.verbose ?? false } : {}),
...(isUsingModuleFederationDevServerExecutor
? { isInitialHost: false }
: {}),
@@ -48,5 +45,5 @@ export async function startDevRemotes(
)
);
}
return devRemotesIters;
return remoteIters;
}
@@ -8,7 +8,7 @@ import {
buildStaticRemotes,
normalizeOptions,
parseStaticRemotesConfig,
startDevRemotes,
startRemotes,
startStaticRemotesFileServer,
} from './lib';
import { eachValueFrom } from '@nx/devkit/src/utils/rxjs-for-await';
@@ -138,11 +138,20 @@ export async function* moduleFederationDevServerExecutor(
);
await buildStaticRemotes(staticRemotesConfig, nxBin, context, options);
const devRemoteIters = await startDevRemotes(
remotes,
const devRemoteIters = await startRemotes(
remotes.devRemotes,
workspaceProjects,
options,
context
context,
'serve'
);
const dynamicRemoteIters = await startRemotes(
remotes.dynamicRemotes,
workspaceProjects,
options,
context,
'serve-static'
);
const staticRemotesIter =
@@ -159,6 +168,7 @@ export async function* moduleFederationDevServerExecutor(
return yield* combineAsyncIterables(
removeBaseUrlEmission(currIter),
...devRemoteIters.map(removeBaseUrlEmission),
...dynamicRemoteIters.map(removeBaseUrlEmission),
...(staticRemotesIter ? [removeBaseUrlEmission(staticRemotesIter)] : []),
createAsyncIterable<{ success: true; baseUrl: string }>(
async ({ next, done }) => {
@@ -287,6 +287,7 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
"executor": "@nx/web:file-server",
"options": {
"buildTarget": "my-dir-my-app:build",
"port": 4200,
"spa": true,
"staticFilePath": "dist/apps/my-dir/my-app/browser",
},
@@ -487,6 +488,7 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
"executor": "@nx/web:file-server",
"options": {
"buildTarget": "my-app:build",
"port": 4200,
"spa": true,
"staticFilePath": "dist/apps/my-app/browser",
},
@@ -982,6 +984,7 @@ exports[`app nested should create project configs 1`] = `
"executor": "@nx/web:file-server",
"options": {
"buildTarget": "my-app:build",
"port": 4200,
"spa": true,
"staticFilePath": "dist/my-dir/my-app/browser",
},
@@ -1095,6 +1098,7 @@ exports[`app not nested should create project configs 1`] = `
"executor": "@nx/web:file-server",
"options": {
"buildTarget": "my-app:build",
"port": 4200,
"spa": true,
"staticFilePath": "dist/my-app/browser",
},
@@ -40,8 +40,8 @@ export async function addE2e(tree: Tree, options: NormalizedSchema) {
linter: options.linter,
skipPackageJson: options.skipPackageJson,
skipFormat: true,
devServerTarget: `${options.name}:serve:development`,
baseUrl: 'http://localhost:4200',
devServerTarget: `${options.name}:${options.e2eWebServerTarget}:development`,
baseUrl: options.e2eWebServerAddress,
rootProject: options.rootProject,
addPlugin,
});
@@ -64,10 +64,10 @@ export async function addE2e(tree: Tree, options: NormalizedSchema) {
js: false,
linter: options.linter,
setParserOptionsProject: options.setParserOptionsProject,
webServerCommand: `${getPackageManagerCommand().exec} nx serve ${
options.name
}`,
webServerAddress: `http://localhost:${options.port ?? 4200}`,
webServerCommand: `${getPackageManagerCommand().exec} nx ${
options.e2eWebServerTarget
} ${options.name}`,
webServerAddress: options.e2eWebServerAddress,
rootProject: options.rootProject,
addPlugin,
});
@@ -90,7 +90,7 @@ function addFileServerTarget(
executor: '@nx/web:file-server',
options: {
buildTarget: `${options.name}:build`,
port: options.port,
port: options.e2ePort,
staticFilePath: isUsingApplicationBuilder
? joinPathFragments(options.outputPath, 'browser')
: undefined,
@@ -1,4 +1,4 @@
import { joinPathFragments, type Tree } from '@nx/devkit';
import { joinPathFragments, readNxJson, type Tree } from '@nx/devkit';
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
import { Linter } from '@nx/eslint';
import { E2eTestRunner, UnitTestRunner } from '../../../utils/test-runners';
@@ -25,8 +25,22 @@ export async function normalizeOptions(
options.rootProject = appProjectRoot === '.';
options.projectNameAndRootFormat = projectNameAndRootFormat;
const nxJson = readNxJson(host);
let e2eWebServerTarget = 'serve';
let e2ePort = options.port ?? 4200;
if (
nxJson.targetDefaults?.[e2eWebServerTarget] &&
(nxJson.targetDefaults?.[e2eWebServerTarget].options?.port ||
nxJson.targetDefaults?.[e2eWebServerTarget].options?.env?.PORT)
) {
e2ePort =
nxJson.targetDefaults?.[e2eWebServerTarget].options?.port ||
nxJson.targetDefaults?.[e2eWebServerTarget].options?.env?.PORT;
}
const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`;
const e2eProjectRoot = options.rootProject ? 'e2e' : `${appProjectRoot}-e2e`;
const e2eWebServerAddress = `http://localhost:${e2ePort}`;
const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim())
@@ -58,6 +72,9 @@ export async function normalizeOptions(
appProjectSourceRoot: `${appProjectRoot}/src`,
e2eProjectRoot,
e2eProjectName,
e2eWebServerAddress,
e2eWebServerTarget,
e2ePort,
parsedTags,
bundler,
outputPath: joinPathFragments(
@@ -11,6 +11,9 @@ export interface NormalizedSchema extends Schema {
appProjectSourceRoot: string;
e2eProjectName: string;
e2eProjectRoot: string;
e2eWebServerAddress: string;
e2eWebServerTarget: string;
e2ePort: number;
parsedTags: string[];
outputPath: string;
}
@@ -71,7 +71,11 @@ export async function libraryGeneratorInternal(
const pkgVersions = versions(tree);
await jsInitGenerator(tree, { ...options, js: false, skipFormat: true });
await jsInitGenerator(tree, {
...libraryOptions,
js: false,
skipFormat: true,
});
await init(tree, { ...libraryOptions, skipFormat: true });
ensureAngularDependencies(tree);
@@ -96,6 +100,7 @@ export async function libraryGeneratorInternal(
await setupTailwindGenerator(tree, {
project: libraryOptions.name,
skipFormat: true,
skipPackageJson: libraryOptions.skipPackageJson,
});
}
@@ -18,6 +18,40 @@ fetch('/assets/module-federation.manifest.json')
.then(() => import('./bootstrap').catch(err => console.error(err)));"
`;
exports[`Init MF --federationType=dynamic should wire up existing remote to dynamic host correctly 1`] = `
"import { NxWelcomeComponent } from './nx-welcome.component';
import { Route } from '@angular/router';
import { loadRemoteModule } from '@nx/angular/mf';
export const appRoutes: Route[] = [
{
path: 'remote1',
loadChildren: () => loadRemoteModule('remote1', './Module').then(m => m.RemoteEntryModule)
},
{
path: '',
component: NxWelcomeComponent
},];
"
`;
exports[`Init MF --federationType=dynamic should wire up existing remote to dynamic host correctly when --typescriptConfiguration=true 1`] = `
"import { NxWelcomeComponent } from './nx-welcome.component';
import { Route } from '@angular/router';
import { loadRemoteModule } from '@nx/angular/mf';
export const appRoutes: Route[] = [
{
path: 'remote1',
loadChildren: () => loadRemoteModule('remote1', './Module').then(m => m.RemoteEntryModule)
},
{
path: '',
component: NxWelcomeComponent
},];
"
`;
exports[`Init MF should add a remote application and add it to a specified host applications router config 1`] = `
"import { NxWelcomeComponent } from './nx-welcome.component';
import { Route } from '@angular/router';
@@ -14,7 +14,7 @@ const config: ModuleFederationConfig = {
* declare module 'my-external-remote';
*
*/
remotes: [<% remotes.forEach(function(remote) { %>'<%= remote.remoteName %>',<% }); %>]<% } %><% if(type === 'remote') { %>
remotes: [<% if (federationType === 'static') { remotes.forEach(function(remote) { %>'<%= remote.remoteName %>',<% }); } %>]<% } %><% if(type === 'remote') { %>
exposes: {<% if(standalone) { %>
'./Routes': '<%= projectRoot %>/src/app/remote-entry/entry.routes.ts',<% } else { %>
'./Module': '<%= projectRoot %>/src/app/remote-entry/entry.module.ts',<% } %>
@@ -12,7 +12,7 @@ module.exports = {
* declare module 'my-external-remote';
*
*/
remotes: [<% remotes.forEach(function(remote) { %>'<%= remote.remoteName %>',<% }); %>]<% } %><% if(type === 'remote') { %>
remotes: [<% if (federationType === 'static') { remotes.forEach(function(remote) { %>'<%= remote.remoteName %>',<% }); } %>]<% } %><% if(type === 'remote') { %>
exposes: {<% if(standalone) { %>
'./Routes': '<%= projectRoot %>/src/app/remote-entry/entry.routes.ts',<% } else { %>
'./Module': '<%= projectRoot %>/src/app/remote-entry/entry.module.ts',<% } %>
@@ -6,7 +6,6 @@ import {
Tree,
updateJson,
} from '@nx/devkit';
import type { Schema } from '../schema';
import { ArrayLiteralExpression } from 'typescript';
import { insertImport } from '@nx/js';
import { addRoute } from '../../../utils/nx-devkit/route-utils';
@@ -14,6 +13,13 @@ import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript'
let tsModule: typeof import('typescript');
export type AddRemoteOptions = {
host: string;
appName: string;
standalone: boolean;
port: number;
};
export function checkIsCommaNeeded(mfRemoteText: string) {
const remoteText = mfRemoteText.replace(/\s+/g, '');
return !remoteText.endsWith(',]')
@@ -23,7 +29,7 @@ export function checkIsCommaNeeded(mfRemoteText: string) {
: false;
}
export function addRemoteToHost(tree: Tree, options: Schema) {
export function addRemoteToHost(tree: Tree, options: AddRemoteOptions) {
if (options.host) {
const hostProject = readProjectConfiguration(tree, options.host);
const pathToMFManifest = joinPathFragments(
@@ -50,20 +56,6 @@ export function addRemoteToHost(tree: Tree, options: Schema) {
addRemoteToDynamicHost(tree, options, pathToMFManifest);
}
// const declarationFilePath = joinPathFragments(
// hostProject.sourceRoot,
// 'remotes.d.ts'
// );
//
// const declarationFileContent =
// (tree.exists(declarationFilePath)
// ? tree.read(declarationFilePath, 'utf-8')
// : '') +
// `\ndeclare module '${options.appName}/${
// options.standalone ? `Routes` : `Module`
// }';`;
// tree.write(declarationFilePath, declarationFileContent);
addLazyLoadedRouteToHostAppModule(tree, options, hostFederationType);
}
}
@@ -77,13 +69,13 @@ function determineHostFederationType(
function addRemoteToStaticHost(
tree: Tree,
options: Schema,
options: AddRemoteOptions,
hostProject: ProjectConfiguration,
isHostUsingTypescrpt: boolean
isHostUsingTypescript: boolean
) {
const hostMFConfigPath = joinPathFragments(
hostProject.root,
isHostUsingTypescrpt
isHostUsingTypescript
? 'module-federation.config.ts'
: 'module-federation.config.js'
);
@@ -115,7 +107,7 @@ function addRemoteToStaticHost(
function addRemoteToDynamicHost(
tree: Tree,
options: Schema,
options: AddRemoteOptions,
pathToMfManifest: string
) {
updateJson(tree, pathToMfManifest, (manifest) => {
@@ -129,7 +121,7 @@ function addRemoteToDynamicHost(
// TODO(colum): future work: allow dev to pass to path to routing module
function addLazyLoadedRouteToHostAppModule(
tree: Tree,
options: Schema,
options: AddRemoteOptions,
hostFederationType: 'dynamic' | 'static'
) {
if (!tsModule) {
@@ -33,6 +33,7 @@ export function generateWebpackConfig(
{
tmpl: '',
type: options.mfType,
federationType: options.federationType,
name: options.appName,
remotes: remotesWithPorts ?? [],
projectRoot: appRoot,
@@ -14,21 +14,10 @@ export function updateHostAppRoutes(tree: Tree, options: Schema) {
const { sourceRoot } = readProjectConfiguration(tree, options.appName);
const remoteRoutes =
options.remotes && Array.isArray(options.remotes)
? options.remotes.reduce(
(routes, remote) =>
`${routes}\n<li><a routerLink='${remote}'>${
names(remote).className
}</a></li>`,
''
)
: '';
tree.write(
joinPathFragments(sourceRoot, 'app/app.component.html'),
`<ul class="remote-menu">
<li><a routerLink="/">Home</a></li>${remoteRoutes}
<li><a routerLink="/">Home</a></li>
</ul>
<router-outlet></router-outlet>
`
@@ -535,6 +535,76 @@ describe('Init MF', () => {
).toBeTruthy();
expect(tree.read('app1/src/main.ts', 'utf-8')).toMatchSnapshot();
});
it('should wire up existing remote to dynamic host correctly', async () => {
await setupMf(tree, {
appName: 'remote1',
mfType: 'remote',
port: 4201,
routing: true,
typescriptConfiguration: false,
standalone: false,
skipFormat: true,
});
await setupMf(tree, {
appName: 'app1',
mfType: 'host',
routing: true,
federationType: 'dynamic',
remotes: ['remote1'],
typescriptConfiguration: false,
standalone: false,
skipFormat: true,
});
expect(tree.read('app1/module-federation.config.js', 'utf-8')).toContain(
'remotes: []'
);
expect(
readJson(tree, 'app1/src/assets/module-federation.manifest.json')
).toEqual({
remote1: 'http://localhost:4201',
});
expect(
tree.read('app1/src/app/app.routes.ts', 'utf-8')
).toMatchSnapshot();
});
it('should wire up existing remote to dynamic host correctly when --typescriptConfiguration=true', async () => {
await setupMf(tree, {
appName: 'remote1',
mfType: 'remote',
port: 4201,
routing: true,
typescriptConfiguration: true,
standalone: false,
skipFormat: true,
});
await setupMf(tree, {
appName: 'app1',
mfType: 'host',
routing: true,
federationType: 'dynamic',
remotes: ['remote1'],
typescriptConfiguration: true,
standalone: false,
skipFormat: true,
});
expect(tree.read('app1/module-federation.config.ts', 'utf-8')).toContain(
'remotes: []'
);
expect(
readJson(tree, 'app1/src/assets/module-federation.manifest.json')
).toEqual({
remote1: 'http://localhost:4201',
});
expect(
tree.read('app1/src/app/app.routes.ts', 'utf-8')
).toMatchSnapshot();
});
});
it('should add a remote to dynamic host correctly', async () => {
@@ -30,7 +30,12 @@ export async function setupMf(tree: Tree, rawOptions: Schema) {
let installTask = () => {};
if (options.mfType === 'remote') {
addRemoteToHost(tree, options);
addRemoteToHost(tree, {
appName: options.appName,
host: options.host,
standalone: options.standalone,
port: options.port,
});
addRemoteEntry(tree, options, projectConfig.root);
removeDeadCodeFromRemote(tree, options);
setupTspathForRemote(tree, options);
@@ -54,6 +59,14 @@ export async function setupMf(tree: Tree, rawOptions: Schema) {
if (options.mfType === 'host') {
setupHostIfDynamic(tree, options);
updateHostAppRoutes(tree, options);
for (const { remoteName, port } of remotesWithPorts) {
addRemoteToHost(tree, {
appName: remoteName,
host: options.appName,
standalone: options.standalone,
port,
});
}
installTask = addDependenciesToPackageJson(
tree,
{},
@@ -1,3 +1,9 @@
/**
* Note to developers: STOP! These exports end up as the public API of @nx/angular/src/utils.
*
* Try hard to not add to this API to reduce the surface area we need to maintain.
*/
export {
isStandalone,
addImportToComponent,
+1 -1
View File
@@ -99,7 +99,7 @@ Instead, Nx needs to know what build target to create the correct configuration
### Environment Variables
Using [executor configurations](recipes/executors/use-executor-configurations#use-executor-configurations) offers flexibility to set environment variables
Using [executor configurations](/concepts/executors-and-configurations#executors-and-configurations) offers flexibility to set environment variables
```json
"targets": {
@@ -16,7 +16,6 @@ export async function addE2e(
options: NormalizedSchema
): Promise<GeneratorCallback> {
const hasPlugin = hasExpoPlugin(tree);
const port = hasPlugin ? 8081 : 4200;
switch (options.e2eTestRunner) {
case 'cypress': {
const hasNxExpoPlugin = hasExpoPlugin(tree);
@@ -47,9 +46,9 @@ export async function addE2e(
// the name and root are already normalized, instruct the generator to use them as is
bundler: 'none',
skipFormat: true,
devServerTarget: `${options.projectName}:serve`,
port,
baseUrl: `http://localhost:${port}`,
devServerTarget: `${options.projectName}:${options.e2eWebServerTarget}`,
port: options.e2ePort,
baseUrl: options.e2eWebServerAddress,
ciWebServerCommand: hasNxExpoPlugin
? `nx run ${options.projectName}:serve-static`
: undefined,
@@ -76,10 +75,10 @@ export async function addE2e(
js: false,
linter: options.linter,
setParserOptionsProject: options.setParserOptionsProject,
webServerCommand: `${getPackageManagerCommand().exec} nx serve ${
options.name
}`,
webServerAddress: `http://localhost:${port}`,
webServerCommand: `${getPackageManagerCommand().exec} nx ${
options.e2eWebServerTarget
} ${options.name}`,
webServerAddress: options.e2eWebServerAddress,
rootProject: options.rootProject,
});
}
@@ -40,6 +40,9 @@ describe('Normalize Options', () => {
rootProject: false,
e2eProjectName: 'my-app-e2e',
e2eProjectRoot: 'my-app-e2e',
e2ePort: 8081,
e2eWebServerAddress: 'http://localhost:8081',
e2eWebServerTarget: 'serve',
} as NormalizedSchema);
});
@@ -72,6 +75,9 @@ describe('Normalize Options', () => {
rootProject: false,
e2eProjectName: 'myApp-e2e',
e2eProjectRoot: 'myApp-e2e',
e2ePort: 8081,
e2eWebServerAddress: 'http://localhost:8081',
e2eWebServerTarget: 'serve',
} as NormalizedSchema);
});
@@ -106,6 +112,9 @@ describe('Normalize Options', () => {
rootProject: false,
e2eProjectName: 'my-app-e2e',
e2eProjectRoot: 'directory-e2e',
e2ePort: 8081,
e2eWebServerAddress: 'http://localhost:8081',
e2eWebServerTarget: 'serve',
} as NormalizedSchema);
});
@@ -138,6 +147,9 @@ describe('Normalize Options', () => {
rootProject: false,
e2eProjectName: 'my-app-e2e',
e2eProjectRoot: 'directory/my-app-e2e',
e2ePort: 8081,
e2eWebServerAddress: 'http://localhost:8081',
e2eWebServerTarget: 'serve',
} as NormalizedSchema);
});
@@ -171,6 +183,9 @@ describe('Normalize Options', () => {
rootProject: false,
e2eProjectName: 'my-app-e2e',
e2eProjectRoot: 'my-app-e2e',
e2ePort: 8081,
e2eWebServerAddress: 'http://localhost:8081',
e2eWebServerTarget: 'serve',
} as NormalizedSchema);
});
});
@@ -1,6 +1,7 @@
import { names, readNxJson, Tree } from '@nx/devkit';
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
import { Schema } from '../schema';
import { ExpoPluginOptions } from '../../../../plugins/plugin';
export interface NormalizedSchema extends Schema {
className: string;
@@ -11,6 +12,9 @@ export interface NormalizedSchema extends Schema {
rootProject: boolean;
e2eProjectName: string;
e2eProjectRoot: string;
e2eWebServerAddress: string;
e2eWebServerTarget: string;
e2ePort: number;
}
export async function normalizeOptions(
@@ -41,8 +45,34 @@ export async function normalizeOptions(
? options.tags.split(',').map((s) => s.trim())
: [];
const rootProject = appProjectRoot === '.';
let e2eWebServerTarget = 'serve';
if (options.addPlugin) {
if (nxJson.plugins) {
for (const plugin of nxJson.plugins) {
if (
typeof plugin === 'object' &&
plugin.plugin === '@nx/expo/plugin' &&
(plugin.options as ExpoPluginOptions).serveTargetName
) {
e2eWebServerTarget = (plugin.options as ExpoPluginOptions)
.serveTargetName;
}
}
}
}
let e2ePort = options.addPlugin ? 8081 : 4200;
if (
nxJson.targetDefaults?.[e2eWebServerTarget] &&
nxJson.targetDefaults?.[e2eWebServerTarget].options?.port
) {
e2ePort = nxJson.targetDefaults?.[e2eWebServerTarget].options.port;
}
const e2eProjectName = rootProject ? 'e2e' : `${appProjectName}-e2e`;
const e2eProjectRoot = rootProject ? 'e2e' : `${appProjectRoot}-e2e`;
const e2eWebServerAddress = `http://localhost:${e2ePort}`;
return {
...options,
@@ -58,5 +88,8 @@ export async function normalizeOptions(
rootProject,
e2eProjectName,
e2eProjectRoot,
e2eWebServerAddress,
e2eWebServerTarget,
e2ePort,
};
}
+7 -1
View File
@@ -175,7 +175,13 @@ export async function* nodeExecutor(
`NX Process exited with code ${code}, waiting for changes to restart...`
);
}
if (!options.watch) done();
if (!options.watch) {
if (code !== 0) {
error(new Error(`Process exited with code ${code}`));
} else {
done();
}
}
resolve();
});
@@ -1,6 +1,8 @@
import { ExecutorContext, joinPathFragments, readJsonFile } from '@nx/devkit';
import { ExecutorContext, readJsonFile } from '@nx/devkit';
import { execSync } from 'child_process';
import { env as appendLocalEnv } from 'npm-run-path';
import { join } from 'path';
import { parseRegistryOptions } from '../../utils/npm-config';
import { logTar } from './log-tar';
import { PublishExecutorSchema } from './schema';
import chalk = require('chalk');
@@ -32,14 +34,14 @@ export default async function runExecutor(
const projectConfig =
context.projectsConfigurations!.projects[context.projectName!]!;
const packageRoot = joinPathFragments(
const packageRoot = join(
context.root,
options.packageRoot ?? projectConfig.root
);
const packageJsonPath = joinPathFragments(packageRoot, 'package.json');
const projectPackageJson = readJsonFile(packageJsonPath);
const packageName = projectPackageJson.name;
const packageJsonPath = join(packageRoot, 'package.json');
const packageJson = readJsonFile(packageJsonPath);
const packageName = packageJson.name;
// If package and project name match, we can make log messages terser
let packageTxt =
@@ -47,7 +49,7 @@ export default async function runExecutor(
? `package "${packageName}"`
: `package "${packageName}" from project "${context.projectName}"`;
if (projectPackageJson.private === true) {
if (packageJson.private === true) {
console.warn(
`Skipped ${packageTxt}, because it has \`"private": true\` in ${packageJsonPath}`
);
@@ -56,32 +58,28 @@ export default async function runExecutor(
};
}
const npmPublishCommandSegments = [`npm publish --json`];
const warnFn = (message: string) => {
console.log(chalk.keyword('orange')(message));
};
const { registry, tag, registryConfigKey } = await parseRegistryOptions(
context.root,
{
packageRoot,
packageJson,
},
{
registry: options.registry,
tag: options.tag,
},
warnFn
);
const npmViewCommandSegments = [
`npm view ${packageName} versions dist-tags --json`,
`npm view ${packageName} versions dist-tags --json --"${registryConfigKey}=${registry}"`,
];
const npmDistTagAddCommandSegments = [
`npm dist-tag add ${packageName}@${packageJson.version} ${tag} --"${registryConfigKey}=${registry}"`,
];
if (options.registry) {
npmPublishCommandSegments.push(`--registry=${options.registry}`);
npmViewCommandSegments.push(`--registry=${options.registry}`);
}
if (options.tag) {
npmPublishCommandSegments.push(`--tag=${options.tag}`);
}
if (options.otp) {
npmPublishCommandSegments.push(`--otp=${options.otp}`);
}
if (isDryRun) {
npmPublishCommandSegments.push(`--dry-run`);
}
// Resolve values using the `npm config` command so that things like environment variables and `publishConfig`s are accounted for
const registry =
options.registry ?? execSync(`npm config get registry`).toString().trim();
const tag = options.tag ?? execSync(`npm config get tag`).toString().trim();
/**
* In a dry-run scenario, it is most likely that all commands are being run with dry-run, therefore
@@ -92,11 +90,11 @@ export default async function runExecutor(
* perform the npm view step, and just show npm publish's dry-run output.
*/
if (!isDryRun && !options.firstRelease) {
const currentVersion = projectPackageJson.version;
const currentVersion = packageJson.version;
try {
const result = execSync(npmViewCommandSegments.join(' '), {
env: processEnv(true),
cwd: packageRoot,
cwd: context.root,
stdio: ['ignore', 'pipe', 'pipe'],
});
@@ -114,14 +112,11 @@ export default async function runExecutor(
if (resultJson.versions.includes(currentVersion)) {
try {
if (!isDryRun) {
execSync(
`npm dist-tag add ${packageName}@${currentVersion} ${tag} --registry=${registry}`,
{
env: processEnv(true),
cwd: packageRoot,
stdio: 'ignore',
}
);
execSync(npmDistTagAddCommandSegments.join(' '), {
env: processEnv(true),
cwd: context.root,
stdio: 'ignore',
});
console.log(
`Added the dist-tag ${tag} to v${currentVersion} for registry ${registry}.\n`
);
@@ -205,11 +200,23 @@ export default async function runExecutor(
console.log('Skipped npm view because --first-release was set');
}
const npmPublishCommandSegments = [
`npm publish ${packageRoot} --json --"${registryConfigKey}=${registry}" --tag=${tag}`,
];
if (options.otp) {
npmPublishCommandSegments.push(`--otp=${options.otp}`);
}
if (isDryRun) {
npmPublishCommandSegments.push(`--dry-run`);
}
try {
const output = execSync(npmPublishCommandSegments.join(' '), {
maxBuffer: LARGE_BUFFER,
env: processEnv(true),
cwd: packageRoot,
cwd: context.root,
stdio: ['ignore', 'pipe', 'pipe'],
});
@@ -15,8 +15,7 @@ export default defineConfig({
nxViteTsPaths(),
dts({
entryRoot: 'src',
tsConfigFilePath: path.join(__dirname, 'tsconfig.lib.json'),
skipDiagnostics: true,
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
}),
],
@@ -2,7 +2,6 @@ import {
ProjectGraphProjectNode,
Tree,
formatFiles,
joinPathFragments,
output,
readJson,
updateJson,
@@ -11,7 +10,7 @@ import {
} from '@nx/devkit';
import * as chalk from 'chalk';
import { exec } from 'node:child_process';
import { relative } from 'node:path';
import { join } from 'node:path';
import { IMPLICIT_DEFAULT_RELEASE_GROUP } from 'nx/src/command-line/release/config/config';
import {
getFirstGitCommit,
@@ -31,6 +30,7 @@ import {
import { interpolate } from 'nx/src/tasks-runner/utils';
import * as ora from 'ora';
import { prerelease } from 'semver';
import { parseRegistryOptions } from '../../utils/npm-config';
import { ReleaseVersionGeneratorSchema } from './schema';
import { resolveLocalPackageDependencies } from './utils/resolve-local-package-dependencies';
import { updateLockFile } from './utils/update-lock-file';
@@ -111,11 +111,7 @@ Valid values are: ${validReleaseVersionPrefixes
);
}
const packageJsonPath = joinPathFragments(packageRoot, 'package.json');
const workspaceRelativePackageJsonPath = relative(
workspaceRoot,
packageJsonPath
);
const packageJsonPath = join(packageRoot, 'package.json');
const color = getColor(projectName);
const log = (msg: string) => {
@@ -124,7 +120,7 @@ Valid values are: ${validReleaseVersionPrefixes
if (!tree.exists(packageJsonPath)) {
throw new Error(
`The project "${projectName}" does not have a package.json available at ${workspaceRelativePackageJsonPath}.
`The project "${projectName}" does not have a package.json available at ${packageJsonPath}.
To fix this you will either need to add a package.json file at that location, or configure "release" within your nx.json to exclude "${projectName}" from the current release group, or amend the packageRoot configuration to point to where the package.json should be.`
);
@@ -136,22 +132,40 @@ To fix this you will either need to add a package.json file at that location, or
)}`
);
const projectPackageJson = readJson(tree, packageJsonPath);
const packageJson = readJson(tree, packageJsonPath);
log(
`🔍 Reading data for package "${projectPackageJson.name}" from ${workspaceRelativePackageJsonPath}`
`🔍 Reading data for package "${packageJson.name}" from ${packageJsonPath}`
);
const { name: packageName, version: currentVersionFromDisk } =
projectPackageJson;
packageJson;
switch (options.currentVersionResolver) {
case 'registry': {
const metadata = options.currentVersionResolverMetadata;
const registry =
metadata?.registry ??
(await getNpmRegistry()) ??
'https://registry.npmjs.org';
const tag = metadata?.tag ?? 'latest';
const registryArg =
typeof metadata?.registry === 'string'
? metadata.registry
: undefined;
const tagArg =
typeof metadata?.tag === 'string' ? metadata.tag : undefined;
const warnFn = (message: string) => {
console.log(chalk.keyword('orange')(message));
};
const { registry, tag, registryConfigKey } =
await parseRegistryOptions(
workspaceRoot,
{
packageRoot: join(workspaceRoot, packageRoot),
packageJson,
},
{
registry: registryArg,
tag: tagArg,
},
warnFn
);
/**
* If the currentVersionResolver is set to registry, and the projects are not independent, we only want to make the request once for the whole batch of projects.
@@ -174,7 +188,7 @@ To fix this you will either need to add a package.json file at that location, or
// Must be non-blocking async to allow spinner to render
currentVersion = await new Promise<string>((resolve, reject) => {
exec(
`npm view ${packageName} version --registry=${registry} --tag=${tag}`,
`npm view ${packageName} version --"${registryConfigKey}=${registry}" --tag=${tag}`,
(error, stdout, stderr) => {
if (error) {
return reject(error);
@@ -429,7 +443,7 @@ To fix this you will either need to add a package.json file at that location, or
if (!specifier) {
log(
`🚫 Skipping versioning "${projectPackageJson.name}" as no changes were detected.`
`🚫 Skipping versioning "${packageJson.name}" as no changes were detected.`
);
continue;
}
@@ -442,13 +456,11 @@ To fix this you will either need to add a package.json file at that location, or
versionData[projectName].newVersion = newVersion;
writeJson(tree, packageJsonPath, {
...projectPackageJson,
...packageJson,
version: newVersion,
});
log(
`✍️ New version ${newVersion} written to ${workspaceRelativePackageJsonPath}`
);
log(`✍️ New version ${newVersion} written to ${packageJsonPath}`);
if (dependentProjects.length > 0) {
log(
@@ -471,34 +483,30 @@ To fix this you will either need to add a package.json file at that location, or
`The dependent project "${dependentProject.source}" does not have a packageRoot available. Please report this issue on https://github.com/nrwl/nx`
);
}
updateJson(
tree,
joinPathFragments(dependentPackageRoot, 'package.json'),
(json) => {
// Auto (i.e.infer existing) by default
let versionPrefix = options.versionPrefix ?? 'auto';
updateJson(tree, join(dependentPackageRoot, 'package.json'), (json) => {
// Auto (i.e.infer existing) by default
let versionPrefix = options.versionPrefix ?? 'auto';
// For auto, we infer the prefix based on the current version of the dependent
if (versionPrefix === 'auto') {
versionPrefix = ''; // we don't want to end up printing auto
// For auto, we infer the prefix based on the current version of the dependent
if (versionPrefix === 'auto') {
versionPrefix = ''; // we don't want to end up printing auto
const current =
json[dependentProject.dependencyCollection][packageName];
if (current) {
const prefixMatch = current.match(/^[~^]/);
if (prefixMatch) {
versionPrefix = prefixMatch[0];
} else {
versionPrefix = '';
}
const current =
json[dependentProject.dependencyCollection][packageName];
if (current) {
const prefixMatch = current.match(/^[~^]/);
if (prefixMatch) {
versionPrefix = prefixMatch[0];
} else {
versionPrefix = '';
}
}
json[dependentProject.dependencyCollection][
packageName
] = `${versionPrefix}${newVersion}`;
return json;
}
);
json[dependentProject.dependencyCollection][
packageName
] = `${versionPrefix}${newVersion}`;
return json;
});
}
}
@@ -571,18 +579,3 @@ function getColor(projectName: string) {
return colors[colorIndex];
}
async function getNpmRegistry() {
// Must be non-blocking async to allow spinner to render
return await new Promise<string>((resolve, reject) => {
exec('npm config get registry', (error, stdout, stderr) => {
if (error) {
return reject(error);
}
if (stderr) {
return reject(stderr);
}
return resolve(stdout.trim());
});
});
}
@@ -434,6 +434,7 @@ export function createTmpTsConfig(
workspaceRoot,
'tmp',
projectRoot,
process.env.NX_TASK_TARGET_TARGET ?? 'build',
'tsconfig.generated.json'
);
const parsedTSConfig = readTsConfigWithRemappedPaths(
+328
View File
@@ -0,0 +1,328 @@
import { ExecException } from 'child_process';
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
import { PackageJson } from 'nx/src/utils/package-json';
import { join } from 'path';
import { getNpmRegistry, getNpmTag, parseRegistryOptions } from './npm-config';
jest.mock('child_process', () => {
const original = jest.requireActual('child_process');
return {
...original,
exec: jest
.fn()
.mockImplementation(
(
command: string,
_: unknown,
callback: (
error: ExecException,
stdout: string,
stderr: string
) => void
) => {
switch (command) {
case 'npm config get @scope:registry':
callback(null, 'https://scoped-registry.com', null);
break;
case 'npm config get @missing:registry':
callback(null, 'undefined', null);
break;
case 'npm config get registry':
callback(null, 'https://custom-registry.com', null);
break;
case 'npm config get tag':
callback(null, 'next', null);
break;
default:
callback(
new Error(`unexpected command: ${command}`),
null,
'ERROR'
);
}
}
),
};
});
describe('npm-config', () => {
let tempFs: TempFs;
beforeEach(() => {
tempFs = new TempFs('npm-config');
});
describe('getNpmRegistry', () => {
it('should return scoped registry if it exists', async () => {
const registry = await getNpmRegistry(tempFs.tempDir, '@scope');
expect(registry).toEqual('https://scoped-registry.com');
});
it('should return registry if scoped registry does not exist', async () => {
const registry = await getNpmRegistry(tempFs.tempDir, '@missing');
expect(registry).toEqual('https://custom-registry.com');
});
it('should return registry if package is not scoped', async () => {
const registry = await getNpmRegistry(tempFs.tempDir);
expect(registry).toEqual('https://custom-registry.com');
});
});
describe('getNpmTag', () => {
it('should return tag from npm config', async () => {
const tag = await getNpmTag(tempFs.tempDir);
expect(tag).toEqual('next');
});
});
describe('parseRegistryOptions', () => {
let logMessage: string;
const logFn = (message: string) => {
logMessage += message;
};
beforeEach(() => {
logMessage = '';
});
it('should warn if .npmrc exists in the package root', async () => {
await tempFs.createFile(
join('packages', 'pkg1', '.npmrc'),
'registry=https://custom-registry.com'
);
await parseRegistryOptions(
tempFs.tempDir,
{
packageRoot: join(tempFs.tempDir, 'packages', 'pkg1'),
packageJson: {
name: 'pkg1',
} as PackageJson,
},
{},
logFn
);
expect(logMessage).toContain(
'Ignoring .npmrc file detected in the package root'
);
});
it('should warn and return registry set in publishConfig', async () => {
const { registry, registryConfigKey } = await parseRegistryOptions(
tempFs.tempDir,
{
packageRoot: tempFs.tempDir,
packageJson: {
name: 'pkg1',
publishConfig: {
registry: 'https://publish-config.com',
} as PackageJson['publishConfig'],
} as PackageJson,
},
{},
logFn
);
expect(logMessage).toContain("Registry detected in the 'publishConfig'");
expect(logMessage).toContain(
'prevents the registry from being overridden'
);
expect(registry).toEqual('https://publish-config.com');
expect(registryConfigKey).toEqual('registry');
});
it('should warn and return registry set in publishConfig instead of registry arg', async () => {
const { registry, registryConfigKey } = await parseRegistryOptions(
tempFs.tempDir,
{
packageRoot: tempFs.tempDir,
packageJson: {
name: 'pkg1',
publishConfig: {
registry: 'https://publish-config.com',
} as PackageJson['publishConfig'],
} as PackageJson,
},
{
registry: 'https://ignored-registry.com',
},
logFn
);
expect(logMessage).toContain("Registry detected in the 'publishConfig'");
expect(logMessage).toContain('This will override your registry option');
expect(registry).toEqual('https://publish-config.com');
expect(registryConfigKey).toEqual('registry');
});
it('should warn and return scoped registry set in publishConfig instead of registry arg for a scoped package', async () => {
const { registry, registryConfigKey } = await parseRegistryOptions(
tempFs.tempDir,
{
packageRoot: tempFs.tempDir,
packageJson: {
name: '@scope/pkg1',
publishConfig: {
'@scope:registry': 'https://publish-config.com',
} as PackageJson['publishConfig'],
} as PackageJson,
},
{
registry: 'https://ignored-registry.com',
},
logFn
);
expect(logMessage).toContain("Registry detected in the 'publishConfig'");
expect(registry).toContain('https://publish-config.com');
expect(registryConfigKey).toEqual('@scope:registry');
});
it('should warn if registry is set in publishConfig for a scoped package, but still return registry arg', async () => {
const { registry, registryConfigKey } = await parseRegistryOptions(
tempFs.tempDir,
{
packageRoot: tempFs.tempDir,
packageJson: {
name: '@scope/pkg1',
publishConfig: {
registry: 'https://publish-config.com',
} as PackageJson['publishConfig'],
} as PackageJson,
},
{
registry: 'https://registry-arg.com',
},
logFn
);
expect(logMessage).toContain("Registry detected in the 'publishConfig'");
expect(registry).toContain('https://registry-arg.com');
expect(registryConfigKey).toEqual('@scope:registry');
});
it('should return registry arg over npm config', async () => {
const { registry, registryConfigKey } = await parseRegistryOptions(
tempFs.tempDir,
{
packageRoot: tempFs.tempDir,
packageJson: {
name: 'pkg1',
} as PackageJson,
},
{
registry: 'https://registry-arg.com',
},
logFn
);
expect(registry).toEqual('https://registry-arg.com');
expect(registryConfigKey).toEqual('registry');
});
it('should return registry arg over npm config for scoped packages', async () => {
const { registry, registryConfigKey } = await parseRegistryOptions(
tempFs.tempDir,
{
packageRoot: tempFs.tempDir,
packageJson: {
name: '@scope/pkg1',
} as PackageJson,
},
{
registry: 'https://registry-arg.com',
},
logFn
);
expect(registry).toEqual('https://registry-arg.com');
expect(registryConfigKey).toEqual('@scope:registry');
});
it('should defer to npm config for scoped registry', async () => {
const { registry, registryConfigKey } = await parseRegistryOptions(
tempFs.tempDir,
{
packageRoot: tempFs.tempDir,
packageJson: {
name: '@scope/pkg1',
} as PackageJson,
},
{},
logFn
);
expect(registry).toEqual('https://scoped-registry.com');
expect(registryConfigKey).toEqual('@scope:registry');
});
it('should defer to npm config for registry if scoped registry does not exist', async () => {
const { registry, registryConfigKey } = await parseRegistryOptions(
tempFs.tempDir,
{
packageRoot: tempFs.tempDir,
packageJson: {
name: '@missing/pkg1',
} as PackageJson,
},
{},
logFn
);
expect(registry).toEqual('https://custom-registry.com');
expect(registryConfigKey).toEqual('@missing:registry');
});
it('should defer to npm config for registry if package is not scoped', async () => {
const { registry, registryConfigKey } = await parseRegistryOptions(
tempFs.tempDir,
{
packageRoot: tempFs.tempDir,
packageJson: {
name: 'pkg1',
} as PackageJson,
},
{},
logFn
);
expect(registry).toEqual('https://custom-registry.com');
expect(registryConfigKey).toEqual('registry');
});
it('should return npm tag from config', async () => {
const { tag } = await parseRegistryOptions(
tempFs.tempDir,
{
packageRoot: tempFs.tempDir,
packageJson: {
name: 'pkg1',
} as PackageJson,
},
{},
logFn
);
expect(tag).toEqual('next');
});
it('should override npm tag when tag option is passed', async () => {
const { tag } = await parseRegistryOptions(
tempFs.tempDir,
{
packageRoot: tempFs.tempDir,
packageJson: {
name: 'pkg1',
} as PackageJson,
},
{
tag: 'alpha',
},
logFn
);
expect(tag).toEqual('alpha');
});
});
});
+121
View File
@@ -0,0 +1,121 @@
import { exec } from 'child_process';
import { existsSync } from 'fs';
import { PackageJson } from 'nx/src/utils/package-json';
import { join, relative } from 'path';
export async function parseRegistryOptions(
cwd: string,
pkg: {
packageRoot: string;
packageJson: PackageJson;
},
options: {
registry?: string;
tag?: string;
},
logWarnFn: (message: string) => void = console.warn
): Promise<{ registry: string; tag: string; registryConfigKey: string }> {
const npmRcPath = join(pkg.packageRoot, '.npmrc');
if (existsSync(npmRcPath)) {
const relativeNpmRcPath = relative(cwd, npmRcPath);
logWarnFn(
`\nIgnoring .npmrc file detected in the package root: ${relativeNpmRcPath}. Nested .npmrc files are not supported by npm. Only the .npmrc file at the root of the workspace will be used. To customize the registry or tag for specific packages, see https://nx.dev/recipes/nx-release/configure-custom-registries\n`
);
}
const scope = pkg.packageJson.name.startsWith('@')
? pkg.packageJson.name.split('/')[0]
: '';
// If the package is scoped, then the registry argument that will
// correctly override the registry in the .npmrc file must be scoped.
const registryConfigKey = scope ? `${scope}:registry` : 'registry';
const publishConfigRegistry =
pkg.packageJson.publishConfig?.[registryConfigKey];
// Even though it won't override the actual registry that's actually used,
// the user might think otherwise, so we should still warn if the user has
// set a 'registry' in 'publishConfig' for a scoped package.
if (publishConfigRegistry || pkg.packageJson.publishConfig?.registry) {
const relativePackageJsonPath = relative(
cwd,
join(pkg.packageRoot, 'package.json')
);
if (options.registry) {
logWarnFn(
`\nRegistry detected in the 'publishConfig' of the package manifest: ${relativePackageJsonPath}. This will override your registry option set in the project configuration or passed via the --registry argument, which is why configuring the registry with 'publishConfig' is not recommended. For details, see https://nx.dev/recipes/nx-release/configure-custom-registries\n`
);
} else {
logWarnFn(
`\nRegistry detected in the 'publishConfig' of the package manifest: ${relativePackageJsonPath}. Configuring the registry in this way is not recommended because it prevents the registry from being overridden in project configuration or via the --registry argument. To customize the registry for specific packages, see https://nx.dev/recipes/nx-release/configure-custom-registries\n`
);
}
}
const registry =
// `npm publish` will always use the publishConfig registry if it exists, even over the --registry arg
publishConfigRegistry ||
options.registry ||
(await getNpmRegistry(cwd, scope));
const tag = options.tag || (await getNpmTag(cwd));
return { registry, tag, registryConfigKey };
}
/**
* Returns the npm registry that is used for publishing.
*
* @param scope the scope of the package for which to determine the registry
* @param cwd the directory where the npm config should be read from
*/
export async function getNpmRegistry(
cwd: string,
scope?: string
): Promise<string> {
let registry: string | undefined;
if (scope) {
registry = await getNpmConfigValue(`${scope}:registry`, cwd);
}
if (!registry) {
registry = await getNpmConfigValue('registry', cwd);
}
return registry;
}
/**
* Returns the npm tag that is used for publishing.
*
* @param cwd the directory where the npm config should be read from
*/
export async function getNpmTag(cwd: string): Promise<string> {
// npm does not support '@scope:tag' in the npm config, so we only need to check for 'tag'.
return getNpmConfigValue('tag', cwd);
}
async function getNpmConfigValue(key: string, cwd: string): Promise<string> {
try {
const result = await execAsync(`npm config get ${key}`, cwd);
return result === 'undefined' ? undefined : result;
} catch (e) {
return Promise.resolve(undefined);
}
}
async function execAsync(command: string, cwd: string): Promise<string> {
// Must be non-blocking async to allow spinner to render
return new Promise<string>((resolve, reject) => {
exec(command, { cwd }, (error, stdout, stderr) => {
if (error) {
return reject(error);
}
if (stderr) {
return reject(stderr);
}
return resolve(stdout.trim());
});
});
}
+1
View File
@@ -88,6 +88,7 @@
"webpack",
// require.resovle is used for these
"@babel/plugin-proposal-decorators",
"file-loader",
"url-loader",
"@svgr/webpack"
]
@@ -1,7 +1,7 @@
{% callout type="caution" title="Can I use component testing?" %}
Next component testing with Nx requires **Cypress version 10.7.0** and up.
You can migrate with to v10 via the [migrate-to-cypress-11 generator](/packages/cypress/generators/migrate-to-cypress-11).
You can migrate with to v11 via the [migrate-to-cypress-11 generator](/packages/cypress/generators/migrate-to-cypress-11).
This generator is for Cypress based component testing.
+1 -1
View File
@@ -39,10 +39,10 @@
"@svgr/webpack": "^8.0.1",
"chalk": "^4.1.0",
"copy-webpack-plugin": "^10.2.4",
"file-loader": "^6.2.0",
"fs-extra": "^11.1.0",
"ignore": "^5.0.4",
"semver": "^7.5.3",
"url-loader": "^4.1.1",
"tslib": "^2.3.0",
"webpack-merge": "^5.8.0",
"@nx/js": "file:../js",
+1 -66
View File
@@ -1,69 +1,4 @@
import { NextConfigComplete } from 'next/dist/server/config-shared';
import { getAliasForProject, getNextConfig } from './with-nx';
describe('getNextConfig', () => {
describe('svgr', () => {
it('should be used by default', () => {
const config = getNextConfig();
const result = config.webpack(
{
module: { rules: [{ oneOf: [] }] },
},
{
buildId: 'build-id',
config: config as NextConfigComplete,
dev: true,
dir: 'dir',
isServer: false,
totalPages: 0,
webpack: undefined,
defaultLoaders: {
babel: {
options: {},
},
},
}
);
expect(
result.module.rules.some((rule) => rule.test?.test('cat.svg'))
).toBe(true);
});
it('should not be used when disabled', () => {
const config = getNextConfig({
nx: {
svgr: false,
},
});
const result = config.webpack(
{
module: { rules: [{ oneOf: [] }] },
},
{
buildId: 'build-id',
config: config as NextConfigComplete,
dev: true,
dir: 'dir',
isServer: false,
totalPages: 0,
webpack: undefined,
defaultLoaders: {
babel: {
options: {},
},
},
}
);
expect(
result.module.rules.some((rule) => rule.test?.test('cat.svg'))
).toBe(false);
});
});
});
import { getAliasForProject } from './with-nx';
describe('getAliasForProject', () => {
it('should return the matching alias for a project', () => {
+48 -15
View File
@@ -337,22 +337,55 @@ export function getNextConfig(
// Default SVGR support to be on for projects.
if (nx?.svgr !== false) {
config.module.rules.push(
// Apply rule for svg imports ending in ?url
{
test: /\.svg$/i,
type: 'asset',
resourceQuery: /url/, // apply to *.svg?url
// TODO(v20): Remove file-loader and use `?react` querystring to differentiate between asset and SVGR.
// It should be:
// use: [{
// test: /\.svg$/i,
// type: 'asset',
// resourceQuery: /react/, // *.svg?react
// },
// {
// test: /\.svg$/i,
// issuer: /\.[jt]sx?$/,
// resourceQuery: { not: [/react/] }, // exclude react component if *.svg?react
// use: ['@svgr/webpack'],
// }],
// See:
// - SVGR: https://react-svgr.com/docs/webpack/#use-svgr-and-asset-svg-in-the-same-project
// - Vite: https://www.npmjs.com/package/vite-plugin-svgr
// - Rsbuild: https://github.com/web-infra-dev/rsbuild/pull/1783
// Note: We also need a migration for any projects that are using SVGR to convert
// `import { ReactComponent as X } from './x.svg` to
// `import X from './x.svg?react';
config.module.rules.push({
test: /\.svg$/,
issuer: { not: /\.(css|scss|sass)$/ },
resourceQuery: {
not: [
/__next_metadata__/,
/__next_metadata_route__/,
/__next_metadata_image_meta__/,
],
},
// Convert all other svg imports to React components
{
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
resourceQuery: { not: [/url/] },
use: ['@svgr/webpack'],
}
);
use: [
{
loader: require.resolve('@svgr/webpack'),
options: {
svgo: false,
titleProp: true,
ref: true,
},
},
{
loader: require.resolve('file-loader'),
options: {
// Next.js hard-codes assets to load from "static/media".
// See: https://github.com/vercel/next.js/blob/53d017d/packages/next/src/build/webpack-config.ts#L1993
name: 'static/media/[name].[hash].[ext]',
},
},
],
});
}
return userWebpack(config, options);
@@ -7,6 +7,7 @@ import {
Tree,
} from '@nx/devkit';
import { initGenerator as jsInitGenerator } from '@nx/js';
import { setupTailwindGenerator } from '@nx/react';
import {
testingLibraryReactVersion,
typesReactDomVersion,
@@ -70,6 +71,14 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
const lintTask = await addLinting(host, options);
tasks.push(lintTask);
if (options.style === 'tailwind') {
const tailwindTask = await setupTailwindGenerator(host, {
project: options.projectName,
});
tasks.push(tailwindTask);
}
const styledTask = addStyleDependencies(host, {
style: options.style,
swc: !host.exists(joinPathFragments(options.appProjectRoot, '.babelrc')),
@@ -48,14 +48,12 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
project: options.e2eProjectName,
directory: 'src',
skipFormat: true,
devServerTarget: `${options.projectName}:${
hasPlugin ? 'start' : 'serve'
}`,
baseUrl: `http://localhost:${hasPlugin ? '3000' : '4200'}`,
devServerTarget: `${options.projectName}:${options.e2eWebServerTarget}`,
baseUrl: options.e2eWebServerAddress,
jsx: true,
webServerCommands: hasPlugin
? {
default: `nx run ${options.projectName}:start`,
default: `nx run ${options.projectName}:${options.e2eWebServerTarget}`,
}
: undefined,
ciWebServerCommand: hasPlugin
@@ -81,9 +79,9 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
js: false,
linter: options.linter,
setParserOptionsProject: options.setParserOptionsProject,
webServerAddress: `http://127.0.0.1:${hasPlugin ? '3000' : '4200'}`,
webServerAddress: `http://127.0.0.1:${options.e2ePort}`,
webServerCommand: `${getPackageManagerCommand().exec} nx ${
hasPlugin ? 'start' : 'serve'
options.e2eWebServerTarget
} ${options.projectName}`,
addPlugin: options.addPlugin,
});
@@ -21,6 +21,9 @@ describe('updateEslint', () => {
unitTestRunner: 'jest',
e2eProjectName: 'my-app-e2e',
e2eProjectRoot: 'my-app-e2e',
e2ePort: 3000,
e2eWebServerTarget: 'start',
e2eWebServerAddress: 'http://localhost:4200',
outputPath: 'dist/my-app',
name: 'my-app',
parsedTags: [],
@@ -56,6 +56,7 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) {
pageStyleContent: `.page {}`,
stylesExt: options.style === 'less' ? options.style : 'css',
style: options.style === 'tailwind' ? 'css' : options.style,
};
const generatedAppFilePath = options.src
@@ -3,6 +3,7 @@ import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/pr
import { Linter } from '@nx/eslint';
import { assertValidStyle } from '@nx/react/src/utils/assertion';
import { Schema } from '../schema';
import { NextPluginOptions } from '../../../plugins/plugin';
export interface NormalizedSchema extends Schema {
projectName: string;
@@ -10,6 +11,9 @@ export interface NormalizedSchema extends Schema {
outputPath: string;
e2eProjectName: string;
e2eProjectRoot: string;
e2eWebServerAddress: string;
e2eWebServerTarget: string;
e2ePort: number;
parsedTags: string[];
fileName: string;
styledModule: null | string;
@@ -42,8 +46,36 @@ export async function normalizeOptions(
options.addPlugin ??= addPlugin;
let e2eWebServerTarget = options.addPlugin ? 'start' : 'serve';
if (options.addPlugin) {
if (nxJson.plugins) {
for (const plugin of nxJson.plugins) {
if (
typeof plugin === 'object' &&
plugin.plugin === '@nx/next/plugin' &&
(plugin.options as NextPluginOptions).startTargetName
) {
e2eWebServerTarget = (plugin.options as NextPluginOptions)
.startTargetName;
}
}
}
}
let e2ePort = options.addPlugin ? 3000 : 4200;
if (
nxJson.targetDefaults?.[e2eWebServerTarget] &&
(nxJson.targetDefaults?.[e2eWebServerTarget].options?.port ||
nxJson.targetDefaults?.[e2eWebServerTarget].options?.env?.PORT)
) {
e2ePort =
nxJson.targetDefaults?.[e2eWebServerTarget].options?.port ||
nxJson.targetDefaults?.[e2eWebServerTarget].options?.env?.PORT;
}
const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`;
const e2eProjectRoot = options.rootProject ? 'e2e' : `${appProjectRoot}-e2e`;
const e2eWebServerAddress = `http://localhost:${e2ePort}`;
const name = names(options.name).fileName;
@@ -62,7 +94,7 @@ export async function normalizeOptions(
const appDir = options.appDir ?? true;
const src = options.src ?? true;
const styledModule = /^(css|scss|less)$/.test(options.style)
const styledModule = /^(css|scss|less|tailwind)$/.test(options.style)
? null
: options.style;
@@ -75,6 +107,9 @@ export async function normalizeOptions(
appProjectRoot,
e2eProjectName,
e2eProjectRoot,
e2eWebServerAddress,
e2eWebServerTarget,
e2ePort,
e2eTestRunner: options.e2eTestRunner || 'cypress',
fileName,
linter: options.linter || Linter.EsLint,
@@ -46,6 +46,10 @@
"value": "less",
"label": "LESS [ https://lesscss.org ]"
},
{
"value": "tailwind",
"label": "tailwind [ https://tailwindcss.com/ ]"
},
{
"value": "styled-components",
"label": "styled-components [ https://styled-components.com ]"
@@ -14,7 +14,7 @@ You can read more about how this generator works, in the [Storybook for Vue over
When running this generator, you will be prompted to provide the following:
- The `name` of the project you want to generate the configuration for.
- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/vue/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. Also, a `test-storybook` target will be generated in your project's `project.json`, with a command to invoke the [Storybook `test-runner`](https://storybook.js.org/docs/vue/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/packages/storybook/documents/interaction-tests)..
- Whether you want to set up [Storybook interaction tests](https://storybook.js.org/docs/vue/writing-tests/interaction-testing) (`interactionTests`). If you choose `yes`, a `play` function will be added to your stories, and all the necessary dependencies will be installed. Also, a `test-storybook` target will be generated in your project's `project.json`, with a command to invoke the [Storybook `test-runner`](https://storybook.js.org/docs/vue/writing-tests/test-runner). You can read more about this in the [Nx Storybook interaction tests documentation page](/recipes/storybook/storybook-interaction-tests#setup-storybook-interaction-tests)..
- Whether you want to `generateStories` for the components in your project. If you choose `yes`, a `.stories.ts` file will be generated next to each of your components in your project.
You must provide a `name` for the generator to work.
@@ -27,14 +27,14 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
directory: 'src',
bundler: 'vite',
skipFormat: true,
devServerTarget: `${options.projectName}:serve`,
devServerTarget: `${options.projectName}:${options.e2eWebServerTarget}`,
webServerCommands: {
default: `${getPackageManagerCommand().exec} nx serve ${
options.projectName
}`,
default: `${getPackageManagerCommand().exec} nx ${
options.e2eWebServerTarget
} ${options.projectName}`,
},
ciWebServerCommand: `nx run ${options.projectName}:serve-static`,
baseUrl: 'http://localhost:4200',
baseUrl: options.e2eWebServerAddress,
jsx: true,
addPlugin: true,
});
@@ -56,10 +56,10 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
js: false,
linter: options.linter,
setParserOptionsProject: options.setParserOptionsProject,
webServerAddress: 'http://localhost:4200',
webServerCommand: `${getPackageManagerCommand().exec} nx serve ${
options.projectName
}`,
webServerAddress: options.e2eWebServerAddress,
webServerCommand: `${getPackageManagerCommand().exec} nx ${
options.e2eWebServerTarget
} ${options.projectName}`,
addPlugin: true,
});
}
@@ -1,6 +1,7 @@
import { Tree, extractLayoutDirectory, names } from '@nx/devkit';
import { Tree, extractLayoutDirectory, names, readNxJson } from '@nx/devkit';
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
import { NormalizedSchema, Schema } from '../schema';
import { NuxtPluginOptions } from '../../../plugins/plugin';
export function normalizeDirectory(options: Schema) {
options.directory = options.directory?.replace(/\\{1,2}/g, '/');
@@ -30,8 +31,31 @@ export async function normalizeOptions(
options.rootProject = appProjectRoot === '.';
options.projectNameAndRootFormat = projectNameAndRootFormat;
const nxJson = readNxJson(host);
let e2eWebServerTarget = 'serve';
if (nxJson.plugins) {
for (const plugin of nxJson.plugins) {
if (
typeof plugin === 'object' &&
plugin.plugin === '@nx/nuxt/plugin' &&
(plugin.options as NuxtPluginOptions).serveTargetName
) {
e2eWebServerTarget = (plugin.options as NuxtPluginOptions)
.serveTargetName;
}
}
}
let e2ePort = 4200;
if (
nxJson.targetDefaults?.[e2eWebServerTarget] &&
nxJson.targetDefaults?.[e2eWebServerTarget].options?.port
) {
e2ePort = nxJson.targetDefaults?.[e2eWebServerTarget].options?.port;
}
const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`;
const e2eProjectRoot = options.rootProject ? 'e2e' : `${appProjectRoot}-e2e`;
const e2eWebServerAddress = `http://localhost:${e2ePort}`;
const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim())
@@ -44,6 +68,9 @@ export async function normalizeOptions(
appProjectRoot,
e2eProjectName,
e2eProjectRoot,
e2eWebServerAddress,
e2eWebServerTarget,
e2ePort,
parsedTags,
style: options.style ?? 'none',
} as NormalizedSchema;
+3
View File
@@ -22,5 +22,8 @@ export interface NormalizedSchema extends Schema {
appProjectRoot: string;
e2eProjectName: string;
e2eProjectRoot: string;
e2eWebServerAddress: string;
e2eWebServerTarget: string;
e2ePort: number;
parsedTags: string[];
}
+1 -1
View File
@@ -4,7 +4,7 @@ version = '0.1.0'
edition = '2021'
[dependencies]
portable-pty = { git = "https://github.com/cammisuli/wezterm" }
portable-pty = { git = "https://github.com/cammisuli/wezterm", rev = "b538ee29e1e89eeb4832fb35ae095564dce34c29" }
anyhow = "1.0.71"
colored = "2"
crossbeam-channel = '0.5'
+25 -1
View File
@@ -57,6 +57,7 @@ export async function reportHandler() {
pmVersion,
localPlugins,
communityPlugins,
registeredPlugins,
packageVersionsWeCareAbout,
outOfSyncPackageGroup,
projectGraphError,
@@ -77,6 +78,14 @@ export async function reportHandler() {
);
});
if (registeredPlugins.length) {
bodyLines.push(LINE_SEPARATOR);
bodyLines.push('Registered Plugins:');
for (const plugin of registeredPlugins) {
bodyLines.push(`${chalk.green(plugin)}`);
}
}
if (communityPlugins.length) {
bodyLines.push(LINE_SEPARATOR);
padding = Math.max(...communityPlugins.map((x) => x.name.length)) + 1;
@@ -130,6 +139,7 @@ export interface ReportData {
pmVersion: string;
localPlugins: string[];
communityPlugins: PackageJson[];
registeredPlugins: string[];
packageVersionsWeCareAbout: {
package: string;
version: string;
@@ -149,8 +159,10 @@ export async function getReportData(): Promise<ReportData> {
const pm = detectPackageManager();
const pmVersion = getPackageManagerVersion(pm);
const localPlugins = await findLocalPlugins(readNxJson());
const nxJson = readNxJson();
const localPlugins = await findLocalPlugins(nxJson);
const communityPlugins = findInstalledCommunityPlugins();
const registeredPlugins = findRegisteredPluginsBeingUsed(nxJson);
let projectGraphError: Error | null = null;
if (isNativeAvailable()) {
@@ -180,6 +192,7 @@ export async function getReportData(): Promise<ReportData> {
pmVersion,
localPlugins,
communityPlugins,
registeredPlugins,
packageVersionsWeCareAbout,
outOfSyncPackageGroup,
projectGraphError,
@@ -268,6 +281,17 @@ export function findInstalledCommunityPlugins(): PackageJson[] {
)
);
}
export function findRegisteredPluginsBeingUsed(nxJson: NxJsonConfiguration) {
if (!nxJson.plugins) {
return [];
}
return nxJson.plugins.map((plugin) =>
typeof plugin === 'object' ? plugin.plugin : plugin
);
}
export function findInstalledPackagesWeCareAbout() {
const packagesWeMayCareAbout: Record<string, string> = {};
// TODO (v19): Remove workaround for hiding @nrwl packages when matching @nx package is found.
@@ -256,7 +256,7 @@ async function processFilesAndCreateAndSerializeProjectGraph(): Promise<Serializ
if (g.error) {
if (g.error instanceof CreateDependenciesError) {
errors.concat(g.error.errors);
errors.push(...g.error.errors);
} else {
return {
error: g.error,
@@ -7,10 +7,6 @@ import runCommands, {
} from './run-commands.impl';
import { env } from 'npm-run-path';
const {
devDependencies: { nx: version },
} = require('package.json');
function normalize(p: string) {
return p.startsWith('/private') ? p.substring(8) : p;
}
@@ -40,6 +36,20 @@ describe('Run Commands', () => {
expect(readFile(f)).toEqual('123');
});
it('should not pass --args into underlying command', async () => {
const f = fileSync().name;
const result = await runCommands(
{
command: `echo`,
__unparsed__: ['--args=--key=123'],
args: '--key=123',
unparsedCommandArgs: { args: '--key=123' },
},
context
);
expect(result.terminalOutput.trim()).not.toContain('--args=--key=123');
});
it('should interpolate all unknown args as if they were --args', async () => {
const f = fileSync().name;
const result = await runCommands(
@@ -180,6 +190,37 @@ describe('Run Commands', () => {
).toEqual('echo one -a=b');
});
it('should not forward all unparsed args when the options is a prop to run command', () => {
expect(
interpolateArgsIntoCommand(
'echo',
{
__unparsed__: ['--args', 'test', 'hello'],
unparsedCommandArgs: { args: 'test' },
} as any,
true
)
).toEqual('echo hello');
expect(
interpolateArgsIntoCommand(
'echo',
{
__unparsed__: ['--args=test', 'hello'],
} as any,
true
)
).toEqual('echo hello');
expect(
interpolateArgsIntoCommand(
'echo',
{ __unparsed__: ['--parallel=true', 'hello'] } as any,
true
)
).toEqual('echo hello');
});
it('should add all args when forwardAllArgs is true', () => {
expect(
interpolateArgsIntoCommand(
@@ -8,6 +8,7 @@ import {
getPseudoTerminal,
PseudoTerminal,
} from '../../tasks-runner/pseudo-terminal';
import { output } from '../../utils/output';
export const LARGE_BUFFER = 1024 * 1000000;
@@ -84,6 +85,9 @@ export interface NormalizedRunCommandsOptions extends RunCommandsOptions {
parsedArgs: {
[k: string]: any;
};
unparsedCommandArgs?: {
[k: string]: string;
};
args?: string;
}
@@ -230,6 +234,7 @@ function normalizeOptions(
options.unknownOptions,
options.args as string
);
options.unparsedCommandArgs = unparsedCommandArgs;
(options as NormalizedRunCommandsOptions).commands.forEach((c) => {
c.command = interpolateArgsIntoCommand(
@@ -301,13 +306,17 @@ async function createProcess(
!isParallel &&
usePty
) {
let terminalOutput = chalk.dim('> ') + commandConfig.command + '\r\n\r\n';
if (streamOutput) {
process.stdout.write(terminalOutput);
}
const cp = pseudoTerminal.runCommand(commandConfig.command, {
cwd,
jsEnv: env,
quiet: !streamOutput,
});
let terminalOutput = '';
return new Promise((res) => {
cp.onOutput((output) => {
terminalOutput += output;
@@ -341,7 +350,10 @@ function nodeProcess(
readyWhen: string,
streamOutput = true
): Promise<{ success: boolean; terminalOutput: string }> {
let terminalOutput = '';
let terminalOutput = chalk.dim('> ') + commandConfig.command + '\r\n\r\n';
if (streamOutput) {
process.stdout.write(terminalOutput);
}
return new Promise((res) => {
const childProcess = exec(commandConfig.command, {
maxBuffer: LARGE_BUFFER,
@@ -450,7 +462,11 @@ export function interpolateArgsIntoCommand(
command: string,
opts: Pick<
NormalizedRunCommandsOptions,
'args' | 'parsedArgs' | '__unparsed__' | 'unknownOptions'
| 'args'
| 'parsedArgs'
| '__unparsed__'
| 'unknownOptions'
| 'unparsedCommandArgs'
>,
forwardAllArgs: boolean
): string {
@@ -477,7 +493,13 @@ export function interpolateArgsIntoCommand(
args += ` ${opts.args}`;
}
if (opts.__unparsed__?.length > 0) {
args += ` ${opts.__unparsed__.join(' ')}`;
const filterdParsedOptions = filterPropKeysFromUnParsedOptions(
opts.__unparsed__,
opts.unparsedCommandArgs
);
if (filterdParsedOptions.length > 0) {
args += ` ${filterdParsedOptions.join(' ')}`;
}
}
return `${command}${args}`;
} else {
@@ -497,3 +519,45 @@ function parseArgs(
configuration: { 'camel-case-expansion': false },
});
}
/**
* This function filters out the prop keys from the unparsed options
* @param __unparsed__ e.g. ['--prop1', 'value1', '--prop2=value2', '--args=test']
* @param unparsedCommandArgs e.g. { prop1: 'value1', prop2: 'value2', args: 'test'}
* @returns filtered options that are not part of the propKeys array e.g. ['--prop1', 'value1', '--prop2=value2']
*/
function filterPropKeysFromUnParsedOptions(
__unparsed__: string[],
unparsedCommandArgs: {
[k: string]: string;
} = {}
): string[] {
const parsedOptions = [];
for (let index = 0; index < __unparsed__.length; index++) {
const element = __unparsed__[index];
if (element.startsWith('--')) {
const key = element.replace('--', '');
if (element.includes('=')) {
if (!propKeys.includes(key.split('=')[0].split('.')[0])) {
// check if the key is part of the propKeys array
parsedOptions.push(element);
}
} else {
// check if the next element is a value for the key
if (propKeys.includes(key)) {
if (
index + 1 < __unparsed__.length &&
__unparsed__[index + 1] === unparsedCommandArgs[key]
) {
index++; // skip the next element
}
} else {
parsedOptions.push(element);
}
}
} else {
parsedOptions.push(element);
}
}
return parsedOptions;
}
@@ -7,7 +7,6 @@ import { WholeFileChange } from '../../file-utils';
import {
getTouchedProjects,
getImplicitlyTouchedProjects,
extractGlobalFilesFromInputs,
} from './workspace-projects';
function getFileChanges(files: string[]) {
@@ -84,6 +83,9 @@ describe('getImplicitlyTouchedProjects', () => {
beforeEach(() => {
nxJson = {
npmScope: 'nrwl',
namedInputs: {
files: ['{workspaceRoot}/a.txt'],
},
projects: {},
};
});
@@ -95,6 +97,11 @@ describe('getImplicitlyTouchedProjects', () => {
namedInputs: {
projectSpecificFiles: ['{workspaceRoot}/a.txt'],
},
targets: {
build: {
inputs: ['projectSpecificFiles'],
},
},
},
b: {
root: 'b',
@@ -125,25 +132,44 @@ describe('getImplicitlyTouchedProjects', () => {
'a',
]);
});
});
describe('extractGlobalFilesFromInputs', () => {
it('should return list of global files from nx.json', () => {
const globalFiles = extractGlobalFilesFromInputs({
namedInputs: {
one: [
'{workspaceRoot}/global1.txt',
{ fileset: '{workspaceRoot}/global2.txt' },
'{projectRoot}/local.txt',
],
},
targetDefaults: {
build: {
inputs: ['{workspaceRoot}/global3.txt'],
it('should return projects which have touched files in their target inputs which are named inputs defined in nx.json', () => {
const graph = buildProjectGraphNodes({
a: {
root: 'a',
targets: {
build: {
inputs: ['files'],
},
},
},
b: {
root: 'b',
},
});
expect(globalFiles).toEqual(['global1.txt', 'global2.txt', 'global3.txt']);
let fileChanges = getFileChanges(['a.txt']);
expect(getImplicitlyTouchedProjects(fileChanges, graph, nxJson)).toEqual([
'a',
]);
});
it('should not return projects which have touched files inputs which are not used by its targets', () => {
const graph = buildProjectGraphNodes({
a: {
root: 'a',
namedInputs: {
files: ['{workspaceRoot}/a.txt'],
},
targets: {},
},
b: {
root: 'b',
},
});
let fileChanges = getFileChanges(['a.txt']);
expect(getImplicitlyTouchedProjects(fileChanges, graph, nxJson)).toEqual(
[]
);
});
});
@@ -1,10 +1,10 @@
import { minimatch } from 'minimatch';
import { TouchedProjectLocator } from '../affected-project-graph-models';
import { NxJsonConfiguration } from '../../../config/nx-json';
import {
createProjectRootMappings,
findProjectForPath,
} from '../../utils/find-project-for-path';
import { InputDefinition } from '../../../config/workspace-json-project-json';
export const getTouchedProjects: TouchedProjectLocator = (
touchedFiles,
@@ -27,22 +27,21 @@ export const getImplicitlyTouchedProjects: TouchedProjectLocator = (
nxJson
): string[] => {
const implicits = {};
const globalFiles = [...extractGlobalFilesFromInputs(nxJson), 'nx.json'];
globalFiles.forEach((file) => {
implicits[file] = '*' as any;
});
Object.values(projectGraphNodes || {}).forEach((node) => {
[
...extractFilesFromNamedInputs(node.data.namedInputs),
...extractFilesFromTargetInputs(node.data.targets),
].forEach((input) => {
implicits[input] ??= [];
const namedInputs = {
...nxJson.namedInputs,
...node.data.namedInputs,
};
extractFilesFromTargetInputs(node.data.targets, namedInputs).forEach(
(input) => {
implicits[input] ??= [];
if (Array.isArray(implicits[input])) {
implicits[input].push(node.name);
if (Array.isArray(implicits[input])) {
implicits[input].push(node.name);
}
}
});
);
});
const touched = new Set<string>();
@@ -66,35 +65,33 @@ export const getImplicitlyTouchedProjects: TouchedProjectLocator = (
return Array.from(touched);
};
export function extractGlobalFilesFromInputs(nxJson: NxJsonConfiguration) {
const globalFiles = [];
globalFiles.push(...extractFilesFromNamedInputs(nxJson.namedInputs));
globalFiles.push(...extractFilesFromTargetInputs(nxJson.targetDefaults));
return globalFiles;
}
function extractFilesFromNamedInputs(namedInputs: any) {
const files = [];
for (const inputs of Object.values(namedInputs || {})) {
files.push(...extractFilesFromInputs(inputs));
}
return files;
}
function extractFilesFromTargetInputs(targets: any) {
function extractFilesFromTargetInputs(
targets: any,
namedInputs: Record<string, Array<string | InputDefinition>>
) {
const globalFiles = [];
for (const target of Object.values(targets || {})) {
if ((target as any).inputs) {
globalFiles.push(...extractFilesFromInputs((target as any).inputs));
globalFiles.push(
...extractFilesFromInputs((target as any).inputs, namedInputs)
);
}
}
return globalFiles;
}
function extractFilesFromInputs(inputs: any) {
function extractFilesFromInputs(
inputs: any,
namedInputs: Record<string, Array<string | InputDefinition>>
) {
const globalFiles = [];
for (const input of inputs) {
if (typeof input === 'string' && input.startsWith('{workspaceRoot}/')) {
if (typeof input === 'string' && input in namedInputs) {
return extractFilesFromInputs(namedInputs[input], namedInputs);
} else if (
typeof input === 'string' &&
input.startsWith('{workspaceRoot}/')
) {
globalFiles.push(input.substring('{workspaceRoot}/'.length));
} else if (input.fileset && input.fileset.startsWith('{workspaceRoot}/')) {
globalFiles.push(input.fileset.substring('{workspaceRoot}/'.length));
@@ -201,6 +201,7 @@ export class ForkedProcessTaskRunner {
private async forkProcessWithPseudoTerminal(
task: Task,
{
temporaryOutputPath,
streamOutput,
taskGraph,
env,
@@ -242,7 +243,7 @@ export class ForkedProcessTaskRunner {
if (code >= 128) {
process.exit(code);
}
this.writeTerminalOutput(temporaryOutputPath, terminalOutput);
res({
code,
terminalOutput,
@@ -1,6 +1,7 @@
import { defaultMaxListeners } from 'events';
import { performance } from 'perf_hooks';
import { relative } from 'path';
import { writeFileSync } from 'fs';
import { TaskHasher } from '../hasher/task-hasher';
import runCommandsImpl from '../executors/run-commands/run-commands.impl';
import { ForkedProcessTaskRunner } from './forked-process-task-runner';
@@ -433,7 +434,7 @@ export class TaskOrchestrator {
terminalOutput
);
}
writeFileSync(temporaryOutputPath, terminalOutput);
results.push({
task,
status,
+2 -1
View File
@@ -4,9 +4,9 @@ import {
InputDefinition,
TargetConfiguration,
} from '../config/workspace-json-project-json';
import { mergeTargetConfigurations } from '../project-graph/utils/project-configuration-utils';
import { readJsonFile } from './fileutils';
import { getNxRequirePaths } from './installation-directory';
import { mergeTargetConfigurations } from '../project-graph/utils/project-configuration-utils';
export interface NxProjectPackageJsonConfiguration {
implicitDependencies?: string[];
@@ -59,6 +59,7 @@ export interface PackageJson {
| {
packages: string[];
};
publishConfig?: Record<string, string>;
// Nx Project Configuration
nx?: NxProjectPackageJsonConfiguration;
@@ -54,7 +54,7 @@ export interface PlaywrightExecutorSchema {
updateSnapshots?: boolean;
ui?: boolean;
uiHost?: string;
uiPort?: string;
uiPort?: number;
skipInstall?: boolean;
}
@@ -155,7 +155,7 @@
"description": "Host to serve UI on; specifying this option opens UI in a browser tab"
},
"uiPort": {
"type": "string",
"type": "number",
"description": "Port to serve UI on, 0 for any free port; specifying this option opens UI in a browser tab"
},
"skipInstall": {

Some files were not shown because too many files have changed in this diff Show More