Compare commits

...

85 Commits

Author SHA1 Message Date
FrozenPandaz f300d356c1 chore(misc): publish 15.8.7 2023-03-17 13:29:09 -04:00
FrozenPandaz 58c4e2fe8d chore(repo): fix craco e2e tests 2023-03-17 12:06:38 -04:00
FrozenPandaz aabd76fb9a fix(react): fix merge conflict for import 2023-03-17 11:04:24 -04:00
Jason Jean 143d8d3dbf fix(core): fix NX_TASK_TARGET_CONFIGURATION being set to 'null' (#15725)
(cherry picked from commit 1e27759bba)
2023-03-17 10:26:05 -04:00
Jason Jean 7d34a05beb fix(react): fix next import (#15724)
(cherry picked from commit 4b9865c187)
2023-03-17 10:26:01 -04:00
Craigory Coppola 47fdc35aa1 fix(core): nx migrate should accept tags other than latest and next for community packages (#15673)
(cherry picked from commit 9791f31c34)
2023-03-17 10:25:58 -04:00
Craigory Coppola 3811844d7c fix(core): wildcard implicit dependency should support scoped project names (#15699)
(cherry picked from commit cc68012978)
2023-03-15 18:33:44 -04:00
Nicholas Cunningham b81c398931 fix(nextjs): refactor how webpack config is loaded with nextjs (#15650)
Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>

(cherry picked from commit be81405a84)
2023-03-15 18:32:52 -04:00
Leosvel Pérez Espinosa 932ed449f2 fix(angular): throw error for invalid project name in withModuleFederation helper (#15694)
(cherry picked from commit 76ec6f9e0f)
2023-03-15 18:29:31 -04:00
Craigory Coppola 996a69098b fix(core): apply nxignore after gitignore (#15679)
(cherry picked from commit 9897cbeb9a)
2023-03-15 18:29:27 -04:00
Craigory Coppola 2b978178ac fix(core): clear line escape code shouldn't erase prefix in output (#15674)
(cherry picked from commit 72d64b66b0)
2023-03-15 18:29:16 -04:00
Craigory Coppola c3d50411b6 fix(core): prevent running arbitrary code when attempting to detect plugin capabilities (#15676)
(cherry picked from commit cd3d316af4)
2023-03-15 18:29:12 -04:00
Craigory Coppola 17e41943a6 fix(core): help text shouldn't include .js file extensions on windows (#15677)
(cherry picked from commit a950719c71)
2023-03-15 18:29:00 -04:00
tko39 5b56bbeebe fix(misc): cli - case in drive letters - windows (#15643)
(cherry picked from commit 87a6b3054e)
2023-03-15 18:28:51 -04:00
Katerina Skroumpelou 36bc8fa8f0 fix(vite): configuration docs and optional chain targets (#15655)
(cherry picked from commit 8e838b5aac)
2023-03-15 18:28:43 -04:00
Jason Jean 27b0a29903 fix(core): include target and configuration being run in env variables (#15668)
(cherry picked from commit 63eced7fca)
2023-03-15 18:28:39 -04:00
Miroslav Jonaš 699a355d4a fix(core): handle missing package version on lockfile parsing (#15648)
(cherry picked from commit 425c13448b)
2023-03-15 18:28:23 -04:00
William Neely 138c920f8c fix(misc): move generator should null-check cypress props (#15436)
(cherry picked from commit 46bc6af3c2)
2023-03-15 18:28:03 -04:00
Jason Jean cb5b5b2c34 fix(misc): handle standalone repos when removing nested projects (#15609)
(cherry picked from commit 636a74b62a)
2023-03-15 18:27:55 -04:00
Zachary DeRose f32eee962c fix(core): only perform pattern matching if file input doesn't exist (#15640)
(cherry picked from commit 5add52bebf)
2023-03-15 18:27:52 -04:00
Miroslav Jonaš 336ed031e3 fix(core): fix invalid yarn parser dependencies grouping key (#15647)
(cherry picked from commit 3f4ba6d2af)
2023-03-15 18:27:46 -04:00
Miroslav Jonaš cd35a0cc8b fix(core): ignore missing modules.yaml during postinstall (#15660)
(cherry picked from commit 8a0398d744)
2023-03-15 18:27:44 -04:00
Craigory Coppola 4dd0d978f4 fix(core): negative implicit dependencies should exclude statically detected dependencies (#15546)
(cherry picked from commit 9acd7757ae)
2023-03-15 18:27:35 -04:00
Leosvel Pérez Espinosa 6e49fae3c4 fix(misc): filter out deps early based on shared config from module federation (#15654)
(cherry picked from commit e2a27039c0)
2023-03-15 18:27:31 -04:00
Jiby Jose d0e831eff2 fix(core): fix pnpm hoisted dependencies resolution with hoisted node-linker (#15594)
(cherry picked from commit 5937597b74)
2023-03-15 18:27:28 -04:00
Leosvel Pérez Espinosa 5214ea93cf fix(angular): resolve browserTarget options correctly in webpack-dev-server (#15638)
(cherry picked from commit db682bf7d6)
2023-03-15 18:27:25 -04:00
Ethan Pursley ccd213b1c7 fix(vite): add configuration option for emptyOutDir (#15619)
(cherry picked from commit ae6dc6b91d)
2023-03-15 18:26:46 -04:00
FrozenPandaz 7bf2c6a341 chore(misc): publish 15.8.6 2023-03-10 18:04:22 -05:00
Leosvel Pérez Espinosa ed362e8b9e fix(angular): misc fixes in some generator schemas (#15602)
(cherry picked from commit 46b3767303)
2023-03-10 16:52:41 -05:00
Jason Jean 49b04b0e09 fix(core): shutdown the daemon when it is invalid (#15550)
(cherry picked from commit ad12ab2ed3)
2023-03-10 16:52:41 -05:00
Zachary DeRose 937516c867 fix(core): interactive migrations should be prompt when necessary (#15572)
(cherry picked from commit 4a8b7ed264)
2023-03-10 16:52:40 -05:00
Katerina Skroumpelou 9c84e1d613 fix(core): refactor the logging logic in e2e tests (#15548)
(cherry picked from commit a0e00c85fc)
2023-03-10 16:52:40 -05:00
Katerina Skroumpelou 27a3cb0559 fix(js): libraries should be buildable by default (#15533)
(cherry picked from commit 16ad3086be)
2023-03-10 16:52:39 -05:00
Craigory Coppola b888ab8f57 chore(core): updates to nx wrapper and naming (#15552)
(cherry picked from commit 803de0d8be)
2023-03-10 16:52:35 -05:00
Emily Marigold Klassen 14121a077a fix(core): issue with registering file dependencies (#15523)
(cherry picked from commit ac9c7e9308)
2023-03-08 18:15:10 -05:00
Jack Hsu ebb130dc7d fix(node): add default build configuration (#15542)
(cherry picked from commit a69f8abdf9)
2023-03-08 17:45:36 -05:00
Leosvel Pérez Espinosa d7ba8db9de fix(angular): prevent updating unchanged projects when syncing changes to the wrapped tree (#15503)
(cherry picked from commit f7367ff0e9)
2023-03-08 17:45:29 -05:00
James Henry 2460b338d8 chore(devkit): fix root when unit testing generators wrapped as schematics (#15506)
(cherry picked from commit d0c38b39e7)
2023-03-08 17:44:24 -05:00
Leosvel Pérez Espinosa 081c2d7a3b fix(angular): respect cli config and schematics defaults in ng cli adapter (#15510)
(cherry picked from commit bfdd2eb5b5)
2023-03-08 17:44:20 -05:00
Miroslav Jonaš b5ea35c5c0 fix(core): catch all lock file parsing/pruning errors and provide fallback (#15158)
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
(cherry picked from commit 5d69c4e68d)
2023-03-08 17:44:12 -05:00
cbl980226 ba53770685 fix(misc): ci workflow generator use correct default branch (#15487)
Co-authored-by: Miroslav Jonaš <meeroslav@users.noreply.github.com>
(cherry picked from commit fc75166f96)
2023-03-08 17:44:01 -05:00
Miroslav Jonaš 6e0a1e204e fix(core): use smarter hoisting of npm packages (#15502)
(cherry picked from commit 8d3f2bdc17)
2023-03-08 17:43:57 -05:00
Jack Hsu d4a0554671 fix(bundling): default Node scriptType to CommonJS since it has the widest compatibility (#15483)
(cherry picked from commit d7cd1538e5)
2023-03-08 17:43:22 -05:00
Jason Jean 6c34f407e4 fix(core): properly diff json from the merge-base (#15476)
(cherry picked from commit ffac350e27)
2023-03-08 17:43:01 -05:00
Miroslav Jonaš 36e4be172b chore(core): fix nx relative paths (#15465)
(cherry picked from commit 7d25c69579)
2023-03-08 17:42:58 -05:00
Leosvel Pérez Espinosa ef5a979e72 fix(core): ensure correct $schema path is generated when adding project configuration (#15463)
(cherry picked from commit 02a450e3bb)
2023-03-08 17:42:38 -05:00
FrozenPandaz e8bcf9ba5f chore(misc): publish 15.8.5 2023-03-03 21:18:46 -05:00
Craigory Coppola 08476dbbb3 Revert "fix(core): add warning if running on an outdated global insta… (#15442)
(cherry picked from commit 1eee5f04b5)
2023-03-03 21:17:43 -05:00
Nicholas Cunningham e34e73f37a chore(core): Update cacheable message for multiselect (#15161)
(cherry picked from commit 9cec533c52)
2023-03-03 21:17:41 -05:00
FrozenPandaz 3c4c052446 chore(misc): publish 15.8.4 2023-03-03 17:54:20 -05:00
Craigory Coppola 923cba8422 fix(core): add warning if running on an outdated global installation (#15408)
(cherry picked from commit e773bdbb79)
2023-03-03 17:01:55 -05:00
Craigory Coppola 83743801d5 fix(core): nx migrate should work in encapsulated Nx repos (#15338)
(cherry picked from commit 4a53ace70d)
2023-03-03 17:01:52 -05:00
Jason Jean 2555490762 fix(core): do not return a default project when nxJson is undefined (#15432)
(cherry picked from commit 17a0d2c085)
2023-03-03 17:01:49 -05:00
Phillip Barta 3e6f2227ae feat(js): added a minimal option to the library generator (#13561)
(cherry picked from commit 8c1d035b0b)
2023-03-03 17:01:45 -05:00
Jason Jean eb7c7bffeb fix(misc): restore tsconfig utils as deprecated in @nrwl/workspace (#15409)
(cherry picked from commit c956435133)
2023-03-03 17:01:38 -05:00
Leosvel Pérez Espinosa bf3fc41196 fix(testing): fix generated testMatch pattern in jest config to support windows (#15415)
(cherry picked from commit 5e12d053a9)
2023-03-03 17:01:28 -05:00
Jonathan Cammisuli 87b1a73b4c fix(core): handle .nxignore properly for the native hasher (#15420)
(cherry picked from commit b0ab036ae4)
2023-03-03 17:01:12 -05:00
Katerina Skroumpelou ee0c88f766 chore(core): rearrange e2e utils (#15421)
(cherry picked from commit 808ca1215c)
2023-03-03 17:00:57 -05:00
Katerina Skroumpelou fa037aa528 fix(js): integrate buildable bundler and compiler options (#15376)
(cherry picked from commit b84125f9dd)
2023-03-03 17:00:47 -05:00
Binyamin Yawitz d7d7f163a4 fix(node) executor bug on Windows (#15368)
(cherry picked from commit a7738caa70)
2023-03-03 17:00:29 -05:00
Caleb Ukle dd7eff1072 fix(testing): type the jest nx preset (#15404)
(cherry picked from commit 456391ee34)
2023-03-03 17:00:24 -05:00
FrozenPandaz 739c427351 chore(misc): publish 15.8.3 2023-03-02 20:10:17 -05:00
Miroslav Jonaš f342baca5a fix(core): plugin should not override existing external nodes (#15219)
(cherry picked from commit 9a014c792c)
2023-03-02 17:53:46 -05:00
Miloš Lajtman fd71c6e288 fix(core): use globbing also for workspace file patterns without '*' (#11298)
(cherry picked from commit a43a99bea5)
2023-03-02 17:53:42 -05:00
Craigory Coppola d30413311b fix(nx-plugin): e2e utils should work (#15397)
(cherry picked from commit a4a9c27637)
2023-03-02 17:04:27 -05:00
Caleb Ukle 0761e4c7a9 fix(web): update snapshots from vite tsconfig changes (#15398)
(cherry picked from commit 76fcf7a319)
2023-03-02 17:04:19 -05:00
Caleb Ukle d15733d00e fix(vite): add missing types for tsconfigs (#15260)
(cherry picked from commit 2e449da177)
2023-03-02 17:04:16 -05:00
Craigory Coppola eba45a9f5b fix(core): standardize chunking elements passing to cli (#15322)
(cherry picked from commit bf08714ad5)
2023-03-02 17:04:13 -05:00
Ruben Casas 68d9df74f6 fix(react): fixes library type not passed to the module federation conf (#15385)
(cherry picked from commit c05425104e)
2023-03-02 17:04:02 -05:00
Jason Jean 1b4ac4b63e fix(devkit): allow ensured packages to resolve from workspace node_mo… (#15386)
(cherry picked from commit 66f498ac8a)
2023-03-02 17:04:00 -05:00
Craigory Coppola 2959f7f033 fix(core): last project should be visible in nx show projects (#15335)
(cherry picked from commit 0aff9dcd5b)
2023-03-02 17:03:57 -05:00
FrozenPandaz f997744b4d chore(misc): publish 15.8.2 2023-03-02 11:51:39 -05:00
Jack Hsu 04a38649b3 fix(core): mark package.json dependencies as static (#15381)
(cherry picked from commit aaed63ba18)
2023-03-02 10:21:35 -05:00
Jason Jean c351b34f62 fix(js): fix parsing link dependencies in yarn (#15364)
(cherry picked from commit 65aac8ea17)
2023-03-02 10:21:33 -05:00
Jason Jean f111580f28 fix(core): handle file renames properly for affected (#15340)
(cherry picked from commit 7aac67c783)
2023-03-02 10:21:28 -05:00
Craigory Coppola bf9844ec72 fix(core): encapsulated nx repos should invoke wrapper when invoked via npx or global install (#15337)
(cherry picked from commit c9244d2536)
2023-03-02 10:21:26 -05:00
Miroslav Jonaš 6c86cb770c fix(linter): ensure ast file detection supports tsx files (#15345)
(cherry picked from commit e767fa4e2a)
2023-03-02 10:21:20 -05:00
Jack Hsu c06edd9426 fix(bundling): use a range rather than ^ for vitest peer dependency … (#15362)
(cherry picked from commit b7ef261182)
2023-03-02 10:21:17 -05:00
Benjamin Cabanes 1c7406379b chore(core): fix browserslist typo (#15309)
(cherry picked from commit 872dcc8547)
2023-03-02 10:21:07 -05:00
Katerina Skroumpelou f1de248ff7 fix(vite): root for running the builder (#15359)
(cherry picked from commit 3d4e542823)
2023-03-02 10:20:55 -05:00
Chau Tran 5ba8d5aa12 fix(js): handle swcrc path mappings if specified (#15328)
(cherry picked from commit c981ac2e0d)
2023-03-02 10:20:52 -05:00
Nicholas Cunningham 18ff2dbcd4 fix(node): lint generator aware that app is root (#15355)
(cherry picked from commit b40c9fa88f)
2023-03-02 10:20:44 -05:00
Jonathan Cammisuli e9a59d60af fix(core): inline source maps for local nx plugins (#15336)
(cherry picked from commit b87f838e9a)
2023-03-02 10:20:39 -05:00
Caleb Ukle 908cf41d74 fix(testing): only install dependency for the desired --testEnvironment (#15244)
(cherry picked from commit 2d2804b0c1)
2023-03-02 10:20:27 -05:00
Caleb Ukle 2f8ca66797 fix(testing): walk all project deps to check if CT project is used in buildTarget (#15252)
(cherry picked from commit e8f19d8cb0)
2023-03-02 10:20:24 -05:00
204 changed files with 4271 additions and 2598 deletions
+6 -6
View File
@@ -1147,9 +1147,9 @@
"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
@@ -1310,9 +1310,9 @@
"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
+9 -9
View File
@@ -1426,13 +1426,13 @@
"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": []
}
],
@@ -1630,14 +1630,14 @@
"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": []
},
"/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).",
@@ -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"],
@@ -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
+3 -3
View File
@@ -486,9 +486,9 @@
"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"
}
]
},
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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.
+47
View File
@@ -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 %}
+4 -4
View File
@@ -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`
@@ -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);
});
);
}
+14 -2
View File
@@ -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`);
});
+1 -1
View File
@@ -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`,
`
+28 -24
View File
@@ -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}();
}
`;
});
+56 -9
View File
@@ -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);
});
+8 -11
View File
@@ -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`);
+2 -2
View File
@@ -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();
-1
View File
@@ -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
View File
@@ -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`,
+2 -2
View File
@@ -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',
}
);
-119
View File
@@ -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();
}
+1 -1
View File
@@ -35,7 +35,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`);
+274
View File
@@ -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();
}
+6 -8
View File
@@ -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 () => {
+18 -2
View File
@@ -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', () => {
+3 -3
View File
@@ -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
+2 -1
View File
@@ -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', () => {
+1 -1
View File
@@ -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)
+463
View File
@@ -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);
});
}
+481
View File
@@ -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) {}
}
}
+128
View File
@@ -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);
+124
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+47
View File
@@ -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,
''
);
}
+27
View File
@@ -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);
}
+54
View File
@@ -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');
}
+19 -3
View File
@@ -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
View File
@@ -1,6 +1,6 @@
{
"packages": ["build/packages/*", "build/packages/nx/native-packages/*"],
"version": "15.8.1",
"version": "15.8.7",
"granularPathspec": false,
"command": {
"publish": {
+1 -1
View File
@@ -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');
+1 -1
View File
@@ -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": {
+15 -5
View File
@@ -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,
@@ -38,27 +38,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 = {
...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 +66,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,12 +82,14 @@ export function executeWebpackDevServerBuilder(
let dependencies: DependentBuildableProjectNode[];
if (!buildLibsFromSource) {
const buildTargetTsConfigPath =
buildTargetConfiguration?.tsConfig ?? buildTarget.options.tsConfig;
const { tsConfigPath, dependencies: foundDependencies } =
createTmpTsConfigForBuildableLibs(buildTargetTsConfigPath, context, {
target: parsedBrowserTarget.target,
});
createTmpTsConfigForBuildableLibs(
buildTargetOptions.buildTargetTsConfigPath,
context,
{
target: parsedBrowserTarget.target,
}
);
dependencies = foundDependencies;
// We can't just pass the tsconfig path in memory to the angular builder
@@ -113,7 +109,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 +142,7 @@ export function executeWebpackDevServerBuilder(
return mergeCustomWebpackConfig(
baseWebpackConfig,
pathToWebpackConfig,
buildTargetConfiguration,
buildTargetOptions,
context.target
);
},
@@ -155,7 +151,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"]
}
@@ -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';
+16
View File
@@ -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
);
@@ -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,
+25 -9
View File
@@ -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(
@@ -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;
+8 -1
View File
@@ -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);
@@ -2,6 +2,7 @@ import * as esbuild from 'esbuild';
import * as path from 'path';
import { join, parse } from 'path';
import {
normalizePath,
ExecutorContext,
joinPathFragments,
ProjectGraphProjectNode,
@@ -197,6 +198,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)
@@ -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(
+2 -1
View File
@@ -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 () => {
+5 -1
View File
@@ -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;
+1
View File
@@ -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)',
],<% } %>
};
@@ -388,7 +388,7 @@ describe('jestProject', () => {
coverageDirectory: './coverage/my-project',
testMatch: [
'<rootDir>/src/**/__tests__/**/*.[jt]s?(x)',
'<rootDir>/src/**/?(*.)+(spec|test).[jt]s?(x)',
'<rootDir>/src/**/*(*.)@(spec|test).[jt]s?(x)',
],
};
"
@@ -421,7 +421,7 @@ describe('jestProject', () => {
coverageDirectory: './coverage/my-project',
testMatch: [
'<rootDir>/src/**/__tests__/**/*.[jt]s?(x)',
'<rootDir>/src/**/?(*.)+(spec|test).[jt]s?(x)',
'<rootDir>/src/**/*(*.)@(spec|test).[jt]s?(x)',
],
};
"
@@ -18,15 +18,13 @@ const schemaDefaults = {
skipSetupFile: false,
skipSerializers: false,
rootProject: false,
testEnvironment: 'jsdom',
} as const;
function normalizeOptions(options: JestProjectSchema) {
if (!options.testEnvironment) {
options.testEnvironment = 'jsdom';
}
if (options.testEnvironment === 'jsdom') {
options.testEnvironment = '';
}
if (!options.hasOwnProperty('supportTsx')) {
options.supportTsx = false;
@@ -31,6 +31,11 @@ export function createFiles(tree: Tree, options: JestProjectSchema) {
generateFiles(tree, join(__dirname, filesFolder), projectConfig.root, {
tmpl: '',
...options,
// jsdom is the default
testEnvironment:
options.testEnvironment === 'none' || options.testEnvironment === 'jsdom'
? ''
: options.testEnvironment,
transformer,
transformerOptions,
js: !!options.js,
+1 -1
View File
@@ -7,7 +7,7 @@ export interface JestProjectSchema {
skipSetupFile?: boolean;
setupFile?: 'angular' | 'web-components' | 'none';
skipSerializers?: boolean;
testEnvironment?: 'node' | 'jsdom' | '';
testEnvironment?: 'node' | 'jsdom' | 'none';
/**
* @deprecated use compiler: "babel" instead
*/
@@ -40,7 +40,7 @@
},
"testEnvironment": {
"type": "string",
"enum": ["jsdom", "node"],
"enum": ["jsdom", "node", "none"],
"description": "The test environment for jest.",
"default": "jsdom",
"x-priority": "important"
+1 -1
View File
@@ -42,7 +42,7 @@ module.exports = function (api: any, options: NxWebBabelPresetOptions = {}) {
process.env.NODE_ENV === 'test'
? { targets: { node: 'current' }, loose: true }
: {
// Allow importing core-js in entrypoint and use browserlist to select polyfills.
// Allow importing core-js in entrypoint and use browserslist to select polyfills.
useBuiltIns: options.useBuiltIns ?? 'entry',
corejs: 3,
// Do not transform modules to CJS
+115 -14
View File
@@ -1,7 +1,118 @@
---
title: JS library generator examples
description: This page contains examples for the @nrwl/js:lib generator.
---
The `@nrwl/js:lib` generator will generate a library for you, and it will configure it according to the options you provide.
```bash
npx nx g @nrwl/js:lib mylib
```
By 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.
You 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.
## Examples
{% tabs %}
{% tab label="Buildable with default compiler (tsc)" %}
Generate a buildable library using the `@nrwl/js:tsc` executor. This uses `tsc` as the compiler.
```bash
npx nx g @nrwl/js:lib mylib
```
{% /tab %}
{% tab label="Buildable with SWC compiler" %}
Generate a buildable library using [SWC](https://swc.rs) as the compiler. This will use the `@nrwl/js:swc` executor.
```bash
npx nx g @nrwl/js:lib mylib --bundler=swc
```
{% /tab %}
{% tab label="Buildable with tsc" %}
Generate a buildable library using tsc as the compiler. This will use the `@nrwl/js:tsc` executor.
```bash
npx nx g @nrwl/js:lib mylib --bundler=tsc
```
{% /tab %}
{% tab label="Buildable, with Rollup as a bundler" %}
Generate 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.
```bash
npx nx g @nrwl/js:lib mylib --bundler=rollup
```
If 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):
```jsonc {% fileName="libs/mylib/project.json" %}
"build": {
"executor": "@nrwl/rollup:rollup",
"options": {
//...
"compiler": "babel"
}
}
```
{% /tab %}
{% tab label="Buildable, with Vite as a bundler" %}
Generate a buildable library using [Vite](https://vitejs.dev/) as the bundler. This will use the `@nrwl/vite:build` executor.
```bash
npx nx g @nrwl/js:lib mylib --bundler=vite
```
{% /tab %}
{% tab label="Using ESBuild" %}
Generate a buildable library using [ESBuild](https://esbuild.github.io/) as the bundler. This will use the `@nrwl/esbuild:esbuild` executor.
```bash
npx nx g @nrwl/js:lib mylib --bundler=esbuild
```
If 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/):
```jsonc {% fileName="libs/mylib/project.json" %}
"build": {
"executor": "@nrwl/esbuild:esbuild",
"options": {
//...
"esbuildOptions": {
"bundle": true
}
}
}
```
{% /tab %}
{% tab label="Minimal publishing target" %}
Generate 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.
```bash
npx nx g lib mylib --publishable
```
{% /tab %}
{% tab label="Using directory flag" %}
Generate a library named `mylib` and put it under a directory named `myapp` (`libs/myapp/mylib`)
@@ -12,22 +123,12 @@ npx nx g lib mylib --directory=myapp
{% /tab %}
{% tab label="Use SWC compiler" %}
{% tab label="Non-buildable library" %}
Generate a library using [SWC](https://swc.rs) as the compiler
Generate a non-buildable library.
```shell
npx nx g lib mylib --compiler=swc
```
{% /tab %}
{% tab label="Minimal publishing target" %}
Generate a **publishable** library with a minimal publishing target
```shell
npx nx g lib mylib --publishable
```bash
npx nx g @nrwl/js:lib mylib --bundler=none
```
{% /tab %}
+18 -3
View File
@@ -1,4 +1,4 @@
import { ExecutorContext } from '@nrwl/devkit';
import { ExecutorContext, readJsonFile, writeJsonFile } from '@nrwl/devkit';
import {
assetGlobsToFiles,
FileInputOutput,
@@ -24,6 +24,7 @@ import {
import { compileSwc, compileSwcWatch } from '../../utils/swc/compile-swc';
import { getSwcrcPath } from '../../utils/swc/get-swcrc-path';
import { generateTmpSwcrc } from '../../utils/swc/inline';
import type { Options } from '@swc/core';
export function normalizeOptions(
options: SwcExecutorOptions,
@@ -68,7 +69,21 @@ export function normalizeOptions(
// default to current directory if projectRootParts is [].
// Eg: when a project is at the root level, outside of layout dir
const swcCwd = projectRootParts.join('/') || '.';
const swcrcPath = getSwcrcPath(options, contextRoot, projectRoot);
let swcrcPath = getSwcrcPath(options, contextRoot, projectRoot);
try {
const swcrcContent = readJsonFile(swcrcPath) as Options;
// if we have path mappings setup but baseUrl isn't specified, then we're proceeding with the following logic
if (
swcrcContent.jsc &&
swcrcContent.jsc.paths &&
!swcrcContent.jsc.baseUrl
) {
swcrcContent.jsc.baseUrl = `./${projectDir}`;
swcrcPath = getSwcrcPath(options, contextRoot, projectRoot, true);
writeJsonFile(swcrcPath, swcrcContent);
}
} catch (e) {}
const swcCliOptions = {
srcPath: projectDir,
@@ -190,7 +205,7 @@ export async function* swcExecutor(
}
function removeTmpSwcrc(swcrcPath: string) {
if (swcrcPath.startsWith('tmp/')) {
if (swcrcPath.includes('tmp/') && swcrcPath.includes('.generated.swcrc')) {
removeSync(dirname(swcrcPath));
}
}
@@ -18,7 +18,7 @@ describe('convert to swc', () => {
pascalCaseFiles: false,
strict: true,
config: 'project',
compiler: 'tsc',
bundler: 'tsc',
};
beforeAll(() => {
@@ -31,7 +31,7 @@ describe('convert to swc', () => {
await jsLibraryGenerator(tree, {
...defaultLibGenerationOptions,
name: 'tsc-lib',
buildable: true,
bundler: 'tsc',
});
expect(
@@ -212,7 +212,7 @@ describe('lib', () => {
).toBeTruthy();
expect(tree.exists('libs/my-dir/my-lib/src/index.ts')).toBeTruthy();
expect(tree.exists(`libs/my-dir/my-lib/.eslintrc.json`)).toBeTruthy();
expect(tree.exists(`libs/my-dir/my-lib/package.json`)).toBeFalsy();
expect(tree.exists(`libs/my-dir/my-lib/package.json`)).toBeTruthy();
});
it('should update project configuration', async () => {
@@ -708,7 +708,7 @@ describe('lib', () => {
...defaultOptions,
name: 'myLib',
unitTestRunner: 'jest',
compiler: 'swc',
bundler: 'swc',
js: true,
});
@@ -727,7 +727,101 @@ describe('lib', () => {
expect(readme).toContain('nx test my-lib');
});
describe('--buildable', () => {
describe('--buildable deprecation - replace with other options', () => {
it('should NOT generate the build target if bundler is none', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
bundler: 'none',
});
const config = readProjectConfiguration(tree, 'my-lib');
expect(config.targets.build).not.toBeDefined();
});
it('should still generate the build target if bundler is undefined', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
});
const config = readProjectConfiguration(tree, 'my-lib');
expect(config.targets.build).toBeDefined();
});
it('should generate the build target', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
bundler: 'tsc',
});
const config = readProjectConfiguration(tree, 'my-lib');
expect(config.targets.build).toEqual({
executor: '@nrwl/js:tsc',
options: {
assets: ['libs/my-lib/*.md'],
main: 'libs/my-lib/src/index.ts',
outputPath: 'dist/libs/my-lib',
tsConfig: 'libs/my-lib/tsconfig.lib.json',
},
outputs: ['{options.outputPath}'],
});
});
it('should generate the build target for swc', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
bundler: 'swc',
});
const config = readProjectConfiguration(tree, 'my-lib');
expect(config.targets.build).toEqual({
executor: '@nrwl/js:swc',
options: {
assets: ['libs/my-lib/*.md'],
main: 'libs/my-lib/src/index.ts',
outputPath: 'dist/libs/my-lib',
tsConfig: 'libs/my-lib/tsconfig.lib.json',
},
outputs: ['{options.outputPath}'],
});
});
it('should generate swcrc for swc', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
bundler: 'swc',
});
expect(tree.exists('libs/my-lib/.swcrc')).toBeTruthy();
});
it('should setup jest project using swc', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
bundler: 'swc',
});
const jestConfig = tree.read('libs/my-lib/jest.config.ts').toString();
expect(jestConfig).toContain('@swc/jest');
});
it('should generate a package.json file', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
bundler: 'tsc',
});
expect(tree.exists('libs/my-lib/package.json')).toBeTruthy();
});
});
describe('--buildable -- make sure old schema still works - no breaking change', () => {
it('should generate the build target', async () => {
await libraryGenerator(tree, {
...defaultOptions,
@@ -805,12 +899,11 @@ describe('lib', () => {
});
});
describe('bundler=rollup', () => {
describe('bundler=rollup - no compiler', () => {
it('should generate correct options for build', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
buildable: true,
bundler: 'rollup',
});
@@ -820,13 +913,11 @@ describe('lib', () => {
);
});
it('should set compiler to swc', async () => {
it('should always set compiler to swc if bundler is rollup', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
buildable: true,
bundler: 'rollup',
compiler: 'swc',
});
const config = readProjectConfiguration(tree, 'my-lib');
@@ -841,7 +932,7 @@ describe('lib', () => {
name: 'myLib',
publishable: true,
importPath: '@proj/my-lib',
compiler: 'tsc',
bundler: 'tsc',
});
const config = readProjectConfiguration(tree, 'my-lib');
@@ -863,7 +954,7 @@ describe('lib', () => {
name: 'myLib',
publishable: true,
importPath: '@proj/my-lib',
compiler: 'tsc',
bundler: 'tsc',
});
const config = readProjectConfiguration(tree, 'my-lib');
@@ -883,7 +974,7 @@ describe('lib', () => {
name: 'myLib',
publishable: true,
importPath: '@proj/my-lib',
compiler: 'tsc',
bundler: 'tsc',
});
expect(tree.exists('tools/scripts/publish.mjs')).toBeTruthy();
@@ -911,11 +1002,11 @@ describe('lib', () => {
expect(tree.exists('libs/my-lib/.babelrc')).toBeFalsy();
});
it('should not generate a .babelrc when compiler is swc (even if flag is set to true)', async () => {
it('should not generate a .babelrc when bundler is swc (even if flag is set to true)', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
compiler: 'swc',
bundler: 'swc',
includeBabelRc: true,
});
@@ -1009,4 +1100,56 @@ describe('lib', () => {
}
);
});
describe('--minimal', () => {
it('should generate a README.md when minimal is set to false', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
minimal: false,
});
expect(tree.exists('libs/my-lib/README.md')).toBeTruthy();
});
it('should not generate a README.md when minimal is set to true', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
minimal: true,
});
expect(tree.exists('libs/my-lib/README.md')).toBeFalsy();
});
it('should generate a README.md and add it to the build assets when buildable is true and minimal is false', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
bundler: 'tsc',
minimal: false,
});
expect(tree.exists('libs/my-lib/README.md')).toBeTruthy();
const project = readProjectConfiguration(tree, 'my-lib');
expect(project.targets.build.options.assets).toStrictEqual([
'libs/my-lib/*.md',
]);
});
it('should not generate a README.md when both bundler and minimal are set', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'myLib',
bundler: 'tsc',
minimal: true,
});
expect(tree.exists('libs/my-lib/README.md')).toBeFalsy();
const project = readProjectConfiguration(tree, 'my-lib');
expect(project.targets.build.options.assets).toEqual([]);
});
});
});
+87 -23
View File
@@ -25,7 +25,7 @@ import {
} from '../../utils/typescript/ts-config';
import { join } from 'path';
import { addMinimalPublishScript } from '../../utils/minimal-publish-script';
import { LibraryGeneratorSchema } from '../../utils/schema';
import { LibraryGeneratorSchema, Bundler } from '../../utils/schema';
import { addSwcConfig } from '../../utils/swc/add-swc-config';
import { addSwcDependencies } from '../../utils/swc/add-swc-dependencies';
import {
@@ -94,7 +94,7 @@ export async function projectGenerator(
if (options.unitTestRunner === 'jest') {
const jestCallback = await addJest(tree, options);
tasks.push(jestCallback);
if (options.compiler === 'swc') {
if (options.bundler === 'swc' || options.bundler === 'rollup') {
replaceJestConfig(tree, options, `${filesDir}/jest-config`);
}
} else if (
@@ -143,34 +143,45 @@ function addProject(
tags: options.parsedTags,
};
if (options.buildable && options.config !== 'npm-scripts') {
if (
options.bundler &&
options.bundler !== 'none' &&
options.config !== 'npm-scripts'
) {
const outputPath = destinationDir
? `dist/${destinationDir}/${options.projectDirectory}`
: `dist/${options.projectDirectory}`;
projectConfiguration.targets.build = {
executor: getBuildExecutor(options),
executor: getBuildExecutor(options.bundler),
outputs: ['{options.outputPath}'],
options: {
outputPath,
main: `${options.projectRoot}/src/index` + (options.js ? '.js' : '.ts'),
tsConfig: `${options.projectRoot}/tsconfig.lib.json`,
// TODO(jack): assets for rollup have validation that we need to fix (assets must be under <project-root>/src)
assets:
options.bundler === 'rollup' ? [] : [`${options.projectRoot}/*.md`],
assets: [],
},
};
if (options.bundler === 'rollup') {
projectConfiguration.targets.build.options.project = `${options.projectRoot}/package.json`;
if (options.compiler === 'swc') {
projectConfiguration.targets.build.options.compiler = 'swc';
}
projectConfiguration.targets.build.options.compiler = 'swc';
}
if (options.compiler === 'swc' && options.skipTypeCheck) {
if (options.bundler === 'swc' && options.skipTypeCheck) {
projectConfiguration.targets.build.options.skipTypeCheck = true;
}
if (
!options.minimal &&
// TODO(jack): assets for rollup have validation that we need to fix (assets must be under <project-root>/src)
options.bundler !== 'rollup'
) {
projectConfiguration.targets.build.options.assets ??= [];
projectConfiguration.targets.build.options.assets.push(
joinPathFragments(options.projectRoot, '*.md')
);
}
if (options.publishable) {
const publishScriptPath = addMinimalPublishScript(tree);
@@ -262,16 +273,16 @@ function createFiles(tree: Tree, options: NormalizedSchema, filesDir: string) {
tmpl: '',
offsetFromRoot: offsetFromRoot(options.projectRoot),
rootTsConfigPath: getRelativePathToRootTsConfig(tree, options.projectRoot),
buildable: options.buildable === true,
buildable: options.bundler && options.bundler !== 'none',
hasUnitTestRunner: options.unitTestRunner !== 'none',
});
if (options.compiler === 'swc') {
if (options.bundler === 'swc' || options.bundler === 'rollup') {
addSwcDependencies(tree);
addSwcConfig(
tree,
options.projectRoot,
options.bundler === 'rollup' ? 'es6' : 'commonjs'
options.bundler === 'swc' ? 'commonjs' : 'es6'
);
} else if (options.includeBabelRc) {
addBabelRc(tree, options);
@@ -299,10 +310,14 @@ function createFiles(tree: Tree, options: NormalizedSchema, filesDir: string) {
};
return json;
});
} else if (!options.buildable) {
} else if (!options.bundler || options.bundler === 'none') {
tree.delete(packageJsonPath);
}
if (options.minimal) {
tree.delete(join(options.projectRoot, 'README.md'));
}
updateTsConfig(tree, options);
}
@@ -319,7 +334,12 @@ async function addJest(
skipSerializers: true,
testEnvironment: options.testEnvironment,
skipFormat: true,
compiler: options.compiler,
compiler:
options.bundler === 'swc' || options.bundler === 'tsc'
? options.bundler
: options.bundler === 'rollup'
? 'swc'
: undefined,
});
}
@@ -352,24 +372,58 @@ function normalizeOptions(
options: LibraryGeneratorSchema,
destinationDir: string
): NormalizedSchema {
/**
* We are deprecating the compiler and the buildable options.
* However, we want to keep the existing behavior for now.
*
* So, if the user has not provided a bundler, we will use the compiler option, if any.
*
* If the user has not provided a bundler and no compiler, but has set buildable to true,
* we will use tsc, since that is the compiler the old generator used to default to, if buildable was true
* and no compiler was provided.
*
* If the user has not provided a bundler and no compiler, and has not set buildable to true, then
* set the bundler to tsc, to preserve old default behaviour (buildable: true by default).
*
* If it's publishable, we need to build the code before publishing it, so again
* we default to `tsc`. In the previous version of this, it would set `buildable` to true
* and that would default to `tsc`.
*
* In the past, the only way to get a non-buildable library was to set buildable to false.
* Now, the only way to get a non-buildble library is to set bundler to none.
* By default, with nothing provided, libraries are buildable with `@nrwl/js:tsc`.
*/
options.bundler = options.bundler ?? options.compiler ?? 'tsc';
if (options.publishable) {
if (!options.importPath) {
throw new Error(
`For publishable libs you have to provide a proper "--importPath" which needs to be a valid npm package name (e.g. my-awesome-lib or @myorg/my-lib)`
);
}
options.buildable = true;
if (options.bundler === 'none') {
options.bundler = 'tsc';
}
}
// This is to preserve old behaviour, buildable: false
if (options.publishable === false && options.buildable === false) {
options.bundler = 'none';
}
const { Linter } = require('@nrwl/linter');
if (options.config === 'npm-scripts') {
options.unitTestRunner = 'none';
options.linter = Linter.None;
options.buildable = false;
options.bundler = 'none';
}
options.compiler ??= 'tsc';
if (options.compiler === 'swc' && options.skipTypeCheck == null) {
if (
(options.bundler === 'swc' || options.bundler === 'rollup') &&
options.skipTypeCheck == null
) {
options.skipTypeCheck = false;
}
@@ -405,6 +459,8 @@ function normalizeOptions(
const importPath =
options.importPath || getImportPath(npmScope, projectDirectory);
options.minimal ??= false;
return {
...options,
fileName,
@@ -449,18 +505,26 @@ function addProjectDependencies(
);
}
// Vite is being installed in the next step if bundler is vite
// noop
return () => {};
}
function getBuildExecutor(options: NormalizedSchema) {
switch (options.bundler) {
function getBuildExecutor(bundler: Bundler) {
switch (bundler) {
case 'esbuild':
return `@nrwl/esbuild:esbuild`;
case 'rollup':
return `@nrwl/rollup:rollup`;
case 'swc':
case 'tsc':
return `@nrwl/js:${bundler}`;
case 'vite':
return `@nrwl/vite:build`;
case 'none':
default:
return `@nrwl/js:${options.compiler}`;
return undefined;
}
}
+11 -5
View File
@@ -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,19 +108,25 @@
"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"],
@@ -60,9 +60,8 @@ describe('Rename swcrc file migration', () => {
async function setup(tree: Tree) {
await libraryGenerator(tree, {
name: 'my-lib',
compiler: 'swc',
bundler: 'swc',
unitTestRunner: 'jest',
buildable: true,
config: 'project',
});
+2 -1
View File
@@ -7,7 +7,7 @@ import type {
import { TransformerEntry } from './typescript/types';
export type Compiler = 'tsc' | 'swc';
export type Bundler = 'none' | 'rollup' | 'esbuild' | 'vite';
export type Bundler = 'swc' | 'tsc' | 'rollup' | 'vite' | 'esbuild' | 'none';
export interface LibraryGeneratorSchema {
name: string;
@@ -31,6 +31,7 @@ export interface LibraryGeneratorSchema {
compiler?: Compiler;
bundler?: Bundler;
skipTypeCheck?: boolean;
minimal?: boolean;
}
export interface ExecutorOptions {
+9 -4
View File
@@ -4,9 +4,14 @@ import { SwcExecutorOptions } from '../schema';
export function getSwcrcPath(
options: SwcExecutorOptions,
contextRoot: string,
projectRoot: string
projectRoot: string,
temp = false
) {
return options.swcrc
? join(contextRoot, options.swcrc)
: join(contextRoot, projectRoot, '.swcrc');
let swcrcPath = options.swcrc ?? join(projectRoot, '.swcrc');
if (temp) {
swcrcPath = join('tmp', swcrcPath.replace('.swcrc', '.generated.swcrc'));
}
return join(contextRoot, swcrcPath);
}
@@ -52,7 +52,7 @@ export function toJsLibraryGeneratorOptions(
): JsLibraryGeneratorSchema {
return {
name: options.name,
buildable: options.buildable,
bundler: options?.buildable ? 'tsc' : 'none',
directory: options.directory,
importPath: options.importPath,
linter: options.linter,
+3 -3
View File
@@ -1,10 +1,10 @@
import { NextConfigComplete } from 'next/dist/server/config-shared';
import { withNx } from './with-nx';
import { getNextConfig } from './with-nx';
describe('withNx', () => {
describe('svgr', () => {
it('should be used by default', () => {
const config = withNx({});
const config = getNextConfig();
const result = config.webpack(
{
@@ -32,7 +32,7 @@ describe('withNx', () => {
});
it('should not be used when disabled', () => {
const config = withNx({
const config = getNextConfig({
nx: {
svgr: false,
},
+139
View File
@@ -1,5 +1,22 @@
import {
createProjectGraphAsync,
joinPathFragments,
offsetFromRoot,
parseTargetString,
ProjectGraph,
ProjectGraphProjectNode,
Target,
workspaceRoot,
} from '@nrwl/devkit';
import {
calculateProjectDependencies,
DependentBuildableProjectNode,
} from '@nrwl/workspace/src/utilities/buildable-libs-utils';
import type { NextConfig } from 'next';
import path = require('path');
import { createWebpackConfig } from '../src/utils/config';
import { NextBuildBuilderOptions } from '../src/utils/types';
export interface WithNxOptions extends NextConfig {
nx?: {
svgr?: boolean;
@@ -34,7 +51,128 @@ function getWithNxContext(): WithNxContext {
};
}
function getTargetConfig(graph: ProjectGraph, target: Target) {
const projectNode = graph.nodes[target.project];
return projectNode.data.targets[target.target];
}
function getOptions(graph: ProjectGraph, target: Target) {
const targetConfig = getTargetConfig(graph, target);
const options = targetConfig.options;
if (target.configuration) {
Object.assign(options, targetConfig.configurations[target.configuration]);
}
return options;
}
function getNxContext(
graph: ProjectGraph,
target: Target
): {
node: ProjectGraphProjectNode;
options: NextBuildBuilderOptions;
projectName: string;
targetName: string;
configurationName?: string;
} {
const targetConfig = getTargetConfig(graph, target);
if ('@nrwl/next:build' === targetConfig.executor) {
return {
node: graph.nodes[target.project],
options: getOptions(graph, target),
projectName: target.project,
targetName: target.target,
configurationName: target.configuration,
};
}
const targetOptions = getOptions(graph, target);
// If we are running serve or export pull the options from the dependent target first (ex. build)
if (targetOptions.devServerTarget) {
const devServerTarget = parseTargetString(
targetOptions.devServerTarget,
graph
);
return getNxContext(graph, devServerTarget);
} else if (
['@nrwl/next:server', '@nrwl/next:export'].includes(targetConfig.executor)
) {
const buildTarget = parseTargetString(targetOptions.buildTarget, graph);
return getNxContext(graph, buildTarget);
} else {
throw new Error(
'Could not determine the config for this Next application.'
);
}
}
export function withNx(
_nextConfig = {} as WithNxOptions,
context: WithNxContext = getWithNxContext()
): () => Promise<NextConfig> {
return async () => {
let dependencies: DependentBuildableProjectNode[] = [];
const graph = await createProjectGraphAsync();
const originalTarget = {
project: process.env.NX_TASK_TARGET_PROJECT,
target: process.env.NX_TASK_TARGET_TARGET,
configuration: process.env.NX_TASK_TARGET_CONFIGURATION,
};
const {
node: projectNode,
options,
projectName: project,
targetName,
configurationName,
} = getNxContext(graph, originalTarget);
const projectDirectory = projectNode.data.root;
if (!options.buildLibsFromSource && targetName) {
const result = calculateProjectDependencies(
graph,
workspaceRoot,
project,
targetName,
configurationName
);
dependencies = result.dependencies;
}
// Get next config
const nextConfig = getNextConfig(_nextConfig, context);
const outputDir = `${offsetFromRoot(projectDirectory)}${
options.outputPath
}`;
nextConfig.distDir =
nextConfig.distDir && nextConfig.distDir !== '.next'
? joinPathFragments(outputDir, nextConfig.distDir)
: joinPathFragments(outputDir, '.next');
const userWebpackConfig = nextConfig.webpack;
nextConfig.webpack = (a, b) =>
createWebpackConfig(
workspaceRoot,
options.root,
options.fileReplacements,
options.assets,
dependencies,
path.join(workspaceRoot, context.libsDir)
)(userWebpackConfig ? userWebpackConfig(a, b) : a, b);
return nextConfig;
};
}
export function getNextConfig(
nextConfig = {} as WithNxOptions,
context: WithNxContext = getWithNxContext()
): NextConfig {
@@ -223,3 +361,4 @@ function addNxEnvVariables(config: any) {
module.exports = withNx;
// Support for newer generated code: `const { withNx } = require(...);`
module.exports.withNx = withNx;
module.exports.getNextConfig = getNextConfig;
@@ -2,7 +2,6 @@ import 'dotenv/config';
import {
ExecutorContext,
readJsonFile,
workspaceLayout,
workspaceRoot,
writeJsonFile,
} from '@nrwl/devkit';
@@ -12,18 +11,13 @@ import { join, resolve } from 'path';
import { copySync, existsSync, mkdir, writeFileSync } from 'fs-extra';
import { gte } from 'semver';
import { directoryExists } from '@nrwl/workspace/src/utilities/fileutils';
import {
calculateProjectDependencies,
DependentBuildableProjectNode,
} from '@nrwl/workspace/src/utilities/buildable-libs-utils';
import { checkAndCleanWithSemver } from '@nrwl/workspace/src/utilities/version-utils';
import { checkAndCleanWithSemver } from '@nrwl/workspace/src/utils/version-utils';
import { prepareConfig } from '../../utils/config';
import { updatePackageJson } from './lib/update-package-json';
import { createNextConfigFile } from './lib/create-next-config-file';
import { checkPublicDirectory } from './lib/check-project';
import { NextBuildBuilderOptions } from '../../utils/types';
import { PHASE_PRODUCTION_BUILD } from '../../utils/constants';
import { getLockFileName } from 'nx/src/lock-file/lock-file';
export default async function buildExecutor(
@@ -33,23 +27,10 @@ export default async function buildExecutor(
// Cast to any to overwrite NODE_ENV
(process.env as any).NODE_ENV ||= 'production';
let dependencies: DependentBuildableProjectNode[] = [];
const root = resolve(context.root, options.root);
const libsDir = join(context.root, workspaceLayout().libsDir);
checkPublicDirectory(root);
if (!options.buildLibsFromSource && context.targetName) {
const result = calculateProjectDependencies(
context.projectGraph,
context.root,
context.projectName,
context.targetName,
context.configurationName
);
dependencies = result.dependencies;
}
// Set `__NEXT_REACT_ROOT` based on installed ReactDOM version
const packageJsonPath = join(root, 'package.json');
const packageJson = existsSync(packageJsonPath)
@@ -66,15 +47,7 @@ export default async function buildExecutor(
(process.env as any).__NEXT_REACT_ROOT ||= 'true';
}
const config = await prepareConfig(
PHASE_PRODUCTION_BUILD,
options,
context,
dependencies,
libsDir
);
await build(root, config as any);
await build(root);
if (!directoryExists(options.outputPath)) {
mkdir(options.outputPath);
@@ -3,7 +3,7 @@ import {
ExecutorContext,
parseTargetString,
readTargetOptions,
runExecutor,
workspaceLayout,
} from '@nrwl/devkit';
import exportApp from 'next/dist/export';
import { join, resolve } from 'path';
@@ -11,15 +11,19 @@ import {
calculateProjectDependencies,
DependentBuildableProjectNode,
} from '@nrwl/workspace/src/utilities/buildable-libs-utils';
import { workspaceLayout } from '@nrwl/devkit';
import { prepareConfig } from '../../utils/config';
import {
NextBuildBuilderOptions,
NextExportBuilderOptions,
} from '../../utils/types';
import { PHASE_EXPORT } from '../../utils/constants';
import nextTrace = require('next/dist/trace');
import { platform } from 'os';
import { execFileSync } from 'child_process';
import * as chalk from 'chalk';
// platform specific command name
const pmCmd = platform() === 'win32' ? `npx.cmd` : 'npx';
export default async function exportExecutor(
options: NextExportBuilderOptions,
@@ -38,13 +42,18 @@ export default async function exportExecutor(
}
const libsDir = join(context.root, workspaceLayout().libsDir);
const buildTarget = parseTargetString(options.buildTarget);
const build = await runExecutor(buildTarget, {}, context);
const buildTarget = parseTargetString(
options.buildTarget,
context.projectGraph
);
for await (const result of build) {
if (!result.success) {
return result;
}
try {
const args = getBuildTargetCommand(options);
execFileSync(pmCmd, args, {
stdio: [0, 1, 2],
});
} catch {
throw new Error(`Build target failed: ${chalk.bold(options.buildTarget)}`);
}
const buildOptions = readTargetOptions<NextBuildBuilderOptions>(
@@ -52,13 +61,6 @@ export default async function exportExecutor(
context
);
const root = resolve(context.root, buildOptions.root);
const config = await prepareConfig(
PHASE_EXPORT,
buildOptions,
context,
dependencies,
libsDir
);
// Taken from:
// https://github.com/vercel/next.js/blob/ead56eaab68409e96c19f7d9139747bac1197aa9/packages/next/cli/next-export.ts#L13
@@ -72,9 +74,13 @@ export default async function exportExecutor(
threads: options.threads,
outdir: `${buildOptions.outputPath}/exported`,
} as any,
nextExportCliSpan,
config
nextExportCliSpan
);
return { success: true };
}
function getBuildTargetCommand(options: NextExportBuilderOptions) {
const cmd = ['nx', 'run', options.buildTarget];
return cmd;
}
@@ -5,14 +5,11 @@ import {
parseTargetString,
readTargetOptions,
runExecutor,
workspaceLayout,
} from '@nrwl/devkit';
import * as chalk from 'chalk';
import { existsSync } from 'fs';
import { join, resolve } from 'path';
import { calculateProjectDependencies } from '@nrwl/workspace/src/utilities/buildable-libs-utils';
import { prepareConfig } from '../../utils/config';
import {
NextBuildBuilderOptions,
NextServeBuilderOptions,
@@ -22,10 +19,6 @@ import {
} from '../../utils/types';
import { customServer } from './lib/custom-server';
import { defaultServer } from './lib/default-server';
import {
PHASE_DEVELOPMENT_SERVER,
PHASE_PRODUCTION_SERVER,
} from '../../utils/constants';
const infoPrefix = `[ ${chalk.dim(chalk.cyan('info'))} ] `;
@@ -48,42 +41,16 @@ export default async function* serveExecutor(
context
);
const root = resolve(context.root, buildOptions.root);
const config = await prepareConfig(
options.dev ? PHASE_DEVELOPMENT_SERVER : PHASE_PRODUCTION_SERVER,
buildOptions,
context,
getDependencies(options, context),
join(context.root, workspaceLayout().libsDir)
);
if (options.customServerTarget) {
yield* runCustomServer(root, config, options, buildOptions, context);
yield* runCustomServer(root, options, buildOptions, context);
} else {
yield* runNextDevServer(root, config, options, buildOptions, context);
}
}
function getDependencies(
options: NextServeBuilderOptions,
context: ExecutorContext
) {
if (options.buildLibsFromSource) {
return [];
} else {
const result = calculateProjectDependencies(
context.projectGraph,
context.root,
context.projectName,
'build', // should be generalized
context.configurationName
);
return result.dependencies;
yield* runNextDevServer(root, options, buildOptions, context);
}
}
async function* runNextDevServer(
root: string,
config: ReturnType<typeof prepareConfig>,
options: NextServeBuilderOptions,
buildOptions: NextBuildBuilderOptions,
context: ExecutorContext
@@ -94,7 +61,6 @@ async function* runNextDevServer(
dir: root,
staticMarkup: options.staticMarkup,
quiet: options.quiet,
conf: config,
port: options.port,
customServer: !!options.customServerTarget,
hostname: options.hostname || 'localhost',
@@ -144,7 +110,6 @@ async function* runNextDevServer(
async function* runCustomServer(
root: string,
config: ReturnType<typeof prepareConfig>,
options: NextServeBuilderOptions,
buildOptions: NextBuildBuilderOptions,
context: ExecutorContext
+16 -91
View File
@@ -1,14 +1,8 @@
import 'nx/src/utils/testing/mock-fs';
import { createWebpackConfig, prepareConfig } from './config';
import { NextBuildBuilderOptions } from '@nrwl/next';
import { dirname } from 'path';
import { createWebpackConfig } from './config';
import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin';
import { PHASE_PRODUCTION_BUILD } from './constants';
jest.mock('@nrwl/webpack', () => ({
createCopyPlugin: () => {},
}));
jest.mock('@nrwl/webpack', () => ({}));
jest.mock('tsconfig-paths-webpack-plugin');
jest.mock('next/dist/server/config', () => ({
__esModule: true,
@@ -57,97 +51,28 @@ describe('Next.js webpack config builder', () => {
});
});
it('should set the rules', () => {
it('should add rules for ts', () => {
const webpackConfig = createWebpackConfig('/root', 'apps/wibble', []);
const config = webpackConfig(
{ resolve: { alias: {} }, module: { rules: [] }, plugins: [] },
{
resolve: { alias: {} },
module: {
rules: [
{
test: /\.*.ts/,
loader: 'some-ts-loader',
},
],
},
plugins: [],
},
{ defaultLoaders: {} }
);
// not much value in checking what they are
// just check they get added
expect(config.module.rules.length).toBe(1);
});
});
describe('prepareConfig', () => {
it('should set the dist directory', async () => {
const config = await prepareConfig(
PHASE_PRODUCTION_BUILD,
{
root: 'apps/wibble',
outputPath: 'dist/apps/wibble',
fileReplacements: [],
},
{ root: '/root' } as any,
[],
''
);
expect(config).toEqual(
expect.objectContaining({
distDir: '../../dist/apps/wibble/.next',
})
);
});
it('should support nextConfig option to customize the config', async () => {
const fullPath = require.resolve('./config.fixture');
const rootPath = dirname(fullPath);
const config = await prepareConfig(
PHASE_PRODUCTION_BUILD,
{
root: 'apps/wibble',
outputPath: 'dist/apps/wibble',
fileReplacements: [],
nextConfig: 'config.fixture',
customValue: 'test',
} as NextBuildBuilderOptions,
{ root: rootPath } as any,
[],
''
);
expect(config).toMatchObject({
myCustomValue: 'test',
});
});
it('should provide error message when nextConfig path is invalid', async () => {
await expect(() =>
prepareConfig(
PHASE_PRODUCTION_BUILD,
{
root: 'apps/wibble',
outputPath: 'dist/apps/wibble',
fileReplacements: [],
nextConfig: 'config-does-not-exist.fixture',
customValue: 'test',
} as NextBuildBuilderOptions,
{ root: '/root' } as any,
[],
''
)
).rejects.toThrow(/Could not find file/);
});
it('should provide error message when nextConfig does not export a function', async () => {
await expect(() =>
prepareConfig(
PHASE_PRODUCTION_BUILD,
{
root: 'apps/wibble',
outputPath: 'dist/apps/wibble',
fileReplacements: [],
nextConfig: require.resolve('./config-not-a-function.fixture'),
customValue: 'test',
} as NextBuildBuilderOptions,
{ root: '/root' } as any,
[],
''
)
).rejects.toThrow(/option does not export a function/);
expect(config.module.rules.length).toBe(2);
});
});
});
+27 -90
View File
@@ -1,31 +1,13 @@
import {
ExecutorContext,
joinPathFragments,
offsetFromRoot,
} from '@nrwl/devkit';
// ignoring while we support both Next 11.1.0 and versions before it
// @ts-ignore
import type { NextConfig } from 'next/dist/server/config-shared';
// @ts-ignore
import type {
PHASE_DEVELOPMENT_SERVER,
PHASE_EXPORT,
PHASE_PRODUCTION_BUILD,
PHASE_PRODUCTION_SERVER,
} from 'next/dist/shared/lib/constants';
import { join, resolve } from 'path';
import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin';
import { Configuration } from 'webpack';
import { FileReplacement, NextBuildBuilderOptions } from './types';
import { Configuration, RuleSetRule } from 'webpack';
import { FileReplacement } from './types';
import { createCopyPlugin, normalizeAssets } from '@nrwl/webpack';
import { WithNxOptions } from '../../plugins/with-nx';
import {
createTmpTsConfig,
DependentBuildableProjectNode,
} from '@nrwl/workspace/src/utilities/buildable-libs-utils';
const loadConfig = require('next/dist/server/config').default;
export function createWebpackConfig(
workspaceRoot: string,
projectRoot: string,
@@ -38,12 +20,10 @@ export function createWebpackConfig(
config: Configuration,
{
isServer,
defaultLoaders,
}: {
buildId: string;
dev: boolean;
isServer: boolean;
defaultLoaders: any;
}
): Configuration {
const mainFields = ['es2015', 'module', 'main'];
@@ -76,12 +56,24 @@ export function createWebpackConfig(
return alias;
}, config.resolve.alias);
config.module.rules.push({
test: /\.([jt])sx?$/,
include: [libsDir],
exclude: /node_modules/,
use: [defaultLoaders.babel],
});
// Apply any rules that work on ts files to the libsDir as well
const rulesToAdd = [];
for (const r of config.module.rules) {
if (typeof r === 'string') {
continue;
}
if (isTsRule(r)) {
rulesToAdd.push({ ...r, include: [libsDir] });
} else if (r.oneOf && r.oneOf.find(isTsRule)) {
rulesToAdd.push({
...r,
oneOf: r.oneOf
.filter(isTsRule)
.map((subRule) => ({ ...subRule, include: [libsDir] })),
});
}
}
config.module.rules.push(...rulesToAdd);
// Copy (shared) assets to `public` folder during client-side compilation
if (!isServer && Array.isArray(assets) && assets.length > 0) {
@@ -99,68 +91,13 @@ export function createWebpackConfig(
};
}
export async function prepareConfig(
phase:
| typeof PHASE_PRODUCTION_BUILD
| typeof PHASE_EXPORT
| typeof PHASE_DEVELOPMENT_SERVER
| typeof PHASE_PRODUCTION_SERVER,
options: NextBuildBuilderOptions,
context: ExecutorContext,
dependencies: DependentBuildableProjectNode[],
libsDir: string
) {
const config = (await loadConfig(phase, options.root, null)) as NextConfig &
WithNxOptions;
const userWebpack = config.webpack;
const userNextConfig = getConfigEnhancer(options.nextConfig, context.root);
// Yes, these do have different capitalisation...
const outputDir = `${offsetFromRoot(options.root)}${options.outputPath}`;
config.distDir =
config.distDir && config.distDir !== '.next'
? joinPathFragments(outputDir, config.distDir)
: joinPathFragments(outputDir, '.next');
config.webpack = (a, b) =>
createWebpackConfig(
context.root,
options.root,
options.fileReplacements,
options.assets,
dependencies,
libsDir
)(userWebpack ? userWebpack(a, b) : a, b);
if (typeof userNextConfig !== 'function') {
throw new Error(
`Module specified by 'nextConfig' option does not export a function. It should be of form 'module.exports = (phase, config, options) => config;'`
);
function isTsRule(r: RuleSetRule): boolean {
if (typeof r === 'string') {
return false;
}
if (!(r.test instanceof RegExp)) {
return false;
}
return userNextConfig(phase, config, { options });
}
function getConfigEnhancer(
pluginPath: undefined | string,
workspaceRoot: string
) {
if (!pluginPath) {
return (_, x) => x;
}
let fullPath: string;
try {
fullPath = require.resolve(pluginPath);
} catch {
fullPath = join(workspaceRoot, pluginPath);
}
try {
return require(fullPath);
} catch {
throw new Error(
`Could not find file specified by 'nextConfig' option: ${fullPath}`
);
}
return r.test.test('a.ts');
}

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