Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6711c0ba67 | |||
| 867180c636 | |||
| f88cc4034d | |||
| 54bc5ca7f3 | |||
| c94c00d5c4 | |||
| cf785efd18 | |||
| 7a57f59c8e | |||
| f85c2df137 | |||
| 7c4f043a7c | |||
| 53ba54fece | |||
| b05f540aab | |||
| cb0a2f1eb8 | |||
| efd89aa316 | |||
| 91b73440e6 | |||
| d86393002b | |||
| 71b96cf248 | |||
| f300d356c1 | |||
| 58c4e2fe8d | |||
| aabd76fb9a | |||
| 143d8d3dbf | |||
| 7d34a05beb | |||
| 47fdc35aa1 | |||
| 3811844d7c | |||
| b81c398931 | |||
| 932ed449f2 | |||
| 996a69098b | |||
| 2b978178ac | |||
| c3d50411b6 | |||
| 17e41943a6 | |||
| 5b56bbeebe | |||
| 36bc8fa8f0 | |||
| 27b0a29903 | |||
| 699a355d4a | |||
| 138c920f8c | |||
| cb5b5b2c34 | |||
| f32eee962c | |||
| 336ed031e3 | |||
| cd35a0cc8b | |||
| 4dd0d978f4 | |||
| 6e49fae3c4 | |||
| d0e831eff2 | |||
| 5214ea93cf | |||
| ccd213b1c7 | |||
| 7bf2c6a341 | |||
| ed362e8b9e | |||
| 49b04b0e09 | |||
| 937516c867 | |||
| 9c84e1d613 | |||
| 27a3cb0559 | |||
| b888ab8f57 | |||
| 14121a077a | |||
| ebb130dc7d | |||
| d7ba8db9de | |||
| 2460b338d8 | |||
| 081c2d7a3b | |||
| b5ea35c5c0 | |||
| ba53770685 | |||
| 6e0a1e204e | |||
| d4a0554671 | |||
| 6c34f407e4 | |||
| 36e4be172b | |||
| ef5a979e72 | |||
| e8bcf9ba5f | |||
| 08476dbbb3 | |||
| e34e73f37a | |||
| 3c4c052446 | |||
| 923cba8422 | |||
| 83743801d5 | |||
| 2555490762 | |||
| 3e6f2227ae | |||
| eb7c7bffeb | |||
| bf3fc41196 | |||
| 87b1a73b4c | |||
| ee0c88f766 | |||
| fa037aa528 | |||
| d7d7f163a4 | |||
| dd7eff1072 | |||
| 739c427351 | |||
| f342baca5a | |||
| fd71c6e288 | |||
| d30413311b | |||
| 0761e4c7a9 | |||
| d15733d00e | |||
| eba45a9f5b | |||
| 68d9df74f6 | |||
| 1b4ac4b63e | |||
| 2959f7f033 | |||
| f997744b4d | |||
| 04a38649b3 | |||
| c351b34f62 | |||
| f111580f28 | |||
| bf9844ec72 | |||
| 6c86cb770c | |||
| c06edd9426 | |||
| 1c7406379b | |||
| f1de248ff7 | |||
| 5ba8d5aa12 | |||
| 18ff2dbcd4 | |||
| e9a59d60af | |||
| 908cf41d74 | |||
| 2f8ca66797 |
@@ -1147,9 +1147,17 @@
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Encapsulated Nx and the Nx Wrapper",
|
||||
"path": "/more-concepts/encapsulated-nx-and-the-wrapper",
|
||||
"id": "encapsulated-nx-and-the-wrapper",
|
||||
"name": "Nx and the Nx Wrapper",
|
||||
"path": "/more-concepts/nx-and-the-wrapper",
|
||||
"id": "nx-and-the-wrapper",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Managing your Global Nx Installation",
|
||||
"path": "/more-concepts/global-nx",
|
||||
"id": "global-nx",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
@@ -1310,9 +1318,17 @@
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Encapsulated Nx and the Nx Wrapper",
|
||||
"path": "/more-concepts/encapsulated-nx-and-the-wrapper",
|
||||
"id": "encapsulated-nx-and-the-wrapper",
|
||||
"name": "Nx and the Nx Wrapper",
|
||||
"path": "/more-concepts/nx-and-the-wrapper",
|
||||
"id": "nx-and-the-wrapper",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Managing your Global Nx Installation",
|
||||
"path": "/more-concepts/global-nx",
|
||||
"id": "global-nx",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
|
||||
@@ -1426,13 +1426,23 @@
|
||||
"tags": ["explore-graph"]
|
||||
},
|
||||
{
|
||||
"id": "encapsulated-nx-and-the-wrapper",
|
||||
"name": "Encapsulated Nx and the Nx Wrapper",
|
||||
"id": "nx-and-the-wrapper",
|
||||
"name": "Nx and the Nx Wrapper",
|
||||
"description": "",
|
||||
"file": "shared/guides/encapsulated-nx-and-the-wrapper",
|
||||
"file": "shared/guides/nx-and-the-wrapper",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/more-concepts/encapsulated-nx-and-the-wrapper",
|
||||
"path": "/more-concepts/nx-and-the-wrapper",
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"id": "global-nx",
|
||||
"name": "Managing your Global Nx Installation",
|
||||
"description": "",
|
||||
"file": "shared/guides/global-nx",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/more-concepts/global-nx",
|
||||
"tags": []
|
||||
}
|
||||
],
|
||||
@@ -1630,14 +1640,24 @@
|
||||
"path": "/more-concepts/how-project-graph-is-built",
|
||||
"tags": ["explore-graph"]
|
||||
},
|
||||
"/more-concepts/encapsulated-nx-and-the-wrapper": {
|
||||
"id": "encapsulated-nx-and-the-wrapper",
|
||||
"name": "Encapsulated Nx and the Nx Wrapper",
|
||||
"/more-concepts/nx-and-the-wrapper": {
|
||||
"id": "nx-and-the-wrapper",
|
||||
"name": "Nx and the Nx Wrapper",
|
||||
"description": "",
|
||||
"file": "shared/guides/encapsulated-nx-and-the-wrapper",
|
||||
"file": "shared/guides/nx-and-the-wrapper",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/more-concepts/encapsulated-nx-and-the-wrapper",
|
||||
"path": "/more-concepts/nx-and-the-wrapper",
|
||||
"tags": []
|
||||
},
|
||||
"/more-concepts/global-nx": {
|
||||
"id": "global-nx",
|
||||
"name": "Managing your Global Nx Installation",
|
||||
"description": "",
|
||||
"file": "shared/guides/global-nx",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/more-concepts/global-nx",
|
||||
"tags": []
|
||||
},
|
||||
"/recipes": {
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The name of the project.",
|
||||
"x-dropdown": "projects",
|
||||
"x-priority": "important"
|
||||
"x-dropdown": "projects"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
@@ -113,7 +112,7 @@
|
||||
"x-priority": "important"
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"required": ["name", "project"],
|
||||
"examplesFile": "## Examples\n\n{% tabs %}\n{% tab label=\"Simple Component\" %}\n\nCreate a component named `my-component`:\n\n```bash\nnx g @nrwl/angular:component my-component\n```\n\n{% /tab %}\n\n{% tab label=\"Standalone Component\" %}\n\nCreate a standalone component named `my-component`:\n\n```bash\nnx g @nrwl/angular:component my-component --standalone\n```\n\n{% /tab %}\n\n{% tab label=\"Single File Component\" %}\n\nCreate a component named `my-component` with inline styles and inline template:\n\n```bash\nnx g @nrwl/angular:component my-component --inlineStyle --inlineTemplate\n```\n\n{% /tab %}\n\n{% tab label=\"Component with OnPush Change Detection Strategy\" %}\n\nCreate a component named `my-component` with OnPush Change Detection Strategy:\n\n```bash\nnx g @nrwl/angular:component my-component --changeDetection=OnPush\n```\n\n{% /tab %}\n",
|
||||
"presets": []
|
||||
},
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name to give to the host Angular application.",
|
||||
"$default": { "$source": "argv", "index": 0 }
|
||||
"$default": { "$source": "argv", "index": 0 },
|
||||
"pattern": "^[a-zA-Z].*$"
|
||||
},
|
||||
"remotes": {
|
||||
"type": "array",
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name to give to the remote Angular app.",
|
||||
"$default": { "$source": "argv", "index": 0 }
|
||||
"$default": { "$source": "argv", "index": 0 },
|
||||
"pattern": "^[a-zA-Z].*$"
|
||||
},
|
||||
"host": {
|
||||
"type": "string",
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The name of the project.",
|
||||
"x-dropdown": "projects",
|
||||
"x-priority": "important"
|
||||
"x-dropdown": "projects"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
@@ -72,7 +71,7 @@
|
||||
"x-priority": "important"
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"required": ["name", "project"],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Generate a directive with an accompanying Single Component Angular Module (SCAM).",
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The name of the project.",
|
||||
"x-dropdown": "projects",
|
||||
"x-priority": "important"
|
||||
"x-dropdown": "projects"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
@@ -58,7 +57,7 @@
|
||||
"x-priority": "important"
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"required": ["name", "project"],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Generate a pipe with an accompanying Single Component Angular Module (SCAM).",
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The name of the project.",
|
||||
"x-dropdown": "projects",
|
||||
"x-priority": "important"
|
||||
"x-dropdown": "projects"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
@@ -118,7 +117,7 @@
|
||||
"x-priority": "important"
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"required": ["name", "project"],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Generate a component with an accompanying Single Component Angular Module (SCAM).",
|
||||
|
||||
@@ -81,12 +81,13 @@
|
||||
"description": "Configure your workspace to use Storybook 7, even though Storybook v7 is still in beta.",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"hidden": true
|
||||
"hidden": true,
|
||||
"aliases": ["storybook7betaConfiguration"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["name"],
|
||||
"examplesFile": "This generator will set up Storybook for your Angular project.\n\n```bash\nnx g @nrwl/angular:storybook-configuration project-name\n```\n\nYou can read more about how this generator works, in the [Storybook for Angular overview page](/packages/storybook/documents/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 `configureCypress`. If you choose `yes`, a Cypress e2e app will be created (or configured) to run against the project's Storybook instance. You can read more about this in the [Storybook for Angular - Cypress section](/packages/storybook/documents/overview-angular#cypress-tests-for-stories).\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- Whether you want to `generateCypressSpecs`. If you choose `yes`, a test file is going to be generated in the project's Cypress e2e app for each of your components.\n- Whether you want to `configureTestRunner`. If you choose `yes`, 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).\n\nYou must provide a `name` for the generator to work.\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 using TypeScript\n\n```bash\nnx g @nrwl/angular:storybook-configuration ui --tsConfiguration=true\n```\n\nThis will generate a Storybook configuration for the `ui` project using TypeScript for the Storybook configuration files (the files inside the `.storybook` directory).\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nrwl/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\n### Generate Storybook configuration for Storybook version 7\n\n```bash\nnx g @nrwl/angular:storybook-configuration ui --storybook7betaConfiguration=true\n```\n\nThis will generate a Storybook configuration for the `ui` project using Storybook version 7. It will install the Storybook version 7 dependencies and configure the Storybook configuration files (the files inside the `.storybook` directory) to use Storybook version 7. You can read more about Storybook 7 Nx support in the [Storybook 7 setup guide](/packages/storybook/documents/storybook-7-setup).\n",
|
||||
"examplesFile": "This generator will set up Storybook for your Angular project.\n\n```bash\nnx g @nrwl/angular:storybook-configuration project-name\n```\n\nYou can read more about how this generator works, in the [Storybook for Angular overview page](/packages/storybook/documents/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 `configureCypress`. If you choose `yes`, a Cypress e2e app will be created (or configured) to run against the project's Storybook instance. You can read more about this in the [Storybook for Angular - Cypress section](/packages/storybook/documents/overview-angular#cypress-tests-for-stories).\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- Whether you want to `generateCypressSpecs`. If you choose `yes`, a test file is going to be generated in the project's Cypress e2e app for each of your components.\n- Whether you want to `configureTestRunner`. If you choose `yes`, 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).\n\nYou must provide a `name` for the generator to work.\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 using TypeScript\n\n```bash\nnx g @nrwl/angular:storybook-configuration ui --tsConfiguration=true\n```\n\nThis will generate a Storybook configuration for the `ui` project using TypeScript for the Storybook configuration files (the files inside the `.storybook` directory).\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nrwl/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\n### Generate Storybook configuration for Storybook version 7\n\n```bash\nnx g @nrwl/angular:storybook-configuration ui --storybook7betaConfiguration=true\n```\n\n{% callout type=\"info\" title=\"For Nx versions <15.9\" %}\nThe flag is called `storybook7betaConfiguration` for Nx versions <15.9.\n{% /callout %}\n\nThis will generate a Storybook configuration for the `ui` project using Storybook version 7. It will install the Storybook version 7 dependencies and configure the Storybook configuration files (the files inside the `.storybook` directory) to use Storybook version 7. You can read more about Storybook 7 Nx support in the [Storybook 7 setup guide](/packages/storybook/documents/storybook-7-setup).\n",
|
||||
"presets": []
|
||||
},
|
||||
"description": "Adds Storybook configuration to a project.",
|
||||
|
||||
@@ -114,7 +114,13 @@
|
||||
"default": false
|
||||
},
|
||||
"sourcemap": {
|
||||
"type": "boolean",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["linked", "inline", "external", "both"]
|
||||
},
|
||||
{ "type": "boolean" }
|
||||
],
|
||||
"alias": "sourceMap",
|
||||
"description": "Generate sourcemap.",
|
||||
"default": false
|
||||
|
||||
@@ -21,6 +21,13 @@
|
||||
"description": "Do not add dependencies to `package.json`.",
|
||||
"x-priority": "internal"
|
||||
},
|
||||
"testEnvironment": {
|
||||
"type": "string",
|
||||
"enum": ["jsdom", "node", "none"],
|
||||
"description": "The test environment for jest. This controls which jest-environment-* package is installed",
|
||||
"default": "jsdom",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"js": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
},
|
||||
"testEnvironment": {
|
||||
"type": "string",
|
||||
"enum": ["jsdom", "node"],
|
||||
"enum": ["jsdom", "node", "none"],
|
||||
"description": "The test environment for jest.",
|
||||
"default": "jsdom",
|
||||
"x-priority": "important"
|
||||
|
||||
@@ -54,13 +54,13 @@ nx g @nrwl/js:lib my-lib
|
||||
|
||||
## Build
|
||||
|
||||
You can `build` libraries that are generated with `--buildable` flag.
|
||||
You can `build` libraries that are generated with a bundler specified.
|
||||
|
||||
```shell
|
||||
nx g @nrwl/js:lib my-buildable-lib --buildable
|
||||
nx g @nrwl/js:lib my-buildable-lib --bundler=rollup
|
||||
```
|
||||
|
||||
Generating a library with `--buildable` will add a `build` target to the library's `project.json` file allows the library to be built.
|
||||
Generating a library with `--bundler` specified will add a `build` target to the library's `project.json` file allows the library to be built.
|
||||
|
||||
```shell
|
||||
nx build my-buildable-lib
|
||||
@@ -95,7 +95,7 @@ Currently, `@nrwl/js` supports the following compilers:
|
||||
- Create a buildable library with `swc`
|
||||
|
||||
```shell
|
||||
nx g @nrwl/js:lib my-swc-lib --compiler=swc --buildable
|
||||
nx g @nrwl/js:lib my-swc-lib --bundler=swc
|
||||
```
|
||||
|
||||
- Convert a `tsc` library to use `swc`
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Generate a buildable library.",
|
||||
"x-priority": "important"
|
||||
"x-deprecated": "Use the `bundler` option for greater control (swc, tsc, rollup, vite, esbuild, none)."
|
||||
},
|
||||
"setParserOptionsProject": {
|
||||
"type": "boolean",
|
||||
@@ -108,23 +108,29 @@
|
||||
"enum": ["tsc", "swc"],
|
||||
"default": "tsc",
|
||||
"description": "The compiler used by the build and test targets",
|
||||
"x-priority": "important"
|
||||
"x-deprecated": "Use the `bundler` option for greater control (swc, tsc, rollup, vite, esbuild, none)."
|
||||
},
|
||||
"bundler": {
|
||||
"description": "The bundler to use.",
|
||||
"description": "The bundler to use. Choosing 'none' means this library is not buildable.",
|
||||
"type": "string",
|
||||
"enum": ["none", "esbuild", "rollup", "vite"],
|
||||
"default": "none",
|
||||
"enum": ["swc", "tsc", "rollup", "vite", "esbuild", "none"],
|
||||
"default": "tsc",
|
||||
"x-prompt": "Which bundler would you like to use to build the library? Choose 'none' to skip build setup.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipTypeCheck": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to skip TypeScript type checking for SWC compiler.",
|
||||
"default": false
|
||||
},
|
||||
"minimal": {
|
||||
"type": "boolean",
|
||||
"description": "Generate a library with a minimal setup. No README.md generated.",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"examplesFile": "## Examples\n\n{% tabs %}\n\n{% tab label=\"Using directory flag\" %}\n\nGenerate a library named `mylib` and put it under a directory named `myapp` (`libs/myapp/mylib`)\n\n```shell\nnpx nx g lib mylib --directory=myapp\n```\n\n{% /tab %}\n\n{% tab label=\"Use SWC compiler\" %}\n\nGenerate a library using [SWC](https://swc.rs) as the compiler\n\n```shell\nnpx nx g lib mylib --compiler=swc\n```\n\n{% /tab %}\n\n{% tab label=\"Minimal publishing target\" %}\n\nGenerate a **publishable** library with a minimal publishing target\n\n```shell\nnpx nx g lib mylib --publishable\n```\n\n{% /tab %}\n\n{% /tabs %}\n",
|
||||
"examplesFile": "---\ntitle: JS library generator examples\ndescription: This page contains examples for the @nrwl/js:lib generator.\n---\n\nThe `@nrwl/js:lib` generator will generate a library for you, and it will configure it according to the options you provide.\n\n```bash\nnpx nx g @nrwl/js:lib mylib\n```\n\nBy default, the library that is generated when you use this executor without passing any options, like the example above, will be a buildable library, using the `@nrwl/js:tsc` executor as a builder.\n\nYou may configure the tools you want to use to build your library, or bundle it too, by passing the `--bundler` flag. The `--bundler` flag controls the compiler and/or the bundler that will be used to build your library. If you choose `tsc` or `swc`, the result will be a buildable library using either `tsc` or `swc` as the compiler. If you choose `rollup` or `vite`, the result will be a buildable library using `rollup` or `vite` as the bundler. In the case of `rollup`, it will default to the `tsc` compiler. If you choose `esbuild`, you may use the [`esbuildOptions` property](https://esbuild.github.io/api/) in your `project.json` under the `build` target options to specify whether you wish to bundle your library or not.\n\n## Examples\n\n{% tabs %}\n\n{% tab label=\"Buildable with default compiler (tsc)\" %}\n\nGenerate a buildable library using the `@nrwl/js:tsc` executor. This uses `tsc` as the compiler.\n\n```bash\nnpx nx g @nrwl/js:lib mylib\n```\n\n{% /tab %}\n\n{% tab label=\"Buildable with SWC compiler\" %}\n\nGenerate a buildable library using [SWC](https://swc.rs) as the compiler. This will use the `@nrwl/js:swc` executor.\n\n```bash\nnpx nx g @nrwl/js:lib mylib --bundler=swc\n```\n\n{% /tab %}\n\n{% tab label=\"Buildable with tsc\" %}\n\nGenerate a buildable library using tsc as the compiler. This will use the `@nrwl/js:tsc` executor.\n\n```bash\nnpx nx g @nrwl/js:lib mylib --bundler=tsc\n```\n\n{% /tab %}\n\n{% tab label=\"Buildable, with Rollup as a bundler\" %}\n\nGenerate a buildable library using [Rollup](https://rollupjs.org) as the bundler. This will use the `@nrwl/rollup:rollup` executor. It will also use [SWC](https://swc.rs) as the compiler.\n\n```bash\nnpx nx g @nrwl/js:lib mylib --bundler=rollup\n```\n\nIf you do not want to use `swc` as the compiler, and want to use the default `babel` compiler, you can do so in your `project.json` under the `build` target options, using the [`compiler` property](https://nx.dev/packages/rollup/executors/rollup#compiler):\n\n```jsonc {% fileName=\"libs/mylib/project.json\" %}\n\"build\": {\n \"executor\": \"@nrwl/rollup:rollup\",\n \"options\": {\n //...\n \"compiler\": \"babel\"\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"Buildable, with Vite as a bundler\" %}\n\nGenerate a buildable library using [Vite](https://vitejs.dev/) as the bundler. This will use the `@nrwl/vite:build` executor.\n\n```bash\nnpx nx g @nrwl/js:lib mylib --bundler=vite\n```\n\n{% /tab %}\n\n{% tab label=\"Using ESBuild\" %}\n\nGenerate a buildable library using [ESBuild](https://esbuild.github.io/) as the bundler. This will use the `@nrwl/esbuild:esbuild` executor.\n\n```bash\nnpx nx g @nrwl/js:lib mylib --bundler=esbuild\n```\n\nIf you want to specify whether you want to bundle your library or not, you can do so in your `project.json` under the `build` target options, using the [`esbuildOptions` property](https://esbuild.github.io/api/):\n\n```jsonc {% fileName=\"libs/mylib/project.json\" %}\n\"build\": {\n \"executor\": \"@nrwl/esbuild:esbuild\",\n \"options\": {\n //...\n \"esbuildOptions\": {\n \"bundle\": true\n }\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"Minimal publishing target\" %}\n\nGenerate a **publishable** library with a minimal publishing target. The result will be a buildable library using the `@nrwl/js:tsc` executor, using `tsc` as the compiler. You can change the compiler or the bundler by passing the `--bundler` flag.\n\n```bash\nnpx nx g lib mylib --publishable\n```\n\n{% /tab %}\n\n{% tab label=\"Using directory flag\" %}\n\nGenerate a library named `mylib` and put it under a directory named `myapp` (`libs/myapp/mylib`)\n\n```shell\nnpx nx g lib mylib --directory=myapp\n```\n\n{% /tab %}\n\n{% tab label=\"Non-buildable library\" %}\n\nGenerate a non-buildable library.\n\n```bash\nnpx nx g @nrwl/js:lib mylib --bundler=none\n```\n\n{% /tab %}\n\n{% /tabs %}\n",
|
||||
"presets": []
|
||||
},
|
||||
"aliases": ["lib"],
|
||||
|
||||
@@ -90,11 +90,12 @@
|
||||
"description": "Configure your workspace to use Storybook 7, even though Storybook v7 is still in beta.",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"hidden": true
|
||||
"hidden": true,
|
||||
"aliases": ["storybook7betaConfiguration"]
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"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.\n\n```bash\nnx g @nrwl/react:storybook-configuration project-name\n```\n\nYou can read more about how this generator works, in the [Storybook for React overview page](/packages/storybook/documents/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 `configureCypress`. If you choose `yes`, a Cypress e2e app will be created (or configured) to run against the project's Storybook instance. You can read more about this in the [Storybook for React - Cypress section](/packages/storybook/documents/overview-react#cypress-tests-for-stories).\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- Whether you want to `generateCypressSpecs`. If you choose `yes`, a test file is going to be generated in the project's Cypress e2e app for each of your components.\n- Whether you want to `configureTestRunner`. If you choose `yes`, 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).\n\nYou must provide a `name` for the generator to work.\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 using TypeScript\n\n```bash\nnx g @nrwl/react:storybook-configuration ui --tsConfiguration=true\n```\n\nThis will generate a Storybook configuration for the `ui` project using TypeScript for the Storybook configuration files (the files inside the `.storybook` directory).\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nrwl/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\n### Generate stories using JavaScript instead of TypeScript\n\n```bash\nnx g @nrwl/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 for Storybook version 7\n\n```bash\nnx g @nrwl/react:storybook-configuration ui --storybook7betaConfiguration=true\n```\n\nThis will generate a Storybook configuration for the `ui` project using Storybook version 7. It will install the Storybook version 7 dependencies and configure the Storybook configuration files (the files inside the `.storybook` directory) to use Storybook version 7. You can read more about Storybook 7 Nx support in the [Storybook 7 setup guide](/packages/storybook/documents/storybook-7-setup).\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.\n\n```bash\nnx g @nrwl/react:storybook-configuration project-name\n```\n\nYou can read more about how this generator works, in the [Storybook for React overview page](/packages/storybook/documents/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 `configureCypress`. If you choose `yes`, a Cypress e2e app will be created (or configured) to run against the project's Storybook instance. You can read more about this in the [Storybook for React - Cypress section](/packages/storybook/documents/overview-react#cypress-tests-for-stories).\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- Whether you want to `generateCypressSpecs`. If you choose `yes`, a test file is going to be generated in the project's Cypress e2e app for each of your components.\n- Whether you want to `configureTestRunner`. If you choose `yes`, 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).\n\nYou must provide a `name` for the generator to work.\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 using TypeScript\n\n```bash\nnx g @nrwl/react:storybook-configuration ui --tsConfiguration=true\n```\n\nThis will generate a Storybook configuration for the `ui` project using TypeScript for the Storybook configuration files (the files inside the `.storybook` directory).\n\n### Ignore certain paths when generating stories\n\n```bash\nnx g @nrwl/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\n### Generate stories using JavaScript instead of TypeScript\n\n```bash\nnx g @nrwl/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 for Storybook version 7\n\n```bash\nnx g @nrwl/react:storybook-configuration ui --storybook7betaConfiguration=true\n```\n\n{% callout type=\"info\" title=\"For Nx versions <15.9\" %}\nThe flag is called `storybook7betaConfiguration` for Nx versions <15.9.\n{% /callout %}\n\nThis will generate a Storybook configuration for the `ui` project using Storybook version 7. It will install the Storybook version 7 dependencies and configure the Storybook configuration files (the files inside the `.storybook` directory) to use Storybook version 7. You can read more about Storybook 7 Nx support in the [Storybook 7 setup guide](/packages/storybook/documents/storybook-7-setup).\n",
|
||||
"presets": []
|
||||
},
|
||||
"description": "Set up storybook for a React app or library.",
|
||||
|
||||
@@ -56,6 +56,10 @@ You can generate Storybook configuration for an individual project with this com
|
||||
nx g @nrwl/storybook:configuration project-name --storybook7betaConfiguration --storybook7UiFramework=@storybook/react-webpack5
|
||||
```
|
||||
|
||||
{% callout type="info" title="For Nx versions <15.9" %}
|
||||
The flag is called `storybook7betaConfiguration` for Nx versions <15.9.
|
||||
{% /callout %}
|
||||
|
||||
In the field `storybook7UiFramework` you must choose one of the following Storybook frameworks:
|
||||
|
||||
- `@storybook/angular`
|
||||
|
||||
@@ -83,7 +83,8 @@
|
||||
"description": "Configure your workspace to use Storybook 7, even though Storybook v7 is still in beta.",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"hidden": true
|
||||
"hidden": true,
|
||||
"aliases": ["storybook7betaConfiguration"]
|
||||
},
|
||||
"storybook7UiFramework": {
|
||||
"type": "string",
|
||||
@@ -110,7 +111,7 @@
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"examplesFile": "---\ntitle: Storybook configuration generator examples\ndescription: This page contains examples for the @nrwl/storybook:configuration generator.\n---\n\nThis is a framework-agnostic generator for setting up Storybook configuration for a project.\n\n```bash\nnx g @nrwl/storybook:configuration\n```\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- The `uiFramework` you want to use. Supported values are: `\"@storybook/angular\"`, `\"@storybook/react\"`, `\"@storybook/react-native\"`, `\"@storybook/html\"`, `\"@storybook/web-components\"`, `\"@storybook/vue\"`, `\"@storybook/vue3\"` and `\"@storybook/svelte\"`.\n- Whether you want to `configureCypress`. If you choose `yes`, a Cypress e2e app will be created (or configured) to run against the project's Storybook instance.\n- Whether you want to `configureTestRunner`. If you choose `yes`, 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).\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](https://nx.dev/packages/storybook#generating-storybook-configuration).\n\nIf you are using Angular, React, React Native or Next.js in your project, it's best to use the framework specific generator:\n\n- [React Storybook Configuration Generator](/packages/react/generators/storybook-configuration) (React and Next.js projects)\n\n- [Angular Storybook Configuration Generator](/packages/angular/generators/storybook-configuration)\n\n- [React Native Storybook Configuration Generator](/packages/react-native/generators/storybook-configuration)\n\n## Examples\n\n### Generate Storybook configuration using TypeScript\n\n```bash\nnx g @nrwl/storybook:configuration ui --uiFramework=@storybook/web-components --tsConfiguration=true\n```\n\nThis will generate a Storybook configuration for the `ui` project using TypeScript for the Storybook configuration files (the files inside the `.storybook` directory).\n\n### Generate Storybook configuration for Storybook version 7\n\n```bash\nnx g @nrwl/storybook:configuration ui --uiFramework=@storybook/react --storybook7betaConfiguration=true\n```\n\nOR\n\n```bash\nnx g @nrwl/storybook:configuration ui --storybook7UiFramework=@storybook/react-vite --storybook7betaConfiguration=true\n```\n\nThis will generate a Storybook configuration for the `ui` project using Storybook version 7. It will install the Storybook version 7 dependencies and configure the Storybook configuration files (the files inside the `.storybook` directory) to use Storybook version 7. You can read more about Storybook 7 Nx support in the [Storybook 7 setup guide](/packages/storybook/documents/storybook-7-setup).\n",
|
||||
"examplesFile": "---\ntitle: Storybook configuration generator examples\ndescription: This page contains examples for the @nrwl/storybook:configuration generator.\n---\n\nThis is a framework-agnostic generator for setting up Storybook configuration for a project.\n\n```bash\nnx g @nrwl/storybook:configuration\n```\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- The `uiFramework` you want to use. Supported values are: `\"@storybook/angular\"`, `\"@storybook/react\"`, `\"@storybook/react-native\"`, `\"@storybook/html\"`, `\"@storybook/web-components\"`, `\"@storybook/vue\"`, `\"@storybook/vue3\"` and `\"@storybook/svelte\"`.\n- Whether you want to `configureCypress`. If you choose `yes`, a Cypress e2e app will be created (or configured) to run against the project's Storybook instance.\n- Whether you want to `configureTestRunner`. If you choose `yes`, 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).\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](https://nx.dev/packages/storybook#generating-storybook-configuration).\n\nIf you are using Angular, React, React Native or Next.js in your project, it's best to use the framework specific generator:\n\n- [React Storybook Configuration Generator](/packages/react/generators/storybook-configuration) (React and Next.js projects)\n\n- [Angular Storybook Configuration Generator](/packages/angular/generators/storybook-configuration)\n\n- [React Native Storybook Configuration Generator](/packages/react-native/generators/storybook-configuration)\n\n## Examples\n\n### Generate Storybook configuration using TypeScript\n\n```bash\nnx g @nrwl/storybook:configuration ui --uiFramework=@storybook/web-components --tsConfiguration=true\n```\n\nThis will generate a Storybook configuration for the `ui` project using TypeScript for the Storybook configuration files (the files inside the `.storybook` directory).\n\n### Generate Storybook configuration for Storybook version 7\n\n```bash\nnx g @nrwl/storybook:configuration ui --uiFramework=@storybook/react --storybook7betaConfiguration=true\n```\n\nOR\n\n```bash\nnx g @nrwl/storybook:configuration ui --storybook7UiFramework=@storybook/react-vite --storybook7betaConfiguration=true\n```\n\n{% callout type=\"info\" title=\"For Nx versions <15.9\" %}\nThe flag is called `storybook7betaConfiguration` for Nx versions <15.9.\n{% /callout %}\n\nThis will generate a Storybook configuration for the `ui` project using Storybook version 7. It will install the Storybook version 7 dependencies and configure the Storybook configuration files (the files inside the `.storybook` directory) to use Storybook version 7. You can read more about Storybook 7 Nx support in the [Storybook 7 setup guide](/packages/storybook/documents/storybook-7-setup).\n",
|
||||
"presets": []
|
||||
},
|
||||
"description": "Add Storybook configuration to a UI library or an application.",
|
||||
|
||||
@@ -51,7 +51,8 @@
|
||||
"description": "Configure your workspace to use Storybook 7, even though Storybook v7 is still in beta.",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"hidden": true
|
||||
"hidden": true,
|
||||
"aliases": ["storybook7betaConfiguration"]
|
||||
},
|
||||
"js": {
|
||||
"type": "boolean",
|
||||
|
||||
@@ -49,6 +49,11 @@
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"emptyOutDir": {
|
||||
"description": "When set to false, outputPath will not be emptied during the build process.",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"sourcemap": {
|
||||
"description": "Output sourcemaps. Use 'hidden' for use with error reporting tools without generating sourcemap comment.",
|
||||
"oneOf": [{ "type": "boolean" }, { "type": "string" }]
|
||||
|
||||
File diff suppressed because one or more lines are too long
+8
-3
@@ -486,9 +486,14 @@
|
||||
"file": "shared/concepts/how-project-graph-is-built"
|
||||
},
|
||||
{
|
||||
"name": "Encapsulated Nx and the Nx Wrapper",
|
||||
"id": "encapsulated-nx-and-the-wrapper",
|
||||
"file": "shared/guides/encapsulated-nx-and-the-wrapper"
|
||||
"name": "Nx and the Nx Wrapper",
|
||||
"id": "nx-and-the-wrapper",
|
||||
"file": "shared/guides/nx-and-the-wrapper"
|
||||
},
|
||||
{
|
||||
"name": "Managing your Global Nx Installation",
|
||||
"id": "global-nx",
|
||||
"file": "shared/guides/global-nx"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
- [Grouping Libraries](/more-concepts/grouping-libraries)
|
||||
- [Buildable and Publishable Libraries](/more-concepts/buildable-and-publishable-libraries)
|
||||
- [How the Project Graph is Built](/more-concepts/how-project-graph-is-built)
|
||||
- [Encapsulated Nx and the Nx Wrapper](/more-concepts/encapsulated-nx-and-the-wrapper)
|
||||
- [Nx and the Nx Wrapper](/more-concepts/nx-and-the-wrapper)
|
||||
|
||||
@@ -42,7 +42,7 @@ Sometimes broad configurations like `> 0.5%, not IE 11` can lead to surprising r
|
||||
|
||||
To see what browsers your configuration is supporting, run `npx browserslist` in the application's directory to get an output of browsers and versions to support.
|
||||
|
||||
```{% command="npx browserlist" %}
|
||||
```{% command="npx browserslist" %}
|
||||
and_chr 61
|
||||
chrome 83
|
||||
edge 83
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
# Encapsulated Nx
|
||||
|
||||
{% callout type="note" title="Available since Nx v15.8.0" %}
|
||||
|
||||
Support for `--encapsulated` was added in Nx v15.8.0. To ensure that it is available, specify the version of nx when running your command so that `npx` doesn't accept an older version that is in the cache. (e.g. `npx nx@latest init --encapsulated` or `npx nx@15.8.0 init --encapsulated`)
|
||||
|
||||
{% /callout %}
|
||||
|
||||
`nx init` accepts a flag called `--encapsulated`. When this flag is passed, Nx manages its own installation within a `.nx` directory. This allows Nx to be used without a `node_modules` folder or root `package.json`.
|
||||
|
||||
To manage the local Nx installation, and invoke Nx, a total of 4 files are created.
|
||||
|
||||
- [.nx/nxw.js](#nx-wrapper)
|
||||
- [nx](#nx-and-nxbat)
|
||||
- [nx.bat](#nx-and-nxbat)
|
||||
- [nx.json](#nx-json)
|
||||
|
||||
## Nx Wrapper
|
||||
|
||||
After running `nx init --encapsulated`, a file is created called `.nx/nxw.js`. This javascript file is responsible for ensuring that your local installation of Nx is up to date with what the repository expects, and then invoking Nx. We refer to this as the "Nx Wrapper".
|
||||
|
||||
## Nx and nx bat
|
||||
|
||||
`nx` and `nx.bat` perform the same function, with `nx` working under the bash shell and `nx.bat` working for windows users. Each script simply checks that `node` and `npm` are available, and then invokes the [nx wrapper](#nx-wrapper). They should each be committed to your repository, so that developers on any operating system are able to use Nx.
|
||||
|
||||
To invoke an Nx command, you would use:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="MacOS / Linux" %}
|
||||
|
||||
```shell
|
||||
> ./nx build my-project
|
||||
> ./nx generate application
|
||||
> ./nx graph
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="Windows" %}
|
||||
|
||||
```shell
|
||||
> ./nx.bat build my-project
|
||||
> ./nx.bat generate application
|
||||
> ./nx.bat graph
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## Nx Json
|
||||
|
||||
This is the configuration file for Nx. A full description of `nx.json`, and the possible configuration options, is located in [the nx.json reference documentation](/reference/nx-json).
|
||||
|
||||
When Nx is encapsulated, an `installation` key is added to `nx.json`. This property tracks the currently installed version of Nx and plugins.
|
||||
@@ -0,0 +1,102 @@
|
||||
# Managing your Global Nx Installation
|
||||
|
||||
Nx can be ran in a total of 3 ways:
|
||||
|
||||
- Through your package manager (e.g. `npx nx`, `yarn nx`, or `pnpm exec nx`)
|
||||
- Through [./nx or ./nx.bat](/more-concepts/nx-and-the-wrapper)
|
||||
- Through a global Nx installation (e.g. `nx`)
|
||||
|
||||
With a global Nx installation, Nx looks for the local copy of Nx in your repo and hands off the process execution to it. This means that whichever version of Nx is installed locally in your repo is still the version of Nx that runs your code. For the most part, this can eliminate any issues that may arise from the global install being outdated.
|
||||
|
||||
## Installing Nx Globally
|
||||
|
||||
Depending on your package manager of choice, you can install Nx with the following commands:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="npm" %}
|
||||
|
||||
```shell
|
||||
npm install --global nx@latest
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="yarn" %}
|
||||
|
||||
```shell
|
||||
yarn global add nx@latest
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="pnpm" %}
|
||||
|
||||
```shell
|
||||
pnpm install --global nx@latest
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## Updating your global Nx installation
|
||||
|
||||
There are some cases where an issue could arise when using an outdated global installation of Nx. If the structure of your Nx workspace no longer matches up with what the globally installed copy of Nx expects, it may fail to hand off to your local installation properly and instead error. This commonly results in errors such as:
|
||||
|
||||
- Could not find Nx modules in this workspace.
|
||||
- The current directory isn't part of an Nx workspace.
|
||||
|
||||
If you find yourself in this position, you will need to update your global install of Nx.
|
||||
|
||||
In most cases, you can update a globally installed npm package by rerunning the command you used to install it, as described [above](#installing-nx-globally)
|
||||
|
||||
If you cannot remember which package manager you installed Nx globally with or are still encountering issues, you can locate other installs of Nx with these commands:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="npm" %}
|
||||
|
||||
```shell
|
||||
npm list --global nx
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="yarn" %}
|
||||
|
||||
```shell
|
||||
yarn global list nx
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="pnpm" %}
|
||||
|
||||
```shell
|
||||
pnpm list --global nx
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
You can then remove the extra global installations by running the following commands for the duplicate installations:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="npm" %}
|
||||
|
||||
```shell
|
||||
npm rm --global nx
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="yarn" %}
|
||||
|
||||
```shell
|
||||
yarn global remove nx
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="pnpm" %}
|
||||
|
||||
```shell
|
||||
pnpm rm --global nx
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Finally, to complete your global installation update, simply reinstall it as described [above](#installing-nx-globally).
|
||||
@@ -0,0 +1,47 @@
|
||||
# `.nx` and the Nx Wrapper
|
||||
|
||||
Nx is able to manage its own installation via the `.nx` directory. This installation is described within [nx.json](/reference/nx-json#installation).
|
||||
|
||||
By allowing Nx to manage its installation, a given repository is no longer required to contain a package.json or node_modules in its root. This is useful for repositories which may not contain any javascript or typescript. Additionally, since the Nx installation is managed inside `.nx`, it is easier to separate out from your other dependencies.
|
||||
|
||||
## Usage
|
||||
|
||||
You can install Nx in the `.nx/installation` directory by running `nx init` in a directory without package.json, and picking to install Nx in the current directory.
|
||||
|
||||
When Nx is installed in `.nx`, you can run Nx via a global Nx installation or the nx and nx.bat scripts that were created. In either case, the wrapper (.nx/nxw.js) will be invoked and ensure that the current workspace is up to date prior to invoking Nx.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="Global Install" %}
|
||||
|
||||
```shell
|
||||
> nx build my-project
|
||||
> nx generate application
|
||||
> nx graph
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="nx shell script" %}
|
||||
|
||||
```shell
|
||||
> ./nx build my-project
|
||||
> ./nx generate application
|
||||
> ./nx graph
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="nx.bat" %}
|
||||
|
||||
```shell
|
||||
> ./nx.bat build my-project
|
||||
> ./nx.bat generate application
|
||||
> ./nx.bat graph
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
{% callout type="note" title="Available since Nx v15.8.7" %}
|
||||
|
||||
Support for `--use-dot-nx-installation` was added in Nx v15.8.7. To ensure that it is available, specify the version of nx when running your command so that `npx` doesn't accept an older version that is in the cache. (e.g. `npx nx@latest init`)
|
||||
|
||||
{% /callout %}
|
||||
@@ -54,13 +54,13 @@ nx g @nrwl/js:lib my-lib
|
||||
|
||||
## Build
|
||||
|
||||
You can `build` libraries that are generated with `--buildable` flag.
|
||||
You can `build` libraries that are generated with a bundler specified.
|
||||
|
||||
```shell
|
||||
nx g @nrwl/js:lib my-buildable-lib --buildable
|
||||
nx g @nrwl/js:lib my-buildable-lib --bundler=rollup
|
||||
```
|
||||
|
||||
Generating a library with `--buildable` will add a `build` target to the library's `project.json` file allows the library to be built.
|
||||
Generating a library with `--bundler` specified will add a `build` target to the library's `project.json` file allows the library to be built.
|
||||
|
||||
```shell
|
||||
nx build my-buildable-lib
|
||||
@@ -95,7 +95,7 @@ Currently, `@nrwl/js` supports the following compilers:
|
||||
- Create a buildable library with `swc`
|
||||
|
||||
```shell
|
||||
nx g @nrwl/js:lib my-swc-lib --compiler=swc --buildable
|
||||
nx g @nrwl/js:lib my-swc-lib --bundler=swc
|
||||
```
|
||||
|
||||
- Convert a `tsc` library to use `swc`
|
||||
|
||||
@@ -56,6 +56,10 @@ You can generate Storybook configuration for an individual project with this com
|
||||
nx g @nrwl/storybook:configuration project-name --storybook7betaConfiguration --storybook7UiFramework=@storybook/react-webpack5
|
||||
```
|
||||
|
||||
{% callout type="info" title="For Nx versions <15.9" %}
|
||||
The flag is called `storybook7betaConfiguration` for Nx versions <15.9.
|
||||
{% /callout %}
|
||||
|
||||
In the field `storybook7UiFramework` you must choose one of the following Storybook frameworks:
|
||||
|
||||
- `@storybook/angular`
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Environment Variables
|
||||
|
||||
The following environment variables are ones that you can set to change the behavior of Nx in different environments.
|
||||
|
||||
| Property | Type | Description |
|
||||
| -------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| NX_BASE | string | The default base branch to use when calculating the affected projects. Can be overridden on the command line with `--base`. |
|
||||
@@ -16,3 +18,13 @@
|
||||
| NX_VERBOSE_LOGGING | boolean | If set to `true`, will print debug information useful for troubleshooting |
|
||||
| NX_DRY_RUN | boolean | If set to `true`, will perform a dry run of the generator. No files will be created and no packages will be installed. |
|
||||
| NX_INTERACTIVE | boolean | If set to `true`, will allow Nx to prompt you in the terminal to answer some further questions when running generators. |
|
||||
|
||||
Nx will set the following environment variables so they can be accessible within the process even outside of executors and generators
|
||||
|
||||
| Property | Type | Description |
|
||||
| ---------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| NX_TASK_TARGET_PROJECT | string | Set to the project name of the task being run. Use this to tell which project is being run. |
|
||||
| NX_TASK_TARGET_TARGET | string | Set to the target name of the task being run. Use this to tell which target of the project is being run. |
|
||||
| NX_TASK_TARGET_CONFIGURATION | string | Set to the configuration name of the task being run. Use this to tell which configuration of the target is being run. |
|
||||
| NX_DRY_RUN | boolean | Set to `true` during dry runs of generators. Use this to avoid side effects during generators. |
|
||||
| NX_INTERACTIVE | boolean | Set to `false` when running generators with `--interactive=false`. Use this to prevent prompting during generators |
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
uniq,
|
||||
updateFile,
|
||||
updateProjectConfig,
|
||||
removeFile,
|
||||
} from '../../utils';
|
||||
import { names } from '@nrwl/devkit';
|
||||
|
||||
@@ -19,154 +20,15 @@ describe('Angular Cypress Component Tests', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
projectName = newProject({ name: uniq('cy-ng') });
|
||||
runCLI(`generate @nrwl/angular:app ${appName} --no-interactive`);
|
||||
runCLI(
|
||||
`generate @nrwl/angular:component fancy-component --project=${appName} --no-interactive`
|
||||
);
|
||||
runCLI(`generate @nrwl/angular:lib ${usedInAppLibName} --no-interactive`);
|
||||
runCLI(
|
||||
`generate @nrwl/angular:component btn --project=${usedInAppLibName} --inlineTemplate --inlineStyle --export --no-interactive`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nrwl/angular:component btn-standalone --project=${usedInAppLibName} --inlineTemplate --inlineStyle --export --standalone --no-interactive`
|
||||
);
|
||||
updateFile(
|
||||
`libs/${usedInAppLibName}/src/lib/btn/btn.component.ts`,
|
||||
`
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: '${projectName}-btn',
|
||||
template: '<button class="text-green-500">{{text}}</button>',
|
||||
styles: []
|
||||
})
|
||||
export class BtnComponent {
|
||||
@Input() text = 'something';
|
||||
}
|
||||
`
|
||||
);
|
||||
updateFile(
|
||||
`libs/${usedInAppLibName}/src/lib/btn-standalone/btn-standalone.component.ts`,
|
||||
`
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
@Component({
|
||||
selector: '${projectName}-btn-standalone',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
template: '<button class="text-green-500">standlone-{{text}}</button>',
|
||||
styles: [],
|
||||
})
|
||||
export class BtnStandaloneComponent {
|
||||
@Input() text = 'something';
|
||||
}
|
||||
`
|
||||
);
|
||||
// use lib in the app
|
||||
createFile(
|
||||
`apps/${appName}/src/app/app.component.html`,
|
||||
`
|
||||
<${projectName}-btn></${projectName}-btn>
|
||||
<${projectName}-btn-standalone></${projectName}-btn-standalone>
|
||||
<${projectName}-nx-welcome></${projectName}-nx-welcome>
|
||||
`
|
||||
);
|
||||
const btnModuleName = names(usedInAppLibName).className;
|
||||
updateFile(
|
||||
`apps/${appName}/src/app/app.component.scss`,
|
||||
`
|
||||
@use 'styleguide' as *;
|
||||
createApp(appName);
|
||||
|
||||
h1 {
|
||||
@include headline;
|
||||
}`
|
||||
);
|
||||
updateFile(
|
||||
`apps/${appName}/src/app/app.module.ts`,
|
||||
`
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import {${btnModuleName}Module} from "@${projectName}/${usedInAppLibName}";
|
||||
createLib(projectName, appName, usedInAppLibName);
|
||||
useLibInApp(projectName, appName, usedInAppLibName);
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
createBuildableLib(projectName, buildableLibName);
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent, NxWelcomeComponent],
|
||||
imports: [BrowserModule, ${btnModuleName}Module],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule {}
|
||||
`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nrwl/angular:lib ${buildableLibName} --buildable --no-interactive`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nrwl/angular:component input --project=${buildableLibName} --inlineTemplate --inlineStyle --export --no-interactive`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nrwl/angular:component input-standalone --project=${buildableLibName} --inlineTemplate --inlineStyle --export --standalone --no-interactive`
|
||||
);
|
||||
updateFile(
|
||||
`libs/${buildableLibName}/src/lib/input/input.component.ts`,
|
||||
`
|
||||
import {Component, Input} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: '${projectName}-input',
|
||||
template: \`<label class="text-green-500">Email: <input class="border-blue-500" type="email" [readOnly]="readOnly"></label>\`,
|
||||
styles : []
|
||||
})
|
||||
export class InputComponent{
|
||||
@Input() readOnly = false;
|
||||
}
|
||||
`
|
||||
);
|
||||
updateFile(
|
||||
`libs/${buildableLibName}/src/lib/input-standalone/input-standalone.component.ts`,
|
||||
`
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
@Component({
|
||||
selector: '${projectName}-input-standalone',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
template: \`<label class="text-green-500">Email: <input class="border-blue-500" type="email" [readOnly]="readOnly"></label>\`,
|
||||
styles : []
|
||||
})
|
||||
export class InputStandaloneComponent{
|
||||
@Input() readOnly = false;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
// make sure assets from the workspace root work.
|
||||
createFile('libs/assets/data.json', JSON.stringify({ data: 'data' }));
|
||||
createFile(
|
||||
'assets/styles/styleguide.scss',
|
||||
`
|
||||
@mixin headline {
|
||||
font-weight: bold;
|
||||
color: darkkhaki;
|
||||
background: lightcoral;
|
||||
font-weight: 24px;
|
||||
}
|
||||
`
|
||||
);
|
||||
updateProjectConfig(appName, (config) => {
|
||||
config.targets['build'].options.stylePreprocessorOptions = {
|
||||
includePaths: ['assets/styles'],
|
||||
};
|
||||
config.targets['build'].options.assets.push({
|
||||
glob: '**/*',
|
||||
input: 'libs/assets',
|
||||
output: 'assets',
|
||||
});
|
||||
return config;
|
||||
});
|
||||
useWorkspaceAssetsInApp(appName);
|
||||
});
|
||||
|
||||
afterAll(() => cleanupProject());
|
||||
@@ -200,9 +62,226 @@ import {CommonModule} from '@angular/common';
|
||||
`generate @nrwl/angular:cypress-component-configuration --project=${buildableLibName} --generate-tests --no-interactive`
|
||||
);
|
||||
}).toThrow();
|
||||
createFile(
|
||||
|
||||
updateTestToAssertTailwindIsNotApplied(buildableLibName);
|
||||
|
||||
runCLI(
|
||||
`generate @nrwl/angular:cypress-component-configuration --project=${buildableLibName} --generate-tests --build-target=${appName}:build --no-interactive`
|
||||
);
|
||||
if (runCypressTests()) {
|
||||
expect(runCLI(`component-test ${buildableLibName} --no-watch`)).toContain(
|
||||
'All specs passed!'
|
||||
);
|
||||
}
|
||||
// add tailwind
|
||||
runCLI(
|
||||
`generate @nrwl/angular:setup-tailwind --project=${buildableLibName}`
|
||||
);
|
||||
updateFile(
|
||||
`libs/${buildableLibName}/src/lib/input/input.component.cy.ts`,
|
||||
`
|
||||
(content) => {
|
||||
// text-green-500 should now apply
|
||||
return content.replace('rgb(0, 0, 0)', 'rgb(34, 197, 94)');
|
||||
}
|
||||
);
|
||||
updateFile(
|
||||
`libs/${buildableLibName}/src/lib/input-standalone/input-standalone.component.cy.ts`,
|
||||
(content) => {
|
||||
// text-green-500 should now apply
|
||||
return content.replace('rgb(0, 0, 0)', 'rgb(34, 197, 94)');
|
||||
}
|
||||
);
|
||||
|
||||
if (runCypressTests()) {
|
||||
expect(runCLI(`component-test ${buildableLibName} --no-watch`)).toContain(
|
||||
'All specs passed!'
|
||||
);
|
||||
checkFilesDoNotExist(`tmp/libs/${buildableLibName}/ct-styles.css`);
|
||||
}
|
||||
}, 300_000);
|
||||
|
||||
it('should test lib with implicit dep on buildTarget', () => {
|
||||
// creates graph like buildableLib -> lib -> app
|
||||
// updates the apps styles and they should apply to the buildableLib
|
||||
// even though app is not directly connected to buildableLib
|
||||
useBuildableLibInLib(projectName, buildableLibName, usedInAppLibName);
|
||||
|
||||
updateBuilableLibTestsToAssertAppStyles(appName, buildableLibName);
|
||||
|
||||
if (runCypressTests()) {
|
||||
expect(runCLI(`component-test ${buildableLibName} --no-watch`)).toContain(
|
||||
'All specs passed!'
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function createApp(appName: string) {
|
||||
runCLI(`generate @nrwl/angular:app ${appName} --no-interactive`);
|
||||
runCLI(
|
||||
`generate @nrwl/angular:component fancy-component --project=${appName} --no-interactive`
|
||||
);
|
||||
}
|
||||
|
||||
function createLib(projectName: string, appName: string, libName: string) {
|
||||
runCLI(`generate @nrwl/angular:lib ${libName} --no-interactive`);
|
||||
runCLI(
|
||||
`generate @nrwl/angular:component btn --project=${libName} --inlineTemplate --inlineStyle --export --no-interactive`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nrwl/angular:component btn-standalone --project=${libName} --inlineTemplate --inlineStyle --export --standalone --no-interactive`
|
||||
);
|
||||
updateFile(
|
||||
`libs/${libName}/src/lib/btn/btn.component.ts`,
|
||||
`
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: '${projectName}-btn',
|
||||
template: '<button class="text-green-500">{{text}}</button>',
|
||||
styles: []
|
||||
})
|
||||
export class BtnComponent {
|
||||
@Input() text = 'something';
|
||||
}
|
||||
`
|
||||
);
|
||||
updateFile(
|
||||
`libs/${libName}/src/lib/btn-standalone/btn-standalone.component.ts`,
|
||||
`
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
@Component({
|
||||
selector: '${projectName}-btn-standalone',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
template: '<button class="text-green-500">standlone-{{text}}</button>',
|
||||
styles: [],
|
||||
})
|
||||
export class BtnStandaloneComponent {
|
||||
@Input() text = 'something';
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
function createBuildableLib(projectName: string, libName: string) {
|
||||
// create lib
|
||||
runCLI(`generate @nrwl/angular:lib ${libName} --buildable --no-interactive`);
|
||||
// create cmp for lib
|
||||
runCLI(
|
||||
`generate @nrwl/angular:component input --project=${libName} --inlineTemplate --inlineStyle --export --no-interactive`
|
||||
);
|
||||
// create standlone cmp for lib
|
||||
runCLI(
|
||||
`generate @nrwl/angular:component input-standalone --project=${libName} --inlineTemplate --inlineStyle --export --standalone --no-interactive`
|
||||
);
|
||||
// update cmp implmentation to use tailwind clasasserting in tests
|
||||
updateFile(
|
||||
`libs/${libName}/src/lib/input/input.component.ts`,
|
||||
`
|
||||
import {Component, Input} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: '${projectName}-input',
|
||||
template: \`<label class="text-green-500">Email: <input class="border-blue-500" type="email" [readOnly]="readOnly"></label>\`,
|
||||
styles : []
|
||||
})
|
||||
export class InputComponent{
|
||||
@Input() readOnly = false;
|
||||
}
|
||||
`
|
||||
);
|
||||
updateFile(
|
||||
`libs/${libName}/src/lib/input-standalone/input-standalone.component.ts`,
|
||||
`
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
@Component({
|
||||
selector: '${projectName}-input-standalone',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
template: \`<label class="text-green-500">Email: <input class="border-blue-500" type="email" [readOnly]="readOnly"></label>\`,
|
||||
styles : []
|
||||
})
|
||||
export class InputStandaloneComponent{
|
||||
@Input() readOnly = false;
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
function useLibInApp(projectName: string, appName: string, libName: string) {
|
||||
createFile(
|
||||
`apps/${appName}/src/app/app.component.html`,
|
||||
`
|
||||
<${projectName}-btn></${projectName}-btn>
|
||||
<${projectName}-btn-standalone></${projectName}-btn-standalone>
|
||||
<${projectName}-nx-welcome></${projectName}-nx-welcome>
|
||||
`
|
||||
);
|
||||
const btnModuleName = names(libName).className;
|
||||
updateFile(
|
||||
`apps/${appName}/src/app/app.component.scss`,
|
||||
`
|
||||
@use 'styleguide' as *;
|
||||
|
||||
h1 {
|
||||
@include headline;
|
||||
}`
|
||||
);
|
||||
updateFile(
|
||||
`apps/${appName}/src/app/app.module.ts`,
|
||||
`
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import {${btnModuleName}Module} from "@${projectName}/${libName}";
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent, NxWelcomeComponent],
|
||||
imports: [BrowserModule, ${btnModuleName}Module],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule {}
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
function useWorkspaceAssetsInApp(appName: string) {
|
||||
// make sure assets from the workspace root work.
|
||||
createFile('libs/assets/data.json', JSON.stringify({ data: 'data' }));
|
||||
createFile(
|
||||
'assets/styles/styleguide.scss',
|
||||
`
|
||||
@mixin headline {
|
||||
font-weight: bold;
|
||||
color: darkkhaki;
|
||||
background: lightcoral;
|
||||
font-weight: 24px;
|
||||
}
|
||||
`
|
||||
);
|
||||
updateProjectConfig(appName, (config) => {
|
||||
config.targets['build'].options.stylePreprocessorOptions = {
|
||||
includePaths: ['assets/styles'],
|
||||
};
|
||||
config.targets['build'].options.assets.push({
|
||||
glob: '**/*',
|
||||
input: 'libs/assets',
|
||||
output: 'assets',
|
||||
});
|
||||
return config;
|
||||
});
|
||||
}
|
||||
|
||||
function updateTestToAssertTailwindIsNotApplied(libName: string) {
|
||||
createFile(
|
||||
`libs/${libName}/src/lib/input/input.component.cy.ts`,
|
||||
`
|
||||
import { MountConfig } from 'cypress/angular';
|
||||
import { InputComponent } from './input.component';
|
||||
|
||||
@@ -229,11 +308,11 @@ describe(InputComponent.name, () => {
|
||||
});
|
||||
});
|
||||
`
|
||||
);
|
||||
);
|
||||
|
||||
createFile(
|
||||
`libs/${buildableLibName}/src/lib/input-standalone/input-standalone.component.cy.ts`,
|
||||
`
|
||||
createFile(
|
||||
`libs/${libName}/src/lib/input-standalone/input-standalone.component.cy.ts`,
|
||||
`
|
||||
import { MountConfig } from 'cypress/angular';
|
||||
import { InputStandaloneComponent } from './input-standalone.component';
|
||||
|
||||
@@ -260,39 +339,50 @@ describe(InputStandaloneComponent.name, () => {
|
||||
});
|
||||
});
|
||||
`
|
||||
);
|
||||
);
|
||||
}
|
||||
function useBuildableLibInLib(
|
||||
projectName: string,
|
||||
buildableLibName: string,
|
||||
libName: string
|
||||
) {
|
||||
const buildLibNames = names(buildableLibName);
|
||||
// use the buildable lib in lib so now buildableLib has an indirect dep on app
|
||||
updateFile(
|
||||
`libs/${libName}/src/lib/btn-standalone/btn-standalone.component.ts`,
|
||||
`
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { InputStandaloneComponent } from '@${projectName}/${buildLibNames.fileName}';
|
||||
@Component({
|
||||
selector: '${projectName}-btn-standalone',
|
||||
standalone: true,
|
||||
imports: [CommonModule, InputStandaloneComponent],
|
||||
template: '<button class="text-green-500">standlone-{{text}}</button>${projectName} <${projectName}-input-standalone></${projectName}-input-standalone>',
|
||||
styles: [],
|
||||
})
|
||||
export class BtnStandaloneComponent {
|
||||
@Input() text = 'something';
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
runCLI(
|
||||
`generate @nrwl/angular:cypress-component-configuration --project=${buildableLibName} --generate-tests --build-target=${appName}:build --no-interactive`
|
||||
);
|
||||
if (runCypressTests()) {
|
||||
expect(runCLI(`component-test ${buildableLibName} --no-watch`)).toContain(
|
||||
'All specs passed!'
|
||||
);
|
||||
function updateBuilableLibTestsToAssertAppStyles(
|
||||
appName: string,
|
||||
buildableLibName: string
|
||||
) {
|
||||
updateFile(
|
||||
`apps/${appName}/src/styles.css`,
|
||||
`label {color: pink !important;}`
|
||||
);
|
||||
|
||||
removeFile(`libs/${buildableLibName}/src/lib/input/input.component.cy.ts`);
|
||||
updateFile(
|
||||
`libs/${buildableLibName}/src/lib/input-standalone/input-standalone.component.cy.ts`,
|
||||
(content) => {
|
||||
// app styles should now apply
|
||||
return content.replace('rgb(34, 197, 94)', 'rgb(255, 192, 203)');
|
||||
}
|
||||
|
||||
// add tailwind
|
||||
runCLI(
|
||||
`generate @nrwl/angular:setup-tailwind --project=${buildableLibName}`
|
||||
);
|
||||
updateFile(
|
||||
`libs/${buildableLibName}/src/lib/input/input.component.cy.ts`,
|
||||
(content) => {
|
||||
// text-green-500 should now apply
|
||||
return content.replace('rgb(0, 0, 0)', 'rgb(34, 197, 94)');
|
||||
}
|
||||
);
|
||||
updateFile(
|
||||
`libs/${buildableLibName}/src/lib/input-standalone/input-standalone.component.cy.ts`,
|
||||
(content) => {
|
||||
// text-green-500 should now apply
|
||||
return content.replace('rgb(0, 0, 0)', 'rgb(34, 197, 94)');
|
||||
}
|
||||
);
|
||||
|
||||
expect(runCLI(`component-test ${buildableLibName} --no-watch`)).toContain(
|
||||
'All specs passed!'
|
||||
);
|
||||
checkFilesDoNotExist(`tmp/libs/${buildableLibName}/ct-styles.css`);
|
||||
}, 300_000);
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,13 @@ describe('nx init (for CRA)', () => {
|
||||
expect(packageJson.devDependencies['@nrwl/vite']).toBeUndefined();
|
||||
expect(packageJson.devDependencies['@nrwl/webpack']).toBeDefined();
|
||||
|
||||
runCLI(`build ${appName}`);
|
||||
runCLI(`build ${appName}`, {
|
||||
env: {
|
||||
// since craco 7.1.0 the NODE_ENV is used, since the tests set it
|
||||
// to "test" is causes an issue with React Refresh Babel
|
||||
NODE_ENV: undefined,
|
||||
},
|
||||
});
|
||||
checkFilesExist(`dist/apps/${appName}/index.html`);
|
||||
});
|
||||
|
||||
@@ -104,7 +110,13 @@ describe('nx init (for CRA)', () => {
|
||||
|
||||
expect(craToNxOutput).toContain('🎉 Done!');
|
||||
|
||||
runCLI(`build ${appName}`);
|
||||
runCLI(`build ${appName}`, {
|
||||
env: {
|
||||
// since craco 7.1.0 the NODE_ENV is used, since the tests set it
|
||||
// to "test" is causes an issue with React Refresh Babel
|
||||
NODE_ENV: undefined,
|
||||
},
|
||||
});
|
||||
checkFilesExist(`dist/${appName}/index.html`);
|
||||
});
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ describe('EsBuild Plugin', () => {
|
||||
const parentLib = uniq('parent-lib');
|
||||
const childLib = uniq('child-lib');
|
||||
runCLI(`generate @nrwl/js:lib ${parentLib} --bundler=esbuild`);
|
||||
runCLI(`generate @nrwl/js:lib ${childLib} --buildable=false`);
|
||||
runCLI(`generate @nrwl/js:lib ${childLib} --bundler=none`);
|
||||
updateFile(
|
||||
`libs/${parentLib}/src/index.ts`,
|
||||
`
|
||||
|
||||
@@ -19,35 +19,39 @@ describe('inlining', () => {
|
||||
afterEach(() => cleanupProject());
|
||||
|
||||
it.each(['tsc', 'swc'])(
|
||||
'should inline libraries with --compiler=%s',
|
||||
async (compiler) => {
|
||||
'should inline libraries with --bundler=%s',
|
||||
async (bundler) => {
|
||||
const parent = uniq('parent');
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${parent} --compiler=${compiler} --no-interactive`
|
||||
`generate @nrwl/js:lib ${parent} --bundler=${bundler} --no-interactive`
|
||||
);
|
||||
|
||||
const buildable = uniq('buildable');
|
||||
runCLI(`generate @nrwl/js:lib ${buildable} --no-interactive`);
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${buildable} --bundler=${bundler} --no-interactive`
|
||||
);
|
||||
|
||||
const buildableTwo = uniq('buildabletwo');
|
||||
runCLI(`generate @nrwl/js:lib ${buildableTwo} --no-interactive`);
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${buildableTwo} --bundler=${bundler} --no-interactive`
|
||||
);
|
||||
|
||||
const nonBuildable = uniq('nonbuildable');
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${nonBuildable} --buildable=false --no-interactive`
|
||||
`generate @nrwl/js:lib ${nonBuildable} --bundler=none --no-interactive`
|
||||
);
|
||||
|
||||
updateFile(`libs/${parent}/src/lib/${parent}.ts`, () => {
|
||||
return `
|
||||
import { ${buildable} } from '@${scope}/${buildable}';
|
||||
import { ${buildableTwo} } from '@${scope}/${buildableTwo}';
|
||||
import { ${nonBuildable} } from '@${scope}/${nonBuildable}';
|
||||
import { ${buildable} } from '@${scope}/${buildable}';
|
||||
import { ${buildableTwo} } from '@${scope}/${buildableTwo}';
|
||||
import { ${nonBuildable} } from '@${scope}/${nonBuildable}';
|
||||
|
||||
export function ${parent}() {
|
||||
${buildable}();
|
||||
${buildableTwo}();
|
||||
${nonBuildable}();
|
||||
}
|
||||
export function ${parent}() {
|
||||
${buildable}();
|
||||
${buildableTwo}();
|
||||
${nonBuildable}();
|
||||
}
|
||||
`;
|
||||
});
|
||||
|
||||
@@ -101,30 +105,30 @@ export function ${parent}() {
|
||||
runCLI(`generate @nrwl/js:lib ${parent} --no-interactive`);
|
||||
|
||||
const child = uniq('child');
|
||||
runCLI(`generate @nrwl/js:lib ${child} --buildable=false --no-interactive`);
|
||||
runCLI(`generate @nrwl/js:lib ${child} --bundler=none --no-interactive`);
|
||||
|
||||
const grandChild = uniq('grandchild');
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${grandChild} --buildable=false --no-interactive`
|
||||
`generate @nrwl/js:lib ${grandChild} --bundler=none --no-interactive`
|
||||
);
|
||||
|
||||
updateFile(`libs/${parent}/src/lib/${parent}.ts`, () => {
|
||||
return `
|
||||
import { ${child} } from '@${scope}/${child}';
|
||||
import { ${child} } from '@${scope}/${child}';
|
||||
|
||||
export function ${parent}() {
|
||||
${child}();
|
||||
}
|
||||
export function ${parent}() {
|
||||
${child}();
|
||||
}
|
||||
`;
|
||||
});
|
||||
|
||||
updateFile(`libs/${child}/src/lib/${child}.ts`, () => {
|
||||
return `
|
||||
import { ${grandChild} } from '@${scope}/${grandChild}';
|
||||
import { ${grandChild} } from '@${scope}/${grandChild}';
|
||||
|
||||
export function ${child}() {
|
||||
${grandChild}();
|
||||
}
|
||||
export function ${child}() {
|
||||
${grandChild}();
|
||||
}
|
||||
`;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { satisfies } from 'semver';
|
||||
import { execSync } from 'child_process';
|
||||
import {
|
||||
checkFilesDoNotExist,
|
||||
checkFilesExist,
|
||||
@@ -21,13 +23,14 @@ describe('js e2e', () => {
|
||||
scope = newProject();
|
||||
});
|
||||
|
||||
afterEach(() => cleanupProject());
|
||||
afterEach(() => {
|
||||
cleanupProject();
|
||||
});
|
||||
|
||||
it('should create libs with js executors (--compiler=swc)', async () => {
|
||||
it('should create libs with js executors (--bundler=swc)', async () => {
|
||||
const lib = uniq('lib');
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${lib} --buildable --compiler=swc --no-interactive`
|
||||
);
|
||||
runCLI(`generate @nrwl/js:lib ${lib} --bundler=swc --no-interactive`);
|
||||
|
||||
const libPackageJson = readJson(`libs/${lib}/package.json`);
|
||||
expect(libPackageJson.scripts).toBeUndefined();
|
||||
|
||||
@@ -45,9 +48,7 @@ describe('js e2e', () => {
|
||||
checkFilesDoNotExist(`libs/${lib}/.babelrc`);
|
||||
|
||||
const parentLib = uniq('parentlib');
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${parentLib} --buildable --compiler=swc --no-interactive`
|
||||
);
|
||||
runCLI(`generate @nrwl/js:lib ${parentLib} --bundler=swc --no-interactive`);
|
||||
const parentLibPackageJson = readJson(`libs/${parentLib}/package.json`);
|
||||
expect(parentLibPackageJson.scripts).toBeUndefined();
|
||||
expect((await runCLIAsync(`test ${parentLib}`)).combinedOutput).toContain(
|
||||
@@ -103,7 +104,7 @@ describe('js e2e', () => {
|
||||
const swcHelpersFromDist = readJson(`dist/libs/${lib}/package.json`)
|
||||
.peerDependencies['@swc/helpers'];
|
||||
|
||||
expect(swcHelpersFromDist).toEqual(swcHelpersFromRoot);
|
||||
expect(satisfies(swcHelpersFromDist, swcHelpersFromRoot)).toBeTruthy();
|
||||
|
||||
updateJson(`libs/${lib}/.swcrc`, (json) => {
|
||||
json.jsc.externalHelpers = false;
|
||||
@@ -116,4 +117,50 @@ describe('js e2e', () => {
|
||||
'peerDependencies.@swc/helpers'
|
||||
);
|
||||
}, 240_000);
|
||||
|
||||
it('should handle swcrc path mappings', async () => {
|
||||
const lib = uniq('lib');
|
||||
runCLI(`generate @nrwl/js:lib ${lib} --bundler=swc --no-interactive`);
|
||||
|
||||
// add a dummy x.ts file for path mappings
|
||||
updateFile(
|
||||
`libs/${lib}/src/x.ts`,
|
||||
`
|
||||
export function x() {
|
||||
console.log('x');
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
// update .swcrc to use path mappings
|
||||
updateJson(`libs/${lib}/.swcrc`, (json) => {
|
||||
json.jsc.paths = {
|
||||
'src/*': ['src/*'],
|
||||
};
|
||||
return json;
|
||||
});
|
||||
|
||||
// update lib.ts to use x
|
||||
updateFile(`libs/${lib}/src/lib/${lib}.ts`, () => {
|
||||
return `
|
||||
// @ts-ignore
|
||||
import { x } from 'src/x';
|
||||
|
||||
export function myLib() {
|
||||
console.log(x());
|
||||
}
|
||||
|
||||
myLib();
|
||||
`;
|
||||
});
|
||||
|
||||
// now run build
|
||||
runCLI(`build ${lib}`);
|
||||
|
||||
// invoke the lib with node
|
||||
const result = execSync(`node dist/libs/${lib}/src/lib/${lib}.js`, {
|
||||
cwd: tmpProjPath(),
|
||||
}).toString();
|
||||
expect(result).toContain('x');
|
||||
}, 240_000);
|
||||
});
|
||||
|
||||
@@ -28,9 +28,7 @@ describe('js e2e', () => {
|
||||
|
||||
it('should create libs with js executors (--compiler=tsc)', async () => {
|
||||
const lib = uniq('lib');
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${lib} --buildable --compiler=tsc --no-interactive`
|
||||
);
|
||||
runCLI(`generate @nrwl/js:lib ${lib} --bundler=tsc --no-interactive`);
|
||||
const libPackageJson = readJson(`libs/${lib}/package.json`);
|
||||
expect(libPackageJson.scripts).toBeUndefined();
|
||||
|
||||
@@ -90,9 +88,7 @@ describe('js e2e', () => {
|
||||
libBuildProcess.kill();
|
||||
|
||||
const parentLib = uniq('parentlib');
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${parentLib} --buildable --compiler=tsc --no-interactive`
|
||||
);
|
||||
runCLI(`generate @nrwl/js:lib ${parentLib} --bundler=tsc --no-interactive`);
|
||||
const parentLibPackageJson = readJson(`libs/${parentLib}/package.json`);
|
||||
expect(parentLibPackageJson.scripts).toBeUndefined();
|
||||
expect((await runCLIAsync(`test ${parentLib}`)).combinedOutput).toContain(
|
||||
@@ -137,8 +133,11 @@ describe('js e2e', () => {
|
||||
const rootPackageJson = readJson(`package.json`);
|
||||
|
||||
expect(
|
||||
readJson(`dist/libs/${lib}/package.json`).peerDependencies.tslib
|
||||
).toEqual(rootPackageJson.dependencies.tslib);
|
||||
satisfies(
|
||||
readJson(`dist/libs/${lib}/package.json`).peerDependencies.tslib,
|
||||
rootPackageJson.dependencies.tslib
|
||||
)
|
||||
).toBeTruthy();
|
||||
|
||||
updateJson(`libs/${lib}/tsconfig.json`, (json) => {
|
||||
json.compilerOptions = { ...json.compilerOptions, importHelpers: false };
|
||||
@@ -170,9 +169,7 @@ describe('package.json updates', () => {
|
||||
it('should update package.json with detected dependencies', async () => {
|
||||
const pmc = getPackageManagerCommand();
|
||||
const lib = uniq('lib');
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${lib} --buildable --compiler=tsc --no-interactive`
|
||||
);
|
||||
runCLI(`generate @nrwl/js:lib ${lib} --bundler=tsc --no-interactive`);
|
||||
|
||||
// Add a dependency for this lib to check the built package.json
|
||||
runCommand(`${pmc.addProd} react`);
|
||||
|
||||
@@ -37,10 +37,10 @@ describe('js e2e', () => {
|
||||
|
||||
it('should allow wildcard ts path alias', async () => {
|
||||
const base = uniq('base');
|
||||
runCLI(`generate @nrwl/js:lib ${base} --buildable --no-interactive`);
|
||||
runCLI(`generate @nrwl/js:lib ${base} --bundler=tsc --no-interactive`);
|
||||
|
||||
const lib = uniq('lib');
|
||||
runCLI(`generate @nrwl/js:lib ${lib} --buildable --no-interactive`);
|
||||
runCLI(`generate @nrwl/js:lib ${lib} --bundler=tsc --no-interactive`);
|
||||
|
||||
updateFile(`libs/${base}/src/index.ts`, () => {
|
||||
return `
|
||||
|
||||
@@ -35,7 +35,7 @@ describe('make-angular-cli-faster', () => {
|
||||
cwd: tmpProjPath(),
|
||||
env: process.env,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
stdio: 'pipe',
|
||||
}
|
||||
)
|
||||
).not.toThrow();
|
||||
|
||||
+73
-27
@@ -4,6 +4,7 @@ import {
|
||||
cleanupProject,
|
||||
getPackageManagerCommand,
|
||||
isNotWindows,
|
||||
killPort,
|
||||
killPorts,
|
||||
newProject,
|
||||
packageManagerLockFile,
|
||||
@@ -20,35 +21,36 @@ import {
|
||||
import { stringUtils } from '@nrwl/workspace';
|
||||
import * as http from 'http';
|
||||
import { checkApp } from './utils';
|
||||
import { removeSync } from 'fs-extra';
|
||||
|
||||
describe('Next.js Applications', () => {
|
||||
let proj: string;
|
||||
let originalEnv: string;
|
||||
let packageManager;
|
||||
|
||||
beforeAll(() => {
|
||||
beforeEach(() => {
|
||||
proj = newProject();
|
||||
packageManager = detectPackageManager(tmpProjPath());
|
||||
});
|
||||
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
beforeEach(() => {
|
||||
originalEnv = process.env.NODE_ENV;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env.NODE_ENV = originalEnv;
|
||||
cleanupProject();
|
||||
});
|
||||
|
||||
it('should generate app + libs', async () => {
|
||||
const appName = uniq('app');
|
||||
const nextLib = uniq('nextlib');
|
||||
const jsLib = uniq('tslib');
|
||||
const buildableLib = uniq('buildablelib');
|
||||
|
||||
runCLI(`generate @nrwl/next:app ${appName} --no-interactive --style=css`);
|
||||
runCLI(`generate @nrwl/next:lib ${nextLib} --no-interactive`);
|
||||
runCLI(`generate @nrwl/js:lib ${jsLib} --no-interactive`);
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${buildableLib} --no-interactive --bundler=vite`
|
||||
);
|
||||
|
||||
// Create file in public that should be copied to dist
|
||||
updateFile(`apps/${appName}/public/a/b.txt`, `Hello World!`);
|
||||
@@ -77,27 +79,36 @@ describe('Next.js Applications', () => {
|
||||
updateFile(
|
||||
`libs/${jsLib}/src/lib/${jsLib}.ts`,
|
||||
`
|
||||
export function testFn(): string {
|
||||
export function jsLib(): string {
|
||||
return 'Hello Nx';
|
||||
};
|
||||
|
||||
// testing whether async-await code in Node / Next.js api routes works as expected
|
||||
export async function testAsyncFn() {
|
||||
export async function jsLibAsync() {
|
||||
return await Promise.resolve('hell0');
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`libs/${buildableLib}/src/lib/${buildableLib}.ts`,
|
||||
`
|
||||
export function buildableLib(): string {
|
||||
return 'Hello Buildable';
|
||||
};
|
||||
`
|
||||
);
|
||||
|
||||
const mainPath = `apps/${appName}/pages/index.tsx`;
|
||||
const content = readFile(mainPath);
|
||||
|
||||
updateFile(
|
||||
`apps/${appName}/pages/api/hello.ts`,
|
||||
`
|
||||
import { testAsyncFn } from '@${proj}/${jsLib}';
|
||||
import { jsLibAsync } from '@${proj}/${jsLib}';
|
||||
|
||||
export default async function handler(_, res) {
|
||||
const value = await testAsyncFn();
|
||||
const value = await jsLibAsync();
|
||||
res.send(value);
|
||||
}
|
||||
`
|
||||
@@ -106,7 +117,8 @@ describe('Next.js Applications', () => {
|
||||
updateFile(
|
||||
mainPath,
|
||||
`
|
||||
import { testFn } from '@${proj}/${jsLib}';
|
||||
import { jsLib } from '@${proj}/${jsLib}';
|
||||
import { buildableLib } from '@${proj}/${buildableLib}';
|
||||
/* eslint-disable */
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
@@ -119,7 +131,8 @@ describe('Next.js Applications', () => {
|
||||
`</h2>`,
|
||||
`</h2>
|
||||
<div>
|
||||
{testFn()}
|
||||
{jsLib()}
|
||||
{buildableLib()}
|
||||
<TestComponent />
|
||||
</div>
|
||||
`
|
||||
@@ -154,6 +167,37 @@ describe('Next.js Applications', () => {
|
||||
`dist/apps/${appName}/public/a/b.txt`,
|
||||
`dist/apps/${appName}/public/shared/ui/hello.txt`
|
||||
);
|
||||
|
||||
// Check that `nx serve <app> --prod` works with previous production build (e.g. `nx build <app>`).
|
||||
const prodServePort = 4000;
|
||||
const prodServeProcess = await runCommandUntil(
|
||||
`run ${appName}:serve --prod --port=${prodServePort}`,
|
||||
(output) => {
|
||||
return output.includes(`localhost:${prodServePort}`);
|
||||
}
|
||||
);
|
||||
|
||||
// Check that the output is self-contained (i.e. can run with its own package.json + node_modules)
|
||||
const distPath = joinPathFragments(tmpProjPath(), 'dist/apps', appName);
|
||||
const selfContainedPort = 3000;
|
||||
const pmc = getPackageManagerCommand();
|
||||
runCommand(`${pmc.install}`, {
|
||||
cwd: distPath,
|
||||
});
|
||||
runCLI(
|
||||
`generate @nrwl/workspace:run-commands serve-prod --project ${appName} --cwd=dist/apps/${appName} --command="npx next start --port=${selfContainedPort}"`
|
||||
);
|
||||
const selfContainedProcess = await runCommandUntil(
|
||||
`run ${appName}:serve-prod`,
|
||||
(output) => {
|
||||
return output.includes(`localhost:${selfContainedPort}`);
|
||||
}
|
||||
);
|
||||
|
||||
prodServeProcess.kill();
|
||||
selfContainedProcess.kill();
|
||||
await killPort(prodServePort);
|
||||
await killPort(selfContainedPort);
|
||||
}, 300_000);
|
||||
|
||||
it('should build and install pruned lock file', () => {
|
||||
@@ -194,7 +238,7 @@ describe('Next.js Applications', () => {
|
||||
updateFile(
|
||||
`libs/${jsLib}/src/lib/${jsLib}.ts`,
|
||||
`
|
||||
export function testFn(): string {
|
||||
export function jsLib(): string {
|
||||
return process.env.NX_CUSTOM_VAR;
|
||||
};
|
||||
`
|
||||
@@ -204,11 +248,11 @@ describe('Next.js Applications', () => {
|
||||
`apps/${appName}/pages/index.tsx`,
|
||||
`
|
||||
import React from 'react';
|
||||
import { testFn } from '@${proj}/${jsLib}';
|
||||
import { jsLib } from '@${proj}/${jsLib}';
|
||||
|
||||
export const Index = ({ greeting }: any) => {
|
||||
return (
|
||||
<p>{testFn()}</p>
|
||||
<p>{jsLib()}</p>
|
||||
);
|
||||
};
|
||||
export default Index;
|
||||
@@ -384,17 +428,19 @@ describe('Next.js Applications', () => {
|
||||
}, 300_000);
|
||||
});
|
||||
|
||||
function getData(port: number, path = ''): Promise<any> {
|
||||
return new Promise((resolve) => {
|
||||
http.get(`http://localhost:${port}${path}`, (res) => {
|
||||
expect(res.statusCode).toEqual(200);
|
||||
let data = '';
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
res.once('end', () => {
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
function getData(port, path = ''): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
http
|
||||
.get(`http://localhost:${port}${path}`, (res) => {
|
||||
expect(res.statusCode).toEqual(200);
|
||||
let data = '';
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
res.once('end', () => {
|
||||
resolve(data);
|
||||
});
|
||||
})
|
||||
.on('error', (err) => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ describe('Node Applications + esbuild', () => {
|
||||
await runCLIAsync(`build ${app}`);
|
||||
|
||||
checkFilesExist(`dist/apps/${app}/main.js`);
|
||||
checkFilesExist(`dist/apps/${app}/main.js.map`);
|
||||
const result = execSync(`node dist/apps/${app}/main.js`, {
|
||||
cwd: tmpProjPath(),
|
||||
}).toString();
|
||||
|
||||
+32
-16
@@ -27,6 +27,7 @@ import {
|
||||
import { exec, execSync } from 'child_process';
|
||||
import * as http from 'http';
|
||||
import { getLockFileName } from 'nx/src/lock-file/lock-file';
|
||||
import { satisfies } from 'semver';
|
||||
|
||||
function getData(port, path = '/api'): Promise<any> {
|
||||
return new Promise((resolve) => {
|
||||
@@ -263,21 +264,36 @@ describe('Build Node apps', () => {
|
||||
})
|
||||
);
|
||||
|
||||
expect(packageJson.dependencies['@nestjs/common']).toEqual(
|
||||
rootPackageJson.dependencies['@nestjs/common']
|
||||
);
|
||||
expect(packageJson.dependencies['@nestjs/core']).toEqual(
|
||||
rootPackageJson.dependencies['@nestjs/core']
|
||||
);
|
||||
expect(packageJson.dependencies['reflect-metadata']).toEqual(
|
||||
rootPackageJson.dependencies['reflect-metadata']
|
||||
);
|
||||
expect(packageJson.dependencies['rxjs']).toEqual(
|
||||
rootPackageJson.dependencies['rxjs']
|
||||
);
|
||||
expect(packageJson.dependencies['tslib']).toEqual(
|
||||
rootPackageJson.dependencies['tslib']
|
||||
);
|
||||
expect(
|
||||
satisfies(
|
||||
packageJson.dependencies['@nestjs/common'],
|
||||
rootPackageJson.dependencies['@nestjs/common']
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
satisfies(
|
||||
packageJson.dependencies['@nestjs/core'],
|
||||
rootPackageJson.dependencies['@nestjs/core']
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
satisfies(
|
||||
packageJson.dependencies['reflect-metadata'],
|
||||
rootPackageJson.dependencies['reflect-metadata']
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
satisfies(
|
||||
packageJson.dependencies['rxjs'],
|
||||
rootPackageJson.dependencies['rxjs']
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
satisfies(
|
||||
packageJson.dependencies['tslib'],
|
||||
rootPackageJson.dependencies['tslib']
|
||||
)
|
||||
).toBeTruthy();
|
||||
|
||||
checkFilesExist(
|
||||
`dist/apps/${nestapp}/${packageManagerLockFile[packageManager]}`
|
||||
@@ -290,7 +306,7 @@ describe('Build Node apps', () => {
|
||||
runCLI(`generate @nrwl/node:app ${nodeapp} --bundler=webpack`);
|
||||
|
||||
const jslib = uniq('jslib');
|
||||
runCLI(`generate @nrwl/js:lib ${jslib} --buildable`);
|
||||
runCLI(`generate @nrwl/js:lib ${jslib} --bundler=tsc`);
|
||||
|
||||
updateFile(
|
||||
`apps/${nodeapp}/src/main.ts`,
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('nx init (for NestCLI)', () => {
|
||||
cwd: e2eCwd,
|
||||
encoding: 'utf-8',
|
||||
env: process.env,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
stdio: 'pipe',
|
||||
}
|
||||
);
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('nx init (for NestCLI)', () => {
|
||||
cwd: projectRoot,
|
||||
encoding: 'utf-8',
|
||||
env: process.env,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
stdio: 'pipe',
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
import type { NxJsonConfiguration } from '@nrwl/devkit';
|
||||
import {
|
||||
newEncapsulatedNxWorkspace,
|
||||
updateFile,
|
||||
updateJson,
|
||||
checkFilesDoNotExist,
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
getPublishedVersion,
|
||||
uniq,
|
||||
} from '@nrwl/e2e/utils';
|
||||
import { bold } from 'chalk';
|
||||
|
||||
describe('encapsulated nx', () => {
|
||||
let runEncapsulatedNx: ReturnType<typeof newEncapsulatedNxWorkspace>;
|
||||
|
||||
beforeAll(() => {
|
||||
runEncapsulatedNx = newEncapsulatedNxWorkspace();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cleanupProject({
|
||||
skipReset: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should support running targets in a encapsulated repo', () => {
|
||||
updateFile(
|
||||
'projects/a/project.json',
|
||||
JSON.stringify({
|
||||
name: 'a',
|
||||
targets: {
|
||||
echo: {
|
||||
command: `echo 'Hello from A'`,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
updateJson<NxJsonConfiguration>('nx.json', (json) => {
|
||||
json.tasksRunnerOptions.default.options.cacheableOperations = ['echo'];
|
||||
json.installation.plugins = {
|
||||
'@nrwl/nest': getPublishedVersion(),
|
||||
};
|
||||
return json;
|
||||
});
|
||||
|
||||
expect(runEncapsulatedNx('echo a')).toContain('Hello from A');
|
||||
|
||||
expect(runEncapsulatedNx('echo a')).toContain(
|
||||
'Nx read the output from the cache instead of running the command for 1 out of 1 tasks'
|
||||
);
|
||||
|
||||
assertNoRootPackages();
|
||||
expect(() =>
|
||||
checkFilesExist(
|
||||
'.nx/installation/package.json',
|
||||
'.nx/installation/package-lock.json',
|
||||
'.nx/cache/terminalOutputs'
|
||||
)
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should work with nx report', () => {
|
||||
const output = runEncapsulatedNx('report');
|
||||
expect(output).toMatch(new RegExp(`nx.*:.*${getPublishedVersion()}`));
|
||||
expect(output).toMatch(
|
||||
new RegExp(`@nrwl/nest.*:.*${getPublishedVersion()}`)
|
||||
);
|
||||
expect(output).not.toContain('@nrwl/express');
|
||||
});
|
||||
|
||||
it('should work with nx list', () => {
|
||||
let output = runEncapsulatedNx('list');
|
||||
const lines = output.split('\n');
|
||||
const installedPluginStart = lines.findIndex((l) =>
|
||||
l.includes('Installed plugins')
|
||||
);
|
||||
const installedPluginEnd = lines.findIndex((l) =>
|
||||
l.includes('Also available')
|
||||
);
|
||||
const installedPluginLines = lines.slice(
|
||||
installedPluginStart + 1,
|
||||
installedPluginEnd
|
||||
);
|
||||
|
||||
expect(installedPluginLines.some((x) => x.includes(`${bold('nx')}`)));
|
||||
expect(
|
||||
installedPluginLines.some((x) => x.includes(`${bold('@nrwl/nest')}`))
|
||||
);
|
||||
|
||||
output = runEncapsulatedNx('list @nrwl/nest');
|
||||
expect(output).toContain('Capabilities in @nrwl/nest');
|
||||
});
|
||||
|
||||
it('should work with basic generators', () => {
|
||||
updateJson<NxJsonConfiguration>('nx.json', (j) => {
|
||||
j.installation.plugins ??= {};
|
||||
j.installation.plugins['@nrwl/workspace'] = getPublishedVersion();
|
||||
return j;
|
||||
});
|
||||
expect(() =>
|
||||
runEncapsulatedNx(`g npm-package ${uniq('pkg')}`)
|
||||
).not.toThrow();
|
||||
expect(() => checkFilesExist());
|
||||
});
|
||||
});
|
||||
|
||||
function assertNoRootPackages() {
|
||||
expect(() =>
|
||||
checkFilesDoNotExist(
|
||||
'node_modules',
|
||||
'package.json',
|
||||
'package-lock.json',
|
||||
'yarn-lock.json',
|
||||
'pnpm-lock.yaml'
|
||||
)
|
||||
).not.toThrow();
|
||||
}
|
||||
@@ -200,8 +200,8 @@ describe('Extra Nx Misc Tests', () => {
|
||||
runCLI(`run ${mylib}:error`);
|
||||
fail('Should error if process errors');
|
||||
} catch (e) {
|
||||
expect(e.stdout.toString()).toContain(
|
||||
'Something went wrong in run-commands - Command failed: exit 1'
|
||||
expect(e.stderr.toString()).toContain(
|
||||
'command "exit 1" exited with non-zero status code'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { NxJsonConfiguration } from '@nrwl/devkit';
|
||||
import {
|
||||
cleanupProject,
|
||||
e2eCwd,
|
||||
getPackageManagerCommand,
|
||||
getPublishedVersion,
|
||||
isNotWindows,
|
||||
newProject,
|
||||
@@ -15,8 +17,10 @@ import {
|
||||
updateFile,
|
||||
updateJson,
|
||||
} from '@nrwl/e2e/utils';
|
||||
import { renameSync } from 'fs';
|
||||
import { renameSync, writeFileSync } from 'fs';
|
||||
import { ensureDirSync } from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import { major } from 'semver';
|
||||
|
||||
describe('Nx Commands', () => {
|
||||
let proj: string;
|
||||
@@ -35,7 +39,7 @@ describe('Nx Commands', () => {
|
||||
|
||||
const s = runCLI('show projects').split('\n');
|
||||
|
||||
expect(s.length).toEqual(4);
|
||||
expect(s.length).toEqual(5);
|
||||
expect(s).toContain(app1);
|
||||
expect(s).toContain(app2);
|
||||
expect(s).toContain(`${app1}-e2e`);
|
||||
@@ -568,3 +572,65 @@ describe('migrate', () => {
|
||||
expect(output).toContain(`Migrations file 'migrations.json' doesn't exist`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('global installation', () => {
|
||||
// Additionally, installing Nx under e2eCwd like this still acts like a global install,
|
||||
// but is easier to cleanup and doesn't mess with the users PC if running tests locally.
|
||||
const globalsPath = path.join(e2eCwd, 'globals', 'node_modules', '.bin');
|
||||
|
||||
let oldPath: string;
|
||||
|
||||
beforeAll(() => {
|
||||
newProject();
|
||||
|
||||
ensureDirSync(globalsPath);
|
||||
writeFileSync(
|
||||
path.join(path.dirname(path.dirname(globalsPath)), 'package.json'),
|
||||
JSON.stringify(
|
||||
{
|
||||
dependencies: {
|
||||
nx: getPublishedVersion(),
|
||||
},
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
|
||||
runCommand(getPackageManagerCommand().install, {
|
||||
cwd: path.join(e2eCwd, 'globals'),
|
||||
});
|
||||
|
||||
// Update process.path to have access to modules installed in e2ecwd/node_modules/.bin,
|
||||
// this lets commands run things like `nx`. We put it at the beginning so they are found first.
|
||||
oldPath = process.env.PATH;
|
||||
process.env.PATH = globalsPath + path.delimiter + process.env.PATH;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.env.PATH = oldPath;
|
||||
});
|
||||
|
||||
it('should invoke Nx commands from local repo', () => {
|
||||
const nxJsContents = readFile('node_modules/nx/bin/nx.js');
|
||||
updateFile('node_modules/nx/bin/nx.js', `console.log('local install');`);
|
||||
let output: string;
|
||||
expect(() => {
|
||||
output = runCommand(`nx show projects`);
|
||||
}).not.toThrow();
|
||||
expect(output).toContain('local install');
|
||||
updateFile('node_modules/nx/bin/nx.js', nxJsContents);
|
||||
});
|
||||
|
||||
it('should warn if local Nx has higher major version', () => {
|
||||
updateJson('node_modules/nx/package.json', (json) => {
|
||||
json.version = `${major(getPublishedVersion()) + 2}.0.0`;
|
||||
return json;
|
||||
});
|
||||
let output: string;
|
||||
expect(() => {
|
||||
output = runCommand(`nx show projects`);
|
||||
}).not.toThrow();
|
||||
expect(output).toContain('Its time to update Nx');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
import type { NxJsonConfiguration } from '@nrwl/devkit';
|
||||
import {
|
||||
newWrappedNxWorkspace,
|
||||
updateFile,
|
||||
updateJson,
|
||||
checkFilesDoNotExist,
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
getPublishedVersion,
|
||||
uniq,
|
||||
readJson,
|
||||
readFile,
|
||||
} from '@nrwl/e2e/utils';
|
||||
import { bold } from 'chalk';
|
||||
|
||||
describe('nx wrapper / .nx installation', () => {
|
||||
let runNxWrapper: ReturnType<typeof newWrappedNxWorkspace>;
|
||||
|
||||
beforeAll(() => {
|
||||
runNxWrapper = newWrappedNxWorkspace();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cleanupProject({
|
||||
skipReset: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should support running targets in a repo with .nx', () => {
|
||||
updateFile(
|
||||
'projects/a/project.json',
|
||||
JSON.stringify({
|
||||
name: 'a',
|
||||
targets: {
|
||||
echo: {
|
||||
command: `echo 'Hello from A'`,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
updateJson<NxJsonConfiguration>('nx.json', (json) => {
|
||||
json.tasksRunnerOptions.default.options.cacheableOperations = ['echo'];
|
||||
json.installation.plugins = {
|
||||
'@nrwl/nest': getPublishedVersion(),
|
||||
};
|
||||
return json;
|
||||
});
|
||||
|
||||
expect(runNxWrapper('echo a')).toContain('Hello from A');
|
||||
|
||||
expect(runNxWrapper('echo a')).toContain(
|
||||
'Nx read the output from the cache instead of running the command for 1 out of 1 tasks'
|
||||
);
|
||||
|
||||
assertNoRootPackages();
|
||||
expect(() =>
|
||||
checkFilesExist(
|
||||
'.nx/installation/package.json',
|
||||
'.nx/installation/package-lock.json',
|
||||
'.nx/cache/terminalOutputs'
|
||||
)
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should work with nx report', () => {
|
||||
const output = runNxWrapper('report');
|
||||
expect(output).toMatch(new RegExp(`nx.*:.*${getPublishedVersion()}`));
|
||||
expect(output).toMatch(
|
||||
new RegExp(`@nrwl/nest.*:.*${getPublishedVersion()}`)
|
||||
);
|
||||
expect(output).not.toContain('@nrwl/express');
|
||||
});
|
||||
|
||||
it('should work with nx list', () => {
|
||||
let output = runNxWrapper('list');
|
||||
const lines = output.split('\n');
|
||||
const installedPluginStart = lines.findIndex((l) =>
|
||||
l.includes('Installed plugins')
|
||||
);
|
||||
const installedPluginEnd = lines.findIndex((l) =>
|
||||
l.includes('Also available')
|
||||
);
|
||||
const installedPluginLines = lines.slice(
|
||||
installedPluginStart + 1,
|
||||
installedPluginEnd
|
||||
);
|
||||
|
||||
expect(installedPluginLines.some((x) => x.includes(`${bold('nx')}`)));
|
||||
expect(
|
||||
installedPluginLines.some((x) => x.includes(`${bold('@nrwl/nest')}`))
|
||||
);
|
||||
|
||||
output = runNxWrapper('list @nrwl/nest');
|
||||
expect(output).toContain('Capabilities in @nrwl/nest');
|
||||
});
|
||||
|
||||
it('should work with basic generators', () => {
|
||||
updateJson<NxJsonConfiguration>('nx.json', (j) => {
|
||||
j.installation.plugins ??= {};
|
||||
j.installation.plugins['@nrwl/workspace'] = getPublishedVersion();
|
||||
return j;
|
||||
});
|
||||
expect(() => runNxWrapper(`g npm-package ${uniq('pkg')}`)).not.toThrow();
|
||||
expect(() => checkFilesExist());
|
||||
});
|
||||
|
||||
it('should work with migrate', () => {
|
||||
updateFile(
|
||||
`.nx/installation/node_modules/migrate-parent-package/package.json`,
|
||||
JSON.stringify({
|
||||
version: '1.0.0',
|
||||
name: 'migrate-parent-package',
|
||||
'nx-migrations': './migrations.json',
|
||||
})
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`.nx/installation/node_modules/migrate-parent-package/migrations.json`,
|
||||
JSON.stringify({
|
||||
schematics: {
|
||||
run11: {
|
||||
version: '1.1.0',
|
||||
description: '1.1.0',
|
||||
factory: './run11',
|
||||
},
|
||||
run20: {
|
||||
version: '2.0.0',
|
||||
description: '2.0.0',
|
||||
implementation: './run20',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`.nx/installation/node_modules/migrate-parent-package/run11.js`,
|
||||
`
|
||||
exports.default = function default_1() {
|
||||
return function(host) {
|
||||
host.create('file-11', 'content11')
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`.nx/installation/node_modules/migrate-parent-package/run20.js`,
|
||||
`
|
||||
exports.default = function (host) {
|
||||
host.write('file-20', 'content20')
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`.nx/installation/node_modules/migrate-child-package/package.json`,
|
||||
JSON.stringify({
|
||||
name: 'migrate-child-package',
|
||||
version: '1.0.0',
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* Patches migration fetcher to load in migrations that we are using to test.
|
||||
*/
|
||||
updateFile(
|
||||
'.nx/installation/node_modules/nx/src/command-line/migrate.js',
|
||||
(content) => {
|
||||
const start = content.indexOf('// testing-fetch-start');
|
||||
const end = content.indexOf('// testing-fetch-end');
|
||||
|
||||
const before = content.substring(0, start);
|
||||
const after = content.substring(end);
|
||||
const newFetch = `
|
||||
function createFetcher(logger) {
|
||||
return function fetch(packageName) {
|
||||
if (packageName === 'migrate-parent-package') {
|
||||
return Promise.resolve({
|
||||
version: '2.0.0',
|
||||
generators: {
|
||||
'run11': {
|
||||
version: '1.1.0'
|
||||
},
|
||||
'run20': {
|
||||
version: '2.0.0',
|
||||
cli: 'nx'
|
||||
}
|
||||
},
|
||||
packageJsonUpdates: {
|
||||
'run-11': {version: '1.1.0', packages: {
|
||||
'migrate-child-package': {version: '9.0.0', alwaysAddToPackageJson: false},
|
||||
}},
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve({version: '9.0.0'});
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
return `${before}${newFetch}${after}`;
|
||||
}
|
||||
);
|
||||
|
||||
updateJson('nx.json', (j: NxJsonConfiguration) => {
|
||||
j.installation = {
|
||||
version: getPublishedVersion(),
|
||||
plugins: {
|
||||
'migrate-child-package': '1.0.0',
|
||||
},
|
||||
};
|
||||
return j;
|
||||
});
|
||||
runNxWrapper(
|
||||
'migrate migrate-parent-package@2.0.0 --from="migrate-parent-package@1.0.0"',
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
NX_MIGRATE_SKIP_INSTALL: 'true',
|
||||
NX_MIGRATE_USE_LOCAL: 'true',
|
||||
NX_WRAPPER_SKIP_INSTALL: 'true',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const nxJson: NxJsonConfiguration = readJson(`nx.json`);
|
||||
expect(nxJson.installation.plugins['migrate-child-package']).toEqual(
|
||||
'9.0.0'
|
||||
);
|
||||
// creates migrations.json
|
||||
const migrationsJson = readJson(`migrations.json`);
|
||||
expect(migrationsJson).toEqual({
|
||||
migrations: [
|
||||
{
|
||||
package: 'migrate-parent-package',
|
||||
version: '1.1.0',
|
||||
name: 'run11',
|
||||
},
|
||||
{
|
||||
package: 'migrate-parent-package',
|
||||
version: '2.0.0',
|
||||
name: 'run20',
|
||||
cli: 'nx',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// runs migrations
|
||||
runNxWrapper('migrate --run-migrations=migrations.json', {
|
||||
env: {
|
||||
...process.env,
|
||||
NX_MIGRATE_SKIP_INSTALL: 'true',
|
||||
NX_MIGRATE_USE_LOCAL: 'true',
|
||||
NX_WRAPPER_SKIP_INSTALL: 'true',
|
||||
},
|
||||
});
|
||||
expect(readFile('file-11')).toEqual('content11');
|
||||
expect(readFile('file-20')).toEqual('content20');
|
||||
});
|
||||
});
|
||||
|
||||
function assertNoRootPackages() {
|
||||
expect(() =>
|
||||
checkFilesDoNotExist(
|
||||
'node_modules',
|
||||
'package.json',
|
||||
'package-lock.json',
|
||||
'yarn-lock.json',
|
||||
'pnpm-lock.yaml'
|
||||
)
|
||||
).not.toThrow();
|
||||
}
|
||||
@@ -73,14 +73,12 @@ describe('Nx Plugin', () => {
|
||||
// doesn't use the collection we are building
|
||||
// we should change it to point to the right collection using relative path
|
||||
// TODO: Re-enable this to work with pnpm
|
||||
xit(`should run the plugin's e2e tests`, async () => {
|
||||
if (isNotWindows()) {
|
||||
const plugin = uniq('plugin-name');
|
||||
runCLI(`generate @nrwl/nx-plugin:plugin ${plugin} --linter=eslint`);
|
||||
const e2eResults = runCLI(`e2e ${plugin}-e2e`);
|
||||
expect(e2eResults).toContain('Successfully ran target e2e');
|
||||
expect(await killPorts()).toBeTruthy();
|
||||
}
|
||||
it(`should run the plugin's e2e tests`, async () => {
|
||||
const plugin = uniq('plugin-name');
|
||||
runCLI(`generate @nrwl/nx-plugin:plugin ${plugin} --linter=eslint`);
|
||||
const e2eResults = runCLI(`e2e ${plugin}-e2e`);
|
||||
expect(e2eResults).toContain('Successfully ran target e2e');
|
||||
expect(await killPorts()).toBeTruthy();
|
||||
}, 250000);
|
||||
|
||||
it('should be able to generate a migration', async () => {
|
||||
|
||||
@@ -180,8 +180,6 @@ describe('Nx Affected and Graph Tests', () => {
|
||||
mylib = uniq('mylib');
|
||||
const nxJson: NxJsonConfiguration = readJson('nx.json');
|
||||
|
||||
delete nxJson.implicitDependencies;
|
||||
|
||||
updateFile('nx.json', JSON.stringify(nxJson));
|
||||
runCommand(`git init`);
|
||||
runCommand(`git config user.email "test@test.com"`);
|
||||
@@ -266,6 +264,24 @@ describe('Nx Affected and Graph Tests', () => {
|
||||
implicitDependencies: [],
|
||||
}));
|
||||
});
|
||||
|
||||
it('should handle file renames', () => {
|
||||
generateAll();
|
||||
|
||||
// Move file
|
||||
updateFile(
|
||||
`apps/${myapp2}/src/index.html`,
|
||||
readFile(`apps/${myapp}/src/index.html`)
|
||||
);
|
||||
removeFile(`apps/${myapp}/src/index.html`);
|
||||
|
||||
const affectedProjects = runCLI(
|
||||
'print-affected --uncommitted --select projects'
|
||||
).split(', ');
|
||||
|
||||
expect(affectedProjects).toContain(myapp);
|
||||
expect(affectedProjects).toContain(myapp2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('print-affected', () => {
|
||||
|
||||
@@ -439,9 +439,9 @@ describe('Nx Running Tests', () => {
|
||||
const libD = uniq('libd-rand');
|
||||
|
||||
runCLI(`generate @nrwl/web:app ${appA}`);
|
||||
runCLI(`generate @nrwl/js:lib ${libA} --buildable --defaults`);
|
||||
runCLI(`generate @nrwl/js:lib ${libB} --buildable --defaults`);
|
||||
runCLI(`generate @nrwl/js:lib ${libC} --buildable --defaults`);
|
||||
runCLI(`generate @nrwl/js:lib ${libA} --bundler=tsc --defaults`);
|
||||
runCLI(`generate @nrwl/js:lib ${libB} --bundler=tsc --defaults`);
|
||||
runCLI(`generate @nrwl/js:lib ${libC} --bundler=tsc --defaults`);
|
||||
runCLI(`generate @nrwl/node:lib ${libD} --defaults`);
|
||||
|
||||
// libA depends on libC
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
checkFilesDoNotExist,
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
getSize,
|
||||
killPorts,
|
||||
newProject,
|
||||
@@ -107,7 +108,7 @@ describe('Build React libraries and apps', () => {
|
||||
|
||||
afterEach(() => {
|
||||
killPorts();
|
||||
// cleanupProject();
|
||||
cleanupProject();
|
||||
});
|
||||
|
||||
describe('Buildable libraries', () => {
|
||||
|
||||
@@ -15,7 +15,7 @@ describe('Rollup Plugin', () => {
|
||||
|
||||
it('should be able to setup project to build node programs with rollup and different compilers', async () => {
|
||||
const myPkg = uniq('my-pkg');
|
||||
runCLI(`generate @nrwl/js:lib ${myPkg} --buildable=false`);
|
||||
runCLI(`generate @nrwl/js:lib ${myPkg} --bundler=none`);
|
||||
updateFile(`libs/${myPkg}/src/index.ts`, `console.log('Hello');\n`);
|
||||
|
||||
// babel (default)
|
||||
|
||||
@@ -0,0 +1,463 @@
|
||||
import { output, PackageManager } from '@nrwl/devkit';
|
||||
import { packageInstall, tmpProjPath } from './create-project-utils';
|
||||
import {
|
||||
detectPackageManager,
|
||||
ensureCypressInstallation,
|
||||
getNpmMajorVersion,
|
||||
getPublishedVersion,
|
||||
getStrippedEnvironmentVariables,
|
||||
} from './get-env-info';
|
||||
import { TargetConfiguration } from '@nrwl/devkit';
|
||||
import { ChildProcess, exec, execSync, ExecSyncOptions } from 'child_process';
|
||||
import { join } from 'path';
|
||||
import { check as portCheck } from 'tcp-port-used';
|
||||
import * as isCI from 'is-ci';
|
||||
import { Workspaces } from '../../packages/nx/src/config/workspaces';
|
||||
import { isVerbose } from './get-env-info';
|
||||
import { updateFile } from './file-utils';
|
||||
import { logError, logInfo, logSuccess, stripConsoleColors } from './log-utils';
|
||||
|
||||
export const kill = require('kill-port');
|
||||
|
||||
export interface RunCmdOpts {
|
||||
silenceError?: boolean;
|
||||
env?: Record<string, string | undefined>;
|
||||
cwd?: string;
|
||||
silent?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets maxWorkers in CI on all projects that require it
|
||||
* so that it doesn't try to run it with 34 workers
|
||||
*
|
||||
* maxWorkers required for: node, web, jest
|
||||
*/
|
||||
export function setMaxWorkers() {
|
||||
if (isCI) {
|
||||
const ws = new Workspaces(tmpProjPath());
|
||||
const projectsConfigurations = ws.readProjectsConfigurations();
|
||||
|
||||
Object.keys(projectsConfigurations.projects).forEach((appName) => {
|
||||
let project = projectsConfigurations.projects[appName];
|
||||
const { build } = project.targets as {
|
||||
[targetName: string]: TargetConfiguration<any>;
|
||||
};
|
||||
|
||||
if (!build) {
|
||||
return;
|
||||
}
|
||||
|
||||
const executor = build.executor as string;
|
||||
if (
|
||||
executor.startsWith('@nrwl/node') ||
|
||||
executor.startsWith('@nrwl/web') ||
|
||||
executor.startsWith('@nrwl/jest')
|
||||
) {
|
||||
build.options.maxWorkers = 4;
|
||||
}
|
||||
|
||||
updateFile(
|
||||
join(project.root, 'project.json'),
|
||||
JSON.stringify(project, null, 2)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function runCommand(
|
||||
command: string,
|
||||
options?: Partial<ExecSyncOptions> & { failOnError?: boolean }
|
||||
): string {
|
||||
const { failOnError, ...childProcessOptions } = options ?? {};
|
||||
try {
|
||||
const r = execSync(`${command}${isVerbose() ? ' --verbose' : ''}`, {
|
||||
cwd: tmpProjPath(),
|
||||
stdio: 'pipe',
|
||||
env: {
|
||||
...getStrippedEnvironmentVariables(),
|
||||
...childProcessOptions?.env,
|
||||
FORCE_COLOR: 'false',
|
||||
},
|
||||
encoding: 'utf-8',
|
||||
...childProcessOptions,
|
||||
});
|
||||
|
||||
if (isVerbose()) {
|
||||
output.log({
|
||||
title: `Command: ${command}`,
|
||||
bodyLines: [r as string],
|
||||
color: 'green',
|
||||
});
|
||||
}
|
||||
|
||||
return r as string;
|
||||
} catch (e) {
|
||||
// this is intentional
|
||||
// npm ls fails if package is not found
|
||||
logError(`Original command: ${command}`, `${e.stdout}\n\n${e.stderr}`);
|
||||
if (!failOnError && (e.stdout || e.stderr)) {
|
||||
return e.stdout + e.stderr;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export function getPackageManagerCommand({
|
||||
path = tmpProjPath(),
|
||||
packageManager = detectPackageManager(path),
|
||||
} = {}): {
|
||||
createWorkspace: string;
|
||||
run: (script: string, args: string) => string;
|
||||
runNx: string;
|
||||
runNxSilent: string;
|
||||
runUninstalledPackage: string;
|
||||
install: string;
|
||||
ciInstall: string;
|
||||
addProd: string;
|
||||
addDev: string;
|
||||
list: string;
|
||||
runLerna: string;
|
||||
} {
|
||||
const npmMajorVersion = getNpmMajorVersion();
|
||||
const publishedVersion = getPublishedVersion();
|
||||
|
||||
return {
|
||||
npm: {
|
||||
createWorkspace: `npx ${
|
||||
+npmMajorVersion >= 7 ? '--yes' : ''
|
||||
} create-nx-workspace@${publishedVersion}`,
|
||||
run: (script: string, args: string) => `npm run ${script} -- ${args}`,
|
||||
runNx: `npx nx`,
|
||||
runNxSilent: `npx nx`,
|
||||
runUninstalledPackage: `npx --yes`,
|
||||
install: 'npm install',
|
||||
ciInstall: 'npm ci',
|
||||
addProd: `npm install --legacy-peer-deps`,
|
||||
addDev: `npm install --legacy-peer-deps -D`,
|
||||
list: 'npm ls --depth 10',
|
||||
runLerna: `npx lerna`,
|
||||
},
|
||||
yarn: {
|
||||
// `yarn create nx-workspace` is failing due to wrong global path
|
||||
createWorkspace: `yarn global add create-nx-workspace@${publishedVersion} && create-nx-workspace`,
|
||||
run: (script: string, args: string) => `yarn ${script} ${args}`,
|
||||
runNx: `yarn nx`,
|
||||
runNxSilent: `yarn --silent nx`,
|
||||
runUninstalledPackage: 'npx --yes',
|
||||
install: 'yarn',
|
||||
ciInstall: 'yarn --frozen-lockfile',
|
||||
addProd: `yarn add`,
|
||||
addDev: `yarn add -D`,
|
||||
list: 'npm ls --depth 10',
|
||||
runLerna: `yarn lerna`,
|
||||
},
|
||||
// Pnpm 3.5+ adds nx to
|
||||
pnpm: {
|
||||
createWorkspace: `pnpm dlx create-nx-workspace@${publishedVersion}`,
|
||||
run: (script: string, args: string) => `pnpm run ${script} -- ${args}`,
|
||||
runNx: `pnpm exec nx`,
|
||||
runNxSilent: `pnpm exec nx`,
|
||||
runUninstalledPackage: 'pnpm dlx',
|
||||
install: 'pnpm i',
|
||||
ciInstall: 'pnpm install --frozen-lockfile',
|
||||
addProd: `pnpm add`,
|
||||
addDev: `pnpm add -D`,
|
||||
list: 'npm ls --depth 10',
|
||||
runLerna: `pnpm exec lerna`,
|
||||
},
|
||||
}[packageManager.trim() as PackageManager];
|
||||
}
|
||||
|
||||
export function runCypressTests() {
|
||||
if (process.env.NX_E2E_RUN_CYPRESS === 'true') {
|
||||
ensureCypressInstallation();
|
||||
return true;
|
||||
} else {
|
||||
console.warn(
|
||||
'Not running Cypress because NX_E2E_RUN_CYPRESS is not set to true.'
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function runCommandAsync(
|
||||
command: string,
|
||||
opts: RunCmdOpts = {
|
||||
silenceError: false,
|
||||
env: process['env'],
|
||||
}
|
||||
): Promise<{ stdout: string; stderr: string; combinedOutput: string }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(
|
||||
command,
|
||||
{
|
||||
cwd: opts.cwd || tmpProjPath(),
|
||||
env: {
|
||||
CI: 'true',
|
||||
...(opts.env || getStrippedEnvironmentVariables()),
|
||||
FORCE_COLOR: 'false',
|
||||
},
|
||||
encoding: 'utf-8',
|
||||
},
|
||||
(err, stdout, stderr) => {
|
||||
if (!opts.silenceError && err) {
|
||||
reject(err);
|
||||
}
|
||||
resolve({
|
||||
stdout: stripConsoleColors(stdout),
|
||||
stderr: stripConsoleColors(stderr),
|
||||
combinedOutput: stripConsoleColors(`${stdout}${stderr}`),
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function runCommandUntil(
|
||||
command: string,
|
||||
criteria: (output: string) => boolean
|
||||
): Promise<ChildProcess> {
|
||||
const pm = getPackageManagerCommand();
|
||||
const p = exec(`${pm.runNx} ${command}`, {
|
||||
cwd: tmpProjPath(),
|
||||
encoding: 'utf-8',
|
||||
env: {
|
||||
CI: 'true',
|
||||
...getStrippedEnvironmentVariables(),
|
||||
FORCE_COLOR: 'false',
|
||||
},
|
||||
});
|
||||
return new Promise((res, rej) => {
|
||||
let output = '';
|
||||
let complete = false;
|
||||
|
||||
function checkCriteria(c) {
|
||||
output += c.toString();
|
||||
if (criteria(stripConsoleColors(output)) && !complete) {
|
||||
complete = true;
|
||||
res(p);
|
||||
}
|
||||
}
|
||||
|
||||
p.stdout?.on('data', checkCriteria);
|
||||
p.stderr?.on('data', checkCriteria);
|
||||
p.on('exit', (code) => {
|
||||
if (!complete) {
|
||||
rej(`Exited with ${code}`);
|
||||
} else {
|
||||
res(p);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function runCLIAsync(
|
||||
command: string,
|
||||
opts: RunCmdOpts = {
|
||||
silenceError: false,
|
||||
env: getStrippedEnvironmentVariables(),
|
||||
silent: false,
|
||||
}
|
||||
): Promise<{ stdout: string; stderr: string; combinedOutput: string }> {
|
||||
const pm = getPackageManagerCommand();
|
||||
return runCommandAsync(
|
||||
`${opts.silent ? pm.runNxSilent : pm.runNx} ${command}`,
|
||||
opts
|
||||
);
|
||||
}
|
||||
|
||||
export function runNgAdd(
|
||||
packageName: string,
|
||||
command?: string,
|
||||
version: string = getPublishedVersion(),
|
||||
opts: RunCmdOpts = {
|
||||
silenceError: false,
|
||||
env: undefined,
|
||||
cwd: tmpProjPath(),
|
||||
}
|
||||
): string {
|
||||
try {
|
||||
const pmc = getPackageManagerCommand();
|
||||
packageInstall(packageName, undefined, version);
|
||||
const fullCommand = pmc.run(
|
||||
`ng g ${packageName}:ng-add`,
|
||||
`${command}${isVerbose() ? ' --verbose' : ''}` ?? ''
|
||||
);
|
||||
const result = execSync(fullCommand, {
|
||||
cwd: tmpProjPath(),
|
||||
stdio: 'pipe',
|
||||
env: { ...(opts.env || getStrippedEnvironmentVariables()) },
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
const r = stripConsoleColors(result);
|
||||
|
||||
if (isVerbose()) {
|
||||
output.log({
|
||||
title: `Original command: ${fullCommand}`,
|
||||
bodyLines: [result as string],
|
||||
color: 'green',
|
||||
});
|
||||
}
|
||||
|
||||
return r;
|
||||
} catch (e) {
|
||||
if (opts.silenceError) {
|
||||
return e.stdout;
|
||||
} else {
|
||||
logError(`Ng Add failed: ${command}`, `${e.stdout}\n\n${e.stderr}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function runCLI(
|
||||
command: string,
|
||||
opts: RunCmdOpts = {
|
||||
silenceError: false,
|
||||
env: undefined,
|
||||
}
|
||||
): string {
|
||||
try {
|
||||
const pm = getPackageManagerCommand();
|
||||
const logs = execSync(
|
||||
`${pm.runNx} ${command} ${isVerbose() ? ' --verbose' : ''}`,
|
||||
{
|
||||
cwd: opts.cwd || tmpProjPath(),
|
||||
env: {
|
||||
CI: 'true',
|
||||
...getStrippedEnvironmentVariables(),
|
||||
...opts.env,
|
||||
},
|
||||
encoding: 'utf-8',
|
||||
stdio: 'pipe',
|
||||
maxBuffer: 50 * 1024 * 1024,
|
||||
}
|
||||
);
|
||||
|
||||
if (isVerbose()) {
|
||||
output.log({
|
||||
title: `Original command: ${command}`,
|
||||
bodyLines: [logs as string],
|
||||
color: 'green',
|
||||
});
|
||||
}
|
||||
|
||||
const r = stripConsoleColors(logs);
|
||||
const needsMaxWorkers = /g.*(express|nest|node|web|react):app.*/;
|
||||
if (needsMaxWorkers.test(command)) {
|
||||
setMaxWorkers();
|
||||
}
|
||||
|
||||
return r;
|
||||
} catch (e) {
|
||||
if (opts.silenceError) {
|
||||
return stripConsoleColors(e.stdout + e.stderr);
|
||||
} else {
|
||||
logError(`Original command: ${command}`, `${e.stdout}\n\n${e.stderr}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function runLernaCLI(
|
||||
command: string,
|
||||
opts: RunCmdOpts = {
|
||||
silenceError: false,
|
||||
env: undefined,
|
||||
}
|
||||
): string {
|
||||
try {
|
||||
const pm = getPackageManagerCommand();
|
||||
const fullCommand = `${pm.runLerna} ${command}${
|
||||
isVerbose() ? ' --verbose' : ''
|
||||
}`;
|
||||
const logs = execSync(fullCommand, {
|
||||
cwd: opts.cwd || tmpProjPath(),
|
||||
env: {
|
||||
CI: 'true',
|
||||
...(opts.env || getStrippedEnvironmentVariables()),
|
||||
},
|
||||
encoding: 'utf-8',
|
||||
stdio: 'pipe',
|
||||
maxBuffer: 50 * 1024 * 1024,
|
||||
});
|
||||
|
||||
if (isVerbose()) {
|
||||
output.log({
|
||||
title: `Original command: ${fullCommand}`,
|
||||
bodyLines: [logs as string],
|
||||
color: 'green',
|
||||
});
|
||||
}
|
||||
const r = stripConsoleColors(logs);
|
||||
|
||||
return r;
|
||||
} catch (e) {
|
||||
if (opts.silenceError) {
|
||||
return stripConsoleColors(e.stdout + e.stderr);
|
||||
} else {
|
||||
logError(`Original command: ${command}`, `${e.stdout}\n\n${e.stderr}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const KILL_PORT_DELAY = 5000;
|
||||
|
||||
export async function killPort(port: number): Promise<boolean> {
|
||||
if (await portCheck(port)) {
|
||||
try {
|
||||
logInfo(`Attempting to close port ${port}`);
|
||||
await kill(port);
|
||||
await new Promise<void>((resolve) =>
|
||||
setTimeout(() => resolve(), KILL_PORT_DELAY)
|
||||
);
|
||||
if (await portCheck(port)) {
|
||||
logError(`Port ${port} still open`);
|
||||
} else {
|
||||
logSuccess(`Port ${port} successfully closed`);
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
logError(`Port ${port} closing failed`);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export async function killPorts(port?: number): Promise<boolean> {
|
||||
return port
|
||||
? await killPort(port)
|
||||
: (await killPort(3333)) && (await killPort(4200));
|
||||
}
|
||||
|
||||
export function waitUntil(
|
||||
predicate: () => boolean,
|
||||
opts: { timeout: number; ms: number; allowError?: boolean } = {
|
||||
timeout: 5000,
|
||||
ms: 50,
|
||||
}
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const t = setInterval(() => {
|
||||
const run = () => {};
|
||||
try {
|
||||
run();
|
||||
if (predicate()) {
|
||||
clearInterval(t);
|
||||
resolve();
|
||||
}
|
||||
} catch (e) {
|
||||
if (opts.allowError) reject(e);
|
||||
}
|
||||
}, opts.ms);
|
||||
|
||||
setTimeout(() => {
|
||||
clearInterval(t);
|
||||
reject(new Error(`Timed out waiting for condition to return true`));
|
||||
}, opts.timeout);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,481 @@
|
||||
import { copySync, ensureDirSync, moveSync, removeSync } from 'fs-extra';
|
||||
import {
|
||||
createFile,
|
||||
directoryExists,
|
||||
tmpBackupProjPath,
|
||||
updateFile,
|
||||
updateJson,
|
||||
} from './file-utils';
|
||||
import {
|
||||
e2eCwd,
|
||||
getLatestLernaVersion,
|
||||
getNpmMajorVersion,
|
||||
getPublishedVersion,
|
||||
getSelectedPackageManager,
|
||||
} from './get-env-info';
|
||||
import * as isCI from 'is-ci';
|
||||
|
||||
import { angularCliVersion } from '@nrwl/workspace/src/utils/versions';
|
||||
import { dump } from '@zkochan/js-yaml';
|
||||
import { execSync, ExecSyncOptions } from 'child_process';
|
||||
|
||||
import { isVerbose } from './get-env-info';
|
||||
import { logError, logInfo } from './log-utils';
|
||||
import {
|
||||
getPackageManagerCommand,
|
||||
runCLI,
|
||||
RunCmdOpts,
|
||||
runCommand,
|
||||
} from './command-utils';
|
||||
import { output } from '@nrwl/devkit';
|
||||
|
||||
let projName: string;
|
||||
|
||||
/**
|
||||
* Sets up a new project in the temporary project path
|
||||
* for the currently selected CLI.
|
||||
*/
|
||||
export function newProject({
|
||||
name = uniq('proj'),
|
||||
packageManager = getSelectedPackageManager(),
|
||||
} = {}): string {
|
||||
try {
|
||||
const useBackupProject = packageManager !== 'pnpm';
|
||||
const projScope = useBackupProject ? 'proj' : name;
|
||||
|
||||
if (!useBackupProject || !directoryExists(tmpBackupProjPath())) {
|
||||
runCreateWorkspace(projScope, {
|
||||
preset: 'empty',
|
||||
packageManager,
|
||||
});
|
||||
|
||||
// Temporary hack to prevent installing with `--frozen-lockfile`
|
||||
if (isCI && packageManager === 'pnpm') {
|
||||
updateFile(
|
||||
'.npmrc',
|
||||
'prefer-frozen-lockfile=false\nstrict-peer-dependencies=false\nauto-install-peers=true'
|
||||
);
|
||||
}
|
||||
|
||||
// TODO(jack): we should tag the projects (e.g. tags: ['package']) and filter from that rather than hard-code packages.
|
||||
const packages = [
|
||||
`@nrwl/angular`,
|
||||
`@nrwl/eslint-plugin-nx`,
|
||||
`@nrwl/express`,
|
||||
`@nrwl/esbuild`,
|
||||
`@nrwl/jest`,
|
||||
`@nrwl/js`,
|
||||
`@nrwl/linter`,
|
||||
`@nrwl/nest`,
|
||||
`@nrwl/next`,
|
||||
`@nrwl/node`,
|
||||
`@nrwl/nx-plugin`,
|
||||
`@nrwl/rollup`,
|
||||
`@nrwl/react`,
|
||||
`@nrwl/storybook`,
|
||||
`@nrwl/vite`,
|
||||
`@nrwl/web`,
|
||||
`@nrwl/webpack`,
|
||||
`@nrwl/react-native`,
|
||||
`@nrwl/expo`,
|
||||
];
|
||||
packageInstall(packages.join(` `), projScope);
|
||||
|
||||
if (useBackupProject) {
|
||||
// stop the daemon
|
||||
execSync('nx reset', {
|
||||
cwd: `${e2eCwd}/proj`,
|
||||
stdio: isVerbose() ? 'inherit' : 'pipe',
|
||||
});
|
||||
|
||||
moveSync(`${e2eCwd}/proj`, `${tmpBackupProjPath()}`);
|
||||
}
|
||||
}
|
||||
projName = name;
|
||||
if (useBackupProject) {
|
||||
copySync(`${tmpBackupProjPath()}`, `${tmpProjPath()}`);
|
||||
}
|
||||
|
||||
if (isVerbose()) {
|
||||
logInfo(`NX`, `E2E test is creating a project: ${tmpProjPath()}`);
|
||||
}
|
||||
return projScope;
|
||||
} catch (e) {
|
||||
logError(`Failed to set up project for e2e tests.`, e.message);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export function runCreateWorkspace(
|
||||
name: string,
|
||||
{
|
||||
preset,
|
||||
appName,
|
||||
style,
|
||||
base,
|
||||
packageManager,
|
||||
extraArgs,
|
||||
ci,
|
||||
useDetectedPm = false,
|
||||
cwd = e2eCwd,
|
||||
bundler,
|
||||
routing,
|
||||
standaloneApi,
|
||||
}: {
|
||||
preset: string;
|
||||
appName?: string;
|
||||
style?: string;
|
||||
base?: string;
|
||||
packageManager?: 'npm' | 'yarn' | 'pnpm';
|
||||
extraArgs?: string;
|
||||
ci?: 'azure' | 'github' | 'circleci';
|
||||
useDetectedPm?: boolean;
|
||||
cwd?: string;
|
||||
bundler?: 'webpack' | 'vite';
|
||||
standaloneApi?: boolean;
|
||||
routing?: boolean;
|
||||
}
|
||||
) {
|
||||
projName = name;
|
||||
|
||||
const pm = getPackageManagerCommand({ packageManager });
|
||||
|
||||
let command = `${pm.createWorkspace} ${name} --preset=${preset} --no-nxCloud --no-interactive`;
|
||||
if (appName) {
|
||||
command += ` --appName=${appName}`;
|
||||
}
|
||||
if (style) {
|
||||
command += ` --style=${style}`;
|
||||
}
|
||||
if (ci) {
|
||||
command += ` --ci=${ci}`;
|
||||
}
|
||||
|
||||
if (bundler) {
|
||||
command += ` --bundler=${bundler}`;
|
||||
}
|
||||
|
||||
if (standaloneApi !== undefined) {
|
||||
command += ` --standaloneApi=${standaloneApi}`;
|
||||
}
|
||||
|
||||
if (routing !== undefined) {
|
||||
command += ` --routing=${routing}`;
|
||||
}
|
||||
|
||||
if (base) {
|
||||
command += ` --defaultBase="${base}"`;
|
||||
}
|
||||
|
||||
if (packageManager && !useDetectedPm) {
|
||||
command += ` --package-manager=${packageManager}`;
|
||||
}
|
||||
|
||||
if (extraArgs) {
|
||||
command += ` ${extraArgs}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const create = execSync(`${command}${isVerbose() ? ' --verbose' : ''}`, {
|
||||
cwd,
|
||||
stdio: 'pipe',
|
||||
env: { CI: 'true', ...process.env },
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
if (isVerbose()) {
|
||||
output.log({
|
||||
title: `Command: ${command}`,
|
||||
bodyLines: [create as string],
|
||||
color: 'green',
|
||||
});
|
||||
}
|
||||
|
||||
return create;
|
||||
} catch (e) {
|
||||
logError(`Original command: ${command}`, `${e.stdout}\n\n${e.stderr}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export function runCreatePlugin(
|
||||
name: string,
|
||||
{
|
||||
pluginName,
|
||||
packageManager,
|
||||
extraArgs,
|
||||
useDetectedPm = false,
|
||||
}: {
|
||||
pluginName?: string;
|
||||
packageManager?: 'npm' | 'yarn' | 'pnpm';
|
||||
extraArgs?: string;
|
||||
useDetectedPm?: boolean;
|
||||
}
|
||||
) {
|
||||
projName = name;
|
||||
|
||||
const pm = getPackageManagerCommand({ packageManager });
|
||||
|
||||
let command = `${
|
||||
pm.runUninstalledPackage
|
||||
} create-nx-plugin@${getPublishedVersion()} ${name}`;
|
||||
|
||||
if (pluginName) {
|
||||
command += ` --pluginName=${pluginName}`;
|
||||
}
|
||||
|
||||
if (packageManager && !useDetectedPm) {
|
||||
command += ` --package-manager=${packageManager}`;
|
||||
}
|
||||
|
||||
if (extraArgs) {
|
||||
command += ` ${extraArgs}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const create = execSync(`${command}${isVerbose() ? ' --verbose' : ''}`, {
|
||||
cwd: e2eCwd,
|
||||
stdio: 'pipe',
|
||||
env: process.env,
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
if (isVerbose()) {
|
||||
output.log({
|
||||
title: `Command: ${command}`,
|
||||
bodyLines: [create as string],
|
||||
color: 'green',
|
||||
});
|
||||
}
|
||||
|
||||
return create;
|
||||
} catch (e) {
|
||||
logError(`Original command: ${command}`, `${e.stdout}\n\n${e.stderr}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export function packageInstall(
|
||||
pkg: string,
|
||||
projName?: string,
|
||||
version = getPublishedVersion(),
|
||||
mode: 'dev' | 'prod' = 'dev'
|
||||
) {
|
||||
const cwd = projName ? `${e2eCwd}/${projName}` : tmpProjPath();
|
||||
const pm = getPackageManagerCommand({ path: cwd });
|
||||
const pkgsWithVersions = pkg
|
||||
.split(' ')
|
||||
.map((pgk) => `${pgk}@${version}`)
|
||||
.join(' ');
|
||||
|
||||
const command = `${
|
||||
mode === 'dev' ? pm.addDev : pm.addProd
|
||||
} ${pkgsWithVersions}${isVerbose() ? ' --verbose' : ''}`;
|
||||
|
||||
try {
|
||||
const install = execSync(
|
||||
`${mode === 'dev' ? pm.addDev : pm.addProd} ${pkgsWithVersions}${
|
||||
isVerbose() ? ' --verbose' : ''
|
||||
}`,
|
||||
{
|
||||
cwd,
|
||||
stdio: 'pipe',
|
||||
env: process.env,
|
||||
encoding: 'utf-8',
|
||||
}
|
||||
);
|
||||
|
||||
if (isVerbose()) {
|
||||
output.log({
|
||||
title: `Command: ${command}`,
|
||||
bodyLines: [install as string],
|
||||
color: 'green',
|
||||
});
|
||||
}
|
||||
|
||||
return install;
|
||||
} catch (e) {
|
||||
logError(`Original command: ${command}`, `${e.stdout}\n\n${e.stderr}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export function runNgNew(
|
||||
projectName: string,
|
||||
packageManager = getSelectedPackageManager()
|
||||
): string {
|
||||
projName = projectName;
|
||||
|
||||
const npmMajorVersion = getNpmMajorVersion();
|
||||
const command = `npx ${
|
||||
+npmMajorVersion >= 7 ? '--yes' : ''
|
||||
} @angular/cli@${angularCliVersion} new ${projectName} --package-manager=${packageManager}`;
|
||||
|
||||
return execSync(command, {
|
||||
cwd: e2eCwd,
|
||||
stdio: isVerbose() ? 'inherit' : 'pipe',
|
||||
env: process.env,
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
}
|
||||
|
||||
export function newLernaWorkspace({
|
||||
name = uniq('lerna-proj'),
|
||||
packageManager = getSelectedPackageManager(),
|
||||
} = {}): string {
|
||||
try {
|
||||
const projScope = name;
|
||||
projName = name;
|
||||
|
||||
const pm = getPackageManagerCommand({ packageManager });
|
||||
|
||||
createNonNxProjectDirectory(projScope, packageManager !== 'pnpm');
|
||||
|
||||
if (packageManager === 'pnpm') {
|
||||
updateFile(
|
||||
'pnpm-workspace.yaml',
|
||||
dump({
|
||||
packages: ['packages/*'],
|
||||
})
|
||||
);
|
||||
updateFile(
|
||||
'.npmrc',
|
||||
'prefer-frozen-lockfile=false\nstrict-peer-dependencies=false\nauto-install-peers=true'
|
||||
);
|
||||
}
|
||||
|
||||
if (isVerbose()) {
|
||||
logInfo(`NX`, `E2E test has created a lerna workspace: ${tmpProjPath()}`);
|
||||
}
|
||||
|
||||
// We need to force the real latest version of lerna to depend on our locally published version of nx
|
||||
updateJson(`package.json`, (json) => {
|
||||
// yarn workspaces can only be enabled in private projects
|
||||
json.private = true;
|
||||
|
||||
const nxVersion = getPublishedVersion();
|
||||
const overrides = {
|
||||
...json.overrides,
|
||||
nx: nxVersion,
|
||||
'@nrwl/devkit': nxVersion,
|
||||
};
|
||||
if (packageManager === 'pnpm') {
|
||||
json.pnpm = {
|
||||
...json.pnpm,
|
||||
overrides: {
|
||||
...json.pnpm?.overrides,
|
||||
...overrides,
|
||||
},
|
||||
};
|
||||
} else if (packageManager === 'yarn') {
|
||||
json.resolutions = {
|
||||
...json.resolutions,
|
||||
...overrides,
|
||||
};
|
||||
} else {
|
||||
json.overrides = overrides;
|
||||
}
|
||||
return json;
|
||||
});
|
||||
|
||||
/**
|
||||
* Again, in order to ensure we override the required version relationships, we first install lerna as a devDep
|
||||
* before running `lerna init`.
|
||||
*/
|
||||
execSync(
|
||||
`${pm.addDev} lerna@${getLatestLernaVersion()}${
|
||||
packageManager === 'pnpm'
|
||||
? ' --workspace-root'
|
||||
: packageManager === 'yarn'
|
||||
? ' -W'
|
||||
: ''
|
||||
}`,
|
||||
{
|
||||
cwd: tmpProjPath(),
|
||||
stdio: isVerbose() ? 'inherit' : 'pipe',
|
||||
env: { CI: 'true', ...process.env },
|
||||
encoding: 'utf-8',
|
||||
}
|
||||
);
|
||||
|
||||
execSync(`${pm.runLerna} init`, {
|
||||
cwd: tmpProjPath(),
|
||||
stdio: isVerbose() ? 'inherit' : 'pipe',
|
||||
env: { CI: 'true', ...process.env },
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
execSync(pm.install, {
|
||||
cwd: tmpProjPath(),
|
||||
stdio: isVerbose() ? 'inherit' : 'pipe',
|
||||
env: { CI: 'true', ...process.env },
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
return projScope;
|
||||
} catch (e) {
|
||||
logError(`Failed to set up lerna workspace for e2e tests.`, e.message);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export function newWrappedNxWorkspace({
|
||||
name = uniq('wrapped'),
|
||||
pmc = getPackageManagerCommand(),
|
||||
} = {}): (command: string, opts?: Partial<ExecSyncOptions>) => string {
|
||||
projName = name;
|
||||
ensureDirSync(tmpProjPath());
|
||||
runCommand(
|
||||
`${pmc.runUninstalledPackage} nx@latest init --use-dot-nx-installation`
|
||||
);
|
||||
return (command: string, opts: Partial<ExecSyncOptions>) => {
|
||||
if (process.platform === 'win32') {
|
||||
return runCommand(`./nx.bat ${command}`, { ...opts, failOnError: true });
|
||||
} else {
|
||||
return runCommand(`./nx ${command}`, { ...opts, failOnError: true });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function getProjectName(): string {
|
||||
return projName;
|
||||
}
|
||||
|
||||
export function tmpProjPath(path?: string) {
|
||||
return path ? `${e2eCwd}/${projName}/${path}` : `${e2eCwd}/${projName}`;
|
||||
}
|
||||
|
||||
export function createNonNxProjectDirectory(
|
||||
name = uniq('proj'),
|
||||
addWorkspaces = true
|
||||
) {
|
||||
projName = name;
|
||||
ensureDirSync(tmpProjPath());
|
||||
createFile(
|
||||
'package.json',
|
||||
JSON.stringify({
|
||||
name,
|
||||
workspaces: addWorkspaces ? ['packages/*'] : undefined,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function uniq(prefix: string) {
|
||||
return `${prefix}${Math.floor(Math.random() * 10000000)}`;
|
||||
}
|
||||
|
||||
// Useful in order to cleanup space during CI to prevent `No space left on device` exceptions
|
||||
export function cleanupProject({
|
||||
skipReset,
|
||||
...opts
|
||||
}: RunCmdOpts & { skipReset?: boolean } = {}) {
|
||||
if (isCI) {
|
||||
// Stopping the daemon is not required for tests to pass, but it cleans up background processes
|
||||
if (!skipReset) {
|
||||
runCLI('reset', opts);
|
||||
}
|
||||
try {
|
||||
removeSync(tmpProjPath());
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { parseJson } from '@nrwl/devkit';
|
||||
import {
|
||||
createFileSync,
|
||||
ensureDirSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
removeSync,
|
||||
renameSync,
|
||||
statSync,
|
||||
writeFileSync,
|
||||
} from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import { e2eCwd } from './get-env-info';
|
||||
import { tmpProjPath } from './create-project-utils';
|
||||
import { promisify } from 'util';
|
||||
import * as treeKill from 'tree-kill';
|
||||
|
||||
export function createFile(f: string, content: string = ''): void {
|
||||
const path = tmpProjPath(f);
|
||||
createFileSync(path);
|
||||
if (content) {
|
||||
updateFile(f, content);
|
||||
}
|
||||
}
|
||||
|
||||
export function updateFile(
|
||||
f: string,
|
||||
content: string | ((content: string) => string)
|
||||
): void {
|
||||
ensureDirSync(path.dirname(tmpProjPath(f)));
|
||||
if (typeof content === 'string') {
|
||||
writeFileSync(tmpProjPath(f), content);
|
||||
} else {
|
||||
writeFileSync(
|
||||
tmpProjPath(f),
|
||||
content(readFileSync(tmpProjPath(f)).toString())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function renameFile(f: string, newPath: string): void {
|
||||
ensureDirSync(path.dirname(tmpProjPath(newPath)));
|
||||
renameSync(tmpProjPath(f), tmpProjPath(newPath));
|
||||
}
|
||||
|
||||
export function checkFilesExist(...expectedFiles: string[]) {
|
||||
expectedFiles.forEach((f) => {
|
||||
const ff = f.startsWith('/') ? f : tmpProjPath(f);
|
||||
if (!exists(ff)) {
|
||||
throw new Error(`File '${ff}' does not exist`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function updateJson<T extends object = any, U extends object = T>(
|
||||
f: string,
|
||||
updater: (value: T) => U
|
||||
) {
|
||||
updateFile(f, (s) => {
|
||||
const json = JSON.parse(s);
|
||||
return JSON.stringify(updater(json), null, 2);
|
||||
});
|
||||
}
|
||||
|
||||
export function checkFilesDoNotExist(...expectedFiles: string[]) {
|
||||
expectedFiles.forEach((f) => {
|
||||
const ff = f.startsWith('/') ? f : tmpProjPath(f);
|
||||
if (exists(ff)) {
|
||||
throw new Error(`File '${ff}' should not exist`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function listFiles(dirName: string) {
|
||||
return readdirSync(tmpProjPath(dirName));
|
||||
}
|
||||
|
||||
export function readJson<T extends Object = any>(f: string): T {
|
||||
const content = readFile(f);
|
||||
return parseJson<T>(content);
|
||||
}
|
||||
|
||||
export function readFile(f: string) {
|
||||
const ff = f.startsWith('/') ? f : tmpProjPath(f);
|
||||
return readFileSync(ff, 'utf-8');
|
||||
}
|
||||
|
||||
export function removeFile(f: string) {
|
||||
const ff = f.startsWith('/') ? f : tmpProjPath(f);
|
||||
removeSync(ff);
|
||||
}
|
||||
|
||||
export function rmDist() {
|
||||
removeSync(`${tmpProjPath()}/dist`);
|
||||
}
|
||||
|
||||
export function directoryExists(filePath: string): boolean {
|
||||
try {
|
||||
return statSync(filePath).isDirectory();
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function fileExists(filePath: string): boolean {
|
||||
try {
|
||||
return statSync(filePath).isFile();
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function exists(filePath: string): boolean {
|
||||
return directoryExists(filePath) || fileExists(filePath);
|
||||
}
|
||||
|
||||
export function getSize(filePath: string): number {
|
||||
return statSync(filePath).size;
|
||||
}
|
||||
|
||||
export function tmpBackupProjPath(path?: string) {
|
||||
return path ? `${e2eCwd}/proj-backup/${path}` : `${e2eCwd}/proj-backup`;
|
||||
}
|
||||
|
||||
export const promisifiedTreeKill: (
|
||||
pid: number,
|
||||
signal: string
|
||||
) => Promise<void> = promisify(treeKill);
|
||||
@@ -0,0 +1,124 @@
|
||||
import { readJsonFile, workspaceRoot } from '@nrwl/devkit';
|
||||
import { execSync } from 'child_process';
|
||||
import { existsSync } from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { dirSync } from 'tmp';
|
||||
|
||||
import * as isCI from 'is-ci';
|
||||
import { PackageManager } from 'nx/src/utils/package-manager';
|
||||
import { tmpProjPath } from './create-project-utils';
|
||||
import { e2eConsoleLogger } from './log-utils';
|
||||
|
||||
export const isWindows = require('is-windows');
|
||||
|
||||
export function getPublishedVersion(): string {
|
||||
process.env.PUBLISHED_VERSION =
|
||||
process.env.PUBLISHED_VERSION ||
|
||||
// read version of built nx package
|
||||
readJsonFile(join(workspaceRoot, `./build/packages/nx/package.json`))
|
||||
.version ||
|
||||
// fallback to latest if built nx package is missing
|
||||
'latest';
|
||||
return process.env.PUBLISHED_VERSION as string;
|
||||
}
|
||||
|
||||
export function detectPackageManager(dir: string = ''): PackageManager {
|
||||
return existsSync(join(dir, 'yarn.lock'))
|
||||
? 'yarn'
|
||||
: existsSync(join(dir, 'pnpm-lock.yaml')) ||
|
||||
existsSync(join(dir, 'pnpm-workspace.yaml'))
|
||||
? 'pnpm'
|
||||
: 'npm';
|
||||
}
|
||||
|
||||
export function isNotWindows() {
|
||||
return !isWindows();
|
||||
}
|
||||
|
||||
export function isOSX() {
|
||||
return process.platform === 'darwin';
|
||||
}
|
||||
|
||||
export function isAndroid() {
|
||||
return (
|
||||
process.platform === 'linux' &&
|
||||
process.env.ANDROID_HOME &&
|
||||
process.env.ANDROID_SDK_ROOT
|
||||
);
|
||||
}
|
||||
|
||||
export const e2eRoot = isCI
|
||||
? dirSync({ prefix: 'nx-e2e-' }).name
|
||||
: '/tmp/nx-e2e';
|
||||
|
||||
export function isVerbose() {
|
||||
return (
|
||||
process.env.NX_VERBOSE_LOGGING === 'true' ||
|
||||
process.argv.includes('--verbose')
|
||||
);
|
||||
}
|
||||
|
||||
export const e2eCwd = `${e2eRoot}/nx`;
|
||||
|
||||
export function getSelectedPackageManager(): 'npm' | 'yarn' | 'pnpm' {
|
||||
return (process.env.SELECTED_PM as 'npm' | 'yarn' | 'pnpm') || 'npm';
|
||||
}
|
||||
|
||||
export function getNpmMajorVersion(): string {
|
||||
const [npmMajorVersion] = execSync(`npm -v`).toString().split('.');
|
||||
return npmMajorVersion;
|
||||
}
|
||||
|
||||
export function getLatestLernaVersion(): string {
|
||||
const lernaVersion = execSync(`npm view lerna version`, {
|
||||
encoding: 'utf-8',
|
||||
}).trim();
|
||||
return lernaVersion;
|
||||
}
|
||||
|
||||
export const packageManagerLockFile = {
|
||||
npm: 'package-lock.json',
|
||||
yarn: 'yarn.lock',
|
||||
pnpm: 'pnpm-lock.yaml',
|
||||
};
|
||||
|
||||
export function ensureCypressInstallation() {
|
||||
let cypressVerified = true;
|
||||
try {
|
||||
const r = execSync('npx cypress verify', {
|
||||
stdio: isVerbose() ? 'inherit' : 'pipe',
|
||||
encoding: 'utf-8',
|
||||
cwd: tmpProjPath(),
|
||||
});
|
||||
if (r.indexOf('Verified Cypress!') === -1) {
|
||||
cypressVerified = false;
|
||||
}
|
||||
} catch {
|
||||
cypressVerified = false;
|
||||
} finally {
|
||||
if (!cypressVerified) {
|
||||
e2eConsoleLogger('Cypress was not verified. Installing Cypress now.');
|
||||
execSync('npx cypress install', {
|
||||
stdio: isVerbose() ? 'inherit' : 'pipe',
|
||||
encoding: 'utf-8',
|
||||
cwd: tmpProjPath(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getStrippedEnvironmentVariables() {
|
||||
return Object.fromEntries(
|
||||
Object.entries(process.env).filter(([key, value]) => {
|
||||
if (key.startsWith('NX_E2E_')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key.startsWith('NX_')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
);
|
||||
}
|
||||
+9
-1171
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,47 @@
|
||||
import * as chalk from 'chalk';
|
||||
|
||||
export const E2E_LOG_PREFIX = `${chalk.reset.inverse.bold.keyword('orange')(
|
||||
' E2E '
|
||||
)}`;
|
||||
|
||||
export function e2eConsoleLogger(message: string, body?: string) {
|
||||
process.stdout.write('\n');
|
||||
process.stdout.write(`${E2E_LOG_PREFIX} ${message}\n`);
|
||||
if (body) {
|
||||
process.stdout.write(`${body}\n`);
|
||||
}
|
||||
process.stdout.write('\n');
|
||||
}
|
||||
|
||||
export function logInfo(title: string, body?: string) {
|
||||
const message = `${chalk.reset.inverse.bold.white(
|
||||
' INFO '
|
||||
)} ${chalk.bold.white(title)}`;
|
||||
return e2eConsoleLogger(message, body);
|
||||
}
|
||||
|
||||
export function logError(title: string, body?: string) {
|
||||
const message = `${chalk.reset.inverse.bold.red(' ERROR ')} ${chalk.bold.red(
|
||||
title
|
||||
)}`;
|
||||
return e2eConsoleLogger(message, body);
|
||||
}
|
||||
|
||||
export function logSuccess(title: string, body?: string) {
|
||||
const message = `${chalk.reset.inverse.bold.green(
|
||||
' SUCCESS '
|
||||
)} ${chalk.bold.green(title)}`;
|
||||
return e2eConsoleLogger(message, body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove log colors for fail proof string search
|
||||
* @param log
|
||||
* @returns
|
||||
*/
|
||||
export function stripConsoleColors(log: string): string {
|
||||
return log?.replace(
|
||||
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
||||
''
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ProjectConfiguration, Workspaces } from '@nrwl/devkit';
|
||||
import { join } from 'path';
|
||||
import { tmpProjPath } from './create-project-utils';
|
||||
import { readJson, updateFile } from './file-utils';
|
||||
|
||||
export function updateProjectConfig(
|
||||
projectName: string,
|
||||
callback: (c: ProjectConfiguration) => ProjectConfiguration
|
||||
) {
|
||||
const workspace = readResolvedConfiguration();
|
||||
const root = workspace.projects[projectName].root;
|
||||
const path = join(root, 'project.json');
|
||||
const current = readJson(path);
|
||||
updateFile(path, JSON.stringify(callback(current), null, 2));
|
||||
}
|
||||
|
||||
export function readResolvedConfiguration() {
|
||||
process.env.NX_PROJECT_GLOB_CACHE = 'false';
|
||||
const ws = new Workspaces(tmpProjPath());
|
||||
return ws.readProjectsConfigurations();
|
||||
}
|
||||
|
||||
export function readProjectConfig(projectName: string): ProjectConfiguration {
|
||||
const root = readResolvedConfiguration().projects[projectName].root;
|
||||
const path = join(root, 'project.json');
|
||||
return readJson(path);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { joinPathFragments } from '@nrwl/devkit';
|
||||
import {
|
||||
getPackageManagerCommand,
|
||||
runCLI,
|
||||
runCLIAsync,
|
||||
runCommand,
|
||||
} from './command-utils';
|
||||
import { uniq } from './create-project-utils';
|
||||
import { readFile } from './file-utils';
|
||||
|
||||
type GeneratorsWithDefaultTests =
|
||||
| '@nrwl/js:lib'
|
||||
| '@nrwl/node:lib'
|
||||
| '@nrwl/react:lib'
|
||||
| '@nrwl/react:app'
|
||||
| '@nrwl/next:app'
|
||||
| '@nrwl/angular:app'
|
||||
| '@nrwl/workspace:lib'
|
||||
| '@nrwl/web:app';
|
||||
|
||||
/**
|
||||
* Runs the pass in generator and then runs test on
|
||||
* the generated project to make sure the default tests pass.
|
||||
*/
|
||||
export async function expectJestTestsToPass(
|
||||
generator: GeneratorsWithDefaultTests | string
|
||||
) {
|
||||
const name = uniq('proj');
|
||||
const generatedResults = runCLI(
|
||||
`generate ${generator} ${name} --no-interactive`
|
||||
);
|
||||
expect(generatedResults).toContain(`jest.config.ts`);
|
||||
|
||||
const results = await runCLIAsync(`test ${name}`);
|
||||
expect(results.combinedOutput).toContain('Test Suites: 1 passed, 1 total');
|
||||
}
|
||||
|
||||
export function expectTestsPass(v: { stdout: string; stderr: string }) {
|
||||
expect(v.stderr).toContain('Ran all test suites');
|
||||
expect(v.stderr).not.toContain('fail');
|
||||
}
|
||||
|
||||
export function expectNoAngularDevkit() {
|
||||
const { list } = getPackageManagerCommand();
|
||||
const result = runCommand(`${list} @angular-devkit/core`);
|
||||
expect(result).not.toContain('@angular-devkit/core');
|
||||
}
|
||||
|
||||
export function expectNoTsJestInJestConfig(appName: string) {
|
||||
const jestConfig = readFile(
|
||||
joinPathFragments('apps', appName, 'jest.config.ts')
|
||||
);
|
||||
expect(jestConfig).not.toContain('ts-jest');
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
cleanupProject,
|
||||
newProject,
|
||||
readFile,
|
||||
rmDist,
|
||||
runCLI,
|
||||
runCommand,
|
||||
@@ -16,15 +15,32 @@ describe('Webpack Plugin', () => {
|
||||
|
||||
it('should be able to setup project to build node programs with webpack and different compilers', async () => {
|
||||
const myPkg = uniq('my-pkg');
|
||||
runCLI(`generate @nrwl/js:lib ${myPkg} --buildable=false`);
|
||||
runCLI(`generate @nrwl/js:lib ${myPkg} --bundler=none`);
|
||||
updateFile(`libs/${myPkg}/src/index.ts`, `console.log('Hello');\n`);
|
||||
|
||||
// babel (default)
|
||||
runCLI(
|
||||
`generate @nrwl/webpack:webpack-project ${myPkg} --target=node --tsConfig=libs/${myPkg}/tsconfig.lib.json --main=libs/${myPkg}/src/index.ts`
|
||||
);
|
||||
|
||||
// Test `scriptType` later during during.
|
||||
updateFile(
|
||||
`libs/${myPkg}/webpack.config.js`,
|
||||
`
|
||||
const { composePlugins, withNx } = require('@nrwl/webpack');
|
||||
|
||||
module.exports = composePlugins(withNx(), (config) => {
|
||||
console.log('scriptType is ' + config.output.scriptType);
|
||||
return config;
|
||||
});
|
||||
`
|
||||
);
|
||||
|
||||
rmDist();
|
||||
runCLI(`build ${myPkg}`);
|
||||
|
||||
const buildOutput = runCLI(`build ${myPkg}`);
|
||||
// Ensure scriptType is not set if we're in Node (it only applies to Web).
|
||||
expect(buildOutput).toContain('scriptType is undefined');
|
||||
let output = runCommand(`node dist/libs/${myPkg}/main.js`);
|
||||
expect(output).toMatch(/Hello/);
|
||||
expect(output).not.toMatch(/Conflicting/);
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"packages": ["build/packages/*", "build/packages/nx/native-packages/*"],
|
||||
"version": "15.8.1",
|
||||
"version": "15.8.8",
|
||||
"granularPathspec": false,
|
||||
"command": {
|
||||
"publish": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// nx-ignore-next-line
|
||||
const withNx = require('@nrwl/next/plugins/with-nx');
|
||||
const { withNx } = require('@nrwl/next/plugins/with-nx');
|
||||
const { copySync } = require('fs-extra');
|
||||
const path = require('path');
|
||||
const redirectRules = require('./redirect-rules.config');
|
||||
|
||||
@@ -120,6 +120,9 @@
|
||||
},
|
||||
{
|
||||
"env": "NX_E2E_CI_CACHE_KEY"
|
||||
},
|
||||
{
|
||||
"env": "CI"
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
|
||||
@@ -44,4 +44,8 @@ This is useful if you have a project that contains components that are not meant
|
||||
nx g @nrwl/angular:storybook-configuration ui --storybook7betaConfiguration=true
|
||||
```
|
||||
|
||||
{% callout type="info" title="For Nx versions <15.9" %}
|
||||
The flag is called `storybook7betaConfiguration` for Nx versions <15.9.
|
||||
{% /callout %}
|
||||
|
||||
This will generate a Storybook configuration for the `ui` project using Storybook version 7. It will install the Storybook version 7 dependencies and configure the Storybook configuration files (the files inside the `.storybook` directory) to use Storybook version 7. You can read more about Storybook 7 Nx support in the [Storybook 7 setup guide](/packages/storybook/documents/storybook-7-setup).
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
"requires": {
|
||||
"@angular/core": ">=15.0.0"
|
||||
},
|
||||
"description": "Remove browserlist config as it's handled by build-angular",
|
||||
"description": "Remove browserslist config as it's handled by build-angular",
|
||||
"factory": "./src/migrations/update-15-2-0/remove-browserlist-config"
|
||||
},
|
||||
"update-typescript-target": {
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
workspaceRoot,
|
||||
} from '@nrwl/devkit';
|
||||
import { existsSync, lstatSync, mkdirSync, writeFileSync } from 'fs';
|
||||
import { dirname, join, relative } from 'path';
|
||||
import { dirname, join, relative, sep } from 'path';
|
||||
import type { BrowserBuilderSchema } from '../src/builders/webpack-browser/webpack-browser.impl';
|
||||
|
||||
/**
|
||||
@@ -168,13 +168,22 @@ function normalizeBuildTargetOptions(
|
||||
);
|
||||
const buildOptions = withSchemaDefaults(options);
|
||||
|
||||
// polyfill entries might be local files or files that are resolved from node_modules
|
||||
// like zone.js.
|
||||
// prevents error from webpack saying can't find <offset>/zone.js.
|
||||
const handlePolyfillPath = (polyfill: string) => {
|
||||
const maybeFullPath = join(workspaceRoot, polyfill.split('/').join(sep));
|
||||
if (existsSync(maybeFullPath)) {
|
||||
return joinPathFragments(offset, polyfill);
|
||||
}
|
||||
return polyfill;
|
||||
};
|
||||
// paths need to be unix paths for angular devkit
|
||||
buildOptions.polyfills =
|
||||
Array.isArray(buildOptions.polyfills) && buildOptions.polyfills.length > 0
|
||||
? (buildOptions.polyfills as string[]).map((p) =>
|
||||
joinPathFragments(offset, p)
|
||||
)
|
||||
: joinPathFragments(offset, buildOptions.polyfills as string);
|
||||
? (buildOptions.polyfills as string[]).map((p) => handlePolyfillPath(p))
|
||||
: handlePolyfillPath(buildOptions.polyfills as string);
|
||||
|
||||
buildOptions.main = joinPathFragments(offset, buildOptions.main);
|
||||
buildOptions.index =
|
||||
typeof buildOptions.index === 'string'
|
||||
@@ -197,6 +206,7 @@ function normalizeBuildTargetOptions(
|
||||
// then we don't want to have the assets/scripts/styles be included to
|
||||
// prevent inclusion of unintended stuff like tailwind
|
||||
if (
|
||||
buildContext.projectName === ctContext.projectName ||
|
||||
isCtProjectUsingBuildProject(
|
||||
ctContext.projectGraph,
|
||||
buildContext.projectName,
|
||||
|
||||
@@ -19,6 +19,13 @@ import { from } from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { getRootTsConfigPath } from 'nx/src/utils/typescript';
|
||||
|
||||
type BuildTargetOptions = {
|
||||
tsConfig: string;
|
||||
buildLibsFromSource?: boolean;
|
||||
customWebpackConfig?: { path?: string };
|
||||
indexFileTransformer?: string;
|
||||
};
|
||||
|
||||
export function executeWebpackDevServerBuilder(
|
||||
rawOptions: Schema,
|
||||
context: import('@angular-devkit/architect').BuilderContext
|
||||
@@ -38,27 +45,25 @@ export function executeWebpackDevServerBuilder(
|
||||
const buildTarget =
|
||||
browserTargetProjectConfiguration.targets[parsedBrowserTarget.target];
|
||||
|
||||
const buildTargetConfiguration = parsedBrowserTarget.configuration
|
||||
? buildTarget.configurations[parsedBrowserTarget.configuration]
|
||||
: buildTarget.defaultConfiguration
|
||||
? buildTarget.configurations[buildTarget.defaultConfiguration]
|
||||
: buildTarget.options;
|
||||
const buildTargetOptions: BuildTargetOptions = {
|
||||
...buildTarget.options,
|
||||
...(parsedBrowserTarget.configuration
|
||||
? buildTarget.configurations[parsedBrowserTarget.configuration]
|
||||
: buildTarget.defaultConfiguration
|
||||
? buildTarget.configurations[buildTarget.defaultConfiguration]
|
||||
: {}),
|
||||
};
|
||||
|
||||
const buildLibsFromSource =
|
||||
options.buildLibsFromSource ??
|
||||
buildTargetConfiguration?.buildLibsFromSource ??
|
||||
buildTarget.options.buildLibsFromSource ??
|
||||
buildTargetOptions.buildLibsFromSource ??
|
||||
true;
|
||||
|
||||
const customWebpackConfig: { path: string } =
|
||||
buildTargetConfiguration?.customWebpackConfig ??
|
||||
buildTarget.options.customWebpackConfig;
|
||||
|
||||
let pathToWebpackConfig: string;
|
||||
if (customWebpackConfig && customWebpackConfig.path) {
|
||||
if (buildTargetOptions.customWebpackConfig?.path) {
|
||||
pathToWebpackConfig = joinPathFragments(
|
||||
context.workspaceRoot,
|
||||
customWebpackConfig.path
|
||||
buildTargetOptions.customWebpackConfig.path
|
||||
);
|
||||
|
||||
if (pathToWebpackConfig && !existsSync(pathToWebpackConfig)) {
|
||||
@@ -68,15 +73,11 @@ export function executeWebpackDevServerBuilder(
|
||||
}
|
||||
}
|
||||
|
||||
const indexFileTransformer: string =
|
||||
buildTargetConfiguration?.indexFileTransformer ??
|
||||
buildTarget.options.indexFileTransformer;
|
||||
|
||||
let pathToIndexFileTransformer: string;
|
||||
if (indexFileTransformer) {
|
||||
if (buildTargetOptions.indexFileTransformer) {
|
||||
pathToIndexFileTransformer = joinPathFragments(
|
||||
context.workspaceRoot,
|
||||
indexFileTransformer
|
||||
buildTargetOptions.indexFileTransformer
|
||||
);
|
||||
|
||||
if (pathToIndexFileTransformer && !existsSync(pathToIndexFileTransformer)) {
|
||||
@@ -88,10 +89,8 @@ export function executeWebpackDevServerBuilder(
|
||||
|
||||
let dependencies: DependentBuildableProjectNode[];
|
||||
if (!buildLibsFromSource) {
|
||||
const buildTargetTsConfigPath =
|
||||
buildTargetConfiguration?.tsConfig ?? buildTarget.options.tsConfig;
|
||||
const { tsConfigPath, dependencies: foundDependencies } =
|
||||
createTmpTsConfigForBuildableLibs(buildTargetTsConfigPath, context, {
|
||||
createTmpTsConfigForBuildableLibs(buildTargetOptions.tsConfig, context, {
|
||||
target: parsedBrowserTarget.target,
|
||||
});
|
||||
dependencies = foundDependencies;
|
||||
@@ -113,7 +112,7 @@ export function executeWebpackDevServerBuilder(
|
||||
// otherwise the build will fail if customWebpack function/file is referencing
|
||||
// local libs. This synchronize the behavior with webpack-browser and
|
||||
// webpack-server implementation.
|
||||
buildTargetConfiguration.tsConfig = tsConfigPath;
|
||||
buildTargetOptions.tsConfig = tsConfigPath;
|
||||
}
|
||||
|
||||
return from(import('@angular-devkit/build-angular')).pipe(
|
||||
@@ -146,7 +145,7 @@ export function executeWebpackDevServerBuilder(
|
||||
return mergeCustomWebpackConfig(
|
||||
baseWebpackConfig,
|
||||
pathToWebpackConfig,
|
||||
buildTargetConfiguration,
|
||||
buildTargetOptions,
|
||||
context.target
|
||||
);
|
||||
},
|
||||
@@ -155,7 +154,7 @@ export function executeWebpackDevServerBuilder(
|
||||
? {
|
||||
indexHtml: resolveIndexHtmlTransformer(
|
||||
pathToIndexFileTransformer,
|
||||
buildTargetConfiguration.tsConfig,
|
||||
buildTargetOptions.tsConfig,
|
||||
context.target
|
||||
),
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ exports[`app at the root should accept numbers in the path 1`] = `"src/9-website
|
||||
|
||||
exports[`app nested should create project configs 1`] = `
|
||||
Object {
|
||||
"$schema": "../node_modules/nx/schemas/project-schema.json",
|
||||
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||
"name": "my-dir-my-app",
|
||||
"prefix": "proj",
|
||||
"projectType": "application",
|
||||
@@ -296,7 +296,7 @@ Object {
|
||||
|
||||
exports[`app not nested should create project configs 1`] = `
|
||||
Object {
|
||||
"$schema": "../node_modules/nx/schemas/project-schema.json",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"name": "my-app",
|
||||
"prefix": "proj",
|
||||
"projectType": "application",
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The name of the project.",
|
||||
"x-dropdown": "projects",
|
||||
"x-priority": "important"
|
||||
"x-dropdown": "projects"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
@@ -113,6 +112,6 @@
|
||||
"x-priority": "important"
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"required": ["name", "project"],
|
||||
"examplesFile": "../../../docs/component-examples.md"
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
}
|
||||
},
|
||||
"pattern": "^[a-zA-Z].*$"
|
||||
},
|
||||
"remotes": {
|
||||
"type": "array",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Tree } from '@nrwl/devkit';
|
||||
import { getPackageManagerCommand } from '@nrwl/devkit';
|
||||
import { execSync } from 'child_process';
|
||||
import { runNxSync } from 'nx/src/utils/child-process';
|
||||
|
||||
export function formatFilesTask(tree: Tree): void {
|
||||
if (
|
||||
@@ -11,8 +11,7 @@ export function formatFilesTask(tree: Tree): void {
|
||||
return;
|
||||
}
|
||||
|
||||
const pmc = getPackageManagerCommand();
|
||||
try {
|
||||
execSync(`${pmc.exec} nx format`, { cwd: tree.root, stdio: [0, 1, 2] });
|
||||
runNxSync(`format`, { cwd: tree.root, stdio: [0, 1, 2] });
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
}
|
||||
},
|
||||
"pattern": "^[a-zA-Z].*$"
|
||||
},
|
||||
"host": {
|
||||
"type": "string",
|
||||
|
||||
@@ -22,8 +22,7 @@
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The name of the project.",
|
||||
"x-dropdown": "projects",
|
||||
"x-priority": "important"
|
||||
"x-dropdown": "projects"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
@@ -77,5 +76,5 @@
|
||||
"x-priority": "important"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
"required": ["name", "project"]
|
||||
}
|
||||
|
||||
@@ -22,8 +22,7 @@
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The name of the project.",
|
||||
"x-dropdown": "projects",
|
||||
"x-priority": "important"
|
||||
"x-dropdown": "projects"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
@@ -58,5 +57,5 @@
|
||||
"x-priority": "important"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
"required": ["name", "project"]
|
||||
}
|
||||
|
||||
@@ -22,8 +22,7 @@
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The name of the project.",
|
||||
"x-dropdown": "projects",
|
||||
"x-priority": "important"
|
||||
"x-dropdown": "projects"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
@@ -123,5 +122,5 @@
|
||||
"x-priority": "important"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
"required": ["name", "project"]
|
||||
}
|
||||
|
||||
@@ -84,7 +84,8 @@
|
||||
"description": "Configure your workspace to use Storybook 7, even though Storybook v7 is still in beta.",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"hidden": true
|
||||
"hidden": true,
|
||||
"aliases": ["storybook7betaConfiguration"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Tree } from '@nrwl/devkit';
|
||||
import { findNodes } from 'nx/src/utils/typescript';
|
||||
import { getSourceNodes } from '@nrwl/workspace/src/utilities/typescript';
|
||||
import { getSourceNodes } from '@nrwl/workspace/src/utilities/typescript/get-source-nodes';
|
||||
import type { PropertyDeclaration } from 'typescript';
|
||||
import { getTsSourceFile } from '../../../utils/nx-devkit/ast-utils';
|
||||
import { ensureTypescript } from '@nrwl/js/src/utils/typescript/ensure-typescript';
|
||||
|
||||
@@ -47,10 +47,26 @@ export async function getModuleFederationConfig(
|
||||
projectGraph = await createProjectGraphAsync();
|
||||
}
|
||||
|
||||
if (!projectGraph.nodes[mfConfig.name]?.data) {
|
||||
throw Error(
|
||||
`Cannot find project "${mfConfig.name}". Check that the name is correct in module-federation.config.js`
|
||||
);
|
||||
}
|
||||
|
||||
const dependencies = getDependentPackagesForProject(
|
||||
projectGraph,
|
||||
mfConfig.name
|
||||
);
|
||||
|
||||
if (mfConfig.shared) {
|
||||
dependencies.workspaceLibraries = dependencies.workspaceLibraries.filter(
|
||||
(lib) => mfConfig.shared(lib.name, {})
|
||||
);
|
||||
dependencies.npmPackages = dependencies.npmPackages.filter((pkg) =>
|
||||
mfConfig.shared(pkg, {})
|
||||
);
|
||||
}
|
||||
|
||||
const sharedLibraries = shareWorkspaceLibraries(
|
||||
dependencies.workspaceLibraries
|
||||
);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"Cypress",
|
||||
"CLI"
|
||||
],
|
||||
"main": "./index.js",
|
||||
"main": "./index",
|
||||
"typings": "./index.d.ts",
|
||||
"author": "Victor Savkin",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -21,10 +21,7 @@ import {
|
||||
} from '@nrwl/devkit';
|
||||
import { Linter, lintProjectGenerator } from '@nrwl/linter';
|
||||
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
|
||||
import {
|
||||
initGenerator as jsInitGenerator,
|
||||
getRelativePathToRootTsConfig,
|
||||
} from '@nrwl/js';
|
||||
import { getRelativePathToRootTsConfig } from '@nrwl/js';
|
||||
import {
|
||||
globalJavaScriptOverrides,
|
||||
globalTypeScriptOverrides,
|
||||
|
||||
@@ -39,20 +39,36 @@ export function getTempTailwindPath(context: ExecutorContext) {
|
||||
}
|
||||
|
||||
/**
|
||||
* also returns true if the ct project and build project are the same.
|
||||
* i.e. component testing inside an app.
|
||||
*/
|
||||
* Checks if the childProjectName is a decendent of the parentProjectName
|
||||
* in the project graph
|
||||
**/
|
||||
export function isCtProjectUsingBuildProject(
|
||||
graph: ProjectGraph,
|
||||
parentProjectName: string,
|
||||
childProjectName: string
|
||||
) {
|
||||
return (
|
||||
parentProjectName === childProjectName ||
|
||||
graph.dependencies[parentProjectName].some(
|
||||
(p) => p.target === childProjectName
|
||||
)
|
||||
): boolean {
|
||||
const isProjectDirectDep = graph.dependencies[parentProjectName].some(
|
||||
(p) => p.target === childProjectName
|
||||
);
|
||||
if (isProjectDirectDep) {
|
||||
return true;
|
||||
}
|
||||
const maybeIntermediateProjects = graph.dependencies[
|
||||
parentProjectName
|
||||
].filter((p) => !graph.externalNodes[p.target]);
|
||||
|
||||
for (const maybeIntermediateProject of maybeIntermediateProjects) {
|
||||
if (
|
||||
isCtProjectUsingBuildProject(
|
||||
graph,
|
||||
maybeIntermediateProject.target,
|
||||
childProjectName
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getProjectConfigByPath(
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "Victor Savkin",
|
||||
"main": "index.js",
|
||||
"main": "./index",
|
||||
"types": "index.d.ts",
|
||||
"dependencies": {
|
||||
"@nrwl/devkit": "file:../devkit",
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"Cypress",
|
||||
"CLI"
|
||||
],
|
||||
"main": "./index.js",
|
||||
"main": "./index",
|
||||
"typings": "./index.d.ts",
|
||||
"author": "Victor Savkin",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -104,7 +104,21 @@ class DevkitTreeFromAngularDevkitTree implements Tree {
|
||||
private tree,
|
||||
private _root: string,
|
||||
private skipWritingConfigInOldFormat?: boolean
|
||||
) {}
|
||||
) {
|
||||
/**
|
||||
* When using the UnitTestTree from @angular-devkit/schematics/testing, the root is just `/`.
|
||||
* This causes a massive issue if `getProjects()` is used in the underlying generator because it
|
||||
* causes fast-glob to be set to work on the user's entire file system.
|
||||
*
|
||||
* Therefore, in this case, patch the root to match what Nx Devkit does and use /virtual instead.
|
||||
*/
|
||||
try {
|
||||
const { UnitTestTree } = require('@angular-devkit/schematics/testing');
|
||||
if (tree instanceof UnitTestTree && _root === '/') {
|
||||
this._root = '/virtual';
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
get root(): string {
|
||||
return this._root;
|
||||
|
||||
@@ -11,7 +11,8 @@ import { requireNx } from '../../nx';
|
||||
import { dirSync } from 'tmp';
|
||||
import { join } from 'path';
|
||||
|
||||
const { readJson, updateJson, getPackageManagerCommand } = requireNx();
|
||||
const { readJson, updateJson, getPackageManagerCommand, workspaceRoot } =
|
||||
requireNx();
|
||||
|
||||
const UNIDENTIFIED_VERSION = 'UNIDENTIFIED_VERSION';
|
||||
const NON_SEMVER_TAGS = {
|
||||
@@ -450,6 +451,7 @@ export function ensurePackage<T extends any = any>(
|
||||
stdio: [0, 1, 2],
|
||||
});
|
||||
|
||||
addToNodePath(join(workspaceRoot, 'node_modules'));
|
||||
addToNodePath(join(tempDir, 'node_modules'));
|
||||
|
||||
// Re-initialize the added paths into require
|
||||
@@ -473,6 +475,11 @@ function addToNodePath(dir: string) {
|
||||
? process.env.NODE_PATH.split(delimiter)
|
||||
: [];
|
||||
|
||||
// The path is already in the node path
|
||||
if (paths.includes(dir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the tmp path
|
||||
paths.push(dir);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"Web",
|
||||
"CLI"
|
||||
],
|
||||
"main": "./index.js",
|
||||
"main": "./index",
|
||||
"typings": "./index.d.ts",
|
||||
"author": "Victor Savkin",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -356,4 +356,39 @@ describe('buildEsbuildOptions', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should set sourcemap', () => {
|
||||
expect(
|
||||
buildEsbuildOptions(
|
||||
'esm',
|
||||
{
|
||||
bundle: false,
|
||||
platform: 'node',
|
||||
main: 'apps/myapp/src/index.ts',
|
||||
outputPath: 'dist/apps/myapp',
|
||||
tsConfig: 'apps/myapp/tsconfig.app.json',
|
||||
project: 'apps/myapp/package.json',
|
||||
outputFileName: 'index.js',
|
||||
assets: [],
|
||||
singleEntry: true,
|
||||
sourcemap: true,
|
||||
external: [],
|
||||
},
|
||||
context
|
||||
)
|
||||
).toEqual({
|
||||
bundle: false,
|
||||
entryNames: '[dir]/[name]',
|
||||
entryPoints: ['apps/myapp/src/index.ts'],
|
||||
format: 'esm',
|
||||
platform: 'node',
|
||||
outdir: 'dist/apps/myapp',
|
||||
tsconfig: 'apps/myapp/tsconfig.app.json',
|
||||
external: undefined,
|
||||
sourcemap: true,
|
||||
outExtension: {
|
||||
'.js': '.js',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as esbuild from 'esbuild';
|
||||
import * as path from 'path';
|
||||
import { join, parse } from 'path';
|
||||
import {
|
||||
normalizePath,
|
||||
ExecutorContext,
|
||||
joinPathFragments,
|
||||
ProjectGraphProjectNode,
|
||||
@@ -38,6 +39,7 @@ export function buildEsbuildOptions(
|
||||
target: options.target,
|
||||
metafile: options.metafile,
|
||||
tsconfig: options.tsConfig,
|
||||
sourcemap: options.sourcemap,
|
||||
format,
|
||||
outExtension: {
|
||||
'.js': outExtension,
|
||||
@@ -197,6 +199,8 @@ export function getRegisterFileContent(
|
||||
mainFile: string,
|
||||
outExtension = '.js'
|
||||
) {
|
||||
mainFile = normalizePath(mainFile);
|
||||
|
||||
// Sort by longest prefix so imports match the most specific path.
|
||||
const sortedKeys = Object.keys(paths).sort(
|
||||
(a: string, b: string) => getPrefixLength(b) - getPrefixLength(a)
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface EsBuildExecutorOptions {
|
||||
esbuildOptions?: Record<string, any>;
|
||||
external?: string[];
|
||||
format?: Array<'esm' | 'cjs'>;
|
||||
generatePackageJson?: boolean;
|
||||
main: string;
|
||||
metafile?: boolean;
|
||||
minify?: boolean;
|
||||
@@ -20,6 +21,7 @@ export interface EsBuildExecutorOptions {
|
||||
outputPath: string;
|
||||
platform?: 'node' | 'browser' | 'neutral';
|
||||
project: string;
|
||||
sourcemap?: boolean | 'linked' | 'inline' | 'external' | 'both';
|
||||
skipTypeCheck?: boolean;
|
||||
target?: string;
|
||||
thirdParty?: boolean;
|
||||
|
||||
@@ -92,7 +92,10 @@
|
||||
"default": false
|
||||
},
|
||||
"sourcemap": {
|
||||
"type": "boolean",
|
||||
"oneOf": [
|
||||
{ "type": "string", "enum": ["linked", "inline", "external", "both"] },
|
||||
{ "type": "boolean" }
|
||||
],
|
||||
"alias": "sourceMap",
|
||||
"description": "Generate sourcemap.",
|
||||
"default": false
|
||||
|
||||
@@ -202,6 +202,13 @@ export function getRelativeImportPath(exportedMember, filePath, basePath) {
|
||||
dirname(filePath),
|
||||
`${modulePath}.ts`
|
||||
);
|
||||
if (!existsSync(moduleFilePath)) {
|
||||
// might be a tsx file
|
||||
moduleFilePath = joinPathFragments(
|
||||
dirname(filePath),
|
||||
`${modulePath}.tsx`
|
||||
);
|
||||
}
|
||||
if (!existsSync(moduleFilePath)) {
|
||||
// might be a index.ts
|
||||
moduleFilePath = joinPathFragments(
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "Victor Savkin",
|
||||
"main": "index.js",
|
||||
"main": "./index",
|
||||
"types": "index.d.ts",
|
||||
"dependencies": {
|
||||
"@nrwl/detox": "file:../detox",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"Cypress",
|
||||
"CLI"
|
||||
],
|
||||
"main": "./index.js",
|
||||
"main": "./index",
|
||||
"typings": "./index.d.ts",
|
||||
"author": "Victor Savkin",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"Unit Testing",
|
||||
"CLI"
|
||||
],
|
||||
"main": "./index.js",
|
||||
"main": "./index",
|
||||
"typings": "./index.d.ts",
|
||||
"author": "Victor Savkin",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export const nxPreset = {
|
||||
import type { Config } from 'jest';
|
||||
export const nxPreset: Config = {
|
||||
// This is one of the patterns that jest finds by default https://jestjs.io/docs/configuration#testmatch-arraystring
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
resolver: '@nrwl/jest/plugins/resolver',
|
||||
|
||||
@@ -131,6 +131,40 @@ export default {
|
||||
expect(packageJson.devDependencies['@types/jest']).toBeDefined();
|
||||
expect(packageJson.devDependencies['ts-jest']).toBeDefined();
|
||||
expect(packageJson.devDependencies['ts-node']).toBeDefined();
|
||||
expect(packageJson.devDependencies['jest-environment-jsdom']).toBeDefined();
|
||||
expect(
|
||||
packageJson.devDependencies['jest-environment-node']
|
||||
).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should add dependencies --testEnvironment=node', async () => {
|
||||
await jestInitGenerator(tree, { testEnvironment: 'node' });
|
||||
const packageJson = readJson(tree, 'package.json');
|
||||
expect(packageJson.devDependencies.jest).toBeDefined();
|
||||
expect(packageJson.devDependencies['@nrwl/jest']).toBeDefined();
|
||||
expect(packageJson.devDependencies['@types/jest']).toBeDefined();
|
||||
expect(packageJson.devDependencies['ts-jest']).toBeDefined();
|
||||
expect(packageJson.devDependencies['ts-node']).toBeDefined();
|
||||
expect(packageJson.devDependencies['jest-environment-node']).toBeDefined();
|
||||
expect(
|
||||
packageJson.devDependencies['jest-environment-jsdom']
|
||||
).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should add dependencies --testEnvironment=none', async () => {
|
||||
await jestInitGenerator(tree, { testEnvironment: 'none' });
|
||||
const packageJson = readJson(tree, 'package.json');
|
||||
expect(packageJson.devDependencies.jest).toBeDefined();
|
||||
expect(packageJson.devDependencies['@nrwl/jest']).toBeDefined();
|
||||
expect(packageJson.devDependencies['@types/jest']).toBeDefined();
|
||||
expect(packageJson.devDependencies['ts-jest']).toBeDefined();
|
||||
expect(packageJson.devDependencies['ts-node']).toBeDefined();
|
||||
expect(
|
||||
packageJson.devDependencies['jest-environment-jsdom']
|
||||
).not.toBeDefined();
|
||||
expect(
|
||||
packageJson.devDependencies['jest-environment-node']
|
||||
).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should make js jest files', async () => {
|
||||
|
||||
@@ -33,6 +33,7 @@ const schemaDefaults = {
|
||||
compiler: 'tsc',
|
||||
js: false,
|
||||
rootProject: false,
|
||||
testEnvironment: 'jsdom',
|
||||
} as const;
|
||||
|
||||
function generateGlobalConfig(tree: Tree, isJS: boolean) {
|
||||
@@ -146,7 +147,6 @@ function updateDependencies(tree: Tree, options: NormalizedSchema) {
|
||||
const devDeps = {
|
||||
'@nrwl/jest': nxVersion,
|
||||
jest: jestVersion,
|
||||
'jest-environment-jsdom': jestVersion,
|
||||
|
||||
// because the default jest-preset uses ts-jest,
|
||||
// jest will throw an error if it's not installed
|
||||
@@ -154,6 +154,10 @@ function updateDependencies(tree: Tree, options: NormalizedSchema) {
|
||||
'ts-jest': tsJestVersion,
|
||||
};
|
||||
|
||||
if (options.testEnvironment !== 'none') {
|
||||
devDeps[`jest-environment-${options.testEnvironment}`] = jestVersion;
|
||||
}
|
||||
|
||||
if (!options.js) {
|
||||
devDeps['ts-node'] = tsNodeVersion;
|
||||
devDeps['@types/jest'] = jestTypesVersion;
|
||||
|
||||
@@ -2,6 +2,7 @@ export interface JestInitSchema {
|
||||
compiler?: 'tsc' | 'babel' | 'swc';
|
||||
js?: boolean;
|
||||
skipPackageJson?: boolean;
|
||||
testEnvironment?: 'node' | 'jsdom' | 'none';
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
@@ -18,6 +18,13 @@
|
||||
"description": "Do not add dependencies to `package.json`.",
|
||||
"x-priority": "internal"
|
||||
},
|
||||
"testEnvironment": {
|
||||
"type": "string",
|
||||
"enum": ["jsdom", "node", "none"],
|
||||
"description": "The test environment for jest. This controls which jest-environment-* package is installed",
|
||||
"default": "jsdom",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"js": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
|
||||
@@ -22,6 +22,6 @@
|
||||
]<% } %><% if(rootProject){ %>,
|
||||
testMatch: [
|
||||
'<rootDir>/src/**/__tests__/**/*.[jt]s?(x)',
|
||||
'<rootDir>/src/**/?(*.)+(spec|test).[jt]s?(x)',
|
||||
'<rootDir>/src/**/*(*.)@(spec|test).[jt]s?(x)',
|
||||
],<% } %>
|
||||
};
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
coverageDirectory: '<%= offsetFromRoot %>coverage/<%= projectRoot %>'<% if(rootProject){ %>,
|
||||
testMatch: [
|
||||
'<rootDir>/src/**/__tests__/**/*.[jt]s?(x)',
|
||||
'<rootDir>/src/**/?(*.)+(spec|test).[jt]s?(x)',
|
||||
'<rootDir>/src/**/*(*.)@(spec|test).[jt]s?(x)',
|
||||
],<% } %>
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user