Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c4c052446 | |||
| 923cba8422 | |||
| 83743801d5 | |||
| 2555490762 | |||
| 3e6f2227ae | |||
| eb7c7bffeb | |||
| bf3fc41196 | |||
| 87b1a73b4c | |||
| ee0c88f766 | |||
| fa037aa528 | |||
| d7d7f163a4 | |||
| dd7eff1072 | |||
| 739c427351 | |||
| f342baca5a | |||
| fd71c6e288 | |||
| d30413311b | |||
| 0761e4c7a9 | |||
| d15733d00e | |||
| eba45a9f5b | |||
| 68d9df74f6 | |||
| 1b4ac4b63e | |||
| 2959f7f033 | |||
| f997744b4d | |||
| 04a38649b3 | |||
| c351b34f62 | |||
| f111580f28 | |||
| bf9844ec72 | |||
| 6c86cb770c | |||
| c06edd9426 | |||
| 1c7406379b | |||
| f1de248ff7 | |||
| 5ba8d5aa12 | |||
| 18ff2dbcd4 | |||
| e9a59d60af | |||
| 908cf41d74 | |||
| 2f8ca66797 |
@@ -1153,6 +1153,14 @@
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Managing your Global Nx Installation",
|
||||
"path": "/more-concepts/global-nx",
|
||||
"id": "global-nx",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"disableCollapsible": false
|
||||
@@ -1317,6 +1325,14 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Managing your Global Nx Installation",
|
||||
"path": "/more-concepts/global-nx",
|
||||
"id": "global-nx",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "All Recipes »",
|
||||
"path": "/recipes",
|
||||
|
||||
@@ -1434,6 +1434,16 @@
|
||||
"isExternal": false,
|
||||
"path": "/more-concepts/encapsulated-nx-and-the-wrapper",
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"id": "global-nx",
|
||||
"name": "Managing your Global Nx Installation",
|
||||
"description": "",
|
||||
"file": "shared/guides/global-nx",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/more-concepts/global-nx",
|
||||
"tags": []
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
@@ -1640,6 +1650,16 @@
|
||||
"path": "/more-concepts/encapsulated-nx-and-the-wrapper",
|
||||
"tags": []
|
||||
},
|
||||
"/more-concepts/global-nx": {
|
||||
"id": "global-nx",
|
||||
"name": "Managing your Global Nx Installation",
|
||||
"description": "",
|
||||
"file": "shared/guides/global-nx",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/more-concepts/global-nx",
|
||||
"tags": []
|
||||
},
|
||||
"/recipes": {
|
||||
"id": "all",
|
||||
"name": "All Recipes »",
|
||||
|
||||
@@ -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"],
|
||||
"enum": ["swc", "tsc", "rollup", "vite", "esbuild", "none"],
|
||||
"default": "none",
|
||||
"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{% /tabs %}\n",
|
||||
"presets": []
|
||||
},
|
||||
"aliases": ["lib"],
|
||||
|
||||
@@ -489,6 +489,11 @@
|
||||
"name": "Encapsulated Nx and the Nx Wrapper",
|
||||
"id": "encapsulated-nx-and-the-wrapper",
|
||||
"file": "shared/guides/encapsulated-nx-and-the-wrapper"
|
||||
},
|
||||
{
|
||||
"name": "Managing your Global Nx Installation",
|
||||
"id": "global-nx",
|
||||
"file": "shared/guides/global-nx"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
# Managing your Global Nx Installation
|
||||
|
||||
Nx can be ran in a total of 3 ways:
|
||||
|
||||
- Through your package manager (e.g. `npx nx`, `yarn nx`, or `pnpm exec nx`)
|
||||
- Through an [encapsulated install](/more-concepts/encapsulated-nx-and-the-wrapper) (e.g. `./nx` or `./nx.bat`)
|
||||
- Through a global Nx installation (e.g. `nx`)
|
||||
|
||||
With a global Nx installation, Nx looks for the local copy of Nx in your repo and hands off the process execution to it. This means that whichever version of Nx is installed locally in your repo is still the version of Nx that runs your code. For the most part, this can eliminate any issues that may arise from the global install being outdated.
|
||||
|
||||
However, there are still cases where an issue could arise. If the structure of your Nx workspace no longer matches up with what the globally installed copy of Nx expects, it may fail to hand off to your local installation properly and instead error. This commonly results in errors such as:
|
||||
|
||||
- Could not find Nx modules in this workspace.
|
||||
- The current directory isn't part of an Nx workspace.
|
||||
|
||||
If you find yourself in this position, you will need to update your global install of Nx.
|
||||
|
||||
## Updating your global Nx installation
|
||||
|
||||
Exactly how you do this will depend on which package manager you originally installed Nx with.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="npm" %}
|
||||
|
||||
```shell
|
||||
npm install --global nx@latest
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="yarn" %}
|
||||
|
||||
```shell
|
||||
yarn global add nx@latest
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="pnpm" %}
|
||||
|
||||
```shell
|
||||
pnpm install --global nx@latest
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
If you cannot remember which package manager you installed Nx globally with or are still encountering issues, you can locate other installs of Nx with these commands:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="npm" %}
|
||||
|
||||
```shell
|
||||
npm list --global nx
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="yarn" %}
|
||||
|
||||
```shell
|
||||
yarn global list nx
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="pnpm" %}
|
||||
|
||||
```shell
|
||||
pnpm list --global nx
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
You can then remove the extra global installations by running the following commands for the duplicate installations:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="npm" %}
|
||||
|
||||
```shell
|
||||
npm rm --global nx
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="yarn" %}
|
||||
|
||||
```shell
|
||||
yarn global remove nx
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="pnpm" %}
|
||||
|
||||
```shell
|
||||
pnpm rm --global nx
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Finally, to complete your global installation update, simply reinstall it with the package manager of your choosing:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="npm" %}
|
||||
|
||||
```shell
|
||||
npm install --global nx@latest
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="yarn" %}
|
||||
|
||||
```shell
|
||||
yarn global add nx@latest
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="pnpm" %}
|
||||
|
||||
```shell
|
||||
pnpm install --global nx@latest
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
@@ -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`
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ describe('EsBuild Plugin', () => {
|
||||
const parentLib = uniq('parent-lib');
|
||||
const childLib = uniq('child-lib');
|
||||
runCLI(`generate @nrwl/js:lib ${parentLib} --bundler=esbuild`);
|
||||
runCLI(`generate @nrwl/js:lib ${childLib} --buildable=false`);
|
||||
runCLI(`generate @nrwl/js:lib ${childLib} --bundler=none`);
|
||||
updateFile(
|
||||
`libs/${parentLib}/src/index.ts`,
|
||||
`
|
||||
|
||||
@@ -19,35 +19,39 @@ describe('inlining', () => {
|
||||
afterEach(() => cleanupProject());
|
||||
|
||||
it.each(['tsc', 'swc'])(
|
||||
'should inline libraries with --compiler=%s',
|
||||
async (compiler) => {
|
||||
'should inline libraries with --bundler=%s',
|
||||
async (bundler) => {
|
||||
const parent = uniq('parent');
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${parent} --compiler=${compiler} --no-interactive`
|
||||
`generate @nrwl/js:lib ${parent} --bundler=${bundler} --no-interactive`
|
||||
);
|
||||
|
||||
const buildable = uniq('buildable');
|
||||
runCLI(`generate @nrwl/js:lib ${buildable} --no-interactive`);
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${buildable} --bundler=${bundler} --no-interactive`
|
||||
);
|
||||
|
||||
const buildableTwo = uniq('buildabletwo');
|
||||
runCLI(`generate @nrwl/js:lib ${buildableTwo} --no-interactive`);
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${buildableTwo} --bundler=${bundler} --no-interactive`
|
||||
);
|
||||
|
||||
const nonBuildable = uniq('nonbuildable');
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${nonBuildable} --buildable=false --no-interactive`
|
||||
`generate @nrwl/js:lib ${nonBuildable} --bundler=none --no-interactive`
|
||||
);
|
||||
|
||||
updateFile(`libs/${parent}/src/lib/${parent}.ts`, () => {
|
||||
return `
|
||||
import { ${buildable} } from '@${scope}/${buildable}';
|
||||
import { ${buildableTwo} } from '@${scope}/${buildableTwo}';
|
||||
import { ${nonBuildable} } from '@${scope}/${nonBuildable}';
|
||||
import { ${buildable} } from '@${scope}/${buildable}';
|
||||
import { ${buildableTwo} } from '@${scope}/${buildableTwo}';
|
||||
import { ${nonBuildable} } from '@${scope}/${nonBuildable}';
|
||||
|
||||
export function ${parent}() {
|
||||
${buildable}();
|
||||
${buildableTwo}();
|
||||
${nonBuildable}();
|
||||
}
|
||||
export function ${parent}() {
|
||||
${buildable}();
|
||||
${buildableTwo}();
|
||||
${nonBuildable}();
|
||||
}
|
||||
`;
|
||||
});
|
||||
|
||||
@@ -98,33 +102,33 @@ export function ${parent}() {
|
||||
|
||||
it('should inline nesting libraries', async () => {
|
||||
const parent = uniq('parent');
|
||||
runCLI(`generate @nrwl/js:lib ${parent} --no-interactive`);
|
||||
runCLI(`generate @nrwl/js:lib ${parent} --bundler=tsc --no-interactive`);
|
||||
|
||||
const child = uniq('child');
|
||||
runCLI(`generate @nrwl/js:lib ${child} --buildable=false --no-interactive`);
|
||||
runCLI(`generate @nrwl/js:lib ${child} --bundler=none --no-interactive`);
|
||||
|
||||
const grandChild = uniq('grandchild');
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${grandChild} --buildable=false --no-interactive`
|
||||
`generate @nrwl/js:lib ${grandChild} --bundler=none --no-interactive`
|
||||
);
|
||||
|
||||
updateFile(`libs/${parent}/src/lib/${parent}.ts`, () => {
|
||||
return `
|
||||
import { ${child} } from '@${scope}/${child}';
|
||||
import { ${child} } from '@${scope}/${child}';
|
||||
|
||||
export function ${parent}() {
|
||||
${child}();
|
||||
}
|
||||
export function ${parent}() {
|
||||
${child}();
|
||||
}
|
||||
`;
|
||||
});
|
||||
|
||||
updateFile(`libs/${child}/src/lib/${child}.ts`, () => {
|
||||
return `
|
||||
import { ${grandChild} } from '@${scope}/${grandChild}';
|
||||
import { ${grandChild} } from '@${scope}/${grandChild}';
|
||||
|
||||
export function ${child}() {
|
||||
${grandChild}();
|
||||
}
|
||||
export function ${child}() {
|
||||
${grandChild}();
|
||||
}
|
||||
`;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { satisfies } from 'semver';
|
||||
import { execSync } from 'child_process';
|
||||
import {
|
||||
checkFilesDoNotExist,
|
||||
checkFilesExist,
|
||||
@@ -21,13 +23,14 @@ describe('js e2e', () => {
|
||||
scope = newProject();
|
||||
});
|
||||
|
||||
afterEach(() => cleanupProject());
|
||||
afterEach(() => {
|
||||
cleanupProject();
|
||||
});
|
||||
|
||||
it('should create libs with js executors (--compiler=swc)', async () => {
|
||||
it('should create libs with js executors (--bundler=swc)', async () => {
|
||||
const lib = uniq('lib');
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${lib} --buildable --compiler=swc --no-interactive`
|
||||
);
|
||||
runCLI(`generate @nrwl/js:lib ${lib} --bundler=swc --no-interactive`);
|
||||
|
||||
const libPackageJson = readJson(`libs/${lib}/package.json`);
|
||||
expect(libPackageJson.scripts).toBeUndefined();
|
||||
|
||||
@@ -45,9 +48,7 @@ describe('js e2e', () => {
|
||||
checkFilesDoNotExist(`libs/${lib}/.babelrc`);
|
||||
|
||||
const parentLib = uniq('parentlib');
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${parentLib} --buildable --compiler=swc --no-interactive`
|
||||
);
|
||||
runCLI(`generate @nrwl/js:lib ${parentLib} --bundler=swc --no-interactive`);
|
||||
const parentLibPackageJson = readJson(`libs/${parentLib}/package.json`);
|
||||
expect(parentLibPackageJson.scripts).toBeUndefined();
|
||||
expect((await runCLIAsync(`test ${parentLib}`)).combinedOutput).toContain(
|
||||
@@ -103,7 +104,7 @@ describe('js e2e', () => {
|
||||
const swcHelpersFromDist = readJson(`dist/libs/${lib}/package.json`)
|
||||
.peerDependencies['@swc/helpers'];
|
||||
|
||||
expect(swcHelpersFromDist).toEqual(swcHelpersFromRoot);
|
||||
expect(satisfies(swcHelpersFromDist, swcHelpersFromRoot)).toBeTruthy();
|
||||
|
||||
updateJson(`libs/${lib}/.swcrc`, (json) => {
|
||||
json.jsc.externalHelpers = false;
|
||||
@@ -116,4 +117,50 @@ describe('js e2e', () => {
|
||||
'peerDependencies.@swc/helpers'
|
||||
);
|
||||
}, 240_000);
|
||||
|
||||
it('should handle swcrc path mappings', async () => {
|
||||
const lib = uniq('lib');
|
||||
runCLI(`generate @nrwl/js:lib ${lib} --bundler=swc --no-interactive`);
|
||||
|
||||
// add a dummy x.ts file for path mappings
|
||||
updateFile(
|
||||
`libs/${lib}/src/x.ts`,
|
||||
`
|
||||
export function x() {
|
||||
console.log('x');
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
// update .swcrc to use path mappings
|
||||
updateJson(`libs/${lib}/.swcrc`, (json) => {
|
||||
json.jsc.paths = {
|
||||
'src/*': ['src/*'],
|
||||
};
|
||||
return json;
|
||||
});
|
||||
|
||||
// update lib.ts to use x
|
||||
updateFile(`libs/${lib}/src/lib/${lib}.ts`, () => {
|
||||
return `
|
||||
// @ts-ignore
|
||||
import { x } from 'src/x';
|
||||
|
||||
export function myLib() {
|
||||
console.log(x());
|
||||
}
|
||||
|
||||
myLib();
|
||||
`;
|
||||
});
|
||||
|
||||
// now run build
|
||||
runCLI(`build ${lib}`);
|
||||
|
||||
// invoke the lib with node
|
||||
const result = execSync(`node dist/libs/${lib}/src/lib/${lib}.js`, {
|
||||
cwd: tmpProjPath(),
|
||||
}).toString();
|
||||
expect(result).toContain('x');
|
||||
}, 240_000);
|
||||
});
|
||||
|
||||
@@ -28,9 +28,7 @@ describe('js e2e', () => {
|
||||
|
||||
it('should create libs with js executors (--compiler=tsc)', async () => {
|
||||
const lib = uniq('lib');
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${lib} --buildable --compiler=tsc --no-interactive`
|
||||
);
|
||||
runCLI(`generate @nrwl/js:lib ${lib} --bundler=tsc --no-interactive`);
|
||||
const libPackageJson = readJson(`libs/${lib}/package.json`);
|
||||
expect(libPackageJson.scripts).toBeUndefined();
|
||||
|
||||
@@ -90,9 +88,7 @@ describe('js e2e', () => {
|
||||
libBuildProcess.kill();
|
||||
|
||||
const parentLib = uniq('parentlib');
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${parentLib} --buildable --compiler=tsc --no-interactive`
|
||||
);
|
||||
runCLI(`generate @nrwl/js:lib ${parentLib} --bundler=tsc --no-interactive`);
|
||||
const parentLibPackageJson = readJson(`libs/${parentLib}/package.json`);
|
||||
expect(parentLibPackageJson.scripts).toBeUndefined();
|
||||
expect((await runCLIAsync(`test ${parentLib}`)).combinedOutput).toContain(
|
||||
@@ -137,8 +133,11 @@ describe('js e2e', () => {
|
||||
const rootPackageJson = readJson(`package.json`);
|
||||
|
||||
expect(
|
||||
readJson(`dist/libs/${lib}/package.json`).peerDependencies.tslib
|
||||
).toEqual(rootPackageJson.dependencies.tslib);
|
||||
satisfies(
|
||||
readJson(`dist/libs/${lib}/package.json`).peerDependencies.tslib,
|
||||
rootPackageJson.dependencies.tslib
|
||||
)
|
||||
).toBeTruthy();
|
||||
|
||||
updateJson(`libs/${lib}/tsconfig.json`, (json) => {
|
||||
json.compilerOptions = { ...json.compilerOptions, importHelpers: false };
|
||||
@@ -170,9 +169,7 @@ describe('package.json updates', () => {
|
||||
it('should update package.json with detected dependencies', async () => {
|
||||
const pmc = getPackageManagerCommand();
|
||||
const lib = uniq('lib');
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${lib} --buildable --compiler=tsc --no-interactive`
|
||||
);
|
||||
runCLI(`generate @nrwl/js:lib ${lib} --bundler=tsc --no-interactive`);
|
||||
|
||||
// Add a dependency for this lib to check the built package.json
|
||||
runCommand(`${pmc.addProd} react`);
|
||||
|
||||
@@ -37,10 +37,10 @@ describe('js e2e', () => {
|
||||
|
||||
it('should allow wildcard ts path alias', async () => {
|
||||
const base = uniq('base');
|
||||
runCLI(`generate @nrwl/js:lib ${base} --buildable --no-interactive`);
|
||||
runCLI(`generate @nrwl/js:lib ${base} --bundler=tsc --no-interactive`);
|
||||
|
||||
const lib = uniq('lib');
|
||||
runCLI(`generate @nrwl/js:lib ${lib} --buildable --no-interactive`);
|
||||
runCLI(`generate @nrwl/js:lib ${lib} --bundler=tsc --no-interactive`);
|
||||
|
||||
updateFile(`libs/${base}/src/index.ts`, () => {
|
||||
return `
|
||||
|
||||
+32
-16
@@ -27,6 +27,7 @@ import {
|
||||
import { exec, execSync } from 'child_process';
|
||||
import * as http from 'http';
|
||||
import { getLockFileName } from 'nx/src/lock-file/lock-file';
|
||||
import { satisfies } from 'semver';
|
||||
|
||||
function getData(port, path = '/api'): Promise<any> {
|
||||
return new Promise((resolve) => {
|
||||
@@ -263,21 +264,36 @@ describe('Build Node apps', () => {
|
||||
})
|
||||
);
|
||||
|
||||
expect(packageJson.dependencies['@nestjs/common']).toEqual(
|
||||
rootPackageJson.dependencies['@nestjs/common']
|
||||
);
|
||||
expect(packageJson.dependencies['@nestjs/core']).toEqual(
|
||||
rootPackageJson.dependencies['@nestjs/core']
|
||||
);
|
||||
expect(packageJson.dependencies['reflect-metadata']).toEqual(
|
||||
rootPackageJson.dependencies['reflect-metadata']
|
||||
);
|
||||
expect(packageJson.dependencies['rxjs']).toEqual(
|
||||
rootPackageJson.dependencies['rxjs']
|
||||
);
|
||||
expect(packageJson.dependencies['tslib']).toEqual(
|
||||
rootPackageJson.dependencies['tslib']
|
||||
);
|
||||
expect(
|
||||
satisfies(
|
||||
packageJson.dependencies['@nestjs/common'],
|
||||
rootPackageJson.dependencies['@nestjs/common']
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
satisfies(
|
||||
packageJson.dependencies['@nestjs/core'],
|
||||
rootPackageJson.dependencies['@nestjs/core']
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
satisfies(
|
||||
packageJson.dependencies['reflect-metadata'],
|
||||
rootPackageJson.dependencies['reflect-metadata']
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
satisfies(
|
||||
packageJson.dependencies['rxjs'],
|
||||
rootPackageJson.dependencies['rxjs']
|
||||
)
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
satisfies(
|
||||
packageJson.dependencies['tslib'],
|
||||
rootPackageJson.dependencies['tslib']
|
||||
)
|
||||
).toBeTruthy();
|
||||
|
||||
checkFilesExist(
|
||||
`dist/apps/${nestapp}/${packageManagerLockFile[packageManager]}`
|
||||
@@ -290,7 +306,7 @@ describe('Build Node apps', () => {
|
||||
runCLI(`generate @nrwl/node:app ${nodeapp} --bundler=webpack`);
|
||||
|
||||
const jslib = uniq('jslib');
|
||||
runCLI(`generate @nrwl/js:lib ${jslib} --buildable`);
|
||||
runCLI(`generate @nrwl/js:lib ${jslib} --bundler=tsc`);
|
||||
|
||||
updateFile(
|
||||
`apps/${nodeapp}/src/main.ts`,
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
cleanupProject,
|
||||
getPublishedVersion,
|
||||
uniq,
|
||||
readJson,
|
||||
readFile,
|
||||
} from '@nrwl/e2e/utils';
|
||||
import { bold } from 'chalk';
|
||||
|
||||
@@ -104,6 +106,161 @@ describe('encapsulated nx', () => {
|
||||
).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;
|
||||
});
|
||||
runEncapsulatedNx(
|
||||
'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
|
||||
runEncapsulatedNx('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() {
|
||||
|
||||
@@ -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`);
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -35,8 +35,8 @@ describe('Nx Affected and Graph Tests', () => {
|
||||
const mypublishablelib = uniq('mypublishablelib');
|
||||
runCLI(`generate @nrwl/web:app ${myapp}`);
|
||||
runCLI(`generate @nrwl/web:app ${myapp2}`);
|
||||
runCLI(`generate @nrwl/js:lib ${mylib}`);
|
||||
runCLI(`generate @nrwl/js:lib ${mylib2}`);
|
||||
runCLI(`generate @nrwl/js:lib ${mylib} --bundler=tsc`);
|
||||
runCLI(`generate @nrwl/js:lib ${mylib2} --bundler=tsc`);
|
||||
runCLI(
|
||||
`generate @nrwl/js:lib ${mypublishablelib} --publishable --importPath=@${proj}/${mypublishablelib}`
|
||||
);
|
||||
@@ -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"`);
|
||||
@@ -197,7 +195,7 @@ describe('Nx Affected and Graph Tests', () => {
|
||||
function generateAll() {
|
||||
runCLI(`generate @nrwl/web:app ${myapp}`);
|
||||
runCLI(`generate @nrwl/web:app ${myapp2}`);
|
||||
runCLI(`generate @nrwl/js:lib ${mylib}`);
|
||||
runCLI(`generate @nrwl/js:lib ${mylib} --bundler=tsc`);
|
||||
runCommand(`git add . && git commit -am "add all"`);
|
||||
}
|
||||
|
||||
@@ -213,7 +211,7 @@ describe('Nx Affected and Graph Tests', () => {
|
||||
expect(runCLI('affected:apps')).toContain(myapp2);
|
||||
runCommand(`git add . && git commit -am "add ${myapp2}"`);
|
||||
|
||||
runCLI(`generate @nrwl/js:lib ${mylib}`);
|
||||
runCLI(`generate @nrwl/js:lib ${mylib} --bundler=tsc`);
|
||||
expect(runCLI('affected:apps')).not.toContain(myapp);
|
||||
expect(runCLI('affected:apps')).not.toContain(myapp2);
|
||||
expect(runCLI('affected:libs')).toContain(mylib);
|
||||
@@ -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', () => {
|
||||
@@ -278,8 +294,8 @@ describe('Nx Affected and Graph Tests', () => {
|
||||
|
||||
runCLI(`generate @nrwl/web:app ${myapp}`);
|
||||
runCLI(`generate @nrwl/web:app ${myapp2}`);
|
||||
runCLI(`generate @nrwl/js:lib ${mylib}`);
|
||||
runCLI(`generate @nrwl/js:lib ${mylib2}`);
|
||||
runCLI(`generate @nrwl/js:lib ${mylib} --bundler=tsc`);
|
||||
runCLI(`generate @nrwl/js:lib ${mylib2} --bundler=tsc`);
|
||||
runCLI(`generate @nrwl/js:lib ${mypublishablelib}`);
|
||||
|
||||
const app1ElementSpec = readFile(
|
||||
@@ -397,8 +413,8 @@ describe('Nx Affected and Graph Tests', () => {
|
||||
runCLI(`generate @nrwl/web:app ${myapp}`);
|
||||
runCLI(`generate @nrwl/web:app ${myapp2}`);
|
||||
runCLI(`generate @nrwl/web:app ${myapp3}`);
|
||||
runCLI(`generate @nrwl/js:lib ${mylib}`);
|
||||
runCLI(`generate @nrwl/js:lib ${mylib2}`);
|
||||
runCLI(`generate @nrwl/js:lib ${mylib} --bundler=tsc`);
|
||||
runCLI(`generate @nrwl/js:lib ${mylib2} --bundler=tsc`);
|
||||
|
||||
runCommand(`git init`);
|
||||
runCommand(`git config user.email "test@test.com"`);
|
||||
|
||||
@@ -439,9 +439,9 @@ describe('Nx Running Tests', () => {
|
||||
const libD = uniq('libd-rand');
|
||||
|
||||
runCLI(`generate @nrwl/web:app ${appA}`);
|
||||
runCLI(`generate @nrwl/js:lib ${libA} --buildable --defaults`);
|
||||
runCLI(`generate @nrwl/js:lib ${libB} --buildable --defaults`);
|
||||
runCLI(`generate @nrwl/js:lib ${libC} --buildable --defaults`);
|
||||
runCLI(`generate @nrwl/js:lib ${libA} --bundler=tsc --defaults`);
|
||||
runCLI(`generate @nrwl/js:lib ${libB} --bundler=tsc --defaults`);
|
||||
runCLI(`generate @nrwl/js:lib ${libC} --bundler=tsc --defaults`);
|
||||
runCLI(`generate @nrwl/node:lib ${libD} --defaults`);
|
||||
|
||||
// libA depends on libC
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
checkFilesDoNotExist,
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
getSize,
|
||||
killPorts,
|
||||
newProject,
|
||||
@@ -107,7 +108,7 @@ describe('Build React libraries and apps', () => {
|
||||
|
||||
afterEach(() => {
|
||||
killPorts();
|
||||
// cleanupProject();
|
||||
cleanupProject();
|
||||
});
|
||||
|
||||
describe('Buildable libraries', () => {
|
||||
|
||||
@@ -15,7 +15,7 @@ describe('Rollup Plugin', () => {
|
||||
|
||||
it('should be able to setup project to build node programs with rollup and different compilers', async () => {
|
||||
const myPkg = uniq('my-pkg');
|
||||
runCLI(`generate @nrwl/js:lib ${myPkg} --buildable=false`);
|
||||
runCLI(`generate @nrwl/js:lib ${myPkg} --bundler=none`);
|
||||
updateFile(`libs/${myPkg}/src/index.ts`, `console.log('Hello');\n`);
|
||||
|
||||
// babel (default)
|
||||
|
||||
@@ -0,0 +1,433 @@
|
||||
import { 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, {
|
||||
cwd: tmpProjPath(),
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
env: {
|
||||
...getStrippedEnvironmentVariables(),
|
||||
...childProcessOptions?.env,
|
||||
FORCE_COLOR: 'false',
|
||||
},
|
||||
encoding: 'utf-8',
|
||||
...childProcessOptions,
|
||||
}).toString();
|
||||
if (process.env.NX_VERBOSE_LOGGING) {
|
||||
console.log(r);
|
||||
}
|
||||
return r;
|
||||
} catch (e) {
|
||||
// this is intentional
|
||||
// npm ls fails if package is not found
|
||||
if (!failOnError && (e.stdout || e.stderr)) {
|
||||
return e.stdout?.toString() + e.stderr?.toString();
|
||||
}
|
||||
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);
|
||||
return execSync(pmc.run(`ng g ${packageName}:ng-add`, command ?? ''), {
|
||||
cwd: tmpProjPath(),
|
||||
stdio: 'pipe',
|
||||
env: { ...(opts.env || getStrippedEnvironmentVariables()) },
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
.toString()
|
||||
.replace(
|
||||
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
||||
''
|
||||
);
|
||||
} catch (e) {
|
||||
if (opts.silenceError) {
|
||||
return e.stdout.toString();
|
||||
} else {
|
||||
logError(
|
||||
`Ng Add failed: ${command}`,
|
||||
`${e.stdout?.toString()}\n\n${e.stderr?.toString()}`
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function runCLI(
|
||||
command: string,
|
||||
opts: RunCmdOpts = {
|
||||
silenceError: false,
|
||||
env: undefined,
|
||||
}
|
||||
): string {
|
||||
try {
|
||||
const pm = getPackageManagerCommand();
|
||||
const logs = execSync(`${pm.runNx} ${command}`, {
|
||||
cwd: opts.cwd || tmpProjPath(),
|
||||
env: { CI: 'true', ...getStrippedEnvironmentVariables(), ...opts.env },
|
||||
encoding: 'utf-8',
|
||||
stdio: 'pipe',
|
||||
maxBuffer: 50 * 1024 * 1024,
|
||||
});
|
||||
const r = stripConsoleColors(logs);
|
||||
|
||||
if (isVerbose()) {
|
||||
console.log(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?.toString() + e.stderr?.toString());
|
||||
} else {
|
||||
logError(
|
||||
`Original command: ${command}`,
|
||||
`${e.stdout?.toString()}\n\n${e.stderr?.toString()}`
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function runLernaCLI(
|
||||
command: string,
|
||||
opts: RunCmdOpts = {
|
||||
silenceError: false,
|
||||
env: undefined,
|
||||
}
|
||||
): string {
|
||||
try {
|
||||
const pm = getPackageManagerCommand();
|
||||
const logs = execSync(`${pm.runLerna} ${command}`, {
|
||||
cwd: opts.cwd || tmpProjPath(),
|
||||
env: { CI: 'true', ...(opts.env || getStrippedEnvironmentVariables()) },
|
||||
encoding: 'utf-8',
|
||||
stdio: 'pipe',
|
||||
maxBuffer: 50 * 1024 * 1024,
|
||||
});
|
||||
const r = stripConsoleColors(logs);
|
||||
|
||||
if (isVerbose()) {
|
||||
console.log(logs);
|
||||
}
|
||||
|
||||
return r;
|
||||
} catch (e) {
|
||||
if (opts.silenceError) {
|
||||
return stripConsoleColors(e.stdout?.toString() + e.stderr?.toString());
|
||||
} else {
|
||||
logError(
|
||||
`Original command: ${command}`,
|
||||
`${e.stdout?.toString()}\n\n${e.stderr?.toString()}`
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const KILL_PORT_DELAY = 5000;
|
||||
|
||||
export async function killPort(port: number): Promise<boolean> {
|
||||
if (await portCheck(port)) {
|
||||
try {
|
||||
logInfo(`Attempting to close port ${port}`);
|
||||
await kill(port);
|
||||
await new Promise<void>((resolve) =>
|
||||
setTimeout(() => resolve(), KILL_PORT_DELAY)
|
||||
);
|
||||
if (await portCheck(port)) {
|
||||
logError(`Port ${port} still open`);
|
||||
} else {
|
||||
logSuccess(`Port ${port} successfully closed`);
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
logError(`Port ${port} closing failed`);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export async function killPorts(port?: number): Promise<boolean> {
|
||||
return port
|
||||
? await killPort(port)
|
||||
: (await killPort(3333)) && (await killPort(4200));
|
||||
}
|
||||
|
||||
export function waitUntil(
|
||||
predicate: () => boolean,
|
||||
opts: { timeout: number; ms: number; allowError?: boolean } = {
|
||||
timeout: 5000,
|
||||
ms: 50,
|
||||
}
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const t = setInterval(() => {
|
||||
const run = () => {};
|
||||
try {
|
||||
run();
|
||||
if (predicate()) {
|
||||
clearInterval(t);
|
||||
resolve();
|
||||
}
|
||||
} catch (e) {
|
||||
if (opts.allowError) reject(e);
|
||||
}
|
||||
}, opts.ms);
|
||||
|
||||
setTimeout(() => {
|
||||
clearInterval(t);
|
||||
reject(new Error(`Timed out waiting for condition to return true`));
|
||||
}, opts.timeout);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
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';
|
||||
|
||||
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 (process.env.NX_VERBOSE_LOGGING == 'true') {
|
||||
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}`;
|
||||
}
|
||||
|
||||
const create = execSync(command, {
|
||||
cwd,
|
||||
stdio: isVerbose() ? 'inherit' : 'pipe',
|
||||
env: { CI: 'true', ...process.env },
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
return create ? create.toString() : '';
|
||||
}
|
||||
|
||||
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}`;
|
||||
}
|
||||
|
||||
const create = execSync(command, {
|
||||
cwd: e2eCwd,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
env: process.env,
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
return create ? create.toString() : '';
|
||||
}
|
||||
|
||||
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 install = execSync(
|
||||
`${mode === 'dev' ? pm.addDev : pm.addProd} ${pkgsWithVersions}`,
|
||||
{
|
||||
cwd,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
env: process.env,
|
||||
encoding: 'utf-8',
|
||||
}
|
||||
);
|
||||
return install ? install.toString() : '';
|
||||
}
|
||||
|
||||
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: ['pipe', 'pipe', 'pipe'],
|
||||
env: process.env,
|
||||
encoding: 'utf-8',
|
||||
}).toString();
|
||||
}
|
||||
|
||||
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 (process.env.NX_VERBOSE_LOGGING == 'true') {
|
||||
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 newEncapsulatedNxWorkspace({
|
||||
name = uniq('encapsulated'),
|
||||
pmc = getPackageManagerCommand(),
|
||||
} = {}): (command: string, opts?: Partial<ExecSyncOptions>) => string {
|
||||
projName = name;
|
||||
ensureDirSync(tmpProjPath());
|
||||
runCommand(`${pmc.runUninstalledPackage} nx@latest init --encapsulated`);
|
||||
return (command: string, opts: Partial<ExecSyncOptions>) => {
|
||||
if (process.platform === 'win32') {
|
||||
return runCommand(`./nx.bat ${command}`, { ...opts, failOnError: true });
|
||||
} else {
|
||||
return runCommand(`./nx ${command}`, { ...opts, failOnError: true });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function getProjectName(): string {
|
||||
return projName;
|
||||
}
|
||||
|
||||
export function tmpProjPath(path?: string) {
|
||||
return path ? `${e2eCwd}/${projName}/${path}` : `${e2eCwd}/${projName}`;
|
||||
}
|
||||
|
||||
export function createNonNxProjectDirectory(
|
||||
name = uniq('proj'),
|
||||
addWorkspaces = true
|
||||
) {
|
||||
projName = name;
|
||||
ensureDirSync(tmpProjPath());
|
||||
createFile(
|
||||
'package.json',
|
||||
JSON.stringify({
|
||||
name,
|
||||
workspaces: addWorkspaces ? ['packages/*'] : undefined,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function uniq(prefix: string) {
|
||||
return `${prefix}${Math.floor(Math.random() * 10000000)}`;
|
||||
}
|
||||
|
||||
// Useful in order to cleanup space during CI to prevent `No space left on device` exceptions
|
||||
export function cleanupProject({
|
||||
skipReset,
|
||||
...opts
|
||||
}: RunCmdOpts & { skipReset?: boolean } = {}) {
|
||||
if (isCI) {
|
||||
// Stopping the daemon is not required for tests to pass, but it cleans up background processes
|
||||
if (!skipReset) {
|
||||
runCLI('reset', opts);
|
||||
}
|
||||
try {
|
||||
removeSync(tmpProjPath());
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { parseJson } from '@nrwl/devkit';
|
||||
import {
|
||||
createFileSync,
|
||||
ensureDirSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
removeSync,
|
||||
renameSync,
|
||||
statSync,
|
||||
writeFileSync,
|
||||
} from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import { e2eCwd } from './get-env-info';
|
||||
import { tmpProjPath } from './create-project-utils';
|
||||
import { promisify } from 'util';
|
||||
import * as treeKill from 'tree-kill';
|
||||
|
||||
export function createFile(f: string, content: string = ''): void {
|
||||
const path = tmpProjPath(f);
|
||||
createFileSync(path);
|
||||
if (content) {
|
||||
updateFile(f, content);
|
||||
}
|
||||
}
|
||||
|
||||
export function updateFile(
|
||||
f: string,
|
||||
content: string | ((content: string) => string)
|
||||
): void {
|
||||
ensureDirSync(path.dirname(tmpProjPath(f)));
|
||||
if (typeof content === 'string') {
|
||||
writeFileSync(tmpProjPath(f), content);
|
||||
} else {
|
||||
writeFileSync(
|
||||
tmpProjPath(f),
|
||||
content(readFileSync(tmpProjPath(f)).toString())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function renameFile(f: string, newPath: string): void {
|
||||
ensureDirSync(path.dirname(tmpProjPath(newPath)));
|
||||
renameSync(tmpProjPath(f), tmpProjPath(newPath));
|
||||
}
|
||||
|
||||
export function checkFilesExist(...expectedFiles: string[]) {
|
||||
expectedFiles.forEach((f) => {
|
||||
const ff = f.startsWith('/') ? f : tmpProjPath(f);
|
||||
if (!exists(ff)) {
|
||||
throw new Error(`File '${ff}' does not exist`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function updateJson<T extends object = any, U extends object = T>(
|
||||
f: string,
|
||||
updater: (value: T) => U
|
||||
) {
|
||||
updateFile(f, (s) => {
|
||||
const json = JSON.parse(s);
|
||||
return JSON.stringify(updater(json), null, 2);
|
||||
});
|
||||
}
|
||||
|
||||
export function checkFilesDoNotExist(...expectedFiles: string[]) {
|
||||
expectedFiles.forEach((f) => {
|
||||
const ff = f.startsWith('/') ? f : tmpProjPath(f);
|
||||
if (exists(ff)) {
|
||||
throw new Error(`File '${ff}' should not exist`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function listFiles(dirName: string) {
|
||||
return readdirSync(tmpProjPath(dirName));
|
||||
}
|
||||
|
||||
export function readJson<T extends Object = any>(f: string): T {
|
||||
const content = readFile(f);
|
||||
return parseJson<T>(content);
|
||||
}
|
||||
|
||||
export function readFile(f: string) {
|
||||
const ff = f.startsWith('/') ? f : tmpProjPath(f);
|
||||
return readFileSync(ff, 'utf-8');
|
||||
}
|
||||
|
||||
export function removeFile(f: string) {
|
||||
const ff = f.startsWith('/') ? f : tmpProjPath(f);
|
||||
removeSync(ff);
|
||||
}
|
||||
|
||||
export function rmDist() {
|
||||
removeSync(`${tmpProjPath()}/dist`);
|
||||
}
|
||||
|
||||
export function directoryExists(filePath: string): boolean {
|
||||
try {
|
||||
return statSync(filePath).isDirectory();
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function fileExists(filePath: string): boolean {
|
||||
try {
|
||||
return statSync(filePath).isFile();
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function exists(filePath: string): boolean {
|
||||
return directoryExists(filePath) || fileExists(filePath);
|
||||
}
|
||||
|
||||
export function getSize(filePath: string): number {
|
||||
return statSync(filePath).size;
|
||||
}
|
||||
|
||||
export function tmpBackupProjPath(path?: string) {
|
||||
return path ? `${e2eCwd}/proj-backup/${path}` : `${e2eCwd}/proj-backup`;
|
||||
}
|
||||
|
||||
export const promisifiedTreeKill: (
|
||||
pid: number,
|
||||
signal: string
|
||||
) => Promise<void> = promisify(treeKill);
|
||||
@@ -0,0 +1,122 @@
|
||||
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`).toString().trim();
|
||||
return lernaVersion;
|
||||
}
|
||||
|
||||
export const packageManagerLockFile = {
|
||||
npm: 'package-lock.json',
|
||||
yarn: 'yarn.lock',
|
||||
pnpm: 'pnpm-lock.yaml',
|
||||
};
|
||||
|
||||
export function ensureCypressInstallation() {
|
||||
let cypressVerified = true;
|
||||
try {
|
||||
const r = execSync('npx cypress verify', {
|
||||
stdio: isVerbose() ? 'inherit' : 'pipe',
|
||||
encoding: 'utf-8',
|
||||
cwd: tmpProjPath(),
|
||||
});
|
||||
if (r.indexOf('Verified Cypress!') === -1) {
|
||||
cypressVerified = false;
|
||||
}
|
||||
} catch {
|
||||
cypressVerified = false;
|
||||
} finally {
|
||||
if (!cypressVerified) {
|
||||
e2eConsoleLogger('Cypress was not verified. Installing Cypress now.');
|
||||
execSync('npx cypress install', {
|
||||
stdio: isVerbose() ? 'inherit' : 'pipe',
|
||||
encoding: 'utf-8',
|
||||
cwd: tmpProjPath(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getStrippedEnvironmentVariables() {
|
||||
return Object.fromEntries(
|
||||
Object.entries(process.env).filter(([key, value]) => {
|
||||
if (key.startsWith('NX_E2E_')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key.startsWith('NX_')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
);
|
||||
}
|
||||
+9
-1171
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,47 @@
|
||||
import * as chalk from 'chalk';
|
||||
|
||||
export const E2E_LOG_PREFIX = `${chalk.reset.inverse.bold.keyword('orange')(
|
||||
' E2E '
|
||||
)}`;
|
||||
|
||||
export function e2eConsoleLogger(message: string, body?: string) {
|
||||
process.stdout.write('\n');
|
||||
process.stdout.write(`${E2E_LOG_PREFIX} ${message}\n`);
|
||||
if (body) {
|
||||
process.stdout.write(`${body}\n`);
|
||||
}
|
||||
process.stdout.write('\n');
|
||||
}
|
||||
|
||||
export function logInfo(title: string, body?: string) {
|
||||
const message = `${chalk.reset.inverse.bold.white(
|
||||
' INFO '
|
||||
)} ${chalk.bold.white(title)}`;
|
||||
return e2eConsoleLogger(message, body);
|
||||
}
|
||||
|
||||
export function logError(title: string, body?: string) {
|
||||
const message = `${chalk.reset.inverse.bold.red(' ERROR ')} ${chalk.bold.red(
|
||||
title
|
||||
)}`;
|
||||
return e2eConsoleLogger(message, body);
|
||||
}
|
||||
|
||||
export function logSuccess(title: string, body?: string) {
|
||||
const message = `${chalk.reset.inverse.bold.green(
|
||||
' SUCCESS '
|
||||
)} ${chalk.bold.green(title)}`;
|
||||
return e2eConsoleLogger(message, body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove log colors for fail proof string search
|
||||
* @param log
|
||||
* @returns
|
||||
*/
|
||||
export function stripConsoleColors(log: string): string {
|
||||
return log.replace(
|
||||
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
||||
''
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ProjectConfiguration, Workspaces } from '@nrwl/devkit';
|
||||
import { join } from 'path';
|
||||
import { tmpProjPath } from './create-project-utils';
|
||||
import { readJson, updateFile } from './file-utils';
|
||||
|
||||
export function updateProjectConfig(
|
||||
projectName: string,
|
||||
callback: (c: ProjectConfiguration) => ProjectConfiguration
|
||||
) {
|
||||
const workspace = readResolvedConfiguration();
|
||||
const root = workspace.projects[projectName].root;
|
||||
const path = join(root, 'project.json');
|
||||
const current = readJson(path);
|
||||
updateFile(path, JSON.stringify(callback(current), null, 2));
|
||||
}
|
||||
|
||||
export function readResolvedConfiguration() {
|
||||
process.env.NX_PROJECT_GLOB_CACHE = 'false';
|
||||
const ws = new Workspaces(tmpProjPath());
|
||||
return ws.readProjectsConfigurations();
|
||||
}
|
||||
|
||||
export function readProjectConfig(projectName: string): ProjectConfiguration {
|
||||
const root = readResolvedConfiguration().projects[projectName].root;
|
||||
const path = join(root, 'project.json');
|
||||
return readJson(path);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { joinPathFragments } from '@nrwl/devkit';
|
||||
import {
|
||||
getPackageManagerCommand,
|
||||
runCLI,
|
||||
runCLIAsync,
|
||||
runCommand,
|
||||
} from './command-utils';
|
||||
import { uniq } from './create-project-utils';
|
||||
import { readFile } from './file-utils';
|
||||
|
||||
type GeneratorsWithDefaultTests =
|
||||
| '@nrwl/js:lib'
|
||||
| '@nrwl/node:lib'
|
||||
| '@nrwl/react:lib'
|
||||
| '@nrwl/react:app'
|
||||
| '@nrwl/next:app'
|
||||
| '@nrwl/angular:app'
|
||||
| '@nrwl/workspace:lib'
|
||||
| '@nrwl/web:app';
|
||||
|
||||
/**
|
||||
* Runs the pass in generator and then runs test on
|
||||
* the generated project to make sure the default tests pass.
|
||||
*/
|
||||
export async function expectJestTestsToPass(
|
||||
generator: GeneratorsWithDefaultTests | string
|
||||
) {
|
||||
const name = uniq('proj');
|
||||
const generatedResults = runCLI(
|
||||
`generate ${generator} ${name} --no-interactive`
|
||||
);
|
||||
expect(generatedResults).toContain(`jest.config.ts`);
|
||||
|
||||
const results = await runCLIAsync(`test ${name}`);
|
||||
expect(results.combinedOutput).toContain('Test Suites: 1 passed, 1 total');
|
||||
}
|
||||
|
||||
export function expectTestsPass(v: { stdout: string; stderr: string }) {
|
||||
expect(v.stderr).toContain('Ran all test suites');
|
||||
expect(v.stderr).not.toContain('fail');
|
||||
}
|
||||
|
||||
export function expectNoAngularDevkit() {
|
||||
const { list } = getPackageManagerCommand();
|
||||
const result = runCommand(`${list} @angular-devkit/core`);
|
||||
expect(result).not.toContain('@angular-devkit/core');
|
||||
}
|
||||
|
||||
export function expectNoTsJestInJestConfig(appName: string) {
|
||||
const jestConfig = readFile(
|
||||
joinPathFragments('apps', appName, 'jest.config.ts')
|
||||
);
|
||||
expect(jestConfig).not.toContain('ts-jest');
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
cleanupProject,
|
||||
newProject,
|
||||
readFile,
|
||||
rmDist,
|
||||
runCLI,
|
||||
runCommand,
|
||||
@@ -16,7 +15,7 @@ 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)
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"packages": ["build/packages/*", "build/packages/nx/native-packages/*"],
|
||||
"version": "15.8.1",
|
||||
"version": "15.8.4",
|
||||
"granularPathspec": false,
|
||||
"command": {
|
||||
"publish": {
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
"requires": {
|
||||
"@angular/core": ">=15.0.0"
|
||||
},
|
||||
"description": "Remove browserlist config as it's handled by build-angular",
|
||||
"description": "Remove browserslist config as it's handled by build-angular",
|
||||
"factory": "./src/migrations/update-15-2-0/remove-browserlist-config"
|
||||
},
|
||||
"update-typescript-target": {
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
workspaceRoot,
|
||||
} from '@nrwl/devkit';
|
||||
import { existsSync, lstatSync, mkdirSync, writeFileSync } from 'fs';
|
||||
import { dirname, join, relative } from 'path';
|
||||
import { dirname, join, relative, sep } from 'path';
|
||||
import type { BrowserBuilderSchema } from '../src/builders/webpack-browser/webpack-browser.impl';
|
||||
|
||||
/**
|
||||
@@ -168,13 +168,22 @@ function normalizeBuildTargetOptions(
|
||||
);
|
||||
const buildOptions = withSchemaDefaults(options);
|
||||
|
||||
// polyfill entries might be local files or files that are resolved from node_modules
|
||||
// like zone.js.
|
||||
// prevents error from webpack saying can't find <offset>/zone.js.
|
||||
const handlePolyfillPath = (polyfill: string) => {
|
||||
const maybeFullPath = join(workspaceRoot, polyfill.split('/').join(sep));
|
||||
if (existsSync(maybeFullPath)) {
|
||||
return joinPathFragments(offset, polyfill);
|
||||
}
|
||||
return polyfill;
|
||||
};
|
||||
// paths need to be unix paths for angular devkit
|
||||
buildOptions.polyfills =
|
||||
Array.isArray(buildOptions.polyfills) && buildOptions.polyfills.length > 0
|
||||
? (buildOptions.polyfills as string[]).map((p) =>
|
||||
joinPathFragments(offset, p)
|
||||
)
|
||||
: joinPathFragments(offset, buildOptions.polyfills as string);
|
||||
? (buildOptions.polyfills as string[]).map((p) => handlePolyfillPath(p))
|
||||
: handlePolyfillPath(buildOptions.polyfills as string);
|
||||
|
||||
buildOptions.main = joinPathFragments(offset, buildOptions.main);
|
||||
buildOptions.index =
|
||||
typeof buildOptions.index === 'string'
|
||||
@@ -197,6 +206,7 @@ function normalizeBuildTargetOptions(
|
||||
// then we don't want to have the assets/scripts/styles be included to
|
||||
// prevent inclusion of unintended stuff like tailwind
|
||||
if (
|
||||
buildContext.projectName === ctContext.projectName ||
|
||||
isCtProjectUsingBuildProject(
|
||||
ctContext.projectGraph,
|
||||
buildContext.projectName,
|
||||
|
||||
@@ -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 {}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -21,10 +21,7 @@ import {
|
||||
} from '@nrwl/devkit';
|
||||
import { Linter, lintProjectGenerator } from '@nrwl/linter';
|
||||
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
|
||||
import {
|
||||
initGenerator as jsInitGenerator,
|
||||
getRelativePathToRootTsConfig,
|
||||
} from '@nrwl/js';
|
||||
import { getRelativePathToRootTsConfig } from '@nrwl/js';
|
||||
import {
|
||||
globalJavaScriptOverrides,
|
||||
globalTypeScriptOverrides,
|
||||
|
||||
@@ -39,20 +39,36 @@ export function getTempTailwindPath(context: ExecutorContext) {
|
||||
}
|
||||
|
||||
/**
|
||||
* also returns true if the ct project and build project are the same.
|
||||
* i.e. component testing inside an app.
|
||||
*/
|
||||
* Checks if the childProjectName is a decendent of the parentProjectName
|
||||
* in the project graph
|
||||
**/
|
||||
export function isCtProjectUsingBuildProject(
|
||||
graph: ProjectGraph,
|
||||
parentProjectName: string,
|
||||
childProjectName: string
|
||||
) {
|
||||
return (
|
||||
parentProjectName === childProjectName ||
|
||||
graph.dependencies[parentProjectName].some(
|
||||
(p) => p.target === childProjectName
|
||||
)
|
||||
): boolean {
|
||||
const isProjectDirectDep = graph.dependencies[parentProjectName].some(
|
||||
(p) => p.target === childProjectName
|
||||
);
|
||||
if (isProjectDirectDep) {
|
||||
return true;
|
||||
}
|
||||
const maybeIntermediateProjects = graph.dependencies[
|
||||
parentProjectName
|
||||
].filter((p) => !graph.externalNodes[p.target]);
|
||||
|
||||
for (const maybeIntermediateProject of maybeIntermediateProjects) {
|
||||
if (
|
||||
isCtProjectUsingBuildProject(
|
||||
graph,
|
||||
maybeIntermediateProject.target,
|
||||
childProjectName
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getProjectConfigByPath(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export const nxPreset = {
|
||||
import type { Config } from 'jest';
|
||||
export const nxPreset: Config = {
|
||||
// This is one of the patterns that jest finds by default https://jestjs.io/docs/configuration#testmatch-arraystring
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
resolver: '@nrwl/jest/plugins/resolver',
|
||||
|
||||
@@ -131,6 +131,40 @@ export default {
|
||||
expect(packageJson.devDependencies['@types/jest']).toBeDefined();
|
||||
expect(packageJson.devDependencies['ts-jest']).toBeDefined();
|
||||
expect(packageJson.devDependencies['ts-node']).toBeDefined();
|
||||
expect(packageJson.devDependencies['jest-environment-jsdom']).toBeDefined();
|
||||
expect(
|
||||
packageJson.devDependencies['jest-environment-node']
|
||||
).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should add dependencies --testEnvironment=node', async () => {
|
||||
await jestInitGenerator(tree, { testEnvironment: 'node' });
|
||||
const packageJson = readJson(tree, 'package.json');
|
||||
expect(packageJson.devDependencies.jest).toBeDefined();
|
||||
expect(packageJson.devDependencies['@nrwl/jest']).toBeDefined();
|
||||
expect(packageJson.devDependencies['@types/jest']).toBeDefined();
|
||||
expect(packageJson.devDependencies['ts-jest']).toBeDefined();
|
||||
expect(packageJson.devDependencies['ts-node']).toBeDefined();
|
||||
expect(packageJson.devDependencies['jest-environment-node']).toBeDefined();
|
||||
expect(
|
||||
packageJson.devDependencies['jest-environment-jsdom']
|
||||
).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should add dependencies --testEnvironment=none', async () => {
|
||||
await jestInitGenerator(tree, { testEnvironment: 'none' });
|
||||
const packageJson = readJson(tree, 'package.json');
|
||||
expect(packageJson.devDependencies.jest).toBeDefined();
|
||||
expect(packageJson.devDependencies['@nrwl/jest']).toBeDefined();
|
||||
expect(packageJson.devDependencies['@types/jest']).toBeDefined();
|
||||
expect(packageJson.devDependencies['ts-jest']).toBeDefined();
|
||||
expect(packageJson.devDependencies['ts-node']).toBeDefined();
|
||||
expect(
|
||||
packageJson.devDependencies['jest-environment-jsdom']
|
||||
).not.toBeDefined();
|
||||
expect(
|
||||
packageJson.devDependencies['jest-environment-node']
|
||||
).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should make js jest files', async () => {
|
||||
|
||||
@@ -33,6 +33,7 @@ const schemaDefaults = {
|
||||
compiler: 'tsc',
|
||||
js: false,
|
||||
rootProject: false,
|
||||
testEnvironment: 'jsdom',
|
||||
} as const;
|
||||
|
||||
function generateGlobalConfig(tree: Tree, isJS: boolean) {
|
||||
@@ -146,7 +147,6 @@ function updateDependencies(tree: Tree, options: NormalizedSchema) {
|
||||
const devDeps = {
|
||||
'@nrwl/jest': nxVersion,
|
||||
jest: jestVersion,
|
||||
'jest-environment-jsdom': jestVersion,
|
||||
|
||||
// because the default jest-preset uses ts-jest,
|
||||
// jest will throw an error if it's not installed
|
||||
@@ -154,6 +154,10 @@ function updateDependencies(tree: Tree, options: NormalizedSchema) {
|
||||
'ts-jest': tsJestVersion,
|
||||
};
|
||||
|
||||
if (options.testEnvironment !== 'none') {
|
||||
devDeps[`jest-environment-${options.testEnvironment}`] = jestVersion;
|
||||
}
|
||||
|
||||
if (!options.js) {
|
||||
devDeps['ts-node'] = tsNodeVersion;
|
||||
devDeps['@types/jest'] = jestTypesVersion;
|
||||
|
||||
@@ -2,6 +2,7 @@ export interface JestInitSchema {
|
||||
compiler?: 'tsc' | 'babel' | 'swc';
|
||||
js?: boolean;
|
||||
skipPackageJson?: boolean;
|
||||
testEnvironment?: 'node' | 'jsdom' | 'none';
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
@@ -18,6 +18,13 @@
|
||||
"description": "Do not add dependencies to `package.json`.",
|
||||
"x-priority": "internal"
|
||||
},
|
||||
"testEnvironment": {
|
||||
"type": "string",
|
||||
"enum": ["jsdom", "node", "none"],
|
||||
"description": "The test environment for jest. This controls which jest-environment-* package is installed",
|
||||
"default": "jsdom",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"js": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
|
||||
@@ -22,6 +22,6 @@
|
||||
]<% } %><% if(rootProject){ %>,
|
||||
testMatch: [
|
||||
'<rootDir>/src/**/__tests__/**/*.[jt]s?(x)',
|
||||
'<rootDir>/src/**/?(*.)+(spec|test).[jt]s?(x)',
|
||||
'<rootDir>/src/**/*(*.)@(spec|test).[jt]s?(x)',
|
||||
],<% } %>
|
||||
};
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
coverageDirectory: '<%= offsetFromRoot %>coverage/<%= projectRoot %>'<% if(rootProject){ %>,
|
||||
testMatch: [
|
||||
'<rootDir>/src/**/__tests__/**/*.[jt]s?(x)',
|
||||
'<rootDir>/src/**/?(*.)+(spec|test).[jt]s?(x)',
|
||||
'<rootDir>/src/**/*(*.)@(spec|test).[jt]s?(x)',
|
||||
],<% } %>
|
||||
};
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,24 +123,4 @@ npx nx g lib mylib --directory=myapp
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Use SWC compiler" %}
|
||||
|
||||
Generate a library using [SWC](https://swc.rs) as the compiler
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 NOT 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).not.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([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,53 @@ 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 none.
|
||||
*
|
||||
* 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`.
|
||||
*/
|
||||
|
||||
options.bundler =
|
||||
options.bundler ??
|
||||
options.compiler ??
|
||||
(options.buildable || options.publishable ? 'tsc' : 'none');
|
||||
|
||||
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') {
|
||||
// It must be buildable, so default to tsc
|
||||
options.bundler = 'tsc';
|
||||
}
|
||||
}
|
||||
|
||||
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 +454,8 @@ function normalizeOptions(
|
||||
const importPath =
|
||||
options.importPath || getImportPath(npmScope, projectDirectory);
|
||||
|
||||
options.minimal ??= false;
|
||||
|
||||
return {
|
||||
...options,
|
||||
fileName,
|
||||
@@ -449,18 +500,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"],
|
||||
"enum": ["swc", "tsc", "rollup", "vite", "esbuild", "none"],
|
||||
"default": "none",
|
||||
"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',
|
||||
});
|
||||
|
||||
|
||||
Vendored
+2
-1
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -267,6 +267,7 @@ export async function addLintingToApplication(
|
||||
unitTestRunner: options.unitTestRunner,
|
||||
skipFormat: true,
|
||||
setParserOptionsProject: options.setParserOptionsProject,
|
||||
rootProject: options.rootProject,
|
||||
});
|
||||
|
||||
return lintTask;
|
||||
|
||||
@@ -47,7 +47,9 @@ export async function initGenerator(tree: Tree, schema: Schema) {
|
||||
})
|
||||
);
|
||||
if (options.unitTestRunner === 'jest') {
|
||||
tasks.push(await jestInitGenerator(tree, schema));
|
||||
tasks.push(
|
||||
await jestInitGenerator(tree, { ...schema, testEnvironment: 'node' })
|
||||
);
|
||||
}
|
||||
|
||||
tasks.push(updateDependencies(tree));
|
||||
|
||||
@@ -49,6 +49,12 @@ describe('lib', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(
|
||||
readJson(tree, 'package.json').devDependencies['jest-environment-jsdom']
|
||||
).not.toBeDefined();
|
||||
expect(
|
||||
readJson(tree, 'package.json').devDependencies['jest-environment-node']
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
it('adds srcRootForCompilationRoot', async () => {
|
||||
|
||||
@@ -95,7 +95,7 @@ describe('NxPlugin Executor Generator', () => {
|
||||
it('should create executors.json if it is not present', async () => {
|
||||
await jsLibraryGenerator(tree, {
|
||||
name: 'test-js-lib',
|
||||
buildable: true,
|
||||
bundler: 'tsc',
|
||||
});
|
||||
const libConfig = readProjectConfiguration(tree, 'test-js-lib');
|
||||
await executorGenerator(tree, {
|
||||
|
||||
@@ -93,7 +93,7 @@ describe('NxPlugin Generator Generator', () => {
|
||||
it('should create generators.json if it is not present', async () => {
|
||||
await jsLibraryGenerator(tree, {
|
||||
name: 'test-js-lib',
|
||||
buildable: true,
|
||||
bundler: 'tsc',
|
||||
});
|
||||
const libConfig = readProjectConfiguration(tree, 'test-js-lib');
|
||||
await generatorGenerator(tree, {
|
||||
|
||||
@@ -93,7 +93,7 @@ export async function pluginGenerator(host: Tree, schema: Schema) {
|
||||
await jsLibraryGenerator(host, {
|
||||
...schema,
|
||||
config: 'project',
|
||||
buildable: true,
|
||||
bundler: options.bundler,
|
||||
importPath: options.npmPackageName,
|
||||
});
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface NormalizedSchema extends Schema {
|
||||
parsedTags: string[];
|
||||
npmScope: string;
|
||||
npmPackageName: string;
|
||||
bundler: 'swc' | 'tsc';
|
||||
}
|
||||
export function normalizeOptions(
|
||||
host: Tree,
|
||||
@@ -44,6 +45,7 @@ export function normalizeOptions(
|
||||
|
||||
return {
|
||||
...options,
|
||||
bundler: options.compiler ?? 'tsc',
|
||||
fileName,
|
||||
npmScope,
|
||||
libsDir,
|
||||
|
||||
@@ -43,7 +43,7 @@ export function runNxCommandAsync(
|
||||
silenceError: false,
|
||||
}
|
||||
): Promise<{ stdout: string; stderr: string }> {
|
||||
if (fileExists('package.json')) {
|
||||
if (fileExists(tmpProjPath('package.json'))) {
|
||||
const pmc = getPackageManagerCommand();
|
||||
return runCommandAsync(`${pmc.exec} nx ${command}`, opts);
|
||||
} else if (process.platform === 'win32') {
|
||||
|
||||
@@ -21,7 +21,7 @@ export function runNxCommand(
|
||||
cwd: tmpProjPath(),
|
||||
env: { ...process.env, ...opts.env },
|
||||
};
|
||||
if (fileExists('package.json')) {
|
||||
if (fileExists(tmpProjPath('package.json'))) {
|
||||
const pmc = getPackageManagerCommand();
|
||||
return execSync(`${pmc.exec} nx ${command}`, execSyncOptions);
|
||||
} else if (process.platform === 'win32') {
|
||||
|
||||
+68
-1
@@ -6,7 +6,13 @@ import {
|
||||
import * as chalk from 'chalk';
|
||||
import { initLocal } from './init-local';
|
||||
import { output } from '../src/utils/output';
|
||||
import { getNxInstallationPath } from 'nx/src/utils/installation-directory';
|
||||
import {
|
||||
getNxInstallationPath,
|
||||
getNxRequirePaths,
|
||||
} from '../src/utils/installation-directory';
|
||||
import { major } from 'semver';
|
||||
import { readJsonFile } from '../src/utils/fileutils';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
const workspace = findWorkspaceRoot(process.cwd());
|
||||
// new is a special case because there is no local workspace to load
|
||||
@@ -53,6 +59,8 @@ if (
|
||||
try {
|
||||
localNx = resolveNx(workspace);
|
||||
} catch {
|
||||
// If we can't resolve a local copy of Nx, we must be global.
|
||||
warnIfUsingOutdatedGlobalInstall();
|
||||
output.error({
|
||||
title: `Could not find Nx modules in this workspace.`,
|
||||
bodyLines: [`Have you run ${chalk.bold.white(`npm/yarn install`)}?`],
|
||||
@@ -62,9 +70,14 @@ if (
|
||||
|
||||
// this file is already in the local workspace
|
||||
if (localNx === resolveNx(null)) {
|
||||
if (localNx.includes('.nx') && !process.env.NX_WRAPPER_SET) {
|
||||
const nxWrapperPath = localNx.replace(/\.nx.*/, '.nx/') + 'nxw.js';
|
||||
require(nxWrapperPath);
|
||||
}
|
||||
initLocal(workspace);
|
||||
} else {
|
||||
// Nx is being run from globally installed CLI - hand off to the local
|
||||
warnIfUsingOutdatedGlobalInstall(getLocalNxVersion(workspace));
|
||||
require(localNx);
|
||||
}
|
||||
}
|
||||
@@ -89,3 +102,57 @@ function resolveNx(workspace: WorkspaceTypeAndRoot | null) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assumes currently running Nx is global install.
|
||||
* Warns if out of date by 1 major version or more.
|
||||
*/
|
||||
function warnIfUsingOutdatedGlobalInstall(localNxVersion?: string) {
|
||||
const globalVersion = require('../package.json').version;
|
||||
const isOutdatedGlobalInstall =
|
||||
globalVersion &&
|
||||
((localNxVersion && major(globalVersion) < major(localNxVersion)) ||
|
||||
(getLatestVersionOfNx() &&
|
||||
major(globalVersion) < major(getLatestVersionOfNx())));
|
||||
|
||||
// Using a global Nx Install
|
||||
if (isOutdatedGlobalInstall) {
|
||||
const bodyLines = localNxVersion
|
||||
? ['Its time to update Nx 🎉']
|
||||
: [
|
||||
'Its possible that this is causing Nx to not pick up your workspace properly.',
|
||||
];
|
||||
|
||||
bodyLines.push(
|
||||
'For more information, see https://nx.dev/more-concepts/global-nx'
|
||||
);
|
||||
output.warn({
|
||||
title: `Its time to update Nx 🎉`,
|
||||
bodyLines,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getLocalNxVersion(workspace: WorkspaceTypeAndRoot): string {
|
||||
return readJsonFile(
|
||||
require.resolve('nx/package.json', {
|
||||
paths: getNxRequirePaths(workspace.dir),
|
||||
})
|
||||
).version;
|
||||
}
|
||||
|
||||
let _latest = null;
|
||||
function getLatestVersionOfNx(): string {
|
||||
if (!_latest) {
|
||||
try {
|
||||
_latest = execSync('npm view nx@latest version').toString().trim();
|
||||
} catch {
|
||||
try {
|
||||
_latest = execSync('pnpm view nx@latest version').toString().trim();
|
||||
} catch {
|
||||
_latest = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return _latest;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,12 @@
|
||||
"version": "15.0.12-beta.1",
|
||||
"description": "Set project names in project.json files",
|
||||
"implementation": "./src/migrations/update-15-1-0/set-project-names"
|
||||
},
|
||||
"15.8.2-update-nx-wrapper": {
|
||||
"cli": "nx",
|
||||
"version": "15.8.2-beta.0",
|
||||
"description": "Updates the nx wrapper in encapsulated repos.",
|
||||
"implementation": "./src/migrations/update-15-8-2/update-nxw"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from '../generators/utils/project-configuration';
|
||||
import { createProjectGraphAsync } from '../project-graph/project-graph';
|
||||
import { readJsonFile } from '../utils/fileutils';
|
||||
import { getNxRequirePaths } from '../utils/installation-directory';
|
||||
import { parseJson } from '../utils/json';
|
||||
import { NX_ERROR, NX_PREFIX } from '../utils/logger';
|
||||
import { readModulePackageJson } from '../utils/package-json';
|
||||
@@ -643,9 +644,10 @@ function resolveMigrationsCollection(name: string): string {
|
||||
if (extname(name)) {
|
||||
collectionPath = require.resolve(name);
|
||||
} else {
|
||||
const { path: packageJsonPath, packageJson } = readModulePackageJson(name, [
|
||||
process.cwd(),
|
||||
]);
|
||||
const { path: packageJsonPath, packageJson } = readModulePackageJson(
|
||||
name,
|
||||
getNxRequirePaths(process.cwd())
|
||||
);
|
||||
|
||||
let pkgJsonSchematics =
|
||||
packageJson['nx-migrations'] ?? packageJson['ng-update'];
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { workspaceRoot } from '../utils/workspace-root';
|
||||
import { runNxSync } from '../utils/child-process';
|
||||
|
||||
export async function connectToNxCloudIfExplicitlyAsked(opts: {
|
||||
[k: string]: any;
|
||||
@@ -24,13 +25,7 @@ export async function connectToNxCloudIfExplicitlyAsked(opts: {
|
||||
output.log({
|
||||
title: '--cloud requires the workspace to be connected to Nx Cloud.',
|
||||
});
|
||||
const pmc = getPackageManagerCommand();
|
||||
const nxCommand = existsSync(join(workspaceRoot, 'package.json'))
|
||||
? `${pmc.exec} nx`
|
||||
: process.platform === 'win32'
|
||||
? './nx.bat'
|
||||
: './nx';
|
||||
execSync(`${nxCommand} connect-to-nx-cloud`, {
|
||||
runNxSync(`connect-to-nx-cloud`, {
|
||||
stdio: [0, 1, 2],
|
||||
});
|
||||
output.success({
|
||||
@@ -60,11 +55,6 @@ export async function connectToNxCloudCommand(
|
||||
const res = await connectToNxCloudPrompt(promptOverride);
|
||||
if (!res) return false;
|
||||
const pmc = getPackageManagerCommand();
|
||||
const nxCommand = pmc
|
||||
? `${pmc.exec} nx`
|
||||
: process.platform === 'win32'
|
||||
? './nx.bat'
|
||||
: './nx';
|
||||
if (pmc) {
|
||||
execSync(`${pmc.addDev} @nrwl/nx-cloud@latest`);
|
||||
} else {
|
||||
@@ -76,7 +66,7 @@ export async function connectToNxCloudCommand(
|
||||
).toString();
|
||||
}
|
||||
}
|
||||
execSync(`${nxCommand} g @nrwl/nx-cloud:init`, {
|
||||
runNxSync(`g @nrwl/nx-cloud:init`, {
|
||||
stdio: [0, 1, 2],
|
||||
});
|
||||
return true;
|
||||
|
||||
@@ -20,6 +20,7 @@ import { createProjectGraphAsync } from '../project-graph/project-graph';
|
||||
import { filterAffected } from '../project-graph/affected/affected-project-graph';
|
||||
import { readNxJson } from '../config/configuration';
|
||||
import { ProjectGraph } from '../config/project-graph';
|
||||
import { chunkify } from '../utils/chunkify';
|
||||
|
||||
const PRETTIER_PATH = require.resolve('prettier/bin-prettier');
|
||||
|
||||
@@ -40,7 +41,7 @@ export async function format(
|
||||
);
|
||||
|
||||
// Chunkify the patterns array to prevent crashing the windows terminal
|
||||
const chunkList: string[][] = chunkify(patterns, 50);
|
||||
const chunkList: string[][] = chunkify(patterns);
|
||||
|
||||
switch (command) {
|
||||
case 'write':
|
||||
@@ -143,14 +144,6 @@ function getPatternsFromProjects(
|
||||
return getProjectRoots(projects, projectGraph);
|
||||
}
|
||||
|
||||
function chunkify(target: string[], size: number): string[][] {
|
||||
return target.reduce((current: string[][], value: string, index: number) => {
|
||||
if (index % size === 0) current.push([]);
|
||||
current[current.length - 1].push(value);
|
||||
return current;
|
||||
}, []);
|
||||
}
|
||||
|
||||
function write(patterns: string[]) {
|
||||
if (patterns.length > 0) {
|
||||
const [swcrcPatterns, regularPatterns] = patterns.reduce(
|
||||
|
||||
@@ -53,6 +53,9 @@ import { nxVersion } from '../utils/versions';
|
||||
import { existsSync } from 'fs';
|
||||
import { workspaceRoot } from '../utils/workspace-root';
|
||||
import { isCI } from '../utils/is-ci';
|
||||
import { getNxRequirePaths } from '../utils/installation-directory';
|
||||
import { readNxJson } from '../config/configuration';
|
||||
import { runNxSync } from '../utils/child-process';
|
||||
|
||||
export interface ResolvedMigrationConfiguration extends MigrationsJson {
|
||||
packageGroup?: ArrayPackageGroup;
|
||||
@@ -100,7 +103,8 @@ function normalizeSlashes(packageName: string): string {
|
||||
}
|
||||
|
||||
export interface MigratorOptions {
|
||||
packageJson: PackageJson;
|
||||
packageJson?: PackageJson;
|
||||
nxInstallation?: NxJsonConfiguration['installation'];
|
||||
getInstalledPackageVersion: (
|
||||
pkg: string,
|
||||
overrides?: Record<string, string>
|
||||
@@ -116,7 +120,7 @@ export interface MigratorOptions {
|
||||
}
|
||||
|
||||
export class Migrator {
|
||||
private readonly packageJson: MigratorOptions['packageJson'];
|
||||
private readonly packageJson?: MigratorOptions['packageJson'];
|
||||
private readonly getInstalledPackageVersion: MigratorOptions['getInstalledPackageVersion'];
|
||||
private readonly fetch: MigratorOptions['fetch'];
|
||||
private readonly installedPkgVersionOverrides: MigratorOptions['from'];
|
||||
@@ -126,9 +130,11 @@ export class Migrator {
|
||||
private readonly packageUpdates: Record<string, PackageUpdate> = {};
|
||||
private readonly collectedVersions: Record<string, string> = {};
|
||||
private readonly promptAnswers: Record<string, boolean> = {};
|
||||
private readonly nxInstallation: NxJsonConfiguration['installation'] | null;
|
||||
|
||||
constructor(opts: MigratorOptions) {
|
||||
this.packageJson = opts.packageJson;
|
||||
this.nxInstallation = opts.nxInstallation;
|
||||
this.getInstalledPackageVersion = opts.getInstalledPackageVersion;
|
||||
this.fetch = opts.fetch;
|
||||
this.installedPkgVersionOverrides = opts.from;
|
||||
@@ -400,33 +406,34 @@ export class Migrator {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { dependencies, devDependencies } = this.packageJson;
|
||||
packageJsonUpdate.packages = Object.entries(packageJsonUpdate.packages)
|
||||
.filter(
|
||||
([packageName, packageUpdate]) =>
|
||||
(!packageUpdate.ifPackageInstalled ||
|
||||
this.getPkgVersion(packageUpdate.ifPackageInstalled)) &&
|
||||
(packageUpdate.alwaysAddToPackageJson ||
|
||||
packageUpdate.addToPackageJson ||
|
||||
!!dependencies?.[packageName] ||
|
||||
!!devDependencies?.[packageName]) &&
|
||||
(!this.collectedVersions[packageName] ||
|
||||
this.gt(
|
||||
packageUpdate.version,
|
||||
this.collectedVersions[packageName]
|
||||
))
|
||||
)
|
||||
.reduce((acc, [packageName, packageUpdate]) => {
|
||||
acc[packageName] = {
|
||||
const dependencies: Record<string, string> = {
|
||||
...this.packageJson?.dependencies,
|
||||
...this.packageJson?.devDependencies,
|
||||
...this.nxInstallation?.plugins,
|
||||
...(this.nxInstallation && { nx: this.nxInstallation.version }),
|
||||
};
|
||||
|
||||
const filtered: Record<string, PackageUpdate> = {};
|
||||
for (const [packageName, packageUpdate] of Object.entries(
|
||||
packageJsonUpdate.packages
|
||||
)) {
|
||||
if (
|
||||
this.shouldApplyPackageUpdate(
|
||||
packageUpdate,
|
||||
packageName,
|
||||
dependencies
|
||||
)
|
||||
) {
|
||||
filtered[packageName] = {
|
||||
version: packageUpdate.version,
|
||||
addToPackageJson: packageUpdate.alwaysAddToPackageJson
|
||||
? 'dependencies'
|
||||
: packageUpdate.addToPackageJson || false,
|
||||
};
|
||||
return acc;
|
||||
}, {} as Record<string, PackageUpdate>);
|
||||
|
||||
if (Object.keys(packageJsonUpdate.packages).length) {
|
||||
}
|
||||
}
|
||||
if (Object.keys(filtered).length) {
|
||||
packageJsonUpdate.packages = filtered;
|
||||
filteredPackageJsonUpdates.push(packageJsonUpdate);
|
||||
}
|
||||
}
|
||||
@@ -434,6 +441,22 @@ export class Migrator {
|
||||
return filteredPackageJsonUpdates;
|
||||
}
|
||||
|
||||
private shouldApplyPackageUpdate(
|
||||
packageUpdate: PackageUpdate,
|
||||
packageName: string,
|
||||
dependencies: Record<string, string>
|
||||
) {
|
||||
return (
|
||||
(!packageUpdate.ifPackageInstalled ||
|
||||
this.getPkgVersion(packageUpdate.ifPackageInstalled)) &&
|
||||
(packageUpdate.alwaysAddToPackageJson ||
|
||||
packageUpdate.addToPackageJson ||
|
||||
!!dependencies?.[packageName]) &&
|
||||
(!this.collectedVersions[packageName] ||
|
||||
this.gt(packageUpdate.version, this.collectedVersions[packageName]))
|
||||
);
|
||||
}
|
||||
|
||||
private addPackageUpdate(name: string, packageUpdate: PackageUpdate): void {
|
||||
if (
|
||||
!this.packageUpdates[name] ||
|
||||
@@ -732,9 +755,10 @@ function createInstalledPackageVersionsResolver(
|
||||
}
|
||||
|
||||
if (!cache[packageName]) {
|
||||
const { packageJson, path } = readModulePackageJson(packageName, [
|
||||
root,
|
||||
]);
|
||||
const { packageJson, path } = readModulePackageJson(
|
||||
packageName,
|
||||
getNxRequirePaths()
|
||||
);
|
||||
// old workspaces would have the temp installation of nx in the cache,
|
||||
// so the resolved package is not the one we need
|
||||
if (!path.startsWith(workspaceRoot)) {
|
||||
@@ -970,7 +994,7 @@ function readPackageMigrationConfig(
|
||||
): PackageMigrationConfig {
|
||||
const { path: packageJsonPath, packageJson: json } = readModulePackageJson(
|
||||
packageName,
|
||||
[dir]
|
||||
getNxRequirePaths(dir)
|
||||
);
|
||||
|
||||
const config = readNxMigrateConfig(json);
|
||||
@@ -1043,7 +1067,7 @@ function updatePackageJson(
|
||||
});
|
||||
}
|
||||
|
||||
function updateInstallationDetails(
|
||||
async function updateInstallationDetails(
|
||||
root: string,
|
||||
updatedPackages: Record<string, PackageUpdate>
|
||||
) {
|
||||
@@ -1064,7 +1088,9 @@ function updateInstallationDetails(
|
||||
for (const dep in nxJson.installation.plugins) {
|
||||
const update = updatedPackages[dep];
|
||||
if (update) {
|
||||
nxJson.installation.plugins[dep] = update.version;
|
||||
nxJson.installation.plugins[dep] = valid(update.version)
|
||||
? update.version
|
||||
: await resolvePackageVersionUsingRegistry(dep, update.version);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1101,10 +1127,13 @@ async function generateMigrationsJsonAndUpdatePackageJson(
|
||||
) {
|
||||
const pmc = getPackageManagerCommand();
|
||||
try {
|
||||
let originalPackageJson = readJsonFile<PackageJson>(
|
||||
join(root, 'package.json')
|
||||
);
|
||||
const from = readNxVersion(originalPackageJson);
|
||||
const rootPkgJsonPath = join(root, 'package.json');
|
||||
let originalPackageJson = existsSync(rootPkgJsonPath)
|
||||
? readJsonFile<PackageJson>(rootPkgJsonPath)
|
||||
: null;
|
||||
const originalNxInstallation = readNxJson().installation;
|
||||
const from =
|
||||
originalNxInstallation?.version ?? readNxVersion(originalPackageJson);
|
||||
|
||||
try {
|
||||
if (
|
||||
@@ -1135,6 +1164,7 @@ async function generateMigrationsJsonAndUpdatePackageJson(
|
||||
|
||||
const migrator = new Migrator({
|
||||
packageJson: originalPackageJson,
|
||||
nxInstallation: originalNxInstallation,
|
||||
getInstalledPackageVersion: createInstalledPackageVersionsResolver(root),
|
||||
fetch: createFetcher(),
|
||||
from: opts.from,
|
||||
@@ -1149,7 +1179,7 @@ async function generateMigrationsJsonAndUpdatePackageJson(
|
||||
);
|
||||
|
||||
updatePackageJson(root, packageUpdates);
|
||||
updateInstallationDetails(root, packageUpdates);
|
||||
await updateInstallationDetails(root, packageUpdates);
|
||||
|
||||
if (migrations.length > 0) {
|
||||
createMigrationsFile(root, [
|
||||
@@ -1352,8 +1382,7 @@ async function runMigrations(
|
||||
if (!__dirname.startsWith(workspaceRoot)) {
|
||||
// we are running from a temp installation with nx latest, switch to running
|
||||
// from local installation
|
||||
const pmc = getPackageManagerCommand();
|
||||
execSync(`${pmc.exec} nx migrate ${args.join(' ')}`, {
|
||||
runNxSync(`migrate ${args.join(' ')}`, {
|
||||
stdio: ['inherit', 'inherit', 'inherit'],
|
||||
env: {
|
||||
...process.env,
|
||||
@@ -1410,11 +1439,17 @@ async function runMigrations(
|
||||
}
|
||||
|
||||
function getStringifiedPackageJsonDeps(root: string): string {
|
||||
const { dependencies, devDependencies } = readJsonFile<PackageJson>(
|
||||
join(root, 'package.json')
|
||||
);
|
||||
try {
|
||||
const { dependencies, devDependencies } = readJsonFile<PackageJson>(
|
||||
join(root, 'package.json')
|
||||
);
|
||||
|
||||
return JSON.stringify([dependencies, devDependencies]);
|
||||
return JSON.stringify([dependencies, devDependencies]);
|
||||
} catch {
|
||||
// We don't really care if the .nx/installation property changes,
|
||||
// whenever nxw is invoked it will handle the dep updates.
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function commitChanges(commitMessage: string): string | null {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { workspaceRoot } from '../utils/workspace-root';
|
||||
import { getPackageManagerCommand } from '../utils/package-manager';
|
||||
import { writeJsonFile } from '../utils/fileutils';
|
||||
import { WatchArguments } from './watch';
|
||||
import { runNxSync } from '../utils/child-process';
|
||||
|
||||
// Ensure that the output takes up the available width of the terminal.
|
||||
yargs.wrap(yargs.terminalWidth());
|
||||
@@ -1090,8 +1091,7 @@ function withListOptions(yargs) {
|
||||
|
||||
function runMigration() {
|
||||
const runLocalMigrate = () => {
|
||||
const pmc = getPackageManagerCommand();
|
||||
execSync(`${pmc.exec} nx _migrate ${process.argv.slice(3).join(' ')}`, {
|
||||
runNxSync(`_migrate ${process.argv.slice(3).join(' ')}`, {
|
||||
stdio: ['inherit', 'inherit', 'inherit'],
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,7 +3,10 @@ import { createProjectGraphAsync } from '../project-graph/project-graph';
|
||||
export async function show(args: { object: 'projects' }): Promise<void> {
|
||||
if (args.object == 'projects') {
|
||||
const graph = await createProjectGraphAsync();
|
||||
process.stdout.write(Object.keys(graph.nodes).join('\n'));
|
||||
const projects = Object.keys(graph.nodes).join('\n');
|
||||
if (projects.length) {
|
||||
console.log(projects);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unrecognized option: ${args.object}`);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { getPackageManagerCommand } from '../utils/package-manager';
|
||||
import { execSync } from 'child_process';
|
||||
import { isNxCloudUsed } from '../utils/nx-cloud-utils';
|
||||
import { output } from '../utils/output';
|
||||
import { runNxSync } from '../utils/child-process';
|
||||
|
||||
export async function viewLogs(): Promise<number> {
|
||||
const pmc = getPackageManagerCommand();
|
||||
@@ -48,10 +49,9 @@ export async function viewLogs(): Promise<number> {
|
||||
output.log({
|
||||
title: 'Connecting to Nx Cloud',
|
||||
});
|
||||
execSync(
|
||||
`${pmc.exec} nx g @nrwl/nx-cloud:init --installation-source=view-logs`,
|
||||
{ stdio: 'ignore' }
|
||||
);
|
||||
runNxSync(`g @nrwl/nx-cloud:init --installation-source=view-logs`, {
|
||||
stdio: 'ignore',
|
||||
});
|
||||
} catch (e) {
|
||||
output.log({
|
||||
title: 'Failed to connect to Nx Cloud',
|
||||
|
||||
@@ -64,7 +64,7 @@ export class Workspaces {
|
||||
});
|
||||
if (matchingProject) return matchingProject;
|
||||
}
|
||||
return nxJson.defaultProject;
|
||||
return nxJson?.defaultProject;
|
||||
}
|
||||
|
||||
readProjectsConfigurations(opts?: {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { spawn } from 'child_process';
|
||||
import { chunkify } from '../utils/chunkify';
|
||||
import { fileExists } from '../utils/fileutils';
|
||||
import { joinPathFragments } from '../utils/path';
|
||||
|
||||
@@ -12,26 +13,9 @@ export async function getGitHashForFiles(
|
||||
);
|
||||
|
||||
const res: Map<string, string> = new Map<string, string>();
|
||||
const promises: Promise<Map<string, string>>[] = [];
|
||||
if (filesToHash.length) {
|
||||
// On windows the max length is limited by the length of
|
||||
// the overall comand, rather than the number of individual
|
||||
// arguments. Since file paths are large and rather variable,
|
||||
// we use a smaller batchSize.
|
||||
const batchSize = process.platform === 'win32' ? 250 : 4000;
|
||||
for (
|
||||
let startIndex = 0;
|
||||
startIndex < filesToHash.length;
|
||||
startIndex += batchSize
|
||||
) {
|
||||
promises.push(
|
||||
getGitHashForBatch(
|
||||
filesToHash.slice(startIndex, startIndex + batchSize),
|
||||
path
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
const promises: Promise<Map<string, string>>[] = chunkify(filesToHash).map(
|
||||
(files) => getGitHashForBatch(files, path)
|
||||
);
|
||||
// Merge batch results into final result set
|
||||
const batchResults = await Promise.all(promises);
|
||||
for (const batch of batchResults) {
|
||||
|
||||
@@ -459,20 +459,12 @@ class TaskHasher {
|
||||
if (!this.filesetHashes[mapKey]) {
|
||||
this.filesetHashes[mapKey] = new Promise(async (res) => {
|
||||
const parts = [];
|
||||
if (fileset.indexOf('*') > -1) {
|
||||
this.projectGraph.allWorkspaceFiles
|
||||
.filter((f) => minimatch(f.file, withoutWorkspaceRoot))
|
||||
.forEach((f) => {
|
||||
parts.push(f.hash);
|
||||
});
|
||||
} else {
|
||||
const matchingFile = this.projectGraph.allWorkspaceFiles.find(
|
||||
(t) => t.file === withoutWorkspaceRoot
|
||||
);
|
||||
if (matchingFile) {
|
||||
parts.push(matchingFile.hash);
|
||||
}
|
||||
}
|
||||
this.projectGraph.allWorkspaceFiles
|
||||
.filter((f) => minimatch(f.file, withoutWorkspaceRoot))
|
||||
.forEach((f) => {
|
||||
parts.push(f.hash);
|
||||
});
|
||||
|
||||
const value = this.hashing.hashArray(parts);
|
||||
res({
|
||||
value,
|
||||
|
||||
@@ -141,7 +141,11 @@ function findVersion(
|
||||
) {
|
||||
return snapshot.resolution.slice(packageName.length + 1);
|
||||
}
|
||||
if (!isBerry && !satisfies(snapshot.version, versionRange)) {
|
||||
if (
|
||||
!isBerry &&
|
||||
snapshot.resolved &&
|
||||
!satisfies(snapshot.version, versionRange)
|
||||
) {
|
||||
return snapshot.resolved;
|
||||
}
|
||||
// otherwise it's a standard version
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import {
|
||||
getNxWrapperContents,
|
||||
nxWrapperPath,
|
||||
} from 'nx/src/nx-init/encapsulated/add-nx-scripts';
|
||||
import { normalizePath } from '../../utils/path';
|
||||
import { Tree } from '../../generators/tree';
|
||||
|
||||
export default async function (tree: Tree) {
|
||||
const wrapperPath = normalizePath(nxWrapperPath());
|
||||
if (tree.exists(wrapperPath)) {
|
||||
tree.write(wrapperPath, getNxWrapperContents());
|
||||
}
|
||||
}
|
||||
@@ -28,25 +28,25 @@ fn hash_file(file: String) -> Option<FileData> {
|
||||
|
||||
#[napi]
|
||||
fn hash_files(workspace_root: String) -> HashMap<String, String> {
|
||||
let mut walker = WalkBuilder::new(&workspace_root);
|
||||
let workspace_root = Path::new(&workspace_root);
|
||||
walker.add_ignore(workspace_root.join(".nxignore"));
|
||||
|
||||
let nx_ignore = workspace_root.join(".nxignore");
|
||||
let git_folder = workspace_root.join(".git");
|
||||
// We should make sure to always ignore node_modules
|
||||
let node_folder = workspace_root.join("node_modules");
|
||||
|
||||
let mut walker = WalkBuilder::new(&workspace_root);
|
||||
walker.hidden(false);
|
||||
walker.add_custom_ignore_filename(&nx_ignore);
|
||||
|
||||
// We should make sure to always ignore node_modules and the .git folder
|
||||
walker.filter_entry(move |entry| {
|
||||
!(entry.path().starts_with(&git_folder) || entry.path().starts_with(&node_folder))
|
||||
});
|
||||
|
||||
// dot files are hidden by default. We want to make sure we include those here
|
||||
walker.hidden(false);
|
||||
|
||||
let (sender, reciever) = unbounded::<(String, Vec<u8>)>();
|
||||
let (sender, receiver) = unbounded::<(String, Vec<u8>)>();
|
||||
|
||||
let receiver_thread = thread::spawn(move || {
|
||||
let mut collection: HashMap<String, String> = HashMap::new();
|
||||
for (path, content) in reciever {
|
||||
for (path, content) in receiver {
|
||||
collection.insert(path, xxh3::xxh3_64(&content).to_string());
|
||||
}
|
||||
collection
|
||||
@@ -56,7 +56,6 @@ fn hash_files(workspace_root: String) -> HashMap<String, String> {
|
||||
|
||||
walker.threads(cpus).build_parallel().run(|| {
|
||||
let tx = sender.clone();
|
||||
let workspace_root = workspace_root.clone();
|
||||
Box::new(move |entry| {
|
||||
use ignore::WalkState::*;
|
||||
|
||||
@@ -69,7 +68,7 @@ fn hash_files(workspace_root: String) -> HashMap<String, String> {
|
||||
return Continue;
|
||||
};
|
||||
|
||||
let Ok(file_path) = dir_entry.path().strip_prefix(&workspace_root) else {
|
||||
let Ok(file_path) = dir_entry.path().strip_prefix(workspace_root) else {
|
||||
return Continue;
|
||||
};
|
||||
|
||||
@@ -155,18 +154,33 @@ mod tests {
|
||||
fn handles_nx_ignore() {
|
||||
let temp_dir = setup_fs();
|
||||
|
||||
temp_dir
|
||||
.child("nested")
|
||||
.child("child.txt")
|
||||
.write_str("data");
|
||||
temp_dir
|
||||
.child("nested")
|
||||
.child("child-two")
|
||||
.child("grand_child.txt")
|
||||
.write_str("data");
|
||||
|
||||
// add nxignore file with baz/
|
||||
temp_dir.child(".nxignore").write_str("baz/").unwrap();
|
||||
temp_dir
|
||||
.child(".nxignore")
|
||||
.write_str(
|
||||
r"baz/
|
||||
nested/child.txt
|
||||
nested/child-two/
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let content = hash_files(temp_dir.display().to_string());
|
||||
let mut file_names = content.iter().map(|c| c.0).collect::<Vec<_>>();
|
||||
file_names.sort();
|
||||
assert_eq!(
|
||||
content,
|
||||
HashMap::from([
|
||||
("foo.txt".into(), "8455857314690418558".into()),
|
||||
("test.txt".into(), "6193209363630369380".into()),
|
||||
("bar.txt".into(), "1707056588989152788".into()),
|
||||
(".nxignore".into(), "5786346484289078730".into())
|
||||
])
|
||||
file_names,
|
||||
vec!(".nxignore", "bar.txt", "foo.txt", "test.txt")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ function getJestOptions(
|
||||
delete jestOptions['testRegex'];
|
||||
jestOptions['testMatch'] = isE2E
|
||||
? ['<rootDir>/test/**/?(*.)+(e2e-spec|e2e-test).[jt]s?(x)']
|
||||
: ['<rootDir>/src/**/?(*.)+(spec|test).[jt]s?(x)'];
|
||||
: ['<rootDir>/src/**/*(*.)@(spec|test).[jt]s?(x)'];
|
||||
|
||||
// set coverage directory for unit test
|
||||
if (!isE2E) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from '../../generators/tree';
|
||||
import { writeJson } from '../../generators/utils/json';
|
||||
|
||||
const nxWrapperPath = (p: typeof import('path') = path) =>
|
||||
export const nxWrapperPath = (p: typeof import('path') = path) =>
|
||||
p.join('.nx', 'nxw.js');
|
||||
|
||||
const NODE_MISSING_ERR =
|
||||
@@ -36,7 +36,7 @@ export function generateEncapsulatedNxSetup(version?: string) {
|
||||
const host = new FsTree(process.cwd(), false);
|
||||
writeMinimalNxJson(host, version);
|
||||
updateGitIgnore(host);
|
||||
host.write(nxWrapperPath(), getNodeScriptContents());
|
||||
host.write(nxWrapperPath(), getNxWrapperContents());
|
||||
host.write('nx.bat', BATCH_SCRIPT_CONTENTS);
|
||||
host.write('nx', SHELL_SCRIPT_CONTENTS, {
|
||||
mode: FsConstants.S_IXUSR | FsConstants.S_IRUSR | FsConstants.S_IWUSR,
|
||||
@@ -75,7 +75,7 @@ export function updateGitIgnore(host: Tree) {
|
||||
);
|
||||
}
|
||||
|
||||
function getNodeScriptContents() {
|
||||
export function getNxWrapperContents() {
|
||||
// Read nxw.js, but remove any empty comments or comments that start with `//#: `
|
||||
// This removes the sourceMapUrl since it is invalid, as well as any internal comments.
|
||||
return readFileSync(path.join(__dirname, 'nxw.js'), 'utf-8').replace(
|
||||
|
||||
@@ -99,7 +99,10 @@ function ensureUpToDateInstallation() {
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
ensureUpToDateInstallation();
|
||||
if (require.main === module || !process.env.NX_WRAPPER_SET) {
|
||||
if (!process.env.NX_WRAPPER_SKIP_INSTALL) {
|
||||
ensureUpToDateInstallation();
|
||||
}
|
||||
process.env.NX_WRAPPER_SET = 'true';
|
||||
require('./installation/node_modules/nx/bin/nx');
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
getPackageManagerCommand,
|
||||
PackageManagerCommands,
|
||||
} from '../utils/package-manager';
|
||||
import { runNxSync } from '../utils/child-process';
|
||||
|
||||
export function askAboutNxCloud() {
|
||||
return enquirer
|
||||
@@ -121,12 +122,8 @@ export function runInstall(
|
||||
}
|
||||
|
||||
export function initCloud(repoRoot: string) {
|
||||
const pmc = getPackageManagerCommand();
|
||||
execSync(
|
||||
`${pmc.exec} nx g @nrwl/nx-cloud:init --installationSource=add-nx-to-monorepo`,
|
||||
{
|
||||
stdio: [0, 1, 2],
|
||||
cwd: repoRoot,
|
||||
}
|
||||
);
|
||||
runNxSync(`g @nrwl/nx-cloud:init --installationSource=add-nx-to-monorepo`, {
|
||||
stdio: [0, 1, 2],
|
||||
cwd: repoRoot,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { existsSync } from 'fs';
|
||||
import { defaultHashing } from '../../../../hasher/hashing-impl';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ProjectGraphBuilder } from '../../../../project-graph/project-graph-builder';
|
||||
@@ -16,13 +17,16 @@ export function buildNpmPackageNodes(builder: ProjectGraphBuilder) {
|
||||
...packageJson.devDependencies,
|
||||
};
|
||||
Object.keys(deps).forEach((d) => {
|
||||
builder.addExternalNode({
|
||||
type: 'npm',
|
||||
name: `npm:${d}`,
|
||||
data: {
|
||||
version: deps[d],
|
||||
packageName: d,
|
||||
},
|
||||
});
|
||||
if (!builder.graph.externalNodes[`npm:${d}`]) {
|
||||
builder.addExternalNode({
|
||||
type: 'npm',
|
||||
name: `npm:${d}`,
|
||||
data: {
|
||||
version: deps[d],
|
||||
packageName: d,
|
||||
hash: defaultHashing.hashArray([d, deps[d]]),
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+3
@@ -116,16 +116,19 @@ describe('explicit package json dependencies', () => {
|
||||
sourceProjectName: 'proj',
|
||||
targetProjectName: 'proj2',
|
||||
sourceProjectFile: 'libs/proj/package.json',
|
||||
type: 'static',
|
||||
},
|
||||
{
|
||||
sourceProjectFile: 'libs/proj/package.json',
|
||||
sourceProjectName: 'proj',
|
||||
targetProjectName: 'npm:external',
|
||||
type: 'static',
|
||||
},
|
||||
{
|
||||
sourceProjectName: 'proj',
|
||||
targetProjectName: 'proj3',
|
||||
sourceProjectFile: 'libs/proj/package.json',
|
||||
type: 'static',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
+7
-1
@@ -1,6 +1,10 @@
|
||||
import { defaultFileRead } from '../file-utils';
|
||||
import { join } from 'path';
|
||||
import { ProjectFileMap, ProjectGraph } from '../../config/project-graph';
|
||||
import {
|
||||
DependencyType,
|
||||
ProjectFileMap,
|
||||
ProjectGraph,
|
||||
} from '../../config/project-graph';
|
||||
import { parseJson } from '../../utils/json';
|
||||
import { getImportPath, joinPathFragments } from '../../utils/path';
|
||||
import { ProjectsConfigurations } from '../../config/workspace-json-project-json';
|
||||
@@ -88,12 +92,14 @@ function processPackageJson(
|
||||
sourceProjectName: sourceProject,
|
||||
targetProjectName: packageNameMap[d],
|
||||
sourceProjectFile: fileName,
|
||||
type: DependencyType.static,
|
||||
});
|
||||
} else if (graph.externalNodes[`npm:${d}`]) {
|
||||
collectedDeps.push({
|
||||
sourceProjectName: sourceProject,
|
||||
targetProjectName: `npm:${d}`,
|
||||
sourceProjectFile: fileName,
|
||||
type: DependencyType.static,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -67,6 +67,16 @@ export class ProjectGraphBuilder {
|
||||
* Adds a external node to the project graph
|
||||
*/
|
||||
addExternalNode(node: ProjectGraphExternalNode): void {
|
||||
// Check if project with the same name already exists
|
||||
if (this.graph.externalNodes[node.name]) {
|
||||
throw new Error(
|
||||
`Multiple projects are named "${node.name}". One has version "${
|
||||
node.data.version
|
||||
}" and the other has version "${
|
||||
this.graph.externalNodes[node.name].data.version
|
||||
}". Please resolve the conflicting package names.`
|
||||
);
|
||||
}
|
||||
this.graph.externalNodes[node.name] = node;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { execSync, ExecSyncOptions } from 'child_process';
|
||||
import { existsSync } from 'fs';
|
||||
import { join, relative } from 'path';
|
||||
import { getPackageManagerCommand } from './package-manager';
|
||||
import { workspaceRoot, workspaceRootInner } from './workspace-root';
|
||||
|
||||
export function runNxSync(
|
||||
cmd: string,
|
||||
options?: ExecSyncOptions & { cwd?: string }
|
||||
) {
|
||||
let baseCmd: string;
|
||||
if (existsSync(join(workspaceRoot, 'package.json'))) {
|
||||
baseCmd = `${getPackageManagerCommand().exec} nx`;
|
||||
} else {
|
||||
options ??= {};
|
||||
options.cwd ??= process.cwd();
|
||||
const offsetFromRoot = relative(
|
||||
options.cwd,
|
||||
workspaceRootInner(options.cwd, null)
|
||||
);
|
||||
if (process.platform === 'win32') {
|
||||
baseCmd = '.\\' + join(`${offsetFromRoot}`, 'nx.bat');
|
||||
} else {
|
||||
baseCmd = './' + join(`${offsetFromRoot}`, 'nx');
|
||||
}
|
||||
}
|
||||
execSync(`${baseCmd} ${cmd}`, options);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { chunkify } from './chunkify';
|
||||
|
||||
describe('chunkify', () => {
|
||||
it('should wrap chunks at passed in size', () => {
|
||||
const files = ['aa', 'bb', 'cc', 'dd', 'ee'];
|
||||
expect(chunkify(files, 4)).toHaveLength(5);
|
||||
expect(chunkify(files, 7)).toHaveLength(3);
|
||||
expect(chunkify(files, 16)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should contain all items from target', () => {
|
||||
const files = ['aa', 'bb', 'cc', 'dd', 'ee'];
|
||||
expect(chunkify(files, 7).flat()).toHaveLength(5);
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user