Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 73f15ebcc8 | |||
| 75b7fa1553 | |||
| ef3d1e227c | |||
| 3abd3528f7 | |||
| 5818942d10 | |||
| a35dff8af2 | |||
| 8d3323eba5 | |||
| 0c7e6cd07c | |||
| c7cb8da5fd | |||
| b027042f2f | |||
| 7dd5498797 | |||
| b2155119cc | |||
| 3a5f41252c | |||
| adbbaba497 | |||
| 82f7a5fe4e | |||
| 7951ded7ea | |||
| 8157ecbaf3 | |||
| b91e26eef3 | |||
| 38da3680cc | |||
| 3e59e9852c | |||
| c67339231d | |||
| 3dfdc1884b | |||
| bcd1cff408 | |||
| 7ba7b6ac3f | |||
| 2518e7e739 | |||
| 9e95a89ef3 | |||
| ad5d29e1b5 | |||
| 6b8878e0c5 | |||
| 4ac0a716a0 | |||
| 516bfbdb1e | |||
| 91ddd8874c | |||
| 06cc155cbc | |||
| f44ebbb142 | |||
| 46542f70d4 | |||
| 9389ab0d26 | |||
| 04b959db98 | |||
| 59148637fb | |||
| b099cd750f | |||
| 42fc59731e | |||
| 7887accb07 | |||
| 7ac7d7130b | |||
| 9cd0c0d7e0 | |||
| bedc48d1a2 | |||
| 32aa8a1e81 | |||
| b0c82e6de4 |
@@ -68,7 +68,6 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
check-latest: true
|
||||
cache: 'pnpm'
|
||||
|
||||
# Ensure that the NPM_TOKEN secret is still valid before wasting any time deriving data or building projects
|
||||
- name: Check NPM Credentials
|
||||
@@ -414,6 +413,7 @@ jobs:
|
||||
pnpm nx-release --local=false $VERSION $DRY_RUN
|
||||
|
||||
- name: (Stable Release Only) Trigger Docs Release
|
||||
# Publish docs only on a full release
|
||||
if: ${{ !github.event.release.prerelease && github.event_name == 'release' }}
|
||||
run: npx ts-node ./scripts/release-docs.ts
|
||||
|
||||
|
||||
@@ -299,6 +299,11 @@
|
||||
"description": "Nx plugin for working with shadcn/ui",
|
||||
"url": "https://github.com/tripss/nx-extend/tree/master/packages/shadcn-ui"
|
||||
},
|
||||
{
|
||||
"name": "@nx-extend/docusaurus",
|
||||
"description": "Nx plugin adding first class support for Docusaurus in your Nx workspace.",
|
||||
"url": "https://github.com/tripss/nx-extend/tree/master/packages/docusaurus"
|
||||
},
|
||||
{
|
||||
"name": "@nativescript/nx",
|
||||
"description": "Nx Plugin adding first class support for NativeScript in your Nx workspace",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Nx Cloud 3.0 — Faster Cache, More Powerful DTE, Better Ergonomics
|
||||
slug: 'nx-cloud-3-0-faster-more-efficient-modernized'
|
||||
authors: [Juri Strumpflohner]
|
||||
cover_image: '/blog/images/2023-04-19/featured_img.webp'
|
||||
tags: [nx, nx-cloud]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Nx Console gets Lit
|
||||
slug: 'nx-console-gets-lit'
|
||||
authors: [Max Kless]
|
||||
cover_image: '/blog/images/2023-06-29/featured_img.webp'
|
||||
tags: [nx, nx-console]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Qwikify your Development with Nx
|
||||
slug: 'qwikify-your-development-with-nx'
|
||||
authors: ['Colum Ferry']
|
||||
cover_image: '/blog/images/2023-08-15/featured_img.png'
|
||||
tags: [nx, changelog, release]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Nx Raises $16M Series A
|
||||
slug: 'nx-raises-16m-series-a'
|
||||
authors: [Jeff Cross]
|
||||
tags: [nx]
|
||||
---
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Nx Conf 2023 — Recap
|
||||
slug: 'nx-conf-2023-recap'
|
||||
authors: [Juri Strumpflohner]
|
||||
cover_image: '/blog/images/2023-10-13/featured_img.webp'
|
||||
tags: [nx, nx-conf]
|
||||
@@ -475,7 +476,7 @@ Jon and Max are the masterminds behind [Nx Console](/getting-started/editor-setu
|
||||
|
||||
They mention the release of the JetBrains IDE support earlier this year and give a big shoutout to [Issam](https://github.com/iguissouma) who was the original author of the Nx Console Webstorm community plugin and who helped a lot with the official Nx Console version for JetBrains IDE.
|
||||
|
||||
Jon and Max demo the new IntelliJ version of Nx console and how it properly integrates as well as how they wrote the new UI completely from the ground up [using Lit](/blog/2023-06-29-nx-console-gets-lit).
|
||||
Jon and Max demo the new IntelliJ version of Nx console and how it properly integrates as well as how they wrote the new UI completely from the ground up [using Lit](/blog/nx-console-gets-lit).
|
||||
|
||||
Clearly they did not spare showing off the awesome new graph capabilities built into Nx Console that allow you to directly navigate to files.
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 'Nx 17 has Landed'
|
||||
slug: 'nx-17-release'
|
||||
authors: ['Juri Strumpflohner', 'Zack DeRose']
|
||||
cover_image: '/blog/images/2023-10-20/featured_img.png'
|
||||
tags: [nx, design]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: State Management Nx React Native/Expo Apps with TanStack Query and Redux
|
||||
slug: 'state-management-nx-react-native-expo-apps-with-tanstack-query-and-redux'
|
||||
authors: [Emily Xiong]
|
||||
image: '/blog/images/2023-11-08/featured_img.webp'
|
||||
tags: [nx, React Native]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Nx Docs AI Assistant
|
||||
slug: 'nx-docs-ai-assistant'
|
||||
authors: [Katerina Skroumpelou]
|
||||
cover_image: '/blog/images/2023-11-21/featured_img.webp'
|
||||
tags: [nx, docs, AI]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Unit Testing Expo Apps With Jest
|
||||
slug: 'unit-testing-expo-apps-with-jest'
|
||||
authors: [Emily Xiong]
|
||||
cover_image: '/blog/images/2023-11-22/featured_img.webp'
|
||||
tags: [nx, unit testing]
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
---
|
||||
title: Nx 17.2 Update
|
||||
slug: 'nx-17-2-release'
|
||||
authors: [Zack DeRose]
|
||||
cover_image: '/blog/images/2023-12-20/featured_img.png'
|
||||
tags: [nx, changelog, release]
|
||||
---
|
||||
|
||||
It’s been a bit since we launched [Nx 17](/blog/2023-10-20-nx-17-release)! In this article, we’ll go over some of the new developments and improvements that have landed in Nx 17.2:
|
||||
It’s been a bit since we launched [Nx 17](/blog/nx-17-release)! In this article, we’ll go over some of the new developments and improvements that have landed in Nx 17.2:
|
||||
|
||||
- [Nx Closes In On 4 Million Weekly NPM Downloads!!](#nx-closes-in-on-4-million-weekly-npm-downloads)
|
||||
- [New Simplified Project Configuration On the Way](#new-simplified-project-configuration-on-the-way)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Nx — Highlights of 2023
|
||||
slug: 'nx-highlights-of-2023'
|
||||
authors: [Juri Strumpflohner, Victor Savkin, Zack DeRose]
|
||||
cover_image: /blog/images/2023-12-28/featured_img.png
|
||||
tags: [nx, nx-cloud]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: What if Nx Plugins Were More Like VSCode Extensions
|
||||
slug: 'what-if-nx-plugins-were-more-like-vscode-extensions'
|
||||
authors: [Juri Strumpflohner]
|
||||
cover_image: '/blog/images/2024-02-05/featured_img.png'
|
||||
tags: [nx, releases]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Introducing @nx/nuxt Enhanced Nuxt.js Support in Nx
|
||||
slug: 'introducing-nx-nuxt-enhanced-nuxt-js-support-in-nx'
|
||||
cover_image: '/blog/images/2024-02-06/featured_img.png'
|
||||
authors: ['Katerina Skroumpelou']
|
||||
tags: [devtools, javascript, monorepos, nuxt]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Fast, Effortless CI
|
||||
slug: 'fast-effortless-ci'
|
||||
authors: [Isaac Mann]
|
||||
cover_image: '/blog/images/2024-02-07/featured_img.png'
|
||||
tags: [nx, nx-cloud, release]
|
||||
@@ -101,7 +102,7 @@ Nx understands that some CI pipelines need more resources than others. To accoun
|
||||
|
||||
## Automatically Split E2E Tasks by File
|
||||
|
||||
Typically, e2e tests are the tasks that take the longest in CI. In order to take advantage of parallelization and task distribution, these large tasks would need to be split into smaller tasks, but doing this manually would involve duplicating a lot of configuration code and making sure to keep that configuration synchronized. Nx 18’s [Project Crystal](/blog/2024-02-05-nx-18-project-crystal) allows you to [automatically create separate Cypress and Playwright tasks](/ci/features/split-e2e-tasks) for each spec file in the e2e project. These individual tasks can all be triggered by running the `e2e-ci` task. What was once a tedious manual process can now be done for you automatically.
|
||||
Typically, e2e tests are the tasks that take the longest in CI. In order to take advantage of parallelization and task distribution, these large tasks would need to be split into smaller tasks, but doing this manually would involve duplicating a lot of configuration code and making sure to keep that configuration synchronized. Nx 18’s [Project Crystal](/blog/what-if-nx-plugins-were-more-like-vscode-extensions) allows you to [automatically create separate Cypress and Playwright tasks](/ci/features/split-e2e-tasks) for each spec file in the e2e project. These individual tasks can all be triggered by running the `e2e-ci` task. What was once a tedious manual process can now be done for you automatically.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Versioning and Releasing Packages in a Monorepo
|
||||
slug: 'versioning-and-releasing-packages-in-a-monorepo'
|
||||
authors: [Juri Strumpflohner]
|
||||
cover_image: '/blog/images/2024-02-09/featured_img.png'
|
||||
tags: [nx, nx-cloud, releases, changelog]
|
||||
@@ -48,11 +49,11 @@ pnpm dlx nx@latest init
|
||||
|
||||
(use `npx nx@latest init` in a NPM workspace)
|
||||
|
||||
This brings up a couple of questions including whether to install [Project Crystal plugins](/blog/2024-02-05-nx-18-project-crystal).
|
||||
This brings up a couple of questions including whether to install [Project Crystal plugins](/blog/what-if-nx-plugins-were-more-like-vscode-extensions).
|
||||
|
||||

|
||||
|
||||
It gives you some additional benefits ([you can read more here](/blog/2024-02-05-nx-18-project-crystal)), but you don’t have to as it is not required for Nx Release.
|
||||
It gives you some additional benefits ([you can read more here](/blog/what-if-nx-plugins-were-more-like-vscode-extensions)), but you don’t have to as it is not required for Nx Release.
|
||||
|
||||
## Installing the JavaScript/TypeScript versioning Package
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Launch Nx Week Recap
|
||||
slug: 'launch-nx-week-recap'
|
||||
authors: [Zack DeRose]
|
||||
cover_image: '/blog/images/2024-02-15/featured_img.png'
|
||||
tags: [nx, nx-cloud, releases, changelog]
|
||||
@@ -36,7 +37,7 @@ Juri announced launched Project Crystal with this video:
|
||||
|
||||
The short version is: Nx project crystal means Nx plugins make your codebases work more seemlessly with less config.
|
||||
|
||||
For the long version, checkout this blog post by Juri where he describes [how Nx plugins are now more like VsCode Extentions](/blog/2024-02-05-nx-18-project-crystal)!
|
||||
For the long version, checkout this blog post by Juri where he describes [how Nx plugins are now more like VsCode Extentions](/blog/what-if-nx-plugins-were-more-like-vscode-extensions)!
|
||||
|
||||
And don’t miss our two conference talks on this subject:
|
||||
|
||||
@@ -80,7 +81,7 @@ We’re very excited about Nx Agents because we think that in its current state,
|
||||
|
||||
{% youtube src="https://www.youtube.com/embed/_FSHQIwITic?si=jDpwTVXLYFXiHEm0" title="Nx Agents" /%}
|
||||
|
||||
Be sure to also checkout the [blog post from Isaac on Nx Agents](/blog/2024-02-07-fast-effortless-ci), including explanations of exclusive features like auto-detection and retrying for flaky tasks and automatically splitting lengthy end-to-end tests!
|
||||
Be sure to also checkout the [blog post from Isaac on Nx Agents](/blog/fast-effortless-ci), including explanations of exclusive features like auto-detection and retrying for flaky tasks and automatically splitting lengthy end-to-end tests!
|
||||
|
||||
You can [signup for Nx Agents NOW](https://nx.app/products/agents#content), and find out [more of the details in our docs](/ci/features/distribute-task-execution)!
|
||||
|
||||
@@ -120,7 +121,7 @@ For a long time, Nx has been purposefully versioning and publishing agnostic, bu
|
||||
|
||||
Therefore, we’ve been working on a new command to the Nx CLI: [nx release](/recipes/nx-release/get-started-with-nx-release#get-started-with-nx-release). We launched this on Friday of our Launch Nx week!
|
||||
|
||||
Juri goes into [full details in this blog post](/blog/2024-02-09-versioning-and-releasing-packages), and James Henry — our Director of Engineering and the primary engineer responsible for both maintaining Lerna and creating Nx Release — expands further in his conference talk:
|
||||
Juri goes into [full details in this blog post](/blog/versioning-and-releasing-packages-in-a-monorepo), and James Henry — our Director of Engineering and the primary engineer responsible for both maintaining Lerna and creating Nx Release — expands further in his conference talk:
|
||||
|
||||
### Conference Talk: Releasing Nx Release
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: Monorepos - Why Speed Matters
|
||||
authors: ['Katerina Skroumpelou']
|
||||
slug: 'monorepos-why-speed-matters'
|
||||
authors: ['Katerina Skroumpelou', 'Jeff Cross']
|
||||
tags: nx, nxdevtools, speed, ci
|
||||
cover_image: '/blog/images/2024-03-20/featured_img.png'
|
||||
---
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Reliable CI. A new execution model fixing both flakiness and slowness
|
||||
slug: 'reliable-ci-a-new-execution-model-fixing-both-flakiness-and-slowness'
|
||||
authors: [Victor Savkin]
|
||||
cover_image: '/blog/images/2024-03-21/featured_img.png'
|
||||
tags: [nx, nx-cloud, releases]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Manage Your Gradle Project using Nx
|
||||
slug: 'manage-your-gradle-project-using-nx'
|
||||
authors: ['Emily Xiong']
|
||||
cover_image: '/blog/images/2024-04-19/featured_img.png'
|
||||
tags: [nx, gradle, how-to]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Nx 19.0 Release!!
|
||||
slug: 'nx-19-release'
|
||||
authors: ['Zack DeRose']
|
||||
cover_image: '/blog/images/2024-05-08/nx-19-thumbnail.png'
|
||||
tags: [nx, release]
|
||||
@@ -14,9 +15,9 @@ Hey folks!
|
||||
|
||||
It's been awhile since February, where in the midsts our big launch week, we launched Nx 18. Nx 18 introduced the biggest new enhancement to Nx to date: Project Crystal.
|
||||
|
||||
[](/blog/2024-02-05-nx-18-project-crystal)
|
||||
[](/blog/what-if-nx-plugins-were-more-like-vscode-extensions)
|
||||
|
||||
You can read more on project crystal [here](/blog/2024-02-05-nx-18-project-crystal). The main thing to know though is that Project Crystal adjusts the idea of Nx plugins, so that the mere presence of the plugin in your Nx Workspace removes the need to maintain any configuration for most cases.
|
||||
You can read more on project crystal [here](/blog/what-if-nx-plugins-were-more-like-vscode-extensions). The main thing to know though is that Project Crystal adjusts the idea of Nx plugins, so that the mere presence of the plugin in your Nx Workspace removes the need to maintain any configuration for most cases.
|
||||
|
||||
Nx 18 was a significant departure for us in several ways - maybe the most noteworthy was that we broke our years-long cadence of releasing a new major version every 6 months.
|
||||
|
||||
@@ -38,11 +39,11 @@ Here's the rundown of the major things we'll cover in this update:
|
||||
|
||||
## NEW PLUGIN: @nx/gradle
|
||||
|
||||
[](/blog/2024-04-19-manage-your-gradle)
|
||||
[](/blog/manage-your-gradle-project-using-nx)
|
||||
|
||||
We've launched a new first-party plugin for Gradle!
|
||||
|
||||
Emily, the engineer that worked on this effort, posted an entire article where you can find the full details [here](/blog/2024-04-19-manage-your-gradle).
|
||||
Emily, the engineer that worked on this effort, posted an entire article where you can find the full details [here](/blog/manage-your-gradle-project-using-nx).
|
||||
|
||||
Thanks to the benefits of Project Crystal - which allows us to determine information to create Nx Tasks based on the way your Gradle projects are setup - by adding this plugin, it makes it easy to add Gradle into an existing workspace without needing any Nx-specific configuration.
|
||||
|
||||
@@ -171,7 +172,7 @@ We've got some cool stats to share from our users regarding the benefits of [Nx
|
||||
|
||||
- **speed**: Reported 30% - 70% faster CI
|
||||
- **cost**: Reported 40% - 75% reduction in CI costs
|
||||
- **reliability**: Nx Cloud's automatic detection and retrying of flaky tests makes the issue of flaky tests largely go away entirely. You can read more on [our thoughts on reliability here](/blog/2024-03-21-reliable-ci).
|
||||
- **reliability**: Nx Cloud's automatic detection and retrying of flaky tests makes the issue of flaky tests largely go away entirely. You can read more on [our thoughts on reliability here](/blog/reliable-ci-a-new-execution-model-fixing-both-flakiness-and-slowness).
|
||||
|
||||
In February, we launched two big enhancements to Nx Cloud: the [Atomizer](/ci/features/split-e2e-tasks) and [Nx Agents](https://nx.app/products/agents#content).
|
||||
|
||||
@@ -195,9 +196,9 @@ And! We've revamped our newsletter, written by our CTO, Victor Savkin. [Subscrib
|
||||
|
||||
## NEW CONFERENCE: Monorepo World 2024
|
||||
|
||||
[](https://monorepo.tools/conf)
|
||||
[](https://monorepo.world)
|
||||
|
||||
We've got a new conference coming up: [Monorepo World 2024](https://monorepo.tools/conf)!
|
||||
We've got a new conference coming up: [Monorepo World 2024](https://monorepo.world)!
|
||||
|
||||
This conference will cover all things dealing with monorepos, and we're excited to host it at the Computer History Museum in Mountain View, California this October 7th!
|
||||
|
||||
|
||||
+10
-10
@@ -2,7 +2,7 @@
|
||||
|
||||
Nx 19.0 has landed, and it's ALL about reinforcing 💎Project Crystal💎!!!
|
||||
|
||||
Catch the details in this [blog post](/blog/2024-05-08-nx-19-release) and watch the video below:
|
||||
Catch the details in this [blog post](/blog/nx-19-release) and watch the video below:
|
||||
|
||||
{% youtube
|
||||
src="https://youtu.be/U6eO8-w9DR0"
|
||||
@@ -12,16 +12,16 @@ title="Nx 19.0 Has Landed!!"
|
||||
## Features
|
||||
|
||||
{% cards cols="2" %}
|
||||
{% card title="NEW PLUGIN: @nx/gradle" type="document" url="/blog/2024-05-08-nx-19-release#new-plugin-nxgradle" /%}
|
||||
{% card title="Nx Atomizer Enhancements" type="document" url="/blog/2024-05-08-nx-19-release#nx-atomizer-enhancements" /%}
|
||||
{% card title="Associated Technologies Added To Tasks" type="document" url="/blog/2024-05-08-nx-19-release##associated-technologies-added-to-tasks" /%}
|
||||
{% card title="Generators to Migrate to Project Crystal" type="document" url="/blog/2024-05-08-nx-19-release#generators-to-migrate-to-project-crystal" /%}
|
||||
{% card title="General Crystal Polishing" type="document" url="/blog/2024-05-08-nx-19-release#general-crystal-polishing" /%}
|
||||
{% card title="Nx Cloud Updates!" type="document" url="/blog/2024-05-08-nx-19-release#nx-cloud-updates" /%}
|
||||
{% card title="More Miscellaneous Updates!" type="document" url="/blog/2024-05-08-nx-19-release#more-miscellaneous-updates" /%}
|
||||
{% card title="NEW CONFERENCE: Monorepo World 2024" type="document" url="/blog/2024-05-08-nx-19-release#new-conference-monorepo-world-2024" /%}
|
||||
{% card title="NEW PLUGIN: @nx/gradle" type="document" url="/blog/nx-19-release#new-plugin-nxgradle" /%}
|
||||
{% card title="Nx Atomizer Enhancements" type="document" url="/blog/nx-19-release#nx-atomizer-enhancements" /%}
|
||||
{% card title="Associated Technologies Added To Tasks" type="document" url="/blog/nx-19-release##associated-technologies-added-to-tasks" /%}
|
||||
{% card title="Generators to Migrate to Project Crystal" type="document" url="/blog/nx-19-release#generators-to-migrate-to-project-crystal" /%}
|
||||
{% card title="General Crystal Polishing" type="document" url="/blog/nx-19-release#general-crystal-polishing" /%}
|
||||
{% card title="Nx Cloud Updates!" type="document" url="/blog/nx-19-release#nx-cloud-updates" /%}
|
||||
{% card title="More Miscellaneous Updates!" type="document" url="/blog/nx-19-release#more-miscellaneous-updates" /%}
|
||||
{% card title="NEW CONFERENCE: Monorepo World 2024" type="document" url="/blog/nx-19-release#new-conference-monorepo-world-2024" /%}
|
||||
{% /cards %}
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
[Updating Bundled Environment Variables: `NX_` to `NX_PUBLIC_`](/blog/2024-05-08-nx-19-release#breaking-change-updating-bundled-environment-variables-to)
|
||||
[Updating Bundled Environment Variables: `NX_` to `NX_PUBLIC_`](/blog/nx-19-release#breaking-change-updating-bundled-environment-variables-to)
|
||||
|
||||
@@ -502,7 +502,7 @@
|
||||
},
|
||||
"generators": {
|
||||
"/nx-api/cypress/generators/init": {
|
||||
"description": "Initialize the `@nrwl/cypress` plugin.",
|
||||
"description": "Initialize the `@nx/cypress` plugin.",
|
||||
"file": "generated/packages/cypress/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -591,7 +591,7 @@
|
||||
},
|
||||
"generators": {
|
||||
"/nx-api/detox/generators/init": {
|
||||
"description": "Initialize the `@nrwl/detox` plugin.",
|
||||
"description": "Initialize the `@nx/detox` plugin.",
|
||||
"file": "generated/packages/detox/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -679,7 +679,7 @@
|
||||
},
|
||||
"generators": {
|
||||
"/nx-api/esbuild/generators/init": {
|
||||
"description": "Initialize the `@nrwl/esbuild` plugin.",
|
||||
"description": "Initialize the `@nx/esbuild` plugin.",
|
||||
"file": "generated/packages/esbuild/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -1018,7 +1018,7 @@
|
||||
"executors": {},
|
||||
"generators": {
|
||||
"/nx-api/express/generators/init": {
|
||||
"description": "Initialize the `@nrwl/express` plugin.",
|
||||
"description": "Initialize the `@nx/express` plugin.",
|
||||
"file": "generated/packages/express/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -1114,7 +1114,7 @@
|
||||
},
|
||||
"generators": {
|
||||
"/nx-api/jest/generators/init": {
|
||||
"description": "Initialize the `@nrwl/jest` plugin.",
|
||||
"description": "Initialize the `@nx/jest` plugin.",
|
||||
"file": "generated/packages/jest/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -1300,7 +1300,7 @@
|
||||
"type": "generator"
|
||||
},
|
||||
"/nx-api/nest/generators/init": {
|
||||
"description": "Initialize the `@nrwl/nest` plugin.",
|
||||
"description": "Initialize the `@nx/nest` plugin.",
|
||||
"file": "generated/packages/nest/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -1506,7 +1506,7 @@
|
||||
},
|
||||
"generators": {
|
||||
"/nx-api/next/generators/init": {
|
||||
"description": "Initialize the `@nrwl/next` plugin.",
|
||||
"description": "Initialize the `@nx/next` plugin.",
|
||||
"file": "generated/packages/next/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -1594,7 +1594,7 @@
|
||||
"executors": {},
|
||||
"generators": {
|
||||
"/nx-api/node/generators/init": {
|
||||
"description": "Initialize the `@nrwl/node` plugin.",
|
||||
"description": "Initialize the `@nx/node` plugin.",
|
||||
"file": "generated/packages/node/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -2178,7 +2178,7 @@
|
||||
},
|
||||
"generators": {
|
||||
"/nx-api/react/generators/init": {
|
||||
"description": "Initialize the `@nrwl/react` plugin.",
|
||||
"description": "Initialize the `@nx/react` plugin.",
|
||||
"file": "generated/packages/react/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -2739,7 +2739,7 @@
|
||||
},
|
||||
"generators": {
|
||||
"/nx-api/rollup/generators/init": {
|
||||
"description": "Initialize the `@nrwl/rollup` plugin.",
|
||||
"description": "Initialize the `@nx/rollup` plugin.",
|
||||
"file": "generated/packages/rollup/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -3172,7 +3172,7 @@
|
||||
},
|
||||
"generators": {
|
||||
"/nx-api/webpack/generators/init": {
|
||||
"description": "Initialize the `@nrwl/webpack` plugin.",
|
||||
"description": "Initialize the `@nx/webpack` plugin.",
|
||||
"file": "generated/packages/webpack/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
|
||||
@@ -494,7 +494,7 @@
|
||||
],
|
||||
"generators": [
|
||||
{
|
||||
"description": "Initialize the `@nrwl/cypress` plugin.",
|
||||
"description": "Initialize the `@nx/cypress` plugin.",
|
||||
"file": "generated/packages/cypress/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -582,7 +582,7 @@
|
||||
],
|
||||
"generators": [
|
||||
{
|
||||
"description": "Initialize the `@nrwl/detox` plugin.",
|
||||
"description": "Initialize the `@nx/detox` plugin.",
|
||||
"file": "generated/packages/detox/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -668,7 +668,7 @@
|
||||
],
|
||||
"generators": [
|
||||
{
|
||||
"description": "Initialize the `@nrwl/esbuild` plugin.",
|
||||
"description": "Initialize the `@nx/esbuild` plugin.",
|
||||
"file": "generated/packages/esbuild/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -1003,7 +1003,7 @@
|
||||
"executors": [],
|
||||
"generators": [
|
||||
{
|
||||
"description": "Initialize the `@nrwl/express` plugin.",
|
||||
"description": "Initialize the `@nx/express` plugin.",
|
||||
"file": "generated/packages/express/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -1097,7 +1097,7 @@
|
||||
],
|
||||
"generators": [
|
||||
{
|
||||
"description": "Initialize the `@nrwl/jest` plugin.",
|
||||
"description": "Initialize the `@nx/jest` plugin.",
|
||||
"file": "generated/packages/jest/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -1281,7 +1281,7 @@
|
||||
"type": "generator"
|
||||
},
|
||||
{
|
||||
"description": "Initialize the `@nrwl/nest` plugin.",
|
||||
"description": "Initialize the `@nx/nest` plugin.",
|
||||
"file": "generated/packages/nest/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -1486,7 +1486,7 @@
|
||||
],
|
||||
"generators": [
|
||||
{
|
||||
"description": "Initialize the `@nrwl/next` plugin.",
|
||||
"description": "Initialize the `@nx/next` plugin.",
|
||||
"file": "generated/packages/next/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -1573,7 +1573,7 @@
|
||||
"executors": [],
|
||||
"generators": [
|
||||
{
|
||||
"description": "Initialize the `@nrwl/node` plugin.",
|
||||
"description": "Initialize the `@nx/node` plugin.",
|
||||
"file": "generated/packages/node/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -2152,7 +2152,7 @@
|
||||
],
|
||||
"generators": [
|
||||
{
|
||||
"description": "Initialize the `@nrwl/react` plugin.",
|
||||
"description": "Initialize the `@nx/react` plugin.",
|
||||
"file": "generated/packages/react/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -2710,7 +2710,7 @@
|
||||
],
|
||||
"generators": [
|
||||
{
|
||||
"description": "Initialize the `@nrwl/rollup` plugin.",
|
||||
"description": "Initialize the `@nx/rollup` plugin.",
|
||||
"file": "generated/packages/rollup/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
@@ -3137,7 +3137,7 @@
|
||||
],
|
||||
"generators": [
|
||||
{
|
||||
"description": "Initialize the `@nrwl/webpack` plugin.",
|
||||
"description": "Initialize the `@nx/webpack` plugin.",
|
||||
"file": "generated/packages/webpack/generators/init.json",
|
||||
"hidden": true,
|
||||
"name": "init",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"presets": []
|
||||
},
|
||||
"description": "Initialize the `@nrwl/cypress` plugin.",
|
||||
"description": "Initialize the `@nx/cypress` plugin.",
|
||||
"aliases": ["ng-add"],
|
||||
"hidden": true,
|
||||
"implementation": "/packages/cypress/src/generators/init/init#cypressInitGeneratorInternal.ts",
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"required": [],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Initialize the `@nrwl/detox` plugin.",
|
||||
"description": "Initialize the `@nx/detox` plugin.",
|
||||
"hidden": true,
|
||||
"implementation": "/packages/detox/src/generators/init/init#detoxInitGeneratorInternal.ts",
|
||||
"aliases": [],
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"required": [],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Initialize the `@nrwl/esbuild` plugin.",
|
||||
"description": "Initialize the `@nx/esbuild` plugin.",
|
||||
"aliases": ["ng-add"],
|
||||
"hidden": true,
|
||||
"implementation": "/packages/esbuild/src/generators/init/init#esbuildInitGenerator.ts",
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"required": [],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Initialize the `@nrwl/express` plugin.",
|
||||
"description": "Initialize the `@nx/express` plugin.",
|
||||
"aliases": ["ng-add"],
|
||||
"hidden": true,
|
||||
"implementation": "/packages/express/src/generators/init/init#initGenerator.ts",
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"required": [],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Initialize the `@nrwl/jest` plugin.",
|
||||
"description": "Initialize the `@nx/jest` plugin.",
|
||||
"aliases": ["ng-add"],
|
||||
"hidden": true,
|
||||
"implementation": "/packages/jest/src/generators/init/init#jestInitGeneratorInternal.ts",
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"required": [],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Initialize the `@nrwl/nest` plugin.",
|
||||
"description": "Initialize the `@nx/nest` plugin.",
|
||||
"aliases": ["ng-add"],
|
||||
"hidden": true,
|
||||
"implementation": "/packages/nest/src/generators/init/init.ts",
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
"alias": "p",
|
||||
"x-deprecated": "Provide The `directory` option instead and use the `as-provided` format. the project will be determined from the directory provided. It will be removed in Nx v20."
|
||||
},
|
||||
"nameAndDirectoryFormat": {
|
||||
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
|
||||
"type": "string",
|
||||
"enum": ["as-provided", "derived"]
|
||||
},
|
||||
"directory": {
|
||||
"description": "Directory where the generated files are placed. when `--nameAndDirectoryFormat=as-provided`, it will be relative to the current working directory. otherwise, it will be relative to the workspace root.",
|
||||
"type": "string",
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
"alias": "p",
|
||||
"x-deprecated": "Provide The `directory` option instead and use the `as-provided` format. the project will be determined from the directory provided. It will be removed in Nx v20."
|
||||
},
|
||||
"nameAndDirectoryFormat": {
|
||||
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
|
||||
"type": "string",
|
||||
"enum": ["as-provided", "derived"]
|
||||
},
|
||||
"directory": {
|
||||
"description": "Directory where the generated files are placed. when `--nameAndDirectoryFormat=as-provided`, it will be relative to the current working directory. otherwise, it will be relative to the workspace root.",
|
||||
"type": "string",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"required": [],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Initialize the `@nrwl/next` plugin.",
|
||||
"description": "Initialize the `@nx/next` plugin.",
|
||||
"hidden": true,
|
||||
"implementation": "/packages/next/src/generators/init/init#nextInitGeneratorInternal.ts",
|
||||
"aliases": [],
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"required": [],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Initialize the `@nrwl/node` plugin.",
|
||||
"description": "Initialize the `@nx/node` plugin.",
|
||||
"aliases": ["ng-add"],
|
||||
"hidden": true,
|
||||
"implementation": "/packages/node/src/generators/init/init.ts",
|
||||
|
||||
@@ -28,6 +28,11 @@
|
||||
"type": "boolean",
|
||||
"description": "If the user will be using GitHub as their git hosting provider",
|
||||
"default": false
|
||||
},
|
||||
"directory": {
|
||||
"type": "string",
|
||||
"description": "The directory where the workspace is located",
|
||||
"x-priority": "internal"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
},
|
||||
"workers": {
|
||||
"alias": "j",
|
||||
"type": "string",
|
||||
"oneOf": [{ "type": "number" }, { "type": "string" }],
|
||||
"description": "Number of concurrent workers or percentage of logical CPU cores, use 1 to run in a single worker"
|
||||
},
|
||||
"list": {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"required": [],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Initialize the `@nrwl/react` plugin.",
|
||||
"description": "Initialize the `@nx/react` plugin.",
|
||||
"aliases": ["ng-add"],
|
||||
"hidden": true,
|
||||
"implementation": "/packages/react/src/generators/init/init#reactInitGenerator.ts",
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"required": [],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Initialize the `@nrwl/rollup` plugin.",
|
||||
"description": "Initialize the `@nx/rollup` plugin.",
|
||||
"aliases": ["ng-add"],
|
||||
"hidden": true,
|
||||
"implementation": "/packages/rollup/src/generators/init/init#rollupInitGenerator.ts",
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"required": [],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Initialize the `@nrwl/webpack` plugin.",
|
||||
"description": "Initialize the `@nx/webpack` plugin.",
|
||||
"aliases": ["ng-add"],
|
||||
"hidden": true,
|
||||
"implementation": "/packages/webpack/src/generators/init/init#webpackInitGeneratorInternal.ts",
|
||||
|
||||
@@ -44,13 +44,14 @@ Next, we'll create a blank `nx.json` configuration file for Nx:
|
||||
{}
|
||||
```
|
||||
|
||||
## Add `.nx/cache` to `.gitignore`
|
||||
## Add `.nx` directories to `.gitignore`
|
||||
|
||||
Next, we'll add [the Nx default cache directory](/features/cache-task-results#where-is-the-cache-stored) to the list of files to be ignored by Git. Update the `.gitignore` file adding the `.nx/cache` entry:
|
||||
Next, we'll add [the Nx default task cache directory](/features/cache-task-results#where-is-the-cache-stored) and the project graph cache directory to the list of files to be ignored by Git. Update the `.gitignore` file adding the `.nx/cache` and `.nx/workspace-data` entry:
|
||||
|
||||
```text {% fileName=".gitignore" %}
|
||||
...
|
||||
.nx/cache
|
||||
.nx/workspace-data
|
||||
```
|
||||
|
||||
## Set Up Caching For a Task
|
||||
|
||||
@@ -18,7 +18,7 @@ The default changelog renderer for `nx release` generates a changelog entry for
|
||||
|
||||
## Include All Metadata
|
||||
|
||||
There are a few options available to modify the default changelog renderer output. They can be applied to both `workspaceChangelog` and `projectChangelogs` in exactly the same way. All three options are true by default:
|
||||
There are a few options available to modify the default changelog renderer output. They can be applied to both `workspaceChangelog` and `projectChangelogs` in exactly the same way. All four options are true by default:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -27,6 +27,7 @@ There are a few options available to modify the default changelog renderer outpu
|
||||
"projectChangelogs": {
|
||||
"renderOptions": {
|
||||
"authors": true,
|
||||
"mapAuthorsToGitHubUsernames": true,
|
||||
"commitReferences": true,
|
||||
"versionTitleDate": true
|
||||
}
|
||||
@@ -40,6 +41,12 @@ There are a few options available to modify the default changelog renderer outpu
|
||||
|
||||
Whether the commit authors should be added to the bottom of the changelog in a "Thank You" section. Defaults to `true`.
|
||||
|
||||
#### `mapAuthorsToGitHubUsernames`
|
||||
|
||||
If authors is enabled, controls whether or not to try to map the authors to their GitHub usernames using https://ungh.cc (from https://github.com/unjs/ungh) and the email addresses found in the commits. Defaults to `true`.
|
||||
|
||||
You should disable this option if you don't want to make any external requests to https://ungh.cc
|
||||
|
||||
#### `commitReferences`
|
||||
|
||||
Whether the commit references (such as commit and/or PR links) should be included in the changelog. Defaults to `true`.
|
||||
@@ -59,6 +66,7 @@ If you prefer a more minimalist changelog, you can set all the options to false,
|
||||
"projectChangelogs": {
|
||||
"renderOptions": {
|
||||
"authors": false,
|
||||
"mapAuthorsToGitHubUsernames": false,
|
||||
"commitReferences": false,
|
||||
"versionTitleDate": false
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ Problem: A task is being executed when you expect it to be replayed from the cac
|
||||
- If you're using a version lower than Nx 17.2.0, check:
|
||||
- the target configuration in the project's `project.json` file has `"cache": true` set, or
|
||||
- the target configuration in `nx.json#targetDefaults` has `"cache": true` set, or
|
||||
- the task is listed in `cacheableOperations` defined in `nx.json#tasksRunnerOptions`
|
||||
|
||||
1. Check if the output of your task is changing the inputs of your task
|
||||
|
||||
|
||||
@@ -119,31 +119,6 @@ The following properties affect the way Nx runs tasks and can be set at the root
|
||||
|
||||
You can configure `parallel` in `nx.json`, but you can also set a `--parallel` flag in the terminal `nx run-many -t test --parallel=5`.
|
||||
|
||||
### Multiple Tasks Runners
|
||||
|
||||
Tasks runners are invoked when you run `nx test`, `nx build`, `nx run-many`, `nx affected`, and so on.
|
||||
|
||||
There is a tasks runner created from the task options defined at the root of `nx.json` named `default`. You can register another tasks runner by adding it to `nx.json` like this:
|
||||
|
||||
```json {% fileName="nx.json" %}
|
||||
{
|
||||
"tasksRunnerOptions": {
|
||||
"another": {
|
||||
"runner": "nx/tasks-runners/default",
|
||||
"options": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then you can run a task with the new runner using the `--runner` flag:
|
||||
|
||||
```shell
|
||||
nx run-many -t build --runner=another
|
||||
```
|
||||
|
||||
The official types of `runner` supported by Nx are `"nx/tasks-runners/default"` and `"nx-cloud"`.
|
||||
|
||||
## Default Base
|
||||
|
||||
Tells Nx which base branch to use when calculating affected projects.
|
||||
|
||||
@@ -385,24 +385,12 @@ All libraries that we generate automatically have aliases created in the root-le
|
||||
}
|
||||
```
|
||||
|
||||
Hence we can easily import them into other libraries and our Angular application. As an example, let's create and expose a `ProductListComponent` component from our `libs/products` library. Either create it by hand or run
|
||||
Hence we can easily import them into other libraries and our Angular application. As an example, let's use the pre-generated `ProductsComponent` component from our `libs/products` library.
|
||||
|
||||
```shell
|
||||
nx g @nx/angular:component product-list --directory=libs/products/src/lib/product-list --standalone --export
|
||||
```
|
||||
|
||||
We don't need to implement anything fancy as we just want to learn how to import it into our main Angular application.
|
||||
|
||||
```html {% fileName="libs/products/src/lib/product-list/product-list.component.html" %}
|
||||
<p>product-list works!</p>
|
||||
```
|
||||
|
||||
Make sure the `ProductListComponent` is exported via the `index.ts` file of our `products` library and is listed in the `exports` of the `ProductsModule`. This is our public API with the rest of the workspace. Only export what's really necessary to be usable outside the library itself.
|
||||
You can see that the `ProductsComponent` is exported via the `index.ts` file of our `products` library so that other projects in the repository can use it. This is our public API with the rest of the workspace. Only export what's really necessary to be usable outside the library itself.
|
||||
|
||||
```ts {% fileName="libs/products/src/index.ts" %}
|
||||
export * from './lib/products/products.component';
|
||||
|
||||
export * from './lib/product-list/product-list.component';
|
||||
```
|
||||
|
||||
We're ready to import it into our main application now. First (if you haven't already), let's set up the Angular router. Configure it in the `app.config.ts`.
|
||||
@@ -426,7 +414,7 @@ And in `app.component.html`:
|
||||
<router-outlet></router-outlet>
|
||||
```
|
||||
|
||||
Then we can add the `ProductListComponent` component to our `app.routes.ts` and render it via the routing mechanism whenever a user hits the `/products` route.
|
||||
Then we can add the `ProductsComponent` component to our `app.routes.ts` and render it via the routing mechanism whenever a user hits the `/products` route.
|
||||
|
||||
```ts {% fileName="apps/angular-store/src/app/app.routes.ts" highlightLines=[10,11,12,13,14] %}
|
||||
import { Route } from '@angular/router';
|
||||
@@ -441,7 +429,7 @@ export const appRoutes: Route[] = [
|
||||
{
|
||||
path: 'products',
|
||||
loadComponent: () =>
|
||||
import('@angular-monorepo/products').then((m) => m.ProductListComponent),
|
||||
import('@angular-monorepo/products').then((m) => m.ProductsComponent),
|
||||
},
|
||||
];
|
||||
```
|
||||
@@ -452,8 +440,7 @@ Serving your app (`nx serve angular-store`) and then navigating to `/products` s
|
||||
|
||||
Let's apply the same for our `orders` library.
|
||||
|
||||
- generate a new component `OrderListComponent` in `libs/orders` and export it in the corresponding `index.ts` file
|
||||
- import it into the `app.routes.ts` and render it via the routing mechanism whenever a user hits the `/orders` route
|
||||
- import the `OrdersComponent` from `libs/orders` into the `app.routes.ts` and render it via the routing mechanism whenever a user hits the `/orders` route
|
||||
|
||||
In the end, your `app.routes.ts` should look similar to this:
|
||||
|
||||
@@ -470,12 +457,12 @@ export const appRoutes: Route[] = [
|
||||
{
|
||||
path: 'products',
|
||||
loadComponent: () =>
|
||||
import('@angular-monorepo/products').then((m) => m.ProductListComponent),
|
||||
import('@angular-monorepo/products').then((m) => m.ProductsComponent),
|
||||
},
|
||||
{
|
||||
path: 'orders',
|
||||
loadComponent: () =>
|
||||
import('@angular-monorepo/orders').then((m) => m.OrderListComponent),
|
||||
import('@angular-monorepo/orders').then((m) => m.OrdersComponent),
|
||||
},
|
||||
];
|
||||
```
|
||||
@@ -484,12 +471,12 @@ Let's also show products in the `inventory` app.
|
||||
|
||||
```ts {% fileName="apps/inventory/src/app/app.component.ts" highlightLines=[2,6] %}
|
||||
import { Component } from '@angular/core';
|
||||
import { ProductListComponent } from '@angular-monorepo/products';
|
||||
import { ProductsComponent } from '@angular-monorepo/products';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [ProductListComponent],
|
||||
selector: 'angular-monorepo-root',
|
||||
imports: [ProductsComponent],
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css'],
|
||||
})
|
||||
@@ -499,7 +486,7 @@ export class AppComponent {
|
||||
```
|
||||
|
||||
```ts {% fileName="apps/inventory/src/app/app.component.html" %}
|
||||
<angular-monorepo-product-list></angular-monorepo-product-list>
|
||||
<lib-products></lib-products>
|
||||
```
|
||||
|
||||
## Visualizing your Project Structure
|
||||
@@ -1185,14 +1172,14 @@ To enforce the rules, Nx ships with a custom ESLint rule. Open the `.eslintrc.ba
|
||||
}
|
||||
```
|
||||
|
||||
To test it, go to your `libs/products/src/lib/product-list/product-list.component.ts` file and import the `OrderListComponent` from the `orders` project:
|
||||
To test it, go to your `libs/products/src/lib/product-list/product-list.component.ts` file and import the `OrdersComponent` from the `orders` project:
|
||||
|
||||
```ts {% fileName="libs/products/src/lib/product-list/product-list.component.ts" highlightLines=[4,5] %}
|
||||
import { Component } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
// This import is not allowed 👇
|
||||
import { OrderListComponent } from '@angular-monorepo/orders';
|
||||
import { OrdersComponent } from '@angular-monorepo/orders';
|
||||
|
||||
@Component({
|
||||
selector: 'angular-monorepo-product-list',
|
||||
@@ -1201,7 +1188,7 @@ import { OrderListComponent } from '@angular-monorepo/orders';
|
||||
templateUrl: './product-list.component.html',
|
||||
styleUrls: ['./product-list.component.css'],
|
||||
})
|
||||
export class ProductListComponent {}
|
||||
export class ProductsComponent {}
|
||||
```
|
||||
|
||||
If you lint your workspace you'll get an error now:
|
||||
@@ -1213,7 +1200,7 @@ NX Running target lint for 7 projects
|
||||
|
||||
/Users/isaac/Documents/code/nx-recipes/angular-monorepo/libs/products/src/lib/product-list/product-list.component.ts
|
||||
5:1 error A project tagged with "scope:products" can only depend on libs tagged with "scope:products", "scope:shared" @nx/enforce-module-boundaries
|
||||
5:10 warning 'OrderListComponent' is defined but never used @typescript-eslint/no-unused-vars
|
||||
5:10 warning 'OrdersComponent' is defined but never used @typescript-eslint/no-unused-vars
|
||||
|
||||
✖ 2 problems (1 error, 1 warning)
|
||||
|
||||
|
||||
@@ -302,7 +302,7 @@ First, let's delete the `outputs` array from `nx.json` so that we don't override
|
||||
Now let's add the `@nx/vite` plugin:
|
||||
|
||||
```{% command="npx nx add @nx/vite" path="~/tuskydesign" %}
|
||||
✔ Installing @nx/vite@18.1.0...
|
||||
✔ Installing @nx/vite...
|
||||
✔ Initializing @nx/vite...
|
||||
|
||||
NX Package @nx/vite added successfully.
|
||||
@@ -311,13 +311,13 @@ Now let's add the `@nx/vite` plugin:
|
||||
The `nx add` command installs the version of the plugin that matches your repo's Nx version and runs that plugin's initialization script. For `@nx/vite`, the initialization script registers the plugin in the `plugins` array of `nx.json` and updates any `package.json` scripts that execute Vite related tasks. Open the project details view for the `demo` app and look at the `build` task.
|
||||
|
||||
```shell {% path="~/tuskydesigns" %}
|
||||
npx nx show project @tuskdesign/demo --web
|
||||
npx nx show project @tuskdesign/demo
|
||||
```
|
||||
|
||||
{% project-details title="Project Details View" jsonFile="shared/tutorials/npm-workspaces-pdv.json" %}
|
||||
{% /project-details %}
|
||||
|
||||
If you hover over the settings for the `build` task, you can see where those settings come from. The `inputs` and `outputs` are defined by the `@nx/vite` plugin from the `vite.config.ts` file where as the `dependsOn` property we set earlier in the tutorial in the `targetDefaults` in the `nx.json` file.
|
||||
If you hover over the settings for the `vite:build` task, you can see where those settings come from. The `inputs` and `outputs` are defined by the `@nx/vite` plugin from the `vite.config.ts` file where as the `dependsOn` property we set earlier in the tutorial in the `targetDefaults` in the `nx.json` file.
|
||||
|
||||
Now let's change where the `build` results are output to in the `vite.config.ts` file.
|
||||
|
||||
@@ -403,7 +403,7 @@ This generator creates a `.github/workflows/ci.yml` file that contains a CI pipe
|
||||
The key line in the CI pipeline is:
|
||||
|
||||
```yml
|
||||
- run: npx nx affected -t lint test build e2e-ci
|
||||
- run: npx nx affected -t lint test build
|
||||
```
|
||||
|
||||
### Connect to Nx Cloud
|
||||
|
||||
@@ -98,7 +98,7 @@ Let's name the initial application `react-store`. In this tutorial we're going t
|
||||
The setup includes..
|
||||
|
||||
- a new React application (`apps/react-store/`)
|
||||
- a Cypress based set of e2e tests (`apps/react-store-e2e/`)
|
||||
- a Playwright based set of e2e tests (`apps/react-store-e2e/`)
|
||||
- Prettier preconfigured
|
||||
- ESLint preconfigured
|
||||
- Jest preconfigured
|
||||
@@ -128,7 +128,7 @@ Nx uses the following syntax to run tasks:
|
||||
Nx identifies available tasks for your project from [tooling configuration files](/concepts/inferred-tasks), `package.json` scripts and the targets defined in `project.json`. To view the tasks that Nx has detected, look in the [Nx Console](/getting-started/editor-setup) project detail view or run:
|
||||
|
||||
```shell
|
||||
nx show project react-store --web
|
||||
nx show project react-store
|
||||
```
|
||||
|
||||
{% project-details title="Project Details View (Simplified)" height="100px" %}
|
||||
@@ -137,13 +137,9 @@ nx show project react-store --web
|
||||
{
|
||||
"project": {
|
||||
"name": "react-store",
|
||||
"type": "app",
|
||||
"data": {
|
||||
"metadata": {
|
||||
"technologies": ["react"]
|
||||
},
|
||||
"root": "apps/react-store",
|
||||
"includedScripts": [],
|
||||
"name": "react-store",
|
||||
"targets": {
|
||||
"build": {
|
||||
"options": {
|
||||
@@ -161,15 +157,13 @@ nx show project react-store --web
|
||||
],
|
||||
"outputs": ["{workspaceRoot}/dist/apps/react-store"],
|
||||
"executor": "nx:run-commands",
|
||||
"configurations": {},
|
||||
"metadata": {
|
||||
"technologies": ["vite"]
|
||||
}
|
||||
"configurations": {}
|
||||
}
|
||||
},
|
||||
"name": "react-store",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "apps/react-store/src",
|
||||
"projectType": "application",
|
||||
"$schema": "node_modules/nx/schemas/project-schema.json",
|
||||
"tags": [],
|
||||
"implicitDependencies": []
|
||||
}
|
||||
@@ -433,35 +427,12 @@ All libraries that we generate automatically have aliases created in the root-le
|
||||
}
|
||||
```
|
||||
|
||||
Hence we can easily import them into other libraries and our React application. As an example, let's create and expose a `ProductList` component from our `libs/products` library. Either create it by hand or run
|
||||
Hence we can easily import them into other libraries and our Angular application. As an example, let's use the pre-generated `ProductsComponent` component from our `libs/products` library.
|
||||
|
||||
```shell
|
||||
nx g @nx/react:component product-list --project=products --directory="libs/products/src/lib/product-list"
|
||||
```
|
||||
|
||||
We don't need to implement anything fancy as we just want to learn how to import it into our main React application.
|
||||
|
||||
```tsx {% fileName="libs/products/src/lib/product-list/product-list.tsx" %}
|
||||
import styles from './product-list.module.css';
|
||||
|
||||
/* eslint-disable-next-line */
|
||||
export interface ProductListProps {}
|
||||
|
||||
export function ProductList(props: ProductListProps) {
|
||||
return (
|
||||
<div className={styles['container']}>
|
||||
<h1>Welcome to ProductList!</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProductList;
|
||||
```
|
||||
|
||||
Make sure the `ProductList` is exported via the `index.ts` file of our `products` library. This is our public API with the rest of the workspace. Only export what's really necessary to be usable outside the library itself.
|
||||
You can see that the `Products` component is exported via the `index.ts` file of our `products` library so that other projects in the repository can use it. This is our public API with the rest of the workspace. Only export what's really necessary to be usable outside the library itself.
|
||||
|
||||
```ts {% fileName="libs/products/src/index.ts" %}
|
||||
export * from './lib/product-list/product-list';
|
||||
export * from './lib/products';
|
||||
```
|
||||
|
||||
We're ready to import it into our main application now. First (if you haven't already), let's set up React Router.
|
||||
@@ -512,13 +483,13 @@ root.render(
|
||||
);
|
||||
```
|
||||
|
||||
Then we can import the `ProductList` component into our `app.tsx` and render it via the routing mechanism whenever a user hits the `/products` route.
|
||||
Then we can import the `Products` component into our `app.tsx` and render it via the routing mechanism whenever a user hits the `/products` route.
|
||||
|
||||
```tsx {% fileName="apps/react-store/src/app/app.tsx" %}
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
|
||||
// importing the component from the library
|
||||
import { ProductList } from '@react-monorepo/products';
|
||||
import { Products } from '@react-monorepo/products';
|
||||
|
||||
function Home() {
|
||||
return <h1>Home</h1>;
|
||||
@@ -528,7 +499,7 @@ export function App() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />}></Route>
|
||||
<Route path="/products" element={<ProductList />}></Route>
|
||||
<Route path="/products" element={<Products />}></Route>
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
@@ -542,15 +513,14 @@ Serving your app (`nx serve react-store`) and then navigating to `/products` sho
|
||||
|
||||
Let's apply the same for our `orders` library.
|
||||
|
||||
- generate a new component `OrderList` in `libs/orders` and export it in the corresponding `index.ts` file
|
||||
- import it into the `app.tsx` and render it via the routing mechanism whenever a user hits the `/orders` route
|
||||
- import the `Orders` component from `libs/orders` into the `app.tsx` and render it via the routing mechanism whenever a user hits the `/orders` route
|
||||
|
||||
In the end, your `app.tsx` should look similar to this:
|
||||
|
||||
```tsx {% fileName="apps/react-store/src/app/app.tsx" %}
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import { ProductList } from '@react-monorepo/products';
|
||||
import { OrderList } from '@react-monorepo/orders';
|
||||
import { Products } from '@react-monorepo/products';
|
||||
import { Orders } from '@react-monorepo/orders';
|
||||
|
||||
function Home() {
|
||||
return <h1>Home</h1>;
|
||||
@@ -560,8 +530,8 @@ export function App() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />}></Route>
|
||||
<Route path="/products" element={<ProductList />}></Route>
|
||||
<Route path="/orders" element={<OrderList />}></Route>
|
||||
<Route path="/products" element={<Products />}></Route>
|
||||
<Route path="/orders" element={<Orders />}></Route>
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
@@ -572,10 +542,10 @@ export default App;
|
||||
Let's also show products in the `inventory` app.
|
||||
|
||||
```tsx {% fileName="apps/inventory/src/app/app.tsx" %}
|
||||
import { ProductList } from '@react-monorepo/products';
|
||||
import { Products } from '@react-monorepo/products';
|
||||
|
||||
export function App() {
|
||||
return <ProductList />;
|
||||
return <Products />;
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -735,22 +705,19 @@ git commit -a -m "some commit message"
|
||||
|
||||
And then make a small change to the `products` library.
|
||||
|
||||
```tsx {% fileName="libs/products/src/lib/product-list/product-list.tsx" %}
|
||||
import styles from './product-list.module.css';
|
||||
```tsx {% fileName="libs/products/src/lib/products.tsx" %}
|
||||
import styles from './products.module.css';
|
||||
|
||||
/* eslint-disable-next-line */
|
||||
export interface ProductListProps {}
|
||||
|
||||
export function ProductList(props: ProductListProps) {
|
||||
export function Products() {
|
||||
return (
|
||||
<div className={styles['container']}>
|
||||
<h1>Welcome to ProductList!</h1>
|
||||
<h1>Welcome to Products!</h1>
|
||||
<p>This is a change. 👋</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProductList;
|
||||
export default Products;
|
||||
```
|
||||
|
||||
One of the key features of Nx in a monorepo setting is that you're able to run tasks only for projects that are actually affected by the code changes that you've made. To run the tests for only the projects affected by this change, run:
|
||||
@@ -970,10 +937,6 @@ To enforce the rules, Nx ships with a custom ESLint rule. Open the `.eslintrc.ba
|
||||
"enforceBuildableLibDependency": true,
|
||||
"allow": [],
|
||||
"depConstraints": [
|
||||
{
|
||||
"sourceTag": "*",
|
||||
"onlyDependOnLibsWithTags": ["*"]
|
||||
},
|
||||
{
|
||||
"sourceTag": "type:feature",
|
||||
"onlyDependOnLibsWithTags": ["type:feature", "type:ui"]
|
||||
@@ -1008,27 +971,24 @@ To enforce the rules, Nx ships with a custom ESLint rule. Open the `.eslintrc.ba
|
||||
}
|
||||
```
|
||||
|
||||
To test it, go to your `libs/products/src/lib/product-list/product-list.tsx` file and import the `OrderList` from the `orders` project:
|
||||
To test it, go to your `libs/products/src/lib/products.tsx` file and import the `Orders` component from the `orders` project:
|
||||
|
||||
```tsx {% fileName="libs/products/src/lib/product-list/product-list.tsx" %}
|
||||
import styles from './product-list.module.css';
|
||||
```tsx {% fileName="libs/products/src/lib/products.tsx" %}
|
||||
import styles from './products.module.css';
|
||||
|
||||
// This import is not allowed 👇
|
||||
import { OrderList } from '@react-monorepo/orders';
|
||||
import { Orders } from '@react-monorepo/orders';
|
||||
|
||||
/* eslint-disable-next-line */
|
||||
export interface ProductListProps {}
|
||||
|
||||
export function ProductList(props: ProductListProps) {
|
||||
export function Products() {
|
||||
return (
|
||||
<div className={styles['container']}>
|
||||
<h1>Welcome to ProductList!</h1>
|
||||
<OrderList />
|
||||
<h1>Welcome to Products!</h1>
|
||||
<p>This is a change. 👋</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProductList;
|
||||
export default Products;
|
||||
```
|
||||
|
||||
If you lint your workspace you'll get an error now:
|
||||
@@ -1038,9 +998,9 @@ If you lint your workspace you'll get an error now:
|
||||
✖ nx run products:lint
|
||||
Linting "products"...
|
||||
|
||||
/Users/isaac/Documents/code/nx-recipes/react-monorepo/libs/products/src/lib/product-list/product-list.tsx
|
||||
/Users/isaac/Documents/code/nx-recipes/react-monorepo/libs/products/src/lib/products.tsx
|
||||
4:1 error A project tagged with "scope:products" can only depend on libs tagged with "scope:products", "scope:shared" @nx/enforce-module-boundaries
|
||||
4:10 warning 'OrderList' is defined but never used @typescript-eslint/no-unused-vars
|
||||
4:10 warning 'Orders' is defined but never used @typescript-eslint/no-unused-vars
|
||||
|
||||
✖ 2 problems (1 error, 1 warning)
|
||||
|
||||
|
||||
@@ -146,12 +146,26 @@ describe('@nx/expo (legacy)', () => {
|
||||
|
||||
it('should install', async () => {
|
||||
// run install command
|
||||
const installResults = await runCLIAsync(
|
||||
let installResults = await runCLIAsync(
|
||||
`install ${appName} --no-interactive`
|
||||
);
|
||||
expect(installResults.combinedOutput).toContain(
|
||||
'Successfully ran target install'
|
||||
);
|
||||
|
||||
installResults = await runCLIAsync(
|
||||
`install ${appName} --packages=@react-native-async-storage/async-storage,react-native-image-picker --no-interactive`
|
||||
);
|
||||
expect(installResults.combinedOutput).toContain(
|
||||
'Successfully ran target install'
|
||||
);
|
||||
const packageJson = readJson(join('apps', appName, 'package.json'));
|
||||
expect(packageJson).toMatchObject({
|
||||
dependencies: {
|
||||
'@react-native-async-storage/async-storage': '*',
|
||||
'react-native-image-picker': '*',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should start', async () => {
|
||||
|
||||
@@ -112,6 +112,30 @@ describe('@nx/expo', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should install', async () => {
|
||||
// run install command
|
||||
let installResults = await runCLIAsync(
|
||||
`install ${appName} --no-interactive`
|
||||
);
|
||||
expect(installResults.combinedOutput).toContain(
|
||||
'Successfully ran target install'
|
||||
);
|
||||
|
||||
installResults = await runCLIAsync(
|
||||
`install ${appName} --packages=@react-native-async-storage/async-storage,react-native-image-picker --no-interactive`
|
||||
);
|
||||
expect(installResults.combinedOutput).toContain(
|
||||
'Successfully ran target install'
|
||||
);
|
||||
const packageJson = readJson(join(appName, 'package.json'));
|
||||
expect(packageJson).toMatchObject({
|
||||
dependencies: {
|
||||
'@react-native-async-storage/async-storage': '*',
|
||||
'react-native-image-picker': '*',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should run e2e for cypress', async () => {
|
||||
if (runE2ETests()) {
|
||||
const results = runCLI(`e2e ${appName}-e2e`);
|
||||
|
||||
+50
-36
@@ -2,7 +2,6 @@ import { parseJson } from '@nx/devkit';
|
||||
import {
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
getSelectedPackageManager,
|
||||
isNotWindows,
|
||||
newProject,
|
||||
readFile,
|
||||
@@ -293,49 +292,64 @@ describe('Extra Nx Misc Tests', () => {
|
||||
});
|
||||
|
||||
describe('Env File', () => {
|
||||
it('should have the right env', () => {
|
||||
const appName = uniq('app');
|
||||
const libName = uniq('lib');
|
||||
|
||||
beforeAll(() => {
|
||||
runCLI(
|
||||
`generate @nx/react:app ${appName} --style=css --bundler=webpack --no-interactive`
|
||||
`generate @nx/js:lib ${libName} --bundler=none --unitTestRunner=none --no-interactive`
|
||||
);
|
||||
});
|
||||
|
||||
it('should have the right env', () => {
|
||||
updateFile(
|
||||
'.env',
|
||||
`FIRSTNAME="firstname"
|
||||
LASTNAME="lastname"
|
||||
NX_USERNAME=$FIRSTNAME $LASTNAME`
|
||||
LASTNAME="lastname"
|
||||
NX_USERNAME=$FIRSTNAME $LASTNAME`
|
||||
);
|
||||
updateFile(
|
||||
`apps/${appName}/src/app/app.tsx`,
|
||||
`
|
||||
import NxWelcome from './nx-welcome';
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<>
|
||||
<NxWelcome title={process.env.NX_USERNAME} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
`
|
||||
);
|
||||
updateFile(
|
||||
`apps/${appName}/src/app/app.spec.tsx`,
|
||||
`import { render } from '@testing-library/react';
|
||||
|
||||
import App from './app';
|
||||
|
||||
describe('App', () => {
|
||||
it('should have a greeting as the title', () => {
|
||||
const { getByText } = render(<App />);
|
||||
expect(getByText(/Welcome firstname lastname/gi)).toBeTruthy();
|
||||
updateJson(join('libs', libName, 'project.json'), (config) => {
|
||||
config.targets.echo = {
|
||||
command: 'echo $NX_USERNAME',
|
||||
};
|
||||
return config;
|
||||
});
|
||||
let result = runCLI(`run ${libName}:echo`);
|
||||
expect(result).toContain('firstname lastname');
|
||||
|
||||
updateFile('.env', (content) => {
|
||||
content = content.replace('firstname', 'firstname2');
|
||||
content = content.replace('lastname', 'lastname2');
|
||||
return content;
|
||||
});
|
||||
result = runCLI(`run ${libName}:echo`);
|
||||
expect(result).toContain('firstname2 lastname2');
|
||||
});
|
||||
`
|
||||
);
|
||||
const unitTestsOutput = runCLI(`test ${appName}`);
|
||||
expect(unitTestsOutput).toContain('Successfully ran target test');
|
||||
|
||||
it('should work with custom env file', () => {
|
||||
updateFile(`libs/${libName}/.custom1.env`, `hello="hello1"`);
|
||||
updateFile(`libs/${libName}/.custom2.env`, `hello="hello2"`);
|
||||
updateJson(join('libs', libName, 'project.json'), (config) => {
|
||||
config.targets.hello1 = {
|
||||
command: 'echo $hello',
|
||||
options: {
|
||||
envFile: `libs/${libName}/.custom1.env`,
|
||||
},
|
||||
};
|
||||
config.targets.hello2 = {
|
||||
command: 'echo $hello',
|
||||
options: {
|
||||
envFile: `libs/${libName}/.custom2.env`,
|
||||
},
|
||||
};
|
||||
return config;
|
||||
});
|
||||
let result = runCLI(`run ${libName}:hello1`);
|
||||
expect(result).toContain('hello1');
|
||||
result = runCLI(`run ${libName}:hello2`);
|
||||
expect(result).toContain('hello2');
|
||||
result = runCLI(`run-many --target=hello1,hello2`);
|
||||
expect(result).toContain('hello1');
|
||||
expect(result).toContain('hello2');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -8,15 +8,34 @@ import {
|
||||
uniq,
|
||||
updateFile,
|
||||
runCommandAsync,
|
||||
listFiles,
|
||||
} from '@nx/e2e/utils';
|
||||
|
||||
describe('Remix E2E Tests', () => {
|
||||
describe('--integrated', () => {
|
||||
let proj: string;
|
||||
|
||||
describe('--integrated (npm)', () => {
|
||||
beforeAll(() => {
|
||||
proj = newProject({ packages: ['@nx/remix', '@nx/react'] });
|
||||
newProject({
|
||||
packages: ['@nx/remix', '@nx/react'],
|
||||
packageManager: 'npm',
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
killPorts();
|
||||
cleanupProject();
|
||||
});
|
||||
|
||||
it('should not cause peer dependency conflicts', async () => {
|
||||
const plugin = uniq('remix');
|
||||
runCLI(
|
||||
`generate @nx/remix:app ${plugin} --projectNameAndRootFormat=as-provided`
|
||||
);
|
||||
|
||||
await runCommandAsync('npm install');
|
||||
}, 120000);
|
||||
});
|
||||
describe('--integrated (yarn)', () => {
|
||||
beforeAll(() => {
|
||||
newProject({ packages: ['@nx/remix', '@nx/react'] });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
fetchProjectGraph,
|
||||
getProjectGraphDataService,
|
||||
useEnvironmentConfig,
|
||||
useIntervalWhen,
|
||||
usePoll,
|
||||
useRouteConstructor,
|
||||
} from '@nx/graph/shared';
|
||||
import {
|
||||
@@ -295,24 +295,23 @@ export function ProjectsSidebar(): JSX.Element {
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
useIntervalWhen(
|
||||
() => {
|
||||
fetchProjectGraph(
|
||||
usePoll(
|
||||
async () => {
|
||||
const response: ProjectGraphClientResponse = await fetchProjectGraph(
|
||||
projectGraphDataService,
|
||||
params,
|
||||
environmentConfig.appConfig
|
||||
).then((response: ProjectGraphClientResponse) => {
|
||||
if (response.hash === lastHash) {
|
||||
return;
|
||||
}
|
||||
projectGraphService.send({
|
||||
type: 'updateGraph',
|
||||
projects: response.projects,
|
||||
dependencies: response.dependencies,
|
||||
fileMap: response.fileMap,
|
||||
});
|
||||
setLastHash(response.hash);
|
||||
);
|
||||
if (response.hash === lastHash) {
|
||||
return;
|
||||
}
|
||||
projectGraphService.send({
|
||||
type: 'updateGraph',
|
||||
projects: response.projects,
|
||||
dependencies: response.dependencies,
|
||||
fileMap: response.fileMap,
|
||||
});
|
||||
setLastHash(response.hash);
|
||||
},
|
||||
5000,
|
||||
environmentConfig.watch
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
fetchProjectGraph,
|
||||
getProjectGraphDataService,
|
||||
useEnvironmentConfig,
|
||||
useIntervalWhen,
|
||||
usePoll,
|
||||
} from '@nx/graph/shared';
|
||||
import { Dropdown, Spinner } from '@nx/graph/ui-components';
|
||||
import { getSystemTheme, Theme, ThemePanel } from '@nx/graph/ui-theme';
|
||||
@@ -71,15 +71,15 @@ export function Shell(): JSX.Element {
|
||||
useLayoutEffect(() => {
|
||||
setErrors(routerErrors);
|
||||
}, [routerErrors]);
|
||||
useIntervalWhen(
|
||||
() => {
|
||||
fetchProjectGraph(
|
||||
|
||||
usePoll(
|
||||
async () => {
|
||||
const response: ProjectGraphClientResponse = await fetchProjectGraph(
|
||||
projectGraphDataService,
|
||||
params,
|
||||
environmentConfig.appConfig
|
||||
).then((response: ProjectGraphClientResponse) => {
|
||||
setErrors(response.errors);
|
||||
});
|
||||
);
|
||||
setErrors(response.errors);
|
||||
},
|
||||
1000,
|
||||
environmentConfig.watch
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
fetchProjectGraph,
|
||||
getProjectGraphDataService,
|
||||
useEnvironmentConfig,
|
||||
useIntervalWhen,
|
||||
usePoll,
|
||||
} from '@nx/graph/shared';
|
||||
import { ErrorRenderer } from '@nx/graph/ui-components';
|
||||
import {
|
||||
@@ -23,20 +23,20 @@ export function ErrorBoundary() {
|
||||
const hasErrorData =
|
||||
isRouteErrorResponse(error) && error.data.errors?.length > 0;
|
||||
|
||||
useIntervalWhen(
|
||||
usePoll(
|
||||
async () => {
|
||||
fetchProjectGraph(projectGraphDataService, params, appConfig).then(
|
||||
(data) => {
|
||||
if (
|
||||
isRouteErrorResponse(error) &&
|
||||
error.data.id === 'project-not-found' &&
|
||||
data.projects.find((p) => p.name === error.data.projectName)
|
||||
) {
|
||||
window.location.reload();
|
||||
}
|
||||
return;
|
||||
}
|
||||
const data = await fetchProjectGraph(
|
||||
projectGraphDataService,
|
||||
params,
|
||||
appConfig
|
||||
);
|
||||
if (
|
||||
isRouteErrorResponse(error) &&
|
||||
error.data.id === 'project-not-found' &&
|
||||
data.projects.find((p) => p.name === error.data.projectName)
|
||||
) {
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
1000,
|
||||
watch
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
fetchProjectGraph,
|
||||
getProjectGraphDataService,
|
||||
useEnvironmentConfig,
|
||||
useIntervalWhen,
|
||||
usePoll,
|
||||
} from '@nx/graph/shared';
|
||||
import { ProjectDetailsHeader } from './project-details-header';
|
||||
|
||||
@@ -35,16 +35,16 @@ export function ProjectDetailsPage() {
|
||||
const projectGraphDataService = getProjectGraphDataService();
|
||||
const params = useParams();
|
||||
|
||||
useIntervalWhen(
|
||||
usePoll(
|
||||
async () => {
|
||||
fetchProjectGraph(projectGraphDataService, params, appConfig).then(
|
||||
(data) => {
|
||||
if (data?.hash !== hash) {
|
||||
window.location.reload();
|
||||
}
|
||||
return;
|
||||
}
|
||||
const data = await fetchProjectGraph(
|
||||
projectGraphDataService,
|
||||
params,
|
||||
appConfig
|
||||
);
|
||||
if (data?.hash !== hash) {
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
1000,
|
||||
watch
|
||||
|
||||
@@ -50,7 +50,8 @@ export function ProjectDetailsWrapper({
|
||||
navigate(
|
||||
routeConstructor(
|
||||
`/projects/${encodeURIComponent(data.projectName)}`,
|
||||
true
|
||||
true,
|
||||
['expanded'] // omit expanded targets from search params
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -75,7 +76,8 @@ export function ProjectDetailsWrapper({
|
||||
pathname: `/tasks/${encodeURIComponent(data.targetName)}`,
|
||||
search: `?projects=${encodeURIComponent(data.projectName)}`,
|
||||
},
|
||||
true
|
||||
true,
|
||||
['expanded'] // omit expanded targets from search params
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -95,9 +97,9 @@ export function ProjectDetailsWrapper({
|
||||
|
||||
const updateSearchParams = (
|
||||
params: URLSearchParams,
|
||||
targetNames: string[]
|
||||
targetNames?: string[]
|
||||
) => {
|
||||
if (targetNames.length === 0) {
|
||||
if (!targetNames || targetNames.length === 0) {
|
||||
params.delete('expanded');
|
||||
} else {
|
||||
params.set('expanded', targetNames.join(','));
|
||||
@@ -118,13 +120,6 @@ export function ProjectDetailsWrapper({
|
||||
if (collapseAllTargets) {
|
||||
collapseAllTargets();
|
||||
}
|
||||
setSearchParams(
|
||||
(currentSearchParams) => {
|
||||
currentSearchParams.delete('expanded');
|
||||
return currentSearchParams;
|
||||
},
|
||||
{ replace: true, preventScrollReset: true }
|
||||
);
|
||||
};
|
||||
}, []); // only run on mount
|
||||
|
||||
@@ -132,7 +127,7 @@ export function ProjectDetailsWrapper({
|
||||
const expandedTargetsParams =
|
||||
searchParams.get('expanded')?.split(',') || [];
|
||||
|
||||
if (expandedTargetsParams.join(',') === expandedTargets.join(',')) {
|
||||
if (expandedTargetsParams.join(',') === expandedTargets?.join(',')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ export * from './lib/external-api-service';
|
||||
export * from './lib/use-environment-config';
|
||||
export * from './lib/app-config';
|
||||
export * from './lib/use-route-constructor';
|
||||
export * from './lib/use-interval-when';
|
||||
export * from './lib/use-poll';
|
||||
export * from './lib/project-graph-data-service/get-project-graph-data-service';
|
||||
export * from './lib/fetch-project-graph';
|
||||
export * from './lib/error-toast';
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
export const useIntervalWhen = (
|
||||
callback: () => void,
|
||||
delay: number,
|
||||
condition: boolean
|
||||
) => {
|
||||
const savedCallback = useRef(() => {});
|
||||
|
||||
useEffect(() => {
|
||||
if (condition) {
|
||||
savedCallback.current = callback;
|
||||
}
|
||||
}, [callback, condition]);
|
||||
|
||||
useEffect(() => {
|
||||
if (condition) {
|
||||
const tick = () => {
|
||||
savedCallback.current();
|
||||
};
|
||||
|
||||
if (delay !== null) {
|
||||
let id = setInterval(tick, delay);
|
||||
return () => clearInterval(id);
|
||||
}
|
||||
}
|
||||
}, [delay, condition]);
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
export const usePoll = (
|
||||
callback: () => Promise<void>,
|
||||
delay: number,
|
||||
condition: boolean
|
||||
) => {
|
||||
const savedCallback = useRef(() => Promise.resolve());
|
||||
|
||||
useEffect(() => {
|
||||
if (condition) {
|
||||
savedCallback.current = callback;
|
||||
}
|
||||
}, [callback, condition]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!condition) {
|
||||
return;
|
||||
}
|
||||
let timeoutId: NodeJS.Timeout;
|
||||
|
||||
async function callTickAfterDelay() {
|
||||
await savedCallback.current();
|
||||
if (delay !== null) {
|
||||
timeoutId = setTimeout(callTickAfterDelay, delay);
|
||||
}
|
||||
}
|
||||
|
||||
callTickAfterDelay();
|
||||
|
||||
return () => {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
};
|
||||
}, [delay, condition]);
|
||||
};
|
||||
@@ -3,13 +3,23 @@ import { getEnvironmentConfig } from './use-environment-config';
|
||||
|
||||
export const useRouteConstructor = (): ((
|
||||
to: To,
|
||||
retainSearchParams: boolean
|
||||
retainSearchParams: boolean,
|
||||
searchParamsKeysToOmit?: string[]
|
||||
) => To) => {
|
||||
const { environment } = getEnvironmentConfig();
|
||||
const { selectedWorkspaceId } = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
return (to: To, retainSearchParams: true) => {
|
||||
return (
|
||||
to: To,
|
||||
retainSearchParams: boolean = true,
|
||||
searchParamsKeysToOmit: string[] = []
|
||||
) => {
|
||||
if (searchParamsKeysToOmit?.length) {
|
||||
searchParamsKeysToOmit.forEach((key) => {
|
||||
searchParams.delete(key);
|
||||
});
|
||||
}
|
||||
let pathname = '';
|
||||
|
||||
if (typeof to === 'object') {
|
||||
|
||||
@@ -103,13 +103,10 @@ export function sourcesRenderer(
|
||||
}
|
||||
}
|
||||
return (
|
||||
<span
|
||||
className="group/line shrink-1 flex flex min-w-0"
|
||||
key={`code-group${idx}`}
|
||||
>
|
||||
<span className="group/line flex" key={`code-group${idx}`}>
|
||||
<span>{element}</span>
|
||||
{sourceElement && (
|
||||
<span className="shrink-1 inline flex min-w-0 pl-2 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100">
|
||||
<span className="min-w-0 flex-1 pl-2 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100">
|
||||
{sourceElement}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -37,8 +37,8 @@ export class GraphTooltipService {
|
||||
if (graph.getTaskInputs) {
|
||||
graph.getTaskInputs(event.data.id).then((inputs) => {
|
||||
if (
|
||||
this.currentTooltip.type === 'taskNode' &&
|
||||
this.currentTooltip.props.id === event.data.id
|
||||
this.currentTooltip?.type === 'taskNode' &&
|
||||
this.currentTooltip?.props.id === event.data.id
|
||||
) {
|
||||
this.openTaskNodeTooltip(event.ref, {
|
||||
...event.data,
|
||||
|
||||
@@ -835,3 +835,64 @@ export const Cart = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// We're normalizing `type` from `projectType`, so if projectType is missing we'll fallback to `type`.
|
||||
// See: packages/nx/src/project-graph/utils/normalize-project-nodes.ts
|
||||
export const FallbackType = {
|
||||
args: {
|
||||
project: {
|
||||
name: 'mypkg',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: '.',
|
||||
name: 'mypkg',
|
||||
targets: {
|
||||
echo: {
|
||||
executor: 'nx:run-script',
|
||||
metadata: {
|
||||
scriptContent: 'echo 1',
|
||||
runCommand: 'npm run echo',
|
||||
},
|
||||
options: {
|
||||
script: 'echo',
|
||||
},
|
||||
configurations: {},
|
||||
},
|
||||
},
|
||||
sourceRoot: '.',
|
||||
implicitDependencies: [],
|
||||
tags: [],
|
||||
},
|
||||
},
|
||||
sourceMap: {
|
||||
root: ['nx/core/project-json', 'project.json'],
|
||||
name: ['nx/core/project-json', 'project.json'],
|
||||
targets: ['nx/core/package-json', 'project.json'],
|
||||
'targets.echo': ['nx/core/package-json-workspaces', 'package.json'],
|
||||
'targets.echo.executor': [
|
||||
'nx/core/package-json-workspaces',
|
||||
'package.json',
|
||||
],
|
||||
'targets.echo.options': [
|
||||
'nx/core/package-json-workspaces',
|
||||
'package.json',
|
||||
],
|
||||
'targets.echo.metadata': [
|
||||
'nx/core/package-json-workspaces',
|
||||
'package.json',
|
||||
],
|
||||
'targets.echo.options.script': [
|
||||
'nx/core/package-json-workspaces',
|
||||
'package.json',
|
||||
],
|
||||
'targets.echo.metadata.scriptContent': [
|
||||
'nx/core/package-json-workspaces',
|
||||
'package.json',
|
||||
],
|
||||
'targets.echo.metadata.runCommand': [
|
||||
'nx/core/package-json-workspaces',
|
||||
'package.json',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -6,7 +6,6 @@ import type { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
// nx-ignore-next-line
|
||||
import { GraphError } from 'nx/src/command-line/graph/graph';
|
||||
/* eslint-enable @nx/enforce-module-boundaries */
|
||||
|
||||
import { EyeIcon } from '@heroicons/react/24/outline';
|
||||
import { PropertyInfoTooltip, Tooltip } from '@nx/graph/ui-tooltips';
|
||||
import { TooltipTriggerText } from '../target-configuration-details/tooltip-trigger-text';
|
||||
@@ -29,6 +28,12 @@ export interface ProjectDetailsProps {
|
||||
viewInProjectGraphPosition?: 'top' | 'bottom';
|
||||
}
|
||||
|
||||
const typeToProjectType = {
|
||||
app: 'Application',
|
||||
lib: 'Library',
|
||||
e2e: 'E2E',
|
||||
};
|
||||
|
||||
export const ProjectDetails = ({
|
||||
project,
|
||||
sourceMap,
|
||||
@@ -41,11 +46,6 @@ export const ProjectDetails = ({
|
||||
const projectData = project.data;
|
||||
const isCompact = variant === 'compact';
|
||||
|
||||
const displayType =
|
||||
projectData.projectType &&
|
||||
projectData.projectType?.charAt(0)?.toUpperCase() +
|
||||
projectData.projectType?.slice(1);
|
||||
|
||||
const technologies = [
|
||||
...new Set(
|
||||
[
|
||||
@@ -98,24 +98,33 @@ export const ProjectDetails = ({
|
||||
</div>
|
||||
<div className="flex justify-between py-2">
|
||||
<div>
|
||||
{projectData.metadata?.description ? (
|
||||
<p className="mb-2 text-sm capitalize text-gray-500 dark:text-slate-400">
|
||||
{projectData.metadata?.description}
|
||||
</p>
|
||||
) : null}
|
||||
{projectData.tags && projectData.tags.length ? (
|
||||
<p>
|
||||
<span className="inline-block w-10 font-medium">Tags:</span>
|
||||
<span className="font-medium">Tags:</span>
|
||||
{projectData.tags?.map((tag) => (
|
||||
<span className="ml-2 font-mono">
|
||||
<span className="ml-2 font-mono lowercase">
|
||||
<Pill text={tag} />
|
||||
</span>
|
||||
))}
|
||||
</p>
|
||||
) : null}
|
||||
<p>
|
||||
<span className="inline-block w-10 font-medium">Root:</span>
|
||||
<span className="font-mono"> {projectData.root}</span>
|
||||
</p>
|
||||
{displayType ? (
|
||||
{projectData.root ? (
|
||||
<p>
|
||||
<span className="inline-block w-10 font-medium">Type:</span>
|
||||
<span className="font-mono"> {displayType}</span>
|
||||
<span className="font-medium">Root:</span>
|
||||
<span className="font-mono"> {projectData.root.trim()}</span>
|
||||
</p>
|
||||
) : null}
|
||||
{projectData.projectType ?? typeToProjectType[project.type] ? (
|
||||
<p>
|
||||
<span className="font-medium">Type:</span>
|
||||
<span className="ml-2 font-mono capitalize">
|
||||
{projectData.projectType ?? typeToProjectType[project.type]}
|
||||
</span>
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
+59
-20
@@ -5,6 +5,7 @@ import type { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
import { TargetConfigurationDetailsListItem } from '../target-configuration-details-list-item/target-configuration-details-list-item';
|
||||
import { TargetConfigurationGroupContainer } from '../target-configuration-details-group-container/target-configuration-details-group-container';
|
||||
import { groupTargets } from '../utils/group-targets';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export interface TargetConfigurationGroupListProps {
|
||||
project: ProjectGraphProjectNode;
|
||||
@@ -26,19 +27,54 @@ export function TargetConfigurationGroupList({
|
||||
onViewInTaskGraph,
|
||||
className = '',
|
||||
}: TargetConfigurationGroupListProps) {
|
||||
const targetsGroup = groupTargets(project);
|
||||
const targetsGroup = useMemo(() => groupTargets(project), [project]);
|
||||
const hasGroups = useMemo(() => {
|
||||
for (const group of Object.entries(targetsGroup.groups)) {
|
||||
if (group[1]?.length > 0) return true;
|
||||
}
|
||||
return false;
|
||||
}, [targetsGroup]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.entries(targetsGroup.groups).map(([targetGroupName, targets]) => {
|
||||
return (
|
||||
<TargetConfigurationGroupContainer
|
||||
targetGroupName={targetGroupName}
|
||||
targetsNumber={targets.length}
|
||||
key={targetGroupName}
|
||||
>
|
||||
<ul className={className}>
|
||||
{targets.map((targetName) => (
|
||||
if (hasGroups) {
|
||||
return (
|
||||
<>
|
||||
{Object.entries(targetsGroup.groups).map(
|
||||
([targetGroupName, targets]) => {
|
||||
if (targets.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<TargetConfigurationGroupContainer
|
||||
targetGroupName={targetGroupName}
|
||||
targetsNumber={targets.length}
|
||||
key={targetGroupName}
|
||||
>
|
||||
<ul className={className}>
|
||||
{targets.map((targetName) => (
|
||||
<TargetConfigurationDetailsListItem
|
||||
project={project}
|
||||
sourceMap={sourceMap}
|
||||
variant={variant}
|
||||
onRunTarget={onRunTarget}
|
||||
onViewInTaskGraph={onViewInTaskGraph}
|
||||
targetName={targetName}
|
||||
collapsable={true}
|
||||
key={targetName}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</TargetConfigurationGroupContainer>
|
||||
);
|
||||
}
|
||||
)}
|
||||
<TargetConfigurationGroupContainer
|
||||
targetGroupName="Others"
|
||||
targetsNumber={targetsGroup.targets.length}
|
||||
key="others-group"
|
||||
>
|
||||
<ul className={`p-2 ${className}`}>
|
||||
{targetsGroup.targets.map((targetName) => {
|
||||
return (
|
||||
<TargetConfigurationDetailsListItem
|
||||
project={project}
|
||||
sourceMap={sourceMap}
|
||||
@@ -49,12 +85,15 @@ export function TargetConfigurationGroupList({
|
||||
collapsable={true}
|
||||
key={targetName}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</TargetConfigurationGroupContainer>
|
||||
);
|
||||
})}
|
||||
<ul className={`mt-8 p-2 ${className}`}>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</TargetConfigurationGroupContainer>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<ul className={className}>
|
||||
{targetsGroup.targets.map((targetName) => {
|
||||
return (
|
||||
<TargetConfigurationDetailsListItem
|
||||
@@ -70,6 +109,6 @@ export function TargetConfigurationGroupList({
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-7
@@ -129,14 +129,15 @@ export const TargetConfigurationDetailsHeader = ({
|
||||
// TODO: fix tooltip overflow in collapsed state
|
||||
data-tooltip={isCollasped ? false : 'View in Task Graph'}
|
||||
data-tooltip-align-right
|
||||
onClick={(e) => {
|
||||
if (isCollasped) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
onViewInTaskGraph({ projectName, targetName });
|
||||
}}
|
||||
>
|
||||
<EyeIcon
|
||||
className={`h-5 w-5 !cursor-pointer`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onViewInTaskGraph({ projectName, targetName });
|
||||
}}
|
||||
/>
|
||||
<EyeIcon className={`h-5 w-5 !cursor-pointer`} />
|
||||
</button>
|
||||
)}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export function ProjectNodeToolTip({
|
||||
)}
|
||||
</h4>
|
||||
{tags.length > 0 ? (
|
||||
<p className="my-2">
|
||||
<p className="my-2 lowercase">
|
||||
<strong>tags</strong>
|
||||
<br></br>
|
||||
{tags.join(', ')}
|
||||
|
||||
@@ -138,7 +138,7 @@ export function Tooltip({
|
||||
width: 'max-content',
|
||||
...animationStyles,
|
||||
}}
|
||||
className="z-10 min-w-[250px] rounded-md border border-slate-500"
|
||||
className="z-10 min-w-[250px] max-w-prose rounded-md border border-slate-500"
|
||||
{...getFloatingProps()}
|
||||
>
|
||||
{showTooltipArrow && (
|
||||
|
||||
@@ -1081,6 +1081,34 @@ const decisionsSection = {
|
||||
// Blog post redirects
|
||||
const blogPosts = {
|
||||
'/blog/2024-05-07-nx-19-release': '/blog/2024-05-08-nx-19-release',
|
||||
'/blog/2024-05-08-nx-19-release': '/blog/nx-19-release',
|
||||
'/blog/2024-04-19-manage-your-gradle':
|
||||
'/blog/manage-your-gradle-project-using-nx',
|
||||
'/blog/2024-03-21-reliable-ci':
|
||||
'/blog/reliable-ci-a-new-execution-model-fixing-both-flakiness-and-slowness',
|
||||
'/blog/2024-03-20-why-speed-matters': '/blog/monorepos-why-speed-matters',
|
||||
'/blog/2024-02-15-launch-week-recap': '/blog/launch-nx-week-recap',
|
||||
'/blog/2024-02-09-versioning-and-releasing-packages':
|
||||
'/blog/versioning-and-releasing-packages-in-a-monorepo',
|
||||
'/blog/2024-02-07-fast-effortless-ci': '/blog/fast-effortless-ci',
|
||||
'/blog/2024-02-06-nuxt-js-support-in-nx':
|
||||
'/blog/introducing-nx-nuxt-enhanced-nuxt-js-support-in-nx',
|
||||
'/blog/2024-02-05-nx-18-project-crystal':
|
||||
'/blog/what-if-nx-plugins-were-more-like-vscode-extensions',
|
||||
'/blog/2023-12-28-highlights-2023': '/blog/nx-highlights-of-2023',
|
||||
'/blog/2023-12-20-nx-17-2-release': '/blog/nx-17-2-release',
|
||||
'/blog/2023-11-22-unit-testing-expo':
|
||||
'/blog/unit-testing-expo-apps-with-jest',
|
||||
'/blog/2023-11-21-ai-assistant': '/blog/nx-docs-ai-assistant',
|
||||
'/blog/2023-11-08-state-management':
|
||||
'/blog/state-management-nx-react-native-expo-apps-with-tanstack-query-and-redux',
|
||||
'/blog/2023-10-20-nx-17-release': '/blog/nx-17-release',
|
||||
'/blog/2023-10-13-nx-conf-2023-recap': '/blog/nx-conf-2023-recap',
|
||||
'/blog/2023-09-25-nx-raises': '/blog/nx-raises-16m-series-a',
|
||||
'/blog/2023-08-15-qwikify-your-dev': '/blog/qwikify-your-development-with-nx',
|
||||
'/blog/2023-06-29-nx-console-gets-lit': '/blog/nx-console-gets-lit',
|
||||
'/blog/2023-04-19-nx-cloud-3':
|
||||
'/blog/nx-cloud-3-0-faster-more-efficient-modernized',
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,7 @@ export function AnnouncementBanner(): JSX.Element {
|
||||
<p className="text-sm font-medium">
|
||||
<span className="md:hidden">
|
||||
<Link
|
||||
href="https://monorepo.tools/conf?utm_source=nx.dev"
|
||||
href="https://monorepo.world?utm_source=nx.dev"
|
||||
className="underline"
|
||||
>
|
||||
Monorepo World: October 7, 2024
|
||||
@@ -21,7 +21,7 @@ export function AnnouncementBanner(): JSX.Element {
|
||||
</span>
|
||||
<span className="ml-2 inline-block">
|
||||
<Link
|
||||
href="https://monorepo.tools/conf?utm_source=nx.dev"
|
||||
href="https://monorepo.world?utm_source=nx.dev"
|
||||
className="font-semibold text-blue-500 underline dark:text-sky-500"
|
||||
>
|
||||
<span className="absolute inset-0" aria-hidden="true" />
|
||||
|
||||
@@ -207,7 +207,7 @@ export const eventItems: MenuItem[] = [
|
||||
name: 'Monorepo World',
|
||||
description:
|
||||
'In person & virtual conference about the latest monorepo advancements.',
|
||||
href: 'https://monorepo.tools/conf',
|
||||
href: 'https://monorepo.world',
|
||||
icon: null,
|
||||
isNew: false,
|
||||
isHighlight: false,
|
||||
|
||||
@@ -7,4 +7,19 @@ describe('heading schema: generateID', () => {
|
||||
'pro-simple-setup'
|
||||
);
|
||||
});
|
||||
|
||||
it('should create id for code based headers', () => {
|
||||
const codeHeader = [
|
||||
{
|
||||
$$mdtype: 'Tag',
|
||||
name: 'code',
|
||||
attributes: {},
|
||||
children: ['launch-templates.<template-name>.init-steps[*].env'],
|
||||
},
|
||||
];
|
||||
|
||||
expect(generateID(codeHeader, {})).toEqual(
|
||||
'launchtemplatestemplatenameinitstepsenv'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,8 +7,31 @@ export function generateID(
|
||||
if (attributes['id'] && typeof attributes['id'] === 'string') {
|
||||
return attributes['id'];
|
||||
}
|
||||
return children
|
||||
.filter((child) => typeof child === 'string')
|
||||
|
||||
const validChildrenNodes: RenderableTreeNode[] = [];
|
||||
|
||||
for (const child of children) {
|
||||
if (!child) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof child === 'string') {
|
||||
validChildrenNodes.push(child);
|
||||
} else if (
|
||||
// allow rendering titles that are wrapped in `code` tags
|
||||
typeof child === 'object' &&
|
||||
'children' in child &&
|
||||
child.name === 'code' &&
|
||||
Array.isArray(child.children)
|
||||
) {
|
||||
const validNestedChild = child.children.filter(
|
||||
(c) => typeof c === 'string'
|
||||
);
|
||||
validChildrenNodes.push(...validNestedChild);
|
||||
}
|
||||
}
|
||||
|
||||
return validChildrenNodes
|
||||
.join(' ')
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
|
||||
+5
-6
@@ -59,9 +59,8 @@
|
||||
"@nestjs/schematics": "^9.1.0",
|
||||
"@nestjs/swagger": "^6.0.0",
|
||||
"@nestjs/testing": "^9.0.0",
|
||||
"@ngrx/effects": "17.0.1",
|
||||
"@ngrx/router-store": "17.0.1",
|
||||
"@ngrx/store": "17.0.1",
|
||||
"@ngrx/router-store": "^18.0.0",
|
||||
"@ngrx/store": "^18.0.0",
|
||||
"@nuxt/kit": "^3.10.0",
|
||||
"@nuxt/schema": "^3.10.0",
|
||||
"@nx/angular": "19.2.0-rc.0",
|
||||
@@ -161,8 +160,8 @@
|
||||
"cz-git": "^1.4.0",
|
||||
"czg": "^1.4.0",
|
||||
"detect-port": "^1.5.1",
|
||||
"dotenv": "~16.3.1",
|
||||
"dotenv-expand": "^10.0.0",
|
||||
"dotenv": "~16.4.5",
|
||||
"dotenv-expand": "~11.0.6",
|
||||
"ejs": "^3.1.7",
|
||||
"enhanced-resolve": "^5.8.3",
|
||||
"esbuild": "0.19.5",
|
||||
@@ -250,7 +249,7 @@
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-redux": "8.0.5",
|
||||
"react-refresh": "^0.10.0",
|
||||
"react-router-dom": "^6.21.2",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"react-textarea-autosize": "^8.5.3",
|
||||
"regenerator-runtime": "0.13.7",
|
||||
"resolve.exports": "1.1.0",
|
||||
|
||||
@@ -1921,6 +1921,18 @@
|
||||
"alwaysAddToPackageJson": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"19.4.0-ngrx": {
|
||||
"version": "19.4.0-beta.1",
|
||||
"requires": {
|
||||
"@angular/core": "^18.0.0"
|
||||
},
|
||||
"packages": {
|
||||
"@ngrx/store": {
|
||||
"version": "^18.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -67,13 +67,16 @@ export async function buildStaticRemotes(
|
||||
}
|
||||
});
|
||||
staticProcess.stderr.on('data', (data) => logger.info(data.toString()));
|
||||
staticProcess.on('exit', (code) => {
|
||||
staticProcess.once('exit', (code) => {
|
||||
stdoutStream.end();
|
||||
staticProcess.stdout.removeAllListeners('data');
|
||||
staticProcess.stderr.removeAllListeners('data');
|
||||
if (code !== 0) {
|
||||
throw new Error(
|
||||
`Remote failed to start. A complete log can be found in: ${remoteBuildLogFile}`
|
||||
);
|
||||
}
|
||||
res();
|
||||
});
|
||||
process.on('SIGTERM', () => staticProcess.kill('SIGTERM'));
|
||||
process.on('exit', () => staticProcess.kill('SIGTERM'));
|
||||
|
||||
@@ -14,8 +14,7 @@ import { loadEsmModule } from './module-loader';
|
||||
async function readDefaultTsConfig(fileName: string) {
|
||||
// these options are mandatory
|
||||
const extraOptions: ts.CompilerOptions = {
|
||||
target: ts.ScriptTarget.ES2020,
|
||||
experimentalDecorators: true,
|
||||
target: ts.ScriptTarget.ES2022,
|
||||
|
||||
// sourcemaps
|
||||
sourceMap: false,
|
||||
@@ -25,7 +24,7 @@ async function readDefaultTsConfig(fileName: string) {
|
||||
outDir: '',
|
||||
declaration: true,
|
||||
|
||||
// ng compiler to options
|
||||
// ng compiler
|
||||
enableResourceInlining: true,
|
||||
|
||||
// these are required to set the appropriate EmitFlags
|
||||
|
||||
@@ -52,6 +52,18 @@ describe('app migrator', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should not migrate project when validation fails', async () => {
|
||||
// add project with no root
|
||||
const project = addProject('app1', {} as any);
|
||||
const migrator = new AppMigrator(tree, {}, project);
|
||||
|
||||
await migrator.migrate();
|
||||
|
||||
expect(tree.exists('apps/app1/project.json')).toBe(false);
|
||||
const { projects } = readJson(tree, 'angular.json');
|
||||
expect(projects.app1).toBeDefined();
|
||||
});
|
||||
|
||||
describe('validation', () => {
|
||||
it('should fail validation when the project root is not specified', async () => {
|
||||
const project = addProject('app1', {} as any);
|
||||
|
||||
@@ -103,12 +103,12 @@ export class AppMigrator extends ProjectMigrator<SupportedTargets> {
|
||||
}
|
||||
|
||||
override async migrate(): Promise<void> {
|
||||
await super.migrate();
|
||||
|
||||
if (this.skipMigration === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
await super.migrate();
|
||||
|
||||
this.updateProjectConfiguration();
|
||||
|
||||
await this.e2eMigrator.migrate();
|
||||
|
||||
@@ -50,6 +50,18 @@ describe('lib migrator', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should not migrate project when validation fails', async () => {
|
||||
// add project with no root
|
||||
const project = addProject('lib1', {} as any);
|
||||
const migrator = new LibMigrator(tree, {}, project);
|
||||
|
||||
await migrator.migrate();
|
||||
|
||||
expect(tree.exists('libs/lib1/project.json')).toBe(false);
|
||||
const { projects } = readJson(tree, 'angular.json');
|
||||
expect(projects.lib1).toBeDefined();
|
||||
});
|
||||
|
||||
describe('validation', () => {
|
||||
it('should fail validation when the project root is not specified', async () => {
|
||||
const project = addProject('lib1', {} as any);
|
||||
|
||||
@@ -41,12 +41,12 @@ export class LibMigrator extends ProjectMigrator {
|
||||
}
|
||||
|
||||
override async migrate(): Promise<void> {
|
||||
await super.migrate();
|
||||
|
||||
if (this.skipMigration === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
await super.migrate();
|
||||
|
||||
await this.updateProjectConfiguration();
|
||||
this.moveProjectFiles();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as chalk from 'chalk';
|
||||
import chalk = require('chalk');
|
||||
import {
|
||||
arrayToString,
|
||||
getProjectValidationResultMessage,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as chalk from 'chalk';
|
||||
import chalk = require('chalk');
|
||||
import type { ValidationError } from './types';
|
||||
|
||||
export function arrayToString(array: string[]): string {
|
||||
|
||||
@@ -67,7 +67,7 @@ function updateConfig(config: {
|
||||
browserTarget?: string;
|
||||
buildTarget?: string;
|
||||
}): void {
|
||||
if (config.browserTarget) {
|
||||
if (config && config.browserTarget) {
|
||||
config.buildTarget ??= config.browserTarget;
|
||||
delete config.browserTarget;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ export {
|
||||
addProviderToAppConfig,
|
||||
addProviderToComponent,
|
||||
addProviderToModule,
|
||||
addViewProviderToComponent,
|
||||
} from './nx-devkit/ast-utils';
|
||||
|
||||
export { addRoute, addProviderToRoute } from './nx-devkit/route-utils';
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
addImportToPipe,
|
||||
addProviderToAppConfig,
|
||||
addProviderToBootstrapApplication,
|
||||
addViewProviderToComponent,
|
||||
isStandalone,
|
||||
} from './ast-utils';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
@@ -334,4 +335,50 @@ export const appConfig: ApplicationConfig = {
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should add view provider to a component', () => {
|
||||
// ARRANGE
|
||||
const pathToComponent = 'app.component.ts';
|
||||
const componentOriginal = `import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-app',
|
||||
template: ''
|
||||
})
|
||||
export class AppComponent {}
|
||||
`;
|
||||
const providerName = 'MyViewProvider';
|
||||
|
||||
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
|
||||
tree.write(pathToComponent, componentOriginal);
|
||||
|
||||
const tsSourceFile = createSourceFile(
|
||||
pathToComponent,
|
||||
componentOriginal,
|
||||
ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
|
||||
// ACT
|
||||
addViewProviderToComponent(
|
||||
tree,
|
||||
tsSourceFile,
|
||||
pathToComponent,
|
||||
providerName
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(pathToComponent, 'utf-8')).toMatchInlineSnapshot(`
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-app',
|
||||
template: '',
|
||||
viewProviders: [MyViewProvider]
|
||||
})
|
||||
export class AppComponent {}
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -826,6 +826,29 @@ export function addProviderToComponent(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a view provider to a Standalone Component
|
||||
* @param host Virtual Tree
|
||||
* @param source TS Source File containing the Component
|
||||
* @param componentPath Path to the Component
|
||||
* @param symbolName The provider to add
|
||||
*/
|
||||
export function addViewProviderToComponent(
|
||||
host: Tree,
|
||||
source: ts.SourceFile,
|
||||
componentPath: string,
|
||||
symbolName: string
|
||||
): ts.SourceFile {
|
||||
return _addSymbolToDecoratorMetadata(
|
||||
host,
|
||||
source,
|
||||
componentPath,
|
||||
'viewProviders',
|
||||
symbolName,
|
||||
'Component'
|
||||
);
|
||||
}
|
||||
|
||||
export function addDeclarationToModule(
|
||||
host: Tree,
|
||||
source: ts.SourceFile,
|
||||
|
||||
@@ -3,7 +3,7 @@ export const nxVersion = require('../../package.json').version;
|
||||
export const angularVersion = '~18.0.0';
|
||||
export const angularDevkitVersion = '~18.0.0';
|
||||
export const ngPackagrVersion = '~18.0.0';
|
||||
export const ngrxVersion = '~17.0.0';
|
||||
export const ngrxVersion = '^18.0.0';
|
||||
export const rxjsVersion = '~7.8.0';
|
||||
export const zoneJsVersion = '~0.14.3';
|
||||
export const angularJsVersion = '1.7.9';
|
||||
|
||||
@@ -283,28 +283,18 @@ async function normalizeArgsMiddleware(
|
||||
|
||||
const packageManager = await determinePackageManager(argv);
|
||||
const defaultBase = await determineDefaultBase(argv);
|
||||
if (process.env.NX_NEW_CLOUD_ONBOARDING === 'true') {
|
||||
const nxCloud =
|
||||
argv.skipGit === true ? 'skip' : await determineNxCloud(argv);
|
||||
const useGitHub =
|
||||
nxCloud === 'skip'
|
||||
? undefined
|
||||
: nxCloud === 'github' ||
|
||||
(await determineIfGitHubWillBeUsed(nxCloud));
|
||||
Object.assign(argv, {
|
||||
nxCloud,
|
||||
useGitHub,
|
||||
packageManager,
|
||||
defaultBase,
|
||||
});
|
||||
} else {
|
||||
const nxCloud = await determineNxCloud(argv);
|
||||
Object.assign(argv, {
|
||||
nxCloud,
|
||||
packageManager,
|
||||
defaultBase,
|
||||
});
|
||||
}
|
||||
const nxCloud =
|
||||
argv.skipGit === true ? 'skip' : await determineNxCloud(argv);
|
||||
const useGitHub =
|
||||
nxCloud === 'skip'
|
||||
? undefined
|
||||
: nxCloud === 'github' || (await determineIfGitHubWillBeUsed(nxCloud));
|
||||
Object.assign(argv, {
|
||||
nxCloud,
|
||||
useGitHub,
|
||||
packageManager,
|
||||
defaultBase,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createSandbox } from './create-sandbox';
|
||||
import { createEmptyWorkspace } from './create-empty-workspace';
|
||||
import { createPreset } from './create-preset';
|
||||
import { setupCI } from './utils/ci/setup-ci';
|
||||
import { initializeGitRepo } from './utils/git/git';
|
||||
import { commitChanges, initializeGitRepo } from './utils/git/git';
|
||||
import { getPackageNameFromThirdPartyPreset } from './utils/preset/get-third-party-preset';
|
||||
import { mapErrorToBodyLines } from './utils/error-utils';
|
||||
|
||||
@@ -51,27 +51,11 @@ export async function createWorkspace<T extends CreateWorkspaceOptions>(
|
||||
);
|
||||
}
|
||||
|
||||
let nxCloudInstallRes;
|
||||
if (nxCloud !== 'skip') {
|
||||
nxCloudInstallRes = await setupNxCloud(
|
||||
directory,
|
||||
packageManager,
|
||||
nxCloud,
|
||||
useGitHub
|
||||
);
|
||||
|
||||
if (nxCloud !== 'yes') {
|
||||
await setupCI(
|
||||
directory,
|
||||
nxCloud,
|
||||
packageManager,
|
||||
nxCloudInstallRes?.code === 0
|
||||
);
|
||||
}
|
||||
}
|
||||
let gitSuccess = false;
|
||||
if (!skipGit && commit) {
|
||||
try {
|
||||
await initializeGitRepo(directory, { defaultBase, commit });
|
||||
gitSuccess = true;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
output.error({
|
||||
@@ -84,6 +68,28 @@ export async function createWorkspace<T extends CreateWorkspaceOptions>(
|
||||
}
|
||||
}
|
||||
|
||||
let nxCloudInstallRes;
|
||||
if (nxCloud !== 'skip') {
|
||||
nxCloudInstallRes = await setupNxCloud(
|
||||
directory,
|
||||
packageManager,
|
||||
nxCloud,
|
||||
useGitHub
|
||||
);
|
||||
|
||||
if (nxCloud !== 'yes') {
|
||||
const nxCIsetupRes = await setupCI(
|
||||
directory,
|
||||
nxCloud,
|
||||
packageManager,
|
||||
nxCloudInstallRes?.code === 0
|
||||
);
|
||||
if (nxCIsetupRes?.code === 0) {
|
||||
commitChanges(directory, `feat(nx): Generated CI workflow`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
nxCloudInfo: nxCloudInstallRes?.stdout,
|
||||
directory,
|
||||
|
||||
@@ -84,3 +84,19 @@ export async function initializeGitRepo(
|
||||
await execute(['commit', `-m "${message}"`]);
|
||||
}
|
||||
}
|
||||
|
||||
export function commitChanges(directory: string, message: string) {
|
||||
try {
|
||||
execSync('git add -A', { encoding: 'utf8', stdio: 'pipe', cwd: directory });
|
||||
execSync('git commit --no-verify -F -', {
|
||||
encoding: 'utf8',
|
||||
stdio: 'pipe',
|
||||
input: message,
|
||||
cwd: directory,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`There was an error committing your Nx Cloud token.\n
|
||||
Please commit the changes manually and push to your new repository.\n
|
||||
\n${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,11 @@ export async function setupNxCloud(
|
||||
try {
|
||||
const pmc = getPackageManagerCommand(packageManager);
|
||||
const res = await execAndWait(
|
||||
process.env.NX_NEW_CLOUD_ONBOARDING === 'true'
|
||||
? `${
|
||||
pmc.exec
|
||||
} nx g nx:connect-to-nx-cloud --installationSource=create-nx-workspace ${
|
||||
useGitHub ? '--github' : ''
|
||||
} --no-interactive`
|
||||
: `${pmc.exec} nx g nx:connect-to-nx-cloud --no-interactive --quiet`,
|
||||
`${
|
||||
pmc.exec
|
||||
} nx g nx:connect-to-nx-cloud --installationSource=create-nx-workspace --directory=${directory} ${
|
||||
useGitHub ? '--github' : ''
|
||||
} --no-interactive`,
|
||||
directory
|
||||
);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"init": {
|
||||
"factory": "./src/generators/init/init#cypressInitGeneratorInternal",
|
||||
"schema": "./src/generators/init/schema.json",
|
||||
"description": "Initialize the `@nrwl/cypress` plugin.",
|
||||
"description": "Initialize the `@nx/cypress` plugin.",
|
||||
"aliases": ["ng-add"],
|
||||
"hidden": true
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"init": {
|
||||
"factory": "./src/generators/init/init#detoxInitGeneratorInternal",
|
||||
"schema": "./src/generators/init/schema.json",
|
||||
"description": "Initialize the `@nrwl/detox` plugin.",
|
||||
"description": "Initialize the `@nx/detox` plugin.",
|
||||
"hidden": true
|
||||
},
|
||||
"application": {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"init": {
|
||||
"factory": "./src/generators/init/init#esbuildInitGenerator",
|
||||
"schema": "./src/generators/init/schema.json",
|
||||
"description": "Initialize the `@nrwl/esbuild` plugin.",
|
||||
"description": "Initialize the `@nx/esbuild` plugin.",
|
||||
"aliases": ["ng-add"],
|
||||
"hidden": true
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ import { joinPathFragments, type ExecutorContext } from '@nx/devkit';
|
||||
import type { ESLint } from 'eslint';
|
||||
import { mkdirSync, writeFileSync } from 'fs';
|
||||
import { interpolate } from 'nx/src/tasks-runner/utils';
|
||||
import { dirname, posix, resolve } from 'path';
|
||||
import { dirname, posix, relative, resolve } from 'path';
|
||||
import { findFlatConfigFile, findOldConfigFile } from '../../utils/config-file';
|
||||
import type { Schema } from './schema';
|
||||
import { resolveAndInstantiateESLint } from './utility/eslint-utils';
|
||||
@@ -45,10 +45,7 @@ export default async function run(
|
||||
// the flat config would be resolved starting from the cwd, which we changed to the workspace root
|
||||
// so we explicitly set the config path to the flat config file path we previously found
|
||||
if (hasFlatConfig && !normalizedOptions.eslintConfig) {
|
||||
normalizedOptions.eslintConfig = posix.relative(
|
||||
systemRoot,
|
||||
flatConfigFilePath
|
||||
);
|
||||
normalizedOptions.eslintConfig = relative(systemRoot, flatConfigFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
addDependenciesToPackageJson,
|
||||
GeneratorCallback,
|
||||
joinPathFragments,
|
||||
offsetFromRoot,
|
||||
ProjectConfiguration,
|
||||
@@ -15,7 +16,7 @@ import {
|
||||
getGlobalFlatEslintConfiguration,
|
||||
} from './global-eslint-config';
|
||||
import { useFlatConfig } from '../../utils/flat-config';
|
||||
import { eslintVersion } from '../../utils/versions';
|
||||
import { eslintVersion, nxVersion } from '../../utils/versions';
|
||||
import {
|
||||
addBlockToFlatConfigExport,
|
||||
addImportToFlatConfig,
|
||||
@@ -31,7 +32,7 @@ export function migrateConfigToMonorepoStyle(
|
||||
tree: Tree,
|
||||
unitTestRunner: string,
|
||||
keepExistingVersions?: boolean
|
||||
): void {
|
||||
): GeneratorCallback {
|
||||
const rootEslintConfig = findEslintFile(tree);
|
||||
let skipCleanup = false;
|
||||
if (
|
||||
@@ -105,6 +106,14 @@ export function migrateConfigToMonorepoStyle(
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return addDependenciesToPackageJson(
|
||||
tree,
|
||||
{},
|
||||
{
|
||||
'@nx/eslint-plugin': nxVersion,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function findLintTarget(
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
ProjectGraph,
|
||||
readJson,
|
||||
Tree,
|
||||
} from '@nx/devkit';
|
||||
|
||||
import { Linter } from '../utils/linter';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { lintProjectGenerator } from './lint-project';
|
||||
|
||||
let projectGraph: ProjectGraph;
|
||||
|
||||
jest.mock('@nx/devkit', () => ({
|
||||
...jest.requireActual<any>('@nx/devkit'),
|
||||
createProjectGraphAsync: jest
|
||||
.fn()
|
||||
.mockImplementation(async () => projectGraph),
|
||||
}));
|
||||
|
||||
describe('@nx/eslint:lint-project (convert to monorepo style)', () => {
|
||||
let tree: Tree;
|
||||
|
||||
const defaultOptions = {
|
||||
skipFormat: false,
|
||||
addPlugin: true,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
const rootpkg = {
|
||||
root: '.',
|
||||
projectType: 'library' as const,
|
||||
targets: {
|
||||
'eslint:lint': {
|
||||
executor: 'nx:run-commands',
|
||||
options: {
|
||||
command: 'eslint .',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
projectGraph = {
|
||||
nodes: {
|
||||
rootpkg: {
|
||||
type: 'lib',
|
||||
name: 'rootpkg',
|
||||
data: rootpkg,
|
||||
},
|
||||
},
|
||||
dependencies: {},
|
||||
};
|
||||
addProjectConfiguration(tree, 'rootpkg', rootpkg);
|
||||
tree.write(
|
||||
'.eslintrc.cjs',
|
||||
`
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
}
|
||||
`
|
||||
);
|
||||
});
|
||||
|
||||
it('should generate a eslint config and configure the target in project configuration', async () => {
|
||||
addProjectConfiguration(tree, 'nestedpkg', {
|
||||
root: 'nestedpkg',
|
||||
projectType: 'library',
|
||||
targets: {},
|
||||
});
|
||||
|
||||
await lintProjectGenerator(tree, {
|
||||
...defaultOptions,
|
||||
linter: Linter.EsLint,
|
||||
project: 'nestedpkg',
|
||||
setParserOptionsProject: false,
|
||||
});
|
||||
|
||||
expect(readJson(tree, 'package.json')).toMatchObject({
|
||||
devDependencies: {
|
||||
'@nx/eslint-plugin': expect.any(String),
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,16 +1,16 @@
|
||||
import {
|
||||
createProjectGraphAsync,
|
||||
formatFiles,
|
||||
GeneratorCallback,
|
||||
NxJsonConfiguration,
|
||||
offsetFromRoot,
|
||||
ProjectConfiguration,
|
||||
ProjectGraph,
|
||||
Tree,
|
||||
readNxJson,
|
||||
formatFiles,
|
||||
offsetFromRoot,
|
||||
readJson,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
runTasksInSerial,
|
||||
Tree,
|
||||
updateJson,
|
||||
updateProjectConfiguration,
|
||||
writeJson,
|
||||
@@ -21,10 +21,7 @@ import { findEslintFile } from '../utils/eslint-file';
|
||||
import { join } from 'path';
|
||||
import { lintInitGenerator } from '../init/init';
|
||||
import type { Linter } from 'eslint';
|
||||
import {
|
||||
findLintTarget,
|
||||
migrateConfigToMonorepoStyle,
|
||||
} from '../init/init-migration';
|
||||
import { migrateConfigToMonorepoStyle } from '../init/init-migration';
|
||||
import { getProjects } from 'nx/src/generators/utils/project-configuration';
|
||||
import { useFlatConfig } from '../../utils/flat-config';
|
||||
import {
|
||||
@@ -36,7 +33,6 @@ import {
|
||||
import {
|
||||
baseEsLintConfigFile,
|
||||
baseEsLintFlatConfigFile,
|
||||
ESLINT_CONFIG_FILENAMES,
|
||||
} from '../../utils/config-file';
|
||||
import { hasEslintPlugin } from '../utils/plugin';
|
||||
import { setupRootEsLint } from './setup-root-eslint';
|
||||
@@ -144,12 +140,13 @@ export async function lintProjectGeneratorInternal(
|
||||
filteredProjects.push(project);
|
||||
}
|
||||
});
|
||||
migrateConfigToMonorepoStyle(
|
||||
const migrateTask = migrateConfigToMonorepoStyle(
|
||||
filteredProjects,
|
||||
tree,
|
||||
options.unitTestRunner,
|
||||
options.keepExistingVersions
|
||||
);
|
||||
tasks.push(migrateTask);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,8 +100,7 @@ function buildExpoTargets(
|
||||
|
||||
const targets: Record<string, TargetConfiguration> = {
|
||||
[options.startTargetName]: {
|
||||
command: `expo start`,
|
||||
options: { cwd: projectRoot },
|
||||
executor: `@nx/expo:start`,
|
||||
},
|
||||
[options.serveTargetName]: {
|
||||
command: `expo start --web`,
|
||||
@@ -124,8 +123,7 @@ function buildExpoTargets(
|
||||
outputs: [getOutputs(projectRoot, 'dist')],
|
||||
},
|
||||
[options.installTargetName]: {
|
||||
command: `expo install`,
|
||||
options: { cwd: workspaceRoot }, // install at workspace root
|
||||
executor: '@nx/expo:install',
|
||||
},
|
||||
[options.prebuildTargetName]: {
|
||||
executor: `@nx/expo:prebuild`,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user