Compare commits

...

51 Commits

Author SHA1 Message Date
Jason Jean 43a34d0b95 fix(core): prevent command injection in getNpmPackageVersion (#34309)
## Current Behavior

The `getNpmPackageVersion` function in
`packages/workspace/src/generators/utils/get-npm-package-version.ts`
uses `execSync` with direct string interpolation of the `packageName`
parameter. When a user runs `create-nx-workspace` with a custom
`--preset` value that doesn't match a known preset, the value flows
unsanitized into a shell command:

```js
execSync(`npm view ${packageName}... version --json`)
```

This allows arbitrary command execution via shell metacharacters (e.g.,
`--preset='pkg$(malicious command)'`).

## Expected Behavior

User-supplied package names are validated against a strict npm package
name regex before being passed to any shell command. The function now
uses `execFileSync` with an args array instead of `execSync` with string
interpolation, providing defense in depth:

1. **Input validation** — rejects anything that isn't a valid npm
package name
2. **Safe execution** — arguments are passed as an array so Node.js
handles escaping, rather than concatenating into a raw shell string

(cherry picked from commit 79d878f240)
2026-02-03 16:37:59 -05:00
Caleb Ukle 8add883499 docs(nx-cloud): add screenshots for cache troubleshoot guide (#34296)
(cherry picked from commit b198606bef)
2026-02-03 16:37:58 -05:00
Craigory Coppola 79319472b6 fix(core): nx should show help for run-one when using project short names (#34303)
## Current Behavior
Given a project name like `:foo`, you can run tasks like `nx test foo`
(note `foo` vs `:foo`), but passing --help throws an error

## Expected Behavior
`--help` works the same with the shortname vs full name

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

Fixes #

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
(cherry picked from commit 1cb6c0b14c)
2026-02-03 16:37:56 -05:00
Jason Jean 0848679fd9 fix(maven): include pom.xml and ancestor pom files as inputs for all targets (#34291)
## Current Behavior

- `pom.xml` is only included as an input when a mojo uses default inputs
- Mojos with specific input configurations (like
`maven-compiler-plugin:compile`) don't get `pom.xml` in their inputs
- Parent `pom.xml` files aren't tracked as inputs

This can lead to stale cache hits when:
1. `pom.xml` changes but a mojo has specific input config
2. A parent `pom.xml` changes (affecting inherited properties,
dependency versions, plugin config)

## Expected Behavior

- Every target should include its own `pom.xml` as an input
- Every target should include ancestor `pom.xml` files (within the
workspace) as inputs
- Cache should invalidate when any relevant `pom.xml` changes

## Related Issue(s)

N/A - discovered during code review

## Changes

- **CacheConfig.kt**: Removed `pom.xml` from `defaultInputs` (now always
added explicitly)
- **MojoAnalyzer.kt**: Added `workspaceRoot` parameter and logic to walk
up the parent chain, adding all in-workspace ancestor `pom.xml` files as
inputs
- **NxProjectAnalyzerMojo.kt**: Pass `workspaceRoot` to `MojoAnalyzer`

(cherry picked from commit 3f6bdc7ff7)
2026-02-03 15:47:10 -05:00
Jason Jean 2a93513d9a chore(repo): update nx to 22.5.0-beta.3 (#34295)
Updating Nx from 22.5.0-beta.2 to 22.5.0-beta.3

(cherry picked from commit cc4ec68bce)
2026-02-03 15:46:27 -05:00
Juri d0f7a77f8f docs(nx-dev): add autonomous AI workflows blog post
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 01d2f64b90)
2026-02-03 12:05:03 -05:00
James Henry 5ebd5acca8 chore(repo): update to pnpm@10.28.2 and clean up pnpm config (#34298)
(cherry picked from commit 39d8a9a6ac)
2026-02-03 12:04:50 -05:00
Jack Hsu 06629a0df1 docs(misc): ignore /docs/og/*.png.md paths (#34289)
This PR excludes `/docs/og/*` paths from assets tracking. This is likely
added by some crawler and is additional compute/noise that we don't care
about.

<img width="1354" height="86" alt="image"
src="https://github.com/user-attachments/assets/6bfda816-0d26-46b3-b432-ae96e5976c37"
/>

(cherry picked from commit 4f02c6b56e)
2026-02-03 12:03:39 -05:00
Jack Hsu 9c6c9a1a0c fix(nx-dev): fix double-counting and exclude assets from page tracking (#34286)
## Current Behavior

1. **track-asset-requests** runs twice per request due to redundant path
patterns:
   ```typescript
   path: ["/*.txt", "/**/*.txt", "/*.md", "/**/*.md"]
   ```
The `/**/*` pattern already matches root level files, so `/*` is
redundant.

2. **track-page-requests** runs on many asset requests even though it
only tracks HTML page views:
   - Font files: `/docs/fonts/*.woff2`, `/docs/*.woff`
   - Images: `/docs/*.svg`, `/docs/*.png`, `/docs/og/*`
   - Pagefind search index: `/docs/pagefind/*`

## Expected Behavior

1. Asset tracking should fire only once per request
2. Page tracking should exclude all non-HTML assets at Netlify level
(zero compute)

## Changes

### track-asset-requests.ts
Simplified path patterns:
```typescript
path: ["/**/*.txt", "/**/*.md"]
```

### track-page-requests.ts
Added comprehensive exclusions:

| Category | Exclusions |
|----------|------------|
| Images | `.svg`, `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.ico`,
`/images/*`, `/og/*` |
| Fonts | `/fonts/*`, `.woff`, `.woff2` |
| Search | `/pagefind/*` |

## Related Issue(s)

Fixes DOC-395

(cherry picked from commit 3f77cd5927)
2026-02-03 12:03:38 -05:00
Jason Jean 4f504372d2 fix(core): resolve daemon client reconnect queue deadlock (#34284)
## Current Behavior

When the daemon dies while processing a request, the reconnect logic
adds the retry back to the promise-based queue. However, the original
request is still blocked in the queue waiting for a response that will
never come (the socket is dead). This creates a deadlock:

1. Original request (`fn1`) is blocked awaiting a promise that will
never resolve
2. Retry request (`fn2`) is queued but can't execute until `fn1`
completes
3. `fn1` can't complete because it's waiting on the dead socket

## Expected Behavior

When reconnecting after daemon death, the retry should resolve the
pending promise that the original queue entry is waiting on, allowing
the queue to proceed normally.

## Related Issue(s)

<!-- No specific issue, discovered during development -->

## Solution

Instead of re-queuing the retry through `sendToDaemonViaQueue` (which
adds to the end of the queue), we now call `sendMessageToDaemon`
directly. This resolves the pending promise that `fn1` is waiting on,
allowing it to complete naturally and the queue to proceed.

Also removed the now-unused `decrementQueueCounter` method from
`PromisedBasedQueue`.

(cherry picked from commit 89aa25e5d0)
2026-02-03 12:03:37 -05:00
Jack Hsu df9dc4290c feat(nx-dev): add server-side page view tracking for docs (#34283)
## Current Behavior

Only markdown and text file requests are tracked server-side via the
`track-asset-requests` edge function. HTML page views are not tracked on
the server, missing requests from AI tools and curl.

## Expected Behavior

Track all doc page views server-side with a new edge function that:
- Sends `server_page_view` events to GA4 with `content_type` param to
differentiate HTML/markdown/text
- Uses Netlify's `excludedPath` config for efficient path filtering
(zero compute for excluded paths)
- Skips non-HTML requests via Accept header check

### Changes

| File | Change |
|------|--------|
| `track-page-requests.ts` | **NEW** - Edge function for HTML page view
tracking on `/docs/*` |
| `track-asset-requests.ts` | Changed event name to `server_page_view`,
added `content_type` param |
| `add-link-headers.ts` | Refactored to use `excludedPath` config
instead of runtime path checks |
| `netlify.toml` | Added edge function declaration for
`track-page-requests` |

### GA Event Schema

```javascript
{
  name: 'server_page_view',
  params: {
    content_type: 'text/html' | 'text/markdown' | 'text/plain',
    file_extension: '.html' | '.md' | '.txt',
    is_ai_tool: 'true' | 'false',
    // ... other params
  }
}
```

## Other Notes

This PR also removes the edge function entries from `netlify.toml` since
it's auto detected from `astro-docs/netlify/edge-functions`. This makes
all the configuration in the actual `.ts` file, not duplicated in the
`netlify.toml` file.

## Related Issue(s)

Closes DOC-395

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit cdd735dc63)
2026-02-03 12:03:36 -05:00
Juri Strumpflohner 50c0038358 docs(core): update AI pages and include new info about configure-ai-agents command (#34257)
changes to:
-
https://deploy-preview-34257--nx-docs.netlify.app/docs/getting-started/ai-setup
-
https://deploy-preview-34257--nx-docs.netlify.app/docs/features/enhance-ai
-
https://deploy-preview-34257--nx-docs.netlify.app/docs/reference/nx-mcp

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
(cherry picked from commit f59c15a410)
2026-02-03 12:03:34 -05:00
Louie Weng 9066622acb fix(gradle): enforce that only one gradle task can be passed into gradle executor (#34269)
<!-- 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 -->

The Gradle executor accepts a taskName option that *should not* contain
multiple space-separated tasks. When multiple tasks are provided, the
batch runner misinterprets the space-separated string as containing
project names rather than treating it as a single task argument, leading
to execution errors and confusion.

This only occurs if the taskName is manually overridden and should not
occur when task names are generated by the project graph plugin.

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

The Gradle executor now validates that taskName contains only a single
task without spaces. If multiple tasks are passed, it throws a clear
error message: "Task '[taskName]' contains spaces. Only a single Gradle
task is allowed per executor invocation." This prevents the batch runner
from misinterpreting the task name and provides immediate feedback to
users about the correct usage.

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

Fixes #

(cherry picked from commit 94319c7531)
2026-02-03 12:03:30 -05:00
Louie Weng c687cc6600 fix(gradle): ensure that batch output is not overriden for atomized targets (#34268)
<!-- 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

When running Gradle tasks in batch mode with atomized targets, if the
same task appears multiple times in the output (which happens when tasks
are atomized and executed separately), the batch runner only captures
the output from the last execution. Previous executions' output gets
overwritten because the splitOutputPerTask function replaces the entire
output for each task name it encounters.

## Expected Behavior

All output from a task should be preserved, even when the task appears
multiple times in the batch output. When the same task name is
encountered multiple times, the outputs should be concatenated rather
than replaced, ensuring developers can see the complete execution
history for atomized targets.

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

Fixes #

(cherry picked from commit 35bc17e4fe)
2026-02-03 12:03:27 -05:00
Copilot 795463d93f docs(dotnet): fix build target dependsOn example (#34206)
## Plan for fixing .NET incremental builds documentation

- [x] Update the "Target dependencies" section to remove `restore` from
the `dependsOn` array
- [x] Add explanation about why `restore` cannot be run through Nx for
solutions using custom frameworks
- [x] Improve explanation clarity based on review feedback
- [x] Verify the "Target configuration" section is consistent with the
changes
- [x] Complete code review and address feedback
- [x] Run security checks (no issues found)
- [x] Address PR feedback: Use JSX `<Aside>` component with import
statement instead of Markdoc tag

## Summary

Successfully fixed the documentation issue in the .NET incremental
builds guide. The changes made:

1. **Removed `restore` from the build target's `dependsOn` array** - The
documentation now correctly shows `"dependsOn": ["^build"]` instead of
`"dependsOn": ["restore", "^build"]`, matching the actual implementation
in the plugin code.

2. **Added a clear explanation** - Included an aside box explaining why
`restore` is not in the `dependsOn` array: because Nx requires NuGet
package restoration to be completed before running any tasks, and
including it would create a circular dependency.

3. **Verified consistency** - Checked that the "Target configuration"
section already showed the correct configuration, ensuring all
documentation is now consistent.

4. **Used correct Starlight component** - Changed from Markdoc `{% aside
%}` tag to JSX `<Aside>` component with proper import statement per
Starlight documentation standards.

The changes align with the actual implementation in
`packages/dotnet/analyzer/Utilities/TargetBuilder.Build.cs` where the
build target's `dependsOn` is set to `[$"^{targetName}"]` (line 56).

<!-- START COPILOT ORIGINAL PROMPT -->

<details>

<summary>Original prompt</summary>

>
> ----
>
> *This section details on the original issue you should resolve*
>
> <issue_title>docs(dotnet): implied conflict in dependsOn of inferred
build task</issue_title>
> <issue_description>### Documentation issue
>
> <!-- (Update "[ ]" to "[x]" to check a box) -->
>
> - [ ] Reporting a typo
> - [ ] Reporting a documentation bug
> - [ ] Documentation improvement
> - [x] Documentation feedback
>
> <!--
> If your issue is not regarding the documentation, please choose an
issue type:
>   https://github.com/nrwl/nx/issues/new/choose
> -->
>
> ### Is there a specific documentation page you are reporting?
>
>
https://nx.dev/docs/technologies/dotnet/guides/incremental-builds#target-dependencies
>
> ### Additional context or description
>
> The code sample provides in this doc includes `"dependsOn":
["restore", "^build"]`, but the automatically inferred `build` target
from this plugin does not actually include the `restore` target in the
dependsOn array. I assume this is by design? The docs seem to confuse it
a bit.
> </issue_description>
>
> <agent_instructions>Remove the `restore` target from the dependsOn
block. Add a small explanation that we can't run restore through Nx
because Nx requires restore to have been completed prior to running
tasks if the solution uses a custom framework</agent_instructions>
>
> ## Comments on the Issue (you are @copilot in this section)
>
> <comments>
> </comments>
>

</details>

<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes nrwl/nx#34150

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AgentEnder <6933928+AgentEnder@users.noreply.github.com>
(cherry picked from commit d65dcfa806)
2026-02-03 12:03:21 -05:00
iceThief (민찬기) fea8528dc1 fix(core): handle multibyte UTF-8 characters in socket message consumption (#34151)
## Current Behavior

When socket data chunks split a multibyte UTF-8 character (e.g., CJK
characters like Korean, Chinese, Japanese) at an arbitrary byte
boundary, `Buffer.toString()` decodes incomplete byte sequences as
replacement characters (�), causing message corruption.

This can occur when:
- File paths contain non-ASCII characters
- Project names include multibyte characters
- Any JSON message contains international text

## Expected Behavior

Multibyte UTF-8 characters should be properly decoded even when split
across multiple socket data chunks. The fix uses Node.js `StringDecoder`
which buffers incomplete multibyte sequences until the remaining bytes
arrive.

## Related Issue(s)

Fixes socket message corruption for paths/names containing multibyte
characters.

(cherry picked from commit e35dcd2050)
2026-02-03 12:03:19 -05:00
Caleb Ukle a43b86316e fix(nx-dev): make headers and table options linkable (#34267)
- fix(nx-dev): always link headers regardless of mdoc or markdown
content source (generated vs static file)
- fix(nx-dev): make option/property columns in table linkable
- the table column header is matched on `options`, `option`,
`properties`, and property` (case insensitive)

https://github.com/user-attachments/assets/7250b9d5-1030-4ebc-9e21-0a05f295bbf5

Note bc mdoc and `renderMarkdown` go through 2 different rendering
pipelines, this logic must bc within the markdoc config and rehype
(markdown) processing logic. tried to shared logic where I could

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 251121530d)
2026-02-03 12:03:18 -05:00
Louie Weng f2142a9d64 chore(repo): enable batch mode (#34245)
<!-- 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
-->

Enable Gradle executor to run tasks in batch mode in CI.

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

Fixes #

(cherry picked from commit bd627f1096)
2026-02-03 12:03:17 -05:00
Victor Savkin 9073f40e1d docs(misc): update the docs to use more direct language (#34264)
<!-- 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 #
2026-01-30 17:34:54 -05:00
Jack Hsu 1c1388dbfd docs(misc): push toc to the right side to match the alignment of the left sidebar (#34266)
This PR aligns the TOC to the right side of the page so the spacing is
more balanced.

<img width="2672" height="1527" alt="image"
src="https://github.com/user-attachments/assets/ef00906a-3788-47fb-a71d-230b3d69c201"
/>

Similar to other docs like React:

<img width="2672" height="1527" alt="image"
src="https://github.com/user-attachments/assets/e9a1cd81-f716-4e09-b148-be80a93b7aee"
/>


---

## Other screen widths

1400px:

<img width="1424" height="1025" alt="Screenshot 2026-01-30 at 12 11
57 PM"
src="https://github.com/user-attachments/assets/d9fc5029-4942-4e32-b2f2-4c66ebcb21df"
/>

1000px (TOC hidden):


<img width="1145" height="1019" alt="Screenshot 2026-01-30 at 12 12
10 PM"
src="https://github.com/user-attachments/assets/ce1cd890-193a-42d7-bb4f-3259146845a8"
/>
2026-01-30 17:34:46 -05:00
Colum Ferry 20da4f23eb fix(testing): preload vitest/node to prevent race condition on Node 24 (#34261)
Preload vitest/node ESM module early in
buildViteTargets/buildVitestTargets
functions before parallel processing occurs. This prevents the
ERR_INTERNAL_ASSERTION error that occurs when multiple vitest.config
files
are processed in parallel on Node 24+.

Fixes #34028
Fixes #33091
2026-01-30 17:34:10 -05:00
Jason Jean 754cb1271c fix(testing): add timeout to runCommandUntil to prevent hanging tests (#34148)
## Current Behavior

The `runCommandUntil` e2e utility function waits indefinitely for the
expected output to appear. If the output never appears (e.g., server
fails to start, different output format, port conflict), the test hangs
forever, causing CI jobs to run for hours before being killed.

## Expected Behavior

The function should timeout after a configurable duration and fail with
a clear error message showing what output was received.

## Related Issue(s)

Fixes hanging e2e tests observed in CI (e.g.,
`e2e-node:e2e-ci--src/node-server.test.ts` hung for 1h 21m).

## Changes

- Added optional `timeout` parameter to `runCommandUntil` opts (default:
5 seconds)
- On timeout: kills the process, logs the collected output, and rejects
with a clear error
- Existing call sites work unchanged; tests needing more startup time
can pass `{ timeout: 30000 }`

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: FrozenPandaz <8104246+FrozenPandaz@users.noreply.github.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
2026-01-30 17:34:05 -05:00
Jack Hsu 02b1c49497 feat(core): add decorative banners for Nx Cloud CNW completion message (#34270)
## Current Behavior

After completing the CNW (Create Nx Workspace) flow with Nx Cloud, users
see a plain text completion message with a link to finish setup.

## Expected Behavior

Users now see one of four completion message variants controlled by
`NX_CNW_FLOW_VARIANT`:
- **Variant 0**: Plain link (control) - always used for enterprise URLs
- **Variant 1**: "Try the full Nx platform" decorative ASCII banner
- **Variant 2**: "Unlock 70% faster CI" decorative ASCII banner
- **Variant 3**: "Reclaim your team's focus" decorative ASCII banner

Key changes:
- Added enterprise URL detection (non-standard Nx Cloud URLs always get
variant 0)
- Locked the cloud prompt to always show "Try the full Nx platform?" (no
longer varies by flow variant)
- Flow variant now only affects the completion banner, not the prompt
- Added `snapshot.nx.app` to standard Nx Cloud hosts
- Removed variant 2 auto-connect behavior (all variants now prompt)

## Screenshots
Variant 0:
<img width="1392" height="1065" alt="variant0"
src="https://github.com/user-attachments/assets/0b18686e-1481-4fc0-995e-1577052887ff"
/>

Variant 1:
<img width="1392" height="1065" alt="variant1"
src="https://github.com/user-attachments/assets/e5909e2e-e1d8-4d04-9721-ba6186d06891"
/>

Variant 2:
<img width="1392" height="1065" alt="variant2"
src="https://github.com/user-attachments/assets/7e9f819f-e3c0-44d7-9760-0cab1d5dd9ac"
/>

Variant 3:
<img width="1392" height="1065" alt="variant3"
src="https://github.com/user-attachments/assets/83f0499f-d807-4dc8-9390-c7eec93590a9"
/>


## Related Issue(s)

Closes CLOUD-4147

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 17:33:15 -05:00
Colum Ferry cd97ff7a13 fix(vite): handle sophisticated vite plugins (#34242)
## Current Behavior
With Vite now providing additional options (environments etc) for
framework authors, vite.config files can be much more simple for the
user.
However, this often assumes that the `root` property will be set and
provided during `Vite CLI` invocation.

When we run `resolveConfig` to determine inputs and outputs, we do not
set this `root` and expect the user to have it in their vite config
file.
For some plugins/frameworks such as Tanstack Start - this causes the
plugin to error.

The `isBuildable` conditions is also not inclusive enough and can skip
projects that should be marked as buildable.

## Expected Behavior
Ensure that sophisticated vite plugins are supported with Nx

## Related Issue(s)

CLOSES NXC-3637

(cherry picked from commit 3d62c8b5c1)
2026-01-29 12:23:18 -05:00
Jack Hsu 075b6542b8 feat(core): add Nx Cloud connect URL to template README (#34249)
## Current Behavior
Template-generated workspaces use a generic link in the README instead
of a per-workspace short link for Nx Cloud setup.

## Expected Behavior
When users opt into Nx Cloud (or are auto-connected via variant 2), the
template README is updated with a personalized connect URL section that
helps them finish setting up their workspace.

---
BEFORE:

<img width="762" height="460" alt="image"
src="https://github.com/user-attachments/assets/58900071-1727-49d1-aa19-279c488b5037"
/>

AFTER:

<img width="1032" height="599" alt="image"
src="https://github.com/user-attachments/assets/a6e3a122-5807-4ba2-90dd-441e41a3280e"
/>

---

## Related Issue(s)
Closes NXC-3783

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 9c3a9d7e13)
2026-01-29 12:23:17 -05:00
Drew Teachout afc27c8ff6 fix(core): do not throw error if worker.stdout is not instanceof socket (#34224)
deno worker.stdout is a Readable/Writeable. To provide better deno
support an error should not be thrown

<!-- 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 -->
If `worker.stdout` or `worker.stderr` are not instanceof Socket then nx
throws an error. This is problematic in Deno where `stdout` and `stderr`
are Readable/Writable and not Socket.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
`startupPluginWorker` function should work in Deno runtime

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

- https://github.com/denoland/deno/issues/31961
- https://github.com/oven-sh/bun/issues/26505

---------

Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
(cherry picked from commit e189dcc101)
2026-01-29 12:23:16 -05:00
Craigory Coppola 089db8762a fix(core): improve plugin worker error messages and lifecycle timeouts (#34251)
## Current Behavior
Plugin workers occasionally fall over during the start up steps.

## Expected Behavior
Improves some issues with the error handling when loading plugin workers
and adds some more logs to help understand what's went wrong here.

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

Fixes #

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: AgentEnder <AgentEnder@users.noreply.github.com>
(cherry picked from commit 4f4b9dc048)
2026-01-29 12:23:15 -05:00
Jason Jean cba9c88d9b chore(repo): update nx to 22.5.0-beta.1 (#34234)
Updating Nx from 22.5.0-beta.0 to 22.5.0-beta.1

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
(cherry picked from commit 8d5b316fdd)
2026-01-29 12:23:04 -05:00
Jack Hsu 52088dd13a docs(misc): add 1% scroll depth tracking to docs and non-docs pages (#34246)
This PR adds scroll depth event at the 10% level so we can filter out
users who actually engages with the page versus those who maybe land on
homepage just to get to docs.

<img width="2672" height="1527" alt="Screenshot 2026-01-28 at 4 00
54 PM"
src="https://github.com/user-attachments/assets/ad9be5ae-442c-48eb-9c1e-70f0b872563b"
/>

Also fix the scroll tracker for astro-docs.

<img width="2672" height="1527" alt="Screenshot 2026-01-28 at 4 00
54 PM"
src="https://github.com/user-attachments/assets/e4a24ef2-b6aa-4f5a-b10b-972b73385fee"
/>

<img width="2672" height="1527" alt="Screenshot 2026-01-28 at 2 39
09 PM"
src="https://github.com/user-attachments/assets/833c71f1-7a96-4637-bb6e-3b21227053f0"
/>

## Related Issue(s)
Closes CLOUD-4211

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit b53fd7c7f5)
2026-01-29 12:22:37 -05:00
Louie Weng cf42933bea fix(gradle): use tooling api compatible flags (#34247)
<!-- 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
-->

--rerun is not tooling api compatible and therefore will break usage of
the batch executor. Replaced the flag with --rerun-tasks.

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

Fixes #

(cherry picked from commit 5a424fa8df)
2026-01-29 12:22:26 -05:00
Jack Hsu 8ad00a791e docs(misc): content negotiation for LLM-friendly docs access (#34239)
## Current Behavior
LLMs and CLI tools must explicitly request the `.md` URL suffix to get
raw markdown content from documentation pages.

## Expected Behavior
When a client requests a docs page with `Accept: text/markdown` header,
the edge function rewrites to serve the `.md` version directly (no
redirect). This enables LLM tools to get markdown content by requesting
the standard URL.

Behavior:
- `Accept: text/markdown` → serves .md content (via rewrite, no
redirect)
- Default (browsers) → serves HTML with Link headers (unchanged)

Examples:
```
curl -H 'Accept: text/markdown' https://deploy-preview-34239--nx-docs.netlify.app/docs/getting-started/intro
curl -H 'Accept: text/markdown' https://deploy-preview-34239--nx-docs.netlify.app/docs/getting-started/tutorials/angular-monorepo-tutorial
```

Uses Netlify Edge Function rewrite (returns URL object) instead of
redirect for single-request response that works with all HTTP clients.

## Related Issue(s)
Closes DOC-389

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 7276e4cff7)
2026-01-29 12:22:26 -05:00
Caleb Ukle dfa878ec14 fix(nx-dev): update dead links across nx-dev UI libraries (#34238)
## Current Behavior

broken links

## Expected Behavior

links aren't broken any more and are updated to expected pages from docs
page.

## Related Issue(s)

Fixes DOC-391

(cherry picked from commit f9258c9d82)
2026-01-29 12:22:17 -05:00
Jack Hsu 5f8458469e feat(nx-dev): add llms-full.txt and HTTP Link headers for LLM discovery (#34232)
This PR adds:
1. `llms-full.txt` that is a full copy of our docs in markdown.
2. HTTP `Link` headers to our docs HTML pages so that they point to the
`.md` (markdown) version, and also to `llms.txt` and `llms-full.txt`.

The `llms-full.txt` is currently at 2.7 MB, which is much less than
other sites that are up to 5MB or more.

<img width="569" height="35" alt="Screenshot 2026-01-27 at 12 11 10 PM"
src="https://github.com/user-attachments/assets/61be02b4-2813-4c39-951c-d831af83e823"
/>

First 100 lines of `llms-full.txt`:

````
# Nx Documentation

> Complete Nx documentation compiled into a single file for LLM consumption.

Nx is a powerful, open source, technology-agnostic build platform designed to efficiently manage codebases of any scale. From small single projects to large enterprise monorepos, Nx provides intelligent task execution, caching, and CI optimization.

This file was generated from 503 documentation pages.
Individual pages are available at: https://nx.dev/docs/{slug}.md

# Quickstart

---
<!-- source: https://nx.dev/docs/quickstart.md -->
## Quickstart with Nx

Get up and running with Nx in just a few minutes by following these simple steps.

{% steps %}

1. Install the Nx CLI

   Installing Nx globally is **optional** - you can use `npx` to run Nx commands without installing it globally, especially if you're working with Node.js projects.

   {% tabs syncKey="install-method" %}
   {% tabitem label="npm" %}

   ```shell
   npm add --global nx
   ```

   **Note:** You can also use Yarn, pnpm, or Bun

   {% /tabitem %}
   {% tabitem label="Homebrew (macOS, Linux)" %}

   ```shell
   brew install nx
   ```

   {% /tabitem %}
   {% tabitem label="Chocolatey (Windows)" %}

   ```shell
   choco install nx
   ```

   {% /tabitem %}
   {% tabitem label="apt (Ubuntu)" %}

   ```shell
   sudo add-apt-repository ppa:nrwl/nx
   sudo apt update
   sudo apt install nx
   ```

   {% /tabitem %}
   {% /tabs %}

2. Start fresh or add to existing project

   For JavaScript-based projects you can **start with a new workspace** using the following command:

   ```shell
   npx create-nx-workspace@latest
   ```

   **Add to an existing project: (recommended also for non-JS projects)**

   ```shell
   npx nx@latest init
   ```

   **Get the complete experience:**
   For a fully integrated development workflow with AI-powered CI features, [start directly from Nx Cloud](https://cloud.nx.app/get-started).

   Learn more: [Start New Project](/docs/getting-started/start-new-project) • [Add to Existing](/docs/getting-started/start-with-existing-project) • [Complete Nx Experience](https://cloud.nx.app/get-started)

3. Run Your First Commands

   Nx provides powerful task execution with built-in caching. Here are some essential commands:

   **Run a task for a single project:**

   ```shell
   nx build my-app
   nx test my-lib
   ```

   **Run tasks for multiple projects:**

   ```shell
   nx run-many -t build test lint
   ```

   Learn more: [Run Tasks](/docs/features/run-tasks) • [Cache Task Results](/docs/features/cache-task-results)

4. What's next?

   Now that you've experienced the Nx basics, choose how you want to continue:

````

## Related Issue(s)
Closes DOC-236

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
(cherry picked from commit 8b245f1b0a)
2026-01-29 12:22:16 -05:00
Benjamin Cabanes 55fe0419e9 docs(nx-dev): migrate color variants to contrast design (#34077)
This design refresh emphasizes the contrast variant aesthetic across all
hero sections, pricing cards, and primary call-to-actions.

- Change 47 instances of variant="primary" to variant="contrast"
- Update ui-courses to use variant="secondary" for GitHub link
- Prefer high-contrast inverted style for primary CTAs
- Maintain proper visual hierarchy with secondary actions
- Replace all slate-* classes with zinc-* equivalents (1,158 instances)
- Replace all sky-* classes with blue-* equivalents (210 instances)
- Update opacity variants, gradients, rings, and borders
- Maintain full dark mode compatibility

(cherry picked from commit a092ed06c4)
2026-01-29 12:22:15 -05:00
MaxKless 86fbeac4ef fix(core): hide already-installed nx packages from suggestion list during nx import (#34227)
## Current Behavior
If something is in `package.json#dependencies`, we still suggest it to
be `nx add`-ed during `nx import`

## Expected Behavior
If a plugin is already installed, we don't suggest it anymore

(cherry picked from commit f89ccb091f)
2026-01-29 12:22:07 -05:00
Jason Jean a40d06ed23 chore(repo): update nx to 22.5.0-beta.0 (#34209)
Updating Nx from 22.4.0-beta.5 to 22.5.0-beta.0

(cherry picked from commit f9ab939c74)
2026-01-29 12:21:31 -05:00
Colum Ferry 758cc7b7c8 fix(web): ensure vitest config file is created (#34216)
`@nx/web:app` generator is incorrectly calling `createOrEditViteConfig`
when bundler != vite and unitTestRunner = vitest.

Ensure it is using the correct file

(cherry picked from commit 7a446e4979)
2026-01-29 12:21:29 -05:00
Jack Hsu b6cb5ccaca feat(core): add variant 2 to CNW cloud prompts with promo message (#34223)
This PR uses three variants for CNW for prompting for Cloud/platform
connection.

- Variant 0: Shows "Try the full Nx platform?" prompt → platform-setup
completion
- Variant 1: Shows "Would you like remote caching..." prompt →
cache-setup completion
- Variant 2: No prompt → platform-promo completion with "Want faster
builds?"

Both template and custom flows use the same messages and prompts.


### Skip (all) -- No changes


<img width="1392" height="994" alt="cnw_all_skip_completion"
src="https://github.com/user-attachments/assets/1579df01-fc36-4324-bc74-19efc18f78af"
/>

### Variant 0 (full platform)

Template prompt:

<img width="1392" height="994" alt="cnw_template_variant_0_prompt"
src="https://github.com/user-attachments/assets/d02e9ddc-fd25-4f6f-ac61-6f6d518fe338"
/>

Template completion:

<img width="1392" height="994" alt="cnw_template_variant_0_completion"
src="https://github.com/user-attachments/assets/12e459a0-962c-4724-82fa-a4f5a3b6fbd8"
/>

Custom prompt:

<img width="1392" height="994" alt="cnw_custom_variant_0_prompt"
src="https://github.com/user-attachments/assets/8143832a-a85e-43b4-81eb-15074c223476"
/>

Custom completion:

<img width="1392" height="994" alt="cnw_custom_variant_0_completion"
src="https://github.com/user-attachments/assets/036ef6f9-4977-459a-bbd2-502670a12d01"
/>

### Variant 1 (remote cache)

Template prompt:

<img width="1392" height="994" alt="cnw_template_variant_1_prompt"
src="https://github.com/user-attachments/assets/f69b540f-7e48-46ae-99e5-f53657176424"
/>

Template completion:

<img width="1392" height="994" alt="cnw_template_variant_1_completion"
src="https://github.com/user-attachments/assets/04c17847-b0f2-4e37-b7d2-5556aa14f510"
/>

Custom prompt:

<img width="1392" height="994" alt="cnw_custom_variant_1_prompt"
src="https://github.com/user-attachments/assets/abdb38b2-611e-458b-85c0-7b89cd1b827e"
/>


Custom completion:

<img width="1348" height="950" alt="cnw_custom_variant_1_completion"
src="https://github.com/user-attachments/assets/e7d35ddb-e8ac-4ad1-889e-9fff89538900"
/>

## Variant 2 (no prompt)

Template completion:

<img width="1392" height="994" alt="cnw_template_variant_2_completion"
src="https://github.com/user-attachments/assets/07050989-a56c-46eb-a0c3-8730916856ff"
/>

Custom completion:

<img width="1392" height="994" alt="cnw_custom_variant_2_completion"
src="https://github.com/user-attachments/assets/94000828-37b2-4059-a13b-0d36972cfd3e"
/>

## Related Issue(s)

Closes CLOUD-4189

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 16:31:20 -05:00
Jack Hsu 1dad304994 Revert "Revert "feat(core): add A/B testing variant 1 to skip cloud p…rompt in CNW (#34106)" (#34191) (#34204)
This reverts commit f016664557.

(cherry picked from commit e108dac1bb)
2026-01-26 16:01:21 -05:00
Colum Ferry eb239f3a91 fix(react): remove file-loader dependency and update svgr migration (#34218)
## Current Behavior
The migration for svgr requires using file-loader which is unmaintained.

## Expected Behavior
Use asset/resource instead of file-loader

## Related Issue(s)

CLOSES NXC-3667

(cherry picked from commit 3fcd2008ef)
2026-01-26 16:01:20 -05:00
Jason Jean c1c1a64daa fix(core): fall back to node_modules when tmp has noexec (#34207)
## Summary

- When `/tmp` is mounted with `noexec`, loading native modules from the
cache fails silently and causes Nx to hang indefinitely
- This adds a fallback to load from `node_modules` when permission
errors occur

## Problem

Users with `/tmp` mounted with `noexec` (a common security hardening
practice) experience Nx hanging forever, even for simple commands like
`nx --version`.

The root cause:
1. Nx copies native `.node` files to `/tmp` to avoid Windows file
locking issues
2. On `noexec` mounts, execution fails with `EACCES`/`EPERM`
3. The error wasn't caught, leading to broken native bindings and
infinite loops

## Solution

Catch permission errors when loading from the cache and fall back to the
original `node_modules` location. This:
- Works automatically without user config
- Preserves Windows file locking fix (only falls back when needed)
- No error messages for users

Closes #33991

(cherry picked from commit 75f36edb8f)
2026-01-26 09:58:23 -05:00
Miguel ca38b53eb4 fix(devkit): allow null values in JSON schema validation (#34167)
<!-- 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 -->

Schema validation (done, for instance, when calling an executor) fails
when an option has value "null" and schema accepts null values. I had it
in a custom executor for `nx-release-publish`, that understands that
`nxReleaseVersionData` is implicitly passed, so I define its schema:
```json
"newVersion": {
  "type": ["string", "null"],
  "description": "The new version of the project, null if no changes detected"
}
```

My code calls `getReleaseClient().releaseVersion(options)`, which gets
me a `projectsVersionData` object with version info. It contains `null`
values (allowed). I then pass it, and ends up in:
```typescript
 // nx/src/tasks-runner/task-orchestrator.ts:531-539
  const combinedOptions = combineOptionsForExecutor(
      task.overrides,  // ← Contains nxReleaseVersionData with null values
      task.target.configuration,
      targetConfiguration,
      schema,           // ← Schema from executor
      task.target.project,
      relativeCwd,
      isVerbose
  );
```

which fails inside:
```typescript
 // nx/src/utils/params.js:126-201
  function validateObject(opts, schema, definitions) {
      // Line 191-200: Iterate through all properties
      Object.keys(opts).forEach((p) => {
          validateProperty(
              p,                              // "nxReleaseVersionData"
              opts[p],                        // { foo: { newVersion: null, ... }}
              (schema.properties ?? {})[p],   // schema for nxReleaseVersionData
              definitions
          );
      });
  }
  ```

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

`null` values should be considered, as they are valid in JSON schemas. It was probably not considered, because we never think that `typeof null === "object"`, but it's unfortunately the case.

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

I will create one

Fixes https://github.com/nrwl/nx/issues/34169

(cherry picked from commit 3672e1a3ea)
2026-01-26 09:58:22 -05:00
Leosvel Pérez Espinosa 30abc3fcc6 feat(core): display batch tasks in the tui (#33695)
Adds support for Batch tasks and displays them in the TUI.

---------

Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
(cherry picked from commit a15881db1a)
2026-01-26 09:58:21 -05:00
Jack Hsu f872ebdad7 docs(misc): collect usage data on .md and .txt files (#34203)
Right now GA only collects page views. This PR allow us to see which
`.md` files are being used. There are mostly useful for AI agents to
fetch without using too many tokens. We want to track usage so we can
see what techniques to guide agents actually work.

## Related Issue(s)
Closes DOC-386

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 1dd4262336)
2026-01-26 09:58:20 -05:00
Jack Hsu 0e37963f67 fix(core): cloud commands are noop when not connected rather than errors (#34193)
This PR makes it so Cloud commands like `npx nx record` and `npx nx
fix-ci` still work without `nxCloudId`. We'll log a warning so that
`ci.yml` using these commands will still work. The warning let's users
know that these do not work without being connected.

Closes #NXC-3753

(cherry picked from commit 687357c82e)
2026-01-26 09:58:19 -05:00
Mark Lindsey a475a43ac0 chore(repo): commit lint mention that commits should be lowercase (#34199)
<!-- 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
Commit linting does not mention requirement for commit message to be
lowercase.
<!-- This is the behavior we have today -->

## Expected Behavior
Hook message should instruct user to use all lowercase for commit
message.
<!-- This is the behavior we should expect with the changes in this PR
-->

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

Fixes #

(cherry picked from commit 3d1e544812)
2026-01-26 09:58:18 -05:00
Jack Hsu dc2e9d7087 fix(core): consolidate GitHub URL messaging when gh push fails (#34196)
When `gh repo create` fails, users see two redundant messages. This
consolidates them into a single message with the helpful `?name=...`
parameter in the GitHub URL.

BEFORE: (We show `Could not push. Push repo to complete setup.` and then
`Push your repo (https://github.com/new)...` again at the end)

<img width="1209" height="560" alt="image"
src="https://github.com/user-attachments/assets/e22c012c-8e0f-4ff9-a6c2-de8267b69b5d"
/>

AFTER: (Only show `Could not push` as an info log, and then complete
setup is shown only once at the end)

<img width="1250" height="591" alt="image"
src="https://github.com/user-attachments/assets/3dc075c7-4418-4373-a07a-1b95f71b3b87"
/>

Closes NXC-3754

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
(cherry picked from commit 9bb69383b8)
2026-01-26 09:58:17 -05:00
Jonathan Cammisuli 71039e8ffd docs(nx-dev): update docs to include SELF_HEALING.md information (#34200)
(cherry picked from commit 1831ace87e)
2026-01-26 09:58:16 -05:00
Mark Lindsey 126710a3ed docs(nx-dev): add bitbucket to self healing docs (#34198)
<!-- 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
Self-healing docs only reference being supported for GitHub, Azure, and
GitLab.
<!-- This is the behavior we have today -->

## Expected Behavior
We should show instructions for all currently supported vcs providers,
including Bitbucket.
<!-- This is the behavior we should expect with the changes in this PR
-->

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

Fixes #

(cherry picked from commit 478afc7046)
2026-01-26 09:58:15 -05:00
JamesHenry acb1b8e55c chore(repo): remove --auto-apply-fixes, it is set in Nx Cloud UI
(cherry picked from commit 1299d044eb)
2026-01-26 09:58:14 -05:00
Craigory Coppola 9b8a9b31f0 fix(core): handle resizing a bit better for inline_tui (#34006)
## Current Behavior
Resizing the TUI while in inline view kinda breaks things. Its
unfortunate, I'm not sure there's a ton to be done, but this PR explores
some solutions

## Expected Behavior
The TUI is less sensitive to resize events with inline mode

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

Fixes #

(cherry picked from commit 273a474047)
2026-01-26 09:58:12 -05:00
536 changed files with 11984 additions and 4590 deletions
+2 -1
View File
@@ -18,6 +18,7 @@ jobs:
main-linux:
runs-on: ubuntu-latest
env:
NX_BATCH_MODE: 'true'
NX_E2E_CI_CACHE_KEY: e2e-github-linux
NX_DAEMON: 'true'
NX_PERF_LOGGING: 'false'
@@ -47,7 +48,7 @@ jobs:
main-branch-name: 'master'
- name: Start CI Run
run: npx nx-cloud@next start-ci-run --auto-apply-fixes="*format:check*,*sync:check*,*conformance:check*,*format-native*,*lint-native*,*lint*,*astro-docs:validate-links*" --distribute-on="./.nx/workflows/dynamic-changesets.yaml" --stop-agents-after="e2e"
run: npx nx-cloud@next start-ci-run --distribute-on="./.nx/workflows/dynamic-changesets.yaml" --stop-agents-after="e2e"
- name: Install dependencies
run: |
+19 -5
View File
@@ -1,8 +1,23 @@
distribute-on:
default: auto linux-large, 3 linux-extra-large
extra-small-changeset: 6 linux-large, 3 linux-extra-large
small-changeset: 6 linux-large, 4 linux-extra-large
medium-changeset: 6 linux-large, 5 linux-extra-large
large-changeset: 6 linux-large, 6 linux-extra-large
extra-large-changeset: 8 linux-large, 8 linux-extra-large
assignment-rules:
- projects:
- e2e-gradle
- e2e-next
- e2e-plugin
targets:
- e2e-ci**
run-on:
- agent: linux-extra-large
parallelism: 2
- projects:
- e2e-angular
- e2e-node
- e2e-react
targets:
- e2e-ci**
run-on:
@@ -25,15 +40,14 @@ assignment-rules:
- projects:
- e2e-release
- e2e-angular
- e2e-react
- e2e-next
- e2e-nuxt
- e2e-web
- e2e-eslint
- e2e-remix
- e2e-cypress
- e2e-docker
- e2e-js
- e2e-nx
- e2e-nx-init
- e2e-dotnet
- e2e-workspace-create
@@ -44,7 +58,7 @@ assignment-rules:
- agent: linux-large
parallelism: 1
- agent: linux-extra-large
parallelism: 1
parallelism: 2
# All other e2e tests can run in parallel
- targets:
Generated
+90 -75
View File
@@ -86,6 +86,15 @@ dependencies = [
"libc",
]
[[package]]
name = "ansi-escape-sequences"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6afb74bb006f83a46d689f70e46ce3a9830db762134b3b585986fa9eab21f75d"
dependencies = [
"regex",
]
[[package]]
name = "anstyle"
version = "1.0.6"
@@ -295,7 +304,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
dependencies = [
"memchr",
"regex-automata 0.4.6",
"regex-automata 0.4.13",
"serde",
]
@@ -790,6 +799,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
[[package]]
name = "east-asian-width"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef8c47de0fcde8bd09c98fc22e4ea10ed14b9c05bf02c309e1e743aee7d0d9f6"
[[package]]
name = "either"
version = "1.10.0"
@@ -1382,8 +1397,8 @@ dependencies = [
"aho-corasick",
"bstr",
"log",
"regex-automata 0.4.6",
"regex-syntax 0.8.2",
"regex-automata 0.4.13",
"regex-syntax 0.8.8",
]
[[package]]
@@ -1587,7 +1602,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.57.0",
"windows-core",
]
[[package]]
@@ -1625,7 +1640,7 @@ dependencies = [
"globset",
"log",
"memchr",
"regex-automata 0.4.6",
"regex-automata 0.4.13",
"same-file",
"walkdir",
"winapi-util",
@@ -1892,7 +1907,7 @@ dependencies = [
"rustc-hash 2.1.1",
"serde",
"serde_json",
"thiserror 2.0.12",
"thiserror 2.0.18",
"tokio",
"tokio-stream",
"tower",
@@ -1916,7 +1931,7 @@ dependencies = [
"rustls-platform-verifier",
"serde",
"serde_json",
"thiserror 2.0.12",
"thiserror 2.0.18",
"tokio",
"tower",
"url",
@@ -1944,7 +1959,7 @@ dependencies = [
"http",
"serde",
"serde_json",
"thiserror 2.0.12",
"thiserror 2.0.18",
]
[[package]]
@@ -2072,9 +2087,9 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.7.1"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "memmap2"
@@ -2456,6 +2471,7 @@ dependencies = [
"watchexec-filterer-ignore",
"watchexec-signals",
"winapi",
"wrap-ansi",
"xxhash-rust",
]
@@ -3040,14 +3056,14 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.3"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.6",
"regex-syntax 0.8.2",
"regex-automata 0.4.13",
"regex-syntax 0.8.8",
]
[[package]]
@@ -3061,13 +3077,13 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.6"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.8.2",
"regex-syntax 0.8.8",
]
[[package]]
@@ -3078,9 +3094,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.8.2"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "rend"
@@ -3616,6 +3632,18 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "string-width"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f00d94d660cfa7bf3669352ebe9e06cc0351e0c9e696a31d051a54fa4bbb2b5"
dependencies = [
"east-asian-width",
"regex",
"strip-ansi-escapes",
"unicode-segmentation",
]
[[package]]
name = "string_cache"
version = "0.8.7"
@@ -3654,6 +3682,15 @@ dependencies = [
"syn 2.0.100",
]
[[package]]
name = "strip-ansi-escapes"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025"
dependencies = [
"vte 0.14.1",
]
[[package]]
name = "strsim"
version = "0.11.1"
@@ -3962,11 +3999,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.12"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl 2.0.12",
"thiserror-impl 2.0.18",
]
[[package]]
@@ -3982,9 +4019,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.12"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
@@ -4459,7 +4496,7 @@ dependencies = [
"ratatui",
"tui-term 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.2.0",
"vte",
"vte 0.13.1",
]
[[package]]
@@ -4473,6 +4510,15 @@ dependencies = [
"vte_generate_state_changes",
]
[[package]]
name = "vte"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077"
dependencies = [
"memchr",
]
[[package]]
name = "vte_generate_state_changes"
version = "0.1.2"
@@ -4827,7 +4873,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
dependencies = [
"windows-collections",
"windows-core 0.61.2",
"windows-core",
"windows-future",
"windows-link",
"windows-numerics",
@@ -4839,19 +4885,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
dependencies = [
"windows-core 0.61.2",
]
[[package]]
name = "windows-core"
version = "0.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
dependencies = [
"windows-implement 0.57.0",
"windows-interface 0.57.0",
"windows-result 0.1.2",
"windows-targets 0.52.6",
"windows-core",
]
[[package]]
@@ -4860,10 +4894,10 @@ version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
dependencies = [
"windows-implement 0.60.2",
"windows-interface 0.59.3",
"windows-implement",
"windows-interface",
"windows-link",
"windows-result 0.3.4",
"windows-result",
"windows-strings",
]
@@ -4873,22 +4907,11 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
"windows-core 0.61.2",
"windows-core",
"windows-link",
"windows-threading",
]
[[package]]
name = "windows-implement"
version = "0.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
@@ -4900,17 +4923,6 @@ dependencies = [
"syn 2.0.100",
]
[[package]]
name = "windows-interface"
version = "0.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
@@ -4934,19 +4946,10 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
"windows-core 0.61.2",
"windows-core",
"windows-link",
]
[[package]]
name = "windows-result"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-result"
version = "0.3.4"
@@ -5253,6 +5256,18 @@ dependencies = [
"wayland-protocols-wlr",
]
[[package]]
name = "wrap-ansi"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "848bc403c11fc8f4d6597f2bad2ce43111fbf6757c7dd7d62590199e8dade5fd"
dependencies = [
"ansi-escape-sequences",
"regex",
"string-width",
"thiserror 2.0.18",
]
[[package]]
name = "wyz"
version = "0.5.1"
+3 -3
View File
@@ -1,7 +1,7 @@
<p style="text-align: center;">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./images/nx-dark.svg">
<img alt="Nx - Smart Repos · Fast Builds" src="./images/nx-light.svg" width="100%">
<img alt="Nx - Smart Monorepos · Fast Builds" src="./images/nx-light.svg" width="100%">
</picture>
</p>
@@ -19,7 +19,7 @@
<hr>
# Smart Repos · Fast Builds
# Smart Monorepos · Fast Builds
Get to green PRs in half the time. Nx optimizes your builds, scales your CI, and fixes failed PRs. Built for developers and AI agents.
@@ -58,7 +58,7 @@ Learn more in the [Nx CI docs &raquo;](https://nx.dev/ci/getting-started/intro?u
- [Our Twitter/X](https://x.com/nxdevtools)
<p style="text-align: center;"><a href="https://www.youtube.com/@nxdevtools/videos" target="_blank" rel="noreferrer"><img src="./images/nx-courses-and-videos.svg"
width="100%" alt="Nx - Smart Repos · Fast Builds"></a></p>
width="100%" alt="Nx - Smart Monorepos · Fast Builds"></a></p>
## Want to help?
+5 -4
View File
@@ -6,6 +6,7 @@ import react from '@astrojs/react';
import markdoc from '@astrojs/markdoc';
import tailwindcss from '@tailwindcss/vite';
import { sidebar } from './sidebar.mts';
import rehypeTableOptionLinks from './src/plugins/utils/rehype-table-option-links.ts';
const BASE = '/docs';
@@ -33,6 +34,9 @@ export default defineConfig({
},
},
},
markdown: {
rehypePlugins: [rehypeTableOptionLinks],
},
trailingSlash: 'never',
// This adapter doesn't support local previews, so only load it on Netlify.
adapter: process.env['NETLIFY'] ? netlify() : undefined,
@@ -94,10 +98,7 @@ export default defineConfig({
'./src/plugins/canonical.middleware.ts',
],
markdown: {
// this breaks the renderMarkdown function in the plugin loader due to starlight path normalization
// as to _why_ it has to normalize a path?
// idk just working around the issue for now but we'll want to have linked headers so will need to fix
headingLinks: false,
headingLinks: true,
},
social: [
{ icon: 'github', label: 'GitHub', href: 'https://github.com/nrwl/nx' },
+1 -1
View File
@@ -11,7 +11,7 @@ test('links in descriptions of properties should correctly link to the same page
await page
.getByTestId('main-pane')
.getByRole('link', { name: 'nxCloudAccessToken' })
.getByRole('link', { name: 'nxCloudAccessToken', exact: true })
.click();
await expect(
+6
View File
@@ -4,9 +4,15 @@ import {
Markdoc,
} from '@astrojs/markdoc/config';
import starlightMarkdoc from '@astrojs/starlight-markdoc';
import { transformOptionsTable } from './src/utils/markdoc-table-option-links';
export default defineMarkdocConfig({
extends: [starlightMarkdoc()],
nodes: {
table: {
transform: transformOptionsTable,
},
},
tags: {
call_to_action: {
render: component('./src/components/markdoc/CallToAction.astro'),
+3
View File
@@ -4,6 +4,9 @@
NX_GRADLE_DISABLE = "true"
NX_MAVEN_DISABLE = "true"
# Edge functions are auto-discovered from netlify/edge-functions/
# Path configuration is in each function's inline `config` export
# Permanent redirects (301 by default)
# Storybook docs consolidation
@@ -0,0 +1,59 @@
import type { Context } from 'https://edge.netlify.com';
/**
* Content negotiation for LLM-friendly docs access.
* See: https://llmstxt.org/
*/
export default async function handler(
request: Request,
context: Context
): Promise<Response | URL> {
const url = new URL(request.url);
const pathname = url.pathname;
const acceptHeader = request.headers.get('accept') || '';
// Serve markdown for LLM tools that explicitly request it
// Or if there are no accept headers passed (e.g. Cursor)
if (!acceptHeader || acceptHeader.includes('text/markdown')) {
const mdPath = pathname.replace(/\/?$/, '.md');
return new URL(mdPath, request.url);
}
const response = await context.next();
const contentType = response.headers.get('content-type') || '';
if (!contentType.includes('text/html')) {
return response;
}
const mdPath = pathname.replace(/\/?$/, '.md');
const linkHeader = [
`<${mdPath}>; rel="alternate"; type="text/markdown"`,
`</docs/llms.txt>; rel="alternate"; type="text/markdown"; title="LLM Index"`,
`</docs/llms-full.txt>; rel="alternate"; type="text/markdown"; title="Full Documentation"`,
].join(', ');
// Netlify responses are immutable
const newHeaders = new Headers(response.headers);
newHeaders.set('Link', linkHeader);
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders,
});
}
export const config = {
path: ['/docs/*'],
excludedPath: [
'/docs/*.md',
'/docs/*.js',
'/docs/*.txt',
'/docs/images/*',
// _astro and other asset paths
'/docs/_*',
],
};
@@ -0,0 +1,107 @@
import type { Context } from 'https://edge.netlify.com';
// Configuration - set these in Netlify environment variables
const GA_MEASUREMENT_ID =
Netlify.env.get('GA_MEASUREMENT_ID') || 'G-XXXXXXXXXX';
const GA_API_SECRET = Netlify.env.get('GA_API_SECRET') || '';
function getClientId(request: Request): string {
// Try to extract existing GA client ID from cookie
const cookies = request.headers.get('cookie') || '';
const gaMatch = cookies.match(/_ga=GA\d+\.\d+\.(\d+\.\d+)/);
if (gaMatch) {
return gaMatch[1];
}
// Generate a new client ID for this request
// For non-browser clients (AI tools), this creates a session-based ID
const timestamp = Date.now();
const random = Math.floor(Math.random() * 1000000000);
return `${random}.${timestamp}`;
}
async function sendToGA4(
request: Request,
context: Context,
pathname: string
): Promise<void> {
if (!GA_API_SECRET) {
console.warn('GA_API_SECRET not configured, skipping analytics');
return;
}
const clientId = getClientId(request);
const userAgent = request.headers.get('user-agent') || 'unknown';
// Detect AI tools from user agent
const isAITool =
/bot|crawler|spider|gpt|claude|anthropic|openai|perplexity|cohere/i.test(
userAgent
);
const payload = {
client_id: clientId,
events: [
{
name: 'server_page_view',
params: {
page_location: request.url,
page_title: pathname,
page_path: pathname,
// Custom parameters for filtering
content_type: pathname.endsWith('.txt')
? 'text/plain'
: 'text/markdown',
file_extension: pathname.substring(pathname.lastIndexOf('.')),
user_agent: userAgent,
is_ai_tool: isAITool ? 'true' : 'false',
country: context.geo?.country?.code || 'unknown',
},
},
],
};
console.log(`Tracked asset path: ${pathname}`);
const endpoint = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
try {
await fetch(endpoint, {
method: 'POST',
body: JSON.stringify(payload),
});
} catch (error) {
// Log but don't fail the request
console.error('Failed to send to GA4:', error);
}
}
export default async function handler(
request: Request,
context: Context
): Promise<Response> {
const url = new URL(request.url);
const pathname = url.pathname;
// Send analytics in background (non-blocking)
context.waitUntil(sendToGA4(request, context, pathname));
// Continue to serve the actual file
const response = await context.next();
// Netlify Edge Function responses are immutable, so create a new Response
const newHeaders = new Headers(response.headers);
newHeaders.set('x-nx-edge-function', 'track-asset-requests');
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders,
});
}
export const config = {
path: ['/**/*.txt', '/**/*.md'],
// Something is adding .png.md to get image paths, exclude those.
excludedPath: ['/docs/og/*'],
};
@@ -0,0 +1,129 @@
import type { Context } from 'https://edge.netlify.com';
const GA_MEASUREMENT_ID =
Netlify.env.get('GA_MEASUREMENT_ID') || 'G-XXXXXXXXXX';
const GA_API_SECRET = Netlify.env.get('GA_API_SECRET') || '';
function shouldTrack(request: Request): boolean {
const accept = request.headers.get('accept') || '';
// Track if:
// - No Accept header (some LLM tools)
// - Accept contains text/html (browsers)
// - Accept contains */* (curl, browser default)
if (!accept || accept.includes('text/html') || accept.includes('*/*')) {
return true;
}
// Skip image/css/js/font requests
return false;
}
function getClientId(request: Request): string {
const cookies = request.headers.get('cookie') || '';
const gaMatch = cookies.match(/_ga=GA\d+\.\d+\.(\d+\.\d+)/);
if (gaMatch) return gaMatch[1];
const timestamp = Date.now();
const random = Math.floor(Math.random() * 1000000000);
return `${random}.${timestamp}`;
}
async function sendToGA4(
request: Request,
context: Context,
pathname: string
): Promise<void> {
if (!GA_API_SECRET) {
console.warn('GA_API_SECRET not configured, skipping analytics');
return;
}
const clientId = getClientId(request);
const userAgent = request.headers.get('user-agent') || 'unknown';
const isAITool =
/bot|crawler|spider|gpt|claude|anthropic|openai|perplexity|cohere/i.test(
userAgent
);
const payload = {
client_id: clientId,
events: [
{
name: 'server_page_view',
params: {
page_location: request.url,
page_title: pathname,
page_path: pathname,
content_type: 'text/html',
file_extension: '.html',
user_agent: userAgent,
is_ai_tool: isAITool ? 'true' : 'false',
country: context.geo?.country?.code || 'unknown',
},
},
],
};
console.log(`Tracked HTML page: ${pathname}`);
const endpoint = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
try {
await fetch(endpoint, {
method: 'POST',
body: JSON.stringify(payload),
});
} catch (error) {
console.error('Failed to send to GA4:', error);
}
}
export default async function handler(
request: Request,
context: Context
): Promise<Response> {
const pathname = new URL(request.url).pathname;
if (shouldTrack(request)) {
context.waitUntil(sendToGA4(request, context, pathname));
}
const response = await context.next();
const newHeaders = new Headers(response.headers);
newHeaders.set('x-nx-edge-function', 'track-page-requests');
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders,
});
}
export const config = {
path: ['/docs/*'],
excludedPath: [
// Text/code files (handled by track-asset-requests or not tracked)
'/docs/*.md',
'/docs/*.js',
'/docs/*.txt',
// Images
'/docs/*.svg',
'/docs/*.png',
'/docs/*.jpg',
'/docs/*.jpeg',
'/docs/*.gif',
'/docs/*.webp',
'/docs/*.ico',
'/docs/images/*',
'/docs/og/*',
// Fonts
'/docs/fonts/*',
'/docs/*.woff',
'/docs/*.woff2',
// Search index (pagefind)
'/docs/pagefind/*',
// Astro build assets
'/docs/_*',
],
};
+1
View File
@@ -16,6 +16,7 @@
"@nx/nx-dev-ui-icons": "workspace:*",
"@nx/nx-dev-ui-markdoc": "workspace:*",
"@tailwindcss/vite": "^4.1.11",
"@types/hast": "^3.0.4",
"astro": "^5.10.1",
"astro-og-canvas": "^0.7.0",
"canvaskit-wasm": "^0.40.0",
+75 -1
View File
@@ -144,6 +144,78 @@
window.twq('config', 'obtp4');
};
// Scroll depth tracking
const SCROLL_THRESHOLDS = [10, 25, 50, 75, 90];
let firedThresholds = new Set();
let scrollTrackingEnabled = false;
let scrollRafId = null;
function getScrollPercentage() {
const scrollTop = window.scrollY || document.documentElement.scrollTop;
const scrollHeight = document.documentElement.scrollHeight;
const clientHeight = window.innerHeight;
return (scrollTop + clientHeight) / scrollHeight;
}
function handleScrollTracking() {
if (!scrollTrackingEnabled) return;
const scrollPercentage = getScrollPercentage() * 100;
// Fire events for all thresholds we've passed but haven't fired yet
for (const threshold of SCROLL_THRESHOLDS) {
if (scrollPercentage >= threshold && !firedThresholds.has(threshold)) {
firedThresholds.add(threshold);
sendSearchEvent(`scroll_${threshold}`, {
event_category: 'scroll',
event_label: window.location.pathname,
});
}
}
}
function throttledScrollHandler() {
if (scrollRafId !== null) return;
scrollRafId = requestAnimationFrame(() => {
handleScrollTracking();
scrollRafId = null;
});
}
function attachScrollListener() {
window.addEventListener('scroll', throttledScrollHandler, {
passive: true,
});
}
function setupScrollTracking() {
// Reset scroll depth on navigation (for SPA-like behavior via View Transitions)
firedThresholds = new Set();
scrollTrackingEnabled = false;
// Delay tracking start to avoid false triggers during navigation
setTimeout(() => {
scrollTrackingEnabled = true;
// Immediately check current scroll position to capture thresholds
// that may have been passed during the delay
handleScrollTracking();
}, 500);
attachScrollListener();
// Handle Astro View Transitions - reset on navigation
document.addEventListener('astro:after-swap', () => {
firedThresholds = new Set();
scrollTrackingEnabled = false;
setTimeout(() => {
scrollTrackingEnabled = true;
// Immediately check current scroll position after navigation
handleScrollTracking();
}, 500);
});
}
const SEARCH_DEBOUNCE_MS = 1000;
let searchDebounceTimer;
let lastSearchQuery = '';
@@ -209,12 +281,14 @@
loadGTM();
loadHubSpot();
setupSearchTracking();
setupScrollTracking();
} else if (window.Cookiebot && window.Cookiebot.consent) {
// Statistics cookies (Google Analytics, GTM, Search Tracking)
// Statistics cookies (Google Analytics, GTM, Search, Scroll Tracking)
if (window.Cookiebot.consent.statistics) {
loadGoogleAnalytics();
loadGTM();
setupSearchTracking();
setupScrollTracking();
}
// Marketing cookies (HubSpot, Apollo, Hotjar, Twitter)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

@@ -122,6 +122,33 @@ steps:
{% /tabitem %}
{% tabitem label="Bitbucket Pipelines" %}
```yaml
# bitbucket-pipelines.yml
image: node:22
pipelines:
pull-requests:
'**':
- step:
name: CI
script:
# Your existing steps which start-ci-run, install
# dependencies, etc.
# These are just illustrative examples...
- npx nx-cloud start-ci-run
- npm ci
- npx nx affected -t lint test build
after-script:
# NEW: Add this section at the end of your step
# IMPORTANT: after-script runs regardless of step success/failure
# so it's like if: always() on GitHub
- npx nx fix-ci
```
{% /tabitem %}
{% /tabs %}
> NOTE: If all tasks succeed then the `fix-ci` command becomes a no-op automatically, so that is why "always" is recommended.
@@ -182,31 +209,62 @@ Tasks matching these patterns will also have high-confidence, verified code chan
Tasks matching these patterns will **never** have code changes auto-applied, even if they match the include patterns or presets specified above. For example: `*e2e*`.
## Customization with CLAUDE.md
## Configuration with SELF_HEALING.md
Create a `CLAUDE.md` file in your repository root to provide additional context to the AI agent:
Create a `.nx/SELF_HEALING.md` file in your repository to provide project-specific instructions to the Self-Healing CI agent. This file contains freeform markdown that the AI agent reads and interprets naturally.
### Failure Classification Rules
{% aside title="Why a dedicated file?" type="note" %}
Using `.nx/SELF_HEALING.md` instead of `AGENTS.md` (or equivalent) separates CI-specific instructions from local development context. The file lives in the `.nx` directory alongside other Nx Cloud configuration.
{% /aside %}
Override how the AI categorizes failures:
### Example SELF_HEALING.md
```markdown
## Failure Classification
# Self-Healing Configuration
- Failures in `**/migrations/**` should be classified as `environment_state`
- Test timeouts in e2e tests are usually `flaky_task`
## Confidence Rules
- Fixes involving "test" targets should require high confidence
- Formatting fixes can be applied with medium confidence
## Off-Limits Areas
- `/src/generated/` - auto-generated, do not modify
- `/legacy/` - requires manual review
## Fix Preferences
- Prefer updating ESLint rules over adding disable comments
- For type errors, prefer explicit types over `any`
## Context
See ARCHITECTURE.md for module boundaries.
```
### Predefined Fixes
### What to Include
Specify deterministic solutions for common failures:
| Section | Purpose | Example |
| -------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
| **Confidence Rules** | Override how the AI categorizes failure severity | "Failures in `**/migrations/**` should be classified as `environment_state`" |
| **Off-Limits Areas** | Directories or files the agent should never modify | "`/src/generated/` - auto-generated code" |
| **Fix Preferences** | Guide the agent's approach to common issues | "Prefer updating ESLint rules over adding disable comments" |
| **Predefined Fixes** | Specify deterministic solutions for known failures | "For lint failures, always try running `nx lint --fix` first" |
| **Context** | Reference other documentation the agent should read | "See ARCHITECTURE.md for module boundaries" |
```markdown
## Predefined Fixes
### Using CLAUDE.md
- For lint failures, always try running `nx lint --fix` first
- Format failures should use `nx format:write`
```
If your repository already has a `CLAUDE.md` file at the root, the Self-Healing CI agent will read it for additional context. When both files exist:
- **SELF_HEALING.md takes precedence** for any conflicting instructions
- Both files are read, so general context in `CLAUDE.md` is still available
- CI-specific instructions should go in `SELF_HEALING.md`
This allows teams to maintain `CLAUDE.md` for local development workflows while using `SELF_HEALING.md` for CI-specific behavior.
### Viewing Configuration Status
After a CI run, navigate to the pipeline execution in Nx Cloud and check the **Configurations** tab to see whether `SELF_HEALING.md` was detected and applied.
## Receiving Fix Notifications
@@ -1,180 +1,82 @@
---
title: 'Enhance Your LLM'
description: 'Learn how Nx enhances your AI assistant by providing rich workspace metadata, architectural insights, and project relationships to make your LLM smarter and more context-aware.'
title: 'Enhance Your AI Coding Agent'
description: 'Learn how Nx enhances your AI assistant by providing rich workspace metadata, architectural insights, and CI integration for autonomous workflows.'
sidebar:
order: 3
filter: 'type:Features'
---
{% youtube src="https://youtu.be/dRQq_B1HSLA" title="We Just Shipped the Monorepo MCP for Copilot" /%}
AI agents are moving beyond autocomplete. They can now operate independently across projects. But most setups hit a wall: agents lack workspace context (seeing files, not architecture), generate inconsistent code, and have a hard time to interact with CI.
Monorepos [provide an ideal foundation for AI-powered development](https://nx.dev/blog/nx-and-ai-why-they-work-together), enabling cross-project reasoning and code generation. However, without proper context, **LLMs struggle to understand your workspace architecture**, seeing only individual files rather than the complete picture.
Nx monorepos solve this by enabling cross-project reasoning and by providing the structured metadata and CI integration that agents need to work autonomously:
Nx transforms your AI assistant by providing rich workspace metadata that enables it to:
- Deep **workspace architecture** understanding and project relationships
- **Code generators** for fast, predictable scaffolding
- **CI pipeline integration** to fix failures autonomously
- The ability to **iterate until CI is green** without human intervention
- Understand your **workspace architecture** and project relationships
- Identify **project owners** and team responsibilities
- Access **Nx documentation** for accurate guidance
- Leverage **code generators** for consistent scaffolding
- Connect to your **CI pipeline** to help fix failures
## Setup
The goal is to transform your AI assistant from a generic code helper into an architecturally-aware collaborator that understands your specific workspace structure and can make intelligent, context-aware decisions.
## How Nx MCP Enhances Your LLM
The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) is an open standard that enables AI models to interact with your development environment through a standardized interface. Nx implements an MCP server via the [Nx Console](/docs/getting-started/editor-setup) that exposes workspace metadata to compatible AI assistants like GitHub Copilot, Claude, and others.
With the Nx MCP server, your AI assistant gains a "map" of your entire system being able to go from just reasoning at the file level to seeing the higher-level picture. This allows the LLM to move between different abstraction levels - from high-level architecture down to specific implementation details:
![Different abstraction levels](../../../assets/features/nx-ai-abstraction-levels.avif)
The Nx MCP server exposes tools for workspace analysis, code generation, documentation lookup, and CI/CD analytics. For a complete list of available tools and their descriptions, see the [Nx MCP Server Reference](/docs/reference/nx-mcp#available-tools).
## Setting Up Nx MCP
To configure Nx for AI agents and AI-Assistants, run the following command:
To configure your Nx workspace for AI agents, run:
```shell
npx nx configure-ai-agents
```
This configures Nx Console which automatically configures and serves the Nx MCP server for you if you're using VSCode or Cursor. It also sets up the corresponding AI agent configuration files (e.g. `CLAUDE.md`, `AGENTS.md`,...).
This sets up:
### IDE Setup
- **Agent configuration files**: `CLAUDE.md`, `AGENTS.md` with workspace-specific guidelines
- **Agent skills**: Specialized capabilities for monorepo workflows, including CI monitoring and self-healing integration
- **Nx MCP server**: Provides tools that combine local workspace metadata with CI context, enabling seamless local-to-CI workflows that unlock true agent autonomy
For VS Code, Cursor, and JetBrains IDE users:
## What This Enables
1. Install [Nx Console](/docs/getting-started/editor-setup) from the marketplace
2. You'll receive a notification to "Improve Copilot/AI agent with Nx-specific context"
3. Click "Yes" to configure the MCP server
### Self-Healing CI Integration
![VS Code showing the Nx MCP installation prompt](../../../assets/features/copilot-mcp-install.avif)
Nx Cloud provides AI-powered [Self-Healing CI](/docs/features/ci-features/self-healing-ci) that analyzes failed runs and proposes verified fixes. With `configure-ai-agents`, your local agent connects to this CI counterpart via skills and the Nx MCP, gaining full context about run information, failures, and suggested fixes.
If you miss the notification, run the `nx.configureMcpServer` (`Nx: Setup MCP Server` in JetBrains) command from the command palette (Cursor: `Ctrl/Cmd + Shift + P`, JetBrains IDEs: `Ctrl/Cmd + Shift + A`).
### Other MCP-Compatible Clients
For other MCP-compatible clients like Claude Desktop, Claude Code, or Warp, you can configure the Nx MCP server manually. See the [Nx MCP Server Reference](/docs/reference/nx-mcp#client-specific-setup) for detailed setup instructions for each client.
Quick example for Claude Code:
```shell
claude mcp add nx-mcp npx nx-mcp@latest
```
## Powerful Use Cases
### Understanding Your Workspace Architecture
{% youtube src="https://youtu.be/RNilYmJJzdk" title="Nx Just Made Your LLM Way Smarter" /%}
Ask your AI assistant about your workspace structure and get detailed, accurate responses about projects, their types, and relationships:
Your agent can autonomously iterate until CI passes:
```text
What is the structure of this workspace?
How are the projects organized?
Commit this work, create a PR, and monitor CI until it's green.
```
With Nx MCP, your AI assistant can:
The workflow:
- Identify applications and libraries in your workspace
- Understand project categorization through tags
- Recognize technology types (feature, UI, data-access)
- Determine project ownership and team responsibilities
1. Agent pushes changes and creates PR
2. Monitors CI pipeline
3. Receives failure context from Nx Cloud and Self-Healing CI
4. Accepts proposed fix or pulls context locally and manually applies it
5. Repeats until CI is green
![Example of LLM understanding project structure](../../../assets/features/nx-ai-example-project-data.avif)
This reduces context-switching—you review the final PR rather than intervening at each failure.
You can also get informed suggestions about where to implement new functionality:
### Workspace Architecture Understanding
```text
Where should I implement a feature for adding products to cart?
```
Nx exposes the project graph and relevant metadata to AI agents. This helps them move faster and more precisely:
![Example of LLM providing implementation guidance](../../../assets/features/nx-ai-example-data-access-feature.avif)
- Identify all applications and libraries in the workspace
- Understand project relationships and dependencies
- Recognize project types and ownership via tags
- Determine which projects are affected by changes
- Suggest where to implement new functionality based on existing structure
Learn more about workspace architecture understanding in our blog post [Nx Just Made Your LLM Way Smarter](https://nx.dev/blog/nx-just-made-your-llm-smarter).
This architectural awareness is critical for agents operating in large monorepos where understanding project relationships determines the quality of generated code.
### Instant CI Failure Resolution
### Predictable, Fast Code Generation
{% youtube src="https://youtu.be/fPqPh4h8RJg" title="Connect Your Editor, CI and LLMs" /%}
AI-generated code is token-intensive, slow, and not guaranteed to align with patterns in other projects. Nx generators solve this by providing predictable scaffolding that agents can invoke and then adapt.
When a CI build fails, Nx Console can notify you directly in your editor:
Your AI agent can:
![Nx Console shows the notification of the CI failure](../../../assets/features/ci-notification.avif)
1. Find generators from [Nx plugins](/docs/plugin-registry) or custom [local workspace generators](/docs/extending-nx/local-generators)
2. Run the generator with correct options
3. Make small adjustments based on the specific situation
Your AI assistant can then:
This approach is faster, produces consistent code across projects, and reduces hallucinations.
1. Access detailed information from Nx Cloud about the failed build
2. Analyze your git history to understand what changed in your PR
3. Understand the error context and affected files
4. Help implement the fix right in your editor
## Learn More
This integration dramatically improves the development velocity because you get immediately notified when an error occurs, you don't even have to leave your editor to understand what broke, and the LLM can help you implement or suggest a possible fix.
Learn more about CI integration in our blog post [Save Time: Connecting Your Editor, CI and LLMs](https://nx.dev/blog/nx-editor-ci-llm-integration).
### Smart Code Generation with AI-Enhanced Generators
{% youtube src="https://youtu.be/PXNjedYhZDs" title="Enhancing Nx Generators with AI" /%}
Nx generators provide predictable code scaffolding, while AI adds intelligence and contextual understanding. Instead of having the AI generate everything from scratch, you get the best of both worlds:
```text
Create a new React library into the packages/orders/feat-cancel-orders folder
and call the library with the same name of the folder structure. Afterwards,
also connect it to the main shop application.
```
Your AI assistant will:
1. Identify the appropriate generator and its parameters
2. Open the Nx Console Generate UI with preset values
3. Let you review and customize the options
4. Execute the generator and help integrate the new code with your existing projects
![LLM invoking the Nx generate UI](../../../assets/features/llm-nx-generate-ui.avif)
This approach ensures consistent code that follows your organization's best practices while still being tailored to your specific needs. Learn more about AI-enhanced generators in our blog post [Enhancing Nx Generators with AI](https://nx.dev/blog/nx-generators-ai-integration).
### Documentation-Aware Configuration
{% youtube src="https://youtu.be/V2W94Sq_v6A?si=aBA-eppEw0fHrh5O&t=388" title="Making Cursor Smarter with an MCP Server" /%}
Get accurate guidance on Nx configuration without worrying about hallucinations or outdated information:
```text
Can you configure Nx release for the packages of this workspace?
Update nx.json with the necessary configuration using conventional commits
as the versioning strategy.
```
The AI assistant will:
1. Query the Nx docs for the latest information on release configuration
2. Understand your workspace structure to identify packages
3. Generate the correct configuration based on your specific needs
4. Apply the changes to your nx.json file
Learn more about documentation-aware configuration in our blog post [Making Cursor Smarter with an MCP Server For Nx Monorepos](https://nx.dev/blog/nx-made-cursor-smarter).
### Cross-Project Dependency Analysis
{% youtube src="https://youtu.be/dRQq_B1HSLA?si=lhHsjRvwgijC1IL8&t=186" title="Nx MCP Now Available for VS Code Copilot" /%}
Understand the impact of changes across your monorepo with questions like:
```text
If I change the public API of feat-product-detail, which other projects
might be affected by that change?
```
Your AI assistant can:
- Analyze the project graph to identify direct and indirect dependencies
- Visualize affected projects using the `nx_visualize_graph` tool
- Suggest strategies for refactoring that minimize impact
- Identify which teams would need to be consulted for major changes
This architectural awareness is particularly powerful in larger monorepos where understanding project relationships is crucial for making informed development decisions.
Learn more about dependency analysis in our blog post [Nx MCP Now Available for VS Code Copilot](https://nx.dev/blog/nx-mcp-vscode-copilot).
- [Autonomous AI Agents at Scale](https://nx.dev/blog/ai-agents-and-continuity): Infrastructure requirements for AI agent workflows
- [Why Nx and AI Work So Well Together](https://nx.dev/blog/nx-and-ai-why-they-work-together): The foundation for AI-powered development
- [Nx MCP Server Reference](/docs/reference/nx-mcp): Complete tool reference and setup instructions
@@ -1,6 +1,6 @@
---
title: 'Building and Testing Angular Apps in Nx'
description: In this tutorial you'll create a frontend-focused workspace with Nx.
description: In this tutorial you'll create a frontend-focused monorepo with Nx.
sidebar:
label: 'Angular Monorepo'
filter: 'type:Guides'
@@ -106,7 +106,7 @@ Root project 'gradle-tutorial'
## Add Nx
Nx is a build system with built in tooling and advanced CI capabilities. It helps you maintain and scale monorepos,
Nx is a monorepo platform with built in tooling and advanced CI capabilities. It helps you maintain and scale monorepos,
both locally and on CI. We will explore the features of Nx in this tutorial by adding it to the Gradle workspace above.
To add Nx, run
@@ -2,7 +2,7 @@
title: 'Building and Testing React Apps in Nx'
sidebar:
label: 'React Monorepo'
description: In this tutorial you'll create a frontend-focused workspace with Nx.
description: In this tutorial you'll create a frontend-focused monorepo with Nx.
filter: 'type:Guides'
---
@@ -7,7 +7,7 @@ sidebar:
filter: 'type:Features'
---
Nx provides deep integration with AI coding assistants through the **Nx Model Context Protocol (MCP) server**, giving your AI assistant comprehensive understanding of your monorepo structure, running processes, and development workflows. When you create a new Nx workspace, it includes AI agent configuration files (`CLAUDE.md` and `AGENTS.md`) that provide guidelines for working with Nx and the Nx MCP server.
This guide shows how to configure your Nx workspace for AI coding assistants. The setup gives your agent workspace context and CI integration, making it smarter when working in an Nx monorepo and more autonomous when iterating on CI failures.
## Configure Nx AI Integration
@@ -18,48 +18,33 @@ title="Set Up AI Agents in Nx"
### Automatic AI Setup
To automatically configure your Nx workspace to work best with AI agents and assistants, run the following command:
To automatically configure your Nx monorepo to work best with AI agents and assistants, run the following command:
```shell
npx nx configure-ai-agents
```
This will prompt you for which AI agents/assistants to configure and make sure you are properly set up with both the [Nx MCP](/docs/features/enhance-ai) as well as corresponding AI agent configuration files (e.g. `AGENTS.md`, `CLAUDE.md` etc).
This will prompt you for which AI agents/assistants to configure and set up the [Nx MCP server](/docs/features/enhance-ai), AI agent configuration files (`AGENTS.md`, `CLAUDE.md`, etc.), and agent skills. For Claude Code, skills are installed via a plugin; for other agents, they're copied into your workspace.
Watch [our Youtube video](https://youtu.be/8gdvIz2r_QM) for a full walkthrough.
### Manual AI Setup
You can of course manually configure the Nx MCP for MCP-compatible clients using the following configuration:
```json
// mcp.json
{
"servers": {
"nx-mcp": {
"command": "npx",
"args": ["nx-mcp@latest"]
}
}
}
```
For Claude Code:
Alternatively, you can install just the skills via:
```shell
claude mcp add nx-mcp npx nx-mcp@latest
npx skills add nrwl/nx-ai-agents-config
```
This copies the skills into your workspace but does not install the Claude Code plugin.
Watch [our Youtube video](https://youtu.be/8gdvIz2r_QM) for a full walkthrough.
### What This Integration Enables
The Nx AI integration provides your coding assistant with powerful capabilities:
- **[Workspace Structure Understanding](https://nx.dev/blog/nx-mcp-vscode-copilot)** - Deep architectural awareness of your monorepo, project relationships, and dependencies
- **[Real-time Terminal Integration](https://nx.dev/blog/nx-terminal-integration-ai)** - AI can read your terminal output, running processes, and error messages without copy-pasting
- **[CI Pipeline Context](https://nx.dev/blog/nx-editor-ci-llm-integration)** - Access to build failures, test results, and deployment status from your CI/CD processes
- **[Enhanced Code Generation](https://nx.dev/blog/nx-generators-ai-integration)** - AI-powered generator suggestions and custom scaffolding with intelligent defaults
- **Cross-project Impact Analysis** - Understanding the implications of changes across your entire monorepo
- **Autonomous Error Debugging** - AI independently accesses context to help fix development issues
- **Workspace Understanding** - Graph-aware exploration of project dependencies and relationships. AI gets structured data instead of grepping through files.
- **[Real-time Terminal Integration](https://nx.dev/blog/nx-terminal-integration-ai)** - AI can read your terminal output, running processes, and error messages without copy-pasting.
- **Reliable Code Generation** - AI invokes Nx generators for predictable scaffolding, then adapts the result to your workspace. Faster, standardized, fewer hallucinations.
- **Autonomous CI Workflows** - The CI monitor skill bridges your local agent with Nx Cloud. Push, monitor, get failures, fix, repeat until CI is green. Enables "Ralph Wiggum loop" patterns where you review the final PR, not every CI hiccup.
- **Cross-project Impact Analysis** - Understanding the implications of changes across your entire monorepo.
## Configure CI to Leverage AI Capabilities
@@ -69,4 +54,5 @@ Read more on the [Self-Healing CI](/docs/features/ci-features/self-healing-ci) d
## Learn More about Nx and AI
Learn more about [why Nx and AI work so well together](https://nx.dev/blog/nx-and-ai-why-they-work-together).
- [Autonomous AI Agents at Scale](https://nx.dev/blog/ai-agents-and-continuity) - Infrastructure requirements for scaling AI agent workflows
- [Why Nx and AI Work So Well Together](https://nx.dev/blog/nx-and-ai-why-they-work-together)
@@ -1,13 +1,13 @@
---
title: What is Nx?
description: 'Nx is an AI-first build platform that connects everything from your editor to CI Helping you deliver fast, without breaking things.'
description: 'Nx is an AI-first monorepo platform that connects everything from your editor to CI. Helping you deliver fast, without breaking things.'
sidebar:
order: 1
label: Introduction
filter: 'type:Features'
---
Nx is a powerful, open source, technology-agnostic build platform designed to efficiently manage codebases of any scale. From small single projects to large enterprise monorepos, Nx provides the platform to **efficiently get from starting a feature in your editor to a green PR**.
Nx is a powerful, open-source, technology-agnostic **monorepo platform** designed to efficiently manage codebases of any scale. From small workspaces to large enterprise monorepos, Nx provides the tools to **efficiently get from starting a feature in your editor to a green PR**.
As teams and codebases grow, productivity bottlenecks multiply: build times increase, CI becomes flaky, and code sharing becomes complex. **Nx reduces friction across your entire development cycle.**
@@ -17,6 +17,12 @@ As teams and codebases grow, productivity bottlenecks multiply: build times incr
Nx is built in a modular fashion, allowing you to adopt as little or as much as you'd like at any moment in your development lifecycle. You can **start with just the core and add additional capabilities incrementally** as your needs grow and complexity increases.
{% callout type="deepdive" title="Can I add Nx to a single-project repo?" %}
Yes, Nx provides value even for single-project repositories. You get fast task caching, intelligent task orchestration, and access to Nx plugins for your specific technology stack. As your project grows into a monorepo, the foundation is already in place.
Nx can also connect multiple repositories into a synthetic monorepo, letting you orchestrate large changes across all connected repos.
{% /callout %}
At the **foundation is Nx Core**, a Rust-based, technology-agnostic task runner. Nx Core creates a knowledge graph of your workspace, understanding project relationships and dependencies. This enables highly optimized and fast task execution regardless of technology stack. It runs `package.json` scripts in [TypeScript monorepos](/docs/technologies/typescript/introduction) or Gradle tasks in [Java projects](/docs/technologies/java/introduction) or [can be extended](/docs/extending-nx/intro) to meet your project's specific needs.
{% callout type="deepdive" title="What do you mean by \"running NPM scripts\"?" %}
@@ -60,7 +66,7 @@ From there, you can gradually enhance your setup by adding features like [task c
{% /callout %}
Nx Core provides everything you need to get started and works perfectly on its own.
**When you're ready for more, the Nx platform offers additional capabilities you can adopt incrementally**.
**When you're ready for more, the Nx monorepo platform offers additional capabilities you can adopt incrementally**.
Extend your setup with [**Nx Cloud**](/docs/getting-started/nx-cloud) for remote caching, distributed task execution, and [**AI-powered self-healing CI**](/docs/features/ci-features/self-healing-ci) that automatically detects, analyzes, and fixes CI failures.
Integrate [**Nx Console**](/docs/getting-started/editor-setup) with your editor for powerful autocomplete, project graph visualization, CI notifications, and an MCP to [make your AI coding assistant smarter](/docs/features/enhance-ai).
Add [**Nx Plugins**](/docs/technologies) for technology-specific automation and DX improvements, or build custom platform capabilities using [Nx Devkit](/docs/extending-nx/intro).
@@ -62,7 +62,7 @@ Choose a preset that matches your technology stack. This gives you a fully confi
You can also choose **an empty workspace preset** (`--preset=ts`) which sets up the bare minimum configuration for TypeScript and Nx. This allows you to add technologies and features incrementally over time as you need them.
## Option 3: Get the complete Nx platform experience
## Option 3: Get the complete Nx monorepo platform experience
[![Nx Cloud onboarding](../../../assets/getting-started/nx-cloud-starting-screen.avif)](https://cloud.nx.app/get-started?utm_source=nx-docs&utm_medium=nx-cloud-onboarding&utm_campaign=start-new-project)
@@ -74,4 +74,4 @@ This means you benefit from intelligent automation right from day one, without h
As your project grows and scales, you'll have access to additional features like **remote caching** to speed up builds across your team, **distributed task execution with Nx Agents** to parallelize work across multiple machines, and **automatic test splitting** to optimize your CI pipeline performance.
[Get started with the complete Nx platform experience →](https://cloud.nx.app/get-started?utm_source=nx-docs&utm_medium=nx-cloud-onboarding&utm_campaign=start-new-project)
[Get started with the complete Nx monorepo platform experience →](https://cloud.nx.app/get-started?utm_source=nx-docs&utm_medium=nx-cloud-onboarding&utm_campaign=start-new-project)
@@ -77,7 +77,7 @@ You can implement your server in any programming language or framework, as long
"openapi": "3.0.0",
"info": {
"title": "Nx custom remote cache specification.",
"description": "Nx is an AI-first build platform that connects everything from your editor to CI. Helping you deliver fast, without breaking things.",
"description": "Nx is an AI-first monorepo platform that connects everything from your editor to CI. Helping you deliver fast, without breaking things.",
"version": "1.0.0"
},
"paths": {
+1 -1
View File
@@ -1,6 +1,6 @@
---
title: Quickstart with Nx
description: Get up and running with Nx in minutes - install Nx, set up your editor, configure AI assistance, and choose your development path.
description: Get up and running with the Nx monorepo platform in minutes - install Nx, set up your editor, configure AI assistance, and choose your development path.
tableOfContents: false
filter: 'type:Guides'
---
+120 -14
View File
@@ -9,22 +9,39 @@ filter: 'type:References'
The Nx MCP server is a [Model Context Protocol](https://modelcontextprotocol.io/introduction) implementation that gives LLMs deep access to your monorepo's structure: project relationships, file mappings, runnable tasks, ownership info, tech stacks, Nx generators, and Nx documentation.
{% aside type="tip" title="Quick Setup" %}
To configure your Nx workspace for AI agents (MCP, skills, and more), run `npx nx configure-ai-agents`. See the [AI Integration guide](/docs/getting-started/ai-setup) for details.
{% /aside %}
For an overview of how Nx enhances AI assistants and powerful use cases, see the [Enhance Your LLM](/docs/features/enhance-ai) guide.
## Installation
There are a few ways to setup the Nx MCP server
There are a few ways to setup the Nx MCP server.
{% aside type="note" title="Top level command" %}
Starting with Nx v21.4.0, you can run the MCP server directly using the `nx mcp` command.
You can still use the standalone `nx-mcp` package if you prefer.
All args are passed through to the underlying MCP server.
{% /aside %}
### via .mcp.json
### via mcp.json
{% tabs syncKey="nx-version" %}
{% tabitem label="Nx >= 21.4" %}
```json
// mcp.json
// .mcp.json
{
"servers": {
"nx-mcp": {
"type": "stdio",
"command": "npx",
"args": ["nx", "mcp"]
}
}
}
```
{% /tabitem %}
{% tabitem label="Nx < 21.4" %}
```json
// .mcp.json
{
"servers": {
"nx-mcp": {
@@ -36,26 +53,55 @@ All args are passed through to the underlying MCP server.
}
```
{% /tabitem %}
{% /tabs %}
### Via Nx Console Extension
If you're using Cursor, VS Code, or JetBrains IDEs, install the [Nx Console extension](/docs/getting-started/editor-setup) which automatically manages the MCP server for you.
If you're using Cursor or VS Code, install the [Nx Console extension](/docs/getting-started/editor-setup) which automatically manages the MCP server for you.
## Client-Specific Setup
> If your preferred client is not listed, then configure your client to run the `npx nx-mcp` command to start up the Nx MCP server.
> If your preferred client is not listed, configure your client to run `npx nx mcp` (Nx >= 21.4) or `npx nx-mcp@latest` (older versions) to start the Nx MCP server.
### Claude Code
{% tabs syncKey="nx-version" %}
{% tabitem label="Nx >= 21.4" %}
```shell
claude mcp add nx-mcp npx nx mcp
```
{% /tabitem %}
{% tabitem label="Nx < 21.4" %}
```shell
claude mcp add nx-mcp npx nx-mcp@latest
```
{% /tabitem %}
{% /tabs %}
### VS Code
{% tabs syncKey="nx-version" %}
{% tabitem label="Nx >= 21.4" %}
```shell
code --add-mcp '{"name":"nx-mcp","command":"npx","args":["nx","mcp"]}'
```
{% /tabitem %}
{% tabitem label="Nx < 21.4" %}
```shell
code --add-mcp '{"name":"nx-mcp","command":"npx","args":["nx-mcp"]}'
```
{% /tabitem %}
{% /tabs %}
Alternatively, configure it in your VS Code settings or use the Nx Console extension for automatic setup.
### Cursor
@@ -76,7 +122,7 @@ If you miss the notification, run the `Nx: Setup MCP Server` command from the co
## Command-Line Options
The `nx-mcp` command accepts the following options:
The `nx mcp` command (or `npx nx-mcp` for older versions) accepts the following options:
| Option | Alias | Description |
| ----------------------- | ----- | ----------------------------------------------------------------------------------------------------------------- |
@@ -92,16 +138,49 @@ The `nx-mcp` command accepts the following options:
If you want to host the server instead of communicating via `stdio`, use the `--transport` and `--port` flags:
{% tabs syncKey="nx-version" %}
{% tabitem label="Nx >= 21.4" %}
```shell
npx nx mcp --transport sse --port 9921
```
{% /tabitem %}
{% tabitem label="Nx < 21.4" %}
```shell
npx nx-mcp@latest --transport sse --port 9921
```
{% /tabitem %}
{% /tabs %}
The HTTP transport supports multiple concurrent connections, allowing different clients to connect simultaneously with independent sessions.
### Filtering Tools
You can limit which tools are available using the `--tools` option with glob patterns:
{% tabs syncKey="nx-version" %}
{% tabitem label="Nx >= 21.4" %}
```shell
# Enable all tools (default)
npx nx mcp --tools "*"
# Disable specific tools
npx nx mcp --tools "*" "!nx_docs"
# Enable only cloud analytics tools
npx nx mcp --tools "cloud_*"
# Enable workspace tools only
npx nx mcp --tools "nx_workspace" "nx_project_details" "nx_docs"
```
{% /tabitem %}
{% tabitem label="Nx < 21.4" %}
```shell
# Enable all tools (default)
npx nx-mcp@latest --tools "*"
@@ -116,6 +195,9 @@ npx nx-mcp@latest --tools "cloud_*"
npx nx-mcp@latest --tools "nx_workspace" "nx_project_details" "nx_docs"
```
{% /tabitem %}
{% /tabs %}
This is useful when you want to restrict the LLM's capabilities or reduce noise in the tool list.
## Available Tools
@@ -151,9 +233,18 @@ This is useful when you want to restrict the LLM's capabilities or reduce noise
| -------------------- | -------------------------------------------------------------------------------------------------------- |
| `nx_visualize_graph` | Opens interactive project or task graph visualizations (requires a running IDE instance with Nx Console) |
### Nx Cloud CI Tools
These tools enable AI agents to interact with CI pipelines and self-healing capabilities:
| Tool | Description |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ci_information` | Retrieves CI pipeline execution information from Nx Cloud for the current branch. Returns pipeline status, failed tasks, and self-healing status. |
| `update_self_healing_fix` | Apply or reject a self-healing CI fix from Nx Cloud. Records the decision on the suggested fix. |
### Nx Cloud Analytics Tools
These tools are only available when connected to an Nx Cloud-enabled workspace. They provide analytics and insights into your CI/CD data:
These tools provide analytics and insights into your CI/CD data:
| Tool | Description |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------ |
@@ -164,8 +255,8 @@ These tools are only available when connected to an Nx Cloud-enabled workspace.
| `cloud_analytics_tasks_search` | Analyzes aggregated task performance statistics including success rates and cache hit rates |
| `cloud_analytics_task_executions_search` | Analyzes individual task execution data to investigate performance trends over time |
{% aside type="note" title="Limited Tools Without Workspace" %}
When no workspace path is specified, only the `nx_docs` and `nx_available_plugins` tools will be available.
{% aside type="note" title="Tool Availability" %}
When no workspace path is specified, only the `nx_docs` and `nx_available_plugins` tools will be available. Nx Cloud tools require a workspace connected to Nx Cloud.
{% /aside %}
## Available Resources
@@ -246,3 +337,18 @@ Using the Nx Cloud tools, the AI can:
1. Access detailed information about the failed build
2. Retrieve terminal output from failed tasks
3. Understand what changed and suggest fixes
### Autonomous CI Monitoring
With the `ci_information` and `update_self_healing_fix` tools, AI agents can monitor CI pipelines and interact with self-healing. For a ready-to-use implementation, see the [CI monitoring skill](/docs/features/enhance-ai#self-healing-ci-integration) in Claude Code:
```text
Monitor CI and fix any failures
```
The AI agent can:
1. Poll CI pipeline status via `ci_information`
2. Receive failure context and self-healing suggestions
3. Apply verified fixes via `update_self_healing_fix`
4. Continue iterating until CI passes
@@ -21,7 +21,7 @@ All .NET build targets generated by the plugin include a `dependsOn` configurati
"targets": {
"build": {
"command": "dotnet build",
"dependsOn": ["restore", "^build"]
"dependsOn": ["^build"]
}
}
}
@@ -29,6 +29,10 @@ All .NET build targets generated by the plugin include a `dependsOn` configurati
The `^build` dependency tells Nx to build all upstream dependencies before building the current project. This replaces the .NET CLI's built-in dependency resolution with Nx's more sophisticated task orchestration.
{% aside type="note" title="Why restore is not in dependsOn" %}
The `restore` target is not included in the `dependsOn` array because Nx requires NuGet package restoration to be completed before running any tasks. Running `restore` through Nx's task graph would create a circular dependency since Nx needs restoration completed to analyze project structure and dependencies.
{% /aside %}
## Avoiding duplicate builds
Consider a workspace with the following structure:
@@ -6,7 +6,7 @@ sidebar:
filter: 'type:Guides'
---
Nx is a general-purpose build system and a general-purpose CLI. It works with JavaScript, TypeScript, Java, C#, Go, etc.. The core plugins Nx comes with do work best with JavaScript or TypeScript.
Nx is a general-purpose monorepo platform and CLI. It works with JavaScript, TypeScript, Java, C#, Go, etc.. The core plugins Nx comes with do work best with JavaScript or TypeScript.
TypeScript is a great choice for many teams, but not for everyone. If you want to use Nx with JavaScript, simply pass `--js` to all generate commands, as follows:
@@ -23,10 +23,23 @@ Problem: A task is being executed when you expect it to be replayed from the cac
{% youtube src="https://youtu.be/zJmhW1iIxpc" title="Debug remote cache misses with Nx Cloud" /%}
- Make sure your repo is [connected to Nx Cloud](/docs/features/ci-features/remote-cache)
- Click on the run details link that is printed in the terminal after you run a task
- Click on the task with cache miss that you want to investigate
- Click the "Compare to similar tasks" link in the top right corner of the task details
- Select one of the similar tasks from the list in the "Compare to" section (or paste a URL of another run)
- Nx Cloud will compare the input details of both tasks and will highlight all the differences
- Note: Nx Cloud cannot access your source code, so it can only tell you which inputs are different based on their saved content hash, but not the exact git diff of the source code.
1. Make sure your repo is [connected to Nx Cloud](/docs/features/ci-features/remote-cache)
2. Click on the run details link that is printed in the terminal after you run a task. You can search for and filter tasks by cache status to find the task with the cache miss you want to investigate.
![Run details page showing task list filtered by cache miss status](../../../assets/guides/nx-cloud/run-details.jpg)
3. Click on the task with the cache miss to open the task details panel, then click the "Compare to similar tasks" button.
![Task details panel showing the Compare to similar tasks button](../../../assets/guides/nx-cloud/run-details-task-details.jpg)
4. Select one of the similar tasks from the list in the "Compared to" section, or paste a run URL to compare against a specific run.
![Compare tasks view showing the run selection panel](../../../assets/guides/nx-cloud/compare-tasks-select-run.jpg)
5. Nx Cloud will compare the hash inputs of both tasks and highlight all the differences, making it easy to identify which inputs changed.
![Compare tasks diff view showing hash input differences between two task runs](../../../assets/guides/nx-cloud/compare-tasks-diff.jpg)
{% aside type="note" title="Note" %}
Nx Cloud cannot access your source code, so it can only tell you which inputs are different based on their saved content hash, but not the exact git diff of the source code.
{% /aside %}
+183
View File
@@ -0,0 +1,183 @@
import type { APIRoute } from 'astro';
import { getCollection } from 'astro:content';
import { readFileSync, existsSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const contentDocsDir = join(__dirname, '../content/docs');
interface DocEntry {
slug: string;
title: string;
content: string;
section: string;
}
/**
* Strips YAML frontmatter from markdown content.
* Frontmatter is delimited by --- at the start of the file.
*/
function stripFrontmatter(content: string): string {
// Match frontmatter: starts with ---, ends with ---
const frontmatterRegex = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/;
return content.replace(frontmatterRegex, '');
}
/**
* Generates llms-full.txt - a concatenated file of all Nx documentation.
* This file contains the full content of all docs for LLM consumption.
* See: https://llmstxt.org/
*/
export const GET: APIRoute = async ({ site }) => {
const siteUrl = site?.origin ?? 'https://nx.dev';
const entries: DocEntry[] = [];
// Preferred section order (same as llms.txt)
const sectionOrder = [
'quickstart',
'getting-started',
'concepts',
'features',
'guides',
'extending-nx',
'technologies',
'reference',
'enterprise',
'troubleshooting',
];
// Section display names
const sectionNames: Record<string, string> = {
'getting-started': 'Getting Started',
concepts: 'Core Concepts',
features: 'Features',
guides: 'Guides',
'extending-nx': 'Extending Nx',
technologies: 'Technologies',
reference: 'Reference',
enterprise: 'Enterprise',
troubleshooting: 'Troubleshooting',
quickstart: 'Quickstart',
};
// Get all docs from the content collection (static .mdoc/.mdx files)
const docs = await getCollection('docs');
for (const doc of docs) {
const slug = doc.id;
const title = doc.data.title || slug.split('/').pop() || slug;
const section = slug.split('/')[0] || 'other';
// Try to get file path from doc.filePath, or try common extensions
let filePath = doc.filePath;
if (!filePath) {
const extensions = ['.mdoc', '.mdx', '.md'];
for (const ext of extensions) {
const testPath = join(contentDocsDir, doc.id + ext);
if (existsSync(testPath)) {
filePath = testPath;
break;
}
}
}
// Skip if no file path found or not readable
if (!filePath || !existsSync(filePath)) {
continue;
}
try {
const rawContent = readFileSync(filePath, 'utf-8');
const content = stripFrontmatter(rawContent);
entries.push({ slug, title, content, section });
} catch {
// Skip files that can't be read
continue;
}
}
// Get plugin docs (executors, generators for each plugin)
try {
const pluginDocs = await getCollection('plugin-docs');
for (const doc of pluginDocs) {
if (doc.data.slug && doc.body) {
const slug = doc.data.slug;
const section = slug.split('/')[0] || 'technologies';
entries.push({
slug,
title: doc.data.title || slug,
content: stripFrontmatter(doc.body),
section,
});
}
}
} catch {
// plugin-docs collection might not exist
}
// Note: Excluding nx-reference-packages (devkit API docs) for now
// to keep the file size under 2MB. These are highly technical
// and can be accessed individually via .md endpoints.
// Sort entries by section order, then by slug within section
entries.sort((a, b) => {
const sectionIndexA = sectionOrder.indexOf(a.section);
const sectionIndexB = sectionOrder.indexOf(b.section);
const orderA = sectionIndexA === -1 ? 999 : sectionIndexA;
const orderB = sectionIndexB === -1 ? 999 : sectionIndexB;
if (orderA !== orderB) {
return orderA - orderB;
}
return a.slug.localeCompare(b.slug);
});
// Generate the llms-full.txt content
const lines: string[] = [
'# Nx Documentation',
'',
'> Complete Nx documentation compiled into a single file for LLM consumption.',
'',
'Nx is a powerful, open-source, technology-agnostic monorepo platform designed to efficiently manage codebases of any scale. From small workspaces to large enterprise monorepos, Nx provides intelligent task execution, caching, and CI optimization.',
'',
`This file was generated from ${entries.length} documentation pages.`,
`Individual pages are available at: ${siteUrl}/docs/{slug}.md`,
'',
];
// Track current section for headers
let currentSection = '';
for (const entry of entries) {
// Add section header when section changes
if (entry.section !== currentSection) {
currentSection = entry.section;
const sectionName = sectionNames[currentSection] || currentSection;
lines.push('');
lines.push(`# ${sectionName}`);
lines.push('');
}
// URL encode spaces in the slug
const encodedSlug = entry.slug.replace(/ /g, '%20');
const sourceUrl = `${siteUrl}/docs/${encodedSlug}.md`;
lines.push('---');
lines.push(`<!-- source: ${sourceUrl} -->`);
lines.push(`## ${entry.title}`);
lines.push('');
lines.push(entry.content.trim());
lines.push('');
}
const content = lines.join('\n');
return new Response(content, {
status: 200,
headers: {
'Content-Type': 'text/markdown; charset=utf-8',
'Cache-Control': 'public, max-age=3600',
},
});
};
+2 -2
View File
@@ -79,9 +79,9 @@ export const GET: APIRoute = async ({ site }) => {
const lines: string[] = [
'# Nx',
'',
'> Nx is an AI-first build platform that connects your editor to CI. It helps you deliver fast without breaking things by optimizing builds, scaling CI, and fixing failed PRs.',
'> Nx is an AI-first monorepo platform that connects your editor to CI. It helps you deliver fast without breaking things by optimizing builds, scaling CI, and fixing failed PRs.',
'',
'Nx is a powerful, open source, technology-agnostic build platform designed to efficiently manage codebases of any scale. From small single projects to large enterprise monorepos, Nx provides intelligent task execution, caching, and CI optimization.',
'Nx is a powerful, open-source, technology-agnostic monorepo platform designed to efficiently manage codebases of any scale. From small workspaces to large enterprise monorepos, Nx provides intelligent task execution, caching, and CI optimization.',
'',
'Note: All documentation pages are available as raw Markdown by appending `.md` to the URL.',
`For example: ${siteUrl}/docs/getting-started/intro.md returns the raw Markdown content.`,
@@ -0,0 +1,33 @@
/** SVG path data for the chain-link icon used in anchor links (matches Starlight heading links). */
export const LINK_ICON_PATH =
'm12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z';
export const TABLE_HEADERS_TO_MATCH = [
'option',
'options',
'properties',
'property',
];
/**
* Converts an option name to an anchor slug.
*
* Strips leading `--` or `-` prefixes, takes only the first (canonical) name
* if aliases are present (comma-separated), lowercases, and replaces
* non-alphanumeric characters with `-`.
*
* Examples:
* `--distribute-on` → `distribute-on`
* `nxCloudUrl` → `nxcloudurl`
* `--output, -o` → `output`
*/
export function optionSlug(raw: string): string {
// Take only the first name if comma-separated aliases exist
let name = raw.split(',')[0].trim();
// Strip leading dashes
name = name.replace(/^-{1,2}/, '');
// Lowercase and replace non-alphanumeric with hyphens
return name
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
}
@@ -0,0 +1,143 @@
/**
* Rehype plugin that adds anchor links to option names in documentation tables.
*
* Targets tables whose first header cell contains "Option". For each data row,
* it extracts the option name from the first cell's `<code>` element, generates
* an anchor slug, sets an `id` on the `<tr>`, and wraps the `<code>` in an
* `<a>` link with the Starlight-style anchor icon.
*/
import type { Element, ElementContent, Root, Text } from 'hast';
import {
LINK_ICON_PATH,
optionSlug,
TABLE_HEADERS_TO_MATCH,
} from './option-slug';
function getTextContent(node: ElementContent | Root): string {
if (node.type === 'text') return (node as Text).value;
if ('children' in node) {
return (node.children as ElementContent[]).map(getTextContent).join('');
}
return '';
}
function isElement(node: ElementContent, tag?: string): node is Element {
return node.type === 'element' && (!tag || node.tagName === tag);
}
function findElement(
children: ElementContent[],
tag: string
): Element | undefined {
for (const child of children) {
if (isElement(child)) {
if (child.tagName === tag) return child;
const found = findElement(child.children, tag);
if (found) return found;
}
}
return undefined;
}
function findAllElements(children: ElementContent[], tag: string): Element[] {
const results: Element[] = [];
for (const child of children) {
if (isElement(child, tag)) results.push(child);
if (isElement(child)) {
results.push(...findAllElements(child.children, tag));
}
}
return results;
}
function makeAnchorIcon(): Element {
return {
type: 'element',
tagName: 'span',
properties: { ariaHidden: 'true', className: ['sl-anchor-icon'] },
children: [
{
type: 'element',
tagName: 'svg',
properties: {
width: '16',
height: '16',
viewBox: '0 0 24 24',
fill: 'currentcolor',
},
children: [
{
type: 'element',
tagName: 'path',
properties: { d: LINK_ICON_PATH },
children: [],
},
],
},
],
};
}
function isOptionsTable(table: Element): boolean {
const thead = findElement(table.children as ElementContent[], 'thead');
if (!thead) return false;
const firstTh = findElement(thead.children as ElementContent[], 'th');
if (!firstTh) return false;
const text = getTextContent(firstTh).trim();
return TABLE_HEADERS_TO_MATCH.includes(text.toLowerCase());
}
function processOptionsTable(table: Element): void {
const tbody = findElement(table.children as ElementContent[], 'tbody');
if (!tbody) return;
const rows = findAllElements(tbody.children as ElementContent[], 'tr');
for (const row of rows) {
const cells = row.children.filter((c) => isElement(c, 'td')) as Element[];
if (cells.length === 0) continue;
const firstCell = cells[0];
const codeEl = findElement(firstCell.children as ElementContent[], 'code');
if (!codeEl) continue;
const optionText = getTextContent(codeEl).trim();
if (!optionText) continue;
const slug = optionSlug(optionText);
if (!slug) continue;
// Set id on the row for anchor targeting
row.properties = row.properties || {};
row.properties.id = slug;
// Find the index of the code element in the cell's children
const codeIndex = firstCell.children.indexOf(codeEl);
if (codeIndex === -1) continue;
// Wrap the <code> element in an <a> link
const anchorLink: Element = {
type: 'element',
tagName: 'a',
properties: {
href: `#${slug}`,
className: ['sl-option-link'],
},
children: [codeEl, makeAnchorIcon()],
};
firstCell.children[codeIndex] = anchorLink;
}
}
export default function rehypeTableOptionLinks() {
return (tree: Root) => {
const tables = findAllElements(tree.children as ElementContent[], 'table');
for (const table of tables) {
if (isOptionsTable(table)) {
processOptionsTable(table);
}
}
};
}
+28
View File
@@ -408,6 +408,13 @@ h6:hover .anchor-link {
border-inline-start: none;
}
@media (min-width: 84rem) {
.right-sidebar-panel {
display: flex;
justify-content: flex-end;
}
}
.content-panel + .content-panel {
border-top: 0;
}
@@ -424,3 +431,24 @@ table code {
font-size: var(--text-sm);
align-items: end; /* Fix tab alignment */
}
/* Table option anchor links */
.sl-markdown-content td .sl-option-link {
color: inherit;
text-decoration: none;
}
.sl-markdown-content td .sl-option-link .sl-anchor-icon {
opacity: 0;
margin-inline-start: 0.25em;
display: inline-flex;
vertical-align: middle;
}
.sl-markdown-content td .sl-option-link .sl-anchor-icon > svg {
width: 0.75em;
height: 0.75em;
}
@media (hover: hover) {
.sl-markdown-content tr:hover .sl-option-link .sl-anchor-icon {
opacity: 1;
}
}
@@ -0,0 +1,114 @@
import Markdoc from '@markdoc/markdoc';
import {
LINK_ICON_PATH,
optionSlug,
TABLE_HEADERS_TO_MATCH,
} from '../plugins/utils/option-slug';
type MarkdocTag = InstanceType<typeof Markdoc.Tag>;
/**
* Check if a value is a Markdoc Tag via `$$mdtype`
* instead of `instanceof` to avoid the issue where the
* `@markdoc/markdoc` instance loaded by this file differs from the one
* loaded by `@astrojs/markdoc` at runtime.
* otherwise the markdoc transform always noops for zero matches
*/
function isTag(value: unknown): value is MarkdocTag {
return (
typeof value === 'object' &&
value !== null &&
(value as Record<string, unknown>).$$mdtype === 'Tag'
);
}
function getTagText(tag: unknown): string {
if (typeof tag === 'string') return tag;
if (isTag(tag)) {
return tag.children.map(getTagText).join('');
}
return '';
}
function findChildTag(
parent: MarkdocTag,
name: string
): MarkdocTag | undefined {
return parent.children.find(
(c): c is MarkdocTag => isTag(c) && c.name === name
);
}
function makeAnchorIcon(): MarkdocTag {
return new Markdoc.Tag(
'span',
{ 'aria-hidden': 'true', class: 'sl-anchor-icon' },
[
new Markdoc.Tag(
'svg',
{
width: '16',
height: '16',
viewBox: '0 0 24 24',
fill: 'currentcolor',
},
[new Markdoc.Tag('path', { d: LINK_ICON_PATH }, [])]
),
]
);
}
/**
* Transform a Markdoc table node, adding anchor links to option rows.
* Non-option/property list tables pass through unmodified.
*/
export function transformOptionsTable(
node: Parameters<
NonNullable<import('@markdoc/markdoc').Schema['transform']>
>[0],
config: Parameters<
NonNullable<import('@markdoc/markdoc').Schema['transform']>
>[1]
): MarkdocTag {
const children = node.transformChildren(config);
const table = new Markdoc.Tag(
'table',
node.transformAttributes(config),
children
);
const thead = findChildTag(table, 'thead');
const tbody = findChildTag(table, 'tbody');
if (!thead || !tbody) return table;
const headerRow = findChildTag(thead, 'tr');
const firstTh = headerRow ? findChildTag(headerRow, 'th') : undefined;
const headerText = firstTh ? getTagText(firstTh).trim() : '';
// make sure the links only show up for tables that 'options'
if (!TABLE_HEADERS_TO_MATCH.includes(headerText.toLowerCase())) return table;
for (const row of tbody.children) {
if (!isTag(row) || row.name !== 'tr') continue;
const firstTd = findChildTag(row, 'td');
if (!firstTd) continue;
const codeTag = findChildTag(firstTd, 'code');
if (!codeTag) continue;
const slug = optionSlug(getTagText(codeTag).trim());
if (!slug) continue;
row.attributes.id = slug;
const codeIndex = firstTd.children.indexOf(codeTag);
firstTd.children[codeIndex] = new Markdoc.Tag(
'a',
{ href: `#${slug}`, class: 'sl-option-link' },
[codeTag, makeAnchorIcon()]
);
}
return table;
}
@@ -437,7 +437,7 @@ From my experience, I've often seen teams start with a single application, which
## Q: Isn't Nx just for Angular projects?
This is a common but understandable misconception. Although Nx was heavily inspired by the Angular CLI initially, it is now a completely independent build system and CLI with first-class support for Angular, React, Node, Next.js, TypeScript and more. And with tons of [community plugins](/community) that extend Nx beyond that.
This is a common but understandable misconception. Although Nx was heavily inspired by the Angular CLI initially, it is now a completely independent monorepo platform and CLI with first-class support for Angular, React, Node, Next.js, TypeScript and more. And with tons of [community plugins](/community) that extend Nx beyond that.
## Conclusion
@@ -30,7 +30,7 @@ If you want to check out the final result, here's the corresponding Github repo:
But before we jump right into the topic, what is Nx? And more specifically, what are Nx Plugins?
Nx is an open-source build system that provides tools and techniques to enhance developer productivity. [Check out this 10 min video overview](https://youtu.be/-_4WMl-Fn0w) of Nx if you want to learn more.
Nx is an open-source monorepo platform that provides tools and techniques to enhance developer productivity. [Check out this 10 min video overview](https://youtu.be/-_4WMl-Fn0w) of Nx if you want to learn more.
Our example, in particular, uses Nx as a dev tool for creating a CLI and plugin. Nx plugins are npm packages that provide integrations between Nx and other technologies. You can use Nx without them, but they can provide great value if applied properly. `my-own-react` is the plugin to integrate React and Nx.
+2 -2
View File
@@ -15,7 +15,7 @@ It's been a bit since we launched [Nx 17](/blog/nx-17-release)! In this article,
- [Module Federation Updates](#module-federation-updates)
- [Nx Release Updates](#nx-release-updates)
- [Angular 17 (AND NgRx 17) Support](#angular-17-and-ngrx-17-support)
- [Smart Repos — Fast Builds](#smart-repos-fast-builds)
- [Smart Monorepos — Fast Builds](#smart-repos-fast-builds)
- [New Canary Releases](#new-canary-releases)
- [Upcoming Release Livestream](#upcoming-release-livestream)
- [Automatically Update Nx](#automatically-update-nx)
@@ -139,7 +139,7 @@ nx migrate latest --interactive
- Run 'nx migrate --run-migrations'
```
## Smart Repos — Fast Builds
## Smart Monorepos — Fast Builds
We just gave our Nx homepage a small facelift, including a new tagline, subtagline and illustration to better reflect Nx's mission statement.
+3 -3
View File
@@ -25,7 +25,7 @@ It is that time again: getting flooded by Year of Review blog posts. We did it l
- [Many OSS repos adopt Nx](#many-oss-repos-adopt-nx)
- [Nx Community](#nx-community)
- [New Content & Improved Docs](#new-content-improved-docs)
- [New Tagline: Smart Repos — Fast Builds](#new-tagline-smart-repos-fast-builds)
- [New Tagline: Smart Monorepos — Fast Builds](#new-tagline-smart-repos-fast-builds)
- [Nx Conf](#nx-conf)
- [Looking ahead — 2024](#looking-ahead-2024)
- [Solving CI](#solving-ci)
@@ -330,11 +330,11 @@ You can also browse them in the [nx-recipes](https://github.com/nrwl/nx-recipes)
And obviously, we jumped on the AI train as well. A couple of months ago, we added the [Nx Assistant](/ai-chat). A ChatGPT-powered interface trained in our docs. [Katerina](https://twitter.com/psybercity) wrote about it [on our blog](/blog/nx-docs-ai-assistant). The AI chat allows to interactively ask questions about Nx and will give you relevant answers from our docs (including linking to the sources).
## New Tagline: Smart Repos — Fast Builds
## New Tagline: Smart Monorepos — Fast Builds
Nx stands out for its flexibility, accommodating for both monorepo and non-monorepo project structures. This approach allows users to begin with simpler project configurations, leveraging the benefits of Nx's robust tooling, and later, when the need arises, seamlessly [migrate to a monorepo](/docs/guides/tips-n-tricks/standalone-to-monorepo).
However, Nx's true strength becomes most apparent at scale, typically within a monorepo setup. We wanted to capture it in our new tagline: **Smart Repos — Fast Builds**.
However, Nx's true strength becomes most apparent at scale, typically within a monorepo setup. We wanted to capture it in our new tagline: **Smart Monorepos — Fast Builds**.
{% tweet url="https://twitter.com/juristr/status/1734558895547568634" /%}
+1 -1
View File
@@ -18,7 +18,7 @@ In 2014, the state of the art for running tests and builds in your repository we
Nx was created in 2017 to address this problem. Nx is a build system that operates on a **higher level** where developers define the relationships between tasks and then Nx to decides the optimal way to run those tasks. In the same way, developers can define the inputs and outputs of tasks, then Nx automatically caches those task results. Developers tell Nx what a task does and then Nx can decide how best to run that task.
With [Nx Agents](/docs/features/ci-features/distribute-task-execution), Nx is applying this same mindset to the problem of slow and costly CI pipelines. Nx gives you both **Smart Repos** and **Fast Builds**.
With [Nx Agents](/docs/features/ci-features/distribute-task-execution), Nx is applying this same mindset to the problem of slow and costly CI pipelines. Nx gives you both **Smart Monorepos** and **Fast Builds**.
## Why is CI So Hard?
+1 -1
View File
@@ -27,7 +27,7 @@ This blog will show you:
Before we start, let's answer this question: what is Nx and why should we use it?
From [nx.dev](): "Nx is a build system with built-in tooling and advanced CI capabilities. It helps you maintain and scale monorepos, both locally and on CI." It sounds good, what benefits does it bring?
From [nx.dev](): "Nx is a monorepo platform with built-in tooling and advanced CI capabilities. It helps you maintain and scale monorepos, both locally and on CI." It sounds good, what benefits does it bring?
Nx adds the following features to your workspace:
@@ -0,0 +1,109 @@
---
title: 'End to End Autonomous AI Agent Workflows with Nx'
slug: autonomous-ai-workflows-with-nx
authors: ['Juri Strumpflohner']
tags: ['nx', 'self-healing']
cover_image: /blog/images/articles/local-to-ci-autonomous-agents.avif
youtubeUrl: https://youtu.be/LaZroK4b-zQ
description: 'Learn how Nx bridges the gap between local AI agents and CI, enabling fully autonomous development workflows with the ci-monitor skill and Self-Healing CI.'
---
If 2025 was the year of agents, **2026 is the year of autonomous workflows**. We started with AI Chats (copy and pasting), moved to agentic tools like Claude Code and Cursor (edit files, run commands, but still babysitting), and are now heading toward agents that perform large chunks of work completely autonomously.
AI agents no longer just autocomplete a line or a function body. They can operate independently across projects. This shift will change how we work. Organizations that adapt quickly, and have the right tooling in place, will have a significant advantage.
{% toc /%}
## CI Breaks Autonomy - How Nx fixes it!
We wrote about [autonomous AI Agents at scale](/blog/ai-agents-and-continuity) and the potential barriers organizations might hit when adopting them. One such aspect where many current setups hit a wall is CI.
The local agent implements everything, pushes to CI, and waits. CI fails? You get pulled back in. Context is lost. This disconnect between local development and CI kills full autonomy.
Nx is modular and can be adopted incrementally, but each piece fits seamlessly together. The `ci-monitor` skill is one such piece.
{% callout type="info" title="What's a skill?" %}
Skills are portable, shareable agent capabilities that extend what your AI coding agent can do. They work across different AI agents and can be shared via [agentskills.io](https://agentskills.io/home).
{% /callout %}
When you have your Nx workspace connected to Nx Cloud, the CI Monitor skill opens a communication channel between your local agent and the Nx Cloud CI run via the Nx MCP server. Your local agent can now:
- Monitor CI pipeline status in real-time
- Receive failure information with full context
- Communicate with Nx's Self-Healing CI agent
- Apply verified fixes automatically
- Keep iterating until CI is green
The disconnect is bridged.
![Local agent communicating with Nx Cloud CI](/blog/images/articles/local-agent-communication-viz.avif)
## Let The Agent Handle the Annoying Part
Fully autonomous AI agents have gained popularity recently, with "Ralph Wiggum loops" being one implementation pattern: autonomous cycles that keep iterating until a task is complete.
{% callout type="info" title="What's a Ralph loop?" %}
Ralph Wiggum loops are autonomous agent workflows where the agent keeps working on a task until completion, without human intervention. The pattern was popularized by [Geoffrey Huntley](https://ghuntley.com/ralph/) and has become a common approach for running AI agents on well-defined tasks.
{% /callout %}
In the [video demo](https://youtu.be/LaZroK4b-zQ), a Ralph loop picks up a well-defined user story from a PRD and implements it autonomously:
1. The local agent reads requirements and implements the feature
2. Runs local quality checks: type checking, linting, testing
3. Creates a PR and starts monitoring CI
When CI fails, the Nx Self-Healing CI agent kicks in. It classifies the failure, proposes a verified fix. The local agent sees this through the ci-monitor, applies the fix, pushes again. Another failure (end-to-end test). Same process. Back and forth until CI is green.
Then you get notified and you review the PR. No interruptions during all this CI back and forth.
> The competitive advantage goes to organizations ready to adopt these workflows.
**But you don't need to go all-in on full autonomy necessarily.** You might prefer interactive collaboration. You pair with the agent on implementation, make decisions together, iterate on the approach. When you're 90% done, you hand off. The agent creates the PR, monitors CI, applies fixes, and notifies you when CI is green and is ready for review.
## How to Get Started
Setting up CI monitoring requires two steps:
### 1. Configure AI Agent Support
```shell
nx configure-ai-agents
```
This sets up your workspace with the MCP configuration and skills that enable agent-CI communication.
### 2. Use the CI Monitor
Leverage the `ci-monitor` skill by asking your AI agent:
```text
Commit the work, create a PR and monitor CI.
```
> **Prerequisite:** Your Nx workspace needs to be connected to Nx Cloud and you should have Self-Healing CI enabled. [More about that in the docs](/docs/features/ci-features/self-healing-ci).
The skill connects to Nx Cloud, watches pipeline progress, and feeds failure information back to your agent. If Self-Healing CI proposes fixes, those become available for your agent to review and apply.
Want to try Ralph loops yourself? Check out this [example repo](https://github.com/juristr/tusky/tree/30ad2ae3f99c595b6f307162e8a92b8ccc6f92fa/ralph) for a working setup you can reference.
## Looking Ahead
Software development is changing rapidly. AI coding agents are becoming part of everyday workflows, and the quality you get out of them vastly differs based on your setup.
The infrastructure matters. How your codebase is organized. The type and quality of context the AI agent has access to. Whether guardrails and feedback loops are in place. Whether your CI platform integrates deeply enough to close the autonomy gap.
These factors determine whether your AI agent can become a productivity multiplier.
Nx enables these autonomous flows. This is just the beginning.
---
Learn more:
- [Autonomous AI Agents at Scale](/blog/ai-agents-and-continuity): Infrastructure requirements for AI agent workflows
- [Self-Healing CI Documentation](/docs/features/ci-features/self-healing-ci): How Nx Cloud's Self-Healing CI works
- [Ralph Wiggum (original concept)](https://ghuntley.com/ralph/): Community origins of the Ralph loop pattern
- 🧠 [Nx Docs](/docs/getting-started/intro)
- 👩‍💻 [Nx GitHub](https://github.com/nrwl/nx)
- 💬 [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: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 KiB

@@ -4,7 +4,7 @@ videoUrl: 'https://youtu.be/8mqHXYIl_qI'
duration: '4:00'
---
Nx powers the “Smart Repos,” while Nx Cloud brings “Fast Builds” into the mix. Designed to extend Nxs efficiency into the CI pipeline, Nx Cloud ensures that even large monorepos stay fast and optimized in CI.
Nx powers the “Smart Monorepos,” while Nx Cloud brings “Fast Builds” into the mix. Designed to extend Nxs efficiency into the CI pipeline, Nx Cloud ensures that even large monorepos stay fast and optimized in CI.
In this lesson, well take the Tasker monorepo, push it to GitHub, set up an Nx Cloud workspace, and link it with your GitHub repository. By the end, your Nx workspace will be fully connected to Nx Cloud, ready to leverage its remote caching and distributed CI capabilities.
+1 -1
View File
@@ -72,7 +72,7 @@
{
"name": "create-nx-workspace",
"packageName": "create-nx-workspace",
"description": "Smart Repos · Fast Builds",
"description": "Smart Monorepos · Fast Builds",
"path": "generated/packages/create-nx-workspace.json",
"schemas": {
"executors": [],
@@ -86,8 +86,10 @@ describe('Angular Module Federation - Federated Libraries', () => {
expect(buildRemoteOutput).toContain('Successfully ran target build');
if (runE2ETests('cypress')) {
const e2eProcess = await runCommandUntil(`e2e ${host}-e2e`, (output) =>
output.includes('All specs passed!')
const e2eProcess = await runCommandUntil(
`e2e ${host}-e2e`,
(output) => output.includes('All specs passed!'),
{ timeout: 120000 }
);
await killProcessAndPorts(e2eProcess.pid, hostPort, hostPort + 1);
}
@@ -175,8 +177,10 @@ describe('Angular Module Federation - Federated Libraries', () => {
expect(buildRemoteOutput).toContain('Successfully ran target build');
if (runE2ETests('cypress')) {
const e2eProcess = await runCommandUntil(`e2e ${host}-e2e`, (output) =>
output.includes('All specs passed!')
const e2eProcess = await runCommandUntil(
`e2e ${host}-e2e`,
(output) => output.includes('All specs passed!'),
{ timeout: 120000 }
);
await killProcessAndPorts(e2eProcess.pid, hostPort, hostPort + 1);
}
@@ -88,8 +88,13 @@ test('renders remotes', async ({ page }) => {
});`
);
if (runE2ETests()) {
const e2eProcess = await runCommandUntil(`e2e ${host}-e2e`, (output) =>
output.includes(`Successfully ran target e2e for project ${host}-e2e`)
const e2eProcess = await runCommandUntil(
`e2e ${host}-e2e`,
(output) =>
output.includes(
`Successfully ran target e2e for project ${host}-e2e`
),
{ timeout: 120000 }
);
await killProcessAndPorts(e2eProcess.pid);
}
+10 -5
View File
@@ -151,7 +151,8 @@ describe('Node.js Framework ESM Support', () => {
`serve ${expressApp}`,
(output) => {
return output.includes('Express ESM serve ready on port');
}
},
{ timeout: 120000 }
);
await promisifiedTreeKill(serveProcess.pid, 'SIGKILL');
@@ -269,7 +270,8 @@ describe('Node.js Framework ESM Support', () => {
`serve ${fastifyApp}`,
(output) => {
return output.includes('Fastify ESM serve ready on port');
}
},
{ timeout: 120000 }
);
await promisifiedTreeKill(serveProcess.pid, 'SIGKILL');
@@ -386,7 +388,8 @@ describe('Node.js Framework ESM Support', () => {
`serve ${koaApp}`,
(output) => {
return output.includes('Koa ESM serve ready on port');
}
},
{ timeout: 120000 }
);
await promisifiedTreeKill(serveProcess.pid, 'SIGKILL');
@@ -444,7 +447,8 @@ describe('Node.js Framework ESM Support', () => {
`serve ${nestApp}`,
(output) => {
return output.includes('Nest ESM server ready on port');
}
},
{ timeout: 120000 }
);
await promisifiedTreeKill(serveProcess.pid, 'SIGKILL');
}, 600000);
@@ -496,7 +500,8 @@ describe('Node.js Framework ESM Support', () => {
`serve ${nestApp}`,
(output) => {
return output.includes('Nest ESM serve ready on port');
}
},
{ timeout: 120000 }
);
await promisifiedTreeKill(serveProcess.pid, 'SIGKILL');
+4 -1
View File
@@ -209,6 +209,7 @@ module.exports = {
return output.includes(`foobar: test foo bar`);
},
{
timeout: 120000,
env: {
NX_DAEMON: 'true',
},
@@ -271,8 +272,8 @@ module.exports = {
const p = await runCommandUntil(
`serve ${nodeapp}`,
(output) => output.includes(`Listening at http://localhost:${port}`),
{
timeout: 120000,
env: {
NX_DAEMON: 'true',
},
@@ -328,6 +329,7 @@ module.exports = {
return output.includes(`listening on ws://localhost:${port}`);
},
{
timeout: 120000,
env: {
NX_DAEMON: 'true',
},
@@ -395,6 +397,7 @@ module.exports = {
return output.includes('Hello World');
},
{
timeout: 120000,
env: {
NX_DAEMON: 'true',
},
+5 -5
View File
@@ -29,7 +29,7 @@ xdescribe('--help output', () => {
expect(output).toMatch(/--coverage|--watch|--bail/i);
// Should NOT contain Nx's help
expect(output).not.toContain('Smart Repos');
expect(output).not.toContain('Smart Monorepos');
expect(output).not.toContain('Run target');
expect(output).not.toContain('Run a target for a project');
});
@@ -44,7 +44,7 @@ xdescribe('--help output', () => {
expect(output).toMatch(/--coverage|--watch|--bail/i);
// Should NOT contain Nx's help
expect(output).not.toContain('Smart Repos');
expect(output).not.toContain('Smart Monorepos');
expect(output).not.toContain('Run target');
expect(output).not.toContain('Run a target for a project');
});
@@ -60,7 +60,7 @@ xdescribe('--help output', () => {
expect(lintOutput).toMatch(/--fix|--format|--quiet/i);
// Should NOT contain Nx's help
expect(lintOutput).not.toContain('Smart Repos');
expect(lintOutput).not.toContain('Smart Monorepos');
expect(lintOutput).not.toContain('Run target');
});
@@ -79,7 +79,7 @@ xdescribe('--help output', () => {
expect(buildOutput).toMatch(/webpack|build.*production/i);
// Should NOT contain Nx's help
expect(buildOutput).not.toContain('Smart Repos');
expect(buildOutput).not.toContain('Smart Monorepos');
expect(buildOutput).not.toContain('Run target');
});
});
@@ -317,7 +317,7 @@ xdescribe('--help output', () => {
// Should still show Jest help even with additional flags
expect(output).toContain('Usage: jest');
expect(output).not.toContain('Smart Repos');
expect(output).not.toContain('Smart Monorepos');
});
});
});
+1 -1
View File
@@ -440,7 +440,7 @@ describe('Nx Commands', () => {
it('should show help if no command provided', () => {
const output = runCLI('', { silenceError: true });
expect(output).toContain('Smart Repos · Fast Builds');
expect(output).toContain('Smart Monorepos · Fast Builds');
expect(output).toContain('Commands:');
});
});
@@ -69,7 +69,8 @@ describe('React Rspack Module Federation - Basic - Playwright', () => {
if (runE2ETests()) {
const e2eResultsSwc = await runCommandUntil(
`e2e ${shell}-e2e`,
(output) => output.includes('Successfully ran target e2e for project')
(output) => output.includes('Successfully ran target e2e for project'),
{ timeout: 120000 }
);
await killProcessAndPorts(e2eResultsSwc.pid, readPort(shell));
@@ -75,7 +75,8 @@ describe('React Module Federation - Webpack Basic - Playwright', () => {
);
const e2eResultsSwc = await runCommandUntil(
`e2e ${shell}-e2e`,
(output) => output.includes('Successfully ran target e2e for project')
(output) => output.includes('Successfully ran target e2e for project'),
{ timeout: 120_000 }
);
console.log(
`[core-webpack-basic-playwright] e2e (swc) completed with PID ${e2eResultsSwc.pid}`
@@ -96,6 +97,7 @@ describe('React Module Federation - Webpack Basic - Playwright', () => {
`e2e ${shell}-e2e`,
(output) => output.includes('Successfully ran target e2e for project'),
{
timeout: 120_000,
env: { NX_PREFER_TS_NODE: 'true' },
}
);
@@ -63,8 +63,11 @@ describe('React Module Federation - Webpack SSR', () => {
`generate @nx/react:host ${shell} --ssr --bundler=webpack --remotes=${remote1},${remote2},${remote3} --style=css --e2eTestRunner=cypress --no-interactive --skipFormat`
);
const serveResult = await runCommandUntil(`serve ${shell}`, (output) =>
output.includes(`Nx SSR Static remotes proxies started successfully`)
const serveResult = await runCommandUntil(
`serve ${shell}`,
(output) =>
output.includes(`Nx SSR Static remotes proxies started successfully`),
{ timeout: 120000 }
);
await killProcessAndPorts(serveResult.pid);
@@ -107,7 +110,8 @@ describe('React Module Federation - Webpack SSR', () => {
if (runE2ETests()) {
const hostE2eResults = await runCommandUntil(
`e2e ${shell}-e2e --no-watch --verbose`,
(output) => output.includes('All specs passed!')
(output) => output.includes('All specs passed!'),
{ timeout: 120000 }
);
await killProcessAndPorts(hostE2eResults.pid);
}
+15 -3
View File
@@ -237,7 +237,11 @@ describe('nx release - independent projects', () => {
const versionWithGitActionsCLIOutput = runCLI(
`release version 999.9.9-version-git-operations-test.2 -p ${pkg1} --git-commit --git-tag --verbose` // add verbose so we get richer output
);
expect(versionWithGitActionsCLIOutput).toMatchInlineSnapshot(`
const filteredOutput = versionWithGitActionsCLIOutput.replace(
/\[plugin-(pool|worker)\].*\n/g,
''
);
expect(filteredOutput).toMatchInlineSnapshot(`
NX Your filter "{project-name}" matched the following projects:
@@ -320,7 +324,11 @@ describe('nx release - independent projects', () => {
const versionWithGitActionsConfigOutput = runCLI(
`release version 999.9.9-version-git-operations-test.3 --verbose --gitTag` // add verbose so we get richer output
);
expect(versionWithGitActionsConfigOutput).toMatchInlineSnapshot(`
const filteredConfigOutput = versionWithGitActionsConfigOutput.replace(
/\[plugin-(pool|worker)\].*\n/g,
''
);
expect(filteredConfigOutput).toMatchInlineSnapshot(`
NX Running release version for project: {project-name}
@@ -516,7 +524,11 @@ describe('nx release - independent projects', () => {
const versionWithGitActionsCLIOutput = runCLI(
`release changelog 999.9.9-changelog-git-operations-test.1 -p ${pkg1} --verbose`
);
expect(versionWithGitActionsCLIOutput).toMatchInlineSnapshot(`
const filteredChangelogOutput = versionWithGitActionsCLIOutput.replace(
/\[plugin-(pool|worker)\].*\n/g,
''
);
expect(filteredChangelogOutput).toMatchInlineSnapshot(`
NX Your filter "{project-name}" matched the following projects:
@@ -54,6 +54,8 @@ expect.addSnapshotSerializer({
.replaceAll('pnpm install --lockfile-only', '{lock-file-command}')
.replaceAll(getSelectedPackageManager(), '{package-manager}')
.replaceAll(e2eRegistryUrl, '{registryUrl}')
// Filter out plugin worker verbose logs
.replaceAll(/\[plugin-(pool|worker)\].*\n/g, '')
// We trim each line to reduce the chances of snapshot flakiness
.split('\n')
.map((r) => r.trim())
@@ -47,6 +47,8 @@ expect.addSnapshotSerializer({
/Integrity:\s*.*/g,
'Integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
)
// Filter out plugin worker verbose logs
.replaceAll(/\[plugin-(pool|worker)\].*\n/g, '')
.split('\n')
.map((r) => r.trim())
@@ -32,6 +32,8 @@ expect.addSnapshotSerializer({
.replaceAll(/Test @[\w\d]+/g, 'Test @{COMMIT_AUTHOR}')
// Normalize the version title date.
.replaceAll(/\(\d{4}-\d{2}-\d{2}\)/g, '(YYYY-MM-DD)')
// Filter out plugin worker verbose logs
.replaceAll(/\[plugin-(pool|worker)\].*\n/g, '')
// We trim each line to reduce the chances of snapshot flakiness
.split('\n')
.map((r) => r.trim())
@@ -29,6 +29,8 @@ expect.addSnapshotSerializer({
.replaceAll(/Test @[\w\d]+/g, 'Test @{COMMIT_AUTHOR}')
// Normalize the version title date.
.replaceAll(/\(\d{4}-\d{2}-\d{2}\)/g, '(YYYY-MM-DD)')
// Filter out plugin worker verbose logs
.replaceAll(/\[plugin-(pool|worker)\].*\n/g, '')
// We trim each line to reduce the chances of snapshot flakiness
.split('\n')
.map((r) => r.trim())
+24 -8
View File
@@ -279,11 +279,10 @@ export function runCommandAsync(
export function runCommandUntil(
command: string,
criteria: (output: string) => boolean,
opts: RunCmdOpts = {
env: undefined,
}
opts: RunCmdOpts & { timeout?: number } = {}
): Promise<ChildProcess> {
const pm = getPackageManagerCommand();
const timeout = opts.timeout ?? 30_000;
const p = exec(`${pm.runNx} ${command}`, {
cwd: tmpProjPath(),
encoding: 'utf-8',
@@ -301,18 +300,37 @@ export function runCommandUntil(
let output = '';
let complete = false;
const timeoutId = setTimeout(() => {
if (!complete) {
complete = true;
p.kill();
logError(
`Output did not meet the criteria:`,
output
.split('\n')
.map((l) => ` ${l}`)
.join('\n')
);
rej(new Error(`Timed out after ${timeout}ms waiting for criteria`));
}
}, timeout);
function checkCriteria(c) {
output += c.toString();
if (criteria(stripConsoleColors(output)) && !complete) {
const strippedOutput = stripConsoleColors(output);
if (criteria(strippedOutput) && !complete) {
complete = true;
clearTimeout(timeoutId);
res(p);
}
}
p.stdout?.on('data', checkCriteria);
p.stderr?.on('data', checkCriteria);
p.on('exit', (code) => {
p.on('close', (code) => {
if (!complete) {
complete = true;
clearTimeout(timeoutId);
logError(
`Original output:`,
output
@@ -320,9 +338,7 @@ export function runCommandUntil(
.map((l) => ` ${l}`)
.join('\n')
);
rej(`Exited with ${code}`);
} else {
res(p);
rej(new Error(`Exited with ${code}`));
}
});
});
+5 -12
View File
@@ -36,12 +36,9 @@ export default async function (globalConfig: Config.ConfigGlobals) {
}
process.env.npm_config_registry = registry;
execSync(
`npm config set //${listenAddress}:${port}/:_authToken "${authToken}" --ws=false`,
{
windowsHide: false,
}
);
// Use environment variable instead of npm config command to avoid polluting other tests
process.env[`npm_config_//${listenAddress}:${port}/:_authToken`] =
authToken;
// bun
process.env.BUN_CONFIG_REGISTRY = registry;
@@ -55,12 +52,8 @@ export default async function (globalConfig: Config.ConfigGlobals) {
process.env.NX_SKIP_PROVENANCE_CHECK = 'true';
global.e2eTeardown = () => {
execSync(
`npm config delete //${listenAddress}:${port}/:_authToken --ws=false`,
{
windowsHide: false,
}
);
// Clean up environment variable instead of npm config command
delete process.env[`npm_config_//${listenAddress}:${port}/:_authToken`];
};
/**
+1 -1
View File
@@ -136,7 +136,7 @@ export function FeedContainer(): JSX.Element {
<div
className={cx(
'left0 fixed bottom-0 right-0 w-full px-4 py-4 lg:px-0 lg:py-6',
'bg-gradient-to-t from-white via-white/75 dark:from-slate-900 dark:via-slate-900/75'
'bg-gradient-to-t from-white via-white/75 dark:from-zinc-900 dark:via-zinc-900/75'
)}
>
<Prompt
+8 -11
View File
@@ -51,7 +51,7 @@ export function FeedAnswer({
return (
<>
<div className="grid h-12 w-12 items-center justify-center rounded-full bg-white text-slate-900 ring-1 ring-slate-200 dark:bg-slate-900 dark:text-white dark:ring-slate-700">
<div className="grid h-12 w-12 items-center justify-center rounded-full bg-white text-zinc-900 ring-1 ring-zinc-200 dark:bg-zinc-900 dark:text-white dark:ring-zinc-700">
<svg
role="img"
viewBox="0 0 24 24"
@@ -65,23 +65,20 @@ export function FeedAnswer({
</div>
<div className="min-w-0 flex-1">
<div>
<div className="flex items-center gap-2 text-lg text-slate-900 dark:text-slate-100">
<div className="flex items-center gap-2 text-lg text-zinc-900 dark:text-zinc-100">
Nx Assistant
</div>
<p className="mt-0.5 flex items-center gap-x-1 text-sm text-slate-500">
<ChatGptLogo
className="h-4 w-4 text-slate-400"
aria-hidden="true"
/>{' '}
<p className="mt-0.5 flex items-center gap-x-1 text-sm text-zinc-500">
<ChatGptLogo className="h-4 w-4 text-zinc-400" aria-hidden="true" />{' '}
AI powered
</p>
</div>
<div className="prose prose-slate dark:prose-invert mt-2 w-full max-w-none 2xl:max-w-4xl">
<div className="prose prose-zinc dark:prose-invert mt-2 w-full max-w-none 2xl:max-w-4xl">
{!isFirst && callout}
{renderMarkdown(normalizedContent, { filePath: '' }).node}
</div>
{!isFirst && (
<div className="text-md group flex-1 gap-4 text-slate-400 transition hover:text-slate-500 md:flex md:items-center md:justify-end">
<div className="text-md group flex-1 gap-4 text-zinc-400 transition hover:text-zinc-500 md:flex md:items-center md:justify-end">
{feedbackStatement ? (
<p className="italic group-hover:flex">
{feedbackStatement === 'good'
@@ -96,7 +93,7 @@ export function FeedAnswer({
<div className="flex gap-4">
<button
className={cx(
'p-1 transition-all hover:rotate-12 hover:text-blue-500 disabled:cursor-not-allowed dark:hover:text-sky-500',
'p-1 transition-all hover:rotate-12 hover:text-blue-500 disabled:cursor-not-allowed dark:hover:text-blue-500',
{ 'text-blue-500': feedbackStatement === 'bad' }
)}
disabled={!!feedbackStatement}
@@ -108,7 +105,7 @@ export function FeedAnswer({
</button>
<button
className={cx(
'p-1 transition-all hover:rotate-12 hover:text-blue-500 disabled:cursor-not-allowed dark:hover:text-sky-500',
'p-1 transition-all hover:rotate-12 hover:text-blue-500 disabled:cursor-not-allowed dark:hover:text-blue-500',
{ 'text-blue-500': feedbackStatement === 'good' }
)}
disabled={!!feedbackStatement}
@@ -1,7 +1,7 @@
export function FeedQuestion({ content }: { content: string }) {
return (
<div className="flex w-full justify-end">
<p className="whitespace-pre-wrap break-words rounded-lg bg-blue-500 px-4 py-2 text-base text-white selection:bg-sky-900 dark:bg-sky-500">
<p className="whitespace-pre-wrap break-words rounded-lg bg-blue-500 px-4 py-2 text-base text-white selection:bg-blue-900 dark:bg-blue-500">
{content}
</p>
</div>
+1 -1
View File
@@ -1,6 +1,6 @@
export function LoadingState(): JSX.Element {
return (
<div className="flex w-full items-center justify-center gap-4 px-4 py-2 text-blue-500 transition duration-150 ease-in-out dark:text-sky-500">
<div className="flex w-full items-center justify-center gap-4 px-4 py-2 text-blue-500 transition duration-150 ease-in-out dark:text-blue-500">
<svg
className="h-5 w-5 animate-spin"
xmlns="http://www.w3.org/2000/svg"
+6 -6
View File
@@ -62,7 +62,7 @@ export function Prompt({
<form
ref={formRef}
onSubmit={handleSubmit}
className="relative mx-auto flex max-w-3xl gap-2 rounded-md border border-slate-300 bg-white px-2 py-2 shadow-lg dark:border-slate-900 dark:bg-slate-700"
className="relative mx-auto flex max-w-3xl gap-2 rounded-md border border-zinc-300 bg-white px-2 py-2 shadow-lg dark:border-zinc-900 dark:bg-zinc-700"
>
<div
className={cx(
@@ -74,7 +74,7 @@ export function Prompt({
<Button
variant="secondary"
size="small"
className={cx('bg-white dark:bg-slate-900')}
className={cx('bg-white dark:bg-zinc-900')}
onClick={handleStopGenerating}
>
<StopIcon aria-hidden="true" className="h-5 w-5" />
@@ -85,7 +85,7 @@ export function Prompt({
<Button
variant="secondary"
size="small"
className={cx('bg-white dark:bg-slate-900')}
className={cx('bg-white dark:bg-zinc-900')}
onClick={handleNewChat}
>
<XMarkIcon aria-hidden="true" className="h-5 w-5" />
@@ -96,7 +96,7 @@ export function Prompt({
<Button
variant="secondary"
size="small"
className={cx('bg-white dark:bg-slate-900')}
className={cx('bg-white dark:bg-zinc-900')}
onClick={onRegenerate}
>
<ArrowPathIcon aria-hidden="true" className="h-5 w-5" />
@@ -123,14 +123,14 @@ export function Prompt({
name="query"
maxLength={500}
disabled={isGenerating}
className="block w-full resize-none border-none bg-transparent p-0 py-3 pl-2 text-sm placeholder-slate-500 focus-within:outline-none focus:placeholder-slate-400 focus:outline-none focus:ring-0 disabled:cursor-not-allowed dark:text-white dark:focus:placeholder-slate-300"
className="block w-full resize-none border-none bg-transparent p-0 py-3 pl-2 text-sm placeholder-zinc-500 focus-within:outline-none focus:placeholder-zinc-400 focus:outline-none focus:ring-0 disabled:cursor-not-allowed dark:text-white dark:focus:placeholder-zinc-300"
placeholder="How does caching work?"
rows={1}
/>
</div>
<div className="flex">
<Button
variant="primary"
variant="contrast"
size="small"
type="submit"
disabled={isGenerating}
@@ -2,16 +2,16 @@ import { InformationCircleIcon } from '@heroicons/react/24/outline';
export function ActivityLimitReached(): JSX.Element {
return (
<div className="rounded-md bg-slate-50 p-4 shadow-sm ring-1 ring-slate-100 dark:bg-slate-800/40 dark:ring-slate-700">
<div className="rounded-md bg-zinc-50 p-4 shadow-sm ring-1 ring-zinc-100 dark:bg-zinc-800/40 dark:ring-zinc-700">
<div className="flex">
<div className="flex-shrink-0">
<InformationCircleIcon
className="h-5 w-5 text-slate-500 dark:text-slate-300"
className="h-5 w-5 text-zinc-500 dark:text-zinc-300"
aria-hidden="true"
/>
</div>
<div className="ml-3 flex-1 md:flex md:justify-between">
<p className="text-sm text-slate-700 dark:text-slate-400">
<p className="text-sm text-zinc-700 dark:text-zinc-400">
You've reached the maximum message history limit. Previous messages
will be removed.
</p>
@@ -3,7 +3,7 @@ import { ReactNode } from 'react';
export function SidebarContainer({ children }: { children: ReactNode[] }) {
return (
<div id="sidebar" data-testid="sidebar">
<div className="hidden h-full w-72 flex-col border-r border-slate-200 md:flex dark:border-slate-700 dark:bg-slate-900">
<div className="hidden h-full w-72 flex-col border-r border-zinc-200 md:flex dark:border-zinc-700 dark:bg-zinc-900">
<div className="relative flex flex-col gap-4 overflow-y-scroll p-4">
{...children}
</div>
@@ -4,17 +4,11 @@ import { useEffect, useRef, useCallback } from 'react';
import { usePathname } from 'next/navigation';
import { sendCustomEvent } from './google-analytics';
function getScrollDepth(pct: number): 0 | 25 | 50 | 75 | 90 {
if (pct >= 0.9) return 90;
if (pct < 0.25) return 0;
if (pct < 0.5) return 25;
if (pct < 0.75) return 50;
return 75;
}
const SCROLL_THRESHOLDS = [10, 25, 50, 75, 90] as const;
export function useWindowScrollDepth(): void {
const pathname = usePathname();
const scrollDepth = useRef(0);
const firedThresholds = useRef<Set<number>>(new Set());
const shouldTrackScroll = useRef(true);
const rafId = useRef<number | null>(null);
@@ -23,13 +17,19 @@ export function useWindowScrollDepth(): void {
if (typeof window === 'undefined') return;
const scrollPercentage =
(window.scrollY + window.innerHeight) /
document.documentElement.scrollHeight;
const depth = getScrollDepth(scrollPercentage);
((window.scrollY + window.innerHeight) /
document.documentElement.scrollHeight) *
100;
if (depth > scrollDepth.current) {
scrollDepth.current = depth;
sendCustomEvent(`scroll_${depth}`, 'scroll', pathname || '/');
// Fire events for all thresholds we've passed but haven't fired yet
for (const threshold of SCROLL_THRESHOLDS) {
if (
scrollPercentage >= threshold &&
!firedThresholds.current.has(threshold)
) {
firedThresholds.current.add(threshold);
sendCustomEvent(`scroll_${threshold}`, 'scroll', pathname || '/');
}
}
}, [pathname]);
@@ -46,12 +46,15 @@ export function useWindowScrollDepth(): void {
shouldTrackScroll.current = false;
const timeout = setTimeout(() => {
scrollDepth.current = 0;
firedThresholds.current = new Set();
shouldTrackScroll.current = true;
// Immediately check current scroll position to capture thresholds
// that may have been passed during the delay
handleScroll();
}, 500);
return () => clearTimeout(timeout);
}, [pathname]);
}, [pathname, handleScroll]);
useEffect(() => {
if (typeof window === 'undefined') return;
@@ -103,7 +103,7 @@ export function DocViewer({
}`,
width: 1600,
height: 800,
alt: 'Nx: Smart Repos · Fast Builds',
alt: 'Nx: Smart Monorepos · Fast Builds',
type: 'image/jpeg',
},
],
@@ -135,7 +135,7 @@ export function DocViewer({
ref={ref}
data-document="main"
className={cx(
'prose prose-slate dark:prose-invert w-full max-w-none 2xl:max-w-4xl',
'prose prose-zinc dark:prose-invert w-full max-w-none 2xl:max-w-4xl',
{ 'xl:max-w-2xl': !hideTableOfContent }
)}
>
@@ -146,7 +146,7 @@ export function DocViewer({
<div>
<div
className={cx(
'sticky top-2 z-20 ml-[max(2rem,calc(50%-8rem))] hidden w-60 space-y-6 overflow-y-auto bg-white text-sm xl:block dark:bg-slate-900'
'sticky top-2 z-20 ml-[max(2rem,calc(50%-8rem))] hidden w-60 space-y-6 overflow-y-auto bg-white text-sm xl:block dark:bg-zinc-900'
)}
>
{widgetData.githubStarsCount > 0 && (
@@ -163,18 +163,18 @@ export function DocViewer({
document={document}
>
<>
<div className="my-4 flex items-center justify-center space-x-2 rounded-md border border-slate-200 pl-2 pr-2 hover:border-slate-400 dark:border-slate-700 print:hidden">
<div className="my-4 flex items-center justify-center space-x-2 rounded-md border border-zinc-200 pl-2 pr-2 hover:border-zinc-400 dark:border-zinc-700 print:hidden">
<button
type="button"
aria-label="Give feedback on this page"
title="Give feedback of this page"
className="whitespace-nowrap border-transparent px-4 py-2 font-bold hover:text-slate-900 dark:hover:text-sky-400"
className="whitespace-nowrap border-transparent px-4 py-2 font-bold hover:text-zinc-900 dark:hover:text-blue-400"
onClick={() => setShowFeedback(true)}
>
Feedback
</button>
</div>
<div className="my-4 flex items-center justify-center space-x-2 rounded-md border border-slate-200 pl-2 pr-2 hover:border-slate-400 dark:border-slate-700 print:hidden">
<div className="my-4 flex items-center justify-center space-x-2 rounded-md border border-zinc-200 pl-2 pr-2 hover:border-zinc-400 dark:border-zinc-700 print:hidden">
{document.filePath ? (
<a
aria-hidden="true"
@@ -190,7 +190,7 @@ export function DocViewer({
target="_blank"
rel="noreferrer"
title="Edit this page on GitHub"
className="whitespace-nowrap border-transparent px-4 py-2 font-bold hover:text-slate-900 dark:hover:text-sky-400"
className="whitespace-nowrap border-transparent px-4 py-2 font-bold hover:text-zinc-900 dark:hover:text-blue-400"
>
Edit this page
</a>
@@ -207,7 +207,7 @@ export function DocViewer({
<div
data-document="related"
className={cx(
'prose prose-slate dark:prose-invert w-full max-w-none pt-8 2xl:max-w-4xl',
'prose prose-zinc dark:prose-invert w-full max-w-none pt-8 2xl:max-w-4xl',
{ 'xl:max-w-2xl': !hideTableOfContent }
)}
>
@@ -221,7 +221,7 @@ export function DocViewer({
hideTableOfContent ? '' : 'xl:hidden'
}`}
>
<div className="ml-4 flex h-0.5 w-full flex-grow rounded bg-slate-50 dark:bg-slate-800/60" />
<div className="ml-4 flex h-0.5 w-full flex-grow rounded bg-zinc-50 dark:bg-zinc-800/60" />
<div className="relative z-0 inline-flex flex-shrink-0 rounded-md shadow-sm">
<button
type="button"
@@ -230,7 +230,7 @@ export function DocViewer({
className={`relative inline-flex items-center rounded-l-md ${
// If there is no file path for this page then don't show edit button.
document.filePath ? '' : 'rounded-r-md'
}border border-slate-200 bg-white px-4 py-2 text-xs font-medium text-slate-600 focus-within:ring-blue-500 hover:bg-slate-50 focus:z-10 focus:outline-none focus:ring-1 dark:border-slate-700 dark:bg-slate-800/60 dark:text-slate-400 dark:focus-within:ring-sky-500 dark:hover:bg-slate-800`}
}border border-zinc-200 bg-white px-4 py-2 text-xs font-medium text-zinc-600 focus-within:ring-blue-500 hover:bg-zinc-50 focus:z-10 focus:outline-none focus:ring-1 dark:border-zinc-700 dark:bg-zinc-800/60 dark:text-zinc-400 dark:focus-within:ring-blue-500 dark:hover:bg-zinc-800`}
onClick={() => setShowFeedback(true)}
>
Feedback
@@ -247,7 +247,7 @@ export function DocViewer({
target="_blank"
rel="noreferrer"
title="Edit this page on GitHub"
className="relative -ml-px inline-flex items-center rounded-r-md border border-slate-200 bg-white px-4 py-2 text-xs font-medium text-slate-600 focus-within:ring-blue-500 hover:bg-slate-50 focus:z-10 focus:outline-none focus:ring-1 dark:border-slate-700 dark:bg-slate-800/60 dark:text-slate-400 dark:focus-within:ring-sky-500 dark:hover:bg-slate-800"
className="relative -ml-px inline-flex items-center rounded-r-md border border-zinc-200 bg-white px-4 py-2 text-xs font-medium text-zinc-600 focus-within:ring-blue-500 hover:bg-zinc-50 focus:z-10 focus:outline-none focus:ring-1 dark:border-zinc-700 dark:bg-zinc-800/60 dark:text-zinc-400 dark:focus-within:ring-blue-500 dark:hover:bg-zinc-800"
>
Edit this page
</a>
@@ -40,12 +40,12 @@ const iconMap: { [key: string]: JSX.Element } = {
function CategoryBox({ category }: { category: RelatedDocumentsCategory }) {
return (
<div className="rounded-lg border border-slate-200 bg-white/60 p-5 dark:border-slate-800/40 dark:bg-slate-800/60">
<div className="rounded-lg border border-zinc-200 bg-white/60 p-5 dark:border-zinc-800/40 dark:bg-zinc-800/60">
<h4 className="mt-0 flex items-center pb-2 text-xl font-bold">
{iconMap[category.id] ?? iconMap.default}
{category.name}
</h4>
<ul className="list-none divide-y divide-slate-300 pl-0 dark:divide-slate-700">
<ul className="list-none divide-y divide-zinc-300 pl-0 dark:divide-zinc-700">
{category.relatedDocuments.map((d) => (
<li
key={d.id}
@@ -53,11 +53,11 @@ function CategoryBox({ category }: { category: RelatedDocumentsCategory }) {
>
<Link
href={d.path}
className="flex flex-grow items-center justify-between no-underline transition-colors ease-out hover:text-blue-700 hover:underline dark:text-sky-500 dark:hover:text-sky-400"
className="flex flex-grow items-center justify-between no-underline transition-colors ease-out hover:text-blue-700 hover:underline dark:text-blue-500 dark:hover:text-blue-400"
prefetch={false}
>
<span>{d.name}</span>
<ArrowRightIcon className="h-4 w-4 text-slate-500 dark:text-slate-400" />
<ArrowRightIcon className="h-4 w-4 text-zinc-500 dark:text-zinc-400" />
</Link>
</li>
))}
@@ -86,9 +86,9 @@ export function TableOfContents({
<Link
href={href}
className={cx(
'block w-full border-l-4 border-slate-200 py-1 pl-3 transition hover:border-slate-500 dark:border-slate-700/40 dark:hover:border-slate-700',
'block w-full border-l-4 border-zinc-200 py-1 pl-3 transition hover:border-zinc-500 dark:border-zinc-700/40 dark:hover:border-zinc-700',
{
'border-slate-500 bg-slate-50 dark:border-slate-700 dark:bg-slate-800/60':
'border-zinc-500 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-800/60':
activeId === item.id && !item.highlightColor,
// region Highlight Color
'border-blue-200 bg-blue-50 hover:border-blue-500 dark:border-blue-700/40 dark:bg-blue-800/40 dark:hover:border-blue-700':
@@ -93,10 +93,10 @@ function FeedbackDialog({
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<DialogPanel className="relative w-full max-w-2xl transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all dark:bg-slate-900">
<DialogPanel className="relative w-full max-w-2xl transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all dark:bg-zinc-900">
<DialogTitle
as="h3"
className="bg-white p-4 text-center text-lg font-medium leading-6 text-slate-700 dark:bg-slate-900 dark:text-slate-400"
className="bg-white p-4 text-center text-lg font-medium leading-6 text-zinc-700 dark:bg-zinc-900 dark:text-zinc-400"
>
What is on your mind?
<button className={styles.closebutton} onClick={closeDialog}>
@@ -154,7 +154,7 @@ function FeedbackDialog({
htmlFor="idea"
tabIndex={0}
onKeyDown={(e) => keydownHandler(e)}
className="inline-flex w-full cursor-pointer items-center justify-between rounded-lg border border-gray-200 bg-white p-5 text-gray-500 hover:bg-slate-50 focus:outline-none focus:ring-1 peer-checked:border-sky-500 peer-checked:text-sky-500 dark:border-gray-700 dark:bg-gray-800 dark:text-slate-400 dark:focus-within:ring-sky-500 dark:hover:bg-slate-800 dark:hover:text-gray-300 dark:peer-checked:text-sky-500"
className="inline-flex w-full cursor-pointer items-center justify-between rounded-lg border border-gray-200 bg-white p-5 text-gray-500 hover:bg-zinc-50 focus:outline-none focus:ring-1 peer-checked:border-blue-500 peer-checked:text-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-zinc-400 dark:focus-within:ring-blue-500 dark:hover:bg-zinc-800 dark:hover:text-gray-300 dark:peer-checked:text-blue-500"
>
<div className="block">
<div className="w-full text-lg font-semibold">Idea</div>
@@ -242,10 +242,10 @@ function FeedbackDialog({
onClick={submitFeedback}
disabled={formDisabled}
className={cx(
'rounded-md border border-slate-200 bg-white px-4 py-2 text-base font-medium text-slate-600 dark:border-slate-700 dark:bg-slate-800/60 dark:text-slate-400',
'rounded-md border border-zinc-200 bg-white px-4 py-2 text-base font-medium text-zinc-600 dark:border-zinc-700 dark:bg-zinc-800/60 dark:text-zinc-400',
{ 'cursor-not-allowed': formDisabled },
{
'focus-within:ring-blue-500 hover:bg-slate-50 focus:z-10 focus:outline-none focus:ring-1 dark:focus-within:ring-sky-500 dark:hover:bg-slate-800':
'focus-within:ring-blue-500 hover:bg-zinc-50 focus:z-10 focus:outline-none focus:ring-1 dark:focus-within:ring-blue-500 dark:hover:bg-zinc-800':
!formDisabled,
}
)}
@@ -129,7 +129,7 @@ export function Content({
<div
aria-hidden="true"
data-tooltip="Schema type"
className="relative inline-flex rounded-md border border-slate-200 bg-slate-50 px-4 py-2 text-xs font-medium uppercase dark:border-slate-700 dark:bg-slate-800/60"
className="relative inline-flex rounded-md border border-zinc-200 bg-zinc-50 px-4 py-2 text-xs font-medium uppercase dark:border-zinc-700 dark:bg-zinc-800/60"
>
{schemaViewModel.type}
</div>
@@ -157,7 +157,7 @@ export function Content({
href={schemaViewModel.packageUrl}
title="See package information"
className={cx(
'relative inline-flex items-center rounded-l-md border border-slate-200 bg-white px-4 py-2 text-xs font-medium text-slate-600 focus-within:ring-blue-500 hover:bg-slate-50 focus:z-10 focus:outline-none focus:ring-1 dark:border-slate-700 dark:bg-slate-800/60 dark:text-slate-400 dark:focus-within:ring-sky-500 dark:hover:bg-slate-800',
'relative inline-flex items-center rounded-l-md border border-zinc-200 bg-white px-4 py-2 text-xs font-medium text-zinc-600 focus-within:ring-blue-500 hover:bg-zinc-50 focus:z-10 focus:outline-none focus:ring-1 dark:border-zinc-700 dark:bg-zinc-800/60 dark:text-zinc-400 dark:focus-within:ring-blue-500 dark:hover:bg-zinc-800',
schemaViewModel.packageName.startsWith('@nx/powerpack')
? 'rounded-md'
: 'rounded-l-md'
@@ -173,7 +173,7 @@ export function Content({
target="_blank"
rel="noreferrer"
title="See this schema on GitHub"
className="relative -ml-px inline-flex items-center rounded-r-md border border-slate-200 bg-white px-4 py-2 text-xs font-medium text-slate-600 focus-within:ring-blue-500 hover:bg-slate-50 focus:z-10 focus:outline-none focus:ring-1 dark:border-slate-700 dark:bg-slate-800/60 dark:text-slate-400 dark:focus-within:ring-sky-500 dark:hover:bg-slate-800"
className="relative -ml-px inline-flex items-center rounded-r-md border border-zinc-200 bg-white px-4 py-2 text-xs font-medium text-zinc-600 focus-within:ring-blue-500 hover:bg-zinc-50 focus:z-10 focus:outline-none focus:ring-1 dark:border-zinc-700 dark:bg-zinc-800/60 dark:text-zinc-400 dark:focus-within:ring-blue-500 dark:hover:bg-zinc-800"
>
<svg
className="mr-2 h-4 w-4"
@@ -237,7 +237,7 @@ export function Content({
{/* We remove the top description on sub property lookup */}
{!schemaViewModel.subReference && (
<>
<div className="prose prose-slate dark:prose-invert max-w-none">
<div className="prose prose-zinc dark:prose-invert max-w-none">
{vm.markdown.header}
{vm.markdown.customContent}
{vm.markdown.usageAndExamples}
@@ -269,7 +269,7 @@ export function Content({
setPresets(p.keys);
}}
type="button"
className="relative inline-flex items-center rounded-md border border-slate-200 bg-white px-4 py-2 text-xs font-medium text-slate-600 hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-800/60 dark:text-slate-400 dark:hover:bg-slate-800"
className="relative inline-flex items-center rounded-md border border-zinc-200 bg-white px-4 py-2 text-xs font-medium text-zinc-600 hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-800/60 dark:text-zinc-400 dark:hover:bg-zinc-800"
>
{p.name}
</button>
@@ -278,7 +278,7 @@ export function Content({
<button
onClick={() => setPresets([])}
type="button"
className="relative inline-flex items-center rounded-md border border-slate-200 bg-white px-4 py-2 text-xs font-medium text-slate-600 hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-800/60 dark:text-slate-400 dark:hover:bg-slate-800"
className="relative inline-flex items-center rounded-md border border-zinc-200 bg-white px-4 py-2 text-xs font-medium text-zinc-600 hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-800/60 dark:text-zinc-400 dark:hover:bg-zinc-800"
>
Reset <XCircleIcon className="ml-1.5 h-4 w-4" />
</button>
@@ -286,7 +286,7 @@ export function Content({
</div>
</>
)}
<div className="rounded-md border border-slate-200 p-0.5 dark:border-slate-700">
<div className="rounded-md border border-zinc-200 p-0.5 dark:border-zinc-700">
<SchemaEditor
packageName={schemaViewModel.packageName}
schemaName={schemaViewModel.schemaMetadata.name}
@@ -21,7 +21,7 @@ export function MigrationViewer({
<summary className="cursor-pointer">
<Heading3 title={schema.name}></Heading3>
</summary>
<div className="prose prose-slate dark:prose-invert mb-6 ml-5">
<div className="prose prose-zinc dark:prose-invert mb-6 ml-5">
<p>{schema.description}</p>
<div className="my-1">
<strong>Version</strong>: {schema.version}
@@ -79,7 +79,7 @@ export function PackageSchemaList({
url: vm.seo.imageUrl,
width: 1600,
height: 800,
alt: 'Nx: Smart Repos · Fast Builds',
alt: 'Nx: Smart Monorepos · Fast Builds',
type: 'image/jpeg',
},
],
@@ -125,7 +125,7 @@ export function PackageSchemaList({
<div className="h-12">{/* SPACER */}</div>
<Heading2 title={'Migrations'} />
<ul className="divide-y divide-slate-100 dark:divide-slate-800">
<ul className="divide-y divide-zinc-100 dark:divide-zinc-800">
{!!filesAndLabels.length
? filesAndLabels.map((schema) =>
typeof schema === 'string' ? (
@@ -79,7 +79,7 @@ export function PackageSchemaSubList({
url: vm.seo.imageUrl,
width: 1600,
height: 800,
alt: 'Nx: Smart Repos · Fast Builds',
alt: 'Nx: Smart Monorepos · Fast Builds',
type: 'image/jpeg',
},
],
@@ -116,7 +116,7 @@ export function PackageSchemaSubList({
) : null}
{vm.type === 'migration' ? (
<ul className="divide-y divide-slate-100 dark:divide-slate-800">
<ul className="divide-y divide-zinc-100 dark:divide-zinc-800">
{filesAndLabels.map((schema) =>
typeof schema === 'string' ? (
<VersionLabelListItem
@@ -142,7 +142,7 @@ export function PackageSchemaSubList({
export const VersionLabelListItem = ({ label }: { label: string }) => {
return label ? (
<li className="relative flex px-1 pt-2 transition focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-offset-2 hover:bg-slate-50 dark:focus-within:ring-sky-500 dark:hover:bg-slate-800/60">
<li className="relative flex px-1 pt-2 transition focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-offset-2 hover:bg-zinc-50 dark:focus-within:ring-blue-500 dark:hover:bg-zinc-800/60">
<div className="pt-2">
<span className="text-sm font-bold">
<Heading2 title={label} />
@@ -56,7 +56,7 @@ export function PackageSchemaViewer({
url: vm.seo.imageUrl,
width: 1600,
height: 800,
alt: 'Nx: Smart Repos · Fast Builds',
alt: 'Nx: Smart Monorepos · Fast Builds',
type: 'image/jpeg',
},
],
@@ -24,13 +24,13 @@ export const ParameterView = (props: {
{props.alias && (
<span
data-tooltip="Property alias"
className="relative -top-0.5 inline-flex rounded-md px-2 text-xs font-semibold leading-5 dark:bg-slate-700"
className="relative -top-0.5 inline-flex rounded-md px-2 text-xs font-semibold leading-5 dark:bg-zinc-700"
>
{props.alias}
</span>
)}
{props.required && (
<span className="relative -top-0.5 inline-flex rounded-md bg-slate-100 px-2 text-xs font-semibold uppercase leading-5 dark:bg-slate-700">
<span className="relative -top-0.5 inline-flex rounded-md bg-zinc-100 px-2 text-xs font-semibold uppercase leading-5 dark:bg-zinc-700">
Required
</span>
)}
@@ -68,7 +68,7 @@ export const ParameterView = (props: {
)}
</div>
<div className="prose prose-slate dark:prose-invert -mt-4 max-w-none">
<div className="prose prose-zinc dark:prose-invert -mt-4 max-w-none">
{
renderMarkdown(props.description, {
filePath: '',
@@ -78,7 +78,7 @@ export const ParameterView = (props: {
{props.deprecated &&
typeof (props.schema as any)['x-deprecated'] === 'string' ? (
<div className="prose prose-slate dark:prose-invert mt-2 rounded-md bg-red-100 px-4 text-red-800 dark:bg-red-800 dark:text-red-100">
<div className="prose prose-zinc dark:prose-invert mt-2 rounded-md bg-red-100 px-4 text-red-800 dark:bg-red-800 dark:text-red-100">
{
renderMarkdown(String((props.schema as any)['x-deprecated']), {
filePath: '',
@@ -5,7 +5,7 @@ import Link from 'next/link';
export const Heading1 = ({ title }: { title: string }) => (
<h1
id={slugify(title)}
className="group mb-5 text-4xl font-extrabold tracking-tight text-slate-900 dark:text-slate-100"
className="group mb-5 text-4xl font-extrabold tracking-tight text-zinc-900 dark:text-zinc-100"
>
<span>{title}</span>
<Link aria-hidden="true" tabIndex={-1} href={'#' + slugify(title)}>
@@ -20,7 +20,7 @@ export const Heading1 = ({ title }: { title: string }) => (
export const Heading2 = ({ title }: { title: string }) => (
<h2
id={slugify(title)}
className="group mb-5 text-2xl font-bold tracking-tight text-slate-800 dark:text-slate-200"
className="group mb-5 text-2xl font-bold tracking-tight text-zinc-800 dark:text-zinc-200"
>
<span>{title}</span>
<Link aria-hidden="true" tabIndex={-1} href={'#' + slugify(title)}>
@@ -35,7 +35,7 @@ export const Heading2 = ({ title }: { title: string }) => (
export const Heading3 = ({ title }: { title: string }) => (
<h3
id={slugify(title)}
className="group text-xl font-semibold tracking-tight text-slate-700 dark:text-slate-300"
className="group text-xl font-semibold tracking-tight text-zinc-700 dark:text-zinc-300"
>
<span>{title}</span>
<Link aria-hidden="true" tabIndex={-1} href={'#' + slugify(title)}>
@@ -16,7 +16,7 @@ export function DocumentList({
documents: DocumentMetadata[];
}): JSX.Element {
return (
<ul className="divide-y divide-slate-100 dark:divide-slate-800">
<ul className="divide-y divide-zinc-100 dark:divide-zinc-800">
{!!documents.length ? (
documents.map((guide) => (
<DocumentListItem key={guide.id} document={guide} />
@@ -36,9 +36,9 @@ function DocumentListItem({
return (
<li
key={document.name}
className="relative flex px-2 py-2 transition focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-offset-2 hover:bg-slate-50 dark:focus-within:ring-sky-500 dark:hover:bg-slate-800/60"
className="relative flex px-2 py-2 transition focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-offset-2 hover:bg-zinc-50 dark:focus-within:ring-blue-500 dark:hover:bg-zinc-800/60"
>
<div className="flex-shrink-0 self-start rounded-lg border-slate-200 bg-slate-100 p-2 dark:border-slate-600 dark:bg-slate-700">
<div className="flex-shrink-0 self-start rounded-lg border-zinc-200 bg-zinc-100 p-2 dark:border-zinc-600 dark:bg-zinc-700">
<DocumentIcon className="h-5 w-5" role="img" />
</div>
<div className="ml-3 py-2">
@@ -61,7 +61,7 @@ export function SchemaList({
type: 'executor' | 'generator';
}): JSX.Element {
return (
<ul className="divide-y divide-slate-100 dark:divide-slate-800">
<ul className="divide-y divide-zinc-100 dark:divide-zinc-800">
{!!files.length ? (
files.map((schema) => (
<SchemaListItem key={schema.name} file={schema} />
@@ -77,9 +77,9 @@ function SchemaListItem({ file }: { file: FileMetadata }): JSX.Element {
return (
<li
key={file.name}
className="relative flex px-2 py-2 transition focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-offset-2 hover:bg-slate-50 dark:focus-within:ring-sky-500 dark:hover:bg-slate-800/60"
className="relative flex px-2 py-2 transition focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-offset-2 hover:bg-zinc-50 dark:focus-within:ring-blue-500 dark:hover:bg-zinc-800/60"
>
<div className="flex-shrink-0 self-start rounded-lg border-slate-200 bg-slate-100 p-2 dark:border-slate-600 dark:bg-slate-700">
<div className="flex-shrink-0 self-start rounded-lg border-zinc-200 bg-zinc-100 p-2 dark:border-zinc-600 dark:bg-zinc-700">
{file.type === 'executor' ? (
<CpuChipIcon className="h-5 w-5" role="img" />
) : (
@@ -99,7 +99,7 @@ function SchemaListItem({ file }: { file: FileMetadata }): JSX.Element {
</span>
)}
</p>
<div className="prose prose-slate dark:prose-invert prose-sm">
<div className="prose prose-zinc dark:prose-invert prose-sm">
{
renderMarkdown(file.description, {
filePath: '',
@@ -117,10 +117,10 @@ function EmptyList({
type: 'executor' | 'generator' | 'document';
}): JSX.Element {
return (
<li className="relative flex px-2 py-2 transition focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-offset-2 hover:bg-slate-50 dark:focus-within:ring-sky-500 dark:hover:bg-slate-800/60">
<div className="flex-shrink-0 self-start rounded-lg border-slate-200 bg-slate-100 p-2 dark:border-slate-600 dark:bg-slate-700">
<li className="relative flex px-2 py-2 transition focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-offset-2 hover:bg-zinc-50 dark:focus-within:ring-blue-500 dark:hover:bg-zinc-800/60">
<div className="flex-shrink-0 self-start rounded-lg border-zinc-200 bg-zinc-100 p-2 dark:border-zinc-600 dark:bg-zinc-700">
<InformationCircleIcon
className="h-5 w-5 flex-shrink-0 rounded-md border-slate-200 bg-slate-50 dark:bg-slate-700 dark:bg-slate-800"
className="h-5 w-5 flex-shrink-0 rounded-md border-zinc-200 bg-zinc-50 dark:bg-zinc-700 dark:bg-zinc-800"
role="img"
/>
</div>
@@ -136,7 +136,7 @@ function EmptyList({
{type} available for this package yet!
</Link>
</p>
<div className="prose prose-slate dark:prose-invert prose-sm">
<div className="prose prose-zinc dark:prose-invert prose-sm">
<a
href="https://github.com/nrwl/nx/discussions"
target="_blank"
@@ -11,7 +11,7 @@ export function TopSchemaLayout({
<div className="mb-8 flex w-full items-center space-x-2">
<div className="w-full flex-grow">
<div
className="relative inline-flex rounded-md border border-slate-200 bg-slate-50 px-4 py-2 text-xs font-medium uppercase dark:border-slate-700 dark:bg-slate-800/60"
className="relative inline-flex rounded-md border border-zinc-200 bg-zinc-50 px-4 py-2 text-xs font-medium uppercase dark:border-zinc-700 dark:bg-zinc-800/60"
aria-hidden="true"
data-tooltip="Installable Package"
>
@@ -26,7 +26,7 @@ export function TopSchemaLayout({
rel="noreferrer"
aria-hidden="true"
title="See package on GitHub"
className="relative inline-flex items-center rounded-md border border-slate-200 bg-slate-50 px-4 py-2 text-xs font-medium dark:border-slate-700 dark:bg-slate-800/60"
className="relative inline-flex items-center rounded-md border border-zinc-200 bg-zinc-50 px-4 py-2 text-xs font-medium dark:border-zinc-700 dark:bg-zinc-800/60"
>
<svg
className="mr-2 h-4 w-4"
@@ -92,15 +92,15 @@ export function AlgoliaSearch({
type="button"
ref={searchButtonRef}
onClick={handleOpen}
className="flex w-full items-center rounded-md bg-white px-2 py-1.5 text-sm leading-4 ring-1 ring-slate-300 transition dark:bg-slate-700 dark:ring-slate-900"
className="flex w-full items-center rounded-md bg-white px-2 py-1.5 text-sm leading-4 ring-1 ring-zinc-300 transition dark:bg-zinc-700 dark:ring-zinc-900"
>
<MagnifyingGlassIcon className="h-4 w-4 flex-none" />
<span className="mx-3 inline-flex text-xs text-slate-300 md:text-sm dark:text-slate-400">
<span className="mx-3 inline-flex text-xs text-zinc-300 md:text-sm dark:text-zinc-400">
Search
</span>
<span
style={{ opacity: browserDetected ? '1' : '0' }}
className="ml-auto hidden flex-none rounded-md border border-slate-200 bg-slate-50 px-1 py-0.5 text-xs font-semibold text-slate-500 md:block dark:border-slate-700 dark:bg-slate-800/60"
className="ml-auto hidden flex-none rounded-md border border-zinc-200 bg-zinc-50 px-1 py-0.5 text-xs font-semibold text-zinc-500 md:block dark:border-zinc-700 dark:bg-zinc-800/60"
>
<span className="sr-only">Press </span>
<kbd className="font-sans">
@@ -122,7 +122,7 @@ export function AlgoliaSearch({
>
<span
style={{ opacity: browserDetected ? '1' : '0' }}
className="ml-auto block flex-none rounded-md border border-slate-200 bg-slate-50/60 px-1 py-0.5 text-xs font-semibold text-slate-400 transition hover:text-slate-500 dark:border-slate-700 dark:bg-slate-800/60 dark:text-slate-500 dark:hover:text-slate-400"
className="ml-auto block flex-none rounded-md border border-zinc-200 bg-zinc-50/60 px-1 py-0.5 text-xs font-semibold text-zinc-400 transition hover:text-zinc-500 dark:border-zinc-700 dark:bg-zinc-800/60 dark:text-zinc-500 dark:hover:text-zinc-400"
>
<span className="sr-only">Press </span>
<kbd className="font-sans">
+1 -1
View File
@@ -3,5 +3,5 @@ import { test, expect } from '@playwright/test';
test('should display the primary heading', async ({ page }) => {
await page.goto('/');
const heading = page.locator('[data-cy="primary-heading"]');
await expect(heading).toContainText('Smart ReposFast Builds');
await expect(heading).toContainText('Smart MonoreposFast Builds');
});
+1 -1
View File
@@ -31,7 +31,7 @@ export async function generateMetadata(
url: post.ogImage,
width: 800,
height: 421,
alt: 'Nx: Smart, Fast and Extensible Build System',
alt: 'Nx: Smart, Fast and Extensible Monorepo Platform',
type: `image/${post.ogImageType}`,
},
...previousImages,
+1 -1
View File
@@ -20,7 +20,7 @@ export const metadata: Metadata = {
url: 'https://nx.dev/socials/nx-media.png',
width: 800,
height: 421,
alt: 'Nx: Smart Repos · Fast Builds',
alt: 'Nx: Smart Monorepos · Fast Builds',
type: 'image/jpeg',
},
],
+1 -1
View File
@@ -28,7 +28,7 @@ export const metadata: Metadata = {
url: 'https://nx.dev/socials/nx-media.png',
width: 800,
height: 421,
alt: 'Nx: Smart Repos · Fast Builds',
alt: 'Nx: Smart Monorepos · Fast Builds',
type: 'image/jpeg',
},
],
+1 -1
View File
@@ -30,7 +30,7 @@ export const metadata: Metadata = {
url: 'https://nx.dev/socials/nx-media.png',
width: 800,
height: 421,
alt: 'Nx: Smart Repos · Fast Builds',
alt: 'Nx: Smart Monorepos · Fast Builds',
type: 'image/jpeg',
},
],
+1 -1
View File
@@ -119,7 +119,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
}}
/>
</head>
<body className="h-full bg-white text-slate-700 antialiased selection:bg-blue-500 selection:text-white dark:bg-slate-900 dark:text-slate-400 dark:selection:bg-sky-500">
<body className="h-full bg-white text-zinc-700 antialiased selection:bg-blue-500 selection:text-white dark:bg-zinc-900 dark:text-zinc-400 dark:selection:bg-blue-500">
<GlobalSearchHandler />
{children}
{bannerCollection.map((bannerConfig) => {

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