Compare commits

...

10 Commits

Author SHA1 Message Date
99dff4fe5c chore: update repository setup for gradle 8.0.2+ [see](https://kotlinlang.org/docs/gradle-configure-project.html#gradle-java-toolchains-support)
Some checks failed
Generate embeddings / cache-and-install (20.19.0) (push) Has been cancelled
publish / stable - armv7-unknown-linux-gnueabihf - node@22.16.0 (push) Has been cancelled
publish / stable - aarch64-unknown-linux-musl - node@22.16.0 (push) Has been cancelled
publish / stable - x86_64-unknown-linux-musl - node@22.16.0 (push) Has been cancelled
publish / Resolve Required Data (push) Has been cancelled
publish / stable - aarch64-pc-windows-msvc - node@22.16.0 (push) Has been cancelled
publish / stable - x86_64-apple-darwin - node@22.16.0 (push) Has been cancelled
publish / stable - x86_64-pc-windows-msvc - node@22.16.0 (push) Has been cancelled
publish / stable - aarch64-unknown-linux-gnu - node@22.16.0 (push) Has been cancelled
publish / stable - x86_64-unknown-linux-gnu - node@22.16.0 (push) Has been cancelled
publish / stable - aarch64-apple-darwin - node@22.16.0 (push) Has been cancelled
publish / Build FreeBSD (push) Has been cancelled
publish / Publish (push) Has been cancelled
publish / (PR Release Failure Only) Create comment for failed PR release (push) Has been cancelled
Issue Statistics / Report status (push) Has been cancelled
2025-06-23 23:31:21 +02:00
Benjamin Cabanes
a74bbaf32c
docs(nx-dev): add Nx Labs page (#31679)
Created a new contact page for Nx Labs to highlight services and provide a form for inquiries. Updated the professional services section in the header menu to include a link to this page.
2025-06-23 15:09:01 -04:00
MaxKless
a8cd1c77e3
chore(repo): add .cursor/mcp.json to gitignore (#31691)
people use it locally with different ports so we should just add it to
gitignore
2025-06-23 15:25:09 +00:00
Benjamin Cabanes
4e55020b1b
docs(nx-dev): update ai page (#31669)
Co-authored-by: Juri <juri.strumpflohner@gmail.com>
2025-06-23 13:42:11 +00:00
Juri
755de341a4 docs(nx-dev): add self-healing CI blog post 2025-06-23 14:53:57 +02:00
Jonathan Gelin
fd31fa633d
fix(js): resolve asset paths relative to workspace root instead of cwd (#31664)
## Description

This PR fixes an issue where asset files copied during a build using the
`@nx/js:tsc` executor are placed in the wrong directory depending on the
current working directory from which the `nx` command is executed.

This behavior becomes particularly problematic in scenarios like release
workflows that rely on `preVersionCommand` to run E2E tests. For
instance, when using tools like Jest from the root of an E2E project,
scripts like `start-local-registry` may trigger a build and run the
`preVersionCommand`. However, instead of placing assets in the expected
`dist` folder of the project, they are incorrectly copied relative to
the E2E folder’s location.


## Reproduction Steps

1. Create a new Nx workspace:

   ```bash
npx --yes create-nx-workspace assets-issue --preset=ts --no-interactive
   cd assets-issue
   ```

2. Add the Nx Plugin package:

   ```bash
   nx add @nx/plugin
   ```

3. Generate a new plugin:

   ```bash
nx g @nx/plugin:plugin packages/my-plugin --linter eslint
--unitTestRunner jest
   ```

4. Add a generator to the plugin:

   ```bash
nx g @nx/plugin:generator packages/my-plugin/src/generators/my-generator
   ```

5. Build the plugin from the workspace root:

   ```bash
   nx build my-plugin
   ```

    Assets are copied correctly:

   ```
   dist/packages/my-plugin/generators/files/src/index.ts.template
   dist/packages/my-plugin/generators/schema.json
   dist/packages/my-plugin/generators/schema.d.ts
   ```

6. Now build the same project from a nested folder:

   ```bash
   mkdir e2e && cd e2e
   nx build my-plugin --skip-nx-cache
   ```

    Assets are copied relative to the current folder:

   ```
   e2e/packages/my-plugin/dist/generators/files/src/index.ts.template
   e2e/packages/my-plugin/dist/generators/schema.json
   e2e/packages/my-plugin/dist/generators/schema.d.ts
   ```

## Expected Behavior

The build output—especially copied assets—should always respect the
project’s `outputPath` configuration regardless of where the `nx`
command is invoked from. The behavior should be consistent and **not
influenced by `process.cwd()`**.
2025-06-23 08:42:59 +02:00
Jack Hsu
57e70d0e91
feat(js): deprecate simpleName option in library generator (#31673)
The simpleName option is no longer useful as we've moved to using
options "as provided" without transformation. Users should provide the
exact name, directory, and import path they want to use.

## Changes
- Add x-deprecated to schema.json marking for removal in Nx 22
- Add runtime warning when simpleName is used


🤖 Generated with [Claude Code](https://claude.ai/code)

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

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

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

## Current Behavior
Users are confused with `--simpleName` with using `--name` AND
`--directory`

## Expected Behavior
We should tell users that only `--name` should be used.

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

Fixes #29508

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-06-20 21:39:55 -04:00
Benjamin Cabanes
8026885128
docs(nx-dev): fix typo (#31677)
Update callout type from 'warn' to 'warning' in CVE blog post.
2025-06-20 20:59:15 +00:00
Nicholas Cunningham
df75799ed7
fix(js): failing e2e test due to dependency (#31676)
This PR updates our `e2e-js` test to include dependencies for all
package managers and not just pnpm.

E2E Matrix for `e2e-js` is now passing:
https://github.com/nrwl/nx/actions/runs/15786673606/job/44504580439
2025-06-20 20:28:49 +00:00
Jack Hsu
1f493bf251
docs(react): update tutorial for ESLint flat config format (#31672)
Update React monorepo tutorial:
- Fix reference to .eslintrc.base.json (now eslint.config.mjs)

🤖 Generated with [Claude Code](https://claude.ai/code)

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

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

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

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

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

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

Fixes #30199

Co-authored-by: Claude <noreply@anthropic.com>
2025-06-20 16:21:35 -04:00
68 changed files with 1538 additions and 143 deletions

7
.cursor/mcp.json Normal file
View File

@ -0,0 +1,7 @@
{
"mcpServers": {
"nx-mcp": {
"url": "http://localhost:9470/sse"
}
}
}

2
.gitignore vendored
View File

@ -75,6 +75,7 @@ storybook-static
.claude/settings.local.json
.cursor/rules/nx-rules.mdc
.cursor/mcp.json
.github/instructions/nx.instructions.md
# Added by Claude Task Master
@ -105,3 +106,4 @@ tasks/
# Raw docs local configuration (machine-specific)
.rawdocs.local.json

View File

@ -17,6 +17,7 @@ youtubeUrl: https://youtu.be/RNilYmJJzdk
- [Save Time: Connecting Your Editor, CI and LLMs](/blog/nx-editor-ci-llm-integration)
- [Enhancing Nx Generators with AI: Predictability Meets Intelligence](/blog/nx-generators-ai-integration)
- [Your AI Assistant Can Now Read Your Terminal: Real-Time Development Error Fixing](/blog/nx-terminal-integration-ai)
- [Introducing Self-Healing CI for Nx and Nx Cloud](/blog/nx-self-healing-ci)
{% /callout %}

View File

@ -17,6 +17,7 @@ youtubeUrl: https://youtu.be/V2W94Sq_v6A
- [Save Time: Connecting Your Editor, CI and LLMs](/blog/nx-editor-ci-llm-integration)
- [Enhancing Nx Generators with AI: Predictability Meets Intelligence](/blog/nx-generators-ai-integration)
- [Your AI Assistant Can Now Read Your Terminal: Real-Time Development Error Fixing](/blog/nx-terminal-integration-ai)
- [Introducing Self-Healing CI for Nx and Nx Cloud](/blog/nx-self-healing-ci)
{% /callout %}

View File

@ -17,6 +17,7 @@ youtubeUrl: https://youtu.be/dRQq_B1HSLA
- [Save Time: Connecting Your Editor, CI and LLMs](/blog/nx-editor-ci-llm-integration)
- [Enhancing Nx Generators with AI: Predictability Meets Intelligence](/blog/nx-generators-ai-integration)
- [Your AI Assistant Can Now Read Your Terminal: Real-Time Development Error Fixing](/blog/nx-terminal-integration-ai)
- [Introducing Self-Healing CI for Nx and Nx Cloud](/blog/nx-self-healing-ci)
{% /callout %}

View File

@ -16,6 +16,7 @@ description: 'Explore how Nx monorepos amplify AI benefits by providing complete
- [Save Time: Connecting Your Editor, CI and LLMs](/blog/nx-editor-ci-llm-integration)
- [Enhancing Nx Generators with AI: Predictability Meets Intelligence](/blog/nx-generators-ai-integration)
- [Your AI Assistant Can Now Read Your Terminal: Real-Time Development Error Fixing](/blog/nx-terminal-integration-ai)
- [Introducing Self-Healing CI for Nx and Nx Cloud](/blog/nx-self-healing-ci)
{% /callout %}

View File

@ -17,6 +17,7 @@ youtubeUrl: https://youtu.be/fPqPh4h8RJg
- **Save Time: Connecting Your Editor, CI and LLMs**
- [Enhancing Nx Generators with AI: Predictability Meets Intelligence](/blog/nx-generators-ai-integration)
- [Your AI Assistant Can Now Read Your Terminal: Real-Time Development Error Fixing](/blog/nx-terminal-integration-ai)
- [Introducing Self-Healing CI for Nx and Nx Cloud](/blog/nx-self-healing-ci)
{% /callout %}

View File

@ -17,6 +17,7 @@ youtubeUrl: https://youtu.be/PXNjedYhZDs
- [Save Time: Connecting Your Editor, CI and LLMs](/blog/nx-editor-ci-llm-integration)
- **Enhancing Nx Generators with AI: Predictability Meets Intelligence**
- [Your AI Assistant Can Now Read Your Terminal: Real-Time Development Error Fixing](/blog/nx-terminal-integration-ai)
- [Introducing Self-Healing CI for Nx and Nx Cloud](/blog/nx-self-healing-ci)
{% /callout %}

View File

@ -5,7 +5,7 @@ authors: ['Victor Savkin']
tags: ['ai', 'development', 'productivity', 'best-practices']
cover_image: /blog/images/articles/bg-practical-ai-guide-part-1.avif
description: 'Learn how to effectively use AI coding assistants beyond simple prompts. Discover proven workflows, best practices, and strategies that transform AI from a novelty into a powerful development multiplier.'
pinned: true
pinned: false
---
> _"Tools amplify your talent. The better your skills, the better the tools serve you."_ — Andrew Hunt, The Pragmatic Programmer

View File

@ -6,7 +6,7 @@ tags: ['nx', 'nx-console', 'ai', 'terminal']
cover_image: /blog/images/articles/bg-nx-tui-llm-integration.avif
description: 'Learn how Nx Console now enables AI assistants to read your terminal output in real-time, automatically detecting and fixing development errors as they happen.'
youtubeUrl: https://youtu.be/Cbc9_W5J6DA
pinned: true
pinned: false
---
{% callout type="deepdive" title="Series: Making your LLM smarter" expanded=true %}
@ -18,6 +18,7 @@ pinned: true
- [Save Time: Connecting Your Editor, CI and LLMs](/blog/nx-editor-ci-llm-integration)
- [Enhancing Nx Generators with AI: Predictability Meets Intelligence](/blog/nx-generators-ai-integration)
- **Your AI Assistant Can Now Read Your Terminal: Real-Time Development Error Fixing**
- [Introducing Self-Healing CI for Nx and Nx Cloud](/blog/nx-self-healing-ci)
{% /callout %}

View File

@ -18,7 +18,7 @@ The CREEP vulnerability allows any contributor with pull request privileges to i
- Nx Cloud is **NOT** affected due to its security architecture
- Review this post to determine if your self-hosted cache solution is vulnerable
{% callout type="warn" title="DIY implementations are vulnerable" %}
{% callout type="warning" title="DIY implementations are vulnerable" %}
DIY remote caches are likely vulnerable. Scanners won't catch all affected implementations, so understanding the vulnerability is crucial.
{% /callout %}

View File

@ -0,0 +1,193 @@
---
title: 'Introducing Self-Healing CI for Nx and Nx Cloud'
slug: nx-self-healing-ci
authors: ['Juri Strumpflohner']
tags: ['nx', 'nx-cloud', 'ai', 'ci']
cover_image: /blog/images/articles/thumb-self-healing-ci.avif
description: 'Introducing Nx Cloud Self-Healing CI: AI agents that automatically detect, analyze, and fix your CI failures so you do not have to babysit PRs.'
youtubeUrl: https://youtu.be/JW5Ki3PkRWA
pinned: true
---
{% callout type="deepdive" title="Series: Making your LLM smarter" %}
- [Nx Just Made Your LLM Way Smarter](/blog/nx-just-made-your-llm-smarter)
- [Making Cursor Smarter with an MCP Server For Nx Monorepos](/blog/nx-made-cursor-smarter)
- [Nx MCP Now Available for VS Code Copilot](/blog/nx-mcp-vscode-copilot)
- [Nx and AI: Why They Work so Well Together](/blog/nx-and-ai-why-they-work-together)
- [Save Time: Connecting Your Editor, CI and LLMs](/blog/nx-editor-ci-llm-integration)
- [Enhancing Nx Generators with AI: Predictability Meets Intelligence](/blog/nx-generators-ai-integration)
- [Your AI Assistant Can Now Read Your Terminal: Real-Time Development Error Fixing](/blog/nx-terminal-integration-ai)
- **Introducing Self-Healing CI for Nx and Nx Cloud**
{% /callout %}
At Nx, we've always focused on making CI faster in two ways: **speeding up your actual builds** with techniques like remote caching and distributed execution, and **accelerating your feedback cycles** by eliminating the delays that waste developer time. The end-goal: **optimizing time to green**.
But what if we could bring this to the next level? **What if your CI could fix itself?** You push a PR with an error, an AI agent automatically identifies the problem, implements the fix, validates it works, and pushes the solution back to your PR?
**Nx Cloud Self-Healing CI** makes this a reality.
{% toc /%}
## Here's what happens now
Picture this: It's 2 PM on a Tuesday. You've been deep in feature development for the past hour when you get a notification in VS Code:
![Notification in your editor about an AI fix](/blog/images/articles/notification-self-healing-ci.avif)
You click the notification, review the one-line import addition, and approve it. Thirty seconds later:
![Nx Console view of the automated fix that has been applied to your PR](/blog/images/articles/nx-console-self-healing-fix-applied.avif)
> ✅ **Fix applied! Your PR is now passing CI**
You never left your editor. Never analyzed error logs. Never manually debugged the issue. Just a quick review and approval, then back to your feature work.
This is Self-Healing CI in action.
## The problem: "babysitting" your PRs
Every developer knows this workflow:
1. **Push your code** and continue working on something else
2. **CI fails** with a simple error (missing import, linting issue, test assertion)
3. **You don't notice for 30+ minutes** because you're focused on other work
4. **Context switch** back to analyze the error and implement a fix
5. **Push the fix** and wait another 5-10 minutes for CI to complete
6. **Repeat** if there are more issues
This "babysitting" wastes countless hours across development teams. The critical waste happens in **step 3**: the delay between failure and awareness.
One big issue is already being handled: **[flaky tasks](/ci/features/flaky-tasks)**. When tests fail intermittently (same code, different results), the system retries them on different agents with zero human intervention.
**The missing piece:** What about **genuine failures**? Real bugs, configuration errors, and dependency issues that need actual code fixes. These can't be solved with retries—they need intelligent analysis and solutions.
**Enter Self-Healing CI.** It tackles failures that need real fixes, providing AI-powered analysis, fix generation, and validation. You stay in control with quick review and approval, while the AI handles the heavy lifting. Combined with flaky task detection, you now have a comprehensive system that handles every type of CI failure—so you can stay focused on building features instead of babysitting PRs.
## How Self-Healing CI works
Here's what happens when you push a PR with Self-Healing CI enabled:
![Self-Healing CI Workflow](/blog/images/articles/self-healing-flow.avif)
1. **You push your PR** - Nothing changes in your workflow
2. **Failure detected** - If tasks fail, instead of just reporting the failure, Nx Cloud starts an AI agent
3. **AI agent analyzes** - The agent examines the error logs, understands your codebase structure through Nx's project graph, and identifies the root cause
4. **Fix proposed** - The agent creates a fix and presents it to you via Nx Console or the integrated GitHub application (e.g. a comment on your GitHub PR)
5. **Validation runs in parallel** - Meanwhile, the agent validates the fix by re-running the originally failed tasks with the proposed changes
6. **Human review and approval** - You can approve the fix immediately if it looks good, or wait for validation to complete for extra confidence
7. **Automatic PR update** - Once you approve, the fix gets committed to your PR as a new commit by the AI agent
8. **Full CI re-run** - Your complete CI pipeline runs again with the applied fix
**You stay in control while the AI does the heavy lifting.** The AI acts like a peer programmer, handling the time-consuming work of analyzing failures, creating fixes, and validating solutions in the background while you continue working on other tasks. You remain in the loop throughout the process. The AI doesn't make changes autonomously, but rather proposes working fixes for your review and approval before they're applied to your PR.
The AI agent is successful at providing meaningful fixes because it **combines the context from Nx and Nx Cloud:**
- it has the complete failure context with the exact tasks that ran, including error logs
- thanks to the Nx graph, it has vast context about the codebase, including project structure, dependencies, configuration and runnable tasks
- combining the two, it can validate a fix by re-running the original CI checks
## Getting started with Self-Healing CI
To enable Self-Healing CI on your workspace:
### 1. Connect Nx Cloud
If you haven't already connected to Nx Cloud:
```shell
npx nx connect
```
You can [start with the free Hobby plan](/pricing) and play around with the new AI features.
Once connected, enable AI features in your [Nx Cloud dashboard](https://nx.app):
1. **Organization Settings**: Enable AI features (required for all AI-powered functionality)
2. **Workspace Settings**: Find the "Self-Healing CI" section and toggle it on (available for all Nx Cloud plans)
![Nx Cloud workspace setting to enable Self-Healing CI](/blog/images/articles/self-healing-ci-setting.avif)
### 2. Configure Your CI Pipeline
If you're using Nx Agents, Self-Healing CI is automatically enabled. Here's an example GitHub Actions configuration with Nx Agents:
```yaml
name: CI
...
jobs:
main:
runs-on: ubuntu-latest
steps:
...
- run: npx nx-cloud start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="build"
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- uses: nrwl/nx-set-shas@v4
- run: npx nx affected -t lint test build
```
If you're not using Nx Agents yet, you can still enable Self-Healing CI by adding the `fix-ci` step to your pipeline. **Important**: This step must run at the end with `if: always()` to ensure it executes even when previous steps fail (which is exactly when you need the fix):
```yaml
name: CI
...
jobs:
main:
runs-on: ubuntu-latest
steps:
...
- run: npm ci
- uses: nrwl/nx-set-shas@v4
- run: npx nx affected -t lint test build
- run: npx nx cloud fix-ci
if: always()
```
### 3. Install Nx Console
To receive notifications about self-healing activities directly in your editor and enable the full AI integration experience, you need [Nx Console](/getting-started/editor-setup) installed for VS Code, Cursor, or IntelliJ.
For the complete AI setup guide, see our [AI integration documentation](/getting-started/ai-integration).
## Wrapping up
Self-Healing CI completes Nx Cloud's comprehensive approach to eliminating CI friction. Combined with our existing flaky task detection and automatic retries, we now have a unified system that handles every type of CI failure automatically: flaky tests get retried transparently, genuine bugs get fixed intelligently. No more "babysitting" your PRs. The system handles the tedious work so you can stay focused on building features.
**Key takeaways:**
- **Eliminates wasted time**: No more 30-minute delays between CI failure and awareness, no more manual debugging of simple errors
- **Leverages Nx's deep context**: AI agents understand your workspace structure, project relationships, and build configurations through Nx's project graph
- **You stay in control**: Proposed fixes are presented for your review and approval—the AI doesn't make autonomous changes
- **Built on proven infrastructure**: Uses the same robust Nx Cloud infrastructure that powers distributed task execution
- **Part of a broader vision**: Continues our mission to optimize "time to green" and eliminate developer workflow friction
**Ready to try it?** Self-Healing CI is rolling out as an early access feature and is available to everyone right now—no special approval or signup required. If you don't have an Nx Cloud account yet, you can quickly [start with the Hobby plan](/pricing), connect your workspace with `npx nx connect`, and get going immediately.
**For enterprise teams:** If you're already using Nx Cloud and want to learn more about how AI features like Self-Healing CI can enhance your existing setup, [reach out to us](/contact). We'd love to help you leverage these capabilities in your organization.
---
Learn more:
- 🧠 [Nx AI Docs](/features/enhance-AI)
- 🌩️ [Nx Cloud](/nx-cloud)
- 👩‍💻 [Nx GitHub](https://github.com/nrwl/nx)
- 👩‍💻 [Nx Console GitHub](https://github.com/nrwl/nx-console)
- 💬 [Nx Official Discord Server](https://go.nx.dev/community)
- 📹 [Nx Youtube Channel](https://www.youtube.com/@nxdevtools)

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 KiB

View File

@ -47,7 +47,8 @@
"simpleName": {
"description": "Don't include the directory in the name of the module or standalone component entry of the library.",
"type": "boolean",
"default": false
"default": false,
"x-deprecated": "Use the --name option to provide the exact name instead. This option will be removed in Nx 22."
},
"addModuleSpec": {
"description": "Add a module spec file.",

View File

@ -130,7 +130,8 @@
"simpleName": {
"description": "Don't include the directory in the generated file name.",
"type": "boolean",
"default": false
"default": false,
"x-deprecated": "Use the --name option to provide the exact name instead. This option will be removed in Nx 22."
},
"useProjectJson": {
"type": "boolean",

View File

@ -132,7 +132,8 @@
"simpleName": {
"description": "Don't include the directory in the name of the module of the library.",
"type": "boolean",
"default": false
"default": false,
"x-deprecated": "Use the --name option to provide the exact name instead. This option will be removed in Nx 22."
},
"useProjectJson": {
"type": "boolean",

View File

@ -183,7 +183,8 @@
"simpleName": {
"description": "Don't include the directory in the name of the module of the library.",
"type": "boolean",
"default": false
"default": false,
"x-deprecated": "Use the --name option to provide the exact name instead. This option will be removed in Nx 22."
},
"useProjectJson": {
"type": "boolean",

View File

@ -974,7 +974,7 @@ Next, let's come up with a set of rules based on these tags:
- `scope:orders` should be able to import from `scope:orders`, `scope:shared` and `scope:products`
- `scope:products` should be able to import from `scope:products` and `scope:shared`
To enforce the rules, Nx ships with a custom ESLint rule. Open the `.eslintrc.base.json` at the root of the workspace and add the following `depConstraints` in the `@nx/enforce-module-boundaries` rule configuration:
To enforce the rules, Nx ships with a custom ESLint rule. Open the `eslint.config.mjs` at the root of the workspace and add the following `depConstraints` in the `@nx/enforce-module-boundaries` rule configuration:
```js {% fileName="eslint.config.mjs" %}
import nx from '@nx/eslint-plugin';

View File

@ -90,18 +90,23 @@ ${content}`
addImports(viteParentLib);
const pm = getSelectedPackageManager();
if (pm === 'pnpm') {
// for pnpm we need to add the local packages as dependencies to each consumer package.json
// Add local packages as dependencies to each consumer package.json
// This is required for all package managers to satisfy dependency checks
const addDeps = (parentLib: string, includeRollupChildLib = false) => {
updateJson(`packages/${parentLib}/package.json`, (json) => {
json.dependencies ??= {};
json.dependencies[`@proj/${esbuildChildLib}`] = 'workspace:*';
json.dependencies[`@proj/${esbuildChildLib}`] =
pm === 'pnpm' ? 'workspace:*' : '*';
if (includeRollupChildLib) {
json.dependencies[`@proj/${rollupChildLib}`] = 'workspace:*';
json.dependencies[`@proj/${rollupChildLib}`] =
pm === 'pnpm' ? 'workspace:*' : '*';
}
json.dependencies[`@proj/${swcChildLib}`] = 'workspace:*';
json.dependencies[`@proj/${tscChildLib}`] = 'workspace:*';
json.dependencies[`@proj/${viteChildLib}`] = 'workspace:*';
json.dependencies[`@proj/${swcChildLib}`] =
pm === 'pnpm' ? 'workspace:*' : '*';
json.dependencies[`@proj/${tscChildLib}`] =
pm === 'pnpm' ? 'workspace:*' : '*';
json.dependencies[`@proj/${viteChildLib}`] =
pm === 'pnpm' ? 'workspace:*' : '*';
return json;
});
};
@ -112,6 +117,7 @@ ${content}`
addDeps(tscParentLib);
addDeps(viteParentLib);
if (pm === 'pnpm') {
const pmc = getPackageManagerCommand({ packageManager: pm });
runCommand(pmc.install);
}

View File

@ -1,76 +0,0 @@
import type { Metadata } from 'next';
import { DefaultLayout } from '@nx/nx-dev/ui-common';
import { Hero } from '@nx/nx-dev/ui-ai-landing-page';
import { ProblemStatement } from '@nx/nx-dev/ui-ai-landing-page';
import { Features } from '@nx/nx-dev/ui-ai-landing-page';
import { CallToAction } from '@nx/nx-dev/ui-ai-landing-page';
import { TechnicalImplementation } from '@nx/nx-dev/ui-ai-landing-page';
export const metadata: Metadata = {
title: 'Nx - Make AI work in large codebases',
description:
'Empower your AI assistants with workspace intelligence to understand your codebase structure, project dependencies, and build processes at a glance.',
alternates: {
canonical: 'https://nx.dev/ai',
},
openGraph: {
title: 'Nx - Make AI work in large codebases',
description:
'Empower your AI assistants with workspace intelligence to understand your codebase structure, project dependencies, and build processes at a glance.',
url: 'https://nx.dev/ai',
siteName: 'Nx',
images: [
{
url: 'https://nx.dev/images/nx-ai-landing-og.png',
width: 1200,
height: 630,
},
],
locale: 'en_US',
type: 'website',
},
keywords: [
'nx',
'ai',
'workspace',
'architecture',
'codebase',
'llm',
'AI workspace development',
'LLM code assistant',
'Nx AI integration',
'multi-project AI tools',
'enterprise AI development',
'intelligent code generation',
'MCP server',
'workspace AI tools',
'monorepo AI',
'architectural intelligence',
'code assistant',
'workspace intelligence',
],
};
export default function AiLandingPage() {
return (
<DefaultLayout>
<Hero />
<div className="mt-32 lg:mt-56" id="problem-statement">
<ProblemStatement />
</div>
<div className="mt-32 lg:mt-56" id="features">
<Features />
</div>
<div className="mt-32 lg:mt-56" id="how-it-works">
<TechnicalImplementation />
</div>
<div className="mt-32 lg:mt-56">
<CallToAction />
</div>
</DefaultLayout>
);
}

View File

@ -0,0 +1,60 @@
import type { Metadata } from 'next';
import { DefaultLayout } from '@nx/nx-dev/ui-common';
import {
AiHero,
CallToAction,
WhileCoding,
WhileRunningCi,
WhileScalingYourOrganization,
} from '@nx/nx-dev/ui-ai-landing-page';
import type { ReactElement } from 'react';
import { NextSeo } from 'next-seo';
export function Ai(): ReactElement {
return (
<>
<NextSeo
title="From your editor to CI, Nx makes your AI a lot more powerful."
description="Empower your AI assistants with workspace intelligence to understand your codebase structure, project dependencies, and build processes at a glance."
openGraph={{
url: 'https://nx.dev/ai',
title:
'From your editor to CI, Nx makes your AI a lot more powerful.',
description:
'Empower your AI assistants with workspace intelligence to understand your codebase structure, project dependencies, and build processes at a glance.',
images: [
{
url: 'https://nx.dev/socials/nx-media.png',
width: 800,
height: 421,
alt: 'Nx: Smart Repos · Fast Builds',
type: 'image/jpeg',
},
],
siteName: 'Nx',
type: 'website',
}}
canonical="https://nx.dev/ai"
/>
<DefaultLayout>
<AiHero />
<div className="mt-12 lg:mt-24">
<WhileCoding />
</div>
<div className="mt-32 lg:mt-56">
<WhileRunningCi />
</div>
<div className="mt-32 lg:mt-56">
<WhileScalingYourOrganization />
</div>
<div className="mt-32 lg:mt-56">
<CallToAction />
</div>
</DefaultLayout>
</>
);
}
export default Ai;

View File

@ -0,0 +1,43 @@
import { useRouter } from 'next/router';
import { NextSeo } from 'next-seo';
import { Footer, Header } from '@nx/nx-dev/ui-common';
import { NxLabsContact } from '@nx/nx-dev/ui-contact';
import { type ReactElement } from 'react';
export function ContactNxLabs(): ReactElement {
const router = useRouter();
return (
<>
<NextSeo
title="Contact Nx Labs"
description="Accelerate Your Nx Adoption with Expert Guidance"
openGraph={{
url: 'https://nx.dev' + router.asPath,
title: 'Contact Nx Labs',
description: 'Accelerate Your Nx Adoption with Expert Guidance',
images: [
{
url: 'https://nx.dev/socials/nx-media.png',
width: 800,
height: 421,
alt: 'Nx: Smart Repos · Fast Builds',
type: 'image/jpeg',
},
],
siteName: 'Nx',
type: 'website',
}}
/>
<Header />
<main id="main" role="main" className="py-24 lg:py-32">
<div>
<NxLabsContact />
</div>
</main>
<Footer />
</>
);
}
export default ContactNxLabs;

View File

@ -1,11 +1,6 @@
import { useRouter } from 'next/router';
import { NextSeo } from 'next-seo';
import {
ButtonLinkProps,
DefaultLayout,
Footer,
Header,
} from '@nx/nx-dev/ui-common';
import { ButtonLinkProps, DefaultLayout } from '@nx/nx-dev/ui-common';
import {
BuiltForEnterprise,
CachePoisoningProtection,

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View File

@ -1,4 +1,8 @@
export * from './lib/hero';
export * from './lib/new/ai-hero';
export * from './lib/new/while-coding';
export * from './lib/new/while-running-ci';
export * from './lib/new/while-scaling-your-organization';
export * from './lib/problem-statement';
export * from './lib/features';
export * from './lib/technical-implementation';

View File

@ -56,7 +56,7 @@ export function CallToAction(): ReactElement {
</div>
{/* Content */}
<div className="mx-auto max-w-2xl text-center">
<div className="mx-auto max-w-3xl text-center">
<h2 className="text-3xl font-medium tracking-tight text-slate-950 sm:text-5xl dark:text-white">
Transform your AI assistant in minutes
</h2>
@ -75,14 +75,6 @@ export function CallToAction(): ReactElement {
</span>
</Link>
{/* <Link
href="https://youtu.be/RNilYmJJzdk"
target="_blank"
rel="noopener noreferrer"
className="text-sm font-semibold leading-6 text-slate-900 dark:text-white"
>
Watch 3-min Demo <span aria-hidden="true"></span>
</Link> */}
</div>
</div>
</section>

View File

@ -0,0 +1,69 @@
import type { ReactElement } from 'react';
import { ButtonLink, SectionHeading } from '@nx/nx-dev/ui-common';
import {
CodeBracketIcon,
ServerStackIcon,
UserGroupIcon,
} from '@heroicons/react/24/outline';
import { NxPowerAi } from './nx-power-ai';
export function AiHero(): ReactElement {
return (
<section>
<div className="mx-auto flex max-w-7xl">
<div className="max-w-4xl px-6 pb-24 pt-12 lg:mx-0 lg:shrink-0 lg:px-8">
<SectionHeading
id="get-speed-and-scale"
as="h1"
variant="display"
className="text-pretty tracking-tight"
>
From editor to CI, <br /> Nx makes your AI <br />
<span className="rounded-lg bg-gradient-to-r from-pink-500 to-fuchsia-500 bg-clip-text text-transparent">
a lot more powerful.
</span>
</SectionHeading>
<div className="mt-6">
<ButtonLink
href="/getting-started/ai-integration"
title="Nx AI Integration"
variant="primary"
size="small"
>
Integrate Nx with your Coding Assistant
</ButtonLink>
</div>
<div className="mt-8 flex flex-wrap items-center gap-4">
<a
href="#while-coding"
className="inline-flex items-center gap-1.5 rounded-full bg-slate-100 px-2 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-200 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-slate-800 dark:text-slate-300 dark:hover:bg-slate-700"
aria-label="Jump to While Coding section"
>
<CodeBracketIcon aria-hidden="true" className="size-4 shrink-0" />
<span>Coding</span>
</a>
<a
href="#while-running-ci"
className="inline-flex items-center gap-1.5 rounded-full bg-slate-100 px-2 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-200 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-slate-800 dark:text-slate-300 dark:hover:bg-slate-700"
aria-label="Jump to While Running CI section"
>
<ServerStackIcon aria-hidden="true" className="size-4 shrink-0" />
<span>Running CI</span>
</a>
<a
href="#while-scaling-your-organization"
className="inline-flex items-center gap-1.5 rounded-full bg-slate-100 px-2 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-200 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-slate-800 dark:text-slate-300 dark:hover:bg-slate-700"
aria-label="Jump to Scaling Your Organization section"
>
<UserGroupIcon aria-hidden="true" className="size-4 shrink-0" />
<span>Scaling Your Organization</span>
</a>
</div>
</div>
<div className="hidden w-auto grow grid-cols-1 place-items-center lg:grid">
<NxPowerAi className="-mt-8" />
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,84 @@
import { ReactElement, ReactNode } from 'react';
import Image from 'next/image';
import { PlayButton } from './play-button';
type VideoFeature = {
type: 'video';
videoUrl: string;
onPlayClick?: (videoUrl: string) => void;
};
type LinkFeature = {
type?: 'link';
videoUrl?: never;
onPlayClick?: never;
};
interface BaseFeatureCardProps {
isAvailable: boolean;
id: string;
title: string;
subtitle: string;
description: ReactNode;
imageUrl: string;
}
export type FeatureCardProps = BaseFeatureCardProps &
(VideoFeature | LinkFeature);
export function FeatureCard({
isAvailable,
id,
title,
subtitle,
description,
type = 'link',
imageUrl,
videoUrl,
onPlayClick,
}: FeatureCardProps): ReactElement {
const handlePlayClick = () => {
if (type === 'video' && videoUrl && onPlayClick) {
onPlayClick(videoUrl);
} else if (type === 'video' && !videoUrl) {
console.warn(
`Video type specified for ${title} but no videoUrl provided`
);
}
};
return (
<div key={id} className="flex flex-col">
<dt className="text-base/7 font-semibold">
<div className="relative mb-6 aspect-video max-h-52 w-full overflow-hidden rounded-xl bg-slate-200 dark:bg-slate-700/60">
<Image
src={imageUrl}
alt={`Thumbnail for ${title}`}
width={1280}
height={720}
loading="lazy"
unoptimized
/>
{type === 'video' && videoUrl && onPlayClick ? (
<div className="absolute inset-0 grid h-full w-full items-center justify-center">
<PlayButton onClick={handlePlayClick} />
</div>
) : null}
{!isAvailable && (
<span className="absolute bottom-2 right-2 inline-flex items-center rounded-md bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-blue-400/30">
Coming soon
</span>
)}
</div>
{title}
<div className="text-xs/7 font-normal italic opacity-80">
{subtitle}
</div>
</dt>
<dd className="mt-1 flex flex-auto flex-col text-base/7">
{description}
</dd>
</div>
);
}

View File

@ -0,0 +1,182 @@
import type { FC, ReactElement, SVGProps } from 'react';
import {
ClaudeIcon,
GoogleGeminiIcon,
MetaIcon,
OpenAiIcon,
} from '@nx/nx-dev/ui-icons';
import { cx } from '@nx/nx-dev/ui-primitives';
const HexagonShape: FC<SVGProps<SVGSVGElement>> = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 22 24"
{...props}
>
<path d="M8.6 1.386C9.474.88 9.911.628 10.376.53a3 3 0 0 1 1.248 0c.464.098.902.35 1.776.856l5.592 3.228c.875.505 1.312.758 1.63 1.11.281.313.494.681.623 1.081.147.452.147.957.147 1.966v6.458c0 1.01 0 1.514-.146 1.966a3 3 0 0 1-.624 1.08c-.318.353-.755.606-1.63 1.11l-5.592 3.23c-.874.504-1.312.757-1.776.855a2.997 2.997 0 0 1-1.248 0c-.465-.098-.902-.35-1.776-.856l-5.592-3.228c-.875-.505-1.312-.758-1.63-1.11a3 3 0 0 1-.623-1.081c-.147-.452-.147-.957-.147-1.966V8.77c0-1.01 0-1.514.147-1.966a3 3 0 0 1 .623-1.08c.318-.353.755-.606 1.63-1.11L8.6 1.384Z" />
</svg>
);
export function NxPowerAi({
className = '',
}: {
className?: string;
}): ReactElement {
return (
<div className={cx('relative mx-auto w-full max-w-md p-4', className)}>
<div className="pointer-events-none">
<ul className="grid grid-cols-5 gap-x-2">
{/*Row 1*/}
<li className="invisible aspect-square" />
<li className="relative isolate flex aspect-square h-20 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-slate-50/90 drop-shadow-sm dark:text-white/5"
/>
</li>
<li className="relative isolate flex aspect-square h-20 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-white/70 drop-shadow-lg dark:text-white/15"
/>
<OpenAiIcon
aria-hidden="true"
className="z-10 size-8 text-black dark:text-white"
/>
</li>
<li className="relative isolate flex aspect-square h-20 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-slate-50/90 drop-shadow-sm dark:text-white/5"
/>
</li>
<li className="invisible aspect-square" />
{/*Row 2*/}
<li className="flex aspect-square translate-x-1/2 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-slate-50/90 drop-shadow-sm dark:text-white/5"
/>
</li>
<li className="relative isolate flex aspect-square h-20 translate-x-1/2 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-white/70 drop-shadow-lg dark:text-white/15"
/>
<ClaudeIcon
aria-hidden="true"
className="z-10 size-8 text-[#D97757]"
/>
</li>
<li className="relative isolate flex aspect-square h-20 translate-x-1/2 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-white/70 drop-shadow-lg dark:text-white/15"
/>
<GoogleGeminiIcon
aria-hidden="true"
className="z-10 size-8 text-[#8E75B2]"
/>
</li>
<li className="flex aspect-square translate-x-1/2 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-slate-50/90 drop-shadow-sm dark:text-white/5"
/>
</li>
<li className="invisible"></li>
{/*Row 3*/}
<li className="invisible" />
<li className="relative isolate flex aspect-square h-20 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-slate-50/90 drop-shadow-sm dark:text-white/5"
/>
</li>
<li className="relative isolate flex aspect-square h-20 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-white/70 drop-shadow-lg dark:text-white/15"
/>
<MetaIcon
aria-hidden="true"
className="z-10 size-8 text-[#0467DF]"
/>
</li>
<li className="relative isolate flex aspect-square h-20 items-center justify-center">
<HexagonShape
aria-hidden="true"
className="absolute inset-0 z-0 text-slate-50/90 drop-shadow-sm dark:text-white/5"
/>
</li>
<li className="invisible" />
</ul>
</div>
{/* Steps List */}
<div className="relative mt-12 space-y-2">
{/* Active Step */}
<div className="rounded-xl border border-slate-200 bg-slate-50 p-4 shadow-sm backdrop-blur-sm dark:border-slate-800/40 dark:bg-slate-800/60">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="flex size-5 items-center justify-center rounded-full border border-white/25 bg-gradient-to-br from-slate-200 to-slate-300 dark:border-slate-900 dark:from-slate-700 dark:to-slate-800">
<div className="size-2 rounded-full bg-white dark:bg-slate-900" />
</div>
<div className="flex-1">
<p className="font-mono text-xs leading-tight text-slate-900 dark:text-slate-100">
Generating a fix for test "ui-profile:test:save-preferences"
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<span className="text-xs text-slate-600 dark:text-slate-400">
Working...
</span>
<span className="relative flex size-1.5">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-orange-400 opacity-75" />
<span className="relative inline-flex size-1.5 rounded-full bg-orange-500" />
</span>
</div>
</div>
</div>
{/* Completed Step */}
<div className="rounded-xl border border-slate-200 bg-slate-50 p-4 opacity-50 shadow-sm backdrop-blur-sm dark:border-slate-800/40 dark:bg-slate-800/60">
<div className="flex items-center justify-between gap-4">
<div className="flex grow items-center space-x-3">
<div className="flex size-5 items-center justify-center rounded-full border border-white/25 bg-gradient-to-br from-slate-200 to-slate-300 dark:border-slate-900 dark:from-slate-700 dark:to-slate-800">
<div className="size-2 rounded-full bg-white dark:bg-slate-900" />
</div>
<div className="w-full grow space-y-1">
<div className="h-1 w-8 rounded-sm bg-slate-800/15 dark:bg-slate-200/35" />
<div className="h-1 w-1/2 rounded-sm bg-slate-500/10 dark:bg-slate-400/35" />
</div>
</div>
<div className="flex items-center space-x-2">
<span className="text-xs text-slate-600 dark:text-slate-300">
Done
</span>
<div className="size-1.5 rounded-full bg-green-500 shadow-sm" />
</div>
</div>
</div>
{/* Pending Step */}
<div className="rounded-xl border border-slate-200 bg-slate-50 p-4 opacity-40 shadow-sm backdrop-blur-sm dark:border-slate-800/40 dark:bg-slate-800/60">
<div className="flex items-center space-x-3">
<div className="flex size-5 items-center justify-center rounded-full border border-white/25 bg-gradient-to-br from-slate-200 to-slate-300 dark:border-slate-900 dark:from-slate-700 dark:to-slate-800">
<div className="size-2 rounded-full bg-white dark:bg-slate-900" />
</div>
<div className="flex-1">
<div className="h-1 w-16 rounded-sm bg-slate-400/20" />
</div>
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,70 @@
import { ComponentProps, ReactElement } from 'react';
import { cx } from '@nx/nx-dev/ui-primitives';
import { MovingBorder } from '@nx/nx-dev/ui-animations';
import { motion } from 'framer-motion';
import { PlayIcon } from '@heroicons/react/24/outline';
export function PlayButton({
className,
...props
}: ComponentProps<'div'>): ReactElement {
const parent = {
initial: {
width: 82,
transition: {
when: 'afterChildren',
},
},
hover: {
width: 296,
transition: {
duration: 0.125,
type: 'tween',
ease: 'easeOut',
},
},
};
const child = {
initial: {
opacity: 0,
x: -6,
},
hover: {
x: 0,
opacity: 1,
transition: {
duration: 0.015,
type: 'tween',
ease: 'easeOut',
},
},
};
return (
<div
className={cx(
'group relative overflow-hidden rounded-full bg-transparent p-[1px] shadow-md',
className
)}
{...props}
>
<div className="absolute inset-0">
<MovingBorder duration={5000} rx="5%" ry="5%">
<div className="size-20 bg-[radial-gradient(var(--blue-500)_40%,transparent_60%)] opacity-[0.8] dark:bg-[radial-gradient(var(--pink-500)_40%,transparent_60%)]" />
</MovingBorder>
</div>
<motion.div
initial="initial"
whileHover="hover"
variants={parent}
className="relative isolate flex size-20 cursor-pointer items-center justify-center gap-6 rounded-full border-2 border-slate-100 bg-white/10 p-6 text-white antialiased backdrop-blur-xl"
>
<PlayIcon aria-hidden="true" className="absolute left-6 top-6 size-8" />
<motion.div variants={child} className="absolute left-20 top-4 w-48">
<p className="text-base font-medium">Watch the video</p>
<p className="text-xs">Make your AI work.</p>
</motion.div>
</motion.div>
</div>
);
}

View File

@ -0,0 +1,146 @@
import { ReactElement, useState } from 'react';
import { SectionHeading, VideoModal } from '@nx/nx-dev/ui-common';
import Link from 'next/link';
import { FeatureCard, type FeatureCardProps } from './feature-card';
const features: FeatureCardProps[] = [
{
isAvailable: true,
id: 'deep-project-understanding',
title: 'Deep Project Understanding Through MCP',
subtitle: '"Context is worth 80 IQ points" - Alan Kay',
description: (
<>
<p className="flex-auto">
Nx connects your AI assistant to its comprehensive workspace knowledge
through the Model Context Protocol (MCP), delivering complete insights
into project graphs, dependencies, and code ownership.
</p>
<div className="mt-4">
<Link
href="/blog/nx-mcp-vscode-copilot"
title="How to setup Nx MCP to your LLM"
className="text-sm/6 font-semibold"
>
How to setup Nx MCP to your LLM <span aria-hidden="true"></span>
</Link>
</div>
</>
),
type: 'video',
videoUrl: 'https://youtu.be/RNilYmJJzdk',
imageUrl: '/images/ai/nx-copilot-mcp-yt-thumb.avif',
},
{
isAvailable: true,
id: 'terminal-awareness-in-real-time',
title: 'Terminal Awareness in Real-Time',
subtitle: 'Your AI sees what you see, when you see it.',
description: (
<>
<p className="flex-auto">
Your AI assistant can access the terminal output on-demand through
MCP. When you ask about failing builds or broken tests, it retrieves
the relevant error messages and combines them with full codebase
context.
</p>
<div className="mt-4">
<Link
href="/blog/nx-terminal-integration-ai"
title="How to set AI terminal integration"
className="text-sm/6 font-semibold"
>
How to set terminal AI integration with Nx{' '}
<span aria-hidden="true"></span>
</Link>
</div>
</>
),
type: 'video',
videoUrl: 'https://youtu.be/Cbc9_W5J6DA',
imageUrl: '/images/ai/terminal-llm-comm-thumb.avif',
},
{
isAvailable: true,
id: 'predictable-workspace-aware-code-generation',
title: 'Predictable, Workspace-Aware Code Generation',
subtitle:
'Combine AI intelligence with consistent generators that follow team standards',
description: (
<>
<p className="flex-auto">
Your AI assistant can trigger code generation using predictable Nx
generators, then take it from there to intelligently integrate the
result into your existing workspace architecture.
</p>
<div className="mt-4">
<Link
href="/blog/nx-generators-ai-integration"
title="How to generate code that works with AI"
className="text-sm/6 font-semibold"
>
How to generate code that works with AI{' '}
<span aria-hidden="true"></span>
</Link>
</div>
</>
),
type: 'video',
videoUrl: 'https://youtu.be/PXNjedYhZDs',
imageUrl: '/images/ai/video-code-gen-and-ai-thumb.avif',
},
];
export function WhileCoding(): ReactElement {
const [openVideoUrl, setOpenVideoUrl] = useState<string | null>(null);
return (
<div
id="while-coding"
className="mx-auto max-w-7xl scroll-mt-32 px-6 lg:px-8"
>
<div className="max-w-2xl">
<div className="h-8 w-36 border-t-2 border-blue-500 dark:border-sky-500" />
<SectionHeading as="h2" variant="title" id="while-coding-title">
While Coding
</SectionHeading>
</div>
<div className="mt-16 sm:mt-20 lg:mt-24">
<dl className="grid grid-cols-1 gap-x-8 gap-y-16 sm:grid-cols-2 lg:grid-cols-3">
{features.map((feature) =>
feature.type === 'video' ? (
<FeatureCard
key={feature.id}
isAvailable={feature.isAvailable}
id={feature.id}
title={feature.title}
subtitle={feature.subtitle}
description={feature.description}
type={feature.type}
imageUrl={feature.imageUrl}
videoUrl={feature.videoUrl}
onPlayClick={setOpenVideoUrl}
/>
) : (
<FeatureCard
key={feature.id}
isAvailable={feature.isAvailable}
id={feature.id}
title={feature.title}
subtitle={feature.subtitle}
description={feature.description}
type={feature.type}
imageUrl={feature.imageUrl}
/>
)
)}
</dl>
</div>
<VideoModal
isOpen={openVideoUrl !== null}
onClose={() => setOpenVideoUrl(null)}
videoUrl={openVideoUrl || ''}
/>
</div>
);
}

View File

@ -0,0 +1,144 @@
import { ReactElement, useState } from 'react';
import { SectionHeading, VideoModal } from '@nx/nx-dev/ui-common';
import Link from 'next/link';
import { FeatureCard, type FeatureCardProps } from './feature-card';
const features: FeatureCardProps[] = [
{
isAvailable: true,
id: 'reliable-tests',
title: 'Reliable Tests',
subtitle: 'Turn flaky tests from time-wasters into non-issues.',
description: (
<>
<p className="flex-auto">
Nx automatically identifies and fixes flaky tests using AI that
analyzes failures with workspace context and generates actual code
fixes.
</p>
<div className="mt-4">
<Link
href="/ci/features/flaky-tasks"
title="How to identify and rerun flaky tasks"
className="text-sm/6 font-semibold"
>
How to identify and rerun flaky tasks{' '}
<span aria-hidden="true"></span>
</Link>
</div>
</>
),
type: 'link',
imageUrl: '/images/ai/nx-flaky-tasks-detection-thumb.avif',
},
{
isAvailable: true,
id: 'self-healing-ci',
title: 'Self-Healing CI',
subtitle: 'Stop babysitting PRs. AI fixes your CI failures automatically.',
description: (
<>
<p className="flex-auto">
AI agents detect, analyze, and propose fixes for CI failures using
your workspace context. Stay focused on features while AI handles the
debugging.
</p>
<div className="mt-4">
<Link
href="/blog/nx-self-healing-ci"
title="Stop babysitting PRs with Self-Healing CI"
className="text-sm/6 font-semibold"
>
Stop babysitting PRs with Self-Healing CI{' '}
<span aria-hidden="true"></span>
</Link>
</div>
</>
),
type: 'video',
videoUrl: 'https://youtu.be/JW5Ki3PkRWA',
imageUrl: '/images/ai/self-healing-ci-thumb.avif',
},
{
isAvailable: true,
id: 'autonomous-ci-optimization',
title: 'Autonomous CI Optimization',
subtitle: 'Let AI manage your CI resources for optimal performance.',
description: (
<>
<p className="flex-auto">
AI-driven resource allocation that learns from your CI patterns to
automatically scale agents and optimize build times.
</p>
<div className="mt-4">
<Link
href="/ci/features/dynamic-agents"
title="How to setup dynamic agents"
className="text-sm/6 font-semibold"
>
How to setup Autonomous CI Optimization{' '}
<span aria-hidden="true"></span>
</Link>
</div>
</>
),
type: 'link',
imageUrl: '/images/ai/autonomous-ci-optimization-thumb.avif',
},
];
export function WhileRunningCi(): ReactElement {
const [openVideoUrl, setOpenVideoUrl] = useState<string | null>(null);
return (
<div className="relative bg-slate-50 py-32 dark:bg-slate-800">
<div
id="while-running-ci"
className="mx-auto max-w-7xl scroll-mt-32 px-6 lg:px-8"
>
<div className="max-w-2xl">
<div className="h-8 w-36 border-t-2 border-emerald-500" />
<SectionHeading as="h2" variant="title" id="while-running-ci-title">
While Running CI
</SectionHeading>
</div>
<div className="mt-16 sm:mt-20 lg:mt-24">
<dl className="grid grid-cols-1 gap-x-8 gap-y-16 sm:grid-cols-2 lg:grid-cols-3">
{features.map((feature) =>
feature.type === 'video' ? (
<FeatureCard
key={feature.id}
isAvailable={feature.isAvailable}
id={feature.id}
title={feature.title}
subtitle={feature.subtitle}
description={feature.description}
type={feature.type}
imageUrl={feature.imageUrl}
videoUrl={feature.videoUrl}
onPlayClick={setOpenVideoUrl}
/>
) : (
<FeatureCard
key={feature.id}
isAvailable={feature.isAvailable}
id={feature.id}
title={feature.title}
subtitle={feature.subtitle}
description={feature.description}
type={feature.type}
imageUrl={feature.imageUrl}
/>
)
)}
</dl>
</div>
<VideoModal
isOpen={openVideoUrl !== null}
onClose={() => setOpenVideoUrl(null)}
videoUrl={openVideoUrl || ''}
/>
</div>
</div>
);
}

View File

@ -0,0 +1,113 @@
import { ReactElement, useState } from 'react';
import { SectionHeading, VideoModal } from '@nx/nx-dev/ui-common';
import { FeatureCard, type FeatureCardProps } from './feature-card';
const features: FeatureCardProps[] = [
{
isAvailable: false,
id: 'architectural-queries',
title: 'Architectural Queries',
subtitle: 'Ask questions about your entire system in plain English.',
description: (
<p className="flex-auto">
Query your workspace data naturally: "Extract all failed CI runs from
the last month" or "What patterns are causing our tests to fail?" AI
analyzes your CI pipeline data, cache performance, and build patterns to
identify bottlenecks and optimization opportunities.
</p>
),
type: 'link',
imageUrl: '/images/ai/ci-querying-thumb.avif',
},
{
isAvailable: false,
id: 'cross-repository-intelligence',
title: 'Cross-Repository Intelligence',
subtitle: 'AI that understands your entire organization.',
description: (
<p className="flex-auto">
Nx Polygraph will extend AI context across multiple repositories,
enabling system-wide refactoring and cross-repo analysis.
</p>
),
type: 'link',
imageUrl: '/images/ai/cross-repository-intelligence-thumb.avif',
},
{
isAvailable: false,
id: 'cross-repository-agentic-refactorings',
title: 'Cross-Repository Agentic Refactorings',
subtitle: 'Modernize your entire organization with AI agents.',
description: (
<>
<p className="flex-auto">
Use autonomous agents to perform large-scale migrations and tech debt
cleanup across all repositories. They understand cross-repo
dependencies to execute complex, org-wide changes safely.
</p>
</>
),
type: 'link',
imageUrl: '/images/ai/cross-repository-agentic-refactorings-thumb.avif',
},
];
export function WhileScalingYourOrganization(): ReactElement {
const [openVideoUrl, setOpenVideoUrl] = useState<string | null>(null);
return (
<div className="relative">
<div
id="while-scaling-your-organization"
className="mx-auto max-w-7xl scroll-mt-32 px-6 lg:px-8"
>
<div className="max-w-2xl">
<div className="h-8 w-36 border-t-2 border-fuchsia-500" />
<SectionHeading
as="h2"
variant="title"
id="while-scaling-your-organization-title"
>
While Scaling Your Organization
</SectionHeading>
</div>
<div className="mt-16 sm:mt-20 lg:mt-24">
<dl className="grid grid-cols-1 gap-x-8 gap-y-16 sm:grid-cols-2 lg:grid-cols-3">
{features.map((feature) =>
feature.type === 'video' ? (
<FeatureCard
key={feature.id}
isAvailable={feature.isAvailable}
id={feature.id}
title={feature.title}
subtitle={feature.subtitle}
description={feature.description}
type={feature.type}
imageUrl={feature.imageUrl}
videoUrl={feature.videoUrl}
onPlayClick={setOpenVideoUrl}
/>
) : (
<FeatureCard
key={feature.id}
isAvailable={feature.isAvailable}
id={feature.id}
title={feature.title}
subtitle={feature.subtitle}
description={feature.description}
type={feature.type}
imageUrl={feature.imageUrl}
/>
)
)}
</dl>
</div>
<VideoModal
isOpen={openVideoUrl !== null}
onClose={() => setOpenVideoUrl(null)}
videoUrl={openVideoUrl || ''}
/>
</div>
</div>
);
}

View File

@ -27,6 +27,7 @@ import {
import { ButtonLink, ButtonLinkProps } from '../button';
import {
enterpriseItems,
professionalServicesItems,
resourceMenuItems,
solutionsItems,
} from './menu-items';
@ -162,6 +163,7 @@ export function Header({ ctaButtons }: HeaderProps): ReactElement {
sections={{
'By roles': solutionsItems,
'For enterprises': enterpriseItems,
'Professional services': professionalServicesItems,
}}
/>
</Popover.Panel>
@ -210,6 +212,14 @@ export function Header({ ctaButtons }: HeaderProps): ReactElement {
)}
</Popover>
<div className="hidden h-6 w-px bg-slate-200 md:block dark:bg-slate-700" />
<Link
href="/ai"
title="AI"
className="hidden gap-2 px-3 py-2 font-medium leading-tight hover:text-blue-500 md:inline-flex dark:text-slate-200 dark:hover:text-sky-500"
prefetch={false}
>
AI
</Link>
<Link
href="/remote-cache"
title="Nx Remote Cache"
@ -253,9 +263,11 @@ export function Header({ ctaButtons }: HeaderProps): ReactElement {
{/*SECONDARY NAVIGATION*/}
<div className="flex-shrink-0 text-sm">
<nav className="flex items-center justify-center space-x-1">
<div className="hidden xl:block">
{buttonsToRender.map((buttonProps, index) => (
<ButtonLink key={index} {...buttonProps} />
))}
</div>
<a
title="Nx is open source, check the code on GitHub"
href="https://github.com/nrwl/nx"
@ -424,6 +436,9 @@ export function Header({ ctaButtons }: HeaderProps): ReactElement {
{[
...Object.values(solutionsItems).flat(),
...Object.values(enterpriseItems).flat(),
...Object.values(
professionalServicesItems
).flat(),
].map((item) => (
<MobileMenuItem
key={item.name}
@ -473,6 +488,22 @@ export function Header({ ctaButtons }: HeaderProps): ReactElement {
</>
)}
</Disclosure>
<Link
href="/ai"
title="AI"
className="flex w-full gap-2 py-4 font-medium leading-tight hover:text-blue-500 dark:text-slate-200 dark:hover:text-sky-500"
prefetch={false}
>
AI
</Link>
<Link
href="/remote-cache"
title="Nx Remote Cache"
className="flex w-full gap-2 py-4 font-medium leading-tight hover:text-blue-500 dark:text-slate-200 dark:hover:text-sky-500"
prefetch={false}
>
Remote Cache
</Link>
<Link
href="/nx-cloud"
title="Nx Cloud"
@ -489,14 +520,6 @@ export function Header({ ctaButtons }: HeaderProps): ReactElement {
>
Pricing
</Link>
<Link
href="/remote-cache"
title="Nx Remote Cache"
className="flex w-full gap-2 py-4 font-medium leading-tight hover:text-blue-500 dark:text-slate-200 dark:hover:text-sky-500"
prefetch={false}
>
Remote Cache
</Link>
<Disclosure as="div">
{({ open }) => (
<>

View File

@ -22,6 +22,7 @@ import {
ServerStackIcon,
ArrowTrendingUpIcon,
CommandLineIcon,
UsersIcon,
} from '@heroicons/react/24/outline';
import { FC, SVGProps } from 'react';
import { DiscordIcon } from '../discord-icon';
@ -320,6 +321,17 @@ export const enterpriseItems: MenuItem[] = [
isHighlight: false,
},
];
export const professionalServicesItems: MenuItem[] = [
{
name: 'Nx Labs',
description:
'From expert training to hands-on engineering support, we meet teams where they are and help them move forward with confidence.',
href: '/contact/labs',
icon: UsersIcon,
isNew: false,
isHighlight: false,
},
];
export const resourceMenuItems = {
Learn: learnItems,

View File

@ -2,3 +2,4 @@ export * from './lib/contact-links';
export * from './lib/how-can-we-help';
export * from './lib/talk-to-our-team';
export * from './lib/talk-to-our-engineering-team';
export * from './lib/nx-labs';

View File

@ -0,0 +1,128 @@
import {
SectionHeading,
HubspotForm,
Strong,
SectionDescription,
} from '@nx/nx-dev/ui-common';
import { type ReactElement } from 'react';
import {
BillIcon,
CapitalOneIcon,
CaterpillarIcon,
ManIcon,
RedwoodJsIcon,
RoyalBankOfCanadaIcon,
ShopifyIcon,
SiriusxmAlternateIcon,
StorybookIcon,
VmwareIcon,
ZipariIcon,
} from '@nx/nx-dev/ui-icons';
export function NxLabsContact(): ReactElement {
return (
<section id="contact-sales">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto max-w-3xl text-center">
<SectionHeading as="h1" variant="display" id="how-can-we-help">
Accelerate Your Nx Adoption with Expert Guidance
</SectionHeading>
</div>
<div className="mx-auto mt-16 flex max-w-5xl flex-col gap-12 md:flex-row lg:gap-8">
<section className="mt-4 flex-1">
<SectionHeading
as="p"
variant="subtitle"
className="mx-auto mt-6 max-w-3xl lg:pr-20"
>
Nx Labs: Service built for outcomes
</SectionHeading>
<SectionDescription as="p" className="mt-6">
Whether you're just getting started with Nx or looking to unlock
its full potential, our{' '}
<Strong>
team of experts delivers tailored solutions that actually work
</Strong>
. From initial setup to advanced optimization, we offer workspace
assessments, hands-on team training, embedded engineering support,
and on-demand expertiseall designed to make your team
self-sufficient.
</SectionDescription>
<SectionDescription as="p" className="mt-4">
Unlike traditional consultants, we believe in planned
obsolescencenothing makes us prouder than seeing more Nx experts
out in the wild.
</SectionDescription>
<SectionDescription as="p" className="mt-4">
Ready to see what a truly optimized workspace looks like? Reach
out and let us know what you need.
</SectionDescription>
<figure className="mt-12 border-l border-slate-200 pl-8 dark:border-slate-800">
<blockquote className="text-base/7">
<p>
Nxs professional services team was instrumental in helping
us modernize our development workflow. The team is extremely
personable and knowledgeable. It is a pleasure to work with
them to get the most out of our investment.
</p>
</blockquote>
<figcaption className="mt-6 flex items-center gap-x-4 text-sm/6">
<img
alt="Wayne Kaskie"
src="https://avatars.githubusercontent.com/u/14349014?v=4"
className="size-8 flex-none rounded-full"
/>
<div>
<div className="font-semibold">Wayne Kaskie</div>
<div className="text-slate-500">
Front End Architect, Zipari
</div>
</div>
<ZipariIcon
aria-hidden="true"
className="ml-auto size-10 text-[#E31E39]"
/>
</figcaption>
</figure>
<div className="mx-auto mt-12 grid w-full grid-cols-4 gap-2 md:grid-cols-4 lg:mt-12">
<div className="col-span-1 flex h-14 items-center justify-center lg:h-28">
<RoyalBankOfCanadaIcon
aria-hidden="true"
className="size-12 text-black dark:text-white"
/>
</div>
<div className="col-span-1 flex h-14 items-center justify-center lg:h-28">
<VmwareIcon
aria-hidden="true"
className="size-28 text-black dark:text-white"
/>
</div>
<div className="col-span-1 flex h-14 items-center justify-center lg:h-28">
<BillIcon
aria-hidden="true"
className="size-14 text-black dark:text-white"
/>
</div>
<div className="col-span-1 flex h-14 items-center justify-center lg:h-28">
<CapitalOneIcon
aria-hidden="true"
className="size-28 text-black dark:text-white"
/>
</div>
</div>
</section>
<section className="flex-1 self-start rounded-xl border border-slate-200 bg-white p-8 dark:border-slate-800/40">
<HubspotForm
region="na1"
portalId="2757427"
formId="d5710d48-85de-4b17-ab97-4c22c25a8f02"
noScript={true}
loading={<div>Loading...</div>}
/>
</section>
</div>
</div>
</section>
);
}

View File

@ -1,8 +1,11 @@
// AI
export * from './lib/ai/claude';
export * from './lib/ai/cursor';
export * from './lib/ai/github-copilot';
export * from './lib/ai/google-gemini';
export * from './lib/ai/intellij-ai';
export * from './lib/ai/model-context-protocol';
export * from './lib/ai/open-ai';
// CI PROVIDERS
export * from './lib/ci-providers/azure-devops';
@ -112,6 +115,7 @@ export * from './lib/products';
// SOCIALS
export * from './lib/socials/discord-icon';
export * from './lib/socials/meta';
export * from './lib/socials/x-icon';
export * from './lib/socials/youtube';

View File

@ -0,0 +1,17 @@
import { FC, SVGProps } from 'react';
/**
* Use `#D97757` for a colored version.
*/
export const ClaudeIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
{...props}
>
<title>Claude</title>
<path d="m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z" />
</svg>
);

View File

@ -0,0 +1,17 @@
import { FC, SVGProps } from 'react';
/**
* Use `#8E75B2` for a colored version.
*/
export const GoogleGeminiIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
{...props}
>
<title>Google Gemini</title>
<path d="M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81" />
</svg>
);

View File

@ -0,0 +1,17 @@
import { FC, SVGProps } from 'react';
/**
* Use `#412991` for a colored version.
*/
export const OpenAiIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
{...props}
>
<title>OpenAI</title>
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
</svg>
);

View File

@ -1,5 +1,8 @@
import { FC, SVGProps } from 'react';
/**
* Use `#E31E39` for a colored version.
*/
export const ZipariIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@ -0,0 +1,17 @@
import { FC, SVGProps } from 'react';
/**
* Use `#0467DF` for a colored version.
*/
export const MetaIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
{...props}
>
<title>Meta</title>
<path d="M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z" />
</svg>
);

View File

@ -3,6 +3,7 @@ import {
formatFiles,
GeneratorCallback,
installPackagesTask,
logger,
runTasksInSerial,
Tree,
} from '@nx/devkit';
@ -47,6 +48,13 @@ export async function libraryGenerator(
);
}
if (schema.simpleName !== undefined && schema.simpleName !== false) {
// TODO(v22): Remove simpleName as user should be using name.
logger.warn(
`The "--simpleName" option is deprecated and will be removed in Nx 22. Please use the "--name" option to provide the exact name you want for the library.`
);
}
if (schema.addTailwind && !schema.buildable && !schema.publishable) {
throw new Error(
`To use "--addTailwind" option, you have to set either "--buildable" or "--publishable".`

View File

@ -47,7 +47,8 @@
"simpleName": {
"description": "Don't include the directory in the name of the module or standalone component entry of the library.",
"type": "boolean",
"default": false
"default": false,
"x-deprecated": "Use the --name option to provide the exact name instead. This option will be removed in Nx 22."
},
"addModuleSpec": {
"description": "Add a module spec file.",

View File

@ -13,5 +13,7 @@ pluginManagement {
gradlePluginPortal()
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version("0.9.0")
}
rootProject.name = "batch-runner"

View File

@ -12,5 +12,7 @@ pluginManagement {
gradlePluginPortal()
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version("0.9.0")
}
rootProject.name = "project-graph"

View File

@ -7,6 +7,7 @@ import {
GeneratorCallback,
installPackagesTask,
joinPathFragments,
logger,
names,
offsetFromRoot,
ProjectConfiguration,
@ -102,6 +103,13 @@ export async function libraryGeneratorInternal(
);
const options = await normalizeOptions(tree, schema);
if (schema.simpleName !== undefined && schema.simpleName !== false) {
// TODO(v22): Remove simpleName as user should be using name.
logger.warn(
`The "--simpleName" option is deprecated and will be removed in Nx 22. Please use the "--name" option to provide the exact name you want for the library.`
);
}
createFiles(tree, options);
await configureProject(tree, options);

View File

@ -130,7 +130,8 @@
"simpleName": {
"description": "Don't include the directory in the generated file name.",
"type": "boolean",
"default": false
"default": false,
"x-deprecated": "Use the --name option to provide the exact name instead. This option will be removed in Nx 22."
},
"useProjectJson": {
"type": "boolean",

View File

@ -66,8 +66,12 @@ describe('AssetInputOutputHandler', () => {
let projectDir: string;
let outputDir: string;
let callback: jest.SpyInstance;
let originalCwd: string;
beforeEach(() => {
// Store original cwd to restore later
originalCwd = process.cwd();
// Resolve to real paths to avoid symlink discrepancies with watcher.
const tmp = fs.realpathSync(path.join(os.tmpdir()));
@ -102,6 +106,11 @@ describe('AssetInputOutputHandler', () => {
});
});
afterEach(() => {
// Restore original cwd
process.chdir(originalCwd);
});
test('watchAndProcessOnAssetChange', async () => {
const dispose = await sut.watchAndProcessOnAssetChange();
@ -219,6 +228,61 @@ describe('AssetInputOutputHandler', () => {
},
]);
});
test('should copy assets to correct location when running from nested directory', async () => {
// Create a nested directory structure to simulate running from a subdirectory
const nestedDir = path.join(rootDir, 'e2e', 'integration-tests');
fs.mkdirSync(nestedDir, { recursive: true });
// Change to nested directory to simulate running nx command from there
process.chdir(nestedDir);
// Create test files
fs.writeFileSync(path.join(rootDir, 'LICENSE'), 'license');
fs.writeFileSync(path.join(projectDir, 'README.md'), 'readme');
fs.writeFileSync(path.join(projectDir, 'docs/test1.md'), 'test');
// Create CopyAssetsHandler with relative outputDir (this is where the bug manifests)
const nestedSut = new CopyAssetsHandler({
rootDir,
projectDir,
outputDir: 'dist/mylib', // relative path - this triggers the bug
callback: callback as any,
assets: [
'mylib/*.md',
{
input: 'mylib/docs',
glob: '**/*.md',
output: 'docs',
},
'LICENSE',
],
});
await nestedSut.processAllAssetsOnce();
expect(callback).toHaveBeenCalledWith([
{
type: 'create',
src: path.join(rootDir, 'LICENSE'),
dest: path.join(rootDir, 'dist/mylib/LICENSE'),
},
]);
expect(callback).toHaveBeenCalledWith([
{
type: 'create',
src: path.join(rootDir, 'mylib/README.md'),
dest: path.join(rootDir, 'dist/mylib/README.md'),
},
]);
expect(callback).toHaveBeenCalledWith([
{
type: 'create',
src: path.join(rootDir, 'mylib/docs/test1.md'),
dest: path.join(rootDir, 'dist/mylib/docs/test1.md'),
},
]);
});
});
function wait(ms: number) {

View File

@ -89,16 +89,21 @@ export class CopyAssetsHandler {
let input: string;
let output: string;
let ignore: string[] | null = null;
const resolvedOutputDir = path.isAbsolute(opts.outputDir)
? opts.outputDir
: path.resolve(opts.rootDir, opts.outputDir);
if (typeof f === 'string') {
pattern = f;
input = path.relative(opts.rootDir, opts.projectDir);
output = path.relative(opts.rootDir, opts.outputDir);
output = path.relative(opts.rootDir, resolvedOutputDir);
} else {
isGlob = true;
pattern = pathPosix.join(f.input, f.glob);
input = f.input;
output = pathPosix.join(
path.relative(opts.rootDir, opts.outputDir),
path.relative(opts.rootDir, resolvedOutputDir),
f.output
);
if (f.ignore)

View File

@ -2,6 +2,7 @@ import type { GeneratorCallback, Tree } from '@nx/devkit';
import {
formatFiles,
joinPathFragments,
logger,
readJson,
runTasksInSerial,
writeJson,
@ -37,6 +38,14 @@ export async function libraryGeneratorInternal(
rawOptions: LibraryGeneratorOptions
): Promise<GeneratorCallback> {
const options = await normalizeOptions(tree, rawOptions);
if (rawOptions.simpleName !== undefined && rawOptions.simpleName !== false) {
// TODO(v22): Remove simpleName as user should be using name.
logger.warn(
`The "--simpleName" option is deprecated and will be removed in Nx 22. Please use the "--name" option to provide the exact name you want for the library.`
);
}
const jsLibraryTask = await jsLibraryGenerator(
tree,
toJsLibraryGeneratorOptions(options)

View File

@ -132,7 +132,8 @@
"simpleName": {
"description": "Don't include the directory in the name of the module of the library.",
"type": "boolean",
"default": false
"default": false,
"x-deprecated": "Use the --name option to provide the exact name instead. This option will be removed in Nx 22."
},
"useProjectJson": {
"type": "boolean",

View File

@ -5,6 +5,7 @@ import {
GeneratorCallback,
installPackagesTask,
joinPathFragments,
logger,
readNxJson,
readProjectConfiguration,
runTasksInSerial,
@ -73,6 +74,14 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) {
`For publishable libs you have to provide a proper "--importPath" which needs to be a valid npm package name (e.g. my-awesome-lib or @myorg/my-lib)`
);
}
if (schema.simpleName !== undefined && schema.simpleName !== false) {
// TODO(v22): Remove simpleName as user should be using name.
logger.warn(
`The "--simpleName" option is deprecated and will be removed in Nx 22. Please use the "--name" option to provide the exact name you want for the library.`
);
}
if (!options.component) {
options.style = 'none';
}

View File

@ -189,7 +189,8 @@
"simpleName": {
"description": "Don't include the directory in the name of the module of the library.",
"type": "boolean",
"default": false
"default": false,
"x-deprecated": "Use the --name option to provide the exact name instead. This option will be removed in Nx 22."
},
"useProjectJson": {
"type": "boolean",

View File

@ -4,8 +4,6 @@
* The settings file is used to specify which projects to include in your build.
* For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.5/userguide/building_swift_projects.html in the Gradle documentation.
*/
pluginManagement {
repositories {
mavenLocal()
@ -13,7 +11,9 @@ pluginManagement {
gradlePluginPortal()
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version("0.9.0")
}
rootProject.name = "nx"
includeBuild("./packages/gradle/project-graph")
includeBuild("./packages/gradle/batch-runner")