Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 303d124655 | |||
| fb6ddb7b3d | |||
| 40307dcd2e | |||
| eb87866dbb | |||
| d2ee81b2e8 | |||
| 25e4bbef74 | |||
| 974e022020 | |||
| e4d1a81e0b | |||
| 4d478a5b4e | |||
| 7d59927ed4 | |||
| 81439245c8 | |||
| 3d0bdf023f | |||
| 791a8ef7e1 | |||
| b6214549d6 | |||
| 0c4857d637 | |||
| 289c1cb470 | |||
| c009c7480e | |||
| 022754c96b | |||
| 519ec11a14 | |||
| 7675f010f4 | |||
| 6ae5cdd915 | |||
| 7d1d14fa03 | |||
| f57780d3a5 | |||
| c4a1de4fae | |||
| 60e301140d | |||
| 560daa55a2 | |||
| b9cdcf30d7 | |||
| 12e21bc7c1 | |||
| 0c31f0b067 | |||
| dbce9f5214 | |||
| 8529d665b4 | |||
| 81f2ddbcd0 | |||
| bccd543695 | |||
| 4758714b87 | |||
| 0c82ac8560 | |||
| 42e1684c7d | |||
| dfd5385553 |
@@ -172,6 +172,7 @@ rust-toolchain @nrwl/nx-native-reviewers
|
||||
/CONTRIBUTING.md @FrozenPandaz @isaacplmann
|
||||
/CODE_OF_CONDUCT.md @FrozenPandaz @isaacplmann
|
||||
/CODEOWNERS @FrozenPandaz @AgentEnder
|
||||
/packages/nx/src/nx-cloud/utilities/url-shorten.ts @MaxKless
|
||||
|
||||
# Scripts
|
||||
/scripts/documentation @nrwl/nx-docs-reviewers
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: 'Nx Enterprise Podcast Episode 1: Hicham El Hammouchi'
|
||||
slug: 'hicham-el-hammouchi-podcast-1'
|
||||
authors: ['Zack DeRose']
|
||||
tags: [podcast]
|
||||
cover_image: /blog/images/2024-06-18/ep-1-hicham.png
|
||||
podcastYoutubeId: 8iiLB_2djZ8
|
||||
podcastSpotifyId: 24yagCNpu9EGj0fCwSDQkj
|
||||
podcastAmazonUrl: https://music.amazon.com/podcasts/a221fdad-36fd-4695-a5b4-038d7b99d284/episodes/899a2e4c-2e56-4dfa-a3e3-e69eb216f2b0/the-enterprise-software-podcast-by-nx-the-enterprise-software-podcast-by-nx-1-hicham-el-hamouchi
|
||||
podcastAppleUrl: https://podcasters.spotify.com/pod/show/enterprise-software/episodes/The-Enterprise-Software-Podcast-By-Nx-1--Hicham-El-Hamouchi-e2l0302
|
||||
podcastIHeartUrl: https://www.iheart.com/podcast/269-the-enterprise-software-po-186891508/episode/the-enterprise-software-podcast-by-nx-186891511/
|
||||
---
|
||||
|
||||
In this episode, Zack DeRose from Nx chats with Hicham El Hammouchi, a veteran in enterprise software. Hicham dives into his background, sharing his career journey and the wealth of experience he's gathered along the way.
|
||||
|
||||
They tackle the tough challenges that companies face in software development. Hicham offers practical insights and real-life examples from his own career, making complex issues easier to understand. He also shares some tried-and-true strategies for overcoming these hurdles, emphasizing the importance of teamwork and effective project management.
|
||||
|
||||
As the chat continues, Zack and Hicham explore the latest tech trends and innovations that are shaking up the industry. Hicham's forward-thinking views provide a sneak peek into the future of enterprise software. They also discuss detailed case studies of successful projects, highlighting the importance of staying focused on customer needs and feedback.
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: 'Nx Enterprise Podcast Episode 2: Tine Kondo'
|
||||
slug: 'tine-kondo-podcast-2'
|
||||
authors: ['Zack DeRose']
|
||||
tags: [podcast]
|
||||
cover_image: /blog/images/2024-07-19/ep-2-tine.png
|
||||
podcastYoutubeId: Nzf3BmymfEo
|
||||
podcastSpotifyId: 0CCQaWCln7rvwkkVvsyxsk
|
||||
podcastAmazonUrl: https://music.amazon.com/podcasts/a221fdad-36fd-4695-a5b4-038d7b99d284/episodes/53934cd9-c521-441e-8523-8b947ed207ca/the-enterprise-software-podcast-by-nx-the-enterprise-software-podcast-by-nx-2-tine-kondo
|
||||
podcastAppleUrl: https://podcasts.apple.com/us/podcast/the-enterprise-software-podcast-by-nx-2-tine-kondo/id1752704996?i=1000662764990
|
||||
podcastIHeartUrl: https://www.iheart.com/podcast/269-the-enterprise-software-po-186891508/episode/the-enterprise-software-podcast-by-nx-197335640/
|
||||
---
|
||||
|
||||
In this episode, welcome Nx Champion and Nx Expert, Tine Kondo. Our discussion highlights how Nx is poised to make an impact in the Java space.
|
||||
|
||||
We touch on the difficulties larger organizations face when merging PRs in a timely manner, emphasizing how a monorepo alleviates the headache of coordinating multiple PRs across various repositories, and share firsthand experiences and frustrations with traditional multi-repo setups, underscoring the benefits of a unified approach.
|
||||
|
||||
In particular, Nx's new plugin: [@nx/gradle](/nx-api/gradle) is of interest, and Tine provides some valuable insight on the impact this plugin can make, as well as the importance for Maven support.
|
||||
@@ -10,6 +10,8 @@ tags: [nx, release]
|
||||
|
||||
In this blog post:
|
||||
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Video Summary](#video-summary)
|
||||
- [Announcing Nx Cloud Hobby Tier](#announcing-nx-cloud-hobby-tier)
|
||||
- [StackBlitz Support](#stackblitz-support)
|
||||
- [Bun and Pnpm v9 Support](#bun-and-pnpm-v9-support)
|
||||
@@ -25,6 +27,7 @@ In this blog post:
|
||||
- [Support for React 19 (rc) and Angular 18.1](#support-for-react-19-rc-and-angular-181)
|
||||
- [Automatically Update Nx](#automatically-update-nx)
|
||||
- [Monorepo World Conference Speakers Announced!!](#monorepo-world-conference-speakers-announced)
|
||||
- [Learn more](#learn-more)
|
||||
|
||||
## Video Summary
|
||||
|
||||
@@ -142,7 +145,7 @@ To accommodate port collisions in end-to-end tests, we've long adjusted our gene
|
||||
|
||||
Unfortunately, this approach is more imperative, as it instructs how to run and order your CI rather than defining what should be run. Additionally, these instructions cause `lint`, `test`, and `build` targets to run first and wait until they all complete before running `e2e-ci`, leading to inefficiencies.
|
||||
|
||||
To address this, all tasks now support a `parallelism` property. By setting this property to `false`, you can instruct the Nx task runner not to run a specific task in parallel. This allows us to define parallelism as a task property, making our CI configuration more declarative.
|
||||
To address this, all tasks now support a [`parallelism` property](/reference/project-configuration#parallelism). By setting this property to `false`, you can instruct the Nx task runner not to run a specific task in parallel. This allows us to define parallelism as a task property, making our CI configuration more declarative.
|
||||
|
||||
Both our `@nx/playwright` and `@nx/cypress` plugins will now automatically set `targetDefaults` for atomized tests to disable parallelism:
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: 'Nx Enterprise Podcast Episode 3: Ahmed Elsakaan'
|
||||
slug: 'ahmed-elsakaan-podcast-3'
|
||||
authors: ['Zack DeRose']
|
||||
tags: [podcast]
|
||||
cover_image: /blog/images/2024-08-14/ep-3-ahmed.png
|
||||
podcastYoutubeId: l_b6EOXqYRg
|
||||
podcastSpotifyId: 4d4oE8B3y9BmECZ3P4uvDP
|
||||
podcastAmazonUrl: https://music.amazon.com/podcasts/a221fdad-36fd-4695-a5b4-038d7b99d284/episodes/28209cf9-1b88-48b5-a798-7b24c843e9b1/the-enterprise-software-podcast-by-nx-the-enterprise-software-podcast-by-nx-3-ahmed-elsakaan
|
||||
podcastAppleUrl: https://podcasts.apple.com/us/podcast/the-enterprise-software-podcast-by-nx-3-ahmed-elsakaan/id1752704996?i=1000665363260
|
||||
podcastIHeartUrl: https://www.iheart.com/podcast/269-the-enterprise-software-po-186891508/episode/the-enterprise-software-podcast-by-nx-205664230/
|
||||
---
|
||||
|
||||
In this episode we welcome Nx Champion and creator of [noodle](https://noodle.run) and [OrbitKit](https://orbitkit.dev/): Ahmed Elsakaan.
|
||||
|
||||
As a monorepo enthusiast, we explore Ahmed's thoughts on monorepos and how these tools can add value to developers - both in the space of setting up your monorepo (like Nx does) and in the space of creating reusable customizable modules (like OrbitKit is attempting).
|
||||
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 754 KiB |
@@ -8,7 +8,7 @@
|
||||
"itemList": [
|
||||
{
|
||||
"id": "getting-started",
|
||||
"name": "Getting Started with Plugins",
|
||||
"name": "Extending Nx with Plugins",
|
||||
"description": "Learn how to extend Nx by creating and releasing your own Nx plugin.",
|
||||
"mediaImage": "",
|
||||
"file": "shared/plugins/intro",
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
"/extending-nx/intro/getting-started": {
|
||||
"id": "getting-started",
|
||||
"name": "Getting Started with Plugins",
|
||||
"name": "Extending Nx with Plugins",
|
||||
"description": "Learn how to extend Nx by creating and releasing your own Nx plugin.",
|
||||
"mediaImage": "",
|
||||
"file": "shared/plugins/intro",
|
||||
@@ -35,31 +35,31 @@
|
||||
},
|
||||
"/extending-nx/tutorials": {
|
||||
"id": "tutorials",
|
||||
"name": "5 Min Tutorials",
|
||||
"name": "Tutorials",
|
||||
"description": "Get started with plugins",
|
||||
"mediaImage": "",
|
||||
"file": "",
|
||||
"itemList": [
|
||||
{
|
||||
"id": "create-plugin",
|
||||
"name": "Create a Local Plugin",
|
||||
"id": "organization-specific-plugin",
|
||||
"name": "Enforce Organizational Best Practices",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/plugins/create-plugin",
|
||||
"file": "shared/plugins/organization-specific-plugin",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/tutorials/create-plugin",
|
||||
"path": "/extending-nx/tutorials/organization-specific-plugin",
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"id": "publish-plugin",
|
||||
"name": "Maintain a Published Plugin",
|
||||
"id": "tooling-plugin",
|
||||
"name": "Create a Tooling Plugin",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/plugins/maintain-published-plugin",
|
||||
"file": "shared/plugins/tooling-plugin",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/tutorials/publish-plugin",
|
||||
"path": "/extending-nx/tutorials/tooling-plugin",
|
||||
"tags": []
|
||||
}
|
||||
],
|
||||
@@ -67,26 +67,26 @@
|
||||
"path": "/extending-nx/tutorials",
|
||||
"tags": []
|
||||
},
|
||||
"/extending-nx/tutorials/create-plugin": {
|
||||
"id": "create-plugin",
|
||||
"name": "Create a Local Plugin",
|
||||
"/extending-nx/tutorials/organization-specific-plugin": {
|
||||
"id": "organization-specific-plugin",
|
||||
"name": "Enforce Organizational Best Practices",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/plugins/create-plugin",
|
||||
"file": "shared/plugins/organization-specific-plugin",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/tutorials/create-plugin",
|
||||
"path": "/extending-nx/tutorials/organization-specific-plugin",
|
||||
"tags": []
|
||||
},
|
||||
"/extending-nx/tutorials/publish-plugin": {
|
||||
"id": "publish-plugin",
|
||||
"name": "Maintain a Published Plugin",
|
||||
"/extending-nx/tutorials/tooling-plugin": {
|
||||
"id": "tooling-plugin",
|
||||
"name": "Create a Tooling Plugin",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/plugins/maintain-published-plugin",
|
||||
"file": "shared/plugins/tooling-plugin",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/tutorials/publish-plugin",
|
||||
"path": "/extending-nx/tutorials/tooling-plugin",
|
||||
"tags": []
|
||||
},
|
||||
"/extending-nx/recipes": {
|
||||
@@ -96,28 +96,6 @@
|
||||
"mediaImage": "",
|
||||
"file": "",
|
||||
"itemList": [
|
||||
{
|
||||
"id": "local-executors",
|
||||
"name": "Write a Simple Executor",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/plugins/local-executors",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/recipes/local-executors",
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"id": "compose-executors",
|
||||
"name": "Compose Executors",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/plugins/compose-executors",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/recipes/compose-executors",
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"id": "local-generators",
|
||||
"name": "Write a Simple Generator",
|
||||
@@ -184,6 +162,28 @@
|
||||
"path": "/extending-nx/recipes/migration-generators",
|
||||
"tags": ["create-your-own-plugin"]
|
||||
},
|
||||
{
|
||||
"id": "local-executors",
|
||||
"name": "Write a Simple Executor",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/plugins/local-executors",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/recipes/local-executors",
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"id": "compose-executors",
|
||||
"name": "Compose Executors",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/plugins/compose-executors",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/recipes/compose-executors",
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"id": "create-preset",
|
||||
"name": "Create a Preset",
|
||||
@@ -208,7 +208,7 @@
|
||||
},
|
||||
{
|
||||
"id": "project-graph-plugins",
|
||||
"name": "Modify the Project Graph",
|
||||
"name": "Infer Tasks or Projects",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/plugins/project-graph-plugins",
|
||||
@@ -216,34 +216,23 @@
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/recipes/project-graph-plugins",
|
||||
"tags": ["create-your-own-plugin", "explore-graph", "inferred-tasks"]
|
||||
},
|
||||
{
|
||||
"id": "publish-plugin",
|
||||
"name": "Publish a Plugin",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/plugins/publish-plugin",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/recipes/publish-plugin",
|
||||
"tags": ["create-your-own-plugin"]
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/recipes",
|
||||
"tags": []
|
||||
},
|
||||
"/extending-nx/recipes/local-executors": {
|
||||
"id": "local-executors",
|
||||
"name": "Write a Simple Executor",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/plugins/local-executors",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/recipes/local-executors",
|
||||
"tags": []
|
||||
},
|
||||
"/extending-nx/recipes/compose-executors": {
|
||||
"id": "compose-executors",
|
||||
"name": "Compose Executors",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/plugins/compose-executors",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/recipes/compose-executors",
|
||||
"tags": []
|
||||
},
|
||||
"/extending-nx/recipes/local-generators": {
|
||||
"id": "local-generators",
|
||||
"name": "Write a Simple Generator",
|
||||
@@ -310,6 +299,28 @@
|
||||
"path": "/extending-nx/recipes/migration-generators",
|
||||
"tags": ["create-your-own-plugin"]
|
||||
},
|
||||
"/extending-nx/recipes/local-executors": {
|
||||
"id": "local-executors",
|
||||
"name": "Write a Simple Executor",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/plugins/local-executors",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/recipes/local-executors",
|
||||
"tags": []
|
||||
},
|
||||
"/extending-nx/recipes/compose-executors": {
|
||||
"id": "compose-executors",
|
||||
"name": "Compose Executors",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/plugins/compose-executors",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/recipes/compose-executors",
|
||||
"tags": []
|
||||
},
|
||||
"/extending-nx/recipes/create-preset": {
|
||||
"id": "create-preset",
|
||||
"name": "Create a Preset",
|
||||
@@ -334,7 +345,7 @@
|
||||
},
|
||||
"/extending-nx/recipes/project-graph-plugins": {
|
||||
"id": "project-graph-plugins",
|
||||
"name": "Modify the Project Graph",
|
||||
"name": "Infer Tasks or Projects",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/plugins/project-graph-plugins",
|
||||
@@ -342,5 +353,16 @@
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/recipes/project-graph-plugins",
|
||||
"tags": ["create-your-own-plugin", "explore-graph", "inferred-tasks"]
|
||||
},
|
||||
"/extending-nx/recipes/publish-plugin": {
|
||||
"id": "publish-plugin",
|
||||
"name": "Publish a Plugin",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/plugins/publish-plugin",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/extending-nx/recipes/publish-plugin",
|
||||
"tags": ["create-your-own-plugin"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1177,6 +1177,14 @@
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Migrate to Inferred Tasks (Project Crystal)",
|
||||
"path": "/recipes/running-tasks/convert-to-inferred",
|
||||
"id": "convert-to-inferred",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"disableCollapsible": false
|
||||
@@ -1962,6 +1970,14 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "File Based Versioning (Version Plans)",
|
||||
"path": "/recipes/nx-release/file-based-versioning-version-plans",
|
||||
"id": "file-based-versioning-version-plans",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Configure Custom Registries",
|
||||
"path": "/recipes/nx-release/configure-custom-registries",
|
||||
@@ -2175,6 +2191,14 @@
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Migrate to Inferred Tasks (Project Crystal)",
|
||||
"path": "/recipes/running-tasks/convert-to-inferred",
|
||||
"id": "convert-to-inferred",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"disableCollapsible": false
|
||||
@@ -2267,6 +2291,14 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Migrate to Inferred Tasks (Project Crystal)",
|
||||
"path": "/recipes/running-tasks/convert-to-inferred",
|
||||
"id": "convert-to-inferred",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Adopting Nx",
|
||||
"path": "/recipes/adopting-nx",
|
||||
@@ -3681,6 +3713,14 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "File Based Versioning (Version Plans)",
|
||||
"path": "/recipes/nx-release/file-based-versioning-version-plans",
|
||||
"id": "file-based-versioning-version-plans",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Configure Custom Registries",
|
||||
"path": "/recipes/nx-release/configure-custom-registries",
|
||||
@@ -3772,6 +3812,14 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "File Based Versioning (Version Plans)",
|
||||
"path": "/recipes/nx-release/file-based-versioning-version-plans",
|
||||
"id": "file-based-versioning-version-plans",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Configure Custom Registries",
|
||||
"path": "/recipes/nx-release/configure-custom-registries",
|
||||
@@ -4946,7 +4994,7 @@
|
||||
"isExternal": false,
|
||||
"children": [
|
||||
{
|
||||
"name": "Getting Started with Plugins",
|
||||
"name": "Extending Nx with Plugins",
|
||||
"path": "/extending-nx/intro/getting-started",
|
||||
"id": "getting-started",
|
||||
"isExternal": false,
|
||||
@@ -4957,7 +5005,7 @@
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Getting Started with Plugins",
|
||||
"name": "Extending Nx with Plugins",
|
||||
"path": "/extending-nx/intro/getting-started",
|
||||
"id": "getting-started",
|
||||
"isExternal": false,
|
||||
@@ -4965,23 +5013,23 @@
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "5 Min Tutorials",
|
||||
"name": "Tutorials",
|
||||
"path": "/extending-nx/tutorials",
|
||||
"id": "tutorials",
|
||||
"isExternal": false,
|
||||
"children": [
|
||||
{
|
||||
"name": "Create a Local Plugin",
|
||||
"path": "/extending-nx/tutorials/create-plugin",
|
||||
"id": "create-plugin",
|
||||
"name": "Enforce Organizational Best Practices",
|
||||
"path": "/extending-nx/tutorials/organization-specific-plugin",
|
||||
"id": "organization-specific-plugin",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Maintain a Published Plugin",
|
||||
"path": "/extending-nx/tutorials/publish-plugin",
|
||||
"id": "publish-plugin",
|
||||
"name": "Create a Tooling Plugin",
|
||||
"path": "/extending-nx/tutorials/tooling-plugin",
|
||||
"id": "tooling-plugin",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
@@ -4990,17 +5038,17 @@
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Create a Local Plugin",
|
||||
"path": "/extending-nx/tutorials/create-plugin",
|
||||
"id": "create-plugin",
|
||||
"name": "Enforce Organizational Best Practices",
|
||||
"path": "/extending-nx/tutorials/organization-specific-plugin",
|
||||
"id": "organization-specific-plugin",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Maintain a Published Plugin",
|
||||
"path": "/extending-nx/tutorials/publish-plugin",
|
||||
"id": "publish-plugin",
|
||||
"name": "Create a Tooling Plugin",
|
||||
"path": "/extending-nx/tutorials/tooling-plugin",
|
||||
"id": "tooling-plugin",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
@@ -5011,22 +5059,6 @@
|
||||
"id": "recipes",
|
||||
"isExternal": false,
|
||||
"children": [
|
||||
{
|
||||
"name": "Write a Simple Executor",
|
||||
"path": "/extending-nx/recipes/local-executors",
|
||||
"id": "local-executors",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Compose Executors",
|
||||
"path": "/extending-nx/recipes/compose-executors",
|
||||
"id": "compose-executors",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Write a Simple Generator",
|
||||
"path": "/extending-nx/recipes/local-generators",
|
||||
@@ -5075,6 +5107,22 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Write a Simple Executor",
|
||||
"path": "/extending-nx/recipes/local-executors",
|
||||
"id": "local-executors",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Compose Executors",
|
||||
"path": "/extending-nx/recipes/compose-executors",
|
||||
"id": "compose-executors",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Create a Preset",
|
||||
"path": "/extending-nx/recipes/create-preset",
|
||||
@@ -5092,32 +5140,24 @@
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Modify the Project Graph",
|
||||
"name": "Infer Tasks or Projects",
|
||||
"path": "/extending-nx/recipes/project-graph-plugins",
|
||||
"id": "project-graph-plugins",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Publish a Plugin",
|
||||
"path": "/extending-nx/recipes/publish-plugin",
|
||||
"id": "publish-plugin",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Write a Simple Executor",
|
||||
"path": "/extending-nx/recipes/local-executors",
|
||||
"id": "local-executors",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Compose Executors",
|
||||
"path": "/extending-nx/recipes/compose-executors",
|
||||
"id": "compose-executors",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Write a Simple Generator",
|
||||
"path": "/extending-nx/recipes/local-generators",
|
||||
@@ -5166,6 +5206,22 @@
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Write a Simple Executor",
|
||||
"path": "/extending-nx/recipes/local-executors",
|
||||
"id": "local-executors",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Compose Executors",
|
||||
"path": "/extending-nx/recipes/compose-executors",
|
||||
"id": "compose-executors",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Create a Preset",
|
||||
"path": "/extending-nx/recipes/create-preset",
|
||||
@@ -5183,12 +5239,20 @@
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Modify the Project Graph",
|
||||
"name": "Infer Tasks or Projects",
|
||||
"path": "/extending-nx/recipes/project-graph-plugins",
|
||||
"id": "project-graph-plugins",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "Publish a Plugin",
|
||||
"path": "/extending-nx/recipes/publish-plugin",
|
||||
"id": "publish-plugin",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1608,6 +1608,17 @@
|
||||
"isExternal": false,
|
||||
"path": "/recipes/running-tasks/skipping-cache",
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"id": "convert-to-inferred",
|
||||
"name": "Migrate to Inferred Tasks (Project Crystal)",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/running-tasks/convert-to-inferred",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/recipes/running-tasks/convert-to-inferred",
|
||||
"tags": ["inferred-tasks", "automate-updating-dependencies"]
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
@@ -2681,6 +2692,17 @@
|
||||
"path": "/recipes/nx-release/customize-conventional-commit-types",
|
||||
"tags": ["nx-release"]
|
||||
},
|
||||
{
|
||||
"id": "file-based-versioning-version-plans",
|
||||
"name": "File Based Versioning (Version Plans)",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/nx-release/file-based-versioning-version-plans",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/recipes/nx-release/file-based-versioning-version-plans",
|
||||
"tags": ["nx-release"]
|
||||
},
|
||||
{
|
||||
"id": "configure-custom-registries",
|
||||
"name": "Configure Custom Registries",
|
||||
@@ -2974,6 +2996,17 @@
|
||||
"isExternal": false,
|
||||
"path": "/recipes/running-tasks/skipping-cache",
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
"id": "convert-to-inferred",
|
||||
"name": "Migrate to Inferred Tasks (Project Crystal)",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/running-tasks/convert-to-inferred",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/recipes/running-tasks/convert-to-inferred",
|
||||
"tags": ["inferred-tasks", "automate-updating-dependencies"]
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
@@ -3101,6 +3134,17 @@
|
||||
"path": "/recipes/running-tasks/skipping-cache",
|
||||
"tags": []
|
||||
},
|
||||
"/recipes/running-tasks/convert-to-inferred": {
|
||||
"id": "convert-to-inferred",
|
||||
"name": "Migrate to Inferred Tasks (Project Crystal)",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/running-tasks/convert-to-inferred",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/recipes/running-tasks/convert-to-inferred",
|
||||
"tags": ["inferred-tasks", "automate-updating-dependencies"]
|
||||
},
|
||||
"/recipes/adopting-nx": {
|
||||
"id": "adopting-nx",
|
||||
"name": "Adopting Nx",
|
||||
@@ -5038,6 +5082,17 @@
|
||||
"path": "/recipes/nx-release/customize-conventional-commit-types",
|
||||
"tags": ["nx-release"]
|
||||
},
|
||||
{
|
||||
"id": "file-based-versioning-version-plans",
|
||||
"name": "File Based Versioning (Version Plans)",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/nx-release/file-based-versioning-version-plans",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/recipes/nx-release/file-based-versioning-version-plans",
|
||||
"tags": ["nx-release"]
|
||||
},
|
||||
{
|
||||
"id": "configure-custom-registries",
|
||||
"name": "Configure Custom Registries",
|
||||
@@ -5164,6 +5219,17 @@
|
||||
"path": "/recipes/nx-release/customize-conventional-commit-types",
|
||||
"tags": ["nx-release"]
|
||||
},
|
||||
"/recipes/nx-release/file-based-versioning-version-plans": {
|
||||
"id": "file-based-versioning-version-plans",
|
||||
"name": "File Based Versioning (Version Plans)",
|
||||
"description": "",
|
||||
"mediaImage": "",
|
||||
"file": "shared/recipes/nx-release/file-based-versioning-version-plans",
|
||||
"itemList": [],
|
||||
"isExternal": false,
|
||||
"path": "/recipes/nx-release/file-based-versioning-version-plans",
|
||||
"tags": ["nx-release"]
|
||||
},
|
||||
"/recipes/nx-release/configure-custom-registries": {
|
||||
"id": "configure-custom-registries",
|
||||
"name": "Configure Custom Registries",
|
||||
|
||||
@@ -269,7 +269,7 @@
|
||||
"description": "",
|
||||
"file": "shared/recipes/plugins/project-graph-plugins",
|
||||
"id": "project-graph-plugins",
|
||||
"name": "Modify the Project Graph",
|
||||
"name": "Infer Tasks or Projects",
|
||||
"path": "/extending-nx/recipes/project-graph-plugins"
|
||||
},
|
||||
{
|
||||
@@ -353,6 +353,13 @@
|
||||
"name": "Automate Updating Dependencies",
|
||||
"path": "/features/automate-updating-dependencies"
|
||||
},
|
||||
{
|
||||
"description": "",
|
||||
"file": "shared/recipes/running-tasks/convert-to-inferred",
|
||||
"id": "convert-to-inferred",
|
||||
"name": "Migrate to Inferred Tasks (Project Crystal)",
|
||||
"path": "/recipes/running-tasks/convert-to-inferred"
|
||||
},
|
||||
{
|
||||
"description": "",
|
||||
"file": "shared/recipes/tips-n-tricks/keep-nx-versions-in-sync",
|
||||
@@ -490,6 +497,13 @@
|
||||
"name": "Customize Conventional Commit Types",
|
||||
"path": "/recipes/nx-release/customize-conventional-commit-types"
|
||||
},
|
||||
{
|
||||
"description": "",
|
||||
"file": "shared/recipes/nx-release/file-based-versioning-version-plans",
|
||||
"id": "file-based-versioning-version-plans",
|
||||
"name": "File Based Versioning (Version Plans)",
|
||||
"path": "/recipes/nx-release/file-based-versioning-version-plans"
|
||||
},
|
||||
{
|
||||
"description": "",
|
||||
"file": "shared/recipes/nx-release/configure-custom-registries",
|
||||
@@ -585,11 +599,18 @@
|
||||
"name": "Inferred Tasks",
|
||||
"path": "/concepts/inferred-tasks"
|
||||
},
|
||||
{
|
||||
"description": "",
|
||||
"file": "shared/recipes/running-tasks/convert-to-inferred",
|
||||
"id": "convert-to-inferred",
|
||||
"name": "Migrate to Inferred Tasks (Project Crystal)",
|
||||
"path": "/recipes/running-tasks/convert-to-inferred"
|
||||
},
|
||||
{
|
||||
"description": "",
|
||||
"file": "shared/recipes/plugins/project-graph-plugins",
|
||||
"id": "project-graph-plugins",
|
||||
"name": "Modify the Project Graph",
|
||||
"name": "Infer Tasks or Projects",
|
||||
"path": "/extending-nx/recipes/project-graph-plugins"
|
||||
}
|
||||
],
|
||||
@@ -626,9 +647,16 @@
|
||||
"description": "",
|
||||
"file": "shared/recipes/plugins/project-graph-plugins",
|
||||
"id": "project-graph-plugins",
|
||||
"name": "Modify the Project Graph",
|
||||
"name": "Infer Tasks or Projects",
|
||||
"path": "/extending-nx/recipes/project-graph-plugins"
|
||||
},
|
||||
{
|
||||
"description": "",
|
||||
"file": "shared/recipes/plugins/publish-plugin",
|
||||
"id": "publish-plugin",
|
||||
"name": "Publish a Plugin",
|
||||
"path": "/extending-nx/recipes/publish-plugin"
|
||||
},
|
||||
{
|
||||
"description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, Playwright and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.",
|
||||
"file": "generated/packages/angular/documents/nx-devkit-angular-devkit",
|
||||
|
||||
@@ -86,7 +86,7 @@ The `targetName`, `ciTargetName`, `componentTestingTargetName`, and `open-cypres
|
||||
|
||||
### Splitting E2E tasks by file
|
||||
|
||||
The `@nx/cypress/plugin` will automatically split your e2e tasks by file. You can read more about this feature [here](/ci/features/split-e2e-tasks).
|
||||
The `@nx/cypress/plugin` will automatically split your e2e tasks by file. You can read more about the Atomizer feature [here](/ci/features/split-e2e-tasks).
|
||||
|
||||
To enable e2e task splitting, make sure there is a `ciWebServerCommand` property set in your `cypress.config.ts` file. It will look something like this:
|
||||
|
||||
|
||||
@@ -68,7 +68,8 @@ The `@nx/gradle` is configured in the `plugins` array in `nx.json`.
|
||||
"options": {
|
||||
"testTargetName": "test",
|
||||
"classesTargetName": "classes",
|
||||
"buildTargetName": "build"
|
||||
"buildTargetName": "build",
|
||||
"ciTargetName": "test-ci"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -77,6 +78,23 @@ The `@nx/gradle` is configured in the `plugins` array in `nx.json`.
|
||||
|
||||
Once a Gradle configuration file has been identified, the targets are created with the name you specify under `testTargetName`, `classesTargetName` or `buildTargetName` in the `nx.json` `plugins` array. The default names for the inferred targets are `test`, `classes` and `build`.
|
||||
|
||||
### Splitting E2E Tests
|
||||
|
||||
The `@nx/gradle` plugin will automatically split your e2e tasks by file if you provide a `ciTargetName`. You can read more about the Atomizer feature [here](/ci/features/split-e2e-tasks). This will create a target with that name which can be used in CI to run the tests for each file in a distributed fashion.
|
||||
|
||||
```json {% fileName="nx.json" highlightLines=[6] %}
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/gradle",
|
||||
"options": {
|
||||
"ciTargetName": "test-ci"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## View Inferred Tasks
|
||||
|
||||
To view inferred tasks for a project, open the [project details view](/features/explore-graph#explore-projects-in-your-workspace) in Nx Console or run `nx show project my-project --web` in the command line.
|
||||
|
||||
@@ -70,7 +70,7 @@ within the same workspace. In this case, you can configure the `@nx/jest/plugin`
|
||||
### Splitting E2E Tests
|
||||
|
||||
If Jest is used to run E2E tests, you can enable [splitting the tasks](/ci/features/split-e2e-tasks) by file to get
|
||||
improved caching, distribution, and retrying flaky tests. Enable this, by providing a `ciTargetName`. This will create a
|
||||
improved caching, distribution, and retrying flaky tests. Enable this Atomizer feature by providing a `ciTargetName`. This will create a
|
||||
target with that name which can be used in CI to run the tests for each file in a distributed fashion.
|
||||
|
||||
```json {% fileName="nx.json" %}
|
||||
|
||||
@@ -65,6 +65,24 @@ The `@nx/playwright/plugin` is configured in the `plugins` array in `nx.json`.
|
||||
|
||||
The `targetName` and `ciTargetName` options control the name of the inferred Playwright tasks. The default names are `e2e` and `e2e-ci`.
|
||||
|
||||
### Splitting E2E Tests
|
||||
|
||||
The `@nx/playwright/plugin` will automatically split your e2e tasks by file if you provide a `ciTargetName`. You can read more about the Atomizer feature [here](/ci/features/split-e2e-tasks). This will create a target with that name which can be used in CI to run the tests for each file in a distributed fashion.
|
||||
|
||||
```json {% fileName="nx.json" %}
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/playwright/plugin",
|
||||
"options": {
|
||||
"targetName": "e2e",
|
||||
"ciTargetName": "e2e-ci"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Splitting E2E tasks by file
|
||||
|
||||
The `@nx/playwright/plugin` will automatically split your e2e tasks by file. You can read more about this feature [here](/ci/features/split-e2e-tasks).
|
||||
|
||||
@@ -104,18 +104,7 @@
|
||||
},
|
||||
"reporter": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"list",
|
||||
"line",
|
||||
"dot",
|
||||
"json",
|
||||
"junit",
|
||||
"null",
|
||||
"github",
|
||||
"html",
|
||||
"blob"
|
||||
],
|
||||
"description": "Reporter to use, comma-separated, can be 'list', 'line', 'dot', 'json', 'junit', 'null', 'github', 'html', 'blob'. To configure reporter options, use the playwright configuration."
|
||||
"description": "Common Reporter values to use, comma-separated, 'list', 'line', 'dot', 'json', 'junit', 'null', 'github', 'html', 'blob'. To configure reporter options, use the playwright configuration."
|
||||
},
|
||||
"retries": {
|
||||
"type": "number",
|
||||
|
||||
@@ -431,6 +431,12 @@
|
||||
"name": "Skip Task Caching",
|
||||
"id": "skipping-cache",
|
||||
"file": "shared/recipes/running-tasks/skipping-cache"
|
||||
},
|
||||
{
|
||||
"name": "Migrate to Inferred Tasks (Project Crystal)",
|
||||
"id": "convert-to-inferred",
|
||||
"tags": ["inferred-tasks", "automate-updating-dependencies"],
|
||||
"file": "shared/recipes/running-tasks/convert-to-inferred"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1015,6 +1021,12 @@
|
||||
"tags": ["nx-release"],
|
||||
"file": "shared/recipes/nx-release/customize-conventional-commit-types"
|
||||
},
|
||||
{
|
||||
"name": "File Based Versioning (Version Plans)",
|
||||
"id": "file-based-versioning-version-plans",
|
||||
"tags": ["nx-release"],
|
||||
"file": "shared/recipes/nx-release/file-based-versioning-version-plans"
|
||||
},
|
||||
{
|
||||
"name": "Configure Custom Registries",
|
||||
"id": "configure-custom-registries",
|
||||
@@ -1457,7 +1469,7 @@
|
||||
"description": "Learn about plugins.",
|
||||
"itemList": [
|
||||
{
|
||||
"name": "Getting Started with Plugins",
|
||||
"name": "Extending Nx with Plugins",
|
||||
"id": "getting-started",
|
||||
"description": "Learn how to extend Nx by creating and releasing your own Nx plugin.",
|
||||
"file": "shared/plugins/intro"
|
||||
@@ -1465,19 +1477,19 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "5 Min Tutorials",
|
||||
"name": "Tutorials",
|
||||
"id": "tutorials",
|
||||
"description": "Get started with plugins",
|
||||
"itemList": [
|
||||
{
|
||||
"name": "Create a Local Plugin",
|
||||
"id": "create-plugin",
|
||||
"file": "shared/plugins/create-plugin"
|
||||
"name": "Enforce Organizational Best Practices",
|
||||
"id": "organization-specific-plugin",
|
||||
"file": "shared/plugins/organization-specific-plugin"
|
||||
},
|
||||
{
|
||||
"name": "Maintain a Published Plugin",
|
||||
"id": "publish-plugin",
|
||||
"file": "shared/plugins/maintain-published-plugin"
|
||||
"name": "Create a Tooling Plugin",
|
||||
"id": "tooling-plugin",
|
||||
"file": "shared/plugins/tooling-plugin"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1486,18 +1498,6 @@
|
||||
"id": "recipes",
|
||||
"description": "Focused instructions to complete a specific task",
|
||||
"itemList": [
|
||||
{
|
||||
"name": "Write a Simple Executor",
|
||||
"id": "local-executors",
|
||||
"tags": [],
|
||||
"file": "shared/recipes/plugins/local-executors"
|
||||
},
|
||||
{
|
||||
"name": "Compose Executors",
|
||||
"id": "compose-executors",
|
||||
"tags": [],
|
||||
"file": "shared/recipes/plugins/compose-executors"
|
||||
},
|
||||
{
|
||||
"name": "Write a Simple Generator",
|
||||
"id": "local-generators",
|
||||
@@ -1534,6 +1534,18 @@
|
||||
"tags": ["create-your-own-plugin"],
|
||||
"file": "shared/recipes/plugins/migration-generators"
|
||||
},
|
||||
{
|
||||
"name": "Write a Simple Executor",
|
||||
"id": "local-executors",
|
||||
"tags": [],
|
||||
"file": "shared/recipes/plugins/local-executors"
|
||||
},
|
||||
{
|
||||
"name": "Compose Executors",
|
||||
"id": "compose-executors",
|
||||
"tags": [],
|
||||
"file": "shared/recipes/plugins/compose-executors"
|
||||
},
|
||||
{
|
||||
"name": "Create a Preset",
|
||||
"id": "create-preset",
|
||||
@@ -1547,7 +1559,7 @@
|
||||
"file": "shared/recipes/plugins/create-install-package"
|
||||
},
|
||||
{
|
||||
"name": "Modify the Project Graph",
|
||||
"name": "Infer Tasks or Projects",
|
||||
"id": "project-graph-plugins",
|
||||
"tags": [
|
||||
"create-your-own-plugin",
|
||||
@@ -1555,6 +1567,12 @@
|
||||
"inferred-tasks"
|
||||
],
|
||||
"file": "shared/recipes/plugins/project-graph-plugins"
|
||||
},
|
||||
{
|
||||
"name": "Publish a Plugin",
|
||||
"id": "publish-plugin",
|
||||
"tags": ["create-your-own-plugin"],
|
||||
"file": "shared/recipes/plugins/publish-plugin"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ You could manually address these problems by splitting your e2e tests into small
|
||||
|
||||
## Set up
|
||||
|
||||
To enable atomized tasks, you need to turn on [inferred tasks](/concepts/inferred-tasks#existing-nx-workspaces) for the [@nx/cypress](/nx-api/cypress#splitting-e2e-tasks-by-file), [@nx/playwright](/nx-api/playwright#splitting-e2e-tasks-by-file), [@nx/jest](/nx-api/jest#splitting-e2e-tests) or [@nx/gradle](/nx-api/gradle) plugins. Run this command to set up inferred tasks:
|
||||
To enable atomized tasks, you need to turn on [inferred tasks](/concepts/inferred-tasks#existing-nx-workspaces) for the [@nx/cypress](/nx-api/cypress#splitting-e2e-tasks-by-file), [@nx/playwright](/nx-api/playwright#splitting-e2e-tests), [@nx/jest](/nx-api/jest#splitting-e2e-tests) or [@nx/gradle](/nx-api/gradle#splitting-e2e-tests) plugins. Run this command to set up inferred tasks:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="Cypress" %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Nx CLI and Access Tokens
|
||||
|
||||
The permissions and membership define what developers can access on nx.app. They don't affect what happens when you run Nx commands locally or on CI. To manage that, you need to provision access tokens. To do that, go to Workspace Options / Manage Access Tokens.
|
||||
The permissions and membership define what developers can access on [Nx Cloud](https://nx.app). They don't affect what happens when you run Nx commands locally or on CI. To manage that, you need to provision access tokens. To do that, go to Workspace Options / Manage Access Tokens.
|
||||
|
||||
## Types of Access Tokens
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ launch-templates:
|
||||
- name: Restore Node Modules Cache
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/cache/main.yaml'
|
||||
inputs:
|
||||
env: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
|
||||
key: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
|
||||
paths: 'node_modules'
|
||||
base_branch: 'main'
|
||||
```
|
||||
@@ -221,13 +221,13 @@ launch-templates:
|
||||
# the cache step requires configuration via env vars
|
||||
# https://github.com/nrwl/nx-cloud-workflows/tree/main/workflow-steps/cache#options
|
||||
inputs:
|
||||
env: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
|
||||
key: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
|
||||
paths: 'node_modules'
|
||||
base_branch: 'main'
|
||||
- name: Restore Browser Binary Cache
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/cache/main.yaml'
|
||||
inputs:
|
||||
env: 'package-lock.json|yarn.lock|pnpm-lock.yaml|"browsers"'
|
||||
key: 'package-lock.json|yarn.lock|pnpm-lock.yaml|"browsers"'
|
||||
paths: |
|
||||
'~/.cache/Cypress'
|
||||
'~/.cache/ms-playwright'
|
||||
@@ -272,13 +272,13 @@ launch-templates:
|
||||
- name: Restore Node Modules Cache
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/cache/main.yaml'
|
||||
inputs:
|
||||
env: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
|
||||
key: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
|
||||
paths: 'node_modules'
|
||||
base_branch: 'main'
|
||||
- name: Restore Browser Binary Cache
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/cache/main.yaml'
|
||||
inputs:
|
||||
env: 'package-lock.json|yarn.lock|pnpm-lock.yaml|"browsers"'
|
||||
key: 'package-lock.json|yarn.lock|pnpm-lock.yaml|"browsers"'
|
||||
paths: |
|
||||
'~/.cache/Cypress'
|
||||
'~/.cache/ms-playwright'
|
||||
@@ -310,7 +310,7 @@ launch-templates:
|
||||
- name: Restore Node Modules Cache
|
||||
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/cache/main.yaml'
|
||||
inputs:
|
||||
env: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
|
||||
key: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
|
||||
paths: 'node_modules'
|
||||
base_branch: 'main'
|
||||
- name: Install Node Modules
|
||||
|
||||
@@ -54,11 +54,11 @@ For more information on the different types of inputs and how to configure input
|
||||
|
||||
## What is Cached
|
||||
|
||||
Nx works on the process level. Regardless of the tools used to build/test/lint/etc.. your project, the results are cached.
|
||||
Nx cache works on the process level. Regardless of the tools used to build/test/lint/etc.. your project, the results are cached. This includes:
|
||||
|
||||
It collects terminal output when running tasks. All the terminal output is cached and then replayed during a cache hit.
|
||||
|
||||
Nx can also cache the files generated by a task. The list of files/folders is listed in the `outputs` property of the project's `package.json` or `project.json`:
|
||||
- **Terminal output:** The terminal output generated when running a task. This includes logs, warnings, and errors.
|
||||
- **Task artifacts:** The output files of a task defined in the [`outputs` property of your project configuration](/recipes/running-tasks/configure-outputs). For example the build output, test results, or linting reports.
|
||||
- **Hash:** The hash of the inputs to the computation. The inputs include the source code, runtime values, and command line arguments. Note that the hash is included in the cache, but the actual inputs are not.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="package.json" %}
|
||||
|
||||
@@ -74,15 +74,13 @@ More details about how to override task configuration is available in these reci
|
||||
|
||||
## Existing Nx Workspaces
|
||||
|
||||
If you have an existing Nx Workspace and upgrade to Nx 18, the migration generator will automatically add `NX_ADD_PLUGINS=false` to your `.env` file. This environment variable allows you to continue to use Nx without inferred tasks. Alternatively, you can set the `useInferencePlugins` property to `false` in `nx.json`.
|
||||
If you have an existing Nx Workspace and upgrade to the latest Nx version, a migration will automatically set `useInferencePlugins` to `false` in `nx.json`. This property allows you to continue to use Nx without inferred tasks.
|
||||
|
||||
We are working on creating migrations for existing workspaces to start switching to inferred tasks, where desired.
|
||||
|
||||
When `NX_ADD_PLUGINS` or `useInferencePlugins` is `false`:
|
||||
When `useInferencePlugins` is `false`:
|
||||
|
||||
1. A newly generated project will have all targets defined with executors - not with inferred tasks.
|
||||
2. Running `nx add @nx/some-plugin` will not create a plugin entry for `@nx/some-plugin` in the `nx.json` file. (So that plugin will not create inferred tasks.)
|
||||
|
||||
If your `.env` file is ignored by git, ensure that everyone in your organization sets `NX_ADD_PLUGINS=false`. This could be done by updating a shared `.env` file or setting the environment variable in everyone's `.bashrc` file.
|
||||
If you want to **migrate** your projects to use inferred tasks, follow the recipe for [migrating to inferred tasks](/recipes/running-tasks/convert-to-inferred).
|
||||
|
||||
Even once a repository has fully embraced inferred tasks, `project.json` and executors will still be useful. The `project.json` file is needed to modify inferred task options and to define tasks that can not be inferred. Some executors perform tasks that can not be accomplished by running a tool directly from the command line (i.e. [TypeScript batch mode](/recipes/tips-n-tricks/enable-tsc-batch-mode)).
|
||||
|
||||
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 114 KiB |
@@ -23,5 +23,5 @@ For example, plugins can accomplish the following:
|
||||
{% cards %}
|
||||
{% card title="Official Plugins" description="The API documentation for Nx Plugins maintained by the Nx core team" type="documentation" url="/nx-api" /%}
|
||||
{% card title="Community Plugins" description="Browse the plugin registry to discover plugins created by the community" type="documentation" url="/plugin-registry" /%}
|
||||
{% card title="Build Your Own Plugin" description="Build your own plugin to use internally or share with the community" type="documentation" url="/extending-nx/tutorials/create-plugin" /%}
|
||||
{% card title="Build Your Own Plugin" description="Build your own plugin to use internally or share with the community" type="documentation" url="/extending-nx/tutorials/organization-specific-plugin" /%}
|
||||
{% /cards %}
|
||||
|
||||
@@ -1,87 +1,71 @@
|
||||
# Use Remote Caching (Nx Replay)
|
||||
|
||||
By default Nx [caches task computations locally](/features/cache-task-results). However, to benefit from the cache across your team and in particular on CI, the computation cache can also be distributed across multiple machines.
|
||||
|
||||
The **Nx Replay** feature of Nx Cloud is a fast, secure and zero-config implementation of remote caching.
|
||||
Repeatedly rebuilding and retesting the same code is costly — not just in terms of wasted resources, but also in terms of developer time. To solve this, Nx includes a sophisticated computation caching system that ensures **code is never rebuilt twice**, saving you both time and resources.
|
||||
|
||||

|
||||
|
||||
In this diagram, Teika runs the build once on his machine, then CI, Kimiko and James can use the cached artifact from Teika instead of re-executing the same work.
|
||||
By default, Nx [caches task computations locally](/features/cache-task-results), but the biggest benefit comes from **sharing this cache across your team and in CI**.
|
||||
|
||||
## Setting Up Nx Cloud
|
||||
- **Zero config** and **secure** by default
|
||||
- Drastically **speeds up task execution times** during local development, and more critically in CI
|
||||
- **Saves money on CI/CD costs** by reducing the number of tasks that need to be executed (we observed 30-70% faster CI & half the cost)
|
||||
|
||||
To use **Nx Replay** you need to connect your workspace to Nx Cloud. See the [connect to Nx Cloud recipe](/ci/intro/connect-to-nx-cloud).
|
||||
Nx **restores terminal output, along with the files and artifacts** created from running the task (e.g., your build or dist directory). If you want to learn more about the conceptual model behind Nx's caching, read [How Caching Works](/concepts/how-caching-works).
|
||||
|
||||
## See Remote Caching in Action
|
||||
## Configure Remote Caching
|
||||
|
||||
To see the remote cache in action, run:
|
||||
To use **Nx Replay**, you need to connect your workspace to Nx Cloud.
|
||||
|
||||
```{% command="nx build header && nx reset && nx build header"%}
|
||||
> nx run header:build
|
||||
|
||||
> header@0.0.0 build
|
||||
> rimraf dist && rollup --config
|
||||
|
||||
src/index.tsx → dist...
|
||||
created dist in 786ms
|
||||
|
||||
—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|
||||
|
||||
NX Successfully ran target build for project header (2s)
|
||||
|
||||
See logs and investigate cache misses at https://cloud.nx.app/runs/k0HDHACpL8
|
||||
|
||||
NX Resetting the Nx workspace cache and stopping the Nx Daemon.
|
||||
|
||||
This might take a few minutes.
|
||||
|
||||
NX Daemon Server - Stopped
|
||||
|
||||
NX Successfully reset the Nx workspace.
|
||||
|
||||
|
||||
> nx run header:build [remote cache]
|
||||
|
||||
|
||||
> header@0.0.0 build
|
||||
> rimraf dist && rollup --config
|
||||
|
||||
|
||||
src/index.tsx → dist...
|
||||
created dist in 786ms
|
||||
|
||||
—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|
||||
|
||||
NX Successfully ran target build for project header (664ms)
|
||||
|
||||
Nx read the output from the cache instead of running the command for 1 out of 1 tasks.
|
||||
|
||||
Nx Cloud made it possible to reuse header: https://nx.app/runs/P0X6ZGTkqZ
|
||||
```shell
|
||||
npx nx connect
|
||||
```
|
||||
|
||||
## Benefits of Nx Replay
|
||||
See the [connect to Nx Cloud recipe](/ci/intro/connect-to-nx-cloud) for all the details.
|
||||
|
||||
There are two ways that Nx Replay directly benefits your organization.
|
||||
## Why use Remote Caching (Nx Replay)?
|
||||
|
||||
### 1. Speed Up CI Pipelines for Modified PRs
|
||||
Nx Replay directly benefits your organization by:
|
||||
|
||||
The first time a PR goes through the CI pipeline, the [affected](/ci/features/affected) command provides most of the time savings. The `affected` command compares the PR against the `main` branch and only runs tasks for projects that could have been affected by the code changes. Unfortunately, all the projects affected by the first CI pipeline for a PR will continue to be affected by all future commits to that PR. This is because `affected` compares the current commit with the `main` branch every time.
|
||||
- **Speeding up CI pipelines:** With Nx Replay, tasks that have already been executed in a PR’s initial CI pipeline run can **reuse cached results in subsequent runs**. This reduces the need to re-run unaffected tasks, significantly speeding up the CI process for modified PRs. This benefit complements the [affected command](/ci/features/affected), which optimizes pipelines by only running tasks for projects that could be impacted by code changes.
|
||||
|
||||
With Nx Replay enabled, any tasks that were run during the first pipeline and not affected by the second commit would reuse the cached results from the first pipeline instead of re-running the task. This gives subsequent pipeline runs a mechanism similar to affected that will [reduce the wasted time in CI](/ci/concepts/reduce-waste).
|
||||
- **Boosting local developer efficiency:** Depending on [how cache permissions](/ci/recipes/security/access-tokens) are set for your workspace, developers can reuse cached results from CI on their local machines. As a result, tasks like builds and tests can complete instantly if they were already executed in CI. This accelerates developer workflows without any extra steps required.
|
||||
|
||||
### 2. Reuse Cached Results from CI on Developer Machines
|
||||
- **Enabling Nx Agents:** Nx Replay is crucial for [Nx Agents](/ci/features/distribute-task-execution) to function efficiently. Nx Agents leverage remote caching as a **transport mechanism** for transferring task artifacts between machines as it distributes tasks. When a task depends on another task that may have been executed on a different agent, Nx Replay ensures the necessary artifacts are transferred seamlessly. This allows each agent to execute only its assigned tasks while relying on cached results for dependencies, ensuring tasks run only once and are shared across all agents. [Learn more about Nx Agents](/ci/features/distribute-task-execution).
|
||||
|
||||
If a task has been executed in CI, a developer running that same task locally can reuse the task result instead of actually running the task. Here are a couple common scenarios where this happens:
|
||||
## What gets stored?
|
||||
|
||||
1. A developer pulls the latest code from `main` and rebuilds an application. The build finishes instantly and they're ready to start working.
|
||||
2. A developer checks out someone else's PR branch to help troubleshoot a problem. They run the tests and all the successful tests finish instantly. They can focus their debugging time on the few tests that are still failing.
|
||||
Nx Cloud stores the following:
|
||||
|
||||
The best part about Nx Replay is that developers will experience the benefits of it without needing to remember to use it. Some of their tasks will just finish much faster than they normally do.
|
||||
- **Terminal output:** The terminal output generated when running a task. This includes logs, warnings, and errors.
|
||||
- **Task artifacts:** The output files of a task defined in the [`outputs` property of your project configuration](/recipes/running-tasks/configure-outputs). For example, the build output, test results, or linting reports.
|
||||
- **Hash:** The hash of the inputs to the computation. The inputs include the source code, runtime values, and command line arguments. Note that the hash is included in the cache, but the actual inputs are not.
|
||||
|
||||
## Nx Replay Enables Nx Agents
|
||||
Learn more about [how caching works](/concepts/how-caching-works#what-is-cached).
|
||||
|
||||
One more indirect benefit of Nx Replay is that it is critical to the way [Nx Agents](/ci/features/distribute-task-execution) is built. Nx Agents relies heavily on the remote cache in order to ensure that all task artifacts are present on the agent machines where they are needed. Each agent can naively run the dependencies for the tasks it is assigned and rely on Nx Replay to retrieve the cached tasks results for those tasks. Nx Replay ensures that each task will only run on one agent and the results of that task will be shared with every agent that needs them.
|
||||
## Security in Remote Caching
|
||||
|
||||
## Skipping Cloud Cache
|
||||
Since we work with many large corporations (including banks, insurance companies, and governments), we take security very seriously. Nx Cloud provides several features to ensure your data remains safe and secure:
|
||||
|
||||
Similar to how `--skip-nx-cache` will instruct Nx not to use the local cache, passing `--no-cloud` will tell Nx not to use the remote cache from Nx Cloud.
|
||||
- **Immutability:** Each cache entry is immutable, meaning once an entry is created, it cannot be altered. This ensures that cached results cannot be tampered with by malicious parties, preventing the injection of vulnerabilities into your build process.
|
||||
|
||||
- **Access Control via Tokens:** Nx Cloud allows you to [control who can read from and write to the cache](/ci/recipes/security/access-tokens). For example, you can configure these settings to restrict cache write access to your CI pipeline while allowing all developers to only read.
|
||||
|
||||
- **End-to-End Encryption:** Nx Cloud supports end-to-end encryption to protect your data. Task artifacts are encrypted before being sent to the remote cache and decrypted when retrieved. This ensures that even if someone gains access to Nx Cloud servers, they cannot view your stored artifacts. For more details, visit the [encryption documentation](/ci/recipes/security/encryption).
|
||||
|
||||
- **Nx Enterprise (Self-Hosting and EU Regions):** For organizations with specific compliance or data residency requirements, Nx Enterprise offers the option to self-host Nx Cloud on your own infrastructure. Additionally, you can choose to host in EU regions, ensuring that your data complies with regional data protection laws. This is available to our [Nx Enterprise customers](/enterprise).
|
||||
|
||||
- **SOC Certification:** Nx and Nx Cloud are SOC Type 1 and Type 2 certified, providing an additional layer of assurance that your data is handled according to industry-standard security practices. For more details, you can visit our [security page](https://security.nx.app).
|
||||
|
||||
### Configure Caching Access
|
||||
|
||||
Caching access can be restricted in terms of read/write access. You can configure this in your [Nx Cloud dashboard](https://nx.app). Learn more about it [here](/ci/recipes/security/access-tokens).
|
||||
|
||||
## FAQ
|
||||
|
||||
### What if the remote cache is offline?
|
||||
|
||||
Nx Replay automatically syncs the remote cache to the local cache folder. As such, if the remote cache is not available, it will automatically fall back to the local cache or just run the task if it is not cached.
|
||||
|
||||
### Skipping Cloud Cache
|
||||
|
||||
To learn more about how to temporarily skip task caching, head over to [our corresponding docs page](/recipes/running-tasks/skipping-cache#skip-remote-caching-from-nx-cloud).
|
||||
|
||||
@@ -140,8 +140,8 @@ Try one of these tutorials for a full walkthrough of what to do after you instal
|
||||
- [Single React App Tutorial](/getting-started/tutorials/react-standalone-tutorial)
|
||||
- [Single Angular App Tutorial](/getting-started/tutorials/angular-standalone-tutorial)
|
||||
- [Single Vue App Tutorial](/getting-started/tutorials/vue-standalone-tutorial)
|
||||
- [React Monorepo Tutorial](/getting-started/tutorials/react-standalone-tutorial)
|
||||
- [Angular Monorepo Tutorial](/getting-started/tutorials/angular-standalone-tutorial)
|
||||
- [React Monorepo Tutorial](/getting-started/tutorials/react-monorepo-tutorial)
|
||||
- [Angular Monorepo Tutorial](/getting-started/tutorials/angular-monorepo-tutorial)
|
||||
|
||||
## More Documentation
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ Nx is a powerful open-source build system that provides tools and techniques for
|
||||
- **Cache Locally & Remotely**: With [local](/features/cache-task-results) and [remote caching](/ci/features/remote-cache), Nx prevents unnecessary re-runs of tasks, saving you valuable dev time.
|
||||
- **Split E2E Tests and Rerun Flaky Tests**: Nx [automatically splits](/ci/features/split-e2e-tasks) large e2e tests to distribute them across VMs. Nx can also automatically [identify and rerun flaky e2e tests](/ci/features/flaky-tasks).
|
||||
- **Automate Dependency Updates**: if you leverage [Nx plugins](/concepts/nx-plugins) you gain additional features such as [code generation](/features/generate-code) and tools to [automatically upgrade](features/automate-updating-dependencies) your codebase and dependencies.
|
||||
- **Make it Your Own**: Nx is highly customizable and extensible. Fine-tune it by [creating your own plugins](/extending-nx/intro/getting-started) and optionally [share them with the community](/extending-nx/tutorials/publish-plugin#publish-your-nx-plugin).
|
||||
- **Make it Your Own**: Nx is highly customizable and extensible. Fine-tune it by [creating a plugin for your organization](/extending-nx/tutorials/organization-specific-plugin) or [creating a tooling plugin](/extending-nx/tutorials/tooling-plugin).
|
||||
|
||||
<!-- - **Monorepo and Single Projects**: Nx supports both, monorepos as well as single-project (standalone) workspaces. -->
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ The `targetName`, `ciTargetName`, `componentTestingTargetName`, and `open-cypres
|
||||
|
||||
### Splitting E2E tasks by file
|
||||
|
||||
The `@nx/cypress/plugin` will automatically split your e2e tasks by file. You can read more about this feature [here](/ci/features/split-e2e-tasks).
|
||||
The `@nx/cypress/plugin` will automatically split your e2e tasks by file. You can read more about the Atomizer feature [here](/ci/features/split-e2e-tasks).
|
||||
|
||||
To enable e2e task splitting, make sure there is a `ciWebServerCommand` property set in your `cypress.config.ts` file. It will look something like this:
|
||||
|
||||
|
||||
@@ -68,7 +68,8 @@ The `@nx/gradle` is configured in the `plugins` array in `nx.json`.
|
||||
"options": {
|
||||
"testTargetName": "test",
|
||||
"classesTargetName": "classes",
|
||||
"buildTargetName": "build"
|
||||
"buildTargetName": "build",
|
||||
"ciTargetName": "test-ci"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -77,6 +78,23 @@ The `@nx/gradle` is configured in the `plugins` array in `nx.json`.
|
||||
|
||||
Once a Gradle configuration file has been identified, the targets are created with the name you specify under `testTargetName`, `classesTargetName` or `buildTargetName` in the `nx.json` `plugins` array. The default names for the inferred targets are `test`, `classes` and `build`.
|
||||
|
||||
### Splitting E2E Tests
|
||||
|
||||
The `@nx/gradle` plugin will automatically split your e2e tasks by file if you provide a `ciTargetName`. You can read more about the Atomizer feature [here](/ci/features/split-e2e-tasks). This will create a target with that name which can be used in CI to run the tests for each file in a distributed fashion.
|
||||
|
||||
```json {% fileName="nx.json" highlightLines=[6] %}
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/gradle",
|
||||
"options": {
|
||||
"ciTargetName": "test-ci"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## View Inferred Tasks
|
||||
|
||||
To view inferred tasks for a project, open the [project details view](/features/explore-graph#explore-projects-in-your-workspace) in Nx Console or run `nx show project my-project --web` in the command line.
|
||||
|
||||
@@ -70,7 +70,7 @@ within the same workspace. In this case, you can configure the `@nx/jest/plugin`
|
||||
### Splitting E2E Tests
|
||||
|
||||
If Jest is used to run E2E tests, you can enable [splitting the tasks](/ci/features/split-e2e-tasks) by file to get
|
||||
improved caching, distribution, and retrying flaky tests. Enable this, by providing a `ciTargetName`. This will create a
|
||||
improved caching, distribution, and retrying flaky tests. Enable this Atomizer feature by providing a `ciTargetName`. This will create a
|
||||
target with that name which can be used in CI to run the tests for each file in a distributed fashion.
|
||||
|
||||
```json {% fileName="nx.json" %}
|
||||
|
||||
@@ -65,6 +65,24 @@ The `@nx/playwright/plugin` is configured in the `plugins` array in `nx.json`.
|
||||
|
||||
The `targetName` and `ciTargetName` options control the name of the inferred Playwright tasks. The default names are `e2e` and `e2e-ci`.
|
||||
|
||||
### Splitting E2E Tests
|
||||
|
||||
The `@nx/playwright/plugin` will automatically split your e2e tasks by file if you provide a `ciTargetName`. You can read more about the Atomizer feature [here](/ci/features/split-e2e-tasks). This will create a target with that name which can be used in CI to run the tests for each file in a distributed fashion.
|
||||
|
||||
```json {% fileName="nx.json" %}
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "@nx/playwright/plugin",
|
||||
"options": {
|
||||
"targetName": "e2e",
|
||||
"ciTargetName": "e2e-ci"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Splitting E2E tasks by file
|
||||
|
||||
The `@nx/playwright/plugin` will automatically split your e2e tasks by file. You can read more about this feature [here](/ci/features/split-e2e-tasks).
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
# Create a Local Plugin
|
||||
|
||||
To get started with building a local Nx Plugin, install the `@nx/plugin` package and generate a plugin:
|
||||
|
||||
```shell
|
||||
nx g @nx/plugin:plugin my-plugin --directory=tools/my-plugin
|
||||
```
|
||||
|
||||
This will create a `my-plugin` project that contains all your plugin code and `my-plugin-e2e` for e2e tests.
|
||||
|
||||
> If you want to create a new workspace for your plugin, run `npx create-nx-plugin my-plugin`. This command will create a new workspace with `my-plugin` and `e2e` projects set up for you.
|
||||
|
||||
## Generator
|
||||
|
||||
To create a new generator run:
|
||||
|
||||
```shell
|
||||
nx generate @nx/plugin:generator my-generator --directory="tools/my-plugin/src/generators/my-generator"
|
||||
```
|
||||
|
||||
The new generator is located in `/src/generators/my-generator`. The `my-generator.ts` file contains the code that runs the generator. This generator creates a new project using a folder of template files.
|
||||
|
||||
For more information about this sample generator, read the [simple generator recipe](/extending-nx/recipes/local-generators).
|
||||
|
||||
### Generator options
|
||||
|
||||
The `schema.d.ts` file contains all the options that the generator supports. By default, it includes `directory`, `tags`, and `name` as the options. If more options need to be added, please update this file and the `schema.json` file.
|
||||
|
||||
{% callout type="note" title="More details" %}
|
||||
The `schema.d.ts` file is used for type checking inside the implementation file. It should match the properties in `schema.json`.
|
||||
{% /callout %}
|
||||
|
||||
### Generator Testing
|
||||
|
||||
The generator spec file includes boilerplate to help get started with testing. This includes setting up an empty workspace.
|
||||
|
||||
These tests should ensure that files within the tree (created with `createTreeWithEmptyWorkspace`) are in the correct place, and contain the right content.
|
||||
|
||||
Full E2Es are supported and will run everything on the file system like a user would.
|
||||
|
||||
## Executor
|
||||
|
||||
To create a new executor run:
|
||||
|
||||
```shell
|
||||
nx generate @nx/plugin:executor my-executor --project=my-plugin
|
||||
```
|
||||
|
||||
The new executor is located in `/src/executors/my-executor`. The `my-executor.ts` file contains the code that runs the executor. This executor emits a console log, but executors can compile code, deploy an app, publish to NPM and much more.
|
||||
|
||||
For more information about this sample executor, read the [simple executor recipe](/extending-nx/recipes/local-executors).
|
||||
|
||||
### Executor testing
|
||||
|
||||
The executor spec file contains boilerplate to run the default exported function from the executor.
|
||||
|
||||
These tests should make sure that the executor is executing and calling the functions that it relies on. Typically, unit tests are more useful for generators and e2e tests are more useful for executors.
|
||||
|
||||
## Testing your plugin
|
||||
|
||||
One of the biggest benefits that the Nx Plugin package provides is support for E2E and unit testing.
|
||||
|
||||
When the E2E app runs, a temporary E2E directory is created in the root of the workspace. This directory is a blank Nx workspace, and will have the plugin's built package installed locally.
|
||||
|
||||
### E2E Testing file
|
||||
|
||||
When the plugin is generated, a test file is created in the `my-plugin-e2e` app. Inside this test file, there is a disabled test that gives you a starting point for writing your own tests. To enable the test, change `xit` to `it`.
|
||||
|
||||
We'll go over a few parts of a test file below:
|
||||
|
||||
```typescript
|
||||
beforeAll(() => {
|
||||
ensureNxProject('my-plugin', 'dist/./.');
|
||||
});
|
||||
|
||||
xit('should be able to build generated projects', async () => {
|
||||
const name = 'proj';
|
||||
const generator = 'PLACEHOLDER';
|
||||
await runNxCommandAsync(`generate my-plugin:${generator} --name ${name}`);
|
||||
expect(() => runNxCommand('build ${proj}')).not.toThrow();
|
||||
expect(() => checkFilesExist(`dist/${name}/index.js`)).not.toThrow();
|
||||
});
|
||||
```
|
||||
|
||||
- The `ensureNxProject` is the function that will create the temporary directory. It takes two arguments, the plugin package name and the dist directory of when it's built.
|
||||
- The `runNxCommandAsync` function will execute a `nx` command in the E2E directory.
|
||||
|
||||
There are additional functions that the `@nx/plugin/testing` package exports. Most of them are file utilities to manipulate and read files in the E2E directory.
|
||||
|
||||
## Using your Nx Plugin Locally
|
||||
|
||||
To use your plugin, simply list it in `nx.json` or use its generators and executors as you would for any other plugin. This could look like `nx g @my-org/my-plugin:lib` for generators or `"executor": "@my-org/my-plugin:build"` for executors. It should be usable in all of the same ways as published plugins in your local workspace immediately after generating it.
|
||||
|
After Width: | Height: | Size: 32 KiB |
@@ -1,58 +1,108 @@
|
||||
# Getting Started with Plugins
|
||||
# Extending Nx with Plugins
|
||||
|
||||
Nx plugins contain [generators](/features/generate-code) and [executors](/concepts/executors-and-configurations) that extend the capabilities of an Nx workspace. They can be shared as npm packages or referenced locally within the same repo.
|
||||
Nx's core functionality focuses on task running and understanding your project and task graph. Nx plugins leverage that functionality to enforce best practices, seamlessly integrate tooling and allow developers to get up and running quickly.
|
||||
|
||||
{% cards cols="4" %}
|
||||
As your repository grows, you'll discover more reasons to create your own plugin
|
||||
|
||||
{% link-card title="Use a Plugin" url="#use-a-plugin" /%}
|
||||
{% link-card title="Create a Plugin" url="#create-a-local-plugin" /%}
|
||||
{% link-card title="Maintain a Published Plugin" url="#maintain-a-published-plugin" /%}
|
||||
{% link-card title="Advanced Plugins" url="#advanced-plugins" /%}
|
||||
- You can help encourage your coworkers to consistently follow best practices by creating [code generators](/features/generate-code) that are custom built for your repository.
|
||||
- You can remove duplicate configuration and ensure accurate caching settings by writing your own [inferred tasks](/concepts/inferred-tasks).
|
||||
- For organizations with multiple monorepos, you can encourage consistency across repositories by providing a repository [preset](/extending-nx/recipes/create-preset) and writing [migrations](/extending-nx/recipes/migration-generators) that will help keep every project in sync.
|
||||
- You can write a plugin that integrates a tool or framework into Nx and then [share your plugin](/extending-nx/recipes/publish-plugin) with the broader community.
|
||||
|
||||
{% /cards %}
|
||||
## Create Your Own Plugin
|
||||
|
||||
## Use a Plugin
|
||||
Get started developing your own plugin with a few terminal commands:
|
||||
|
||||
Nx plugins help you scaffold new projects, pre-configure tooling, follow best practices, and modularize your codebase.
|
||||
{% side-by-side %}
|
||||
|
||||
{% cards cols="3" %}
|
||||
```shell {% title="Create a plugin in a new workspace" %}
|
||||
npx create-nx-plugin my-plugin
|
||||
```
|
||||
|
||||
{% card title="Browse Existing Plugins" description="Find a plugin to use" url="/plugin-registry" /%}
|
||||
{% card title="Use Task Executors" description="Run operations on your code" url="/concepts/executors-and-configurations" /%}
|
||||
{% card title="Generate Code" description="Create or modify code" url="/features/generate-code" /%}
|
||||
```shell {% title="Add a plugin to an existing workspace" %}
|
||||
npx nx add @nx/plugin
|
||||
npx nx g plugin my-plugin
|
||||
```
|
||||
|
||||
{% /cards %}
|
||||
{% /side-by-side %}
|
||||
|
||||
## Create a Local Plugin
|
||||
## Learn by Doing
|
||||
|
||||
Local plugins allow you to automate repository specific tasks and enforce best practices (e.g., generating projects or components, running third-party tools).
|
||||
You can follow along with one of the step by step tutorials below that is focused on a particular use-case. These tutorials expect you to already have the following skills:
|
||||
|
||||
{% cards cols="3" %}
|
||||
|
||||
{% card title="Create a Plugin" description="Set up a new plugin" url="/extending-nx/tutorials/create-plugin" /%}
|
||||
{% card title="Local Generators" description="Add a generator to your plugin" url="/extending-nx/recipes/local-generators" /%}
|
||||
{% card title="Local Executors" description="Add an executor to your plugin" url="/extending-nx/recipes/local-executors" /%}
|
||||
|
||||
{% /cards %}
|
||||
|
||||
## Maintain a Published Plugin
|
||||
|
||||
If your plugin has functionality that would be useful in more than just your repo, you can publish it to npm and register it on the nx.dev site for others to find.
|
||||
- [Run tasks](/features/run-tasks) with Nx and configure Nx to [infers tasks for you](/concepts/inferred-tasks)
|
||||
- [Use code generators](/features/generate-code)
|
||||
- Understand the [project graph](/features/explore-graph)
|
||||
- Write [TypeScript](https://www.typescriptlang.org/) code
|
||||
|
||||
{% cards cols="2" %}
|
||||
|
||||
{% card title="Share Your Plugin" description="Submit your plugin to the Nx plugin registry" url="/extending-nx/tutorials/publish-plugin" /%}
|
||||
{% card title="Migration Generators" description="Update repos when you introduce breaking changes" url="/extending-nx/recipes/migration-generators" /%}
|
||||
{% link-card title="Enforce Best Practices in Your Repository" type="tutorial" url="/extending-nx/tutorials/organization-specific-plugin" icon="office" /%}
|
||||
{% link-card title="Integrate a Tool Into an Nx Repository" type="tutorial" url="/extending-nx/tutorials/tooling-plugin" icon="tool" /%}
|
||||
|
||||
{% /cards %}
|
||||
|
||||
## Advanced Plugins
|
||||
## Create Your First Code Generator
|
||||
|
||||
You can also hook into the way Nx works and modify it to suit your needs
|
||||
Wire up a new generator with this terminal command:
|
||||
|
||||
{% cards cols="2" %}
|
||||
```shell
|
||||
npx nx g generator library-with-readme --directory=my-plugin/src/generators/library-with-readme
|
||||
```
|
||||
|
||||
{% card title="Scaffold a New Workspace" description="Set up a new repo" url="/extending-nx/recipes/create-preset" /%}
|
||||
{% card title="Project Graph Plugins" description="Modify the Nx graph" url="/extending-nx/recipes/project-graph-plugins" /%}
|
||||
### Understand the Generator Functionality
|
||||
|
||||
{% /cards %}
|
||||
This command will register the generator in the plugin's `generators.json` file and create some default generator code in the `library-with-readme` folder. The `libraryWithReadmeGenerator` function in the `generator.ts` file is where the generator functionality is defined.
|
||||
|
||||
```typescript {% fileName="my-plugin/src/generators/library-with-readme/generator.ts" %}
|
||||
export async function libraryWithReadmeGenerator(
|
||||
tree: Tree,
|
||||
options: LibraryWithReadmeGeneratorSchema
|
||||
) {
|
||||
const projectRoot = `libs/${options.name}`;
|
||||
addProjectConfiguration(tree, options.name, {
|
||||
root: projectRoot,
|
||||
projectType: 'library',
|
||||
sourceRoot: `${projectRoot}/src`,
|
||||
targets: {},
|
||||
});
|
||||
generateFiles(tree, path.join(__dirname, 'files'), projectRoot, options);
|
||||
await formatFiles(tree);
|
||||
}
|
||||
```
|
||||
|
||||
This generator calls the following functions:
|
||||
|
||||
- `addProjectConfiguration` - Create a new project configured for TypeScript code.
|
||||
- `generateFiles` - Create files in the new project based on the template files in the `files` folder.
|
||||
- `formatFiles` - Format the newly created files with Prettier.
|
||||
|
||||
You can find more helper functions in the [Nx Devkit reference documentation](/nx-api/devkit/documents/nx_devkit).
|
||||
|
||||
### Create a README Template File
|
||||
|
||||
We can remove the generated `index.ts.template` file and add our own `README.md.template` file in the `files` folder.
|
||||
|
||||
```typescript {% fileName="my-plugin/src/generators/library-with-readme/files/README.md.template" %}
|
||||
# <%= name %>
|
||||
|
||||
This was generated by the `library-with-readme` generator!
|
||||
```
|
||||
|
||||
The template files that are used in the `generateFiles` function can inject variables and functionality using the EJS syntax. Our README template will replace `<%= name %>` with the name specified in the generator. Read more about the EJS syntax in the [creating files with a generator recipe](/extending-nx/recipes/creating-files).
|
||||
|
||||
### Run Your Generator
|
||||
|
||||
You can test your generator in dry-run mode with the following command:
|
||||
|
||||
```shell
|
||||
npx nx g my-plugin:library-with-readme mylib --dry-run
|
||||
```
|
||||
|
||||
If you're happy with the files that are generated, you can actually run the generator by removing the `--dry-run` flag.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Browse the plugin registry](/plugin-registry) to find one that suits your needs.
|
||||
- [Sign up for Nx Enterprise](/enterprise) to get dedicated support from Nx team members.
|
||||
- [Collaborate on the Nx Discord](https://go.nx.dev/community) to work with other plugin authors.
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
# Enforce Organizational Best Practices with a Local Plugin
|
||||
|
||||
Every repository has a unique set of conventions and best practices that developers need to learn in order to write code that integrates well with the rest of the code base. It is important to document those best practices, but developers don't always read the documentation and even if they have read the documentation, they don't consistently follow the documentation every time they perform a task. Nx allows you to encode these best practices in code generators that have been tailored to your specific repository.
|
||||
|
||||
In this tutorial, we will create a generator that helps enforce the follow best practices:
|
||||
|
||||
- Every project in this repository should use Vitest for unit tests.
|
||||
- Every project in this repository should be tagged with a `scope:*` tag that is chosen from the list of available scopes.
|
||||
- Projects should be placed in folders that match the scope that they are assigned.
|
||||
- Vitest should clear mocks before running tests.
|
||||
|
||||
## Get Started
|
||||
|
||||
Let's first create a new workspace with the `create-nx-workspace` command:
|
||||
|
||||
```shell
|
||||
npx create-nx-workspace myorg --preset=react-integrated --ci=github
|
||||
```
|
||||
|
||||
Then we , install the `@nx/plugin` package and generate a plugin:
|
||||
|
||||
```shell
|
||||
npx nx add @nx/plugin
|
||||
npx nx g @nx/plugin:plugin recommended --directory=tools/recommended
|
||||
```
|
||||
|
||||
This will create a `recommended` project that contains all your plugin code.
|
||||
|
||||
## Create a Customized Library Generator
|
||||
|
||||
To create a new generator run:
|
||||
|
||||
```shell
|
||||
npx nx generate @nx/plugin:generator library --directory="tools/recommended/src/generators/library"
|
||||
```
|
||||
|
||||
The new generator is located in `tools/recommended/src/generators/library`. The `generator.ts` file contains the code that runs the generator. We can delete the `files` directory since we won't be using it and update the `generator.ts` file with the following code:
|
||||
|
||||
```ts {% fileName="tools/recommended/src/generators/library/generator.ts" %}
|
||||
import { Tree } from '@nx/devkit';
|
||||
import { Linter } from '@nx/eslint';
|
||||
import { libraryGenerator as reactLibraryGenerator } from '@nx/react';
|
||||
import { LibraryGeneratorSchema } from './schema';
|
||||
|
||||
export async function libraryGenerator(
|
||||
tree: Tree,
|
||||
options: LibraryGeneratorSchema
|
||||
) {
|
||||
const callbackAfterFilesUpdated = await reactLibraryGenerator(tree, {
|
||||
...options,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
linter: Linter.EsLint,
|
||||
style: 'css',
|
||||
unitTestRunner: 'vitest',
|
||||
});
|
||||
|
||||
return callbackAfterFilesUpdated;
|
||||
}
|
||||
|
||||
export default libraryGenerator;
|
||||
```
|
||||
|
||||
Notice how this generator is calling the `@nx/react` plugin's `library` generator with a predetermined list of options. This helps developers to always create projects with the recommended settings.
|
||||
|
||||
We're returning the `callbackAfterFilesUpdated` function because the `@nx/react:library` generator sometimes needs to install packages from NPM after the file system has been updated by the generator. You can provide your own callback function instead, if you have tasks that rely on actual files being present.
|
||||
|
||||
To try out the generator in dry-run mode, use the following command:
|
||||
|
||||
```shell
|
||||
npx nx g @myorg/recommended:library test-library --dry-run
|
||||
```
|
||||
|
||||
Remove the `--dry-run` flag to actually create a new project.
|
||||
|
||||
### Add Generator Options
|
||||
|
||||
The `schema.d.ts` file contains all the options that the generator supports. By default, it includes only a `name` option. Let's add a directory option to pass on to the `@nx/react` generator.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="schema.d.ts" %}
|
||||
|
||||
```ts {% fileName="tools/recommended/src/generators/library/schema.d.ts" %}
|
||||
export interface LibraryGeneratorSchema {
|
||||
name: string;
|
||||
directory?: string;
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="schema.json" %}
|
||||
|
||||
```json {% fileName="tools/recommended/src/generators/library/schema.json" %}
|
||||
{
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "Library",
|
||||
"title": "",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
},
|
||||
"x-prompt": "What name would you like to use?"
|
||||
},
|
||||
"directory": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
{% callout type="note" title="More details" %}
|
||||
The `schema.d.ts` file is used for type checking inside the implementation file. It should match the properties in `schema.json`.
|
||||
{% /callout %}
|
||||
|
||||
The schema files not only provide structure to the CLI, but also allow [Nx Console](/getting-started/editor-setup) to show an accurate UI for the generator.
|
||||
|
||||

|
||||
|
||||
Notice how we made the `description` argument optional in both the JSON and type files. If we call the generator without passing a directory, the project will be created in a directory with same name as the project. We can test the changes to the generator with the following command:
|
||||
|
||||
```shell
|
||||
npx nx g @myorg/recommended:library test-library --directory=nested/directory/test-library --dry-run
|
||||
```
|
||||
|
||||
### Choose a Scope
|
||||
|
||||
It can be helpful to tag a library with a scope that matches the application it should be associated with. With these tags in place, you can [set up rules](/features/enforce-module-boundaries) for how projects can depend on each other. For our repository, let's say the scopes can be `store`, `api` or `shared` and the default directory structure should match the chosen scope. We can update the generator to encourage developers to maintain this structure.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="schema.d.ts" %}
|
||||
|
||||
```ts {% fileName="tools/recommended/src/generators/library/schema.d.ts" %}
|
||||
export interface LibraryGeneratorSchema {
|
||||
name: string;
|
||||
scope: string;
|
||||
directory?: string;
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="schema.json" %}
|
||||
|
||||
```json {% fileName="tools/recommended/src/generators/library/schema.json" %}
|
||||
{
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "Library",
|
||||
"title": "",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
},
|
||||
"x-prompt": "What name would you like to use?"
|
||||
},
|
||||
"scope": {
|
||||
"type": "string",
|
||||
"description": "The scope of your library.",
|
||||
"enum": ["api", "store", "shared"],
|
||||
"x-prompt": {
|
||||
"message": "What is the scope of this library?",
|
||||
"type": "list",
|
||||
"items": [
|
||||
{
|
||||
"value": "store",
|
||||
"label": "store"
|
||||
},
|
||||
{
|
||||
"value": "api",
|
||||
"label": "api"
|
||||
},
|
||||
{
|
||||
"value": "shared",
|
||||
"label": "shared"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"directory": {
|
||||
"type": "string",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="generator.ts" %}
|
||||
|
||||
```ts {% fileName="tools/recommended/src/generators/library/generator.ts" %}
|
||||
import { Tree } from '@nx/devkit';
|
||||
import { Linter } from '@nx/eslint';
|
||||
import { libraryGenerator as reactLibraryGenerator } from '@nx/react';
|
||||
import { LibraryGeneratorSchema } from './schema';
|
||||
|
||||
export async function libraryGenerator(
|
||||
tree: Tree,
|
||||
options: LibraryGeneratorSchema
|
||||
) {
|
||||
const callbackAfterFilesUpdated = await reactLibraryGenerator(tree, {
|
||||
...options,
|
||||
tags: `scope:${options.scope}`,
|
||||
directory: options.directory || `${options.scope}/${options.name}`,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
linter: Linter.EsLint,
|
||||
style: 'css',
|
||||
unitTestRunner: 'vitest',
|
||||
});
|
||||
|
||||
return callbackAfterFilesUpdated;
|
||||
}
|
||||
|
||||
export default libraryGenerator;
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
We can check that the scope logic is being applied correctly by running the generator again and specifying a scope.
|
||||
|
||||
```shell
|
||||
npx nx g @myorg/recommended:library test-library --scope=shared --dry-run
|
||||
```
|
||||
|
||||
This should create the `test-library` in the `shared` folder.
|
||||
|
||||
## Configure Tasks
|
||||
|
||||
You can also use your Nx plugin to configure how your tasks are run. Usually, organization focused plugins configure tasks by modifying the configuration files for each project. If you have developed your own tooling scripts for your organization, you may want to create an executor or infer tasks, but that process is covered in more detail in the tooling plugin tutorial.
|
||||
|
||||
Let's update our library generator to set the `clearMocks` property to `true` in the `vitest` configuration. First we'll run the `reactLibraryGenerator` and then we'll modify the created files.
|
||||
|
||||
```ts {% fileName="tools/recommended/src/generators/library/generator.ts" %}
|
||||
import { formatFiles, Tree, runTasksInSerial } from '@nx/devkit';
|
||||
import { Linter } from '@nx/eslint';
|
||||
import { libraryGenerator as reactLibraryGenerator } from '@nx/react';
|
||||
import { LibraryGeneratorSchema } from './schema';
|
||||
|
||||
export async function libraryGenerator(
|
||||
tree: Tree,
|
||||
options: LibraryGeneratorSchema
|
||||
) {
|
||||
const directory = options.directory || `${options.scope}/${options.name}`;
|
||||
|
||||
const tasks = [];
|
||||
tasks.push(
|
||||
await reactLibraryGenerator(tree, {
|
||||
...options,
|
||||
tags: `scope:${options.scope}`,
|
||||
directory,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
linter: Linter.EsLint,
|
||||
style: 'css',
|
||||
unitTestRunner: 'vitest',
|
||||
})
|
||||
);
|
||||
|
||||
updateViteConfiguration(tree, directory);
|
||||
await formatFiles(tree);
|
||||
|
||||
return runTasksInSerial(...tasks);
|
||||
}
|
||||
|
||||
function updateViteConfiguration(tree, directory) {
|
||||
// Read the vite configuration file
|
||||
let viteConfiguration =
|
||||
tree.read(`${directory}/vite.config.ts`)?.toString() || '';
|
||||
|
||||
// Modify the configuration
|
||||
// This is done with a naive search and replace, but could be done in a more robust way using AST nodes.
|
||||
viteConfiguration = viteConfiguration.replace(
|
||||
`globals: true,`,
|
||||
`globals: true,\n clearMocks:true,`
|
||||
);
|
||||
|
||||
// Write the modified configuration back to the file
|
||||
tree.write(`${directory}/vite.config.ts`, viteConfiguration);
|
||||
}
|
||||
|
||||
export default libraryGenerator;
|
||||
```
|
||||
|
||||
We updated the generator to use some new helper functions from the Nx devkit. Here are a few functions you may find useful. See the [full API reference](/nx-api/devkit/documents/nx_devkit) for all the options.
|
||||
|
||||
- [`runTasksInSerial`](/nx-api/devkit/documents/runTasksInSerial) - Allows you to collect many callbacks and return them all at the end of the generator.
|
||||
- [`formatFiles`](/nx-api/devkit/documents/formatFiles) - Run Prettier on the repository
|
||||
- [`readProjectConfiguration`](/nx-api/devkit/documents/readProjectConfiguration) - Get the calculated project configuration for a single project
|
||||
- [`updateNxJson`](/nx-api/devkit/documents/updateNxJson) - Update the `nx.json` file
|
||||
|
||||
Now let's check to make sure that the `clearMocks` property is set correctly by the generator. First, we'll commit our changes so far. Then, we'll run the generator without the `--dry-run` flag so we can inspect the file contents.
|
||||
|
||||
```shell
|
||||
git add .
|
||||
git commit -am "library generator"
|
||||
npx nx g @myorg/recommended:library store-test --scope=store
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that we have a working library generator, here are some more topics you may want to investigate.
|
||||
|
||||
- [Generate files](/extending-nx/recipes/creating-files) from EJS templates
|
||||
- [Modify files](/extending-nx/recipes/modifying-files) with string replacement or AST transformations
|
||||
|
||||
## Encourage Adoption
|
||||
|
||||
Once you have a set of generators in place in your organization's plugin, the rest of the work is all communication. Let your developers know that the plugin is available and encourage them to use it. These are the most important points to communicate to your developers:
|
||||
|
||||
- Whenever there are multiple plugins that provide a generator with the same name, use the `recommended` version
|
||||
- If there are repetitive or error prone processes that they identify, ask the plugin team to write a generator for that process
|
||||
|
||||
Now you can go through all the README files in the repository and replace any multiple step instructions with a single line calling a generator.
|
||||
@@ -0,0 +1,428 @@
|
||||
# Integrate a New Tool into an Nx Repository with a Tooling Plugin
|
||||
|
||||
Nx Plugins can be used to easily integrate a tool or framework into an Nx repository. If there is no plugin available for your favorite tool or framework, you can write your own.
|
||||
|
||||
In this tutorial, we'll create a plugin that helps to integrate the [Astro]() framework. `Astro` is a JavaScript web framework optimized for building fast, content-driven websites. We'll call our plugin `nx-astro`.
|
||||
|
||||
To create a plugin in a brand new repository, use the `create-nx-plugin` command:
|
||||
|
||||
```shell
|
||||
npx create-nx-plugin nx-astro
|
||||
```
|
||||
|
||||
Skip the `create-*` package prompt, since we won't be creating a preset.
|
||||
|
||||
## Understand Tooling Configuration Files
|
||||
|
||||
When integrating your tool into an Nx repository, you first need to have a clear understanding of how your tool works. Pay special attention to all the possible formats for configuration files, so that your plugin can process any valid configuration options.
|
||||
|
||||
For our `nx-astro` plugin, we'll read information from the `astro.config.mjs` or `astro.config.ts` file. We'll mainly be interested in the `srcDir`, `publicDir` and `outDir` properties specified in the `defineConfig` object. `srcDir` and `publicDir` define input files that are used in the build process and `outDir` defines what the build output will be created.
|
||||
|
||||
```js {% fileName="astro.config.mjs" %}
|
||||
import { defineConfig } from 'astro/config';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
srcDir: './src',
|
||||
publicDir: './public',
|
||||
outDir: './dist',
|
||||
});
|
||||
```
|
||||
|
||||
## Create an Inferred Task
|
||||
|
||||
The easiest way for people integrate your tool into their repository is for them to use inferred tasks. When leveraging inferred tasks, all your users need to do is install your plugin and the tool configuration file to their projects. Your plugin will take care of registering tasks with Nx and setting up the correct caching settings.
|
||||
|
||||
Once the inferred task logic is written, we want to be able to automatically create a task for any project that has a `astro.config.*` file defined in the root of the project. We'll name the task based on our plugin configuration in the `nx.json` file:
|
||||
|
||||
```json {% fileName="nx.json" %}
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"plugin": "nx-astro",
|
||||
"options": {
|
||||
"buildTargetName": "build",
|
||||
"devTargetName": "dev"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If the `astro.config.mjs` for a project looks like our example in the previous section, then the inferred configuration for the `build` task should look like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"command": "astro build",
|
||||
"cache": true,
|
||||
"inputs": [
|
||||
"{projectRoot}/astro.config.mjs",
|
||||
"{projectRoot}/src/**/*",
|
||||
"{projectRoot}/public/**/*",
|
||||
{
|
||||
"externalDependencies": ["astro"]
|
||||
}
|
||||
],
|
||||
"outputs": ["{projectRoot}/dist"]
|
||||
}
|
||||
```
|
||||
|
||||
To create an inferred task, we need to export a `createNodesV2` function from the plugin's `index.ts` file. The entire file is shown below with inline comments to explain what is happening in each section.
|
||||
|
||||
```ts {% fileName="src/index.ts" %}
|
||||
import {
|
||||
CreateNodesV2,
|
||||
TargetConfiguration,
|
||||
createNodesFromFiles,
|
||||
joinPathFragments,
|
||||
readJsonFile,
|
||||
} from '@nx/devkit';
|
||||
import { readdirSync, readFileSync } from 'fs';
|
||||
import { dirname, join, resolve } from 'path';
|
||||
|
||||
// Expected format of the plugin options defined in nx.json
|
||||
export interface AstroPluginOptions {
|
||||
buildTargetName?: string;
|
||||
devTargetName?: string;
|
||||
}
|
||||
|
||||
// File glob to find all the configuration files for this plugin
|
||||
const astroConfigGlob = '**/astro.config.{mjs,ts}';
|
||||
|
||||
// Entry function that Nx calls to modify the graph
|
||||
export const createNodesV2: CreateNodesV2<AstroPluginOptions> = [
|
||||
astroConfigGlob,
|
||||
async (configFiles, options, context) => {
|
||||
return await createNodesFromFiles(
|
||||
(configFile, options, context) =>
|
||||
createNodesInternal(configFile, options, context),
|
||||
configFiles,
|
||||
options,
|
||||
context
|
||||
);
|
||||
},
|
||||
];
|
||||
|
||||
async function createNodesInternal(configFilePath, options, context) {
|
||||
const projectRoot = dirname(configFilePath);
|
||||
|
||||
// Do not create a project if package.json or project.json isn't there.
|
||||
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
|
||||
if (
|
||||
!siblingFiles.includes('package.json') &&
|
||||
!siblingFiles.includes('project.json')
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Contents of the astro config file
|
||||
const astroConfigContent = readFileSync(
|
||||
resolve(context.workspaceRoot, configFilePath)
|
||||
).toString();
|
||||
|
||||
// Read config values using Regex.
|
||||
// There are better ways to read config values, but this works for the tutorial
|
||||
function getConfigValue(propertyName: string, defaultValue: string) {
|
||||
const result = new RegExp(`${propertyName}: '(.*)'`).exec(
|
||||
astroConfigContent
|
||||
);
|
||||
if (result && result[1]) {
|
||||
return result[1];
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
const srcDir = getConfigValue('srcDir', './src');
|
||||
const publicDir = getConfigValue('publicDir', './public');
|
||||
const outDir = getConfigValue('outDir', './dist');
|
||||
|
||||
// Inferred task final output
|
||||
const buildTarget: TargetConfiguration = {
|
||||
command: `astro build`,
|
||||
options: { cwd: projectRoot },
|
||||
cache: true,
|
||||
inputs: [
|
||||
'{projectRoot}/astro.config.mjs',
|
||||
joinPathFragments('{projectRoot}', srcDir, '**', '*'),
|
||||
joinPathFragments('{projectRoot}', publicDir, '**', '*'),
|
||||
{
|
||||
externalDependencies: ['astro'],
|
||||
},
|
||||
],
|
||||
outputs: [`{projectRoot}/${outDir}`],
|
||||
};
|
||||
const devTarget: TargetConfiguration = {
|
||||
command: `astro dev`,
|
||||
options: { cwd: projectRoot },
|
||||
};
|
||||
|
||||
// Project configuration to be merged into the rest of the Nx configuration
|
||||
return {
|
||||
projects: {
|
||||
[projectRoot]: {
|
||||
targets: {
|
||||
[options.buildTargetName]: buildTarget,
|
||||
[options.devTargetName]: devTarget,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
We'll test out this inferred task a little later in the tutorial.
|
||||
|
||||
Inferred tasks work well for getting users started using your tool quickly, but you can also provide users with [executors](/extending-nx/recipes/local-executors), which are another way of encapsulating a task script for easy use in an Nx workspace. Without inferred tasks, executors must be explicitly configured for each task.
|
||||
|
||||
## Create an Init Generator
|
||||
|
||||
You'll want to create generators to automate the common coding tasks for developers that use your tool. The most obvious coding task is the initial setup of the plugin. We'll create an `init` generator to automatically register the `nx-astro` plugin and start inferring tasks.
|
||||
|
||||
If you create a generator named `init`, Nx will automatically run that generator when someone installs your plugin with the `nx add nx-astro` command. This generator should provide a good default set up for using your plugin. In our case, we need to register the plugin in the `nx.json` file.
|
||||
|
||||
To create the generator run the following command:
|
||||
|
||||
```shell
|
||||
npx nx g generator init --directory=src/generators/init
|
||||
```
|
||||
|
||||
Then we can edit the `generator.ts` file to define the generator functionality:
|
||||
|
||||
```ts {% fileName="src/generators/init/generator.ts" %}
|
||||
import { formatFiles, readNxJson, Tree, updateNxJson } from '@nx/devkit';
|
||||
import { InitGeneratorSchema } from './schema';
|
||||
|
||||
export async function initGenerator(tree: Tree, options: InitGeneratorSchema) {
|
||||
const nxJson = readNxJson(tree) || {};
|
||||
const hasPlugin = nxJson.plugins?.some((p) =>
|
||||
typeof p === 'string' ? p === 'nx-astro' : p.plugin === 'nx-astro'
|
||||
);
|
||||
if (!hasPlugin) {
|
||||
if (!nxJson.plugins) {
|
||||
nxJson.plugins = [];
|
||||
}
|
||||
nxJson.plugins = [
|
||||
...nxJson.plugins,
|
||||
{
|
||||
plugin: 'nx-astro',
|
||||
options: {
|
||||
buildTargetName: 'build',
|
||||
devTargetName: 'dev',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
updateNxJson(tree, nxJson);
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
export default initGenerator;
|
||||
```
|
||||
|
||||
This will automatically add the plugin configuration to the `nx.json` file if the plugin is not already registered.
|
||||
|
||||
We need to remove the generated `name` option from the generator schema files so that the `init` generator can be executed without passing any arguments.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="schema.d.ts" %}
|
||||
|
||||
```ts {% fileName="src/generators/init/schema.d.ts" %}
|
||||
export interface InitGeneratorSchema {}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="schema.json" %}
|
||||
|
||||
```json {% fileName="src/generators/init/schema.json" %}
|
||||
{
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "Init",
|
||||
"title": "",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## Create an Application Generator
|
||||
|
||||
Let's make one more generator to automatically create a simple Astro application. First we'll create the generator:
|
||||
|
||||
```shell
|
||||
npx nx g generator application --directory=src/generators/application
|
||||
```
|
||||
|
||||
Then we'll update the `generator.ts` file to define the generator functionality:
|
||||
|
||||
```ts {% fileName="src/generators/application/generator.ts" %}
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
formatFiles,
|
||||
generateFiles,
|
||||
Tree,
|
||||
} from '@nx/devkit';
|
||||
import * as path from 'path';
|
||||
import { ApplicationGeneratorSchema } from './schema';
|
||||
|
||||
export async function applicationGenerator(
|
||||
tree: Tree,
|
||||
options: ApplicationGeneratorSchema
|
||||
) {
|
||||
const projectRoot = `${options.name}`;
|
||||
addProjectConfiguration(tree, options.name, {
|
||||
root: projectRoot,
|
||||
projectType: 'application',
|
||||
sourceRoot: `${projectRoot}/src`,
|
||||
targets: {},
|
||||
});
|
||||
generateFiles(tree, path.join(__dirname, 'files'), projectRoot, options);
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
export default applicationGenerator;
|
||||
```
|
||||
|
||||
The `generateFiles` function will use the template files in the `files` folder to add files to the generated project.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="package.json__templ__" %}
|
||||
|
||||
```json {% fileName="src/generators/application/files/package.json__templ__" %}
|
||||
{
|
||||
"name": "<%= name %>",
|
||||
"dependencies": {}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="astro.config.mjs" %}
|
||||
|
||||
```js {% fileName="src/generators/application/files/astro.config.mjs" %}
|
||||
import { defineConfig } from 'astro/config';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({});
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="index.astro" %}
|
||||
|
||||
```{% fileName="src/generators/application/files/src/pages/index.astro" %}
|
||||
---
|
||||
// Welcome to Astro! Everything between these triple-dash code fences
|
||||
// is your "component frontmatter". It never runs in the browser.
|
||||
console.log('This runs in your terminal, not the browser!');
|
||||
---
|
||||
<!-- Below is your "component template." It's just HTML, but with
|
||||
some magic sprinkled in to help you build great templates. -->
|
||||
<html>
|
||||
<body>
|
||||
<h1>Hello, World!</h1>
|
||||
</body>
|
||||
</html>
|
||||
<style>
|
||||
h1 {
|
||||
color: orange;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="robots.txt" %}
|
||||
|
||||
```json {% fileName="src/generators/application/files/public/robots.txt" %}
|
||||
# Example: Allow all bots to scan and index your site.
|
||||
# Full syntax: https://developers.google.com/search/docs/advanced/robots/create-robots-txt
|
||||
User-agent: *
|
||||
Allow: /
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The generator options in the schema files can be left unchanged.
|
||||
|
||||
## Test Your Plugin
|
||||
|
||||
The plugin is generated with a default e2e test (`e2e/src/nx-astro.spec.ts`) that:
|
||||
|
||||
1. Launches a local npm registry with Verdaccio
|
||||
2. Publishes the current version of the `nx-astro` plugin to the local registry
|
||||
3. Creates an empty Nx workspace
|
||||
4. Installs `nx-astro` in the Nx workspace
|
||||
|
||||
Let's update the e2e tests to make sure that the inferred tasks are working correctly. We'll update the `beforeAll` function to use `nx add` to add the `nx-astro` plugin and call our `application` generator.
|
||||
|
||||
```ts {% fileName="e2e/src/nx-astro.spec.ts" %}
|
||||
beforeAll(() => {
|
||||
projectDirectory = createTestProject();
|
||||
|
||||
// The plugin has been built and published to a local registry in the jest globalSetup
|
||||
// Install the plugin built with the latest source code into the test repo
|
||||
execSync('npx nx add nx-astro@e2e', {
|
||||
cwd: projectDirectory,
|
||||
stdio: 'inherit',
|
||||
env: process.env,
|
||||
});
|
||||
execSync('npx nx g nx-astro:application my-lib', {
|
||||
cwd: projectDirectory,
|
||||
stdio: 'inherit',
|
||||
env: process.env,
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Now we can add a new test that verifies the inferred task configuration:
|
||||
|
||||
```ts {% fileName="e2e/src/nx-astro.spec.ts" %}
|
||||
it('should infer tasks', () => {
|
||||
const projectDetails = JSON.parse(
|
||||
execSync('nx show project my-lib --json', {
|
||||
cwd: projectDirectory,
|
||||
}).toString()
|
||||
);
|
||||
|
||||
expect(projectDetails).toMatchObject({
|
||||
name: 'my-lib',
|
||||
root: 'my-lib',
|
||||
sourceRoot: 'my-lib/src',
|
||||
targets: {
|
||||
build: {
|
||||
cache: true,
|
||||
executor: 'nx:run-commands',
|
||||
inputs: [
|
||||
'{projectRoot}/astro.config.mjs',
|
||||
'{projectRoot}/src/**/*',
|
||||
'{projectRoot}/public/**/*',
|
||||
{
|
||||
externalDependencies: ['astro'],
|
||||
},
|
||||
],
|
||||
options: {
|
||||
command: 'astro build',
|
||||
cwd: 'my-lib',
|
||||
},
|
||||
outputs: ['{projectRoot}/./dist'],
|
||||
},
|
||||
dev: {
|
||||
executor: 'nx:run-commands',
|
||||
options: {
|
||||
command: 'astro dev',
|
||||
cwd: 'my-lib',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you have a working plugin, here are a few other topics you may want to investigate:
|
||||
|
||||
- [Publish your Nx plugin](/extending-nx/recipes/publish-plugin) to npm and the Nx plugin registry
|
||||
- [Write migration generators](/extending-nx/recipes/migration-generators) to automatically account for breaking changes
|
||||
- [Create a preset](/extending-nx/recipes/create-preset) to scaffold out an entire new repository
|
||||
@@ -0,0 +1,120 @@
|
||||
# File Based Versioning ("Version Plans")
|
||||
|
||||
Tools such as Changesets and Beachball helped popularize the concept of tracking the desired semver version bump in a separate file on disk (which is committed to your repository alongside your code changes). This has the advantage of separating the desired bump from your git commits themselves, which can be very useful if you are not able to enforce that all contributors follow a strict commit message format ([e.g. Conventional Commits](/recipes/nx-release/automatically-version-with-conventional-commits)), or if you want multiple commits to be included in the same version bump and therefore not map commits 1:1 with changelog entries.
|
||||
|
||||
Nx release supports file based versioning as a first class use-case through a feature called "version plans". The idea behind the name is that you are creating a _plan_ to version; a plan which will be _applied_ sometime in the future when you actually invoke the `nx release` CLI or programmatic API. Therefore you can think about version plans as having two main processes: creating version plans and applying version plans. We will cover both in this recipe, but first we need to enable the feature itself.
|
||||
|
||||
## Enable Version Plans
|
||||
|
||||
To enable version plans as a feature in your workspace, set `release.versionPlans` to `true` in `nx.json`:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"release": {
|
||||
"versionPlans": true
|
||||
// other release config such as projects to include in releases etc
|
||||
// projects: ["packages/**/*"]
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also enable or disable this for specific release groups by setting the property at the group level if you don't want to apply it to all matching projects in your workspace.
|
||||
|
||||
## Create Version Plans
|
||||
|
||||
Version plan files live in the `.nx/version-plans/` directory within your workspace (which needs to be tracked by git, so ensure that you are not ignoring the whole `.nx` directory, and instead only the `.nx/workspace-data` and `.nx/cache` directories).
|
||||
|
||||
The files themselves are written in markdown (`.md` files) and contain Front Matter YAML metadata at the top of the file. The Front Matter YAML section is denoted via triple dashes `---` at the start and end of the section. For example:
|
||||
|
||||
```md {% fileName=".nx/version-plans/version-plan-1723732065047.md" %}
|
||||
---
|
||||
#
|
||||
# FRONT MATTER YAML HERE
|
||||
#
|
||||
---
|
||||
|
||||
#
|
||||
|
||||
# Regular markdown here
|
||||
|
||||
#
|
||||
```
|
||||
|
||||
We leverage the Front Matter YAML section to store a mapping of project or release group names to desired semver bump types. The general markdown section represents the description of the change(s) made that will be used in any relevant `CHANGELOG.md` files that are generated later at release time.
|
||||
|
||||
For example, the following Front Matter YAML section specifies that the `my-app` project should have a `minor` version bump and describes the changes (again, note that there are no constraints on the format of the description, it can contain multiple lines, paragraphs etc):
|
||||
|
||||
```md {% fileName=".nx/version-plans/version-plan-1723732065047.md" %}
|
||||
---
|
||||
my-app: minor
|
||||
---
|
||||
|
||||
This is an awesome change!
|
||||
|
||||
A new paragraph describing the change in greater detail. All of this will be included in the CHANGELOG.md. All of this structure within the markdown section is optional and flexible.
|
||||
```
|
||||
|
||||
Any number of different projects and different desired semver bumps can be combined within a single version plan file (which represents one change and therefore changelog entry, if applicable). For example:
|
||||
|
||||
```md {% fileName=".nx/version-plans/version-plan-1723732065047.md" %}
|
||||
---
|
||||
my-app: minor
|
||||
my-lib: patch
|
||||
release-group-a: major
|
||||
---
|
||||
|
||||
One change that affects multiple projects and release groups.
|
||||
```
|
||||
|
||||
The project or release group names specified in the Front Matter YAML section must match the names of the projects and/or release groups in your workspace. If a project or release group is not found, an error will be thrown when applying the version plan as part of running `nx release`.
|
||||
|
||||
Because these are just files, they can be created manually or by any custom scripts you may wish to write. They simply have to follow the guidance above around structure, location (`./.nx/version-plans/`) and naming (`.md` extension). The exact file name does not matter, it just needs to be unique within the `.nx/version-plans/` directory.
|
||||
|
||||
To make things easier, Nx release comes with a built in command to help you generate valid version plan files:
|
||||
|
||||
```sh
|
||||
nx release plan
|
||||
```
|
||||
|
||||
When you run this command you will receive a series of interactive prompts which guide you through the process of creating a version plan file. It will generate a unique name for you and ensure it is written to the correct location.
|
||||
|
||||
## Apply Version Plans at Release Time
|
||||
|
||||
Using version plans does not change how versioning, changelog generation and publishing is invoked, you can still use the `nx release` CLI or programmatic API as you would for any other versioning strategy.
|
||||
|
||||
The only difference is that Nx release will know to reference your version plan files as the source of truth for the desired version bumps. You still retain the same control around resolving the current version (disk vs registry vs git tags) however you want, and other configuration options around things like git operations are all still applicable.
|
||||
|
||||
When you run `nx release` or use the programmatic API, Nx will look for version plan files in the `.nx/version-plans/` directory and apply the desired version bumps to the projects and release groups specified in the Front Matter YAML section of each file. If a project or release group is not found, an error will be thrown and the release will not proceed. Once a particular version plan has been applied it will be deleted from the `.nx/version-plans/` directory so that it does not inadvertently get applied again in the future. The deleted file will be staged and committed alongside your other changed files that were modified directly as part of the release command (depending on your Nx release configuration).
|
||||
|
||||
## Ensure That Version Plans Exist for Relevant Changes
|
||||
|
||||
When making changes to your codebase and using version plans as your versioning strategy it is likely that you will want to ensure that a version plan file exists for the changes you are making.
|
||||
|
||||
Attempting to keep track of this manually as a part of pull request reviews can be error prone and time consuming, therefore Nx release provides a `nx release plan:check` command which can be used to ensure that a version plan file exists for the changes you are making.
|
||||
|
||||
```sh
|
||||
nx release plan:check
|
||||
```
|
||||
|
||||
Running this command will analyze the changed files (supporting the same options you may be familiar with from `nx affected`, such as `--base`, `--head`, `--files`, `--uncommitted`, etc) and then determine which projects have been "touched" as a result. Note that it is specifically touched projects, and not affected in this case, because only directly changed projects are relevant for versioning. The side-effects of versioning independently released dependents are handled by the release process itself (controllable via the `version.generatorOptions.updatedDependents` option).
|
||||
|
||||
Nx release will compare the touched projects to the projects and release groups that are specified in the version plan files in the `.nx/version-plans/` directory. If a version plan file does not exist, the command will print an error message and return a non-zero exit code, which can be used to fail CI builds or other automation.
|
||||
|
||||
By default, all files that have changed are considered, but you may not want all files under a project to require a version plan be created for them. For example, you may wish to ignore test only files from consideration from this check. The way you can achieve this is by setting version plans to be a configuration object instead of a boolean, and set the `"ignorePatternsForPlanCheck"` property to an array of glob patterns that should be ignored when checking for version plans. For example:
|
||||
|
||||
````jsonc
|
||||
{
|
||||
"release": {
|
||||
"versionPlans": {
|
||||
"ignorePatternsForPlanCheck": ["**/*.spec.ts"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To see more details about the changed files that were detected and the filtering logic that was used to determine the ultimately changed projects behind the scenes, you can pass `--verbose` to the command:
|
||||
|
||||
```sh
|
||||
nx release plan:check --verbose
|
||||
````
|
||||
@@ -13,7 +13,7 @@ title="Develop a Nx Preset for your Nx Plugin"
|
||||
|
||||
At its core, a preset is a special [generator](/features/generate-code) that is shipped as part of an Nx Plugin package.
|
||||
|
||||
All first-party Nx presets are built into Nx itself, but you can [create your own plugin](/extending-nx/intro/getting-started) and create a generator with the magic name: `preset`. Once you've [published your plugin](/extending-nx/tutorials/publish-plugin) on npm, you can now run the `create-nx-workspace` command with the preset option set to the name of your published package.
|
||||
All first-party Nx presets are built into Nx itself, but you can [create your own plugin](/extending-nx/intro/getting-started) and create a generator with the magic name: `preset`. Once you've [published your plugin](/extending-nx/tutorials/tooling-plugin) on npm, you can now run the `create-nx-workspace` command with the preset option set to the name of your published package.
|
||||
|
||||
To use a concrete example, let's look at the [`qwik-nx`](https://www.npmjs.com/package/qwik-nx) Nx community plugin. They include a [preset generator](https://github.com/qwikifiers/qwik-nx/tree/main/packages/qwik-nx/src/generators/preset) that you can use to create a new Nx workspace with Qwik support.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Local Executors
|
||||
# Write a Simple Executor
|
||||
|
||||
Creating Executors for your workspace standardizes scripts that are run during your development/building/deploying tasks in order to provide guidance in the terminal with `--help` and when invoking with [Nx Console](/getting-started/editor-setup)
|
||||
|
||||
@@ -6,7 +6,7 @@ This guide shows you how to create, run, and customize executors within your Nx
|
||||
|
||||
## Creating an executor
|
||||
|
||||
If you don't already have a local plugin, use Nx to generate one:
|
||||
If you don't already have a plugin, use Nx to generate one:
|
||||
|
||||
```shell {% skipRescope=true %}
|
||||
nx add @nx/plugin
|
||||
|
||||
@@ -1,45 +1,17 @@
|
||||
# Maintain a Published Plugins
|
||||
|
||||
To create a plugin, see the [create a local plugin tutorial](/extending-nx/tutorials/create-plugin).
|
||||
|
||||
## Publish your Nx Plugin
|
||||
# Publish your Nx Plugin
|
||||
|
||||
In order to use your plugin in other workspaces or share it with the community, you will need to publish it to an npm registry. To publish your plugin follow these steps:
|
||||
|
||||
1. `nx publish my-plugin --ver=1.0.0` which automatically builds `my-plugin`
|
||||
1. `nx nx-release-publish nx-cfonts`
|
||||
2. Follow the prompts from npm.
|
||||
3. That's it!
|
||||
|
||||
{% callout type="warning" title="Version bump" %}
|
||||
Currently you will have to modify the `package.json` version by yourself or with a tool.
|
||||
{% /callout %}
|
||||
|
||||
After that, you can then install your plugin like any other npm package -
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="npm" %}
|
||||
After that, you can then install your plugin like any other Nx plugin -
|
||||
|
||||
```shell
|
||||
npm add -D @my-org/my-plugin
|
||||
nx add nx-cfonts
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="yarn" %}
|
||||
|
||||
```shell
|
||||
yarn add -D @my-org/my-plugin
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="pnpm" %}
|
||||
|
||||
```shell
|
||||
pnpm add -D @my-org/my-plugin
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## List your Nx Plugin
|
||||
|
||||
Nx provides a utility (`nx list`) that lists both core and community plugins. You can submit your plugin to be added to this list, but it needs to meet a few criteria first:
|
||||
@@ -72,7 +44,3 @@ Once those criteria are met, you can submit your plugin by following the steps b
|
||||
> The `yarn submit-plugin` command automatically opens the GitHub pull request process with the correct template.
|
||||
|
||||
We will then verify the plugin, offer suggestions or merge the pull request!
|
||||
|
||||
## Write Migrations
|
||||
|
||||
Once other repos are using your plugin, it would help them if you write migrations to automatically update their configuration files whenever you make breaking changes. Read the [migration generators guide](/extending-nx/recipes/migration-generators) to find out how.
|
||||
@@ -0,0 +1,204 @@
|
||||
# Migrate to Inferred Tasks (Project Crystal)
|
||||
|
||||
In this recipe, you'll learn how to migrate an existing Nx workspace from using executors in `project.json` to using [inferred tasks](/concepts/inferred-tasks).
|
||||
|
||||
The main benefits of migrating to inferred tasks are
|
||||
|
||||
- reducing the amount of configuration needed in `project.json`
|
||||
- inferring the correct cache settings based on the tool configuration files
|
||||
- [splitting tasks (Atomizer)](/ci/features/split-e2e-tasks) for plugins that support it
|
||||
|
||||
{% youtube
|
||||
src="https://youtu.be/wADNsVItnsM"
|
||||
title="Project Crystal"
|
||||
/%}
|
||||
|
||||
For the best experience, we recommend that you [migrate](/features/automate-updating-dependencies) to the latest Nx version before continuing. At minimum, you should be on Nx version 19.6.
|
||||
|
||||
```shell
|
||||
npx nx migrate latest
|
||||
```
|
||||
|
||||
## Migrate a Plugin
|
||||
|
||||
Most of the official plugins come with a `convert-to-inferred` generator. This generator will
|
||||
|
||||
- register the inference plugin in the `plugins` section of `nx.json`
|
||||
- migrate executor options into the tool's configuration files (where applicable)
|
||||
- clean up `project.json` to remove targets and options that are unnecessary
|
||||
|
||||
To get started, run `nx g convert-to-inferred`, and you'll be prompted to choose a plugin to migrate.
|
||||
|
||||
```text {% command="npx nx g convert-to-inferred" %}
|
||||
? Which generator would you like to use? …
|
||||
@nx/eslint:convert-to-inferred
|
||||
@nx/playwright:convert-to-inferred
|
||||
@nx/vite:convert-to-inferred
|
||||
|
||||
None of the above
|
||||
```
|
||||
|
||||
{% callout type="note" title="Third-party plugins" %}
|
||||
For third-party plugins that provide `convert-to-inferred` generators, you should pick the `None of the above` option and type in the name of the package manually. Alternatively, you can also provide the package explicitly with `nx g <plugin>:convert-to-inferred`.
|
||||
{% /callout %}
|
||||
|
||||
We recommend that you migrate the plugins one at a time, and check that the configurations are correct before continuing to the next plugin. If you only want to try it on a single project, pass the `--project` option.
|
||||
|
||||
## Understand the Migration Process
|
||||
|
||||
The `convert-to-inferred` generator removes uses of executors from the corresponding plugin. For example, if `@nx/vite` is migrated, then uses of [`@nx/vite:build`](/nx-api/vite/executors/build), [`@nx/vite:dev-server`](/nx-api/vite/executors/dev-server), [`@nx/vite:preview-server`](/nx-api/vite/executors/preview-server), and [`@nx/vite:test`](/nx-api/vite/executors/test) executors will be removed.
|
||||
|
||||
Target and configuration names are maintained for each project in their `project.json` files. A target may be removed from `project.json` if everything is inferred--that is, options and configurations are not customized. To get the full project details (including all inferred tasks), run:
|
||||
|
||||
```shell
|
||||
npx nx show project <project-name>
|
||||
```
|
||||
|
||||
For example, if we migrated the `@nx/vite` plugin for a single app (i.e. `nx g @nx/vite:convert-to-inferred --project demo`), then running `nx show project demo` will show a screen similar to the following.
|
||||
|
||||
{% project-details title="Test" height="100px" %}
|
||||
|
||||
```json
|
||||
{
|
||||
"project": {
|
||||
"name": "demo",
|
||||
"data": {
|
||||
"root": " apps/demo",
|
||||
"projectType": "application",
|
||||
"targets": {
|
||||
"serve": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"command": "vite dev"
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"executor": "nx:run-commands",
|
||||
"inputs": ["production", "^production"],
|
||||
"outputs": ["{projectRoot}/dist"],
|
||||
"options": {
|
||||
"command": "vite build"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sourceMap": {
|
||||
"targets": ["apps/demo/vite.config.ts", "@nx/vite"],
|
||||
"targets.serve": ["apps/demo/vite.config.ts", "@nx/vite"],
|
||||
"targets.build": ["apps/demo/vite.config.ts", "@nx/vite"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /project-details %}
|
||||
|
||||
You'll notice that the `serve` and `build` tasks are running the [Vite CLI](https://vitejs.dev/guide/cli.html) and there are no references to Nx executors. Since the targets directly invoke the Vite CLI, any options that may be passed to it can be passed via Nx commands. e.g. `nx serve demo --cors --port 8888` enables CORs and uses port `8888` using [Vite CLI options](https://vitejs.dev/guide/cli.html#options)
|
||||
The same CLI setup applies to other plugins as well.
|
||||
|
||||
- `@nx/cypess` calls the [Cypress CLI](https://docs.cypress.io/guides/guides/command-line)
|
||||
- `@nx/playwright` calls the [Playwright CLI](https://playwright.dev/docs/test-cli)
|
||||
- `@nx/webpack` calls the [Webpack CLI](https://webpack.js.org/api/cli/)
|
||||
- etc.
|
||||
|
||||
Read the recipe on [passing args to commands](/recipes/running-tasks/pass-args-to-commands) for more information.
|
||||
|
||||
### Configuration File Changes
|
||||
|
||||
There may also be changes to the configuration files used by the underlying tool. The changes come with comments to explain them, and may also provide next steps for you to take. One common change is to add support for different configuration options. For example, if we have an existing Vite app with the following build target:
|
||||
|
||||
```json {% fileName="project.json" %}
|
||||
"build": {
|
||||
"executor": "@nx/vite:build",
|
||||
"options": {
|
||||
"mode": "development"
|
||||
},
|
||||
"defaultConfiguration": "production",
|
||||
"configurations": {
|
||||
"development": {},
|
||||
"production": {},
|
||||
"ci": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Where we have `development`, `production`, and `ci` configurations. Then running `nx g @nx/vite:convert-to-inferred` will result in these lines added to `vite.config.ts`.
|
||||
|
||||
```ts {% fileName="vite.config.ts" highlightLines=["6-15"] %}
|
||||
/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
|
||||
// These options were migrated by @nx/vite:convert-to-inferred from the project.json file.
|
||||
const configValues = { default: {}, development: {}, production: {}, ci: {} };
|
||||
|
||||
// Determine the correct configValue to use based on the configuration
|
||||
const nxConfiguration = process.env.NX_TASK_TARGET_CONFIGURATION ?? 'default';
|
||||
|
||||
const options = {
|
||||
...configValues.default,
|
||||
...(configValues[nxConfiguration] ?? {}),
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
root: __dirname,
|
||||
cacheDir: '../../node_modules/.vite/apps/demo',
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
The configuration changes ensure that passing `--configuration` still work for the target. Differences in options can be added to the `configValues` object, and the right value is determined using the `NX_TASK_TARGET_CONFIGURATION` [environment variable](/reference/environment-variables). Again, there may be other types of changes so read the comments to understand them.
|
||||
|
||||
### Register the Plugin with Nx
|
||||
|
||||
Lastly, you can inspect the `nx.json` file to see a new `plugins` entry. For `@nx/vite`, there should be an entry like this:
|
||||
|
||||
```json {% fileName="nx.json" %}
|
||||
{
|
||||
"plugin": "@nx/vite/plugin",
|
||||
"options": {
|
||||
"buildTargetName": "build",
|
||||
"serveTargetName": "serve",
|
||||
"previewTargetName": "preview",
|
||||
"testTargetName": "test",
|
||||
"serveStaticTargetName": "serve-static"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You may change the target name options to change how Nx adds them to the project. For example, if you use `"serveTargetName": "dev"` then you would run `nx dev demo` rather than `nx serve demo` for your Vite project.
|
||||
|
||||
## Verify the Migration
|
||||
|
||||
The migrations maintain the same targets and configurations for each project, thus to verify it you should run the affected targets.
|
||||
|
||||
For example
|
||||
|
||||
- for `@nx/vite` you should check the `build`, `serve`, and `test` targets
|
||||
- for `@nx/playwright` you should check the `e2e` targets
|
||||
- for `@nx/eslint` you should check the `lint` target
|
||||
- etc.
|
||||
|
||||
Remember that the target names are defined in the plugin configuration in `nx.json`.
|
||||
|
||||
Make sure that the tasks are all passing before migrating another plugin.
|
||||
|
||||
## Enable Atomizer (task splitting)
|
||||
|
||||
These plugins come with the [Atomizer](/ci/features/split-e2e-tasks) feature.
|
||||
|
||||
- `@nx/cypress`
|
||||
- `@nx/jest`
|
||||
- `@nx/gradle`
|
||||
- `@nx/playwright`
|
||||
|
||||
The Atomizer splits potentially slow tasks into separate tasks per file. This feature along with [task distribution](/ci/features/distribute-task-execution) can speed up CI by distributing the split tasks among many agents.
|
||||
|
||||
To enable Atomizer, make sure that you are [connected to Nx Cloud](/ci/intro/connect-to-nx-cloud), and that you have distribution enabled in CI. Some plugins require extra configuration to enable Atomizer, so check the [individual plugin documentation page](/nx-api) for more details.
|
||||
|
||||
{% call-to-action title="Connect to Nx Cloud" icon="nxcloud" description="Enable task distribution and Atomizer" url="/ci/intro/connect-to-nx-cloud" /%}
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you run into any issues during the migration, refer to the [troubleshooting guide](/troubleshooting/convert-to-inferred).
|
||||
@@ -64,6 +64,7 @@
|
||||
- [Reduce Repetitive Configuration](/recipes/running-tasks/reduce-repetitive-configuration)
|
||||
- [Change Cache Location](/recipes/running-tasks/change-cache-location)
|
||||
- [Skip Task Caching](/recipes/running-tasks/skipping-cache)
|
||||
- [Migrate to Inferred Tasks (Project Crystal)](/recipes/running-tasks/convert-to-inferred)
|
||||
- [Adopting Nx](/recipes/adopting-nx)
|
||||
- [NPM/Yarn/PNPM workspaces](/recipes/adopting-nx/adding-to-monorepo)
|
||||
- [Migrate From Turborepo](/recipes/adopting-nx/from-turborepo)
|
||||
@@ -160,6 +161,7 @@
|
||||
- [Release Projects Independently](/recipes/nx-release/release-projects-independently)
|
||||
- [Automatically Version with Conventional Commits](/recipes/nx-release/automatically-version-with-conventional-commits)
|
||||
- [Customize Conventional Commit Types](/recipes/nx-release/customize-conventional-commit-types)
|
||||
- [File Based Versioning (Version Plans)](/recipes/nx-release/file-based-versioning-version-plans)
|
||||
- [Configure Custom Registries](/recipes/nx-release/configure-custom-registries)
|
||||
- [Publish in CI/CD](/recipes/nx-release/publish-in-ci-cd)
|
||||
- [Automate GitHub Releases](/recipes/nx-release/automate-github-releases)
|
||||
@@ -226,22 +228,23 @@
|
||||
- Extending-nx
|
||||
|
||||
- [Intro](/extending-nx/intro)
|
||||
- [Getting Started with Plugins](/extending-nx/intro/getting-started)
|
||||
- [5 Min Tutorials](/extending-nx/tutorials)
|
||||
- [Create a Local Plugin](/extending-nx/tutorials/create-plugin)
|
||||
- [Maintain a Published Plugin](/extending-nx/tutorials/publish-plugin)
|
||||
- [Extending Nx with Plugins](/extending-nx/intro/getting-started)
|
||||
- [Tutorials](/extending-nx/tutorials)
|
||||
- [Enforce Organizational Best Practices](/extending-nx/tutorials/organization-specific-plugin)
|
||||
- [Create a Tooling Plugin](/extending-nx/tutorials/tooling-plugin)
|
||||
- [Recipes](/extending-nx/recipes)
|
||||
- [Write a Simple Executor](/extending-nx/recipes/local-executors)
|
||||
- [Compose Executors](/extending-nx/recipes/compose-executors)
|
||||
- [Write a Simple Generator](/extending-nx/recipes/local-generators)
|
||||
- [Compose Generators](/extending-nx/recipes/composing-generators)
|
||||
- [Provide Options for Generators](/extending-nx/recipes/generator-options)
|
||||
- [Create Files](/extending-nx/recipes/creating-files)
|
||||
- [Modify Files](/extending-nx/recipes/modifying-files)
|
||||
- [Write a Migration](/extending-nx/recipes/migration-generators)
|
||||
- [Write a Simple Executor](/extending-nx/recipes/local-executors)
|
||||
- [Compose Executors](/extending-nx/recipes/compose-executors)
|
||||
- [Create a Preset](/extending-nx/recipes/create-preset)
|
||||
- [Create an Install Package](/extending-nx/recipes/create-install-package)
|
||||
- [Modify the Project Graph](/extending-nx/recipes/project-graph-plugins)
|
||||
- [Infer Tasks or Projects](/extending-nx/recipes/project-graph-plugins)
|
||||
- [Publish a Plugin](/extending-nx/recipes/publish-plugin)
|
||||
|
||||
- Ci
|
||||
|
||||
|
||||
@@ -478,13 +478,21 @@ describe('Linter', () => {
|
||||
`import { names } from '@nx/devkit';\n\n` +
|
||||
content.replace(/=> .*;/, `=> names('${mylib}').className;`)
|
||||
);
|
||||
// intentionally set an obsolete dependency
|
||||
updateJson(`libs/${mylib}/package.json`, (json) => {
|
||||
json.dependencies['@nx/js'] = nxVersion;
|
||||
return json;
|
||||
});
|
||||
|
||||
// output should now report missing dependency
|
||||
// output should now report missing dependency and obsolete dependency
|
||||
out = runCLI(`lint ${mylib}`, { silenceError: true });
|
||||
expect(out).toContain('they are missing');
|
||||
expect(out).toContain('@nx/devkit');
|
||||
expect(out).toContain(
|
||||
`The "@nx/js" package is not used by "${mylib}" project`
|
||||
);
|
||||
|
||||
// should fix the missing dependency issue
|
||||
// should fix the missing and obsolete dependency issues
|
||||
out = runCLI(`lint ${mylib} --fix`, { silenceError: true });
|
||||
expect(out).toContain(
|
||||
`Successfully ran target lint for project ${mylib}`
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
}
|
||||
},
|
||||
"build-client": {
|
||||
"dependsOn": ["nx:build-native"],
|
||||
"configurations": {
|
||||
"dev": {},
|
||||
"nx-console": {},
|
||||
|
||||
@@ -54,7 +54,9 @@ export type Framework =
|
||||
| 'planetscale'
|
||||
| 'mongodb'
|
||||
| 'mfe'
|
||||
| 'eslint';
|
||||
| 'eslint'
|
||||
| 'office'
|
||||
| 'tool';
|
||||
|
||||
export const frameworkIcons: Record<
|
||||
Framework,
|
||||
@@ -1842,4 +1844,43 @@ export const frameworkIcons: Record<
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
office: {
|
||||
image: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M2.25 21h19.5m-18-18v18m10.5-18v18m6-13.5V21M6.75 6.75h.75m-.75 3h.75m-.75 3h.75m3-6h.75m-.75 3h.75m-.75 3h.75M6.75 21v-3.375c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21M3 3h12m-.75 4.5H21m-3.75 3.75h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008v-.008Z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
tool: {
|
||||
image: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M21.75 6.75a4.5 4.5 0 0 1-4.884 4.484c-1.076-.091-2.264.071-2.95.904l-7.152 8.684a2.548 2.548 0 1 1-3.586-3.586l8.684-7.152c.833-.686.995-1.874.904-2.95a4.5 4.5 0 0 1 6.336-4.486l-3.276 3.276a3.004 3.004 0 0 0 2.25 2.25l3.276-3.276c.256.565.398 1.192.398 1.852Z"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M4.867 19.125h.008v.008h-.008v-.008Z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { readFileSync, readdirSync, accessSync, constants } from 'fs';
|
||||
import { readFileSync, accessSync, constants } from 'fs';
|
||||
import { join, basename, parse, resolve } from 'path';
|
||||
import { extractFrontmatter } from '@nx/nx-dev/ui-markdoc';
|
||||
import { sortPosts } from './blog.util';
|
||||
@@ -19,7 +19,6 @@ export class BlogApi {
|
||||
throw new Error('public blog root cannot be undefined');
|
||||
}
|
||||
}
|
||||
|
||||
async getBlogTags(): Promise<string[]> {
|
||||
const blogs = await this.getBlogs();
|
||||
const tags = new Set<string>();
|
||||
@@ -28,8 +27,15 @@ export class BlogApi {
|
||||
});
|
||||
return Array.from(tags);
|
||||
}
|
||||
async getBlogs(
|
||||
filterFn?: (post: BlogPostDataEntry) => boolean
|
||||
): Promise<BlogPostDataEntry[]> {
|
||||
return await this.getAllBlogs(filterFn);
|
||||
}
|
||||
|
||||
async getBlogs(): Promise<BlogPostDataEntry[]> {
|
||||
async getAllBlogs(
|
||||
filterFn?: (post: BlogPostDataEntry) => boolean
|
||||
): Promise<BlogPostDataEntry[]> {
|
||||
const files: string[] = await readdir(this.options.blogRoot);
|
||||
const authors = JSON.parse(
|
||||
readFileSync(join(this.options.blogRoot, 'authors.json'), 'utf8')
|
||||
@@ -62,66 +68,22 @@ export class BlogApi {
|
||||
ogImageType: type,
|
||||
filePath,
|
||||
slug,
|
||||
podcastYoutubeId: frontmatter.podcastYoutubeId,
|
||||
podcastSpotifyId: frontmatter.podcastSpotifyId,
|
||||
podcastIHeartUrl: frontmatter.podcastIHeartUrl,
|
||||
podcastAppleUrl: frontmatter.podcastAppleUrl,
|
||||
podcastAmazonUrl: frontmatter.podcastAmazonUrl,
|
||||
};
|
||||
if (!frontmatter.draft || process.env.NODE_ENV === 'development') {
|
||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||
const shouldIncludePost = !frontmatter.draft || isDevelopment;
|
||||
|
||||
if (shouldIncludePost && (!filterFn || filterFn(post))) {
|
||||
allPosts.push(post);
|
||||
}
|
||||
}
|
||||
return sortPosts(allPosts);
|
||||
}
|
||||
|
||||
getBlogPosts(): BlogPostDataEntry[] {
|
||||
const files: string[] = readdirSync(this.options.blogRoot);
|
||||
const authors = JSON.parse(
|
||||
readFileSync(join(this.options.blogRoot, 'authors.json'), 'utf8')
|
||||
);
|
||||
const allPosts: BlogPostDataEntry[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = join(this.options.blogRoot, file);
|
||||
// filter out directories (e.g. images)
|
||||
if (!filePath.endsWith('.md')) continue;
|
||||
|
||||
const content = readFileSync(filePath, 'utf8');
|
||||
const frontmatter = extractFrontmatter(content);
|
||||
const slug = this.calculateSlug(filePath, frontmatter);
|
||||
const { image, type } = this.determineOgImage(frontmatter.cover_image);
|
||||
const post = {
|
||||
content,
|
||||
title: frontmatter.title ?? null,
|
||||
description: frontmatter.description ?? null,
|
||||
authors: authors.filter((author) =>
|
||||
frontmatter.authors.includes(author.name)
|
||||
),
|
||||
date: this.calculateDate(file, frontmatter),
|
||||
cover_image: frontmatter.cover_image
|
||||
? `/documentation${frontmatter.cover_image}` // Match the prefix used by markdown parser
|
||||
: null,
|
||||
tags: frontmatter.tags ?? [],
|
||||
reposts: frontmatter.reposts ?? [],
|
||||
pinned: frontmatter.pinned ?? false,
|
||||
ogImage: image,
|
||||
ogImageType: type,
|
||||
filePath,
|
||||
slug,
|
||||
};
|
||||
|
||||
if (!frontmatter.draft || process.env.NODE_ENV === 'development') {
|
||||
allPosts.push(post);
|
||||
}
|
||||
}
|
||||
|
||||
return sortPosts(allPosts);
|
||||
}
|
||||
|
||||
getBlogPost(slug: string): BlogPostDataEntry {
|
||||
const blogs = this.getBlogPosts();
|
||||
const blog = blogs.find((b) => b.slug === slug);
|
||||
if (!blog) {
|
||||
throw new Error(`Could not find blog post with slug: ${slug}`);
|
||||
}
|
||||
return blog;
|
||||
}
|
||||
// Optimize this so we don't read the FS multiple times
|
||||
async getBlogPostBySlug(
|
||||
slug: string | null
|
||||
@@ -171,6 +133,7 @@ export class BlogApi {
|
||||
if (!imagePath) {
|
||||
return { image: defaultImage, type: defaultType };
|
||||
}
|
||||
|
||||
const { ext } = parse(imagePath);
|
||||
|
||||
if (!allowedExtensions.includes(ext)) {
|
||||
@@ -190,10 +153,14 @@ export class BlogApi {
|
||||
}
|
||||
|
||||
return {
|
||||
image: imagePath.replace(ext, foundExt),
|
||||
image: join('documentation', imagePath.replace(ext, foundExt)),
|
||||
type: foundExt.replace('.', ''),
|
||||
};
|
||||
}
|
||||
return { image: imagePath, type: ext.replace('.', '') };
|
||||
|
||||
return {
|
||||
image: join('documentation', imagePath),
|
||||
type: ext.replace('.', ''),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,11 @@ export type BlogPostDataEntry = {
|
||||
pinned?: boolean;
|
||||
filePath: string;
|
||||
slug: string;
|
||||
ogImage: string;
|
||||
ogImageType: string;
|
||||
podcastYoutubeId?: string;
|
||||
podcastSpotifyId?: string;
|
||||
podcastAmazonUrl?: string;
|
||||
podcastAppleUrl?: string;
|
||||
podcastIHeartUrl?: string;
|
||||
};
|
||||
|
||||
export type BlogAuthor = {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { BlogApi } from './blog.api';
|
||||
import { PodcastDataEntry } from './podcast.model';
|
||||
|
||||
export class PodcastApi {
|
||||
_blogApi: BlogApi;
|
||||
|
||||
constructor(options: { blogApi: BlogApi }) {
|
||||
this._blogApi = options.blogApi;
|
||||
}
|
||||
|
||||
async getPodcastBlogs(): Promise<PodcastDataEntry[]> {
|
||||
return await this._blogApi.getBlogs((post) =>
|
||||
post.tags.map((t) => t.toLowerCase()).includes('podcast')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { BlogPostDataEntry } from './blog.model';
|
||||
|
||||
export interface PodcastDataEntry extends BlogPostDataEntry {
|
||||
duration?: string;
|
||||
}
|
||||
@@ -4,3 +4,5 @@ export * from './lib/blog.util';
|
||||
export * from './lib/blog.api';
|
||||
export * from './lib/blog.model';
|
||||
export * from './lib/tags.api';
|
||||
export * from './lib/podcast.model';
|
||||
export * from './lib/podcast.api';
|
||||
|
||||
@@ -26,7 +26,7 @@ export const metadata: Metadata = {
|
||||
},
|
||||
};
|
||||
async function getBlogs() {
|
||||
return await blogApi.getBlogPosts();
|
||||
return await blogApi.getBlogs();
|
||||
}
|
||||
|
||||
async function getBlogTags() {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Metadata } from 'next';
|
||||
import { podcastApi } from '../../lib/podcast.api';
|
||||
import { DefaultLayout } from '@nx/nx-dev/ui-common';
|
||||
import { Hero, PodcastList } from '@nx/nx-dev/ui-podcast';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Nx Podcast - Updates from the Nx & Nx Cloud team',
|
||||
description: 'Latest podcasts from the Nx & Nx Cloud core team',
|
||||
openGraph: {
|
||||
url: 'https://nx.dev/podcast',
|
||||
title: 'Nx Podcast - Updates from the Nx & Nx Cloud team',
|
||||
description:
|
||||
'Stay updated with the latest podcasts from the Nx & Nx Cloud team.',
|
||||
images: [
|
||||
{
|
||||
url: 'https://nx.dev/socials/nx-media.png',
|
||||
width: 800,
|
||||
height: 421,
|
||||
alt: 'Nx: Smart Monorepos · Fast CI',
|
||||
type: 'image/jpeg',
|
||||
},
|
||||
],
|
||||
siteName: 'NxDev',
|
||||
type: 'website',
|
||||
},
|
||||
};
|
||||
|
||||
async function getPodcasts() {
|
||||
return await podcastApi.getPodcastBlogs();
|
||||
}
|
||||
export default async function Page() {
|
||||
const podcasts = await getPodcasts();
|
||||
return (
|
||||
<DefaultLayout>
|
||||
<Hero />
|
||||
<PodcastList podcasts={podcasts} />
|
||||
</DefaultLayout>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { blogApi } from './blog.api';
|
||||
import { PodcastApi } from '@nx/nx-dev/data-access-documents/node-only';
|
||||
|
||||
export const podcastApi = new PodcastApi({ blogApi });
|
||||
@@ -4,13 +4,12 @@ import { ProcessedDocument, RelatedDocument } from '@nx/nx-dev/models-document';
|
||||
import { MenuItem } from '@nx/nx-dev/models-menu';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { GetStaticPaths, GetStaticProps } from 'next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { menusApi } from '../lib/menus.api';
|
||||
import { useNavToggle } from '../lib/navigation-toggle.effect';
|
||||
import { nxDocumentationApi } from '../lib/nx.api';
|
||||
import { tagsApi } from '../lib/tags.api';
|
||||
import { fetchGithubStarCount } from '../lib/githubStars.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function NxDocumentation({
|
||||
document,
|
||||
@@ -23,25 +22,7 @@ export default function NxDocumentation({
|
||||
relatedDocuments: RelatedDocument[];
|
||||
widgetData: { githubStarsCount: number };
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const menuWithSections = {
|
||||
sections: [getBasicNxSection(menu)],
|
||||
@@ -62,18 +43,13 @@ export default function NxDocumentation({
|
||||
toggleNav={toggleNav}
|
||||
navIsOpen={navIsOpen}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<DocViewer
|
||||
document={document}
|
||||
relatedDocuments={relatedDocuments}
|
||||
widgetData={widgetData}
|
||||
/>
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -129,7 +129,7 @@ export default async function handler(request: NextRequest) {
|
||||
|
||||
const response: Stream<OpenAI.Chat.Completions.ChatCompletionChunk> =
|
||||
await openai.chat.completions.create({
|
||||
model: 'gpt-3.5-turbo-16k',
|
||||
model: 'gpt-4o-mini',
|
||||
messages: chatMessages,
|
||||
temperature: 0,
|
||||
stream: true,
|
||||
|
||||
@@ -4,13 +4,12 @@ import { ProcessedDocument, RelatedDocument } from '@nx/nx-dev/models-document';
|
||||
import { Menu, MenuItem } from '@nx/nx-dev/models-menu';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { GetStaticPaths, GetStaticProps } from 'next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { ciApi } from '../../lib/ci.api';
|
||||
import { menusApi } from '../../lib/menus.api';
|
||||
import { useNavToggle } from '../../lib/navigation-toggle.effect';
|
||||
import { tagsApi } from '../../lib/tags.api';
|
||||
import { fetchGithubStarCount } from '../../lib/githubStars.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function Pages({
|
||||
document,
|
||||
@@ -23,25 +22,7 @@ export default function Pages({
|
||||
relatedDocuments: RelatedDocument[];
|
||||
widgetData: { githubStarsCount: number };
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: {
|
||||
document: ProcessedDocument;
|
||||
@@ -70,18 +51,13 @@ export default function Pages({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<DocViewer
|
||||
document={vm.document}
|
||||
relatedDocuments={vm.relatedDocuments}
|
||||
widgetData={widgetData}
|
||||
/>
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,13 +4,12 @@ import { ProcessedDocument, RelatedDocument } from '@nx/nx-dev/models-document';
|
||||
import { Menu, MenuItem } from '@nx/nx-dev/models-menu';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { GetStaticProps } from 'next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { ciApi } from '../../lib/ci.api';
|
||||
import { menusApi } from '../../lib/menus.api';
|
||||
import { useNavToggle } from '../../lib/navigation-toggle.effect';
|
||||
import { tagsApi } from '../../lib/tags.api';
|
||||
import { fetchGithubStarCount } from '../../lib/githubStars.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function CloudRoot({
|
||||
document,
|
||||
@@ -23,25 +22,7 @@ export default function CloudRoot({
|
||||
relatedDocuments: RelatedDocument[];
|
||||
widgetData: { githubStarsCount: number };
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: {
|
||||
document: ProcessedDocument;
|
||||
@@ -70,18 +51,13 @@ export default function CloudRoot({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<DocViewer
|
||||
document={document}
|
||||
relatedDocuments={vm.relatedDocuments}
|
||||
widgetData={widgetData}
|
||||
/>
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useNavToggle } from '../../lib/navigation-toggle.effect';
|
||||
import { nxPluginsApi } from '../../lib/plugins.api';
|
||||
import { tagsApi } from '../../lib/tags.api';
|
||||
import { fetchGithubStarCount } from '../../lib/githubStars.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function Pages({
|
||||
document,
|
||||
@@ -23,25 +24,7 @@ export default function Pages({
|
||||
relatedDocuments: RelatedDocument[];
|
||||
widgetData: { githubStarsCount: number };
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: {
|
||||
document: ProcessedDocument;
|
||||
@@ -70,18 +53,13 @@ export default function Pages({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<DocViewer
|
||||
document={vm.document}
|
||||
relatedDocuments={vm.relatedDocuments}
|
||||
widgetData={widgetData}
|
||||
/>
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,13 +4,12 @@ import { ProcessedDocument, RelatedDocument } from '@nx/nx-dev/models-document';
|
||||
import { Menu, MenuItem } from '@nx/nx-dev/models-menu';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { GetStaticProps } from 'next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { menusApi } from '../../lib/menus.api';
|
||||
import { useNavToggle } from '../../lib/navigation-toggle.effect';
|
||||
import { nxPluginsApi } from '../../lib/plugins.api';
|
||||
import { tagsApi } from '../../lib/tags.api';
|
||||
import { fetchGithubStarCount } from '../../lib/githubStars.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function PluginsRoot({
|
||||
document,
|
||||
@@ -23,25 +22,7 @@ export default function PluginsRoot({
|
||||
relatedDocuments: RelatedDocument[];
|
||||
widgetData: { githubStarsCount: number };
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: {
|
||||
document: ProcessedDocument;
|
||||
@@ -70,18 +51,13 @@ export default function PluginsRoot({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<DocViewer
|
||||
document={document}
|
||||
relatedDocuments={vm.relatedDocuments}
|
||||
widgetData={widgetData}
|
||||
/>
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,13 +7,12 @@ import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { GetStaticPaths } from 'next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { nxPackagesApi } from '../../../../lib/packages.api';
|
||||
import { tagsApi } from '../../../../lib/tags.api';
|
||||
import { fetchGithubStarCount } from '../../../../lib/githubStars.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function PackageDocument({
|
||||
document,
|
||||
@@ -27,25 +26,7 @@ export default function PackageDocument({
|
||||
relatedDocuments: RelatedDocument[];
|
||||
widgetData: { githubStarsCount: number };
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: {
|
||||
document: ProcessedDocument;
|
||||
@@ -77,18 +58,13 @@ export default function PackageDocument({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<DocViewer
|
||||
document={vm.document}
|
||||
relatedDocuments={vm.relatedDocuments}
|
||||
widgetData={widgetData}
|
||||
/>
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,12 +4,11 @@ import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { GetStaticPaths } from 'next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { PackageSchemaSubList } from '@nx/nx-dev/feature-package-schema-viewer/src/lib/package-schema-sub-list';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { nxPackagesApi } from '../../../../lib/packages.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function DocumentsIndex({
|
||||
menu,
|
||||
@@ -18,25 +17,7 @@ export default function DocumentsIndex({
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
|
||||
menu: {
|
||||
@@ -69,14 +50,9 @@ export default function DocumentsIndex({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaSubList pkg={vm.package} type={'document'} />
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
@@ -92,6 +68,7 @@ export const getStaticPaths: GetStaticPaths = () => {
|
||||
fallback: 'blocking',
|
||||
};
|
||||
};
|
||||
|
||||
export async function getStaticProps({
|
||||
params,
|
||||
}: {
|
||||
|
||||
@@ -8,11 +8,10 @@ import {
|
||||
} from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { GetStaticPaths } from 'next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { nxPackagesApi } from '../../../../lib/packages.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function PackageExecutor({
|
||||
menu,
|
||||
@@ -23,25 +22,7 @@ export default function PackageExecutor({
|
||||
pkg: ProcessedPackageMetadata;
|
||||
schema: SchemaMetadata;
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: {
|
||||
menu: Menu;
|
||||
@@ -79,14 +60,9 @@ export default function PackageExecutor({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
@@ -124,6 +100,7 @@ function getData(
|
||||
menu: menusApi.getMenu('nx-api', 'nx-api'),
|
||||
};
|
||||
}
|
||||
|
||||
export async function getStaticProps({
|
||||
params,
|
||||
}: {
|
||||
|
||||
@@ -4,12 +4,11 @@ import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { GetStaticPaths } from 'next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { PackageSchemaSubList } from '@nx/nx-dev/feature-package-schema-viewer/src/lib/package-schema-sub-list';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { nxPackagesApi } from '../../../../lib/packages.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function ExecutorsIndex({
|
||||
menu,
|
||||
@@ -18,25 +17,7 @@ export default function ExecutorsIndex({
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
|
||||
menu: {
|
||||
@@ -69,14 +50,9 @@ export default function ExecutorsIndex({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaSubList pkg={vm.package} type={'executor'} />
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
@@ -92,6 +68,7 @@ export const getStaticPaths: GetStaticPaths = () => {
|
||||
fallback: 'blocking',
|
||||
};
|
||||
};
|
||||
|
||||
export async function getStaticProps({
|
||||
params,
|
||||
}: {
|
||||
|
||||
@@ -8,11 +8,10 @@ import {
|
||||
} from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { GetStaticPaths } from 'next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { nxPackagesApi } from '../../../../lib/packages.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function PackageGenerator({
|
||||
menu,
|
||||
@@ -23,26 +22,7 @@ export default function PackageGenerator({
|
||||
pkg: ProcessedPackageMetadata;
|
||||
schema: SchemaMetadata;
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: {
|
||||
menu: Menu;
|
||||
package: ProcessedPackageMetadata;
|
||||
@@ -79,14 +59,9 @@ export default function PackageGenerator({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
@@ -124,6 +99,7 @@ function getData(
|
||||
menu: menusApi.getMenu('nx-api', 'nx-api'),
|
||||
};
|
||||
}
|
||||
|
||||
export async function getStaticProps({
|
||||
params,
|
||||
}: {
|
||||
|
||||
@@ -4,12 +4,11 @@ import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { GetStaticPaths } from 'next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { PackageSchemaSubList } from '@nx/nx-dev/feature-package-schema-viewer/src/lib/package-schema-sub-list';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { nxPackagesApi } from '../../../../lib/packages.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function GeneratorsIndex({
|
||||
menu,
|
||||
@@ -18,25 +17,7 @@ export default function GeneratorsIndex({
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
|
||||
menu: {
|
||||
@@ -69,14 +50,9 @@ export default function GeneratorsIndex({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaSubList pkg={vm.package} type={'generator'} />
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
@@ -92,6 +68,7 @@ export const getStaticPaths: GetStaticPaths = () => {
|
||||
fallback: 'blocking',
|
||||
};
|
||||
};
|
||||
|
||||
export async function getStaticProps({
|
||||
params,
|
||||
}: {
|
||||
|
||||
@@ -6,12 +6,11 @@ import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { GetStaticPaths } from 'next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { menusApi } from '../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../lib/navigation-toggle.effect';
|
||||
import { nxPackagesApi } from '../../../lib/packages.api';
|
||||
import { tagsApi } from '../../../lib/tags.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function Package({
|
||||
overview,
|
||||
@@ -22,25 +21,7 @@ export default function Package({
|
||||
overview: string;
|
||||
pkg: ProcessedPackageMetadata;
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
|
||||
menu: {
|
||||
@@ -73,14 +54,9 @@ export default function Package({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaList pkg={vm.package} overview={overview} />
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
@@ -129,6 +105,7 @@ function getData(packageName: string): {
|
||||
pkg,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params }: { params: { name: string } }) {
|
||||
try {
|
||||
return { props: getData(params.name) };
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useMemo } from 'react';
|
||||
import { menusApi } from '../../lib/menus.api';
|
||||
import { useNavToggle } from '../../lib/navigation-toggle.effect';
|
||||
import { nxPackagesApi } from '../../lib/packages.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function Packages({
|
||||
packages,
|
||||
@@ -97,11 +98,7 @@ export default function Packages({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent>
|
||||
<div className="mx-auto w-full grow items-stretch px-4 sm:px-6 lg:px-8 2xl:max-w-6xl">
|
||||
<div id="content-wrapper" className="w-full flex-auto flex-col">
|
||||
<div className="mb-6 pt-8">
|
||||
@@ -152,7 +149,7 @@ export default function Packages({
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -3,12 +3,11 @@ import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { PackageSchemaSubList } from '@nx/nx-dev/feature-package-schema-viewer/src/lib/package-schema-sub-list';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function DocumentsIndex({
|
||||
menu,
|
||||
@@ -17,25 +16,7 @@ export default function DocumentsIndex({
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
|
||||
menu: {
|
||||
@@ -68,14 +49,9 @@ export default function DocumentsIndex({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaSubList pkg={vm.package} type={'document'} />
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,13 +5,12 @@ import { ProcessedDocument, RelatedDocument } from '@nx/nx-dev/models-document';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { content } from '../../../../lib/rspack/content/overview';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { fetchGithubStarCount } from '../../../../lib/githubStars.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function Overview({
|
||||
document,
|
||||
@@ -25,25 +24,7 @@ export default function Overview({
|
||||
relatedDocuments: RelatedDocument[];
|
||||
widgetData: { githubStarsCount: number };
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: {
|
||||
document: ProcessedDocument;
|
||||
@@ -75,18 +56,13 @@ export default function Overview({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<DocViewer
|
||||
document={vm.document}
|
||||
relatedDocuments={vm.relatedDocuments}
|
||||
widgetData={widgetData}
|
||||
/>
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,13 +5,12 @@ import { ProcessedDocument, RelatedDocument } from '@nx/nx-dev/models-document';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { content } from '../../../../lib/rspack/content/rspack-config-setup';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { fetchGithubStarCount } from '../../../../lib/githubStars.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function RspackConfigSetup({
|
||||
document,
|
||||
@@ -25,25 +24,7 @@ export default function RspackConfigSetup({
|
||||
relatedDocuments: RelatedDocument[];
|
||||
widgetData: { githubStarsCount: number };
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: {
|
||||
document: ProcessedDocument;
|
||||
@@ -75,18 +56,13 @@ export default function RspackConfigSetup({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<DocViewer
|
||||
document={vm.document}
|
||||
relatedDocuments={vm.relatedDocuments}
|
||||
widgetData={widgetData}
|
||||
/>
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,13 +5,12 @@ import { ProcessedDocument, RelatedDocument } from '@nx/nx-dev/models-document';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { content } from '../../../../lib/rspack/content/rspack-plugin';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { fetchGithubStarCount } from '../../../../lib/githubStars.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function RspackPlugins({
|
||||
document,
|
||||
@@ -25,25 +24,7 @@ export default function RspackPlugins({
|
||||
relatedDocuments: RelatedDocument[];
|
||||
widgetData: { githubStarsCount: number };
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: {
|
||||
document: ProcessedDocument;
|
||||
@@ -75,18 +56,13 @@ export default function RspackPlugins({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<DocViewer
|
||||
document={vm.document}
|
||||
relatedDocuments={vm.relatedDocuments}
|
||||
widgetData={widgetData}
|
||||
/>
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,12 +7,11 @@ import {
|
||||
SchemaMetadata,
|
||||
} from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { schema } from '../../../../lib/rspack/schema/executors/dev-server';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function DevServerExecutor({
|
||||
menu,
|
||||
@@ -23,25 +22,7 @@ export default function DevServerExecutor({
|
||||
pkg: ProcessedPackageMetadata;
|
||||
schema: SchemaMetadata;
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: {
|
||||
menu: Menu;
|
||||
@@ -79,14 +60,9 @@ export default function DevServerExecutor({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,12 +3,11 @@ import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { PackageSchemaSubList } from '@nx/nx-dev/feature-package-schema-viewer/src/lib/package-schema-sub-list';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function ExecutorsIndex({
|
||||
menu,
|
||||
@@ -17,25 +16,7 @@ export default function ExecutorsIndex({
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
|
||||
menu: {
|
||||
@@ -68,14 +49,9 @@ export default function ExecutorsIndex({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaSubList pkg={vm.package} type={'executor'} />
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,12 +7,11 @@ import {
|
||||
SchemaMetadata,
|
||||
} from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { schema } from '../../../../lib/rspack/schema/executors/rspack';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function RspackExecutor({
|
||||
menu,
|
||||
@@ -23,25 +22,7 @@ export default function RspackExecutor({
|
||||
pkg: ProcessedPackageMetadata;
|
||||
schema: SchemaMetadata;
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: {
|
||||
menu: Menu;
|
||||
@@ -79,14 +60,9 @@ export default function RspackExecutor({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,12 +7,11 @@ import {
|
||||
SchemaMetadata,
|
||||
} from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { schema } from '../../../../lib/rspack/schema/generators/application';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function ApplicationGenerator({
|
||||
menu,
|
||||
@@ -23,25 +22,7 @@ export default function ApplicationGenerator({
|
||||
pkg: ProcessedPackageMetadata;
|
||||
schema: SchemaMetadata;
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: {
|
||||
menu: Menu;
|
||||
@@ -79,14 +60,9 @@ export default function ApplicationGenerator({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,12 +7,11 @@ import {
|
||||
SchemaMetadata,
|
||||
} from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { schema } from '../../../../lib/rspack/schema/generators/configuration';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function ConfigurationGenerator({
|
||||
menu,
|
||||
@@ -23,25 +22,7 @@ export default function ConfigurationGenerator({
|
||||
pkg: ProcessedPackageMetadata;
|
||||
schema: SchemaMetadata;
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: {
|
||||
menu: Menu;
|
||||
@@ -79,14 +60,9 @@ export default function ConfigurationGenerator({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,12 +3,11 @@ import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { PackageSchemaSubList } from '@nx/nx-dev/feature-package-schema-viewer/src/lib/package-schema-sub-list';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function GeneratorsIndex({
|
||||
menu,
|
||||
@@ -17,25 +16,7 @@ export default function GeneratorsIndex({
|
||||
menu: MenuItem[];
|
||||
pkg: ProcessedPackageMetadata;
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
|
||||
menu: {
|
||||
@@ -68,14 +49,9 @@ export default function GeneratorsIndex({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaSubList pkg={vm.package} type={'generator'} />
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,12 +7,11 @@ import {
|
||||
SchemaMetadata,
|
||||
} from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { menusApi } from '../../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
|
||||
import { schema } from '../../../../lib/rspack/schema/generators/init';
|
||||
import { pkg } from '../../../../lib/rspack/pkg';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function InitGenerator({
|
||||
menu,
|
||||
@@ -23,25 +22,7 @@ export default function InitGenerator({
|
||||
pkg: ProcessedPackageMetadata;
|
||||
schema: SchemaMetadata;
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: {
|
||||
menu: Menu;
|
||||
@@ -79,14 +60,9 @@ export default function InitGenerator({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,12 +4,11 @@ import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
|
||||
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
|
||||
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
|
||||
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { menusApi } from '../../../lib/menus.api';
|
||||
import { useNavToggle } from '../../../lib/navigation-toggle.effect';
|
||||
import { content } from '../../../lib/rspack/content/overview';
|
||||
import { pkg } from '../../../lib/rspack/pkg';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
export default function RspackIndex({
|
||||
overview,
|
||||
@@ -20,25 +19,7 @@ export default function RspackIndex({
|
||||
overview: string;
|
||||
pkg: ProcessedPackageMetadata;
|
||||
}): JSX.Element {
|
||||
const router = useRouter();
|
||||
const { toggleNav, navIsOpen } = useNavToggle();
|
||||
const wrapperElement = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = (url: string) => {
|
||||
if (url.includes('#')) return;
|
||||
if (!wrapperElement) return;
|
||||
|
||||
(wrapperElement as any).current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router, wrapperElement]);
|
||||
|
||||
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
|
||||
menu: {
|
||||
@@ -71,14 +52,9 @@ export default function RspackIndex({
|
||||
navIsOpen={navIsOpen}
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
<div
|
||||
ref={wrapperElement}
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent resetScrollOnNavigation={true}>
|
||||
<PackageSchemaList pkg={vm.package} overview={overview} />
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useRouter } from 'next/router';
|
||||
import { menusApi } from '../lib/menus.api';
|
||||
import { useNavToggle } from '../lib/navigation-toggle.effect';
|
||||
import { nxPackagesApi } from '../lib/packages.api';
|
||||
import { ScrollableContent } from '@nx/ui-scrollable-content';
|
||||
|
||||
declare const fetch: any;
|
||||
let qualityIndicators = require('./quality-indicators.json');
|
||||
@@ -112,11 +113,7 @@ export default function Browse(props: BrowseProps): JSX.Element {
|
||||
toggleNav={toggleNav}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
id="wrapper"
|
||||
data-testid="wrapper"
|
||||
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
|
||||
>
|
||||
<ScrollableContent>
|
||||
<div className="mx-auto w-full grow items-stretch px-4 sm:px-6 lg:px-8 2xl:max-w-6xl">
|
||||
<div id="content-wrapper" className="w-full flex-auto flex-col">
|
||||
<div className="mb-6 pt-8">
|
||||
@@ -130,7 +127,7 @@ export default function Browse(props: BrowseProps): JSX.Element {
|
||||
Are you a plugin author? You can{' '}
|
||||
<a
|
||||
className="underline"
|
||||
href="/extending-nx/tutorials/publish-plugin#list-your-nx-plugin"
|
||||
href="/extending-nx/tutorials/tooling-plugin#list-your-nx-plugin"
|
||||
>
|
||||
add your plugin to the registry
|
||||
</a>{' '}
|
||||
@@ -141,7 +138,7 @@ export default function Browse(props: BrowseProps): JSX.Element {
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
</ScrollableContent>
|
||||
</main>
|
||||
</div>
|
||||
</>
|
||||
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 23 KiB |
@@ -805,7 +805,11 @@ const nested5minuteTutorialUrls = {
|
||||
|
||||
const pluginUrls = {
|
||||
'/plugin-features/create-your-own-plugin':
|
||||
'/extending-nx/tutorials/create-plugin',
|
||||
'/extending-nx/tutorials/organization-specific-plugin',
|
||||
'/extending-nx/tutorials/create-plugin':
|
||||
'/extending-nx/tutorials/organization-specific-plugin',
|
||||
'/extending-nx/tutorials/publish-plugin':
|
||||
'/extending-nx/tutorials/tooling-plugin',
|
||||
'/recipes/advanced-plugins': '/extending-nx/recipes',
|
||||
'/recipes/advanced-plugins/create-preset':
|
||||
'/extending-nx/recipes/create-preset',
|
||||
|
||||
@@ -4,6 +4,7 @@ import Image from 'next/image';
|
||||
import { BlogAuthors } from './authors';
|
||||
import { ChevronLeftIcon } from '@heroicons/react/24/outline';
|
||||
import { renderMarkdown } from '@nx/nx-dev/ui-markdoc';
|
||||
import { EpisodePlayer } from './episode-player';
|
||||
|
||||
export interface BlogDetailsProps {
|
||||
post: BlogPostDataEntry;
|
||||
@@ -65,16 +66,28 @@ export function BlogDetails({ post }: BlogDetailsProps) {
|
||||
{post.title}
|
||||
</h1>
|
||||
</header>
|
||||
{post.cover_image && (
|
||||
<div className="mx-auto mb-16 aspect-[1.7] w-full max-w-screen-md">
|
||||
<Image
|
||||
className="h-full w-full object-cover md:rounded-md"
|
||||
src={post.cover_image}
|
||||
alt={post.title}
|
||||
width={1400}
|
||||
height={735}
|
||||
{post.podcastYoutubeId && post.podcastSpotifyId ? (
|
||||
<div className="mx-auto mb-16 w-full max-w-screen-md">
|
||||
<EpisodePlayer
|
||||
podcastYoutubeId={post.podcastYoutubeId}
|
||||
podcastSpotifyId={post.podcastSpotifyId}
|
||||
amazonUrl={post.podcastAmazonUrl}
|
||||
appleUrl={post.podcastAppleUrl}
|
||||
iHeartUrl={post.podcastIHeartUrl}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
post.cover_image && (
|
||||
<div className="mx-auto mb-16 aspect-[1.7] w-full max-w-screen-md">
|
||||
<Image
|
||||
className="h-full w-full object-cover md:rounded-md"
|
||||
src={post.cover_image}
|
||||
alt={post.title}
|
||||
width={1400}
|
||||
height={735}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
<div className="mx-auto min-w-0 max-w-3xl flex-auto px-4 pb-24 lg:px-0 lg:pb-16">
|
||||
<div className="relative">
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
AmazonMusicIcon,
|
||||
ApplePodcastsIcon,
|
||||
IHeartRadioIcon,
|
||||
SpotifyIcon,
|
||||
} from '@nx/nx-dev/ui-icons';
|
||||
import Link from 'next/link';
|
||||
|
||||
export function EpisodePlayer({
|
||||
podcastYoutubeId,
|
||||
podcastSpotifyId,
|
||||
amazonUrl,
|
||||
appleUrl,
|
||||
iHeartUrl,
|
||||
}: {
|
||||
podcastYoutubeId: string;
|
||||
podcastSpotifyId: string;
|
||||
amazonUrl?: string;
|
||||
appleUrl?: string;
|
||||
iHeartUrl?: string;
|
||||
}) {
|
||||
const [viewType, setViewType] = useState<ViewMode>('audio');
|
||||
return (
|
||||
<div className="flex basis-2/3 flex-col items-center justify-center gap-2">
|
||||
{viewType === 'audio' ? (
|
||||
<>
|
||||
<iframe
|
||||
style={{ borderRadius: '12px' }}
|
||||
src={`https://open.spotify.com/embed/episode/${podcastSpotifyId}?utm_source=generator&theme=0`}
|
||||
width="100%"
|
||||
height="152"
|
||||
frameBorder="0"
|
||||
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
|
||||
loading="lazy"
|
||||
key="audio"
|
||||
></iframe>
|
||||
</>
|
||||
) : (
|
||||
<iframe
|
||||
style={{ borderRadius: '12px' }}
|
||||
width="100%"
|
||||
height="400"
|
||||
src={`https://www.youtube.com/embed/${podcastYoutubeId}?si=8rkgAzJfLfd-hxAA`}
|
||||
title="YouTube video player"
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerPolicy="strict-origin-when-cross-origin"
|
||||
allowFullScreen
|
||||
key="video"
|
||||
></iframe>
|
||||
)}
|
||||
<div className="container my-1 flex">
|
||||
<div className="basis-1/2">
|
||||
{viewType === 'audio' && (
|
||||
<PlatformLinks
|
||||
amazonUrl={amazonUrl}
|
||||
appleUrl={appleUrl}
|
||||
iHeartUrl={iHeartUrl}
|
||||
podcastSpotifyId={podcastSpotifyId}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex basis-1/2 flex-wrap justify-end gap-6 sm:gap-4">
|
||||
<button
|
||||
className="flex shrink-0 items-center gap-2 text-slate-400 hover:text-slate-800 dark:text-slate-600 dark:hover:text-slate-200"
|
||||
onClick={() => setViewType(getOpposite(viewType))}
|
||||
>
|
||||
Switch to {getOpposite(viewType) === 'audio' ? 'Audio' : 'Video'}
|
||||
</button>
|
||||
<Link
|
||||
className="flex shrink-0 items-center gap-2 text-slate-400 hover:text-slate-800 dark:text-slate-600 dark:hover:text-slate-200"
|
||||
href="/podcast"
|
||||
>
|
||||
More Podcasts
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PlatformLinks({
|
||||
amazonUrl,
|
||||
appleUrl,
|
||||
iHeartUrl,
|
||||
podcastSpotifyId,
|
||||
}: {
|
||||
amazonUrl?: string;
|
||||
appleUrl?: string;
|
||||
iHeartUrl?: string;
|
||||
podcastSpotifyId: string;
|
||||
}): JSX.Element {
|
||||
const platforms = [
|
||||
{
|
||||
name: 'Amazon Music',
|
||||
url: amazonUrl,
|
||||
icon: AmazonMusicIcon,
|
||||
},
|
||||
{
|
||||
name: 'Apple Podcasts',
|
||||
url: appleUrl,
|
||||
icon: ApplePodcastsIcon,
|
||||
},
|
||||
{
|
||||
name: 'iHeartRadio',
|
||||
url: iHeartUrl,
|
||||
icon: IHeartRadioIcon,
|
||||
},
|
||||
{
|
||||
name: 'Spotify',
|
||||
url: `https://open.spotify.com/episode/${podcastSpotifyId}?si=Nqd7F40hQXugagH8oDxxpA`,
|
||||
icon: SpotifyIcon,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ul className="flex flex-wrap gap-6 sm:gap-4">
|
||||
{platforms
|
||||
.filter((platform) => !!platform.url)
|
||||
.map((platform) => {
|
||||
return (
|
||||
<li
|
||||
key={platform.name}
|
||||
className="inline-block cursor-pointer place-items-center rounded-2xl border border-slate-100 bg-white p-4 text-slate-600 transition-all hover:scale-[1.02] hover:text-slate-950 dark:border-slate-800/60 dark:bg-slate-950 dark:text-slate-400 dark:hover:text-white"
|
||||
>
|
||||
<a
|
||||
href={platform.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex h-full w-full items-center justify-center"
|
||||
>
|
||||
<platform.icon className="h-6 w-6 shrink-0" />
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
type ViewMode = 'audio' | 'video';
|
||||
|
||||
function getOpposite(viewMode: ViewMode): ViewMode {
|
||||
if (viewMode === 'audio') return 'video';
|
||||
return 'audio';
|
||||
}
|
||||
@@ -9,7 +9,7 @@ export interface CTAProps {
|
||||
export function CallToAction({
|
||||
mainActionTitle = 'Get started with Nx',
|
||||
mainActionLinkText = 'Get started',
|
||||
mainActionLink = '/docs',
|
||||
mainActionLink = '/getting-started/intro',
|
||||
}: CTAProps): JSX.Element {
|
||||
return (
|
||||
<section className="relative isolate px-6 py-32 sm:py-40 lg:px-8">
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
UserGroupIcon,
|
||||
ComputerDesktopIcon,
|
||||
GlobeAltIcon,
|
||||
MicrophoneIcon,
|
||||
VideoCameraIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { FC, SVGProps } from 'react';
|
||||
import { DiscordIcon } from '../discord-icon';
|
||||
@@ -172,6 +174,22 @@ export const learnItems: MenuItem[] = [
|
||||
isNew: false,
|
||||
isHighlight: false,
|
||||
},
|
||||
{
|
||||
name: 'Podcasts',
|
||||
description: null,
|
||||
href: '/podcast',
|
||||
icon: MicrophoneIcon,
|
||||
isNew: false,
|
||||
isHighlight: false,
|
||||
},
|
||||
{
|
||||
name: 'Webinars',
|
||||
description: null,
|
||||
href: 'https://go.nx.dev/webinar',
|
||||
icon: ComputerDesktopIcon,
|
||||
isNew: false,
|
||||
isHighlight: false,
|
||||
},
|
||||
{
|
||||
name: 'Video tutorials',
|
||||
description: null,
|
||||
@@ -215,10 +233,10 @@ export const eventItems: MenuItem[] = [
|
||||
isHighlight: false,
|
||||
},
|
||||
{
|
||||
name: 'Webinars',
|
||||
name: 'Live Streams',
|
||||
description: null,
|
||||
href: 'https://go.nx.dev/webinar',
|
||||
icon: ComputerDesktopIcon,
|
||||
href: 'https://www.youtube.com/@nxdevtools/streams',
|
||||
icon: VideoCameraIcon,
|
||||
isNew: false,
|
||||
isHighlight: false,
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ export function SectionsMenu({
|
||||
<div className="flex flex-col gap-2 overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-slate-200 dark:bg-slate-900 dark:ring-slate-800">
|
||||
<div className="divide-y divide-slate-200 dark:divide-slate-800">
|
||||
{Object.keys(sections).map((section) => (
|
||||
<div>
|
||||
<div key={section}>
|
||||
<h5 className="px-4 pt-6 text-sm text-slate-500 dark:text-slate-400">
|
||||
{section}
|
||||
</h5>
|
||||
|
||||
@@ -23,7 +23,7 @@ export function HowCanWeHelp(): JSX.Element {
|
||||
className="h-5 w-5 shrink-0"
|
||||
/>
|
||||
<h3 className="text-xl font-medium text-slate-700 dark:text-slate-300">
|
||||
Sales
|
||||
Talk to Sales
|
||||
</h3>
|
||||
</div>
|
||||
<p className="mt-4">
|
||||
@@ -47,7 +47,7 @@ export function HowCanWeHelp(): JSX.Element {
|
||||
className="h-5 w-5 shrink-0"
|
||||
/>
|
||||
<h3 className="text-xl font-medium text-slate-700 dark:text-slate-300">
|
||||
Engineers
|
||||
Learn more about Nx Cloud
|
||||
</h3>
|
||||
</div>
|
||||
<p className="mt-4">
|
||||
|
||||
@@ -6,6 +6,8 @@ import { Theme, useTheme } from '@nx/nx-dev/ui-theme';
|
||||
import { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useIsomorphicLayoutEffect } from '@nx/nx-dev/ui-primitives';
|
||||
import { MonorepoWorldIcon } from '@nx/nx-dev/ui-icons';
|
||||
import { motion, MotionConfig } from 'framer-motion';
|
||||
|
||||
export function Hero(): JSX.Element {
|
||||
return (
|
||||
@@ -23,23 +25,6 @@ export function Hero(): JSX.Element {
|
||||
</div>
|
||||
<div className="z-20 mx-auto grid h-screen max-w-6xl grid-cols-1 place-items-center text-center">
|
||||
<div className="container">
|
||||
<div className="-mt-16 hidden sm:mb-8 sm:flex sm:justify-center">
|
||||
<div className="relative rounded-full bg-white px-3 py-1 text-sm leading-6 ring-1 ring-slate-900/10 transition-all hover:ring-slate-900/20 dark:bg-slate-950 dark:ring-slate-100/10 dark:hover:ring-slate-100/20">
|
||||
<span className="text-blue-500 dark:text-sky-500">
|
||||
Monorepo World
|
||||
</span>{' '}
|
||||
- The conf for monorepos and dev tooling.{' '}
|
||||
<Link
|
||||
href="https://monorepo.world"
|
||||
title="Discover Nx Agents"
|
||||
className="font-semibold text-blue-500 dark:text-sky-500"
|
||||
prefetch={false}
|
||||
>
|
||||
<span className="absolute inset-0" aria-hidden="true"></span>
|
||||
Find out more <span aria-hidden="true">→</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<SectionHeading as="h1" variant="display" data-cy="primary-heading">
|
||||
<span className="rounded-lg bg-gradient-to-r from-cyan-500 to-blue-500 bg-clip-text text-transparent">
|
||||
Smart
|
||||
@@ -63,7 +48,6 @@ export function Hero(): JSX.Element {
|
||||
variant="subtitle"
|
||||
className="mx-auto mt-6 max-w-4xl"
|
||||
>
|
||||
{/*Structured, maintainable and efficient monorepos. Locally and on CI, easy as that.*/}
|
||||
<Strong>Build system</Strong>, optimized for monorepos, with plugins
|
||||
for popular frameworks and tools and{' '}
|
||||
<Strong>advanced CI capabilities</Strong> including caching and
|
||||
@@ -96,6 +80,58 @@ export function Hero(): JSX.Element {
|
||||
Contact us
|
||||
</ButtonLink>
|
||||
</div>
|
||||
<div className="mt-12 flex justify-center">
|
||||
<MotionConfig reducedMotion="user">
|
||||
<motion.div
|
||||
initial={{
|
||||
y: 16,
|
||||
opacity: 0,
|
||||
}}
|
||||
whileInView={{
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
}}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
ease: 'easeOut',
|
||||
duration: 0.225,
|
||||
delay: 1,
|
||||
}}
|
||||
className="pointer-events-auto relative mx-auto w-full max-w-lg overflow-hidden rounded-lg bg-slate-950 text-left text-white shadow-lg ring-1 ring-white ring-opacity-5 transition hover:bg-slate-800"
|
||||
>
|
||||
<div className="p-4">
|
||||
<div className="flex items-start gap-6">
|
||||
<div className="-m-4 hidden flex-shrink-0 sm:block">
|
||||
<img
|
||||
src="/images/conferences/monorepoworld-vertical-banner.avif"
|
||||
alt="MonorepoWorld conference image"
|
||||
className="size-32"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-0 flex-1 pt-1">
|
||||
<p className="text-base font-semibold">
|
||||
<a
|
||||
href="https://monorepo.world/?utm_source=nxdev&utm_medium=website&utm_campaign=homepage_banner&utm_campaign=monorepoworld"
|
||||
title="Join us at Monorepo World"
|
||||
target="_blank"
|
||||
>
|
||||
<span className="absolute inset-0" />
|
||||
The Nx team will be at Monorepo World!
|
||||
</a>
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-slate-300">
|
||||
The ultimate conference for{' '}
|
||||
<span className="text-[#DDFB24]">monorepos</span> and
|
||||
developer{' '}
|
||||
<span className="text-[#DDFB24]">tooling</span> on 0ct.
|
||||
7. 2024, Mountain View, CA.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</MotionConfig>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -66,7 +66,7 @@ export function TeamAndCommunity(): JSX.Element {
|
||||
<div className="group/item relative flex items-center gap-2 rounded-xl border border-slate-100 p-4 transition hover:text-slate-950 dark:border-slate-800/60 dark:hover:text-white">
|
||||
<DocumentIcon aria-hidden="true" className="size-6 shrink-0" />
|
||||
<Link
|
||||
href="/docs?utm_medium=website&utm_campaign=homepage_links&utm_content=cta_team_and_community"
|
||||
href="/getting-started/intro?utm_medium=website&utm_campaign=homepage_links&utm_content=cta_team_and_community"
|
||||
className="grow text-base"
|
||||
prefetch={false}
|
||||
>
|
||||
|
||||
@@ -73,6 +73,15 @@ export * from './lib/customers/zipari';
|
||||
export * from './lib/editors/jetbrains';
|
||||
export * from './lib/editors/visual-studio-code';
|
||||
|
||||
// OTHERS
|
||||
export * from './lib/monorepo-world';
|
||||
|
||||
// PODCASTS
|
||||
export * from './lib/podcasts/amazon-music';
|
||||
export * from './lib/podcasts/apple-podcasts';
|
||||
export * from './lib/podcasts/i-heart-radio';
|
||||
export * from './lib/podcasts/spotify';
|
||||
|
||||
// PRODUCTS
|
||||
export * from './lib/products';
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ComponentPropsWithoutRef } from 'react';
|
||||
|
||||
export function MonorepoWorldIcon(props: ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<path d="M4.95951 15.2616v-4.7654c0-.82631.64074-1.49877 1.45095-1.49877.77235 0 1.43154.67246 1.43154 1.49877v3.296l1.96008-.9991v-2.2969c0-.82631.65942-1.49877 1.45092-1.49877.7724 0 1.4129.67246 1.4129 1.49877v.8369l1.9746-1.0066C14.5545 8.46636 13.0638 7 11.2536 7c-.9797 0-1.82771.44126-2.44924 1.13332C8.2568 7.44126 7.37148 7 6.41027 7 4.52607 7 3 8.55635 3 10.4962v5.7643l1.95951-.9989ZM17.5251 9.72944v4.76546c0 .8263-.6408 1.4988-1.451 1.4988-.7723 0-1.4315-.6725-1.4315-1.4988v-3.296l-1.9601.9991v2.2969c0 .8263-.6594 1.4988-1.451 1.4988-.7723 0-1.41287-.6725-1.41287-1.4988v-.8369L7.8441 14.6646c.08596 1.8601 1.57666 3.3265 3.3869 3.3265.9796 0 1.8277-.4413 2.4492-1.1333.5476.692 1.4329 1.1333 2.3941 1.1333 1.8842 0 3.4103-1.5564 3.4103-3.4962V8.73073l-1.9595.99871ZM21.0205 18c.541 0 .9795-.4472.9795-.9989 0-.5517-.4385-.9989-.9795-.9989-.5409 0-.9794.4472-.9794.9989 0 .5517.4385.9989.9794.9989Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { FC, SVGProps } from 'react';
|
||||
|
||||
/**
|
||||
* Color: #9933CC
|
||||
*/
|
||||
|
||||
export const ApplePodcastsIcon: FC<SVGProps<SVGSVGElement>> = (props) => {
|
||||
return (
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<title>Apple Podcasts</title>
|
||||
<path d="M5.34 0A5.328 5.328 0 000 5.34v13.32A5.328 5.328 0 005.34 24h13.32A5.328 5.328 0 0024 18.66V5.34A5.328 5.328 0 0018.66 0zm6.525 2.568c2.336 0 4.448.902 6.056 2.587 1.224 1.272 1.912 2.619 2.264 4.392.12.59.12 2.2.007 2.864a8.506 8.506 0 01-3.24 5.296c-.608.46-2.096 1.261-2.336 1.261-.088 0-.096-.091-.056-.46.072-.592.144-.715.48-.856.536-.224 1.448-.874 2.008-1.435a7.644 7.644 0 002.008-3.536c.208-.824.184-2.656-.048-3.504-.728-2.696-2.928-4.792-5.624-5.352-.784-.16-2.208-.16-3 0-2.728.56-4.984 2.76-5.672 5.528-.184.752-.184 2.584 0 3.336.456 1.832 1.64 3.512 3.192 4.512.304.2.672.408.824.472.336.144.408.264.472.856.04.36.03.464-.056.464-.056 0-.464-.176-.896-.384l-.04-.03c-2.472-1.216-4.056-3.274-4.632-6.012-.144-.706-.168-2.392-.03-3.04.36-1.74 1.048-3.1 2.192-4.304 1.648-1.737 3.768-2.656 6.128-2.656zm.134 2.81c.409.004.803.04 1.106.106 2.784.62 4.76 3.408 4.376 6.174-.152 1.114-.536 2.03-1.216 2.88-.336.43-1.152 1.15-1.296 1.15-.023 0-.048-.272-.048-.603v-.605l.416-.496c1.568-1.878 1.456-4.502-.256-6.224-.664-.67-1.432-1.064-2.424-1.246-.64-.118-.776-.118-1.448-.008-1.02.167-1.81.562-2.512 1.256-1.72 1.704-1.832 4.342-.264 6.222l.413.496v.608c0 .336-.027.608-.06.608-.03 0-.264-.16-.512-.36l-.034-.011c-.832-.664-1.568-1.842-1.872-2.997-.184-.698-.184-2.024.008-2.72.504-1.878 1.888-3.335 3.808-4.019.41-.145 1.133-.22 1.814-.211zm-.13 2.99c.31 0 .62.06.844.178.488.253.888.745 1.04 1.259.464 1.578-1.208 2.96-2.72 2.254h-.015c-.712-.331-1.096-.956-1.104-1.77 0-.733.408-1.371 1.112-1.745.224-.117.534-.176.844-.176zm-.011 4.728c.988-.004 1.706.349 1.97.97.198.464.124 1.932-.218 4.302-.232 1.656-.36 2.074-.68 2.356-.44.39-1.064.498-1.656.288h-.003c-.716-.257-.87-.605-1.164-2.644-.341-2.37-.416-3.838-.218-4.302.262-.616.974-.966 1.97-.97z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import { FC, SVGProps } from 'react';
|
||||
|
||||
/**
|
||||
* Color: #C6002B
|
||||
*/
|
||||
|
||||
export const IHeartRadioIcon: FC<SVGProps<SVGSVGElement>> = (props) => {
|
||||
return (
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<title>iHeartRadio</title>
|
||||
<path d="M4.403 21.983c.597 0 1.023-.306 1.023-.817v-.012c0-.489-.375-.784-1.017-.784H3.182v1.613zm-1.67-1.8c0-.125.102-.228.221-.228h1.489c.488 0 .88.148 1.13.398.193.193.307.472.307.784v.011c0 .654-.443 1.034-1.062 1.154l.988 1.272c.046.051.074.102.074.164 0 .12-.114.222-.227.222-.091 0-.16-.05-.21-.12l-1.12-1.453H3.183v1.346a.228.228 0 01-.228.227.227.227 0 01-.221-.227v-3.55m6.674 2.29l-.914-2.035-.915 2.034zm-2.812 1.164l1.614-3.528c.056-.125.142-.2.284-.2h.022c.137 0 .228.075.279.2l1.613 3.522a.31.31 0 01.029.113c0 .12-.097.216-.216.216-.108 0-.182-.074-.222-.165l-.415-.914H7.402l-.415.926c-.04.097-.113.153-.216.153a.204.204 0 01-.204-.204.26.26 0 01.028-.12m6.078-.118c1.005 0 1.647-.682 1.647-1.563v-.011c0-.88-.642-1.574-1.647-1.574h-.932v3.148zm-1.38-3.335c0-.125.102-.228.221-.228h1.16c1.249 0 2.112.858 2.112 1.977v.012c0 1.119-.863 1.988-2.113 1.988h-1.159a.226.226 0 01-.221-.227v-3.522m4.481-.029c0-.124.103-.227.222-.227.125 0 .227.103.227.227v3.579a.228.228 0 01-.227.227.227.227 0 01-.222-.227v-3.579m5.027 1.801v-.011c0-.904-.659-1.642-1.568-1.642s-1.556.727-1.556 1.63v.012c0 .903.659 1.642 1.567 1.642.91 0 1.557-.728 1.557-1.631zm-3.59 0v-.011c0-1.097.824-2.057 2.033-2.057 1.21 0 2.023.949 2.023 2.045v.012c0 1.096-.824 2.056-2.034 2.056s-2.022-.949-2.022-2.045m2.03-17.192c0 1.397-.754 2.773-2.242 4.092a.345.345 0 01-.458-.517c1.333-1.182 2.01-2.385 2.01-3.575v-.016c0-.966-.606-2.103-1.38-2.588a.345.345 0 11.367-.586c.97.61 1.703 1.974 1.703 3.174zM14.76 7.677a.345.345 0 11-.337-.602c.799-.448 1.336-1.318 1.339-2.167a2.096 2.096 0 00-1.124-1.855.345.345 0 11.321-.611 2.785 2.785 0 011.493 2.46v.011c-.004 1.09-.683 2.199-1.692 2.764zm-2.772-1.015a1.498 1.498 0 11.001-2.997 1.498 1.498 0 01-.001 2.997zm-2.303.882a.345.345 0 01-.47.133c-1.009-.565-1.688-1.674-1.692-2.764v-.01a2.785 2.785 0 011.493-2.461.346.346 0 01.321.611 2.096 2.096 0 00-1.124 1.855c.003.849.54 1.719 1.34 2.166a.345.345 0 01.132.47zM7.464 8.825a.344.344 0 01-.488.03C5.49 7.536 4.734 6.16 4.734 4.763v-.016c0-1.2.732-2.564 1.703-3.174a.346.346 0 01.367.586c-.774.485-1.38 1.622-1.38 2.588v.016c0 1.19.677 2.393 2.01 3.575a.345.345 0 01.03.487zM16.152 0c-1.727 0-3.27.915-4.164 2.252C11.094.915 9.55 0 7.823 0A4.982 4.982 0 002.84 4.983c0 1.746 1.106 3.005 2.261 4.17l4.518 4.272a.371.371 0 00.626-.27V9.827c0-.963.78-1.743 1.743-1.745a1.745 1.745 0 011.742 1.745v3.328c0 .326.39.493.626.27l4.518-4.272c1.155-1.165 2.261-2.424 2.261-4.17A4.982 4.982 0 0016.152 0M4.582 14.766h1.194v1.612h1.532v-1.612H8.5v4.307H7.308v-1.637H5.776v1.637H4.582v-4.307m6.527 2.353a.563.563 0 00-.578-.587c-.308 0-.55.238-.578.587zm-2.264.305v-.012c0-.972.696-1.741 1.68-1.741 1.15 0 1.68.842 1.68 1.82 0 .075 0 .16-.007.24H9.971c.093.364.357.549.72.549.277 0 .498-.105.738-.34l.647.536c-.32.406-.782.677-1.447.677-1.045 0-1.784-.695-1.784-1.729m7.29-1.68h1.17v.67c.19-.454.498-.75 1.051-.725v1.23h-.098c-.609 0-.954.351-.954 1.12v1.034h-1.168v-3.329m2.95 2.295v-1.353h-.393v-.942h.393v-.842h1.17v.842h.775v.942h-.775v1.126c0 .234.105.332.32.332.153 0 .301-.043.442-.11v.916c-.209.117-.485.19-.812.19-.7 0-1.12-.307-1.12-1.1m-15.65-3.584a.62.62 0 100 1.24.62.62 0 000-1.24m10.502 3.952c-.303.013-.483-.161-.483-.371 0-.203.16-.307.454-.307h.667v.036c-.004.137-.06.617-.638.642zm1.746-1.008c0-1.033-.739-1.729-1.784-1.729-.665 0-1.126.271-1.447.677l.647.536c.24-.234.461-.34.738-.34.359 0 .621.182.716.537l.001.025-.77.003c-.956.013-1.458.37-1.458 1.045 0 .65.464.999 1.262.999.432 0 .764-.17.987-.401v.32h1.106v-1.628l.002-.032V17.4M3.458 15.99h-.043a.61.61 0 00-.61.61v2.474h1.263v-2.474a.61.61 0 00-.61-.61" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import { FC, SVGProps } from 'react';
|
||||
|
||||
/**
|
||||
* Color: #1DB954
|
||||
*/
|
||||
|
||||
export const SpotifyIcon: FC<SVGProps<SVGSVGElement>> = (props) => {
|
||||
return (
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<title>Spotify</title>
|
||||
<path d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nx/react/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||