Compare commits

...

22 Commits

Author SHA1 Message Date
Jason Jean 76f145aff0 fix(misc): adjust nx cloud ab test (#26866)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

![image](https://github.com/nrwl/nx/assets/8104246/98c3b475-d213-4e81-8c80-4010714ad4f7)

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

![image](https://github.com/nrwl/nx/assets/8104246/2a67ae6b-2718-45f3-adfe-2dcc88c8ecbd)

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

(cherry picked from commit 92e09d9322)
2024-07-08 18:57:38 -04:00
Pavlo e2eda2cd5e feat(core): add support for wildcards in dependsOn (#19611)
Now it is possible to define targets like this:

```
{
  "targets": {
    "build-css": {},
    "build-js": {},
    "test": {
      "dependsOn": ["build-*"]
    },
  }
}
```

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

## Current Behavior
No support for wildcard target dependencies.

## Expected Behavior
This PR is an example of what I described here:
https://github.com/nrwl/nx/issues/19414.

## Related Issue(s)
Closes #19414

---------

Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
(cherry picked from commit 3e0d2de468)
2024-07-08 18:57:37 -04:00
Ben Snyder 8462387245 feat(linter): support eslint.config.cjs and *.cjs extension with flat config (#26637)
(cherry picked from commit 81fe13250a)
2024-07-08 18:57:36 -04:00
Craigory Coppola 95f851c4e3 fix(core): ensure better create nodes error messaging (#26811)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
We see a stack trace pointing to where the aggregate create nodes error
is thrown rather than the messaging about the individual errors

<img width="911" alt="image"
src="https://github.com/nrwl/nx/assets/6933928/61907d8c-bb77-4a39-b55f-42fa38c0ed19">

## Expected Behavior
The message is shown

<img width="946" alt="image"
src="https://github.com/nrwl/nx/assets/6933928/f79f5aeb-e5b2-4ff1-9cbc-a7f10e8f910e">

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

(cherry picked from commit 3cbe2abc25)
2024-07-08 18:57:36 -04:00
Emily Xiong 3493ef7618 fix(gradle): fix gradle exclude src/test (#26741)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

(cherry picked from commit 4ae16b361f)
2024-07-05 16:10:28 -04:00
Jonathan Gelin 4eae2d20bd fix(nx-plugin): tslib is not set as a dependency when using create-package generator (#22429)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

## Current Behavior
The linting failed on clean `create-nx-plugin` generation:

![image](https://github.com/nrwl/nx/assets/954509/80bfbaeb-0994-48d9-a6dc-20f8f723a95d)

## Expected Behavior
After a clean generation, build, test, and lint should be successful

Co-authored-by: Emily Xiong <xiongemi@gmail.com>
(cherry picked from commit 23ce6af2cc)
2024-07-05 16:10:27 -04:00
Naymi 49da9b19f4 fix(core): recursive resolve deps on create command graph (#22989)
## introductory
<img width="620" alt="image"
src="https://github.com/nrwl/nx/assets/21343944/1ef5e2a2-0f65-4fde-aa7d-b52152af59a7">

## Current Behavior

```shell
nx exec --projects=my-node-app -- pwd
TypeError: graph.dependencies[id] is not iterable
    at _findCycle (/Users/anatolijfedorov/prjcts/tmp/nx-nest-workspace/node_modules/nx/src/tasks-runner/task-graph-utils.js:8:39)
    at _findCycle (/Users/anatolijfedorov/prjcts/tmp/nx-nest-workspace/node_modules/nx/src/tasks-runner/task-graph-utils.js:11:23)
    at findCycle (/Users/anatolijfedorov/prjcts/tmp/nx-nest-workspace/node_modules/nx/src/tasks-runner/task-graph-utils.js:23:23)
    at createCommandGraph (/Users/anatolijfedorov/prjcts/tmp/nx-nest-workspace/node_modules/nx/src/commands-runner/create-command-graph.js:25:52)
    at getCommandProjects (/Users/anatolijfedorov/prjcts/tmp/nx-nest-workspace/node_modules/nx/src/commands-runner/get-command-projects.js:7:72)
    at runScriptAsNxTarget (/Users/anatolijfedorov/prjcts/tmp/nx-nest-workspace/node_modules/nx/src/command-line/exec/exec.js:60:73)
    at Object.nxExecCommand (/Users/anatolijfedorov/prjcts/tmp/nx-nest-workspace/node_modules/nx/src/command-line/exec/exec.js:44:16)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async Object.handler (/Users/anatolijfedorov/prjcts/tmp/nx-nest-workspace/node_modules/nx/src/command-line/exec/command-object.js:11:13)

```

## Expected Behavior

```shell
nx exec --projects=my-node-app -- pwd
/tmp/nx-nest-workspace/my-node-lib-2
/tmp/nx-nest-workspace/my-node-lib
/tmp/nx-nest-workspace/apps/my-node-app
```

Fixes https://github.com/nrwl/nx/issues/22994
Added function that resolves dependencies recursively

Co-authored-by: afedorov <afedorov@rvision.ru>
(cherry picked from commit e15479b691)
2024-07-05 14:52:36 -04:00
Jack Hsu c8c42958e3 docs(nx-cloud): update onboarding flow to use "nx connect" (#26842)
The new Nx Cloud onboarding flow is to run `nx connect` and follow the
prompts. There's no need to explain beyond that in the docs since the
flow is prone to changes in nx.app.

(cherry picked from commit 2baf672b61)
2024-07-05 14:52:35 -04:00
Jack Hsu 8947560166 docs(misc): disable prefetch on page load for links (#26837)
This PR changes `<Link>` to use `prefetch={false}` in the following
components:

1. Header
2. Sidebar
3. Docs header
4. Markdown content

This means that prefetch happens on hover of the link rather than page
load, and will reduce the amount of edge requests made from the app.

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

(cherry picked from commit c7878533ed)
2024-07-05 14:52:34 -04:00
Jack Hsu 7dc54ff536 feat(react-native): update react-native-svg to 15.3.0 (#26827)
There's a bug in 15.2.0 so this PR updates the version. See:
https://github.com/software-mansion/react-native-svg/issues/2241#issuecomment-2115255421

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

(cherry picked from commit 7caf9017f0)
2024-07-05 14:52:33 -04:00
Emily Xiong 8c84ab1aa8 feat(graph): add copy button for entire target configuration (#26284)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
<img width="540" alt="Screenshot 2024-07-03 at 5 29 12 PM"
src="https://github.com/nrwl/nx/assets/16211801/bed98c56-3bd5-4170-893b-cefe5fe292f9">

<img width="1195" alt="Screenshot 2024-07-03 at 5 29 03 PM"
src="https://github.com/nrwl/nx/assets/16211801/544a4c4e-299f-40fc-a767-215d9c758dd8">

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

(cherry picked from commit 311710e56c)
2024-07-05 14:52:33 -04:00
MaxKless 892c98fe9b fix(core): ignore errors from cleanupNativeFileCache (revert to previous behaviour) (#26806)
(cherry picked from commit e09bad9363)
2024-07-05 14:52:32 -04:00
Nicholas Cunningham ae38180934 fix(node): Enable e2e test (#23508)
Re-enables e2e tests for node

Docker changes to be merged first: https://github.com/nrwl/nx/pull/26365

(cherry picked from commit 295c4229db)
2024-07-05 14:52:31 -04:00
Juri Strumpflohner d4b1a7e774 feat(nx-dev): update next to fix img fetchpriority error (#26766)
## Current Behavior

`fetchpriority` attribute error in Next image loading cmp.

<img width="1260" alt="image"
src="https://github.com/nrwl/nx/assets/542458/9ba9f539-e0ad-4fe1-9a6a-64db856fd25c">

## Expected Behavior

Upgrading patch version of next fixes it.

//Edit: some context:
https://github.com/vercel/next.js/issues/65161#issuecomment-2087899325

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

(cherry picked from commit 929a57c127)
2024-07-05 14:52:31 -04:00
Juri Strumpflohner 3ea1ecd1b2 fix(nx-dev): plugin-registry menu (#26767)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

When on https://nx.dev/plugin-registry the side-bar menu doesn't open on
mobile.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

Should obviously open. Also adjusted the top-level header menu
"Documentation" -> "Docs" (just sounds better)

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

(cherry picked from commit 6ce107d6e1)
2024-07-05 14:52:30 -04:00
Jason Jean e4ae1e7b01 fix(core): always inherit output from plugins (#26797)
(cherry picked from commit 28939dd982)
2024-07-05 14:52:29 -04:00
Jack Hsu 314c2fdf88 fix(core): register swc transpiler once per compilerOptions (#26807)
Apply the same patch for `@swc-node/register` that we did for `ts-node`.
This prevent errors during graph construction.

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #26798

(cherry picked from commit 8cf96cc5b2)
2024-07-05 14:52:28 -04:00
Isaac Mann 9f118e846d docs(core): react standalone tutorial (#26304)
Update the react standalone tutorial to start from `npm create vite --
--template=react-ts`

(cherry picked from commit 65c3e560e2)
2024-07-05 14:52:28 -04:00
EugeneKruglei a6d74286a9 fix(core): cannot read properties of undefined (reading 'options') (#26721)
(cherry picked from commit 6e84cbfac0)
2024-07-05 14:52:26 -04:00
Isaac Mann 23add04f18 docs(core): sync top menu features with sidebar (#26814)
- Syncs top menu features drop down names with sidebar
- Moves Setup enterprise feature under Enterprise -> On Premise
- Collapse concepts section in CI tab

(cherry picked from commit 7cd35e0ca1)
2024-07-05 14:52:25 -04:00
Isaac Mann 8dc97c3ba8 docs(core): vue cli tutorial (#26451)
Update the Vue standalone tutorial to start from a Vue CLI generated
project

(cherry picked from commit fa3c5c42cd)
2024-07-05 14:52:24 -04:00
Leosvel Pérez Espinosa ec37b1e5e8 fix(js): normalize excluded paths to task inputs correctly in typescript plugin (#26801)
(cherry picked from commit adb81afa91)
2024-07-05 14:52:23 -04:00
107 changed files with 2922 additions and 1545 deletions
+2 -2
View File
@@ -121,9 +121,9 @@ Generate a 'src/' directory for Next.js
Type: `string`
Choices: [yes, github, circleci, skip]
Choices: [github, circleci, gitlab, azure, bitbucket, skip, yes]
Do you want Nx Cloud to make your CI fast?
Which CI provider would you like to use?
### packageManager
+66 -55
View File
@@ -28,6 +28,17 @@
"path": "/ci/intro/why-nx-cloud",
"tags": []
},
{
"id": "connect-to-cloud",
"name": "Connect Nx Cloud",
"description": "",
"mediaImage": "",
"file": "nx-cloud/intro/connect-to-cloud",
"itemList": [],
"isExternal": false,
"path": "/ci/intro/connect-to-cloud",
"tags": []
},
{
"id": "tutorials",
"name": "Tutorials",
@@ -89,6 +100,17 @@
"path": "/ci/intro/why-nx-cloud",
"tags": []
},
"/ci/intro/connect-to-cloud": {
"id": "connect-to-cloud",
"name": "Connect Nx Cloud",
"description": "",
"mediaImage": "",
"file": "nx-cloud/intro/connect-to-cloud",
"itemList": [],
"isExternal": false,
"path": "/ci/intro/connect-to-cloud",
"tags": []
},
"/ci/intro/tutorials": {
"id": "tutorials",
"name": "Tutorials",
@@ -217,17 +239,6 @@
"isExternal": false,
"path": "/ci/features/flaky-tasks",
"tags": []
},
{
"id": "on-premise",
"name": "Set up Nx Cloud On-Premise",
"description": "Set up Nx Cloud on machines that you control",
"mediaImage": "",
"file": "nx-cloud/features/nx-enterprise-on-prem",
"itemList": [],
"isExternal": false,
"path": "/ci/features/on-premise",
"tags": ["on-premise"]
}
],
"isExternal": false,
@@ -300,17 +311,6 @@
"path": "/ci/features/flaky-tasks",
"tags": []
},
"/ci/features/on-premise": {
"id": "on-premise",
"name": "Set up Nx Cloud On-Premise",
"description": "Set up Nx Cloud on machines that you control",
"mediaImage": "",
"file": "nx-cloud/features/nx-enterprise-on-prem",
"itemList": [],
"isExternal": false,
"path": "/ci/features/on-premise",
"tags": ["on-premise"]
},
"/ci/concepts": {
"id": "concepts",
"name": "Concepts",
@@ -425,17 +425,6 @@
"mediaImage": "",
"file": "",
"itemList": [
{
"id": "connect-to-cloud",
"name": "Connect Nx Cloud",
"description": "",
"mediaImage": "",
"file": "nx-cloud/recipes/connect-to-cloud",
"itemList": [],
"isExternal": false,
"path": "/ci/recipes/set-up/connect-to-cloud",
"tags": []
},
{
"id": "monorepo-ci-azure",
"name": "Setting up Azure Pipelines",
@@ -622,6 +611,17 @@
"mediaImage": "",
"file": "",
"itemList": [
{
"id": "on-premise",
"name": "Set up Nx Cloud On-Premise",
"description": "Set up Nx Cloud on machines that you control",
"mediaImage": "",
"file": "nx-cloud/features/nx-enterprise-on-prem",
"itemList": [],
"isExternal": false,
"path": "/ci/recipes/enterprise/on-premise/on-premise",
"tags": ["on-premise"]
},
{
"id": "auth-single-admin",
"name": "Authenticate with a Single Admin",
@@ -855,17 +855,6 @@
"mediaImage": "",
"file": "",
"itemList": [
{
"id": "connect-to-cloud",
"name": "Connect Nx Cloud",
"description": "",
"mediaImage": "",
"file": "nx-cloud/recipes/connect-to-cloud",
"itemList": [],
"isExternal": false,
"path": "/ci/recipes/set-up/connect-to-cloud",
"tags": []
},
{
"id": "monorepo-ci-azure",
"name": "Setting up Azure Pipelines",
@@ -937,17 +926,6 @@
"path": "/ci/recipes/set-up",
"tags": ["distribute-task-execution"]
},
"/ci/recipes/set-up/connect-to-cloud": {
"id": "connect-to-cloud",
"name": "Connect Nx Cloud",
"description": "",
"mediaImage": "",
"file": "nx-cloud/recipes/connect-to-cloud",
"itemList": [],
"isExternal": false,
"path": "/ci/recipes/set-up/connect-to-cloud",
"tags": []
},
"/ci/recipes/set-up/monorepo-ci-azure": {
"id": "monorepo-ci-azure",
"name": "Setting up Azure Pipelines",
@@ -1206,6 +1184,17 @@
"mediaImage": "",
"file": "",
"itemList": [
{
"id": "on-premise",
"name": "Set up Nx Cloud On-Premise",
"description": "Set up Nx Cloud on machines that you control",
"mediaImage": "",
"file": "nx-cloud/features/nx-enterprise-on-prem",
"itemList": [],
"isExternal": false,
"path": "/ci/recipes/enterprise/on-premise/on-premise",
"tags": ["on-premise"]
},
{
"id": "auth-single-admin",
"name": "Authenticate with a Single Admin",
@@ -1400,6 +1389,17 @@
"mediaImage": "",
"file": "",
"itemList": [
{
"id": "on-premise",
"name": "Set up Nx Cloud On-Premise",
"description": "Set up Nx Cloud on machines that you control",
"mediaImage": "",
"file": "nx-cloud/features/nx-enterprise-on-prem",
"itemList": [],
"isExternal": false,
"path": "/ci/recipes/enterprise/on-premise/on-premise",
"tags": ["on-premise"]
},
{
"id": "auth-single-admin",
"name": "Authenticate with a Single Admin",
@@ -1504,6 +1504,17 @@
"path": "/ci/recipes/enterprise/on-premise",
"tags": []
},
"/ci/recipes/enterprise/on-premise/on-premise": {
"id": "on-premise",
"name": "Set up Nx Cloud On-Premise",
"description": "Set up Nx Cloud on machines that you control",
"mediaImage": "",
"file": "nx-cloud/features/nx-enterprise-on-prem",
"itemList": [],
"isExternal": false,
"path": "/ci/recipes/enterprise/on-premise/on-premise",
"tags": ["on-premise"]
},
"/ci/recipes/enterprise/on-premise/auth-single-admin": {
"id": "auth-single-admin",
"name": "Authenticate with a Single Admin",
+50 -66
View File
@@ -284,7 +284,7 @@
"disableCollapsible": false
},
{
"name": "Explore your Workspace",
"name": "Explore Your Workspace",
"path": "/features/explore-graph",
"id": "explore-graph",
"isExternal": false,
@@ -376,14 +376,6 @@
"isExternal": true,
"children": [],
"disableCollapsible": false
},
{
"name": "Set up Nx Cloud On-Premise",
"path": "/ci/features/on-premise",
"id": "on-premise",
"isExternal": true,
"children": [],
"disableCollapsible": false
}
],
"disableCollapsible": false
@@ -408,7 +400,7 @@
"disableCollapsible": false
},
{
"name": "Explore your Workspace",
"name": "Explore Your Workspace",
"path": "/features/explore-graph",
"id": "explore-graph",
"isExternal": false,
@@ -500,14 +492,6 @@
"isExternal": true,
"children": [],
"disableCollapsible": false
},
{
"name": "Set up Nx Cloud On-Premise",
"path": "/ci/features/on-premise",
"id": "on-premise",
"isExternal": true,
"children": [],
"disableCollapsible": false
}
],
"disableCollapsible": false
@@ -560,14 +544,6 @@
"children": [],
"disableCollapsible": false
},
{
"name": "Set up Nx Cloud On-Premise",
"path": "/ci/features/on-premise",
"id": "on-premise",
"isExternal": true,
"children": [],
"disableCollapsible": false
},
{
"name": "Concepts",
"path": "/concepts",
@@ -5169,6 +5145,14 @@
"children": [],
"disableCollapsible": false
},
{
"name": "Connect Nx Cloud",
"path": "/ci/intro/connect-to-cloud",
"id": "connect-to-cloud",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Tutorials",
"path": "/ci/intro/tutorials",
@@ -5213,6 +5197,14 @@
"children": [],
"disableCollapsible": false
},
{
"name": "Connect Nx Cloud",
"path": "/ci/intro/connect-to-cloud",
"id": "connect-to-cloud",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Tutorials",
"path": "/ci/intro/tutorials",
@@ -5307,14 +5299,6 @@
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Set up Nx Cloud On-Premise",
"path": "/ci/features/on-premise",
"id": "on-premise",
"isExternal": false,
"children": [],
"disableCollapsible": false
}
],
"disableCollapsible": false
@@ -5367,14 +5351,6 @@
"children": [],
"disableCollapsible": false
},
{
"name": "Set up Nx Cloud On-Premise",
"path": "/ci/features/on-premise",
"id": "on-premise",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Concepts",
"path": "/ci/concepts",
@@ -5460,14 +5436,6 @@
"id": "set-up",
"isExternal": false,
"children": [
{
"name": "Connect Nx Cloud",
"path": "/ci/recipes/set-up/connect-to-cloud",
"id": "connect-to-cloud",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Setting up Azure Pipelines",
"path": "/ci/recipes/set-up/monorepo-ci-azure",
@@ -5605,6 +5573,14 @@
"id": "on-premise",
"isExternal": false,
"children": [
{
"name": "Set up Nx Cloud On-Premise",
"path": "/ci/recipes/enterprise/on-premise/on-premise",
"id": "on-premise",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Authenticate with a Single Admin",
"path": "/ci/recipes/enterprise/on-premise/auth-single-admin",
@@ -5774,14 +5750,6 @@
"id": "set-up",
"isExternal": false,
"children": [
{
"name": "Connect Nx Cloud",
"path": "/ci/recipes/set-up/connect-to-cloud",
"id": "connect-to-cloud",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Setting up Azure Pipelines",
"path": "/ci/recipes/set-up/monorepo-ci-azure",
@@ -5833,14 +5801,6 @@
],
"disableCollapsible": false
},
{
"name": "Connect Nx Cloud",
"path": "/ci/recipes/set-up/connect-to-cloud",
"id": "connect-to-cloud",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Setting up Azure Pipelines",
"path": "/ci/recipes/set-up/monorepo-ci-azure",
@@ -6031,6 +5991,14 @@
"id": "on-premise",
"isExternal": false,
"children": [
{
"name": "Set up Nx Cloud On-Premise",
"path": "/ci/recipes/enterprise/on-premise/on-premise",
"id": "on-premise",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Authenticate with a Single Admin",
"path": "/ci/recipes/enterprise/on-premise/auth-single-admin",
@@ -6172,6 +6140,14 @@
"id": "on-premise",
"isExternal": false,
"children": [
{
"name": "Set up Nx Cloud On-Premise",
"path": "/ci/recipes/enterprise/on-premise/on-premise",
"id": "on-premise",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Authenticate with a Single Admin",
"path": "/ci/recipes/enterprise/on-premise/auth-single-admin",
@@ -6247,6 +6223,14 @@
],
"disableCollapsible": false
},
{
"name": "Set up Nx Cloud On-Premise",
"path": "/ci/recipes/enterprise/on-premise/on-premise",
"id": "on-premise",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Authenticate with a Single Admin",
"path": "/ci/recipes/enterprise/on-premise/auth-single-admin",
+2 -35
View File
@@ -385,7 +385,7 @@
},
{
"id": "explore-graph",
"name": "Explore your Workspace",
"name": "Explore Your Workspace",
"description": "",
"mediaImage": "",
"file": "shared/features/explore-graph",
@@ -510,17 +510,6 @@
"isExternal": true,
"path": "/ci/features/flaky-tasks",
"tags": []
},
{
"id": "on-premise",
"name": "Set up Nx Cloud On-Premise",
"description": "Set up Nx Cloud on machines that you control",
"mediaImage": "",
"file": "",
"itemList": [],
"isExternal": true,
"path": "/ci/features/on-premise",
"tags": []
}
],
"isExternal": false,
@@ -556,7 +545,7 @@
},
"/features/explore-graph": {
"id": "explore-graph",
"name": "Explore your Workspace",
"name": "Explore Your Workspace",
"description": "",
"mediaImage": "",
"file": "shared/features/explore-graph",
@@ -681,17 +670,6 @@
"isExternal": true,
"path": "/ci/features/flaky-tasks",
"tags": []
},
{
"id": "on-premise",
"name": "Set up Nx Cloud On-Premise",
"description": "Set up Nx Cloud on machines that you control",
"mediaImage": "",
"file": "",
"itemList": [],
"isExternal": true,
"path": "/ci/features/on-premise",
"tags": []
}
],
"isExternal": false,
@@ -764,17 +742,6 @@
"path": "/ci/features/flaky-tasks",
"tags": []
},
"/ci/features/on-premise": {
"id": "on-premise",
"name": "Set up Nx Cloud On-Premise",
"description": "Set up Nx Cloud on machines that you control",
"mediaImage": "",
"file": "",
"itemList": [],
"isExternal": true,
"path": "/ci/features/on-premise",
"tags": []
},
"/concepts": {
"id": "concepts",
"name": "Concepts",
+2 -2
View File
@@ -248,7 +248,7 @@
"description": "",
"file": "shared/features/explore-graph",
"id": "explore-graph",
"name": "Explore your Workspace",
"name": "Explore Your Workspace",
"path": "/features/explore-graph"
},
{
@@ -1134,7 +1134,7 @@
"file": "nx-cloud/features/nx-enterprise-on-prem",
"id": "on-premise",
"name": "Set up Nx Cloud On-Premise",
"path": "/ci/features/on-premise"
"path": "/ci/recipes/enterprise/on-premise/on-premise"
}
],
"docker": [
@@ -121,9 +121,9 @@ Generate a 'src/' directory for Next.js
Type: `string`
Choices: [yes, github, circleci, skip]
Choices: [github, circleci, gitlab, azure, bitbucket, skip, yes]
Do you want Nx Cloud to make your CI fast?
Which CI provider would you like to use?
### packageManager
+13 -21
View File
@@ -99,7 +99,7 @@
"file": "shared/features/cache-task-results"
},
{
"name": "Explore your Workspace",
"name": "Explore Your Workspace",
"id": "explore-graph",
"tags": ["explore-graph"],
"file": "shared/features/explore-graph"
@@ -179,14 +179,6 @@
"file": "",
"path": "/ci/features/flaky-tasks",
"isExternal": true
},
{
"name": "Set up Nx Cloud On-Premise",
"id": "on-premise",
"description": "Set up Nx Cloud on machines that you control",
"file": "",
"path": "/ci/features/on-premise",
"isExternal": true
}
]
}
@@ -1572,6 +1564,11 @@
"id": "why-nx-cloud",
"file": "nx-cloud/intro/why-nx-cloud"
},
{
"name": "Connect Nx Cloud",
"id": "connect-to-cloud",
"file": "nx-cloud/intro/connect-to-cloud"
},
{
"name": "Tutorials",
"id": "tutorials",
@@ -1633,13 +1630,6 @@
"id": "flaky-tasks",
"description": "Learn how Nx Cloud is able to automatically identify flaky tasks and re-run them for you.",
"file": "nx-cloud/features/flaky-tasks"
},
{
"name": "Set up Nx Cloud On-Premise",
"description": "Set up Nx Cloud on machines that you control",
"id": "on-premise",
"tags": ["on-premise"],
"file": "nx-cloud/features/nx-enterprise-on-prem"
}
]
},
@@ -1684,11 +1674,6 @@
"description": "Learn how to set up Nx Cloud for your workspace.",
"tags": ["distribute-task-execution"],
"itemList": [
{
"name": "Connect Nx Cloud",
"id": "connect-to-cloud",
"file": "nx-cloud/recipes/connect-to-cloud"
},
{
"name": "Setting up Azure Pipelines",
"id": "monorepo-ci-azure",
@@ -1780,6 +1765,13 @@
"id": "on-premise",
"description": "Manage an on-premise installation of Nx Cloud",
"itemList": [
{
"name": "Set up Nx Cloud On-Premise",
"description": "Set up Nx Cloud on machines that you control",
"id": "on-premise",
"tags": ["on-premise"],
"file": "nx-cloud/features/nx-enterprise-on-prem"
},
{
"name": "Authenticate with a Single Admin",
"id": "auth-single-admin",
+4
View File
@@ -14,6 +14,10 @@ Implementing an efficient CI setup for monorepos - while crucial - can be challe
[Create an account on Nx Cloud](https://cloud.nx.app) and connect your repository.
```shell
npx nx connect
```
## Learn about Nx on CI
{% cards cols="2" lgCols="4" mdCols="4" smCols="2" %}
+44
View File
@@ -0,0 +1,44 @@
# Connect to Nx Cloud
Nx Cloud directly integrates with your existing CI setup.
![Nx Cloud Overview](/shared/images/nx-cloud/nx-cloud-overview.webp)
In a nutshell, here's how this works:
**Step 1: Connect your workspace to Nx Cloud**
This can be done by signing up on [nx.app](https://nx.app) and then connecting to your git repository.
```shell
npx nx connect
```
**Step 2: Your CI script triggers Nx Cloud**
```yml
- name: Start CI run
run: 'npx nx-cloud start-ci-run --distribute-on="8 linux-medium-js"'
```
Let us generate the workflow file for you, if you don't already have one.
```shell
npx nx g ci-workflow
```
Or, check out our [recipes for the various CI providers](/ci/recipes/set-up).
**Step 3: Run your Nx commands as usual**
```yml
- run: npx nx-cloud record -- nx format:check
- run: npx nx affected -t lint test build
- run: npx nx affected -t e2e-ci --parallel 1
```
All these commands are automatically picked up by Nx Cloud, split up into smaller tasks and distributed across the specified number of machines.
**Step 4: All results are played back automatically**
Nx Cloud automatically plays back all results to your CI system, as if distribution never happened. You can continue doing post-processing on the results, like uploading test reports, deploying artifacts etc.
+2 -32
View File
@@ -21,39 +21,9 @@ All this is possible because Nx Cloud directly integrates with Nx and has knowle
Read more about individual features of Nx Cloud in the [features section](/ci/features).
## How does Nx Cloud integrate into my existing CI setup
## Integrate Nx Cloud into my CI setup
Nx Cloud directly integrates with your existing CI setup.
![Nx Cloud Overview](/shared/images/nx-cloud/nx-cloud-overview.webp)
In a nutshell, here's how this works:
**Step 1: Connect your workspace to Nx Cloud**
This can be done by signing up on [nx.app](https://nx.app) and then connecting to your git repository. Read more about all the details [on this doc page](/ci/recipes/set-up/connect-to-cloud).
**Step 2: Your CI script triggers Nx Cloud**
```yml
- name: Start CI run
run: 'npx nx-cloud start-ci-run --distribute-on="8 linux-medium-js"'
```
Check out our [recipes for the various CI providers](/ci/recipes/set-up).
**Step 3: Run your Nx command as usual**
```yml
- run: npx nx-cloud record -- nx format:check
- run: npx nx affected -t lint test build e2e-ci
```
All these commands are automatically picked up by Nx Cloud, split up into smaller tasks and distributed across the specified number of machines.
**Step 4: All results are played back automatically**
Nx Cloud automatically plays back all results to your CI system, as if distribution never happened. You can continue doing post-processing on the results, like uploading test reports, deploying artifacts etc.
Ready to experience fast CI? Read the [connect to Nx Cloud](/ci/intro/connect-to-cloud) page for more details.
## Learn more
-33
View File
@@ -1,33 +0,0 @@
# Connect to Nx Cloud
Create an account on [nx.app](https://nx.app). There are several ways to connect your repository to Nx Cloud.
#### Connect Directly Through GitHub
If your repository is hosted on GitHub, we recommend you create an Nx Cloud organization based on your GitHub organization.
![Connect Your VCS Account](/nx-cloud/tutorial/connect-vcs-account.png)
After that, connect you repository.
![Connect Your Repository](/nx-cloud/tutorial/connect-repository.png)
This will send a pull request to your repository that will add the `nxCloudAccessToken` property to `nx.json`.
![Nx Cloud Setup PR](/nx-cloud/tutorial/nx-cloud-setup-pr.png)
This wires up all the CI for you and configures access. Folks who can see your repository can see your workspace on nx.app.
## Manually Connect Your Workspace
If your repository is hosted on a different source control provider, you can also connect to Nx Cloud manually. You'll need to add a source control integration later to enable [Nx Agents](/ci/features/distribute-task-execution).
Run the following command in your repository:
```shell
pnpm nx connect
```
Click the link in the terminal to claim your workspace on [nx.app](https://nx.app).
The command generates an `nxCloudAccessToken` property inside of `nx.json`. This is a read-only token that should be committed to the repository.
+1 -1
View File
@@ -10,7 +10,7 @@ In this diagram, Teika runs the build once on his machine, then CI, Kimiko and J
## Setting Up Nx Cloud
To use **Nx Replay** you need to connect your workspace to Nx Cloud. See the [connect to Nx Cloud recipe](/ci/recipes/set-up/connect-to-cloud).
To use **Nx Replay** you need to connect your workspace to Nx Cloud. See the [connect to Nx Cloud recipe](/ci/intro/connect-to-cloud).
## See Remote Caching in Action
Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 KiB

+3 -3
View File
@@ -16,7 +16,7 @@
- [Features](/features)
- [Run Tasks](/features/run-tasks)
- [Cache Task Results](/features/cache-task-results)
- [Explore your Workspace](/features/explore-graph)
- [Explore Your Workspace](/features/explore-graph)
- [Generate Code](/features/generate-code)
- [Automate Updating Dependencies](/features/automate-updating-dependencies)
- [Enforce Module Boundaries](/features/enforce-module-boundaries)
@@ -245,6 +245,7 @@
- [Intro](/ci/intro)
- [CI with Nx](/ci/intro/ci-with-nx)
- [Why Nx Cloud?](/ci/intro/why-nx-cloud)
- [Connect Nx Cloud](/ci/intro/connect-to-cloud)
- [Tutorials](/ci/intro/tutorials)
- [Circle CI with Nx](/ci/intro/tutorials/circle)
- [GitHub Actions with Nx](/ci/intro/tutorials/github-actions)
@@ -255,7 +256,6 @@
- [Dynamically Allocate Agents](/ci/features/dynamic-agents)
- [Automatically Split E2E Tasks (Atomizer)](/ci/features/split-e2e-tasks)
- [Identify and Re-run Flaky Tasks](/ci/features/flaky-tasks)
- [Set up Nx Cloud On-Premise](/ci/features/on-premise)
- [Concepts](/ci/concepts)
- [The Building Blocks of Fast CI](/ci/concepts/building-blocks-fast-ci)
- [Reduce Wasted Time in CI](/ci/concepts/reduce-waste)
@@ -263,7 +263,6 @@
- [Cache Security](/ci/concepts/cache-security)
- [Recipes](/ci/recipes)
- [Set Up CI](/ci/recipes/set-up)
- [Connect Nx Cloud](/ci/recipes/set-up/connect-to-cloud)
- [Setting up Azure Pipelines](/ci/recipes/set-up/monorepo-ci-azure)
- [Setting up CircleCI](/ci/recipes/set-up/monorepo-ci-circle-ci)
- [Setting up GitHub Actions](/ci/recipes/set-up/monorepo-ci-github-actions)
@@ -281,6 +280,7 @@
- [Enable Azure DevOps PR Integration](/ci/recipes/source-control-integration/azure-devops)
- [Enterprise](/ci/recipes/enterprise)
- [On-Premise](/ci/recipes/enterprise/on-premise)
- [Set up Nx Cloud On-Premise](/ci/recipes/enterprise/on-premise/on-premise)
- [Authenticate with a Single Admin](/ci/recipes/enterprise/on-premise/auth-single-admin)
- [Authenticate with GitHub](/ci/recipes/enterprise/on-premise/auth-github)
- [On-Prem VM Setup](/ci/recipes/enterprise/on-premise/ami-setup)
+2 -4
View File
@@ -509,7 +509,7 @@ export default App;
Serving your app (`nx serve react-store`) and then navigating to `/products` should give you the following result:
![products route](/shared/images/tutorial-react-standalone/react-tutorial-products-route.png)
![products route](/shared/tutorials/react-tutorial-products-route.png)
Let's apply the same for our `orders` library.
@@ -1026,9 +1026,7 @@ NX Ran target lint for 7 projects (2s)
- nx run products:lint
```
If you have the ESLint plugin installed in your IDE you should immediately see an error:
![ESLint module boundary error](/shared/images/tutorial-react-standalone/react-standalone-module-boundaries.png)
If you have the ESLint plugin installed in your IDE you should also immediately see an error.
Learn more about how to [enforce module boundaries](/features/enforce-module-boundaries).
@@ -0,0 +1,248 @@
{
"project": {
"name": "react-app",
"type": "lib",
"data": {
"root": ".",
"targets": {
"vite:build": {
"options": {
"cwd": ".",
"command": "vite build"
},
"cache": true,
"dependsOn": ["^vite:build"],
"inputs": [
"default",
"^default",
{
"externalDependencies": ["vite"]
}
],
"outputs": ["{projectRoot}/dist"],
"executor": "nx:run-commands",
"configurations": {}
},
"build": {
"executor": "nx:run-script",
"metadata": {
"scriptContent": "nx vite:build",
"runCommand": "npm run build"
},
"cache": true,
"dependsOn": ["typecheck"],
"options": {
"script": "build"
},
"configurations": {}
}
},
"sourceRoot": ".",
"name": "react-app",
"projectType": "library",
"metadata": {
"targetGroups": {
"NPM Scripts": ["build"]
}
},
"implicitDependencies": [],
"tags": []
}
},
"sourceMap": {
"root": ["package.json", "nx/core/package-json-workspaces"],
"targets": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:build": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:build.options": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:build.cache": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:build.dependsOn": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:build.inputs": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:build.outputs": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:build.executor": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:build.options.cwd": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:build.options.command": ["vite.config.ts", "@nx/vite/plugin"],
"targets.serve": ["vite.config.ts", "@nx/vite/plugin"],
"targets.serve.options": ["vite.config.ts", "@nx/vite/plugin"],
"targets.serve.executor": ["vite.config.ts", "@nx/vite/plugin"],
"targets.serve.options.cwd": ["vite.config.ts", "@nx/vite/plugin"],
"targets.serve.options.command": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:preview": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:preview.options": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:preview.executor": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:preview.options.cwd": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:preview.options.command": [
"vite.config.ts",
"@nx/vite/plugin"
],
"targets.serve-static": ["vite.config.ts", "@nx/vite/plugin"],
"targets.serve-static.executor": ["vite.config.ts", "@nx/vite/plugin"],
"targets.serve-static.options": ["vite.config.ts", "@nx/vite/plugin"],
"targets.serve-static.options.buildTarget": [
"vite.config.ts",
"@nx/vite/plugin"
],
"targets.serve-static.options.spa": ["vite.config.ts", "@nx/vite/plugin"],
"targets.eslint:lint": [".eslintrc.cjs", "@nx/eslint/plugin"],
"targets.eslint:lint.cache": [".eslintrc.cjs", "@nx/eslint/plugin"],
"targets.eslint:lint.options": [".eslintrc.cjs", "@nx/eslint/plugin"],
"targets.eslint:lint.inputs": [".eslintrc.cjs", "@nx/eslint/plugin"],
"targets.eslint:lint.outputs": [".eslintrc.cjs", "@nx/eslint/plugin"],
"targets.eslint:lint.executor": [".eslintrc.cjs", "@nx/eslint/plugin"],
"targets.eslint:lint.options.cwd": [".eslintrc.cjs", "@nx/eslint/plugin"],
"targets.eslint:lint.options.command": [
".eslintrc.cjs",
"@nx/eslint/plugin"
],
"targets.typecheck": ["package.json", "nx/core/package-json-workspaces"],
"targets.typecheck.executor": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.typecheck.options": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.typecheck.metadata": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.typecheck.cache": ["nx.json", "nx/core/target-defaults"],
"targets.typecheck.options.script": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.typecheck.metadata.scriptContent": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.typecheck.metadata.runCommand": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build": ["package.json", "nx/core/package-json-workspaces"],
"targets.build.executor": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build.options": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build.metadata": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build.cache": ["nx.json", "nx/core/target-defaults"],
"targets.build.options.script": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build.metadata.scriptContent": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build.metadata.runCommand": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.lint": ["package.json", "nx/core/package-json-workspaces"],
"targets.lint.executor": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.lint.options": ["package.json", "nx/core/package-json-workspaces"],
"targets.lint.metadata": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.lint.cache": ["nx.json", "nx/core/target-defaults"],
"targets.lint.options.script": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.lint.metadata.scriptContent": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.lint.metadata.runCommand": [
"package.json",
"nx/core/package-json-workspaces"
],
"sourceRoot": ["package.json", "nx/core/package-json-workspaces"],
"name": ["package.json", "nx/core/package-json-workspaces"],
"projectType": ["package.json", "nx/core/package-json-workspaces"],
"metadata.targetGroups": [
"package.json",
"nx/core/package-json-workspaces"
],
"metadata.targetGroups.NPM Scripts": [
"package.json",
"nx/core/package-json-workspaces"
],
"metadata.targetGroups.NPM Scripts.0": [
"package.json",
"nx/core/package-json-workspaces"
],
"metadata.targetGroups.NPM Scripts.1": [
"package.json",
"nx/core/package-json-workspaces"
],
"metadata.targetGroups.NPM Scripts.2": [
"package.json",
"nx/core/package-json-workspaces"
],
"metadata.targetGroups.NPM Scripts.3": [
"package.json",
"nx/core/package-json-workspaces"
],
"metadata.targetGroups.NPM Scripts.4": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.dev": ["package.json", "nx/core/package-json-workspaces"],
"targets.dev.executor": ["package.json", "nx/core/package-json-workspaces"],
"targets.dev.options": ["package.json", "nx/core/package-json-workspaces"],
"targets.dev.metadata": ["package.json", "nx/core/package-json-workspaces"],
"targets.dev.options.script": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.dev.metadata.scriptContent": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.dev.metadata.runCommand": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build.dependsOn": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.preview": ["package.json", "nx/core/package-json-workspaces"],
"targets.preview.executor": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.preview.options": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.preview.metadata": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.preview.options.script": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.preview.metadata.scriptContent": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.preview.metadata.runCommand": [
"package.json",
"nx/core/package-json-workspaces"
]
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 166 KiB

+186
View File
@@ -0,0 +1,186 @@
{
"project": {
"name": "vue-app",
"type": "lib",
"data": {
"root": ".",
"targets": {
"vite:build": {
"options": {
"cwd": ".",
"command": "vite build"
},
"cache": true,
"dependsOn": ["^vite:build"],
"inputs": [
"default",
"^default",
{
"externalDependencies": ["vite"]
}
],
"outputs": ["{projectRoot}/dist"],
"executor": "nx:run-commands",
"configurations": {}
},
"build-only": {
"executor": "nx:run-script",
"metadata": {
"scriptContent": "nx vite:build",
"runCommand": "npm run build-only"
},
"outputs": ["{projectRoot}/dist"],
"cache": true,
"options": {
"script": "build-only"
},
"configurations": {}
},
"build": {
"executor": "nx:run-script",
"metadata": {
"scriptContent": "nx exec -- echo 'Ran type-check and build-only'",
"runCommand": "npm run build"
},
"dependsOn": [
"type-check",
{
"target": "build-only",
"params": "forward"
}
],
"options": {
"script": "build"
},
"configurations": {}
}
},
"sourceRoot": ".",
"name": "vue-app",
"projectType": "library",
"metadata": {
"targetGroups": {
"NPM Scripts": ["build", "build-only"]
}
},
"implicitDependencies": [],
"tags": []
}
},
"sourceMap": {
"root": ["package.json", "nx/core/package-json-workspaces"],
"targets": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:build": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:build.options": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:build.cache": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:build.dependsOn": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:build.inputs": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:build.outputs": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:build.executor": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:build.options.cwd": ["vite.config.ts", "@nx/vite/plugin"],
"targets.vite:build.options.command": ["vite.config.ts", "@nx/vite/plugin"],
"targets.build-only": ["package.json", "nx/core/package-json-workspaces"],
"targets.build-only.executor": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build-only.options": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build-only.metadata": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build-only.outputs": ["nx.json", "nx/core/target-defaults"],
"targets.build-only.cache": ["nx.json", "nx/core/target-defaults"],
"targets.build-only.options.script": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build-only.metadata.scriptContent": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build-only.metadata.runCommand": [
"package.json",
"nx/core/package-json-workspaces"
],
"sourceRoot": ["package.json", "nx/core/package-json-workspaces"],
"name": ["package.json", "nx/core/package-json-workspaces"],
"projectType": ["package.json", "nx/core/package-json-workspaces"],
"metadata.targetGroups": [
"package.json",
"nx/core/package-json-workspaces"
],
"metadata.targetGroups.NPM Scripts": [
"package.json",
"nx/core/package-json-workspaces"
],
"metadata.targetGroups.NPM Scripts.0": [
"package.json",
"nx/core/package-json-workspaces"
],
"metadata.targetGroups.NPM Scripts.1": [
"package.json",
"nx/core/package-json-workspaces"
],
"metadata.targetGroups.NPM Scripts.2": [
"package.json",
"nx/core/package-json-workspaces"
],
"metadata.targetGroups.NPM Scripts.3": [
"package.json",
"nx/core/package-json-workspaces"
],
"metadata.targetGroups.NPM Scripts.4": [
"package.json",
"nx/core/package-json-workspaces"
],
"metadata.targetGroups.NPM Scripts.5": [
"package.json",
"nx/core/package-json-workspaces"
],
"metadata.targetGroups.NPM Scripts.6": [
"package.json",
"nx/core/package-json-workspaces"
],
"metadata.targetGroups.NPM Scripts.7": [
"package.json",
"nx/core/package-json-workspaces"
],
"metadata.targetGroups.NPM Scripts.8": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build": ["package.json", "nx/core/package-json-workspaces"],
"targets.build.executor": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build.metadata": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build.dependsOn": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build.options": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build.options.script": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build.metadata.scriptContent": [
"package.json",
"nx/core/package-json-workspaces"
],
"targets.build.metadata.runCommand": [
"package.json",
"nx/core/package-json-workspaces"
]
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

File diff suppressed because it is too large Load Diff
@@ -0,0 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Node Applications + webpack should generate a Dockerfile 1`] = `
"# This file is generated by Nx.
#
# Build the docker image with \`npx nx docker-build docker-express-app\`.
# Tip: Modify "docker-build" options in project.json to change docker build args.
#
# Run the container with \`docker run -p 3000:3000 -t docker-express-app\`.
FROM docker.io/node:lts-alpine
ENV HOST=0.0.0.0
ENV PORT=3000
WORKDIR /app
RUN addgroup --system docker-express-app && \\
adduser --system -G docker-express-app docker-express-app
COPY dist/apps/docker-express-app docker-express-app/
RUN chown -R docker-express-app:docker-express-app .
# You can remove this install step if you build with \`--bundle\` option.
# The bundled output will include external dependencies.
RUN npm --prefix docker-express-app --omit=dev -f install
CMD [ "node", "docker-express-app" ]
"
`;
+87 -61
View File
@@ -2,6 +2,7 @@ import {
checkFilesDoNotExist,
checkFilesExist,
cleanupProject,
getSelectedPackageManager,
killPort,
newProject,
promisifiedTreeKill,
@@ -14,11 +15,19 @@ import {
} from '@nx/e2e/utils';
import { join } from 'path';
xdescribe('Node Applications + webpack', () => {
describe('Node Applications + webpack', () => {
let proj: string;
const selectedPm = getSelectedPackageManager();
// TODO(nicholas): Look into how this can work with npm instead of forcing pnpm.
beforeAll(() => {
proj = newProject({
packages: ['@nx/node'],
// npm has resolution for ajv some packages require ajv6 and some require ajv8 and npm resolves it to ajv6 (Error: Cannot find module 'ajv/dist/compile/codegen')
// - ajv@6 (fork-ts-checker-webpack-plugin, terser-webpack-plugin, webpack)
// - ajv@8 (babel-loader)
// Solution is to use pnpm or run fix it via npm ex.(npm dedupe --force)
packageManager: selectedPm === 'npm' ? 'pnpm' : selectedPm,
});
});
@@ -47,10 +56,10 @@ xdescribe('Node Applications + webpack', () => {
}
}
async function runE2eTests(appName: string) {
process.env.PORT = '5000';
async function runE2eTests(appName: string, port: number = 5000) {
process.env.PORT = `${port}`;
const childProcess = await runCommandUntil(`serve ${appName}`, (output) => {
return output.includes('http://localhost:5000');
return output.includes(`http://localhost:${port}`);
});
const result = runCLI(`e2e ${appName}-e2e --verbose`);
expect(result).toContain('Setting up...');
@@ -58,13 +67,11 @@ xdescribe('Node Applications + webpack', () => {
expect(result).toContain('Successfully ran target e2e');
await promisifiedTreeKill(childProcess.pid, 'SIGKILL');
await killPort(5000);
await killPort(port);
process.env.PORT = '';
}
// Disabled due to flakiness of ajv disabled (Error: Cannot find module 'ajv/dist/compile/codegen')
// TODO: (nicholas) Re-enable when the flakiness is resolved
xit('should generate an app using webpack', async () => {
describe('frameworks', () => {
const testLib1 = uniq('test1');
const testLib2 = uniq('test2');
const expressApp = uniq('expressapp');
@@ -72,79 +79,98 @@ xdescribe('Node Applications + webpack', () => {
const koaApp = uniq('koaapp');
const nestApp = uniq('nest');
runCLI(`generate @nx/node:lib ${testLib1}`);
runCLI(`generate @nx/node:lib ${testLib2} --importPath=@acme/test2`);
runCLI(
`generate @nx/node:app ${expressApp} --framework=express --no-interactive`
);
runCLI(
`generate @nx/node:app ${fastifyApp} --framework=fastify --no-interactive`
);
runCLI(`generate @nx/node:app ${koaApp} --framework=koa --no-interactive`);
runCLI(
`generate @nx/node:app ${nestApp} --framework=nest --bundler=webpack --no-interactive`
);
beforeAll(() => {
runCLI(`generate @nx/node:lib ${testLib1}`);
runCLI(`generate @nx/node:lib ${testLib2} --importPath=@acme/test2`);
runCLI(
`generate @nx/node:app ${expressApp} --framework=express --port=7000 --no-interactive`
);
runCLI(
`generate @nx/node:app ${fastifyApp} --framework=fastify --port=7001 --no-interactive`
);
runCLI(
`generate @nx/node:app ${koaApp} --framework=koa --port=7002 --no-interactive`
);
runCLI(
`generate @nx/node:app ${nestApp} --framework=nest --port=7003 --bundler=webpack --no-interactive`
);
// Use esbuild by default
checkFilesDoNotExist(`apps/${expressApp}/webpack.config.js`);
checkFilesDoNotExist(`apps/${fastifyApp}/webpack.config.js`);
checkFilesDoNotExist(`apps/${koaApp}/webpack.config.js`);
addLibImport(expressApp, testLib1);
addLibImport(expressApp, testLib2, '@acme/test2');
addLibImport(fastifyApp, testLib1);
addLibImport(fastifyApp, testLib2, '@acme/test2');
addLibImport(koaApp, testLib1);
addLibImport(koaApp, testLib2, '@acme/test2');
// Uses only webpack
checkFilesExist(`apps/${nestApp}/webpack.config.js`);
addLibImport(nestApp, testLib1);
addLibImport(nestApp, testLib2, '@acme/test2');
});
expect(() => runCLI(`lint ${expressApp}`)).not.toThrow();
expect(() => runCLI(`lint ${fastifyApp}`)).not.toThrow();
expect(() => runCLI(`lint ${koaApp}`)).not.toThrow();
expect(() => runCLI(`lint ${nestApp}`)).not.toThrow();
it('should generate an app defaults using webpack or esbuild', async () => {
// Use esbuild by default
checkFilesDoNotExist(`apps/${expressApp}/webpack.config.js`);
checkFilesDoNotExist(`apps/${fastifyApp}/webpack.config.js`);
checkFilesDoNotExist(`apps/${koaApp}/webpack.config.js`);
expect(() => runCLI(`lint ${expressApp}-e2e`)).not.toThrow();
expect(() => runCLI(`lint ${fastifyApp}-e2e`)).not.toThrow();
expect(() => runCLI(`lint ${koaApp}-e2e`)).not.toThrow();
expect(() => runCLI(`lint ${nestApp}-e2e`)).not.toThrow();
// Uses only webpack
checkFilesExist(`apps/${nestApp}/webpack.config.js`);
// Only Fastify generates with unit tests since it supports them without additional libraries.
expect(() => runCLI(`test ${fastifyApp}`)).not.toThrow();
expect(() => runCLI(`lint ${expressApp}`)).not.toThrow();
expect(() => runCLI(`lint ${fastifyApp}`)).not.toThrow();
expect(() => runCLI(`lint ${koaApp}`)).not.toThrow();
expect(() => runCLI(`lint ${nestApp}`)).not.toThrow();
// https://github.com/nrwl/nx/issues/16601
const nestMainContent = readFile(`apps/${nestApp}/src/main.ts`);
updateFile(
`apps/${nestApp}/src/main.ts`,
`
expect(() => runCLI(`lint ${expressApp}-e2e`)).not.toThrow();
expect(() => runCLI(`lint ${fastifyApp}-e2e`)).not.toThrow();
expect(() => runCLI(`lint ${koaApp}-e2e`)).not.toThrow();
expect(() => runCLI(`lint ${nestApp}-e2e`)).not.toThrow();
// Only Fastify generates with unit tests since it supports them without additional libraries.
expect(() => runCLI(`test ${fastifyApp}`)).not.toThrow();
// https://github.com/nrwl/nx/issues/16601
const nestMainContent = readFile(`apps/${nestApp}/src/main.ts`);
updateFile(
`apps/${nestApp}/src/main.ts`,
`
${nestMainContent}
// Make sure this is not replaced during build time
console.log('env: ' + process.env['NODE_ENV']);
`
);
runCLI(`build ${nestApp}`);
expect(readFile(`dist/apps/${nestApp}/main.js`)).toContain(
`'env: ' + process.env['NODE_ENV']`
);
);
runCLI(`build ${nestApp}`);
expect(readFile(`dist/apps/${nestApp}/main.js`)).toContain(
`'env: ' + process.env['NODE_ENV']`
);
}, 300_000);
addLibImport(expressApp, testLib1);
addLibImport(expressApp, testLib2, '@acme/test2');
addLibImport(fastifyApp, testLib1);
addLibImport(fastifyApp, testLib2, '@acme/test2');
addLibImport(koaApp, testLib1);
addLibImport(koaApp, testLib2, '@acme/test2');
it('should e2e test express app', async () => {
await runE2eTests(expressApp, 7000);
});
addLibImport(nestApp, testLib1);
addLibImport(nestApp, testLib2, '@acme/test2');
it('should e2e test fastify app', async () => {
await runE2eTests(fastifyApp, 7001);
});
await runE2eTests(expressApp);
await runE2eTests(fastifyApp);
await runE2eTests(koaApp);
await runE2eTests(nestApp);
}, 900_000);
it('should e2e test koa app', async () => {
await runE2eTests(koaApp, 7002);
});
it('should e2e test nest app', async () => {
await runE2eTests(nestApp, 7003);
});
});
it('should generate a Dockerfile', async () => {
const expressApp = uniq('expressapp');
const expressApp = 'docker-express-app'; // needs to be consistent for the Dockerfile snapshot
runCLI(
`generate @nx/node:app ${expressApp} --framework=express --docker --no-interactive`
`generate @nx/node:app ${expressApp} --framework=express --docker --no-interactive`
);
checkFilesExist(`apps/${expressApp}/Dockerfile`);
const dockerFile = readFile(`apps/${expressApp}/Dockerfile`);
expect(dockerFile).toMatchSnapshot();
}, 300_000);
it('should support waitUntilTargets for serve target', async () => {
+12 -35
View File
@@ -1,13 +1,8 @@
import {
ClipboardDocumentCheckIcon,
ClipboardDocumentIcon,
} from '@heroicons/react/24/outline';
// @ts-ignore
import { CopyToClipboard } from 'react-copy-to-clipboard';
// @ts-ignore
import SyntaxHighlighter, { createElement } from 'react-syntax-highlighter';
import { JSX, ReactNode, useEffect, useMemo, useState } from 'react';
import { JSX, ReactNode, useMemo } from 'react';
import { twMerge } from 'tailwind-merge';
import { CopyToClipboardButton } from '@nx/graph/ui-components';
export function JsonCodeBlockPreTag({
children,
@@ -30,45 +25,27 @@ export function JsonCodeBlockPreTag({
export interface JsonCodeBlockProps {
data: any;
renderSource: (propertyName: string) => ReactNode;
copyTooltipText: string;
}
export function JsonCodeBlock(props: JsonCodeBlockProps): JSX.Element {
const [copied, setCopied] = useState(false);
const jsonString = useMemo(
() => JSON.stringify(props.data, null, 2),
[props.data]
);
useEffect(() => {
if (!copied) return;
const t = setTimeout(() => {
setCopied(false);
}, 3000);
return () => clearTimeout(t);
}, [copied]);
return (
<div className="code-block group relative w-full">
<div className="absolute right-0 top-0 z-10 flex">
<CopyToClipboard
<CopyToClipboardButton
text={jsonString}
onCopy={() => {
setCopied(true);
}}
>
<button
type="button"
className={twMerge(
'not-prose flex',
'border border-slate-200 bg-slate-50/50 p-2 dark:border-slate-700 dark:bg-slate-800/60',
'opacity-0 transition-opacity group-hover:opacity-100'
)}
>
{copied ? (
<ClipboardDocumentCheckIcon className="h-5 w-5 text-blue-500 dark:text-sky-500" />
) : (
<ClipboardDocumentIcon className="h-5 w-5" />
)}
</button>
</CopyToClipboard>
tooltipAlignment="right"
tooltipText={props.copyTooltipText}
className={twMerge(
'not-prose flex',
'border border-slate-200 bg-slate-50/50 p-2 dark:border-slate-700 dark:bg-slate-800/60',
'opacity-0 transition-opacity group-hover:opacity-100'
)}
/>
</div>
<SyntaxHighlighter
language="json"
+1
View File
@@ -1,3 +1,4 @@
export * from './lib/copy-to-clipboard-button';
export * from './lib/debounced-text-input';
export * from './lib/tag';
export * from './lib/dropdown';
@@ -0,0 +1,20 @@
import type { Meta, StoryObj } from '@storybook/react';
import {
CopyToClipboardButton,
CopyToClipboardButtonProps,
} from './copy-to-clipboard-button';
const meta: Meta<typeof CopyToClipboardButton> = {
component: CopyToClipboardButton,
title: 'CopyToClipboardButton',
};
export default meta;
type Story = StoryObj<typeof CopyToClipboardButton>;
export const Simple: Story = {
args: {
text: 'Hello, world!',
tooltipAlignment: 'left',
} as CopyToClipboardButtonProps,
};
@@ -0,0 +1,61 @@
// @ts-ignore
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { JSX, ReactNode, useEffect, useState } from 'react';
import {
ClipboardDocumentCheckIcon,
ClipboardDocumentIcon,
} from '@heroicons/react/24/outline';
export interface CopyToClipboardButtonProps {
text: string;
tooltipText?: string;
tooltipAlignment?: 'left' | 'right';
className?: string;
children?: ReactNode;
}
export function CopyToClipboardButton({
text,
tooltipAlignment,
tooltipText,
className,
children,
}: CopyToClipboardButtonProps) {
const [copied, setCopied] = useState(false);
useEffect(() => {
if (!copied) return;
const t = setTimeout(() => {
setCopied(false);
}, 3000);
return () => clearTimeout(t);
}, [copied]);
return (
<CopyToClipboard
text={text}
onCopy={() => {
setCopied(true);
}}
>
<button
type="button"
data-tooltip={tooltipText ? tooltipText : false}
data-tooltip-align-right={tooltipAlignment === 'right'}
data-tooltip-align-left={tooltipAlignment === 'left'}
className={className}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
{copied ? (
<ClipboardDocumentCheckIcon className="inline h-5 w-5 text-blue-500 dark:text-sky-500" />
) : (
<ClipboardDocumentIcon className="inline h-5 w-5" />
)}
{children}
</button>
</CopyToClipboard>
);
}
@@ -1,17 +0,0 @@
import type { Meta, StoryObj } from '@storybook/react';
import { CopyToClipboard } from './copy-to-clipboard';
const meta: Meta<typeof CopyToClipboard> = {
component: CopyToClipboard,
title: 'CopyToClipboard',
};
export default meta;
type Story = StoryObj<typeof CopyToClipboard>;
export const Simple: Story = {
args: {
onCopy: () => {},
tooltipAlignment: 'left',
},
};
@@ -1,36 +0,0 @@
import { ClipboardIcon } from '@heroicons/react/24/outline';
import { JSX, useEffect, useState } from 'react';
interface CopyToClipboardProps {
onCopy: () => void;
tooltipAlignment?: 'left' | 'right';
}
export function CopyToClipboard(props: CopyToClipboardProps): JSX.Element {
const [copied, setCopied] = useState(false);
useEffect(() => {
if (copied) {
const timeout = setTimeout(() => {
setCopied(false);
}, 3000);
return () => clearTimeout(timeout);
}
});
return (
<span
data-tooltip="Copy to clipboard"
data-tooltip-align-right={props.tooltipAlignment === 'right'}
>
<ClipboardIcon
className={`inline h-4 w-5 !cursor-pointer ${
copied ? 'text-sky-500' : ''
}`}
onClick={(e) => {
e.stopPropagation();
setCopied(true);
props.onCopy();
}}
></ClipboardIcon>
</span>
);
}
@@ -1,5 +1,3 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
import type { ProjectGraphProjectNode } from '@nx/devkit';
@@ -71,7 +69,7 @@ export const ProjectDetails = ({
>
<div
className={twMerge(
`flex items-center justify-between`,
`flex flex-wrap items-center justify-between`,
isCompact ? `gap-1` : `mb-4 gap-2`
)}
>
@@ -90,17 +88,15 @@ export const ProjectDetails = ({
className="h-6 w-6"
/>
</div>
<span>
{onViewInProjectGraph && viewInProjectGraphPosition === 'top' && (
<ViewInProjectGraphButton
callback={() =>
onViewInProjectGraph({ projectName: project.name })
}
/>
)}{' '}
</span>
{onViewInProjectGraph && viewInProjectGraphPosition === 'top' && (
<ViewInProjectGraphButton
callback={() =>
onViewInProjectGraph({ projectName: project.name })
}
/>
)}
</div>
<div className="flex justify-between py-2">
<div className="flex flex-wrap justify-between py-2">
<div>
{projectData.metadata?.description ? (
<p className="mb-2 text-sm capitalize text-gray-500 dark:text-slate-400">
@@ -133,16 +129,14 @@ export const ProjectDetails = ({
) : null}
</div>
<div className="self-end">
<span>
{onViewInProjectGraph &&
viewInProjectGraphPosition === 'bottom' && (
<ViewInProjectGraphButton
callback={() =>
onViewInProjectGraph({ projectName: project.name })
}
/>
)}{' '}
</span>
{onViewInProjectGraph &&
viewInProjectGraphPosition === 'bottom' && (
<ViewInProjectGraphButton
callback={() =>
onViewInProjectGraph({ projectName: project.name })
}
/>
)}
</div>
</div>
</header>
@@ -1,6 +1,7 @@
/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
import type { TargetConfiguration } from '@nx/devkit';
import { CopyToClipboardButton } from '@nx/graph/ui-components';
import {
ChevronDownIcon,
ChevronUpIcon,
@@ -17,7 +18,6 @@ import { twMerge } from 'tailwind-merge';
import { Pill } from '../pill';
import { TargetTechnologies } from '../target-technologies/target-technologies';
import { SourceInfo } from '../source-info/source-info';
import { CopyToClipboard } from '../copy-to-clipboard/copy-to-clipboard';
import { getDisplayHeaderFromTargetConfiguration } from '../utils/get-display-header-from-target-configuration';
import { TargetExecutor } from '../target-executor/target-executor';
@@ -53,10 +53,6 @@ export const TargetConfigurationDetailsHeader = ({
onViewInTaskGraph,
onNxConnect,
}: TargetConfigurationDetailsHeaderProps) => {
const handleCopyClick = async (copyText: string) => {
await window.navigator.clipboard.writeText(copyText);
};
if (!collapsable) {
// when collapsable is false, isCollasped should be false
isCollasped = false;
@@ -156,6 +152,12 @@ export const TargetConfigurationDetailsHeader = ({
</div>
</div>
<div className="flex items-center gap-2">
<CopyToClipboardButton
text={JSON.stringify(targetConfiguration, null, 2)}
tooltipText={!isCollasped ? 'Copy Target' : undefined}
tooltipAlignment="right"
className="rounded-md bg-inherit p-1 text-sm text-slate-600 ring-1 ring-inset ring-slate-400/40 hover:bg-slate-200 dark:text-slate-300 dark:ring-slate-400/30 dark:hover:bg-slate-700/60"
/>
{onViewInTaskGraph && (
<button
className="rounded-md bg-inherit p-1 text-sm text-slate-600 ring-1 ring-inset ring-slate-400/40 hover:bg-slate-200 dark:text-slate-300 dark:ring-slate-400/30 dark:hover:bg-slate-700/60"
@@ -194,21 +196,22 @@ export const TargetConfigurationDetailsHeader = ({
</div>
{!isCollasped && (
<div className="ml-5 mt-2 text-sm">
<SourceInfo
data={sourceMap[`targets.${targetName}`]}
propertyKey={`targets.${targetName}`}
color="text-gray-500 dark:text-slate-400"
/>
<div className="flex">
<SourceInfo
data={sourceMap[`targets.${targetName}`]}
propertyKey={`targets.${targetName}`}
color="text-gray-500 dark:text-slate-400"
/>
</div>
{targetName !== 'nx-release-publish' && (
<div className="mt-2 text-right">
<code className="ml-4 rounded bg-gray-100 px-2 py-1 font-mono text-gray-800 dark:bg-gray-700 dark:text-gray-300">
nx run {projectName}:{targetName}
</code>
<span>
<CopyToClipboard
onCopy={() =>
handleCopyClick(`nx run ${projectName}:${targetName}`)
}
<CopyToClipboardButton
text={`nx run ${projectName}:${targetName}`}
tooltipText="Copy Command"
tooltipAlignment="right"
/>
</span>
@@ -1,12 +1,11 @@
/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
import type { TargetConfiguration } from '@nx/devkit';
import { JsonCodeBlock } from '@nx/graph/ui-code-block';
import { CopyToClipboardButton } from '@nx/graph/ui-components';
import { useCallback, useContext, useEffect, useState } from 'react';
import { FadingCollapsible } from './fading-collapsible';
import { TargetConfigurationProperty } from './target-configuration-property';
import { CopyToClipboard } from '../copy-to-clipboard/copy-to-clipboard';
import { PropertyInfoTooltip, Tooltip } from '@nx/graph/ui-tooltips';
import { TooltipTriggerText } from './tooltip-trigger-text';
import { Pill } from '../pill';
@@ -53,10 +52,6 @@ export default function TargetConfigurationDetails({
const [collapsed, setCollapsed] = useState(true);
const { expandedTargets, toggleTarget } = useContext(ExpandedTargetsContext);
const handleCopyClick = async (copyText: string) => {
await window.navigator.clipboard.writeText(copyText);
};
const handleCollapseToggle = useCallback(() => {
if (toggleTarget) {
toggleTarget(targetName);
@@ -110,10 +105,7 @@ export default function TargetConfigurationDetails({
<div className="p-4 text-base">
<div className="group mb-4">
<h4 className="mb-4">
<TargetExecutorTitle
{...displayHeader}
handleCopyClick={handleCopyClick}
/>
<TargetExecutorTitle {...displayHeader} />
</h4>
<p className="pl-5 font-mono">
<TargetExecutor {...displayHeader} link={link}>
@@ -131,10 +123,7 @@ export default function TargetConfigurationDetails({
{script && (
<div className="group mb-4">
<h4 className="mb-4">
<TargetExecutorTitle
script={script}
handleCopyClick={handleCopyClick}
/>
<TargetExecutorTitle script={script} />
</h4>
<p className="pl-5 font-mono">
<TargetExecutor script={script} link={link}>
@@ -164,6 +153,7 @@ export default function TargetConfigurationDetails({
<FadingCollapsible>
<JsonCodeBlock
data={options}
copyTooltipText="Copy Options"
renderSource={(propertyName: string) => (
<TargetSourceInfo
className="flex min-w-0 pl-4"
@@ -198,14 +188,11 @@ export default function TargetConfigurationDetails({
</span>
</Tooltip>
<span className="mb-1 ml-2 hidden group-hover:inline">
<CopyToClipboard
onCopy={() =>
handleCopyClick(
`"inputs": ${JSON.stringify(
targetConfiguration.inputs
)}`
)
}
<CopyToClipboardButton
text={`"inputs": ${JSON.stringify(
targetConfiguration.inputs
)}`}
tooltipText="Copy Inputs"
/>
</span>
</h4>
@@ -239,14 +226,11 @@ export default function TargetConfigurationDetails({
</span>
</Tooltip>
<span className="mb-1 ml-2 hidden group-hover:inline">
<CopyToClipboard
onCopy={() =>
handleCopyClick(
`"outputs": ${JSON.stringify(
targetConfiguration.outputs
)}`
)
}
<CopyToClipboardButton
text={`"outputs": ${JSON.stringify(
targetConfiguration.outputs
)}`}
tooltipText="Copy Outputs"
/>
</span>
</h4>
@@ -279,15 +263,12 @@ export default function TargetConfigurationDetails({
<TooltipTriggerText>Depends On</TooltipTriggerText>
</span>
</Tooltip>
<span className="inline pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100">
<CopyToClipboard
onCopy={() =>
handleCopyClick(
`"dependsOn": ${JSON.stringify(
targetConfiguration.dependsOn
)}`
)
}
<span className="mb-1 ml-2 hidden group-hover:inline">
<CopyToClipboardButton
text={`"dependsOn": ${JSON.stringify(
targetConfiguration.dependsOn
)}`}
tooltipText="Copy Depends On"
/>
</span>
</h4>
@@ -336,6 +317,7 @@ export default function TargetConfigurationDetails({
<FadingCollapsible>
<JsonCodeBlock
data={targetConfiguration.configurations}
copyTooltipText="Copy Configurations"
renderSource={(propertyName: string) => (
<TargetSourceInfo
className="flex min-w-0 pl-4"
@@ -1,29 +1,26 @@
import { PropertyInfoTooltip, Tooltip } from '@nx/graph/ui-tooltips';
import { CopyToClipboard } from '../copy-to-clipboard/copy-to-clipboard';
import { CopyToClipboardButton } from '@nx/graph/ui-components';
import { TooltipTriggerText } from '../target-configuration-details/tooltip-trigger-text';
export function TargetExecutorTitle({
commands,
command,
script,
handleCopyClick,
executor,
}: {
handleCopyClick: (copyText: string) => void;
commands?: string[];
command?: string;
script?: string;
executor?: string;
}) {
if (commands && commands.length) {
return (
<span className="font-medium">
Commands
<span className="mb-1 ml-2 hidden group-hover:inline">
<CopyToClipboard
onCopy={() =>
handleCopyClick(
`"commands": [${commands.map((c) => `"${c}"`).join(', ')}]`
)
}
<CopyToClipboardButton
text={`"commands": [${commands.map((c) => `"${c}"`).join(', ')}]`}
tooltipText="Copy Commands"
/>
</span>
</span>
@@ -34,8 +31,9 @@ export function TargetExecutorTitle({
<span className="font-medium">
Command
<span className="mb-1 ml-2 hidden group-hover:inline">
<CopyToClipboard
onCopy={() => handleCopyClick(`"command": "${command}"`)}
<CopyToClipboardButton
text={`"command": "${command}"`}
tooltipText="Copy Command"
/>
</span>
</span>
@@ -46,7 +44,7 @@ export function TargetExecutorTitle({
<span className="font-medium">
Script
<span className="mb-1 ml-2 hidden group-hover:inline">
<CopyToClipboard onCopy={() => handleCopyClick(script)} />
<CopyToClipboardButton text={script} tooltipText="Copy Script" />
</span>
</span>
);
@@ -58,6 +56,12 @@ export function TargetExecutorTitle({
>
<span className="font-medium">
<TooltipTriggerText>Executor</TooltipTriggerText>
<span className="mb-1 ml-2 hidden group-hover:inline">
<CopyToClipboardButton
text={executor ?? ''}
tooltipText="Copy Executor"
/>
</span>
</span>
</Tooltip>
);
@@ -95,7 +95,8 @@ export function getBasicNxCloudSection(items: MenuItem[]): MenuSection {
.map((m) => {
return {
...m,
disableCollapsible: !m.id.endsWith('tutorial'),
disableCollapsible:
!m.id.endsWith('tutorial') && !m.id.endsWith('concepts'),
};
}),
};
@@ -54,6 +54,7 @@ function CategoryBox({ category }: { category: RelatedDocumentsCategory }) {
<Link
href={d.path}
className="flex flex-grow items-center justify-between no-underline hover:text-sky-600 hover:underline dark:hover:text-sky-400"
prefetch={false}
>
<span>{d.name}</span>
<ArrowRightIcon className="h-4 w-4 text-slate-500 dark:text-slate-400" />
@@ -93,6 +93,7 @@ export function TableOfContents({
'pl-6': item.level === 3,
}
)}
prefetch={false}
>
{item.level === 1 ? 'Overview' : item.title}
</Link>
@@ -129,6 +129,7 @@ export function Content({
className="relative mx-4 inline-flex rounded-md border border-green-100 bg-green-50 px-4 py-2 text-xs font-medium text-green-600 dark:border-green-900 dark:bg-green-900/30 dark:text-green-400"
href="/recipes/other/rescope"
title="Nx 16 package name changes"
prefetch={false}
>
Rescope @nrwl to @nx
</Link>
@@ -20,7 +20,11 @@ function Hit({
hit: InternalDocSearchHit | StoredDocSearchHit;
children: ReactNode;
}): JSX.Element {
return <Link href={hit.url}>{children}</Link>;
return (
<Link href={hit.url} prefetch={false}>
{children}
</Link>
);
}
export function AlgoliaSearch({
@@ -32,7 +32,7 @@ const pages: Array<{ title: string; path: string }> = [
},
{
title: 'Running Nx Cloud Enterprise',
path: '/ci/features/on-premise',
path: '/ci/recipes/enterprise/on-premise/on-premise',
},
{
title: 'Auth (Basic)',
+5 -1
View File
@@ -240,7 +240,11 @@ export default function Changelog(props: ChangeLogProps): JSX.Element {
</h1>
<p className="mt-4">
All the Nx goodies in one page, sorted by release. See our{' '}
<Link className="underline" href="/reference/releases">
<Link
className="underline"
href="/reference/releases"
prefetch={false}
>
release page
</Link>{' '}
for information about the release cycle and LTS policy.
+6 -1
View File
@@ -114,7 +114,11 @@ export default function Packages({
<p>
In version 16, we have rescoped our packages to{' '}
<code>@nx/*</code> from <code>@nrwl/*</code>.{' '}
<Link href="/recipes/other/rescope" className="underline">
<Link
href="/recipes/other/rescope"
className="underline"
prefetch={false}
>
Read more about the rescope
</Link>
</p>
@@ -129,6 +133,7 @@ export default function Packages({
key={'ref-' + pkg.name}
href={pkg.path}
className="group relative flex items-center gap-3 rounded-md border border-slate-200 bg-slate-50/40 p-4 text-sm capitalize shadow-sm transition hover:bg-slate-50 dark:border-slate-800/40 dark:bg-slate-800/60 dark:hover:bg-slate-800"
prefetch={false}
>
<img
className="h-5 w-5 object-cover opacity-75 dark:invert"
+19 -14
View File
@@ -1,10 +1,14 @@
import { getBasicPluginsSection } from '@nx/nx-dev/data-access-menu';
import { Menu } from '@nx/nx-dev/models-menu';
import { Breadcrumbs, DocumentationHeader, Footer } from '@nx/nx-dev/ui-common';
import { getBasicNxSection } from '@nx/nx-dev/data-access-menu';
import { MenuItem } from '@nx/nx-dev/models-menu';
import {
Breadcrumbs,
DocumentationHeader,
Footer,
SidebarContainer,
} from '@nx/nx-dev/ui-common';
import { PluginDirectory } from '@nx/nx-dev/ui-community';
import { NextSeo } from 'next-seo';
import { useRouter } from 'next/router';
import { useRef } from 'react';
import { menusApi } from '../lib/menus.api';
import { useNavToggle } from '../lib/navigation-toggle.effect';
import { nxPackagesApi } from '../lib/packages.api';
@@ -20,7 +24,7 @@ interface PluginInfo {
}
interface BrowseProps {
pluginList: PluginInfo[];
// segments: string[];
menu: MenuItem[];
}
export async function getStaticProps(): Promise<{ props: BrowseProps }> {
@@ -56,7 +60,7 @@ export async function getStaticProps(): Promise<{ props: BrowseProps }> {
isOfficial: false,
})),
],
// segments,
menu: menusApi.getMenu('nx', ''),
},
};
}
@@ -64,14 +68,9 @@ export async function getStaticProps(): Promise<{ props: BrowseProps }> {
export default function Browse(props: BrowseProps): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
const vm: {
menu: Menu;
} = {
menu: {
sections: [getBasicPluginsSection(menusApi.getMenu('extending-nx', ''))],
},
const menu = {
sections: [getBasicNxSection(props.menu)],
};
return (
@@ -106,8 +105,14 @@ export default function Browse(props: BrowseProps): JSX.Element {
role="main"
className="flex h-full flex-1 overflow-y-hidden"
>
<div className="hidden">
<SidebarContainer
menu={menu}
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
</div>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
+16
View File
@@ -43,6 +43,7 @@ const tips: NewYearTip[] = [
<Link
href="/getting-started/intro"
className="text-slate-900 underline dark:text-slate-100"
prefetch={false}
>
Intro to Nx
</Link>
@@ -78,6 +79,7 @@ const tips: NewYearTip[] = [
<Link
href="/concepts/integrated-vs-package-based"
className="text-slate-900 underline dark:text-slate-100"
prefetch={false}
>
Types of Repos Guide
</Link>
@@ -109,6 +111,7 @@ const tips: NewYearTip[] = [
<Link
href="/ci/features/distribute-task-execution"
className="text-slate-900 underline dark:text-slate-100"
prefetch={false}
>
Nx Agents
</Link>
@@ -120,6 +123,7 @@ const tips: NewYearTip[] = [
<Link
href="/ci/intro/ci-with-nx"
className="text-slate-900 underline dark:text-slate-100"
prefetch={false}
>
CI with Nx
</Link>
@@ -153,6 +157,7 @@ const tips: NewYearTip[] = [
<Link
href="/recipes/adopting-nx/adding-to-existing-project"
className="text-slate-900 underline dark:text-slate-100"
prefetch={false}
>
Add Nx to an Existing Project
</Link>
@@ -187,6 +192,7 @@ const tips: NewYearTip[] = [
<Link
href="/concepts/module-federation/micro-frontend-architecture"
className="text-slate-900 underline dark:text-slate-100"
prefetch={false}
>
Micro Frontends with Nx
</Link>
@@ -211,18 +217,21 @@ const tips: NewYearTip[] = [
<Link
href="/getting-started/tutorials/react-standalone-tutorial"
className="m-4 w-20"
prefetch={false}
>
{frameworkIcons.react.image}
</Link>
<Link
href="/getting-started/tutorials/angular-standalone-tutorial"
className="m-4 w-20"
prefetch={false}
>
{frameworkIcons.angular.image}
</Link>
<Link
href="/getting-started/tutorials/vue-standalone-tutorial"
className="m-4 w-20"
prefetch={false}
>
{frameworkIcons.vue.image}
</Link>
@@ -232,6 +241,7 @@ const tips: NewYearTip[] = [
<Link
href="/showcase/example-repos"
className="text-slate-900 underline dark:text-slate-100"
prefetch={false}
>
Nx with your Favorite Tech
</Link>
@@ -266,6 +276,7 @@ const tips: NewYearTip[] = [
<Link
href="/extending-nx/recipes/create-install-package"
className="text-slate-900 underline dark:text-slate-100"
prefetch={false}
>
Build Your Own CLI
</Link>
@@ -304,6 +315,7 @@ const tips: NewYearTip[] = [
<Link
href="https://go.nx.dev/nx-agents-ea"
className="text-slate-900 underline dark:text-slate-100"
prefetch={false}
>
Sign up now for early access.
</Link>
@@ -313,6 +325,7 @@ const tips: NewYearTip[] = [
<Link
href="/ci/features/distribute-task-execution"
className="text-slate-900 underline dark:text-slate-100"
prefetch={false}
>
Nx Agents
</Link>
@@ -347,6 +360,7 @@ const tips: NewYearTip[] = [
<Link
href="/recipes/tips-n-tricks/standalone-to-integrated"
className="text-slate-900 underline dark:text-slate-100"
prefetch={false}
>
convert a standalone app repo to a monorepo
</Link>
@@ -378,6 +392,7 @@ const tips: NewYearTip[] = [
<Link
href="https://blog.nrwl.io/evergreen-tooling-more-than-just-codemods-fc68f32ce605"
className="text-slate-900 underline dark:text-slate-100"
prefetch={false}
>
Evergreen Tooling
</Link>
@@ -388,6 +403,7 @@ const tips: NewYearTip[] = [
<Link
href="/features/automate-updating-dependencies"
className="text-slate-900 underline dark:text-slate-100"
prefetch={false}
>
Automate Updating Dependencies
</Link>
+4 -1
View File
@@ -426,7 +426,9 @@ const nxCloudUrls = {
'/nx-cloud/features/distribute-task-execution',
'/concepts/affected': '/ci/features/affected',
'/nx-cloud/private-cloud': '/ci/recipes/enterprise/on-premise',
'/nx-cloud/private-cloud/get-started': '/ci/features/on-premise',
'/nx-cloud/private-cloud/get-started':
'/ci/recipes/enterprise/on-premise/on-premise',
'/ci/features/on-premise': '/ci/recipes/enterprise/on-premise/on-premise',
'/nx-cloud/private-cloud/auth-single-admin':
'/nx-cloud/recipes/enterprise/on-premise/auth-single-admin',
'/nx-cloud/private-cloud/auth-github':
@@ -478,6 +480,7 @@ const nxCloudUrls = {
'/ci/concepts/parallelization-distribution',
'/nx-cloud/:path*': '/ci/:path*',
'/core-features/:path*': '/features/:path*',
'/ci/recipes/set-up/connect-to-cloud': '/ci/intro/connect-to-cloud',
};
/**
+1
View File
@@ -46,6 +46,7 @@ export function BlogDetails({ post }: BlogDetailsProps) {
<Link
href="/blog"
className="flex w-20 shrink-0 items-center gap-2 text-slate-400 hover:text-slate-800 dark:text-slate-600 dark:hover:text-slate-200"
prefetch={false}
>
<ChevronLeftIcon className="h-3 w-3" />
Blog
+1
View File
@@ -28,6 +28,7 @@ export function BlogEntry({ post }: BlogEntryProps) {
href={`/blog/${post.slug}`}
title={post.title}
className="text-balance text-lg font-semibold text-slate-900 dark:text-white"
prefetch={false}
>
<span className="absolute inset-0" aria-hidden="true" />
{post.title}
+1
View File
@@ -30,6 +30,7 @@ export function MoreBlogs({ blogs }: MoreBlogsProps) {
href={`/blog/${post.slug}`}
key={post.slug}
className="relative flex items-center gap-6 border-b border-slate-200 py-5 text-sm before:absolute before:inset-x-[-16px] before:inset-y-[-2px] before:z-[-1] before:rounded-xl before:bg-slate-200 before:opacity-0 last:border-0 before:hover:opacity-100 dark:border-slate-800 dark:before:bg-slate-800/50"
prefetch={false}
>
<span className="w-1/2 flex-none text-balance font-medium text-slate-500 sm:w-5/12 dark:text-white">
{post.title}
+1
View File
@@ -110,6 +110,7 @@ export const ButtonLink = forwardRef(function (
href={href}
title={title}
className={getLayoutClassName(className)}
prefetch={false}
{...props}
>
<ButtonInner variant={variant} size={size}>
+5
View File
@@ -165,6 +165,7 @@ export function Footer(): JSX.Element {
href={item.href}
title={item.label}
className="text-sm text-slate-500 hover:text-slate-600 dark:hover:text-slate-400"
prefetch={false}
>
<span className="sr-only">{item.name}</span>
<item.icon className="h-6 w-6" aria-hidden="true" />
@@ -187,6 +188,7 @@ export function Footer(): JSX.Element {
<Link
href={item.href}
className="text-sm text-slate-500 hover:text-slate-600 dark:hover:text-slate-400"
prefetch={false}
>
{item.name}
</Link>
@@ -204,6 +206,7 @@ export function Footer(): JSX.Element {
<Link
href={item.href}
className="text-sm text-slate-500 hover:text-slate-600 dark:hover:text-slate-400"
prefetch={false}
>
{item.name}
</Link>
@@ -223,6 +226,7 @@ export function Footer(): JSX.Element {
<Link
href={item.href}
className="text-sm text-slate-500 hover:text-slate-600 dark:hover:text-slate-400"
prefetch={false}
>
{item.name}
</Link>
@@ -240,6 +244,7 @@ export function Footer(): JSX.Element {
<Link
href={item.href}
className="text-sm text-slate-500 hover:text-slate-600 dark:hover:text-slate-400"
prefetch={false}
>
{item.name}
</Link>
@@ -36,6 +36,7 @@ export function DefaultMenuItem({
title={item.name}
target={hasExternalLink ? '_blank' : '_self'}
className="text-sm font-medium text-slate-900 dark:text-slate-200"
prefetch={false}
>
{item.name}
{item.isNew ? (
@@ -43,6 +43,7 @@ function Menu({ tabs }: { tabs: any[] }): JSX.Element {
'whitespace-nowrap border-b-2 py-2 text-sm font-medium'
)}
aria-current={tab.current ? 'page' : undefined}
prefetch={false}
>
{tab.name}
</Link>
@@ -203,6 +204,7 @@ export function DocumentationHeader({
<Link
href="/"
className="flex flex-grow items-center px-4 text-slate-900 lg:px-0 dark:text-white"
prefetch={false}
>
<span className="sr-only">Nx</span>
<NxIcon aria-hidden="true" className="h-8 w-8" />
@@ -210,6 +212,7 @@ export function DocumentationHeader({
<Link
href="/getting-started/intro"
className="ml-2 hidden items-center px-4 text-slate-900 lg:flex lg:px-0 dark:text-white"
prefetch={false}
>
<span className="text-xl font-bold uppercase tracking-wide">
Docs
@@ -320,13 +323,15 @@ export function DocumentationHeader({
href="/getting-started/intro"
title="Documentation"
className="hidden px-3 py-2 font-medium leading-tight hover:text-blue-500 md:inline-flex dark:text-slate-200 dark:hover:text-sky-500"
prefetch={false}
>
Documentation
Docs
</Link>
<Link
href="/blog"
title="Blog"
className="hidden px-3 py-2 font-medium leading-tight hover:text-blue-500 md:inline-flex dark:text-slate-200 dark:hover:text-sky-500"
prefetch={false}
>
Blog
</Link>
@@ -391,6 +396,7 @@ export function DocumentationHeader({
className="hidden cursor-pointer px-3 py-2 text-sm font-medium leading-tight hover:text-blue-500 md:inline-flex dark:text-slate-200 dark:hover:text-sky-500"
title="Contact Us"
href="/contact"
prefetch={false}
>
Contact
</Link>
+11 -2
View File
@@ -54,6 +54,7 @@ export function Header(): JSX.Element {
<Link
href="/"
className="mr-4 flex items-center text-slate-900 dark:text-white"
prefetch={false}
>
<span className="sr-only">Nx</span>
<NxIcon aria-hidden="true" className="h-8 w-8" />
@@ -156,13 +157,15 @@ export function Header(): JSX.Element {
href="/getting-started/intro"
title="Documentation"
className="hidden px-3 py-2 font-medium leading-tight hover:text-blue-500 md:inline-flex dark:text-slate-200 dark:hover:text-sky-500"
prefetch={false}
>
Documentation
Docs
</Link>
<Link
href="/blog"
title="Blog"
className="hidden px-3 py-2 font-medium leading-tight hover:text-blue-500 md:inline-flex dark:text-slate-200 dark:hover:text-sky-500"
prefetch={false}
>
Blog
</Link>
@@ -227,6 +230,7 @@ export function Header(): JSX.Element {
className="hidden cursor-pointer px-3 py-2 font-medium leading-tight hover:text-blue-500 md:inline-flex dark:text-slate-200 dark:hover:text-sky-500"
title="Contact Us"
href="/contact"
prefetch={false}
>
Contact
</Link>
@@ -260,6 +264,7 @@ export function Header(): JSX.Element {
<Link
href="/"
className="flex items-center text-slate-900 dark:text-white"
prefetch={false}
>
<span className="sr-only">Nx</span>
<NxIcon aria-hidden="true" className="h-8 w-8" />
@@ -315,6 +320,7 @@ export function Header(): JSX.Element {
<Link
href="/"
className="flex items-center text-slate-900 dark:text-white"
prefetch={false}
>
<svg
role="img"
@@ -435,13 +441,15 @@ export function Header(): JSX.Element {
href="/getting-started/intro"
title="Documentation"
className="block py-4 font-medium leading-tight hover:text-blue-500 dark:text-slate-200 dark:hover:text-sky-500"
prefetch={false}
>
Documentation
Docs
</Link>
<Link
href="/blog"
title="Blog"
className="block py-4 font-medium leading-tight hover:text-blue-500 dark:text-slate-200 dark:hover:text-sky-500"
prefetch={false}
>
Blog
</Link>
@@ -501,6 +509,7 @@ export function Header(): JSX.Element {
href="/contact"
title="Contact"
className="block py-4 font-medium leading-tight hover:text-blue-500 dark:text-slate-200 dark:hover:text-sky-500"
prefetch={false}
>
Contact
</Link>
+14 -12
View File
@@ -9,7 +9,9 @@ import {
PlayCircleIcon,
ShareIcon,
Squares2X2Icon,
RectangleGroupIcon,
ChatBubbleBottomCenterTextIcon,
ArrowUpCircleIcon,
} from '@heroicons/react/24/outline';
import { FC, SVGProps } from 'react';
import { NxAgentsIcon } from '../nx-agents-icon';
@@ -27,7 +29,7 @@ export interface MenuItem {
export const featuresItems: MenuItem[] = [
{
name: 'Task Running',
name: 'Run Tasks',
// description: 'Run one or many tasks in parallel.',
description: null,
href: '/features/run-tasks',
@@ -36,7 +38,7 @@ export const featuresItems: MenuItem[] = [
isHighlight: false,
},
{
name: 'Local Caching',
name: 'Cache Task Results',
// description: 'Speeds up your local workflow.',
description: null,
href: '/features/cache-task-results',
@@ -45,7 +47,7 @@ export const featuresItems: MenuItem[] = [
isHighlight: false,
},
{
name: 'Nx Graph',
name: 'Explore Your Workspace',
// description: 'See interactions for tasks and modules.',
description: null,
href: '/features/explore-graph',
@@ -54,16 +56,16 @@ export const featuresItems: MenuItem[] = [
isHighlight: false,
},
{
name: 'Automated updates',
name: 'Automate Updating Dependencies',
// description: 'Keep running on latest without effort.',
description: null,
href: '/features/automate-updating-dependencies',
icon: ArrowPathIcon,
icon: ArrowUpCircleIcon,
isNew: false,
isHighlight: false,
},
{
name: 'Module Boundaries',
name: 'Enforce Module Boundaries',
// description: 'Partition your code into defined units.',
description: null,
href: '/features/enforce-module-boundaries',
@@ -72,16 +74,16 @@ export const featuresItems: MenuItem[] = [
isHighlight: false,
},
{
name: 'Nx Release',
name: 'Manage Releases',
// description: 'Versioning, changelog, publishing.',
description: null,
href: '/features/manage-releases',
icon: CubeIcon,
isNew: true,
isNew: false,
isHighlight: false,
},
{
name: 'Nx Replay',
name: 'Use Remote Caching (Nx Replay)',
description: 'Zero-config, fast & secure remote cache solution.',
href: '/ci/features/remote-cache',
icon: NxReplayIcon,
@@ -89,12 +91,12 @@ export const featuresItems: MenuItem[] = [
isHighlight: true,
},
{
name: 'Nx Agents',
name: 'Distribute Task Execution (Nx Agents)',
description:
'One-line config for distributing tasks, E2E tests split & flaky tasks rerun.',
'One-line config for distributing tasks across multiple machines',
href: '/ci/features/distribute-task-execution',
icon: NxAgentsIcon,
isNew: true,
isNew: false,
isHighlight: true,
},
];
@@ -34,6 +34,7 @@ export function MobileMenuItem({
title={item.name}
target={hasExternalLink ? '_blank' : '_self'}
className="text-sm font-medium text-slate-900 dark:text-slate-200"
prefetch={false}
>
{item.name}
{item.isNew ? (
+1
View File
@@ -47,6 +47,7 @@ export function PluginCard({
target={isOfficial ? undefined : '_blank'}
rel={isOfficial ? undefined : 'noreferrer'}
className="flex grow flex-col focus:outline-none"
prefetch={false}
>
<span className="absolute inset-0" aria-hidden="true" />
<p className="mb-2 line-clamp-3 grow text-sm">{description}</p>
+22 -3
View File
@@ -103,7 +103,11 @@ function SidebarSectionItems({ item }: { item: MenuItem }): JSX.Element {
onClick={handleCollapseToggle}
>
{item.disableCollapsible ? (
<Link href={item.path as string} className="hover:underline">
<Link
href={item.path as string}
className="hover:underline"
prefetch={false}
>
{item.name}
</Link>
) : (
@@ -134,6 +138,7 @@ function SidebarSectionItems({ item }: { item: MenuItem }): JSX.Element {
className={cx(
'relative block py-1 text-slate-500 transition-colors duration-200 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-300'
)}
prefetch={false}
>
<span
className={cx('relative', {
@@ -187,11 +192,17 @@ export function SidebarMobile({
const router = useRouter();
const isCI: boolean = router.asPath.startsWith('/ci');
const isAPI: boolean = router.asPath.startsWith('/nx-api');
const isPlugins: boolean = router.asPath.startsWith('/extending-nx');
const isExtendingNx: boolean = router.asPath.startsWith('/extending-nx');
const isPlugins: boolean = router.asPath.startsWith('/plugin-registry');
const isChangelog: boolean = router.asPath.startsWith('/changelog');
const isAiChat: boolean = router.asPath.startsWith('/ai-chat');
const isNx: boolean =
!isCI && !isAPI && !isPlugins && !isChangelog && !isAiChat;
!isCI &&
!isAPI &&
!isExtendingNx &&
!isPlugins &&
!isChangelog &&
!isAiChat;
const sections = {
general: [
@@ -220,6 +231,11 @@ export function SidebarMobile({
{
name: 'Extending Nx',
href: '/extending-nx/intro/getting-started',
current: isExtendingNx,
},
{
name: 'Plugins',
href: '/plugin-registry',
current: isPlugins,
},
{
@@ -275,6 +291,7 @@ export function SidebarMobile({
<Link
href="/"
className="flex flex-grow items-center px-4 text-slate-900 lg:px-0 dark:text-white"
prefetch={false}
>
<span className="sr-only">Nx</span>
<NxIcon aria-hidden="true" className="h-8 w-8" />
@@ -296,6 +313,7 @@ export function SidebarMobile({
'whitespace-nowrap p-4 text-center text-sm font-medium'
)}
aria-current={section.current ? 'page' : undefined}
prefetch={false}
>
{section.name}
</Link>
@@ -313,6 +331,7 @@ export function SidebarMobile({
'whitespace-nowrap p-4 text-center text-sm font-medium'
)}
aria-current={section.current ? 'page' : undefined}
prefetch={false}
>
{section.name}
</Link>
@@ -19,7 +19,11 @@ export function ConnectWithUs(): JSX.Element {
</p>
<p className="py-4">
Looking for community plugins? Find them listed in the{' '}
<Link href="/plugin-registry" className="font-semibold underline">
<Link
href="/plugin-registry"
className="font-semibold underline"
prefetch={false}
>
plugin registry
</Link>
.
@@ -145,6 +145,7 @@ export function ContactLinks(): JSX.Element {
href="/getting-started/intro"
title="Nx documentation"
className="mt-2 flex items-center gap-2 text-sm text-slate-500 transition hover:text-slate-800 dark:hover:text-slate-400"
prefetch={false}
>
<span>Nx docs</span>
<ChevronRightIcon aria-hidden="true" className="h-3 w-3" />
@@ -60,6 +60,7 @@ export const BentoGridItem = ({
href={url}
title="Learn more"
className="float-right text-sm font-medium transition duration-200 group-hover/bento:text-blue-500 group-hover/bento:dark:text-sky-500"
prefetch={false}
>
<span className="group absolute inset-0" />
@@ -62,6 +62,7 @@ export function CallToAction(): JSX.Element {
href="/contact/engineering"
title="Talk to the engineering team"
className="rounded-md bg-slate-950 px-3.5 py-2.5 text-sm font-semibold text-slate-100 shadow-sm hover:bg-slate-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white dark:bg-white dark:text-slate-900 dark:hover:bg-slate-100"
prefetch={false}
>
Talk to engineering
</Link>
@@ -71,6 +72,7 @@ export function CallToAction(): JSX.Element {
href="/contact/sales"
title="Talk to the sales team"
className="font-semibold underline"
prefetch={false}
>
Speak directly to sales
</Link>
+1
View File
@@ -33,6 +33,7 @@ export function Hero(): JSX.Element {
href="/contact/sales"
title="Talk to the sales team"
className="font-semibold underline"
prefetch={false}
>
Speak directly to sales
</Link>
@@ -37,6 +37,7 @@ export function ExtensibleAndIntegrated(): JSX.Element {
href="/getting-started/editor-setup"
title="Install VSCode's native extension for Nx"
className="font-medium text-blue-500 dark:text-sky-500"
prefetch={false}
>
VSCode
</Link>{' '}
@@ -45,6 +46,7 @@ export function ExtensibleAndIntegrated(): JSX.Element {
href="/getting-started/editor-setup"
className="font-medium text-blue-500 dark:text-sky-500"
title="Install JetBrains' native plugin for Nx"
prefetch={false}
>
JetBrains
</Link>{' '}
@@ -52,6 +54,7 @@ export function ExtensibleAndIntegrated(): JSX.Element {
<Link
href="/getting-started/editor-setup#neovim"
className="font-medium text-blue-500 dark:text-sky-500"
prefetch={false}
>
Neovim
</Link>
@@ -70,6 +73,7 @@ export function ExtensibleAndIntegrated(): JSX.Element {
href="https://nx.app/?utm_source=nx.dev"
title="Nx Cloud: Distributed Task execution & Caching"
className="font-medium text-blue-500 dark:text-sky-500"
prefetch={false}
target="_blank"
rel="noreferrer"
>
@@ -92,7 +92,7 @@ export function IdeIntegrationTab(): JSX.Element {
</div>
<div className="mt-3 sm:ml-3 sm:mt-0">
<h3 className="text-sm font-medium">{feature.name}</h3>
<Link href={feature.link}>
<Link href={feature.link} prefetch={false}>
<span className="absolute inset-0" aria-hidden="true" />
<p className="mt-2 text-sm text-slate-500">
{feature.description}
+1
View File
@@ -33,6 +33,7 @@ export function Hero(): JSX.Element {
href="/ci/features/distribute-task-execution"
title="Discover Nx Agents"
className="font-semibold text-blue-500 dark:text-sky-500"
prefetch={false}
>
<span className="absolute inset-0" aria-hidden="true"></span>Read
more <span aria-hidden="true"></span>
@@ -116,6 +116,7 @@ export function MigrationsAndCodeGeneration(): JSX.Element {
<Link
href="https://nx.dev/features/automate-updating-dependencies"
className="ml-2 underline"
prefetch={false}
>
https://nx.dev/features/automate-updating-dependencies
</Link>
@@ -30,6 +30,7 @@ export function MonorepoStyles(): JSX.Element {
href="/recipes/adopting-nx/adding-to-monorepo"
title="Install VSCode's native extension for Nx"
className="font-medium text-blue-500 dark:text-sky-500"
prefetch={false}
>
Add Nx on top!
</Link>{' '}
@@ -41,6 +42,7 @@ export function MonorepoStyles(): JSX.Element {
href="/extending-nx"
title="Install VSCode's native extension for Nx"
className="font-medium text-blue-500 dark:text-sky-500"
prefetch={false}
>
powerful plugins
</Link>
+1
View File
@@ -149,6 +149,7 @@ export function NxWithCi(): JSX.Element {
<Link
href="/ci/features/remote-cache"
title="Discover Nx Replay"
prefetch={false}
>
<span className="absolute inset-0"></span>Nx Replay
</Link>
@@ -6,6 +6,7 @@ export function CustomLink(props: any) {
return (
<Link
prefetch={false}
{...props}
passHref
target={target}
@@ -83,6 +83,7 @@ export function Cards({
<Link
className="group flex items-center whitespace-nowrap border-transparent px-4 py-0 text-sm font-semibold no-underline transition-all duration-200 ease-in-out hover:text-slate-900 dark:hover:text-sky-400"
href={moreLink}
prefetch={false}
>
Browse more
<span
@@ -117,6 +118,7 @@ export function LinkCard({
href={url}
className="no-prose relative col-span-1 flex flex-col items-center rounded-md border border-slate-200 bg-slate-50/40 p-4 text-center font-semibold shadow-sm transition focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-offset-2 hover:bg-slate-100 dark:border-slate-800/40 dark:bg-slate-800/60 dark:hover:bg-slate-800"
style={{ textDecorationLine: 'none' }}
prefetch={false}
>
{icon && (
<div
@@ -177,6 +179,7 @@ export function Card({
href={url}
title={title}
className="group flex flex-col items-stretch rounded-md border border-slate-200 bg-slate-50/40 text-sm no-underline shadow-sm transition focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-offset-2 hover:bg-slate-50 dark:border-slate-800/40 dark:bg-slate-800/60 dark:hover:bg-slate-800"
prefetch={false}
>
{!!hasYoutubeId && (
<div className="max-h-24">
@@ -207,6 +207,7 @@ export function Persona({
title={title}
aria-hidden="true"
className="absolute inset-0"
prefetch={false}
/>
</div>
</section>
@@ -14,6 +14,7 @@ export function Pill({
<Link
href={url}
className="flex items-center font-semibold no-underline group-hover:underline"
prefetch={false}
>
<span className="absolute inset-0" aria-hidden="true"></span>
{children}
+2 -2
View File
@@ -332,13 +332,13 @@
"json-schema-to-typescript": "^10.1.5",
"jsonpointer": "^5.0.0",
"license-checker": "^25.0.1",
"next": "14.2.3",
"next": "14.2.4",
"next-seo": "^5.13.0",
"node-machine-id": "1.1.12",
"npm-run-path": "^4.0.1",
"preact": "10.6.4",
"react": "18.3.1",
"react-copy-to-clipboard": "^5.0.3",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "18.3.1",
"react-syntax-highlighter": "^15.5.0",
"regenerator-runtime": "0.13.7",
@@ -25,10 +25,19 @@ function shouldSkipInitialTargetRun(
project: string,
target: string
): boolean {
const allTargetNames = new Set<string>();
for (const projectName in projectGraph.nodes) {
const project = projectGraph.nodes[projectName];
for (const targetName in project.data.targets ?? {}) {
allTargetNames.add(targetName);
}
}
const projectDependencyConfigs = getDependencyConfigs(
{ project, target },
{},
projectGraph
projectGraph,
Array.from(allTargetNames)
);
// if the task runner already ran the target, skip the initial run
@@ -2,37 +2,51 @@ import { execSync } from 'node:child_process';
import { isCI } from '../ci/is-ci';
import { getPackageManagerCommand } from '../package-manager';
export const NxCloudChoices = ['yes', 'github', 'circleci', 'skip'];
export const NxCloudChoices = [
'github',
'circleci',
'gitlab',
'azure',
'bitbucket',
'skip',
'yes', // Deprecated but still handled
];
const messageOptions = {
const messageOptions: Record<string, MessageData[]> = {
setupCI: [
{
code: 'enable-nx-cloud',
message: `Do you want Nx Cloud to make your CI fast?`,
initial: 1,
code: 'which-ci-provider',
message: `Which CI provider would you like to use?`,
initial: 0,
choices: [
{ value: 'yes', name: 'Yes, enable Nx Cloud' },
{ value: 'github', name: 'Yes, configure Nx Cloud for GitHub Actions' },
{ value: 'circleci', name: 'Yes, configure Nx Cloud for Circle CI' },
{ value: 'skip', name: 'Skip for now' },
{ value: 'github', name: 'GitHub Actions' },
{ value: 'circleci', name: 'Circle CI' },
{ value: 'gitlab', name: 'Gitlab' },
{ value: 'azure', name: 'Azure DevOps' },
{ value: 'bitbucket', name: 'BitBucket Pipelines' },
{ value: 'skip', name: '\nDo it later' },
],
footer:
'\nWatch a short video on Nx Cloud at https://nx.dev/ci/intro/why-nx-cloud',
hint: `\n(it's free and can be disabled any time)`,
fallback: undefined,
'\nRemote caching, task distribution, and test splitting are provided by Nx Cloud. Read more at https://nx.dev/ci',
fallback: { value: 'skip', key: 'setupNxCloud' },
},
{
code: 'set-up-ci',
message: `Set up CI with caching, distribution and test deflaking`,
initial: 0,
choices: [
{ value: 'github', name: 'Yes, for GitHub Actions with Nx Cloud' },
{ value: 'circleci', name: 'Yes, for CircleCI with Nx Cloud' },
{ value: 'skip', name: 'Skip for now' },
{ value: 'github', name: 'GitHub Actions' },
{ value: 'circleci', name: 'CircleCI' },
{ value: 'gitlab', name: 'Gitlab' },
{ value: 'azure', name: 'Azure DevOps' },
{
value: 'bitbucket',
name: 'BitBucket Pipelines',
},
{ value: 'skip', name: '\nDo it later' },
],
footer:
'\nWatch a short video on Nx Cloud at https://nx.dev/ci/intro/why-nx-cloud',
hint: `\n(it's free and can be disabled any time)`,
'\nRemote caching, task distribution, and test splitting are provided by Nx Cloud. Read more at https://nx.dev/ci',
fallback: { value: 'skip', key: 'setupNxCloud' },
},
],
@@ -51,10 +65,18 @@ const messageOptions = {
fallback: undefined,
},
],
} as const;
};
export type MessageKey = keyof typeof messageOptions;
type MessageData = (typeof messageOptions)[MessageKey][number];
interface MessageData {
code: string;
message: string;
initial: number;
choices: Array<{ value: string; name: string }>;
footer: string;
hint?: string;
fallback?: { value: string; key: MessageKey };
}
export class PromptMessages {
private selectedMessages: { [key in MessageKey]?: number } = {};
@@ -162,11 +162,11 @@ describe('eslint-utils', () => {
});
describe('ESLint Flat Config', () => {
it('should throw if a non eslint.config.js file is used with ESLint Flat Config', async () => {
it('should throw if a non eslint.config.js or eslint.config.cjs file is used with ESLint Flat Config', async () => {
await expect(
resolveAndInstantiateESLint('./.eslintrc.json', {} as any, true)
).rejects.toThrowErrorMatchingInlineSnapshot(
`"When using the new Flat Config with ESLint, all configs must be named eslint.config.js and .eslintrc files may not be used. See https://eslint.org/docs/latest/use/configure/configuration-files-new"`
`"When using the new Flat Config with ESLint, all configs must be named eslint.config.js or eslint.config.cjs and .eslintrc files may not be used. See https://eslint.org/docs/latest/use/configure/configuration-files"`
);
});
@@ -10,7 +10,8 @@ export async function resolveAndInstantiateESLint(
) {
if (useFlatConfig && eslintConfigPath && !isFlatConfig(eslintConfigPath)) {
throw new Error(
'When using the new Flat Config with ESLint, all configs must be named eslint.config.js and .eslintrc files may not be used. See https://eslint.org/docs/latest/use/configure/configuration-files-new'
// todo: add support for eslint.config.mjs,
'When using the new Flat Config with ESLint, all configs must be named eslint.config.js or eslint.config.cjs and .eslintrc files may not be used. See https://eslint.org/docs/latest/use/configure/configuration-files'
);
}
const ESLint = await resolveESLintClass(useFlatConfig);
@@ -7,7 +7,10 @@ import {
} from '@nx/devkit';
import type { Tree } from '@nx/devkit';
import type { Linter } from 'eslint';
import { useFlatConfig } from '../../utils/flat-config';
import {
flatConfigEslintFilename,
useFlatConfig,
} from '../../utils/flat-config';
import {
addBlockToFlatConfigExport,
addCompatToFlatConfig,
@@ -174,7 +177,7 @@ export function addOverrideToLintConfig(
if (useFlatConfig(tree)) {
const fileName = joinPathFragments(
root,
isBase ? baseEsLintFlatConfigFile : 'eslint.config.js'
isBase ? baseEsLintFlatConfigFile : flatConfigEslintFilename(tree)
);
const flatOverride = generateFlatOverride(override);
let content = tree.read(fileName, 'utf8');
@@ -220,7 +223,7 @@ export function updateOverrideInLintConfig(
) => Linter.ConfigOverride<Linter.RulesRecord>
) {
if (useFlatConfig(tree)) {
const fileName = joinPathFragments(root, 'eslint.config.js');
const fileName = joinPathFragments(root, flatConfigEslintFilename(tree));
let content = tree.read(fileName, 'utf8');
content = replaceOverride(content, root, lookup, update);
tree.write(fileName, content);
@@ -262,7 +265,7 @@ export function lintConfigHasOverride(
if (useFlatConfig(tree)) {
const fileName = joinPathFragments(
root,
isBase ? baseEsLintFlatConfigFile : 'eslint.config.js'
isBase ? baseEsLintFlatConfigFile : flatConfigEslintFilename(tree)
);
const content = tree.read(fileName, 'utf8');
return hasOverride(content, lookup);
@@ -282,7 +285,7 @@ export function replaceOverridesInLintConfig(
overrides: Linter.ConfigOverride<Linter.RulesRecord>[]
) {
if (useFlatConfig(tree)) {
const fileName = joinPathFragments(root, 'eslint.config.js');
const fileName = joinPathFragments(root, flatConfigEslintFilename(tree));
let content = tree.read(fileName, 'utf8');
// we will be using compat here so we need to make sure it's added
if (overrides.some(overrideNeedsCompat)) {
@@ -311,7 +314,7 @@ export function addExtendsToLintConfig(
) {
const plugins = Array.isArray(plugin) ? plugin : [plugin];
if (useFlatConfig(tree)) {
const fileName = joinPathFragments(root, 'eslint.config.js');
const fileName = joinPathFragments(root, flatConfigEslintFilename(tree));
const pluginExtends = generatePluginExtendsElement(plugins);
let content = tree.read(fileName, 'utf8');
content = addCompatToFlatConfig(content);
@@ -341,7 +344,7 @@ export function addPluginsToLintConfig(
) {
const plugins = Array.isArray(plugin) ? plugin : [plugin];
if (useFlatConfig(tree)) {
const fileName = joinPathFragments(root, 'eslint.config.js');
const fileName = joinPathFragments(root, flatConfigEslintFilename(tree));
let content = tree.read(fileName, 'utf8');
const mappedPlugins: { name: string; varName: string; imp: string }[] = [];
plugins.forEach((name) => {
@@ -369,7 +372,7 @@ export function addIgnoresToLintConfig(
ignorePatterns: string[]
) {
if (useFlatConfig(tree)) {
const fileName = joinPathFragments(root, 'eslint.config.js');
const fileName = joinPathFragments(root, flatConfigEslintFilename(tree));
const block = generateAst<ts.ObjectLiteralExpression>({
ignores: ignorePatterns.map((path) => mapFilePath(path)),
});
+3 -2
View File
@@ -1,8 +1,8 @@
import { existsSync, statSync } from 'fs';
import { basename, dirname, join, resolve } from 'path';
import { eslintFlatConfigFilenames } from './flat-config';
// TODO(leo): add support for eslint.config.mjs and eslint.config.cjs
export const ESLINT_FLAT_CONFIG_FILENAMES = ['eslint.config.js'];
export const ESLINT_FLAT_CONFIG_FILENAMES = eslintFlatConfigFilenames;
export const ESLINT_OLD_CONFIG_FILENAMES = [
'.eslintrc',
@@ -40,6 +40,7 @@ export function findFlatConfigFile(
ESLINT_FLAT_CONFIG_FILENAMES
);
if (configFilePath) {
console.log(`Found eslint flat config file at: ${configFilePath}`);
return configFilePath;
}
if (currentDir === workspaceRoot) {
+21 -2
View File
@@ -1,5 +1,24 @@
import { Tree } from '@nx/devkit';
export function useFlatConfig(tree: Tree): boolean {
return tree.exists('eslint.config.js');
// todo: add support for eslint.config.mjs,
export const eslintFlatConfigFilenames = [
'eslint.config.js',
'eslint.config.cjs',
];
export function flatConfigEslintFilename(tree: Tree): string {
for (const file of eslintFlatConfigFilenames) {
if (tree.exists(file)) {
return file;
}
}
throw new Error('Could not find flat config file');
}
export function useFlatConfig(tree: Tree): boolean {
try {
return !!flatConfigEslintFilename(tree);
} catch {
return false;
}
}
+6
View File
@@ -5,6 +5,12 @@
"cli": "nx",
"description": "Add task projectReportAll to build.gradle file",
"factory": "./src/migrations/19-4-0/add-project-report-all"
},
"change-regex-production-test": {
"version": "19.4.1-beta.0",
"cli": "nx",
"description": "This function changes !{projectRoot}/test/**/* in nx.json for production to !{projectRoot}/src/test/**/*",
"factory": "./src/migrations/19-4-1/change-regex-test-production"
}
},
"packageJsonUpdates": {}
@@ -81,16 +81,16 @@ describe('@nx/gradle:init', () => {
});
const nxJson = readNxJson(tree);
expect(nxJson.namedInputs).toMatchInlineSnapshot(`
{
"default": [
"{projectRoot}/**/*",
],
"production": [
"default",
"!{projectRoot}/test/**/*",
],
}
`);
{
"default": [
"{projectRoot}/**/*",
],
"production": [
"default",
"!{projectRoot}/src/test/**/*",
],
}
`);
});
it('should not overwrite existing namedInputs', async () => {
@@ -102,7 +102,7 @@ describe('@nx/gradle:init', () => {
'!{projectRoot}/cypress/**/*',
'!{projectRoot}/**/*.cy.[jt]s?(x)',
'!{projectRoot}/cypress.config.[jt]s',
'!{projectRoot}/test/**/*',
'!{projectRoot}/src/test/**/*',
],
},
});
@@ -122,7 +122,7 @@ describe('@nx/gradle:init', () => {
"!{projectRoot}/cypress/**/*",
"!{projectRoot}/**/*.cy.[jt]s?(x)",
"!{projectRoot}/cypress.config.[jt]s",
"!{projectRoot}/test/**/*",
"!{projectRoot}/src/test/**/*",
"default",
],
}
+2 -2
View File
@@ -146,7 +146,7 @@ allprojects {
}
}
function updateNxJsonConfiguration(tree: Tree) {
export function updateNxJsonConfiguration(tree: Tree) {
const nxJson = readNxJson(tree);
if (!nxJson.namedInputs) {
@@ -158,7 +158,7 @@ function updateNxJsonConfiguration(tree: Tree) {
);
const productionFileSet = nxJson.namedInputs.production ?? [];
nxJson.namedInputs.production = Array.from(
new Set([...productionFileSet, 'default', '!{projectRoot}/test/**/*'])
new Set([...productionFileSet, 'default', '!{projectRoot}/src/test/**/*'])
);
updateNxJson(tree, nxJson);
}
@@ -0,0 +1,71 @@
import { Tree, readNxJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import update from './change-regex-test-production';
describe('change-regex-test-production', () => {
let tree: Tree;
beforeAll(() => {
tree = createTreeWithEmptyWorkspace();
});
it('should not add to the namedInputs if it does not exist', async () => {
tree.write(
'nx.json',
JSON.stringify({ namedInputs: {}, plugins: ['@nx/gradle'] })
);
update(tree);
expect(readNxJson(tree)).toMatchInlineSnapshot(`
{
"namedInputs": {},
"plugins": [
"@nx/gradle",
],
}
`);
});
it('should not add to the namedInputs production if it is empty', async () => {
tree.write(
'nx.json',
JSON.stringify({
namedInputs: { production: [] },
plugins: ['@nx/gradle'],
})
);
update(tree);
expect(readNxJson(tree)).toMatchInlineSnapshot(`
{
"namedInputs": {
"production": [],
},
"plugins": [
"@nx/gradle",
],
}
`);
});
it('should remove !{projectRoot}/test/**/* from the namedInputs production', async () => {
tree.write(
'nx.json',
JSON.stringify({
namedInputs: { production: ['!{projectRoot}/test/**/*'] },
plugins: ['@nx/gradle'],
})
);
update(tree);
expect(readNxJson(tree)).toMatchInlineSnapshot(`
{
"namedInputs": {
"production": [
"!{projectRoot}/src/test/**/*",
],
},
"plugins": [
"@nx/gradle",
],
}
`);
});
});
@@ -0,0 +1,19 @@
import { Tree, readNxJson, updateNxJson } from '@nx/devkit';
import { hasGradlePlugin } from '../../utils/has-gradle-plugin';
// This function changes !{projectRoot}/test/**/* in nx.json for production to !{projectRoot}/src/test/**/*
export default function update(tree: Tree) {
if (!hasGradlePlugin(tree)) {
return;
}
const nxJson = readNxJson(tree);
if (!nxJson) {
return;
}
const production = nxJson.namedInputs?.production;
if (production?.includes('!{projectRoot}/test/**/*')) {
production[production.indexOf('!{projectRoot}/test/**/*')] =
'!{projectRoot}/src/test/**/*';
updateNxJson(tree, nxJson);
}
}
@@ -439,14 +439,16 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
it('should add extended config files', async () => {
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.json': JSON.stringify({
extends: '../../tsconfig.foo.json',
include: ['src/**/*.ts'],
'tsconfig.base.json': JSON.stringify({
exclude: ['node_modules', 'tmp'],
}),
'tsconfig.foo.json': JSON.stringify({
extends: './tsconfig.base.json',
}),
'tsconfig.base.json': '{}',
'libs/my-lib/tsconfig.json': JSON.stringify({
extends: '../../tsconfig.foo.json',
include: ['src/**/*.ts'],
}),
'libs/my-lib/package.json': `{}`,
});
expect(await invokeCreateNodesOnMatchingFiles(context, {}))
@@ -467,6 +469,8 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"{workspaceRoot}/tsconfig.base.json",
"{projectRoot}/tsconfig.json",
"{projectRoot}/src/**/*.ts",
"!{workspaceRoot}/node_modules",
"!{workspaceRoot}/tmp",
"^production",
{
"externalDependencies": [
@@ -1455,15 +1459,17 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
it('should add extended config files', async () => {
await applyFilesToTempFsAndContext(tempFs, context, {
'tsconfig.base.json': JSON.stringify({
exclude: ['node_modules', 'tmp'],
}),
'tsconfig.foo.json': JSON.stringify({
extends: './tsconfig.base.json',
}),
'libs/my-lib/tsconfig.json': '{}',
'libs/my-lib/tsconfig.lib.json': JSON.stringify({
extends: '../../tsconfig.foo.json',
include: ['src/**/*.ts'],
}),
'tsconfig.foo.json': JSON.stringify({
extends: './tsconfig.base.json',
}),
'tsconfig.base.json': '{}',
'libs/my-lib/package.json': `{}`,
});
expect(
@@ -1488,6 +1494,8 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"{workspaceRoot}/tsconfig.base.json",
"{projectRoot}/tsconfig.lib.json",
"{projectRoot}/src/**/*.ts",
"!{workspaceRoot}/node_modules",
"!{workspaceRoot}/tmp",
"^production",
{
"externalDependencies": [
+11 -2
View File
@@ -282,7 +282,11 @@ function getInputs(
pathToInputOrOutput(p, workspaceRoot, projectRoot)
),
...Array.from(includePaths).map((p: string) =>
joinPathFragments('{projectRoot}', p)
pathToInputOrOutput(
joinPathFragments(projectRoot, p),
workspaceRoot,
projectRoot
)
)
);
} else {
@@ -294,7 +298,12 @@ function getInputs(
if (excludePaths.size) {
inputs.push(
...Array.from(excludePaths).map(
(p: string) => `!${joinPathFragments('{projectRoot}', p)}`
(p: string) =>
`!${pathToInputOrOutput(
joinPathFragments(projectRoot, p),
workspaceRoot,
projectRoot
)}`
)
);
}
@@ -27,12 +27,22 @@ function normalizeOptions(
function addDocker(tree: Tree, options: SetUpDockerOptions) {
const projectConfig = readProjectConfiguration(tree, options.project);
const outputPath =
projectConfig.targets[options.buildTarget]?.options['outputPath'];
if (!projectConfig) {
throw new Error(`Cannot find project configuration for ${options.project}`);
}
if (!outputPath && !options.outputPath) {
throw new Error(
`The output path for the project ${options.project} is not defined. Please provide it as an option to the generator.`
);
}
generateFiles(tree, join(__dirname, './files'), projectConfig.root, {
tmpl: '',
buildLocation: options.outputPath,
buildLocation: options.outputPath ?? outputPath,
project: options.project,
});
}
+1 -1
View File
@@ -54,7 +54,7 @@ export async function resetHandler(args: ResetCommandOptions) {
try {
await cleanupNativeFileCache();
} catch {
errors.push('Failed to clean up the native file cache.');
// ignore, deleting the native file cache is not critical and can fail if another process is locking the file
}
try {
await cleanupWorkspaceData();
@@ -0,0 +1,97 @@
import { source } from '@angular-devkit/schematics';
import { createCommandGraph } from './create-command-graph';
import { CommandGraph } from './command-graph';
export { createCommandGraph } from './create-command-graph';
describe('createCommandGraph', () => {
it('should create command graph with empty dependencies', () => {
const projectGraph = {
dependencies: {},
nodes: {},
};
const projectNames = [];
const nxArgs = {};
const result = createCommandGraph(projectGraph, projectNames, nxArgs);
expect(result).toEqual({ dependencies: {}, roots: [] });
});
it('should create command graph with dependencies', () => {
const projectGraph = {
dependencies: {
dep1: [{ target: 'dep2', type: 'static', source: 'dep1' }],
dep2: [],
},
nodes: {
dep1: {
type: 'lib' as 'lib',
name: 'dep1',
data: {
files: [],
root: 'dep1',
},
},
dep2: {
type: 'lib' as 'lib',
name: 'dep2',
data: {
files: [],
root: 'dep2',
},
},
},
};
const projectNames = ['dep1'];
const nxArgs = {};
const result = createCommandGraph(projectGraph, projectNames, nxArgs);
expect(result).toEqual({
dependencies: { dep1: ['dep2'], dep2: [] },
roots: ['dep2'],
});
});
it('should create command graph with nested dependencies', () => {
const projectGraph = {
dependencies: {
dep1: [{ target: 'dep2', type: 'static', source: 'dep1' }],
dep2: [],
dep3: [{ target: 'dep1', type: 'static', source: 'dep3' }],
},
nodes: {
dep1: {
type: 'lib' as 'lib',
name: 'dep1',
data: {
files: [],
root: 'dep1',
},
},
dep2: {
type: 'lib' as 'lib',
name: 'dep2',
data: {
files: [],
root: 'dep2',
},
},
dep3: {
type: 'lib' as 'lib',
name: 'dep3',
data: {
files: [],
root: 'dep3',
},
},
},
};
const result = createCommandGraph(
projectGraph,
['dep1', 'dep2', 'dep3'],
{}
);
expect(result).toEqual({
dependencies: { dep1: ['dep2'], dep2: [], dep3: ['dep1'] },
roots: ['dep2'],
});
});
});
@@ -4,6 +4,46 @@ import { NxArgs } from '../utils/command-line-utils';
import { output } from '../utils/output';
import { CommandGraph } from './command-graph';
/**
* Make structure { lib: [dep], dep: [dep1], dep1: [] } from projectName lib and projectGraph
* @param projectGraph
* @param projectName
* @param resolved reference to an object that will contain resolved dependencies
* @returns
*/
const recursiveResolveDeps = (
projectGraph: ProjectGraph,
projectName: string,
resolved: Record<string, string[]>
) => {
if (projectGraph.dependencies[projectName].length === 0) {
// no deps - no resolve
resolved[projectName] = [];
return;
}
// if already resolved - just skip
if (resolved[projectName]) {
return resolved[projectName];
}
// deps string list
const projectDeps = [
...new Set(
projectGraph.dependencies[projectName]
.map((projectDep) => projectDep.target)
.filter((projectDep) => projectGraph.nodes[projectDep])
).values(),
];
// define
resolved[projectName] = projectDeps;
if (projectDeps.length > 0) {
for (const dep of projectDeps) {
recursiveResolveDeps(projectGraph, dep, resolved);
}
}
};
export function createCommandGraph(
projectGraph: ProjectGraph,
projectNames: string[],
@@ -11,17 +51,7 @@ export function createCommandGraph(
): CommandGraph {
const dependencies: Record<string, string[]> = {};
for (const projectName of projectNames) {
if (projectGraph.dependencies[projectName].length >= 1) {
dependencies[projectName] = [
...new Set(
projectGraph.dependencies[projectName]
.map((projectDep) => projectDep.target)
.filter((projectDep) => projectGraph.nodes[projectDep])
).values(),
];
} else {
dependencies[projectName] = [];
}
recursiveResolveDeps(projectGraph, projectName, dependencies);
}
const roots = Object.keys(dependencies).filter(
(d) => dependencies[d].length === 0
+36 -17
View File
@@ -1,7 +1,7 @@
import { dirname, join, sep } from 'path';
import type { TsConfigOptions } from 'ts-node';
import type { CompilerOptions } from 'typescript';
import { NX_PREFIX, logger, stripIndent } from '../../../utils/logger';
import { logger, NX_PREFIX, stripIndent } from '../../../utils/logger';
const swcNodeInstalled = packageIsInstalled('@swc-node/register');
const tsNodeInstalled = packageIsInstalled('ts-node/register');
@@ -120,17 +120,9 @@ export function getSwcTranspiler(
return typeof cleanupFn === 'function' ? cleanupFn : () => {};
}
const registered = new Set<string>();
export function getTsNodeTranspiler(
compilerOptions: CompilerOptions
): (...args: unknown[]) => unknown {
// Just return if transpiler was already registered before.
const registrationKey = JSON.stringify(compilerOptions);
if (registered.has(registrationKey)) {
return () => {};
}
const { register } = require('ts-node') as typeof import('ts-node');
// ts-node doesn't provide a cleanup method
const service = register({
@@ -139,7 +131,6 @@ export function getTsNodeTranspiler(
// we already read and provide the compiler options, so prevent ts-node from reading them again
skipProject: true,
});
registered.add(registrationKey);
const { transpiler, swc } = service.options;
@@ -227,6 +218,11 @@ function filterRecognizedTsConfigTsNodeOptions(jsonObject: any): {
return { recognized: filteredTsConfigOptions, unrecognized };
}
const registered = new Map<
string,
{ refCount: number; cleanup: () => (...args: unknown[]) => unknown }
>();
export function getTranspiler(
compilerOptions: CompilerOptions,
tsConfigRaw?: unknown
@@ -246,15 +242,38 @@ export function getTranspiler(
compilerOptions.inlineSourceMap = true;
compilerOptions.skipLibCheck = true;
if (swcNodeInstalled && !preferTsNode) {
return () => getSwcTranspiler(compilerOptions);
// Just return if transpiler was already registered before.
const registrationKey = JSON.stringify(compilerOptions);
const registrationEntry = registered.get(registrationKey);
if (registered.has(registrationKey)) {
registrationEntry.refCount++;
return registrationEntry.cleanup;
}
// We can fall back on ts-node if it's available
if (tsNodeInstalled) {
const tsNodeOptions =
filterRecognizedTsConfigTsNodeOptions(tsConfigRaw).recognized;
return () => getTsNodeTranspiler(compilerOptions);
const _getTranspiler =
swcNodeInstalled && !preferTsNode
? getSwcTranspiler
: tsNodeInstalled
? // We can fall back on ts-node if it's available
getTsNodeTranspiler
: undefined;
if (_getTranspiler) {
const transpilerCleanup = _getTranspiler(compilerOptions);
const currRegistrationEntry = {
refCount: 1,
cleanup: () => {
return () => {
currRegistrationEntry.refCount--;
if (currRegistrationEntry.refCount === 0) {
registered.delete(registrationKey);
transpilerCleanup();
}
};
},
};
registered.set(registrationKey, currRegistrationEntry);
return currRegistrationEntry.cleanup;
}
}
@@ -106,7 +106,7 @@ export interface PluginCreateMetadataResult {
}
| {
success: false;
error: string;
error: Error;
tx: string;
};
}
@@ -325,7 +325,7 @@ async function startPluginWorker() {
ipcPath,
],
{
stdio: process.stdout.isTTY ? 'inherit' : 'ignore',
stdio: 'inherit',
env,
detached: true,
shell: false,
@@ -121,7 +121,11 @@ const server = createServer((socket) => {
} catch (e) {
return {
type: 'createMetadataResult',
payload: { success: false, error: e.stack, tx },
payload: {
success: false,
error: createSerializableError(e),
tx,
},
};
}
},
@@ -393,9 +393,11 @@ export async function createProjectConfigurations(
} else {
errorBodyLines.push(` - ${e.message}`);
}
const innerStackTrace = ' ' + e.stack.split('\n').join('\n ');
errorBodyLines.push(innerStackTrace);
}
error.message = errorBodyLines.join('\n');
error.stack = errorBodyLines.join('\n');
// This represents a single plugin erroring out with a hard error.
errors.push(error);
@@ -7,18 +7,27 @@ import {
import { Task, TaskGraph } from '../config/task-graph';
import { TargetDefaults, TargetDependencies } from '../config/nx-json';
import { TargetDependencyConfig } from '../devkit-exports';
import { findMatchingProjects } from '../utils/find-matching-projects';
import { output } from '../utils/output';
export class ProcessTasks {
private readonly seen = new Set<string>();
readonly tasks: { [id: string]: Task } = {};
readonly dependencies: { [k: string]: string[] } = {};
private readonly allTargetNames: string[];
constructor(
private readonly extraTargetDependencies: TargetDependencies,
private readonly projectGraph: ProjectGraph
) {}
) {
const allTargetNames = new Set<string>();
for (const projectName in projectGraph.nodes) {
const project = projectGraph.nodes[projectName];
for (const targetName in project.data.targets ?? {}) {
allTargetNames.add(targetName);
}
}
this.allTargetNames = Array.from(allTargetNames);
}
processTasks(
projectNames: string[],
@@ -100,7 +109,8 @@ export class ProcessTasks {
const dependencyConfigs = getDependencyConfigs(
{ project: task.target.project, target: task.target.target },
this.extraTargetDependencies,
this.projectGraph
this.projectGraph,
this.allTargetNames
);
for (const dependencyConfig of dependencyConfigs) {
const taskOverrides =
@@ -108,40 +118,7 @@ export class ProcessTasks {
? overrides
: { __overrides_unparsed__: [] };
if (dependencyConfig.projects) {
/** LERNA SUPPORT START - Remove in v20 */
// Lerna uses `dependencies` in `prepNxOptions`, so we need to maintain
// support for it until lerna can be updated to use the syntax.
//
// This should have been removed in v17, but the updates to lerna had not
// been made yet.
//
// TODO(@agentender): Remove this part in v20
if (typeof dependencyConfig.projects === 'string') {
if (dependencyConfig.projects === 'self') {
this.processTasksForSingleProject(
task,
task.target.project,
dependencyConfig,
configuration,
taskOverrides,
overrides
);
continue;
} else if (dependencyConfig.projects === 'dependencies') {
this.processTasksForDependencies(
projectUsedToDeriveDependencies,
dependencyConfig,
configuration,
task,
taskOverrides,
overrides
);
continue;
}
}
/** LERNA SUPPORT END - Remove in v17 */
this.processTasksForMatchingProjects(
this.processTasksForMultipleProjects(
dependencyConfig,
configuration,
task,
@@ -170,31 +147,22 @@ export class ProcessTasks {
}
}
private processTasksForMatchingProjects(
private processTasksForMultipleProjects(
dependencyConfig: TargetDependencyConfig,
configuration: string,
task: Task,
taskOverrides: Object | { __overrides_unparsed__: any[] },
overrides: Object
) {
const targetProjectSpecifiers =
typeof dependencyConfig.projects === 'string'
? [dependencyConfig.projects]
: dependencyConfig.projects;
const matchingProjects = findMatchingProjects(
targetProjectSpecifiers,
this.projectGraph.nodes
);
if (matchingProjects.length === 0) {
if (dependencyConfig.projects.length === 0) {
output.warn({
title: `\`dependsOn\` is misconfigured for ${task.target.project}:${task.target.target}`,
bodyLines: [
`Project patterns "${targetProjectSpecifiers}" does not match any projects.`,
`Project patterns "${dependencyConfig.projects}" does not match any projects.`,
],
});
}
for (const projectName of matchingProjects) {
for (const projectName of dependencyConfig.projects) {
this.processTasksForSingleProject(
task,
projectName,
@@ -15,16 +15,16 @@ function _findCycle(
return null;
}
export function findCycle(taskGraph: {
export function findCycle(graph: {
dependencies: Record<string, string[]>;
}): string[] | null {
const visited = {};
for (const t of Object.keys(taskGraph.dependencies)) {
for (const t of Object.keys(graph.dependencies)) {
visited[t] = false;
}
for (const t of Object.keys(taskGraph.dependencies)) {
const cycle = _findCycle(taskGraph, t, visited, [t]);
for (const t of Object.keys(graph.dependencies)) {
const cycle = _findCycle(graph, t, visited, [t]);
if (cycle) return cycle;
}
+110
View File
@@ -1,5 +1,6 @@
import {
expandDependencyConfigSyntaxSugar,
getDependencyConfigs,
getOutputsForTargetAndConfiguration,
interpolate,
transformLegacyOutputs,
@@ -408,6 +409,7 @@ describe('utils', () => {
const result = transformLegacyOutputs('myapp', e);
expect(result).toEqual(['{workspaceRoot}/dist']);
}
expect.assertions(1);
});
it('should prefix unix-absolute paths with {workspaceRoot}', () => {
@@ -418,6 +420,7 @@ describe('utils', () => {
const result = transformLegacyOutputs('myapp', e);
expect(result).toEqual(['{workspaceRoot}/dist']);
}
expect.assertions(1);
});
});
@@ -429,6 +432,7 @@ describe('utils', () => {
const result = transformLegacyOutputs('myapp', e);
expect(result).toEqual(['{workspaceRoot}/dist']);
}
expect.assertions(1);
});
it('should prefix paths within the project with {projectRoot}', () => {
@@ -439,6 +443,7 @@ describe('utils', () => {
const result = transformLegacyOutputs('myapp', e);
expect(result).toEqual(['{projectRoot}/dist']);
}
expect.assertions(1);
});
describe('expandDependencyConfigSyntaxSugar', () => {
@@ -509,6 +514,111 @@ describe('utils', () => {
target: 'target:with:colons',
});
});
it('supports wildcards in targets', () => {
const result = getDependencyConfigs(
{ project: 'project', target: 'build' },
{},
{
dependencies: {},
nodes: {
project: {
name: 'project',
type: 'app',
data: {
root: 'libs/project',
targets: {
build: {
dependsOn: ['build-*'],
},
'build-css': {},
'build-js': {},
'then-build-something-else': {},
},
},
},
},
},
['build', 'build-css', 'build-js', 'then-build-something-else']
);
expect(result).toEqual([
{
target: 'build-css',
projects: ['project'],
},
{
target: 'build-js',
projects: ['project'],
},
]);
});
it('should support wildcards with dependencies', () => {
const result = getDependencyConfigs(
{ project: 'project', target: 'build' },
{},
{
dependencies: {},
nodes: {
project: {
name: 'project',
type: 'app',
data: {
root: 'libs/project',
targets: {
build: {
dependsOn: ['^build-*'],
},
'then-build-something-else': {},
},
},
},
dep1: {
name: 'dep1',
type: 'lib',
data: {
root: 'libs/dep1',
targets: {
'build-css': {},
'build-js': {},
},
},
},
dep2: {
name: 'dep2',
type: 'lib',
data: {
root: 'libs/dep2',
targets: {
'build-python': {},
},
},
},
},
},
[
'build',
'build-css',
'build-js',
'then-build-something-else',
'build-python',
]
);
expect(result).toEqual([
{
target: 'build-css',
dependencies: true,
},
{
target: 'build-js',
dependencies: true,
},
{
target: 'build-python',
dependencies: true,
},
]);
});
});
describe('validateOutputs', () => {
+143 -27
View File
@@ -15,40 +15,81 @@ import { splitByColons } from '../utils/split-target';
import { getExecutorInformation } from '../command-line/run/executor-utils';
import { CustomHasher, ExecutorConfig } from '../config/misc-interfaces';
import { readProjectsConfigurationFromProjectGraph } from '../project-graph/project-graph';
import {
GLOB_CHARACTERS,
findMatchingProjects,
} from '../utils/find-matching-projects';
import { minimatch } from 'minimatch';
export type NormalizedTargetDependencyConfig = TargetDependencyConfig & {
projects: string[];
};
export function getDependencyConfigs(
{ project, target }: { project: string; target: string },
extraTargetDependencies: Record<string, (TargetDependencyConfig | string)[]>,
projectGraph: ProjectGraph
): TargetDependencyConfig[] | undefined {
projectGraph: ProjectGraph,
allTargetNames: string[]
): NormalizedTargetDependencyConfig[] | undefined {
const dependencyConfigs = (
projectGraph.nodes[project].data?.targets[target]?.dependsOn ??
// This is passed into `run-command` from programmatic invocations
extraTargetDependencies[target] ??
[]
).map((config) =>
typeof config === 'string'
? expandDependencyConfigSyntaxSugar(config, projectGraph)
: config
).flatMap((config) =>
normalizeDependencyConfigDefinition(
config,
project,
projectGraph,
allTargetNames
)
);
for (const dependencyConfig of dependencyConfigs) {
if (dependencyConfig.projects && dependencyConfig.dependencies) {
output.error({
title: `dependsOn is improperly configured for ${project}:${target}`,
bodyLines: [
`dependsOn.projects and dependsOn.dependencies cannot be used together.`,
],
});
process.exit(1);
}
}
return dependencyConfigs;
}
export function normalizeDependencyConfigDefinition(
definition: string | TargetDependencyConfig,
currentProject: string,
graph: ProjectGraph,
allTargetNames: string[]
): NormalizedTargetDependencyConfig[] {
return expandWildcardTargetConfiguration(
normalizeDependencyConfigProjects(
expandDependencyConfigSyntaxSugar(definition, graph),
currentProject,
graph
),
allTargetNames
);
}
export function normalizeDependencyConfigProjects(
dependencyConfig: TargetDependencyConfig,
currentProject: string,
graph: ProjectGraph
): NormalizedTargetDependencyConfig {
const noStringConfig =
normalizeTargetDependencyWithStringProjects(dependencyConfig);
if (noStringConfig.projects) {
dependencyConfig.projects = findMatchingProjects(
noStringConfig.projects,
graph.nodes
);
} else if (!noStringConfig.dependencies) {
dependencyConfig.projects = [currentProject];
}
return dependencyConfig as NormalizedTargetDependencyConfig;
}
export function expandDependencyConfigSyntaxSugar(
dependencyConfigString: string,
dependencyConfigString: string | TargetDependencyConfig,
graph: ProjectGraph
): TargetDependencyConfig {
if (typeof dependencyConfigString !== 'string') {
return dependencyConfigString;
}
const [dependencies, targetString] = dependencyConfigString.startsWith('^')
? [true, dependencyConfigString.substring(1)]
: [false, dependencyConfigString];
@@ -62,22 +103,69 @@ export function expandDependencyConfigSyntaxSugar(
};
}
const { projects, target } = readProjectAndTargetFromTargetString(
targetString,
graph.nodes
);
return projects ? { projects, target } : { target };
}
// Weakmap let's the cache get cleared by garbage collector if allTargetNames is no longer used
const patternResultCache = new WeakMap<
string[],
// Map< Pattern, Dependency Configs >
Map<string, NormalizedTargetDependencyConfig[]>
>();
export function expandWildcardTargetConfiguration(
dependencyConfig: NormalizedTargetDependencyConfig,
allTargetNames: string[]
): NormalizedTargetDependencyConfig[] {
if (!GLOB_CHARACTERS.some((char) => dependencyConfig.target.includes(char))) {
return [dependencyConfig];
}
let cache = patternResultCache.get(allTargetNames);
if (!cache) {
cache = new Map();
patternResultCache.set(allTargetNames, cache);
}
const cachedResult = cache.get(dependencyConfig.target);
if (cachedResult) {
return cachedResult;
}
const matcher = minimatch.filter(dependencyConfig.target);
const matchingTargets = allTargetNames.filter((t) => matcher(t));
const result = matchingTargets.map((t) => ({
...dependencyConfig,
target: t,
}));
cache.set(dependencyConfig.target, result);
return result;
}
export function readProjectAndTargetFromTargetString(
targetString: string,
projects: Record<string, ProjectGraphProjectNode>
): { projects?: string[]; target: string } {
// Support for both `project:target` and `target:with:colons` syntax
const [maybeProject, ...segments] = splitByColons(targetString);
// if no additional segments are provided, then the string references
// a target of the same project
if (!segments.length) {
// if no additional segments are provided, then the string references
// a target of the same project
return { target: maybeProject };
}
return {
} else if (maybeProject in projects) {
// Only the first segment could be a project. If it is, the rest is a target.
// If its not, then the whole targetString was a target with colons in its name.
target: maybeProject in graph.nodes ? segments.join(':') : targetString,
return { projects: [maybeProject], target: segments.join(':') };
} else {
// If the first segment is a project, then we have a specific project. Otherwise, we don't.
projects: maybeProject in graph.nodes ? [maybeProject] : undefined,
};
return { target: targetString };
}
}
export function getOutputs(
@@ -92,6 +180,34 @@ export function getOutputs(
);
}
export function normalizeTargetDependencyWithStringProjects(
dependencyConfig: TargetDependencyConfig
): Omit<TargetDependencyConfig, 'projects'> & { projects: string[] } {
if (typeof dependencyConfig.projects === 'string') {
/** LERNA SUPPORT START - Remove in v20 */
// Lerna uses `dependencies` in `prepNxOptions`, so we need to maintain
// support for it until lerna can be updated to use the syntax.
//
// This should have been removed in v17, but the updates to lerna had not
// been made yet.
//
// TODO(@agentender): Remove this part in v20
if (dependencyConfig.projects === 'self') {
delete dependencyConfig.projects;
} else if (dependencyConfig.projects === 'dependencies') {
dependencyConfig.dependencies = true;
delete dependencyConfig.projects;
return;
/** LERNA SUPPORT END - Remove in v20 */
} else {
dependencyConfig.projects = [dependencyConfig.projects];
}
}
return dependencyConfig as Omit<TargetDependencyConfig, 'projects'> & {
projects: string[];
};
}
class InvalidOutputsError extends Error {
constructor(public outputs: string[], public invalidOutputs: Set<string>) {
super(InvalidOutputsError.createMessage(invalidOutputs));
@@ -201,7 +317,7 @@ export function getOutputsForTargetAndConfiguration(
const targetConfiguration = node.data.targets[target];
const options = {
...targetConfiguration.options,
...targetConfiguration?.options,
...targetConfiguration?.configurations?.[configuration],
...overrides,
};

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