Compare commits

...

41 Commits

Author SHA1 Message Date
Juri 9ea3e0dab2 feat(nx-dev): llm trying new routing options 2025-03-31 10:48:04 +02:00
Juri 74006809be fix(nx-dev): llm route handling 2025-03-31 10:48:04 +02:00
Juri 13f1e03047 feat(nx-dev): add .md routes for LLMs 2025-03-31 10:48:04 +02:00
Juri 3bb6abea9f feat(nx-dev): render for LLMs 2025-03-31 10:48:04 +02:00
Nicholas Cunningham bf8848da95 fix(webpack): add extension alias support for handling ESM libs (#30513)
<!-- 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 -->
Currently, if you have a webpack application that uses out
NxWebpackAppPlugin and has a non-buildable lib that has exports with
extension enabled for example:`export * from './lib/lib8446520.js';`.
The app fails to build.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
When using webpack and including libraries that contain extension it
should resolve.

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

Fixes #30492
2025-03-28 11:51:20 -06:00
Leosvel Pérez Espinosa 90ff03d42d fix(angular): include @angular/google-maps in package updates (#30530)
## Current Behavior

The `@angular/google-maps` package version is not updated when running
`nx migrate`.

## Expected Behavior

The `@angular/google-maps` package version should be updated when
running `nx migrate`.

## Related Issue(s)

Fixes #30523
2025-03-28 12:22:37 +01:00
Nicholas Cunningham 4a3a8241c3 fix(nx-dev): add data-document attribute to improve search (#30524) 2025-03-27 15:19:07 -06:00
Mike Hartington d194248f53 docs(nx-dev): update install steps for java (#30526)
<!-- 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 -->
Update the install instructions for the new java landing page

## 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 #
2025-03-27 20:03:28 +00:00
Juri Strumpflohner ea04a2ed72 feat(nx-dev): add java landing page (#30508)
## Current Behavior
<!-- This is the behavior we have today -->

Nothing there.

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

Adds a new
[/java](https://nx-dev-git-nxdev-gradle-landing-page-nrwl.vercel.app/java)
landing page

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

Fixes #

---------

Co-authored-by: Mike Hartington <mikehartington@gmail.com>
Co-authored-by: Mike Hartington <mhartington@users.noreply.github.com>
2025-03-27 15:01:00 -04:00
Craigory Coppola 3587aeabb7 fix(core): fixup error handling for get generator info (#30525)
Some error handling is missing after #30501
2025-03-27 18:59:36 +00:00
Craigory Coppola 4039970495 fix(misc): init should prompt for cloud when using dot nx folder (#30501)
<!-- 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 nx is installed in the `.nx` directory the init flow is
substantially different. This changes how some prompting works as well
as how plugin installation flows.

## Expected Behavior
The two flows operate in a similar manner.

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

Fixes #
2025-03-27 12:08:57 -04:00
Nicholas Cunningham 415979e554 fix(nextjs): update documentation for buildable libraries and bundler configuration (#30500)
<!-- 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 -->
Currently, the docs does not differentiate between inferred target
outputs and executor outputs.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Since the outputs differ we should highlight the difference in our docs.
Also, if we pass a bundler to the Next.js lib app generator it should
respect that option.

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

Fixes #
2025-03-27 09:50:46 -06:00
Colum Ferry c8e4e0f9ef fix(rspack): remove unused plugin-minify (#30521)
## Current Behavior
`@nx/rspack` set ups currently install `@rspack/plugin-minify` which has
been deprecated. It is also unused in the configurations that are
generated.

## Expected Behavior
Remove unused the unused `@rspack/plugin-minify`

## Related Issue(s)

Fixes #30318
2025-03-27 13:18:59 +00:00
Colum Ferry d12a4f4b12 fix(module-federation): the module federation package depends on rspack (#30520)
## Current Behavior
The `@nx/module-federation` depends on the `@rspack/core` package but it
is currently only set as a `peerDep` meaning users need to install the
package manually.

## Expected Behavior
As the package depends on `@rspack/core`, make sure it is set as a
dependency

## Related Issue(s)

Fixes #30307
2025-03-27 13:18:37 +00:00
Colum Ferry e08f3479e6 fix(webpack): allow baseHref to not be set #30291 (#30519)
## Current Behavior
The `NxAppWebpackPlugin` and `NxAppRspackPlugin` both always set `<base
href="` even when it is set to undefined.

## Expected Behavior
If `baseHref` is set to false, do not set `<base href`.

## Related Issues
#30291
2025-03-27 13:18:16 +00:00
Jack Stevenson 9fa8930afe fix(vite): ensure test target dependency is not duplicated (#30289)
<!-- 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
Every time the vitest generator was run a new duplicate '^build'
dependency was added to the target defaults for the test task.

## Expected Behavior
No duplicate entries added!

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

Fixes #30288

---------

Co-authored-by: Colum Ferry <cferry09@gmail.com>
2025-03-27 12:46:07 +00:00
Leosvel Pérez Espinosa bae3acd48e fix(misc): handle outputs with globs when normalizing tsconfig path mappings for buidable libraries (#30506)
## Current Behavior

If a buildable library depends on another buildable library that has
`outputs` using globs the build fails when using an executor that remaps
the dependency TypeScript path mappings to the build outputs.

## Expected Behavior

Building a buildable library using an executor that remaps the
dependency TypeScript path mappings to the build outputs should succeed.
The remapping logic should identify and replace the glob patterns and
keep the fixed part of the pattern (no segment with wildcards).

Note: additionally, an obsolete check was removed from the
`@nx/angular:package` and `@nx/angular:delegate-build`.

## Related Issue(s)

Fixes #30041
2025-03-27 10:31:53 +01:00
Leosvel Pérez Espinosa e4e9973db3 fix(js): infer typecheck task for buildable libraries with tsc (#30505)
## Current Behavior

Buildable libraries using `tsc` for `build` don't get a `typecheck`
task, which results in test files not being type-checked.

## Expected Behavior

The `typecheck` task should be inferred, and test files should be
type-checked.

## Related Issue(s)

Fixes #
2025-03-27 09:35:17 +01:00
Nicholas Cunningham 1a235d7236 fix(react): react-router should work with jest out of the box (#30487)
Jest should be compatible with react-router out of the box.

<!-- 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 -->
Currently, there are two issues when using `jest` with react-router out
of the box

1. Test files are not included from `tsconfig`
2. While running the test `jsdom` is missing Node's `TextEncoder` and
`TextDecoder` so compilation fails.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Running a test should work without issues when you create a react-router
app with Jest.

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

Fixes #30387
2025-03-26 10:44:28 -06:00
Isaac Mann b371512462 docs(core): add champions (#30498)
Add Kevin Oliveira and Guilherme Siqinelli to the champions list
2025-03-26 11:28:49 -04:00
Isaac Mann b21af21657 docs(core): api icons fallback (#30499)
Add rsbuild icon and update rspack icon. `nx-api` icons default to Nx
icon if none is specified.
2025-03-26 11:28:37 -04:00
Jonathan Cammisuli e6d8b3913b fix(core): add more detection for ci platforms (#30507) 2025-03-26 15:16:32 +00:00
Leosvel Pérez Espinosa ab311c0f07 fix(js): normalize tsconfig include paths correctly in @nx/js/typescript plugin (#30496)
## Current Behavior

The `@nx/js/typescript` plugin infers incorrect inputs for tsconfig
`include` patterns when their last segment doesn't contain a wildcard or
an extension (e.g. `"include": ["src"]`).

## Expected Behavior

The `@nx/js/typescript` plugin should [normalize such `include`
patterns](https://www.typescriptlang.org/tsconfig/#include) and infer
valid inputs.

## Related Issue(s)

Fixes #30014
2025-03-26 15:26:05 +01:00
Richard Roozenboom 9cd7579040 fix(core): ensure local plugin is transpiled when using index files (#30133)
<!-- 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 -->
In some use cases the local plugins generators and executors are not
transpiled, which causes an error when you execute them. see for example
![Screenshot 2025-02-21 at 09 07
10](https://github.com/user-attachments/assets/1fd03a4b-1ac4-4827-b240-b4ea88d491a2)

This issue is introduced by this PR #29539 and is actually a regression
of an error we had in NX 18, see #22958

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
This PR fixes this issue by updating the if statement to register the
typescript transpiler when this is not done yet.

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

Fixes #30132
2025-03-26 10:00:26 -04:00
Leosvel Pérez Espinosa 4bd64770a5 feat(linter): support eslint-config-prettier v10 (#30480)
Add support for `eslint-config-prettier` v10.

## Current Behavior

## Expected Behavior

## Related Issue(s)

Fixes #30145
2025-03-26 07:45:46 -04:00
Craigory Coppola 9dd4766059 fix(core): init should use pr version when specified (#30497)
<!-- 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
`nx init` always either installs `next` or `latest` versions of Nx. The
intent here is that running `npx nx init` should always provide the
latest version of Nx, and we added a condition to use `next` if we
detected a beta version of Nx was running to facilitate easier testing
of RC and beta releases. The full logic to determine which version of Nx
to setup is below:

```ts
const version =
    process.env.NX_VERSION ?? (prerelease(nxVersion) ? 'next' : 'latest');
```

The goal here was to avoid hitting the `npx` cache and accidentally
creating new workspaces with an older Nx version. This logic falls apart
a bit when considering prereleases though, as `npx nx@next init` would
always check the tag to make sure its up to date rather than using a
cached version. This is the case when providing **_any_** tag to `npx`.

A bad side effect of the above is that when trying to test PR builds and
the like, `nx init` will never setup the PR build when running `npx
nx@0.0.0-pr....` opting instead to setup `next`.

## Expected Behavior
Running `npx nx@0.0.0-pr.... init` sets up an nx workspace using the
specified PR release.

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

Fixes #
2025-03-25 20:36:15 -04:00
Juri 50561ff009 fix(nx-dev): adjust alignment on the pricing addon descriptions 2025-03-25 16:32:57 +01:00
Leosvel Pérez Espinosa 5dbe040374 fix(misc): override customConditions when using an incompatible module resolution (#30477)
## Current Behavior

In a few different places (Crystal plugins, executors, generators,
helpers) where `ts-node` compiler options are overridden and
`moduleResolution` is being set to something other than `node16`,
`nodenext`, or `bundler`, an error can occur if the `customConditions`
compiler option is being used.

## Expected Behavior

When overriding the `ts-node` compiler options and changing forcing
`moduleResolution` to have a value that's incompatible with
`customConditions`, the latter should be unset (set to `null`) to avoid
errors.

## Related Issue(s)

Fixes #
2025-03-25 07:51:02 -04:00
Miroslav Jonaš 30781f7fe3 feat(repo): replace explicit matrix with dynamic parsed from json (#30120)
This PR replaces the cumbersome explicit matrix in `e2e-matrix.yml` with
a dynamic matrix built from the input JSON data.

## 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 #
2025-03-24 11:56:47 -04:00
Nicholas Cunningham 2c55685492 fix(react): update react router logic with selected bundler (#30399)
<!-- 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 -->
Currently, when you generate a react app and select `--use-react-router`
and `--bundler=` any other bundler except `vite` you would be forced
into using `vite`

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
When you generate a React app and opt into using `--use-react-router`
with `--bundler=webpack` (for example) you will get an error stating the
React Router can only be used with `vite`.

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

Fixes #
2025-03-24 09:45:23 -06:00
Leosvel Pérez Espinosa cfe32c2560 feat(misc): add --useProjectJson flag to CNW (#30475)
## Current Behavior

Creating new workspaces since Nx 20.6.0 will generate the Nx
configuration in `package.json` files. This is intended, given that it
is part of the new setup using TypeScript Project References and Package
Manager Workspaces, but there's no way to choose to generate the Nx
configuration in `project.json` files. Project generators do allow to
choose but there's no way to do it when creating a new workspace. This
forces users who want to use `project.json` files to generate an empty
workspace and then use a project generator.

## Expected Behavior

When creating a new Nx workspace, users can provide an option
(`--use-project-json`) to generate the Nx configuration in
`project.json` files.

## Related Issue(s)

Fixes #30464
2025-03-24 08:42:18 -04:00
Nicolas Beaussart 3d710ce923 fix(react): only add release config for publishable librarires (#30474) 2025-03-24 13:48:28 +04:00
Tine Kondo cecd60710b fix(core): fix local registry not being considered when using bun (#30459) 2025-03-23 22:29:31 +04:00
James Henry 3be73ede0f chore(repo): fix lockfile (#30467) 2025-03-23 13:59:11 +04:00
Jordan 12af01453a docs(core): improve grammar of keep-nx-versions-in-sync (#30308) 2025-03-22 15:51:36 +04:00
James Garbutt 8575fa1495 cleanup(angular): migrate to picomatch (#30081) 2025-03-22 15:48:22 +04:00
Nate Kidwell 013da9ea1c chore: include bun mention in bug.yml (#30306) 2025-03-22 15:46:36 +04:00
Jason Jean 1c238c0a74 fix(core): pass the project graph into the batch instead of recreating (#30455)
<!-- 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 -->

Running a batch executor recreates the project graph

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

The project graph is passed in when running a batch executor so that it
doesn't have to get recreated.

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

Fixes #
2025-03-21 22:07:24 +00:00
Leosvel Pérez Espinosa 176c792e34 feat(misc): set a development conditional export for buildable libraries when using the ts solution setup (#30451)
Update library generators to set a `development` conditional export for
buildable libraries' `package.json` files and set the
`customConditions` compiler options in `tsconfig.base.json`. This will
only be done for workspaces using the TS solution setup.

## Current Behavior

## Expected Behavior

## Related Issue(s)

Fixes #
2025-03-21 17:00:25 -04:00
Jack Hsu 533e9ffc25 docs(react): update react monorepo tutorial (#30454)
This PR updates the React monorepo tutorial.

- Remove video embeds since the content is outdated (e.g. shows old TS
setup) -- we have a separate task to update and add back later
- Update the generated files to ensure they are in sync
- Update to show Playwright instead of Cypress since that is the default
- Remove one mention of `project.json` for configuration, and point to
the Inferred Tasks page instead to learn how tasks are automatically
configured
- Update code example for `apps/react-store/src/app/app.tsx` to render
`Welcome react-store` instead of `Home` so that unit and e2e tests still
pass without having to update those as well (better for the flow of the
tutorial)
2025-03-21 16:45:43 -04:00
Colum Ferry 487aa6fa78 feat(module-federation): add ssr support to rspack crystal plugin (#30437)
## Current Behavior
The current `NxModuleFederationPlugin` does not support SSR


## Expected Behavior
The current `NxModuleFederationPlugin` supports SSR
2025-03-21 15:17:45 +00:00
177 changed files with 5768 additions and 1437 deletions
+1 -1
View File
@@ -60,7 +60,7 @@ body:
label: Package Manager Version
description: |
If `nx report` doesn't work, please provide the name and the version of your package manager.
You can get version information by running `yarn --version`, `pnpm --version` or `npm --version`, depending on the package manager used.
You can get version information by running `PACKAGE_MANAGER --version`, where PACKAGE_MANAGER is any of `yarn`, `pnpm`, `bun` or `npm`, depending on the package manager used.
- type: checkboxes
id: os
attributes:
+47 -296
View File
@@ -24,24 +24,33 @@ jobs:
os:
- ubuntu-latest
- macos-latest
- windows-latest
node_version:
- 18
- 20
- 22
# - 23
exclude:
# run just node v20 on macos
# run just node v20 on macos and windows
- os: macos-latest
node_version: 18
- os: macos-latest
node_version: 22
# - os: macos-latest
# node_version: 23
- os: windows-latest
node_version: 18
- os: windows-latest
node_version: 22
# - os: windows-latest
# node_version: 23
name: Cache install (${{ matrix.os }}, node v${{ matrix.node_version }})
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -99,300 +108,41 @@ jobs:
if: steps.cache-cypress.outputs.cache-hit != 'true'
run: npx cypress install
prepare-matrix:
name: Prepare matrix combinations
if: ${{ github.repository_owner == 'nrwl' }}
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.process-json.outputs.MATRIX }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Process matrix data
id: process-json
run:
echo "MATRIX=$(npx tsx .github/workflows/nightly/process-matrix.ts | jq -c .)" >> $GITHUB_OUTPUT
e2e:
if: ${{ github.repository_owner == 'nrwl' }}
needs: preinstall
needs:
- preinstall
- prepare-matrix
permissions:
contents: read
runs-on: ${{ matrix.os }}
timeout-minutes: 90
strategy:
matrix:
os:
- ubuntu-latest
- macos-latest
node_version:
- 18
- 20
- 22
# - 23
package_manager:
- npm
- yarn
- pnpm
project:
- e2e-angular
- e2e-cypress
- e2e-detox
- e2e-esbuild
- e2e-eslint
- e2e-expo
- e2e-gradle
- e2e-jest
- e2e-js
- e2e-lerna-smoke-tests
- e2e-next
- e2e-node
- e2e-nuxt
- e2e-nx-init
- e2e-nx
- e2e-playwright
- e2e-plugin
- e2e-react
- e2e-react-native
- e2e-release
- e2e-remix
- e2e-rollup
- e2e-storybook
- e2e-vite
- e2e-vue
- e2e-web
- e2e-webpack
- e2e-workspace-create
include:
# os short names
- os: ubuntu-latest
os_name: 'Linux'
- os: macos-latest
os_name: 'MacOS'
# test timeouts
- os: ubuntu-latest
os_timeout: 60
- os: macos-latest
os_timeout: 90
# codeowner groups
- project: e2e-angular
codeowners: 'S04SS457V38'
- project: e2e-cypress
codeowners: 'S04T16BTJJY'
- project: e2e-detox
codeowners: 'S04TNCNJG5N'
- project: e2e-esbuild
codeowners: 'S04SJ6HHP0X'
- project: e2e-expo
codeowners: 'S04TNCNJG5N'
- project: e2e-gradle
codeowners: 'S04TNCNJG5N'
- project: e2e-jest
codeowners: 'S04T16BTJJY'
- project: e2e-js
codeowners: 'S04SJ6HHP0X'
- project: e2e-lerna-smoke-tests
codeowners: 'S04TNCVEETS'
- project: e2e-eslint
codeowners: 'S04SYJGKSCT'
- project: e2e-next
codeowners: 'S04TNCNJG5N'
- project: e2e-node
codeowners: 'S04SJ6HHP0X'
- project: e2e-nx-init
codeowners: 'S04SYHYKGNP'
- project: e2e-nx
codeowners: 'S04SYHYKGNP'
- project: e2e-plugin
codeowners: 'S04SYHYKGNP'
- project: e2e-release
codeowners: 'S04SYHYKGNP'
- project: e2e-react
codeowners: 'S04TNCNJG5N'
- project: e2e-react-native
codeowners: 'S04TNCNJG5N'
- project: e2e-web
codeowners: 'S04SJ6PL98X'
- project: e2e-rollup
codeowners: 'S04SJ6PL98X'
- project: e2e-storybook
codeowners: 'S04SVQ8H0G5'
- project: e2e-playwright
codeowners: 'S04SVQ8H0G5'
- project: e2e-remix
codeowners: 'S04SVQ8H0G5'
- project: e2e-vite
codeowners: 'S04SJ6PL98X'
- project: e2e-vue
codeowners: 'S04SJ6PL98X'
- project: e2e-nuxt
codeowners: 'S04SJ6PL98X'
- project: e2e-webpack
codeowners: 'S04SJ6PL98X'
- project: e2e-workspace-create
codeowners: 'S04SYHYKGNP'
exclude:
# exclude react-native tests from ubuntu
- os: ubuntu-latest
project: e2e-react-native
- os: ubuntu-latest
project: e2e-detox
- os: ubuntu-latest
project: e2e-expo
# exclude non-CNW/Lerna tests from non-LTS node versions
- node_version: 18
project: e2e-angular
- node_version: 18
project: e2e-cypress
- node_version: 18
project: e2e-detox
- node_version: 18
project: e2e-esbuild
- node_version: 18
project: e2e-expo
- node_version: 18
project: e2e-gradle
- node_version: 18
project: e2e-jest
- node_version: 18
project: e2e-js
- node_version: 18
project: e2e-eslint
- node_version: 18
project: e2e-next
- node_version: 18
project: e2e-node
- node_version: 18
project: e2e-nuxt
- node_version: 18
project: e2e-nx-init
- node_version: 18
project: e2e-nx
- node_version: 18
project: e2e-plugin
- node_version: 18
project: e2e-playwright
- node_version: 18
project: e2e-react
- node_version: 18
project: e2e-react-native
- node_version: 18
project: e2e-web
- node_version: 18
project: e2e-remix
- node_version: 18
project: e2e-rollup
- node_version: 18
project: e2e-storybook
- node_version: 18
project: e2e-vite
- node_version: 18
project: e2e-vue
- node_version: 18
project: e2e-webpack
- node_version: 22
project: e2e-angular
- node_version: 22
project: e2e-cypress
- node_version: 22
project: e2e-detox
- node_version: 22
project: e2e-esbuild
- node_version: 22
project: e2e-expo
- node_version: 22
project: e2e-gradle
- node_version: 22
project: e2e-jest
- node_version: 22
project: e2e-js
- node_version: 22
project: e2e-eslint
- node_version: 22
project: e2e-next
- node_version: 22
project: e2e-node
- node_version: 22
project: e2e-nuxt
- node_version: 22
project: e2e-nx-init
- node_version: 22
project: e2e-nx
- node_version: 22
project: e2e-plugin
- node_version: 22
project: e2e-playwright
- node_version: 22
project: e2e-react
- node_version: 22
project: e2e-react-native
- node_version: 22
project: e2e-web
- node_version: 22
project: e2e-remix
- node_version: 22
project: e2e-rollup
- node_version: 22
project: e2e-storybook
- node_version: 22
project: e2e-vite
- node_version: 22
project: e2e-vue
- node_version: 22
project: e2e-webpack
# - node_version: 23
# project: e2e-angular
# - node_version: 23
# project: e2e-cypress
# - node_version: 23
# project: e2e-detox
# - node_version: 23
# project: e2e-esbuild
# - node_version: 23
# project: e2e-expo
# - node_version: 23
# project: e2e-gradle
# - node_version: 23
# project: e2e-jest
# - node_version: 23
# project: e2e-js
# - node_version: 23
# project: e2e-eslint
# - node_version: 23
# project: e2e-next
# - node_version: 23
# project: e2e-node
# - node_version: 23
# project: e2e-nuxt
# - node_version: 23
# project: e2e-nx-init
# - node_version: 23
# project: e2e-nx
# - node_version: 23
# project: e2e-plugin
# - node_version: 23
# project: e2e-playwright
# - node_version: 23
# project: e2e-react
# - node_version: 23
# project: e2e-react-native
# - node_version: 23
# project: e2e-web
# - node_version: 23
# project: e2e-remix
# - node_version: 23
# project: e2e-rollup
# - node_version: 23
# project: e2e-storybook
# - node_version: 23
# project: e2e-vite
# - node_version: 23
# project: e2e-vue
# - node_version: 23
# project: e2e-webpack
# run just npm v20 on macos
- os: macos-latest
package_manager: yarn
- os: macos-latest
package_manager: pnpm
- os: macos-latest
node_version: 18
- os: macos-latest
node_version: 22
# - os: macos-latest
# node_version: 23
matrix: ${{fromJson(needs.prepare-matrix.outputs.matrix)}} # Load matrix from previous job
fail-fast: false
name: ${{ matrix.os_name }}/${{ matrix.package_manager }}/${{ matrix.node_version }} ${{ join(matrix.project) }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Prepare dir for output
run: mkdir -p outputs
@@ -470,30 +220,28 @@ jobs:
- name: Set starting timestamp
id: before-e2e
shell: bash
run: |
echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT
- name: Run e2e tests
id: e2e-run
run: pnpm nx run-many -t e2e-local -p ${{ matrix.project }}
run: pnpm nx run ${{ matrix.project }}:e2e-local
shell: bash
timeout-minutes: ${{ matrix.os_timeout }}
env:
GIT_AUTHOR_EMAIL: test@test.com
GIT_AUTHOR_NAME: Test
GIT_COMMITTER_EMAIL: test@test.com
GIT_COMMITTER_NAME: Test
NX_E2E_CI_CACHE_KEY: e2e-gha-${{ matrix.os }}-${{ matrix.node_version }}-${{ matrix.package_manager }}
NX_DAEMON: 'true'
NX_PERF_LOGGING: 'false'
NX_E2E_VERBOSE_LOGGING: 'true'
NX_NATIVE_LOGGING: 'false'
NX_E2E_RUN_E2E: 'true'
NX_CLOUD_NO_TIMEOUTS: 'true'
NX_E2E_SKIP_CLEANUP: 'true'
NODE_OPTIONS: --max_old_space_size=8192
SELECTED_PM: ${{ matrix.package_manager }}
npm_config_registry: http://localhost:4872
YARN_REGISTRY: http://localhost:4872
NX_CACHE_DIRECTORY: 'tmp'
NX_E2E_SKIP_CLEANUP: 'true'
NX_E2E_RUN_E2E: 'true'
NX_E2E_VERBOSE_LOGGING: 'true'
NX_PERF_LOGGING: 'false'
NX_DAEMON: 'true'
NX_SKIP_LOG_GROUPING: 'true'
- name: Save matrix config in file
if: ${{ always() }}
@@ -521,6 +269,8 @@ jobs:
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled && failure() }}
uses: mxschmitt/action-tmate@v3.8
timeout-minutes: 15
with:
sudo: ${{ matrix.os != 'windows-latest' }} # disable sudo for windows debugging
process-result:
if: ${{ always() && github.repository_owner == 'nrwl' }}
@@ -580,6 +330,7 @@ jobs:
});
result += `\`\`\``;
core.setOutput('SLACK_MESSAGE', trimSpace(result));
console.log(trimSpace(result));
function humanizeDuration(num) {
let res = '';
@@ -607,7 +358,7 @@ jobs:
};
const macosProjects = ['e2e-detox', 'e2e-expo', 'e2e-react-native'];
combined.forEach((matrix) => {
if (matrix.os_name === 'Linux' && matrix.node_version === 18) {
if (matrix.os_name === 'Linux' && matrix.node_version === 20) {
pmReport[matrix.package_manager] += matrix.duration;
}
if (matrix.os_name === 'Linux' || macosProjects.includes(matrix.project)) {
-470
View File
@@ -1,470 +0,0 @@
name: E2E matrix (Windows)
on:
schedule:
- cron: "0 5 * * *"
workflow_dispatch:
inputs:
debug_enabled:
type: boolean
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
required: false
default: false
env:
CYPRESS_CACHE_FOLDER: ${{ github.workspace }}/.cypress
permissions: { }
jobs:
preinstall:
if: ${{ github.repository_owner == 'nrwl' }}
runs-on: windows-latest
strategy:
matrix:
node_version:
- 18
- 20
- 22
# - 23
name: Cache install (node v${{ matrix.node_version }})
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.8.0
run_install: false
- name: Set node
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node_version }}
cache: 'pnpm'
- name: Cache node_modules
id: cache-modules
uses: actions/cache@v4
with:
lookup-only: true
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ matrix.node_version }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install packages
if: steps.cache-modules.outputs.cache-hit != 'true'
run: pnpm install --frozen-lockfile
- name: Cache Cypress
id: cache-cypress
uses: actions/cache@v4
with:
lookup-only: true
path: '${{ github.workspace }}/.cypress'
key: windows-cypress
- name: Install Cypress
if: steps.cache-cypress.outputs.cache-hit != 'true'
run: pnpm cypress install
e2e:
if: ${{ github.repository_owner == 'nrwl' }}
needs: preinstall
permissions:
contents: read
runs-on: windows-latest
strategy:
matrix:
node_version:
- 18
- 20
- 22
# - 23
package_manager:
- npm
project:
- e2e-angular
- e2e-cypress
- e2e-esbuild
- e2e-eslint
- e2e-jest
- e2e-js
- e2e-lerna-smoke-tests
- e2e-next
- e2e-node
- e2e-nuxt
- e2e-nx-init
- e2e-nx
- e2e-playwright
- e2e-plugin
- e2e-react
- e2e-release
- e2e-remix
- e2e-rollup
- e2e-storybook
- e2e-vite
- e2e-vue
- e2e-web
- e2e-webpack
- e2e-workspace-create
include:
# codeowner groups
- project: e2e-angular
codeowners: 'S04SS457V38'
- project: e2e-cypress
codeowners: 'S04T16BTJJY'
- project: e2e-esbuild
codeowners: 'S04SJ6HHP0X'
- project: e2e-jest
codeowners: 'S04T16BTJJY'
- project: e2e-js
codeowners: 'S04SJ6HHP0X'
- project: e2e-lerna-smoke-tests
codeowners: 'S04TNCVEETS'
- project: e2e-eslint
codeowners: 'S04SYJGKSCT'
- project: e2e-next
codeowners: 'S04TNCNJG5N'
- project: e2e-node
codeowners: 'S04SJ6HHP0X'
- project: e2e-nx-init
codeowners: 'S04SYHYKGNP'
- project: e2e-nx
codeowners: 'S04SYHYKGNP'
- project: e2e-plugin
codeowners: 'S04SYHYKGNP'
- project: e2e-release
codeowners: 'S04SYHYKGNP'
- project: e2e-react
codeowners: 'S04TNCNJG5N'
- project: e2e-web
codeowners: 'S04SJ6PL98X'
- project: e2e-rollup
codeowners: 'S04SJ6PL98X'
- project: e2e-storybook
codeowners: 'S04SVQ8H0G5'
- project: e2e-playwright
codeowners: 'S04SVQ8H0G5'
- project: e2e-remix
codeowners: 'S04SVQ8H0G5'
- project: e2e-vite
codeowners: 'S04SJ6PL98X'
- project: e2e-vue
codeowners: 'S04SJ6PL98X'
- project: e2e-nuxt
codeowners: 'S04SJ6PL98X'
- project: e2e-webpack
codeowners: 'S04SJ6PL98X'
- project: e2e-workspace-create
codeowners: 'S04SYHYKGNP'
exclude:
# exclude non-CNW/Lerna tests from non-LTS node versions
- node_version: 18
project: e2e-angular
- node_version: 18
project: e2e-cypress
- node_version: 18
project: e2e-esbuild
- node_version: 18
project: e2e-jest
- node_version: 18
project: e2e-js
- node_version: 18
project: e2e-eslint
- node_version: 18
project: e2e-next
- node_version: 18
project: e2e-node
- node_version: 18
project: e2e-nuxt
- node_version: 18
project: e2e-nx-init
- node_version: 18
project: e2e-nx
- node_version: 18
project: e2e-plugin
- node_version: 18
project: e2e-playwright
- node_version: 18
project: e2e-react
- node_version: 18
project: e2e-web
- node_version: 18
project: e2e-remix
- node_version: 18
project: e2e-rollup
- node_version: 18
project: e2e-storybook
- node_version: 18
project: e2e-vite
- node_version: 18
project: e2e-vue
- node_version: 18
project: e2e-webpack
- node_version: 22
project: e2e-angular
- node_version: 22
project: e2e-cypress
- node_version: 22
project: e2e-esbuild
- node_version: 22
project: e2e-jest
- node_version: 22
project: e2e-js
- node_version: 22
project: e2e-eslint
- node_version: 22
project: e2e-next
- node_version: 22
project: e2e-node
- node_version: 22
project: e2e-nuxt
- node_version: 22
project: e2e-nx-init
- node_version: 22
project: e2e-nx
- node_version: 22
project: e2e-plugin
- node_version: 22
project: e2e-playwright
- node_version: 22
project: e2e-react
- node_version: 22
project: e2e-web
- node_version: 22
project: e2e-remix
- node_version: 22
project: e2e-rollup
- node_version: 22
project: e2e-storybook
- node_version: 22
project: e2e-vite
- node_version: 22
project: e2e-vue
- node_version: 22
project: e2e-webpack
# - node_version: 23
# project: e2e-angular
# - node_version: 23
# project: e2e-cypress
# - node_version: 23
# project: e2e-esbuild
# - node_version: 23
# project: e2e-jest
# - node_version: 23
# project: e2e-js
# - node_version: 23
# project: e2e-eslint
# - node_version: 23
# project: e2e-next
# - node_version: 23
# project: e2e-node
# - node_version: 23
# project: e2e-nuxt
# - node_version: 23
# project: e2e-nx-init
# - node_version: 23
# project: e2e-nx
# - node_version: 23
# project: e2e-plugin
# - node_version: 23
# project: e2e-playwright
# - node_version: 23
# project: e2e-react
# - node_version: 23
# project: e2e-web
# - node_version: 23
# project: e2e-remix
# - node_version: 23
# project: e2e-rollup
# - node_version: 23
# project: e2e-storybook
# - node_version: 23
# project: e2e-vite
# - node_version: 23
# project: e2e-vue
# - node_version: 23
# project: e2e-webpack
fail-fast: false
name: ${{ matrix.project }} (v${{ matrix.node_version }})
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Prepare dir for output
run: mkdir -p outputs
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9.8.0
run_install: false
- name: Use Node.js ${{ matrix.node_version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node_version }}
cache: 'pnpm'
- name: Cache node_modules
id: cache-modules
uses: actions/cache@v4
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ matrix.node_version }}-${{ github.run_id }}
- name: Install packages
run: pnpm install --frozen-lockfile
- name: Cache Cypress
id: cache-cypress
uses: actions/cache@v4
with:
path: '${{ github.workspace }}/.cypress'
key: ${{ runner.os }}-cypress
- name: Install Cypress
if: steps.cache-cypress.outputs.cache-hit != 'true'
run: npx cypress install
- name: Configure git metadata (needed for lerna smoke tests)
run: |
git config --global user.email test@test.com
git config --global user.name "Test Test"
- name: Run e2e tests
id: e2e-run
run: pnpm nx run ${{ matrix.project }}:e2e-local
shell: bash
timeout-minutes: 180
env:
GIT_AUTHOR_EMAIL: test@test.com
GIT_AUTHOR_NAME: Test
GIT_COMMITTER_EMAIL: test@test.com
GIT_COMMITTER_NAME: Test
NX_E2E_CI_CACHE_KEY: e2e-gha-windows-${{ matrix.node_version }}-${{ matrix.package_manager }}
NODE_OPTIONS: --max_old_space_size=8192
SELECTED_PM: ${{ matrix.package_manager }}
npm_config_registry: http://localhost:4872
NX_CACHE_DIRECTORY: 'tmp'
NX_E2E_SKIP_CLEANUP: 'true'
NX_E2E_RUN_E2E: 'true'
NX_E2E_VERBOSE_LOGGING: 'true'
NX_PERF_LOGGING: 'false'
NX_DAEMON: 'true'
NX_SKIP_LOG_GROUPING: 'true'
- name: Save matrix config in file
if: ${{ always() }}
id: save-matrix
shell: bash
run: |
matrix=$((
echo '${{ toJSON(matrix) }}'
) | jq -c '. + { "status": "${{ steps.e2e-run.outcome}}" }')
echo "$matrix" > 'outputs/matrix.json'
- name: Upload matrix config
uses: actions/upload-artifact@v4
if: ${{ always() }}
with:
name: windows-${{ matrix.node_version}}-${{ matrix.package_manager}}-${{ matrix.project }}
overwrite: true
if-no-files-found: 'ignore'
path: 'outputs/matrix.json'
- name: Setup tmate session
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled && failure() }}
uses: mxschmitt/action-tmate@v3.8
timeout-minutes: 15
with:
sudo: false # disable sudo for windows debugging
process-result:
if: ${{ always() && github.repository_owner == 'nrwl' }}
runs-on: ubuntu-latest
needs: e2e
outputs:
message: ${{ steps.process-json.outputs.SLACK_MESSAGE }}
codeowners: ${{ steps.process-json.outputs.CODEOWNERS }}
steps:
- name: Prepare dir for output
run: mkdir -p outputs
- name: Load outputs
uses: actions/download-artifact@v4
with:
path: outputs
- name: Join and stringify matrix configs
id: combine-json
shell: bash
run: |
combined=$((jq -s . outputs/*/matrix.json) | jq tostring)
echo "combined=$combined" >> $GITHUB_OUTPUT
- name: Make slack outputs
id: process-json
uses: actions/github-script@v7
env:
GITHUB_TOKEN: ${{ github.token }}
with:
script: |
const combined = JSON.parse(${{ steps.combine-json.outputs.combined }});
const failedProjects = combined.filter(c => c.status === 'failure').sort((a, b) => a.project.localeCompare(b.project));
// codeowners
const codeowners = new Set();
failedProjects.forEach(c => {
codeowners.add(c.codeowners);
});
core.setOutput('CODEOWNERS', Array.from(codeowners).join(','));
// message
let result = `
*OS* Windows
*Package manager* npm
\`\`\`
| Failed project | Node |
|--------------------------------|------|`;
failedProjects.forEach(matrix => {
result += `\n| ${matrix.project.padEnd(30)} | v${matrix.node_version} |`
});
result += `\`\`\``;
const message = result.split('\n').map(l => l.trim()).join('\n');
core.setOutput('SLACK_MESSAGE', message);
report-failure:
if: ${{ failure() && github.repository_owner == 'nrwl' && github.event_name != 'workflow_dispatch' }}
needs: process-result
runs-on: ubuntu-latest
name: Report failure
steps:
- name: Send notification
uses: ravsamhq/notify-slack-action@v2
with:
status: 'failure'
message_format: '{emoji} Workflow has {status_message} ${{ needs.process-result.outputs.message }}'
notification_title: '{workflow}'
footer: '<{run_url}|View Run> / Last commit <{commit_url}|{commit_sha}>'
mention_groups: ${{ needs.process-result.outputs.codeowners }}
env:
SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }}
report-success:
if: ${{ success() && github.repository_owner == 'nrwl' && github.event_name != 'workflow_dispatch' }}
needs: e2e
runs-on: ubuntu-latest
name: Report success
steps:
- name: Send notification
uses: ravsamhq/notify-slack-action@v2
with:
status: ${{ needs.e2e.result }}
message_format: '{emoji} Workflow has {status_message}'
notification_title: '{workflow}'
footer: '<{run_url}|View Run> / Last commit <{commit_url}|{commit_sha}>'
env:
SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }}
+117
View File
@@ -0,0 +1,117 @@
type MatrixDataProject = {
name: string,
codeowners: string,
};
type MatrixDataOS = {
os: string, // GH runner machine name: e.g. ubuntu-latest
os_name: string, // short name that will be printed in the report and on the action
os_timeout: number, // 60
package_managers: string[], // package managers to run on this OS
node_versions: number[], // node versions to run on this OS
excluded?: string[], // projects to exclude from running on this OS
};
type MatrixData = {
coreProjects: MatrixDataProject[],
projects: MatrixDataProject[],
nodeTLS: number,
setup: MatrixDataOS[],
}
// TODO: Extract Slack groups into named groups for easier maintenance
const matrixData: MatrixData = {
coreProjects: [
{ name: 'e2e-lerna-smoke-tests', codeowners: 'S04TNCVEETS' },
{ name: 'e2e-js', codeowners: 'S04SJ6HHP0X' },
{ name: 'e2e-nx-init', codeowners: 'S04SYHYKGNP' },
{ name: 'e2e-nx', codeowners: 'S04SYHYKGNP' },
{ name: 'e2e-release', codeowners: 'S04SYHYKGNP' },
{ name: 'e2e-workspace-create', codeowners: 'S04SYHYKGNP' }
],
projects: [
{ name: 'e2e-angular', codeowners: 'S04SS457V38' },
{ name: 'e2e-cypress', codeowners: 'S04T16BTJJY' },
{ name: 'e2e-detox', codeowners: 'S04TNCNJG5N' },
{ name: 'e2e-esbuild', codeowners: 'S04SJ6HHP0X' },
{ name: 'e2e-expo', codeowners: 'S04TNCNJG5N' },
{ name: 'e2e-gradle', codeowners: 'S04TNCNJG5N' },
{ name: 'e2e-jest', codeowners: 'S04T16BTJJY' },
{ name: 'e2e-eslint', codeowners: 'S04SYJGKSCT' },
{ name: 'e2e-next', codeowners: 'S04TNCNJG5N' },
{ name: 'e2e-node', codeowners: 'S04SJ6HHP0X' },
{ name: 'e2e-plugin', codeowners: 'S04SYHYKGNP' },
{ name: 'e2e-react', codeowners: 'S04TNCNJG5N' },
{ name: 'e2e-react-native', codeowners: 'S04TNCNJG5N' },
{ name: 'e2e-web', codeowners: 'S04SJ6PL98X' },
{ name: 'e2e-rollup', codeowners: 'S04SJ6PL98X' },
{ name: 'e2e-storybook', codeowners: 'S04SVQ8H0G5' },
{ name: 'e2e-playwright', codeowners: 'S04SVQ8H0G5' },
{ name: 'e2e-remix', codeowners: 'S04SVQ8H0G5' },
{ name: 'e2e-rspack', codeowners: 'S04SJ6HHP0X' },
{ name: 'e2e-vite', codeowners: 'S04SJ6PL98X' },
{ name: 'e2e-vue', codeowners: 'S04SJ6PL98X' },
{ name: 'e2e-nuxt', codeowners: 'S04SJ6PL98X' },
{ name: 'e2e-webpack', codeowners: 'S04SJ6PL98X' }
],
nodeTLS: 20,
setup: [
{ os: 'ubuntu-latest', os_name: 'Linux', os_timeout: 60, package_managers: ['npm', 'pnpm', 'yarn'], node_versions: [18, 20, 22], excluded: ['e2e-detox', 'e2e-react-native', 'e2e-expo'] },
{ os: 'macos-latest', os_name: 'MacOS', os_timeout: 90, package_managers: ['npm'], node_versions: [20] },
{ os: 'windows-latest', os_name: 'WinOS', os_timeout: 180, package_managers: ['npm'], node_versions: [20], excluded: ['e2e-detox', 'e2e-react-native', 'e2e-expo'] }
]
};
const matrix: Array<{
project: string,
codeowners: string,
node_version: number,
package_manager: string,
os: string,
os_name: string,
os_timeout: number
}> = [];
function addMatrixCombo(project: MatrixDataProject, nodeVersion: number, pm: number, os: number) {
matrix.push({
project: project.name,
codeowners: project.codeowners,
node_version: nodeVersion,
package_manager: matrixData.setup[os].package_managers[pm],
os: matrixData.setup[os].os,
os_name: matrixData.setup[os].os_name,
os_timeout: matrixData.setup[os].os_timeout,
});
}
function processProject(project: MatrixDataProject, nodeVersion?: number) {
for (let os = 0; os < matrixData.setup.length; os++) {
for (let pm = 0; pm < matrixData.setup[os].package_managers.length; pm++) {
if (!matrixData.setup[os].excluded || !matrixData.setup[os].excluded?.includes(project.name)) {
if (nodeVersion) {
addMatrixCombo(project, nodeVersion, pm, os);
} else {
for (let n = 0; n < matrixData.setup[os].node_versions.length; n++) {
addMatrixCombo(project, matrixData.setup[os].node_versions[n], pm, os);
}
}
}
}
}
}
// process core projects
for (let p = 0; p < matrixData.coreProjects.length; p++) {
processProject(matrixData.coreProjects[p]);
}
// process other projects
for (let p = 0; p < matrixData.projects.length; p++) {
processProject(matrixData.projects[p], matrixData.nodeTLS);
}
if (matrix.length > 256) {
throw new Error('You have exceeded the size of the matrix. GitHub allows only 256 jobs in a matrix. Found ${matrix.length} jobs.');
}
// print result to stdout for pipeline to consume
process.stdout.write(JSON.stringify({ include: matrix }, null, 0));
@@ -46,6 +46,7 @@ Install `create-nx-workspace` globally to invoke the command directly, or use `n
| `--style` | string | Stylesheet type to be used with certain stacks. |
| `--unitTestRunner` | `jest`, `vitest`, `none` | Test runner to use for unit tests. |
| `--useGitHub` | boolean | Will you be using GitHub as your git hosting provider? (Default: `false`) |
| `--useProjectJson` | boolean | Use a 'project.json' file for the Nx configuration instead of a 'package.json' file. This defaults to 'true' when '--no-workspaces' is used. Otherwise, it defaults to 'false'. |
| `--useReactRouter` | boolean | Generate a Server-Side Rendered (SSR) React app using React Router. |
| `--version` | boolean | Show version number. |
| `--workspaces` | boolean | Use package manager workspaces. (Default: `true`) |
+10
View File
@@ -1727,6 +1727,16 @@
}
},
"migrations": {
"/nx-api/eslint/migrations/20.7.0-package-updates": {
"description": "",
"file": "generated/packages/eslint/migrations/20.7.0-package-updates.json",
"hidden": false,
"name": "20.7.0-package-updates",
"version": "20.7.0-beta.4",
"originalFilePath": "/packages/eslint",
"path": "/nx-api/eslint/migrations/20.7.0-package-updates",
"type": "migration"
},
"/nx-api/eslint/migrations/20.4.0-typescript-eslint-package-updates": {
"description": "",
"file": "generated/packages/eslint/migrations/20.4.0-typescript-eslint-package-updates.json",
+10
View File
@@ -1715,6 +1715,16 @@
}
],
"migrations": [
{
"description": "",
"file": "generated/packages/eslint/migrations/20.7.0-package-updates.json",
"hidden": false,
"name": "20.7.0-package-updates",
"version": "20.7.0-beta.4",
"originalFilePath": "/packages/eslint",
"path": "eslint/migrations/20.7.0-package-updates",
"type": "migration"
},
{
"description": "",
"file": "generated/packages/eslint/migrations/20.4.0-typescript-eslint-package-updates.json",
@@ -37,6 +37,10 @@
"alwaysAddToPackageJson": false
},
"@angular/cdk": { "version": "~19.0.0", "alwaysAddToPackageJson": false },
"@angular/google-maps": {
"version": "~19.0.0",
"alwaysAddToPackageJson": false
},
"ng-packagr": { "version": "~19.0.0", "alwaysAddToPackageJson": false },
"zone.js": { "version": "~0.15.0", "alwaysAddToPackageJson": false }
},
@@ -37,6 +37,10 @@
"alwaysAddToPackageJson": false
},
"@angular/cdk": { "version": "~19.1.0", "alwaysAddToPackageJson": false },
"@angular/google-maps": {
"version": "~19.1.0",
"alwaysAddToPackageJson": false
},
"ng-packagr": { "version": "~19.1.0", "alwaysAddToPackageJson": false }
},
"aliases": [],
@@ -37,6 +37,10 @@
"alwaysAddToPackageJson": false
},
"@angular/cdk": { "version": "~19.2.1", "alwaysAddToPackageJson": false },
"@angular/google-maps": {
"version": "~19.2.1",
"alwaysAddToPackageJson": false
},
"ng-packagr": { "version": "~19.2.0", "alwaysAddToPackageJson": false }
},
"aliases": [],
@@ -0,0 +1,12 @@
{
"name": "20.7.0-package-updates",
"version": "20.7.0-beta.4",
"packages": { "eslint-config-prettier": { "version": "^10.0.0" } },
"aliases": [],
"description": "",
"hidden": false,
"implementation": "",
"path": "/packages/eslint",
"schema": null,
"type": "migration"
}
@@ -231,13 +231,53 @@ Next.js applications can be build with:
nx build my-new-app
```
And if you generated a library with --buildable, then you can build a library as well:
And if you generated a library with `--bundler`, then you can build a library as well:
```shell
nx build my-new-lib
```
After running a build, the output will be in the `dist` folder. You can customize the output folder by setting `outputPath` in the project's `project.json` file.
After running a build, the output will be in the `{workspaceRoot}/dist/{projectRoot}` folder.
{% tabs %}
{% tab label="Using inferred tasks" %}
You can customize the output folder path by update the bundler's config. For example vite's config can be updated in `vite.config.ts`:
```typescript {% fileName="apps/my-next-app/vite.config.ts" highlightLines=[4]%}
import { defineConfig } from 'vite';
export default defineConfig(() => ({
build: {
outDir: 'dist/my-next-app',
},
}));
```
{% /tab %}
{% tab label="Using the @nx/next:build executor" %}
You can customize the output folder by setting `outputPath` in the project's `project.json` file
```json {% fileName="apps/my-next-app/project.json" highlightLines=[9]%}
{
"root": "apps/my-next-app",
"sourceRoot": "apps/my-next-app/src",
"targets": {
"build": {
"executor": "@nx/next:build",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/my-next-app"
}
}
}
}
```
{% /tab %}
{% /tabs %}
The library in `dist` is publishable to npm or a private registry.
@@ -46,6 +46,7 @@ Install `create-nx-workspace` globally to invoke the command directly, or use `n
| `--style` | string | Stylesheet type to be used with certain stacks. |
| `--unitTestRunner` | `jest`, `vitest`, `none` | Test runner to use for unit tests. |
| `--useGitHub` | boolean | Will you be using GitHub as your git hosting provider? (Default: `false`) |
| `--useProjectJson` | boolean | Use a 'project.json' file for the Nx configuration instead of a 'package.json' file. This defaults to 'true' when '--no-workspaces' is used. Otherwise, it defaults to 'false'. |
| `--useReactRouter` | boolean | Generate a Server-Side Rendered (SSR) React app using React Router. |
| `--version` | boolean | Show version number. |
| `--workspaces` | boolean | Use package manager workspaces. (Default: `true`) |
@@ -108,6 +108,10 @@
"description": "Whether to use package manager workspaces.",
"type": "boolean",
"default": true
},
"useProjectJson": {
"type": "boolean",
"description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file."
}
},
"additionalProperties": true,
@@ -125,6 +125,10 @@
"description": "Whether to use package manager workspaces.",
"type": "boolean",
"default": true
},
"useProjectJson": {
"type": "boolean",
"description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file."
}
},
"required": ["preset", "name"],
+42 -2
View File
@@ -231,13 +231,53 @@ Next.js applications can be build with:
nx build my-new-app
```
And if you generated a library with --buildable, then you can build a library as well:
And if you generated a library with `--bundler`, then you can build a library as well:
```shell
nx build my-new-lib
```
After running a build, the output will be in the `dist` folder. You can customize the output folder by setting `outputPath` in the project's `project.json` file.
After running a build, the output will be in the `{workspaceRoot}/dist/{projectRoot}` folder.
{% tabs %}
{% tab label="Using inferred tasks" %}
You can customize the output folder path by update the bundler's config. For example vite's config can be updated in `vite.config.ts`:
```typescript {% fileName="apps/my-next-app/vite.config.ts" highlightLines=[4]%}
import { defineConfig } from 'vite';
export default defineConfig(() => ({
build: {
outDir: 'dist/my-next-app',
},
}));
```
{% /tab %}
{% tab label="Using the @nx/next:build executor" %}
You can customize the output folder by setting `outputPath` in the project's `project.json` file
```json {% fileName="apps/my-next-app/project.json" highlightLines=[9]%}
{
"root": "apps/my-next-app",
"sourceRoot": "apps/my-next-app/src",
"targets": {
"build": {
"executor": "@nx/next:build",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/my-next-app"
}
}
}
}
```
{% /tab %}
{% /tabs %}
The library in `dist` is publishable to npm or a private registry.
@@ -5,7 +5,7 @@ description: Learn how to ensure all Nx plugins in your repository are using the
# Keep Nx Versions in Sync
If your Nx plugin versions do not match the version of `nx` in your repository, you can encounter some difficult to debug errors. To get your Nx plugins back in sync, follow the steps below:
If your Nx plugin versions do not match the version of `nx` in your repository, you may encounter some difficulties when debugging errors. To get your Nx plugins back in sync, follow the steps below:
1. Identify all the official Nx plugins that are used in your repo. This includes `nx` and any packages in the `@nx/` organization scope, except for plugins that are still in [nx-labs](https://github.com/nrwl/nx-labs). Also, `nx-cloud` does not need to match the other package versions.
2. Run `nx report` and identify the minimum and maximum version numbers for all the packages that need to by in sync.
+49 -65
View File
@@ -48,8 +48,6 @@ title="Nx React Monorepo Tutorial Walkthrough"
## Creating a new React Monorepo
{% video-link link="https://youtu.be/gc4N7kxiA50?t=66" /%}
Create a new React monorepo with the following command:
```{% command="npx create-nx-workspace@latest react-monorepo --preset=react-monorepo" path="~" %}
@@ -59,7 +57,7 @@ NX Let's create a new workspace [https://nx.dev/getting-started/intro]
✔ Application name · react-store
✔ Which bundler would you like to use? · vite
✔ Which unit test runner would you like to use? · vitest
✔ Test runner to use for end to end (E2E) tests · cypress
✔ Test runner to use for end to end (E2E) tests · playwright
✔ Default stylesheet format · css
✔ Would you like to use ESLint? · Yes
✔ Would you like to use Prettier for code formatting? · Yes
@@ -70,7 +68,6 @@ Let's name the initial application `react-store`. In this tutorial we're going t
```
└─ react-monorepo
├─ ...
├─ apps
│ ├─ react-store
│ │ ├─ public
@@ -92,12 +89,15 @@ Let's name the initial application `react-store`. In this tutorial we're going t
│ │ └─ vite.config.ts
│ └─ react-store-e2e
│ └─ ...
├─ ...
├─ package.json
├─ nx.json
├─ tsconfig.base.json
package.json
tsconfig.json
└─ vitest.workspace.ts
```
The setup includes..
The setup includes:
- a new React application (`apps/react-store/`)
- a Playwright based set of e2e tests (`apps/react-store-e2e/`)
@@ -111,8 +111,6 @@ The [`nx.json` file](/reference/nx-json) contains configuration settings for Nx
## Serving the App
{% video-link link="https://youtu.be/gc4N7kxiA50?t=120" /%}
To serve your new React application, just run:
```shell
@@ -127,8 +125,6 @@ Nx uses the following syntax to run tasks:
### Inferred Tasks
{% video-link link="https://youtu.be/gc4N7kxiA50?t=158" /%}
Nx identifies available tasks for your project from [tooling configuration files](/concepts/inferred-tasks), `package.json` scripts and the targets defined in `project.json`. To view the tasks that Nx has detected, look in the [Nx Console](/getting-started/editor-setup) project detail view or run:
```shell
@@ -245,37 +241,36 @@ export default defineConfig({
## Add Another Application
{% video-link link="https://youtu.be/gc4N7kxiA50?t=259" /%}
Nx plugins usually provide [generators](/features/generate-code) that allow you to easily scaffold code, configuration or entire projects. To see what capabilities the `@nx/react` plugin provides, run the following command and inspect the output:
```{% command="npx nx list @nx/react" path="react-monorepo" %}
NX Capabilities in @nx/react:
GENERATORS
GENERATORS
init : Initialize the `@nx/react` plugin.
application : Create a React application.
library : Create a React library.
component : Create a React component.
redux : Create a Redux slice for a project.
storybook-configuration : Set up storybook for a React app or library.
component-story : Generate storybook story for a React component
stories : Create stories/specs for all components declared in an app or library.
hook : Create a hook.
cypress-component-configuration : Setup Cypress component testing for a React project
component-test : Generate a Cypress component test for a React component
setup-tailwind : Set up Tailwind configuration for a project.
setup-ssr : Set up SSR configuration for a project.
host : Generate a host react application
remote : Generate a remote react application
federate-module : Federate a module.
init : Initialize the `@nx/react` plugin.
application : Create a React application.
library : Create a React library.
component : Create a React component.
redux : Create a Redux slice for a project.
storybook-configuration : Set up storybook for a React app or library.
component-story : Generate storybook story for a React component
stories : Create stories/specs for all components declared in an app or library.
hook : Create a hook.
host : Generate a host react application
remote : Generate a remote react application
cypress-component-configuration : Setup Cypress component testing for a React project
component-test : Generate a Cypress component test for a React component
setup-tailwind : Set up Tailwind configuration for a project.
setup-ssr : Set up SSR configuration for a project.
federate-module : Federate a module.
EXECUTORS/BUILDERS
EXECUTORS/BUILDERS
module-federation-dev-server : Serve a host or remote application.
module-federation-ssr-dev-server : Serve a host application along with it's known remotes.
module-federation-dev-server : Serve a host or remote application.
module-federation-ssr-dev-server : Serve a host application along with it's known remotes.
module-federation-static-server : Serve a host and its remotes statically.
```
{% callout type="info" title="Prefer a more visual UI?" %}
@@ -291,9 +286,9 @@ Run the following command to generate a new `inventory` application. Note how we
```{% command="npx nx g @nx/react:app apps/inventory" path="react-monorepo" %}
NX Generating @nx/react:application
✔ Would you like to add React Router to this application? (y/N) · false
✔ Would you like to add routing to this application? (y/N) · false
✔ What unit test runner should be used? · vitest
✔ Which E2E test runner would you like to use? · cypress
✔ Which E2E test runner would you like to use? · playwright
CREATE apps/inventory/index.html
CREATE apps/inventory/public/favicon.ico
CREATE apps/inventory/src/app/app.spec.tsx
@@ -306,19 +301,15 @@ CREATE apps/inventory/src/app/app.tsx
CREATE apps/inventory/src/styles.css
CREATE apps/inventory/tsconfig.json
CREATE apps/inventory/package.json
CREATE apps/inventory/eslint.config.mjs
UPDATE nx.json
CREATE apps/inventory/tsconfig.spec.json
CREATE apps/inventory/vite.config.ts
CREATE apps/inventory/eslint.config.mjs
CREATE apps/inventory-e2e/package.json
CREATE apps/inventory-e2e/src/e2e/app.cy.ts
CREATE apps/inventory-e2e/src/support/app.po.ts
CREATE apps/inventory-e2e/src/support/e2e.ts
CREATE apps/inventory-e2e/src/fixtures/example.json
CREATE apps/inventory-e2e/src/support/commands.ts
CREATE apps/inventory-e2e/cypress.config.ts
CREATE apps/inventory-e2e/tsconfig.json
CREATE apps/inventory-e2e/src/example.spec.ts
CREATE apps/inventory-e2e/playwright.config.ts
UPDATE tsconfig.json
CREATE apps/inventory-e2e/tsconfig.json
CREATE apps/inventory-e2e/eslint.config.mjs
NOTE: The "dryRun" flag means no changes were made.
@@ -332,8 +323,6 @@ npx nx g @nx/react:app apps/inventory
## Share Code with Local Libraries
{% video-link link="https://youtu.be/gc4N7kxiA50?t=324" /%}
When you develop your React application, usually all your logic sits in the `app` folder. Ideally separated by various folder names which represent your "domains". As your app grows, however, the app becomes more and more monolithic and the code is unable to be shared with other applications.
```
@@ -367,8 +356,6 @@ Nx allows you to separate this logic into "local libraries". The main benefits i
### Create Local Libraries
{% video-link link="https://youtu.be/gc4N7kxiA50?t=366" /%}
Let's assume our domain areas include `products`, `orders` and some more generic design system components, called `ui`. We can generate a new library for each of these areas using the React library generator:
```
@@ -426,8 +413,6 @@ Each of these libraries
### Import Libraries into the React Applications
{% video-link link="https://youtu.be/gc4N7kxiA50?t=456" /%}
All libraries that we generate are automatically included in the `workspaces` defined in the root-level `package.json`.
```json {% fileName="package.json" %}
@@ -509,7 +494,7 @@ import { Route, Routes } from 'react-router-dom';
import { Products } from '@react-monorepo/products';
function Home() {
return <h1>Home</h1>;
return <h1>Welcome react-store</h1>;
}
export function App() {
@@ -570,8 +555,6 @@ export default App;
## Visualize your Project Structure
{% video-link link="https://youtu.be/gc4N7kxiA50?t=530" /%}
Nx automatically detects the dependencies between the various parts of your workspace and builds a [project graph](/features/explore-graph). This graph is used by Nx to perform various optimizations such as determining the correct order of execution when running tasks like `npx nx build`, identifying [affected projects](/features/run-tasks#run-tasks-on-projects-affected-by-a-pr) and more. Interestingly you can also visualize it.
Just run:
@@ -675,9 +658,7 @@ Exercise for you: change the codebase such that `ui` is used by `orders` and `pr
## Testing and Linting - Running Multiple Tasks
{% video-link link="https://youtu.be/gc4N7kxiA50?t=589" /%}
Our current setup doesn't just come with targets for serving and building the React application, but also has targets for unit testing, e2e testing and linting. Again, these are defined in the `project.json` file. We can use the same syntax as before to run these tasks:
Our current setup doesn't just come with targets for serving and building the React application, but also has targets for unit testing, e2e testing and linting. We can use the same syntax as before to run these tasks:
```bash
npx nx test react-store # runs the tests for react-store
@@ -691,6 +672,17 @@ More conveniently, we can also run tasks in parallel using the following syntax:
npx nx run-many -t test
```
These tasks are automatically configured by through [Nx plugins](/concepts/inferred-tasks). You can view available tasks for a project by using the `nx show project` command.
```shell
npx nx show project react-store
npx nx show project react-store-e2e
npx nx show project inventory
# To list all projects
npx nx show projects
```
### Cache Tasks
One thing to highlight is that Nx is able to [cache the tasks you run](/features/cache-task-results).
@@ -714,8 +706,6 @@ Not all tasks might be cacheable though. You can configure the `cache` settings
### Test Affected Projects
{% video-link link="https://youtu.be/gc4N7kxiA50?t=614" /%}
Commit your changes to git.
```shell
@@ -848,8 +838,6 @@ npx nx graph --affected
## Build the Apps for Deployment
{% video-link link="https://youtu.be/gc4N7kxiA50?t=713" /%}
If you're ready and want to ship your applications, you can build them using
```{% command="npx nx run-many -t build" path="react-monorepo" %}
@@ -861,7 +849,7 @@ If you're ready and want to ship your applications, you can build them using
NX Successfully ran target build for 2 projects (1s)
```
All the required files will be placed in `/apps/react-store/dist` and `/apps/inventory/dist` and can be deployed to your favorite hosting provider.
All the required files will be placed in `apps/react-store/dist` and `apps/inventory/dist` and can be deployed to your favorite hosting provider.
Nx will run any script defined in `package.json`, so you can create a `deploy` task that sends the build output to your hosting provider.
@@ -917,8 +905,6 @@ npx nx affected -t deploy
## Imposing Constraints with Module Boundary Rules
{% video-link link="https://youtu.be/gc4N7kxiA50?t=791" /%}
Once you modularize your codebase you want to make sure that the libs are not coupled to each other in an uncontrolled way. Here are some examples of how we might want to guard our small demo workspace:
- we might want to allow `orders` to import from `shared-ui` but not the other way around
@@ -1091,8 +1077,6 @@ Learn more about how to [enforce module boundaries](/features/enforce-module-bou
Make sure you have completed the previous sections of this tutorial before starting this one. If you want a clean starting point, you can check out the [reference code](https://github.com/nrwl/nx-recipes/tree/main/react-monorepo) as a starting point.
{% /callout %}
{% video-link link="https://youtu.be/gc4N7kxiA50?t=907" /%}
This tutorial walked you through how Nx can improve the local development experience, but the biggest difference Nx makes is in CI. As repositories get bigger, making sure that the CI is fast, reliable and maintainable can get very challenging. Nx provides a solution.
- Nx reduces wasted time in CI with the [`affected` command](/ci/features/affected).
@@ -1130,7 +1114,7 @@ npx nx generate ci-workflow
The key lines in the CI pipeline are:
```yml {% fileName=".github/workflows/ci.yml" highlightLines=["10-14", "21-23"] %}
```yml {% fileName=".github/workflows/ci.yml" highlightLines=["11-15", "27-31"] %}
name: CI
# ...
jobs:
@@ -1154,7 +1138,7 @@ jobs:
cache: 'npm'
- run: npm ci --legacy-peer-deps
- run: npx cypress install
- run: npx playwright install --with-deps
- uses: nrwl/nx-set-shas@v4
# Prepend any command with "nx-cloud record --" to record its logs to Nx Cloud
+7 -4
View File
@@ -132,16 +132,19 @@ ${content}`
// check typecheck
expect(runCLI(`typecheck ${esbuildParentLib}`)).toContain(
`Successfully ran target typecheck for project @proj/${esbuildParentLib} and 4 tasks it depends on`
`Successfully ran target typecheck for project @proj/${esbuildParentLib} and 5 tasks it depends on`
);
expect(runCLI(`typecheck ${rollupParentLib}`)).toContain(
`Successfully ran target typecheck for project @proj/${rollupParentLib} and 4 tasks it depends on`
`Successfully ran target typecheck for project @proj/${rollupParentLib} and 5 tasks it depends on`
);
expect(runCLI(`typecheck ${swcParentLib}`)).toContain(
`Successfully ran target typecheck for project @proj/${swcParentLib} and 4 tasks it depends on`
`Successfully ran target typecheck for project @proj/${swcParentLib} and 5 tasks it depends on`
);
expect(runCLI(`typecheck ${tscParentLib}`)).toContain(
`Successfully ran target typecheck for project @proj/${tscParentLib} and 5 tasks it depends on`
);
expect(runCLI(`typecheck ${viteParentLib}`)).toContain(
`Successfully ran target typecheck for project @proj/${viteParentLib} and 4 tasks it depends on`
`Successfully ran target typecheck for project @proj/${viteParentLib} and 5 tasks it depends on`
);
// check lint
+1
View File
@@ -106,6 +106,7 @@ describe('Node Applications', () => {
expect(() => runCLI(`lint ${nodelib}`)).not.toThrow();
expect(() => runCLI(`test ${nodelib}`)).not.toThrow();
expect(() => runCLI(`build ${nodelib}`)).not.toThrow();
expect(() => runCLI(`typecheck ${nodelib}`)).not.toThrow();
const p = await runCommandUntil(
`serve ${nodeapp}`,
@@ -47,6 +47,9 @@ describe('Nx Plugin (TS solution)', () => {
expect(runCLI(`lint @proj/${plugin}`)).toContain(
`Successfully ran target lint for project @proj/${plugin}`
);
expect(runCLI(`typecheck @proj/${plugin}`)).toContain(
`Successfully ran target typecheck for project @proj/${plugin}`
);
expect(runCLI(`build @proj/${plugin}`)).toContain(
`Successfully ran target build for project @proj/${plugin}`
);
+102 -45
View File
@@ -9,60 +9,117 @@ import {
} from '@nx/e2e/utils';
describe('React Router Applications', () => {
beforeAll(() => {
newProject({ packages: ['@nx/react'] });
ensureCypressInstallation();
});
afterAll(() => cleanupProject());
it('should generate a react-router application', async () => {
describe('TS paths', () => {
const appName = uniq('app');
runCLI(
`generate @nx/react:app ${appName} --use-react-router --routing --no-interactive`
);
beforeAll(() => {
newProject({ packages: ['@nx/react'] });
ensureCypressInstallation();
runCLI(
`generate @nx/react:app ${appName} --use-react-router --routing --linter=eslint --unit-test-runner=vitest --no-interactive`
);
});
const packageJson = JSON.parse(readFile('package.json'));
expect(packageJson.dependencies['react-router']).toBeDefined();
expect(packageJson.dependencies['@react-router/node']).toBeDefined();
expect(packageJson.dependencies['@react-router/serve']).toBeDefined();
expect(packageJson.dependencies['isbot']).toBeDefined();
afterAll(() => cleanupProject());
checkFilesExist(`${appName}/app/app.tsx`);
checkFilesExist(`${appName}/app/entry.client.tsx`);
checkFilesExist(`${appName}/app/entry.server.tsx`);
checkFilesExist(`${appName}/app/routes.tsx`);
checkFilesExist(`${appName}/react-router.config.ts`);
checkFilesExist(`${appName}/vite.config.ts`);
it('should generate a react-router application', async () => {
const packageJson = JSON.parse(readFile('package.json'));
expect(packageJson.dependencies['react-router']).toBeDefined();
expect(packageJson.dependencies['@react-router/node']).toBeDefined();
expect(packageJson.dependencies['@react-router/serve']).toBeDefined();
expect(packageJson.dependencies['isbot']).toBeDefined();
checkFilesExist(`${appName}/app/app.tsx`);
checkFilesExist(`${appName}/app/entry.client.tsx`);
checkFilesExist(`${appName}/app/entry.server.tsx`);
checkFilesExist(`${appName}/app/routes.tsx`);
checkFilesExist(`${appName}/react-router.config.ts`);
checkFilesExist(`${appName}/vite.config.ts`);
});
it('should be able to build a react-router application', async () => {
const buildResult = runCLI(`build ${appName}`);
expect(buildResult).toContain('Successfully ran target build');
});
it('should be able to lint a react-router application', async () => {
const lintResult = runCLI(`lint ${appName}`);
expect(lintResult).toContain('Successfully ran target lint');
});
it('should be able to test and typecheck a react-router application', async () => {
const typeCheckResult = runCLI(`typecheck ${appName}`);
expect(typeCheckResult).toContain('Successfully ran target typecheck');
});
it('should be able to test and typecheck a react-router application with jest', async () => {
const jestApp = uniq('jestApp');
runCLI(
`generate @nx/react:app ${jestApp} --use-react-router --routing --unit-test-runner=jest --no-interactive`
);
const testResult = runCLI(`test ${jestApp}`);
expect(testResult).toContain('Successfully ran target test');
const typeCheckResult = runCLI(`typecheck ${jestApp}`);
expect(typeCheckResult).toContain('Successfully ran target typecheck');
});
});
it('should be able to build a react-router application', async () => {
describe('TS Solution', () => {
const appName = uniq('app');
runCLI(
`generate @nx/react:app ${appName} --use-react-router --routing --no-interactive`
);
beforeAll(() => {
newProject({ preset: 'ts', packages: ['@nx/react'] });
ensureCypressInstallation();
runCLI(
`generate @nx/react:app ${appName} --use-react-router --routing --linter=eslint --unit-test-runner=vitest --no-interactive`
);
});
const buildResult = runCLI(`build ${appName}`);
expect(buildResult).toContain('Successfully ran target build');
});
afterAll(() => cleanupProject());
it('should be able to lint a react-router application', async () => {
const appName = uniq('app');
runCLI(
`generate @nx/react:app ${appName} --use-react-router --routing --linter=eslint --no-interactive`
);
it('should generate a react-router application', async () => {
const packageJson = JSON.parse(readFile('package.json'));
expect(packageJson.dependencies['react-router']).toBeDefined();
expect(packageJson.dependencies['@react-router/node']).toBeDefined();
expect(packageJson.dependencies['@react-router/serve']).toBeDefined();
expect(packageJson.dependencies['isbot']).toBeDefined();
const buildResult = runCLI(`lint ${appName}`);
expect(buildResult).toContain('Successfully ran target lint');
});
checkFilesExist(`${appName}/app/app.tsx`);
checkFilesExist(`${appName}/app/entry.client.tsx`);
checkFilesExist(`${appName}/app/entry.server.tsx`);
checkFilesExist(`${appName}/app/routes.tsx`);
checkFilesExist(`${appName}/react-router.config.ts`);
checkFilesExist(`${appName}/vite.config.ts`);
});
it('should be able to test a react-router application', async () => {
const appName = uniq('app');
runCLI(
`generate @nx/react:app ${appName} --use-react-router --routing --unit-test-runner=vitest --no-interactive`
);
it('should be able to build a react-router application', async () => {
const buildResult = runCLI(`build ${appName}`);
expect(buildResult).toContain('Successfully ran target build');
});
const buildResult = runCLI(`test ${appName}`);
expect(buildResult).toContain('Successfully ran target test');
it('should be able to lint a react-router application', async () => {
const lintResult = runCLI(`lint ${appName}`);
expect(lintResult).toContain('Successfully ran target lint');
});
it('should be able to test and typecheck a react-router application', async () => {
const testResult = runCLI(`test ${appName}`);
expect(testResult).toContain('Successfully ran target test');
const typeCheckResult = runCLI(`typecheck ${appName}`);
expect(typeCheckResult).toContain('Successfully ran target typecheck');
});
it('should be able to test and typecheck a react-router application with jest', async () => {
const jestApp = uniq('jestApp');
runCLI(
`generate @nx/react:app ${jestApp} --use-react-router --routing --unit-test-runner=jest --no-interactive`
);
const testResult = runCLI(`test ${jestApp}`);
expect(testResult).toContain('Successfully ran target test');
const typeCheckResult = runCLI(`typecheck ${jestApp}`);
expect(typeCheckResult).toContain('Successfully ran target typecheck');
});
});
});
+49
View File
@@ -1,9 +1,15 @@
import {
checkFilesExist,
cleanupProject,
getPackageManagerCommand,
newProject,
readFile,
readJson,
runCLI,
runCommand,
uniq,
updateFile,
updateJson,
} from '@nx/e2e/utils';
describe('React (TS solution)', () => {
@@ -38,4 +44,47 @@ describe('React (TS solution)', () => {
`Successfully ran target test for project ${lib}`
);
}, 90000);
it('should be able to use Webpack to build apps with an imported lib', async () => {
const appName = uniq('app');
const libName = uniq('lib');
runCLI(
`generate @nx/react:app packages/${appName} --bundler=webpack --no-interactive --skipFormat --linter=eslint --unitTestRunner=none`
);
runCLI(
`generate @nx/js:lib libs/${libName} --bundler=none --no-interactive --unit-test-runner=none --skipFormat --linter=eslint`
);
const mainPath = `packages/${appName}/src/main.tsx`;
updateFile(
mainPath,
`
import {${libName}} from '@${workspaceName}/${libName}';
${readFile(mainPath)}
console.log(${libName}());
`
);
runCLI('sync');
// Add library to package.json to make sure it is linked (not needed for npm package manager)
updateJson(`packages/${appName}/package.json`, (json) => {
return {
...json,
devDependencies: {
...(json.devDependencies || {}),
[`@${workspaceName}/${libName}`]: 'workspace:*',
},
};
});
runCommand(
`cd packages/${appName} && ${getPackageManagerCommand().install}`
);
runCLI(`build ${appName}`);
checkFilesExist(`packages/${appName}/dist/index.html`);
}, 90_000);
});
+1 -1
View File
@@ -10,7 +10,7 @@ import {
updateFile,
} from '@nx/e2e/utils';
describe('Build React applications and libraries with Vite', () => {
describe('Build React applications and libraries with Webpack', () => {
beforeAll(() => {
newProject({
packages: ['@nx/react'],
+1 -1
View File
@@ -106,7 +106,7 @@ ${content}`
// check typecheck
expect(runCLI(`typecheck ${reactApp}`)).toContain(
`Successfully ran target typecheck for project @proj/${reactApp} and 5 tasks it depends on`
`Successfully ran target typecheck for project @proj/${reactApp} and 6 tasks it depends on`
);
}, 300_000);
});
@@ -6,6 +6,7 @@ import {
import { readFileSync } from 'fs';
import { join } from 'path';
import { TagsApi } from './tags.api';
// The rewriteMarkdownLinks function is used by routes, not by this API
interface StaticDocumentPaths {
params: { segments: string[] };
@@ -13,6 +14,11 @@ interface StaticDocumentPaths {
export class DocumentsApi {
private readonly manifest: Record<string, DocumentMetadata>;
private readonly processContent: boolean;
private readonly id: string;
private readonly prefix: string;
private readonly publicDocsRoot: string;
private readonly tagsApi: TagsApi;
constructor(
private readonly options: {
@@ -21,6 +27,7 @@ export class DocumentsApi {
prefix: string;
publicDocsRoot: string;
tagsApi: TagsApi;
processContent?: boolean;
}
) {
if (!options.id) {
@@ -46,6 +53,11 @@ export class DocumentsApi {
this.getAngularRspackPackage()
);
}
this.id = options.id;
this.prefix = options.prefix;
this.publicDocsRoot = options.publicDocsRoot;
this.tagsApi = options.tagsApi;
this.processContent = options.processContent !== false;
}
private getManifestKey(path: string): string {
@@ -99,28 +111,29 @@ export class DocumentsApi {
}
getFilePath(path: string): string {
return join(this.options.publicDocsRoot, `${path}.md`);
return join(this.publicDocsRoot, `${path}.md`);
}
getParamsStaticDocumentPaths(): StaticDocumentPaths[] {
return Object.keys(this.manifest).map((path) => ({
params: {
segments: !!this.options.prefix
? [this.options.prefix].concat(path.split('/').filter(Boolean).flat())
segments: !!this.prefix
? [this.prefix].concat(path.split('/').filter(Boolean).flat())
: path.split('/').filter(Boolean).flat(),
},
}));
}
getSlugsStaticDocumentPaths(): string[] {
if (this.options.prefix)
return Object.keys(this.manifest).map(
(path) => `/${this.options.prefix}` + path
);
if (this.prefix)
return Object.keys(this.manifest).map((path) => `/${this.prefix}` + path);
return Object.keys(this.manifest);
}
getDocument(path: string[]): ProcessedDocument {
path = path.map((p) => p.replace('.md', ''));
console.log('🔥 ath', path);
const document: DocumentMetadata | null =
this.manifest[this.getManifestKey(path.join('/'))] || null;
@@ -131,8 +144,12 @@ export class DocumentsApi {
path[2] === 'documents'
) {
const file = `generated/devkit/${path.slice(3).join('/')}`;
let content = readFileSync(this.getFilePath(file), 'utf8');
// Add code to process markdown links if needed
return {
content: readFileSync(this.getFilePath(file), 'utf8'),
content,
description: '',
filePath: this.getFilePath(file),
id: path.at(-1) || '',
@@ -146,8 +163,13 @@ export class DocumentsApi {
);
}
if (this.isDocumentIndex(document)) return this.getDocumentIndex(path);
let content = readFileSync(this.getFilePath(document.file), 'utf8');
// Add code to process markdown links if needed
return {
content: readFileSync(this.getFilePath(document.file), 'utf8'),
content,
description: document.description,
filePath: this.getFilePath(document.file),
id: document.id,
@@ -182,8 +204,7 @@ export class DocumentsApi {
getRelatedDocuments(tags: string[]): Record<string, RelatedDocument[]> {
const relatedDocuments = {};
tags.forEach(
(tag) =>
(relatedDocuments[tag] = this.options.tagsApi.getAssociatedItems(tag))
(tag) => (relatedDocuments[tag] = this.tagsApi.getAssociatedItems(tag))
);
return relatedDocuments;
@@ -204,9 +225,7 @@ export class DocumentsApi {
(card) =>
`{% card title="${card.title}" description="${
card.description
}" url="${[this.options.prefix, card.url]
.filter(Boolean)
.join('/')}" /%}\n`
}" url="${[this.prefix, card.url].filter(Boolean).join('/')}" /%}\n`
)
.join('');
return [
@@ -226,9 +245,10 @@ export class DocumentsApi {
`Document not found in manifest with: "${path.join('/')}"`
);
if (!!document.file)
if (!!document.file) {
const content = readFileSync(this.getFilePath(document.file), 'utf8');
return {
content: readFileSync(this.getFilePath(document.file), 'utf8'),
content: content,
description: document.description,
filePath: this.getFilePath(document.file),
id: document.id,
@@ -236,9 +256,11 @@ export class DocumentsApi {
relatedDocuments: this.getRelatedDocuments(document.tags),
tags: document.tags,
};
}
const content = this.generateDocumentIndexTemplate(document);
return {
content: this.generateDocumentIndexTemplate(document),
content: content,
description: document.description,
filePath: '',
id: document.id,
+66
View File
@@ -0,0 +1,66 @@
import { DefaultLayout } from '@nx/nx-dev/ui-common';
import {
CallToAction,
Features,
FeatureSections,
GettingStarted,
Hero,
Resources,
} from '@nx/nx-dev/ui-gradle';
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Nx For Java',
description:
'Add Nx to your Java project for distributed task execution, intelligent caching, and affected commands.',
alternates: {
canonical: 'https://nx.dev/java',
},
openGraph: {
url: 'https://nx.dev/java',
title: 'Nx For Java',
description:
'Add Nx to your Java project for distributed task execution, intelligent caching, and affected commands.',
images: [
{
url: 'https://nx.dev/socials/nx-gradle-media.png',
width: 800,
height: 421,
alt: 'Nx For Java',
type: 'image/jpeg',
},
],
siteName: 'Nx',
type: 'website',
},
};
export default function JavaPage(): JSX.Element {
return (
<DefaultLayout>
<Hero />
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mt-16 scroll-mt-16 lg:mt-32" id="features">
<Features />
</div>
{/* <div className="mt-16 scroll-mt-16 lg:mt-32" id="resources"> */}
{/* <Resources /> */}
{/* </div> */}
</div>
{/* <div className="mt-16 scroll-mt-16 lg:mt-32" id="feature-sections"> */}
{/* <FeatureSections /> */}
{/* </div> */}
<div className="mt-16 scroll-mt-16 lg:mt-32">
<GettingStarted />
</div>
<div className="overflow-hidden py-8 sm:py-8">
<CallToAction />
</div>
</DefaultLayout>
);
}
+256
View File
@@ -0,0 +1,256 @@
import { NextResponse } from 'next/server';
import fs from 'fs';
import path from 'path';
interface DocumentMetadata {
id: string;
name: string;
description: string;
path: string;
file: string;
isExternal: boolean;
itemList: DocumentMetadata[];
tags: string[];
}
interface MenuItem {
id: string;
name: string;
path: string;
isExternal: boolean;
disableCollapsible: boolean;
children: MenuItem[];
}
interface MenuSection {
id: string;
menu: MenuItem[];
}
// Markdown content for llms.txt - this is the static part
const llmsContent = `# Nx and LLMs
Nx provides excellent support for Large Language Models (LLMs) integration in your development workflow.
## Features
- AI-powered code generation
- Smart dependency analysis
- Automated refactoring suggestions
- Context-aware project scaffolding
## Getting Started
To get started with Nx LLM features, check out our documentation at [nx.dev/ai](https://nx.dev/ai).
## Supported Models
Nx works with various language models including:
- GPT-4
- Claude
- Llama
- And more
## Community
Join our community to learn more about how developers are using Nx with LLMs to supercharge their productivity.
`;
/**
* Reads all manifest files to create a mapping of paths to descriptions
*/
function loadDocumentDescriptions(): Record<string, string> {
try {
const descriptions: Record<string, string> = {};
const manifestsDir = path.join(
process.cwd(),
'public/documentation/generated/manifests'
);
// Load primary manifests (nx, extending-nx, ci)
const manifestFiles = ['nx.json', 'extending-nx.json', 'ci.json'];
manifestFiles.forEach((file) => {
try {
const manifestPath = path.join(manifestsDir, file);
if (fs.existsSync(manifestPath)) {
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
// Each manifest is a dictionary where keys are paths and values are document metadata
Object.values(manifest).forEach((doc: DocumentMetadata) => {
if (doc.path && doc.description) {
descriptions[doc.path] = doc.description;
}
});
}
} catch (error) {
console.error(`Error loading manifest file ${file}:`, error);
}
});
return descriptions;
} catch (error) {
console.error('Error loading document descriptions:', error);
return {};
}
}
/**
* Generates markdown for a menu item and its children
*/
function generateMenuItemMarkdown(
item: MenuItem,
descriptions: Record<string, string>,
indent: number = 0
): string {
if (item.isExternal) {
return '';
}
const spacing = ' '.repeat(indent * 2);
const description = descriptions[item.path] || '';
const link = item.path.startsWith('http')
? `[${item.name}](${item.path})`
: `[${item.name}](/m${item.path})`;
// Place description after the link, not inside it
let markdown = `${spacing}- ${link}${
description ? ': ' + description : ''
}\n`;
if (item.children && item.children.length > 0) {
item.children.forEach((child) => {
markdown += generateMenuItemMarkdown(child, descriptions, indent + 1);
});
}
return markdown;
}
/**
* Generates a sitemap from menu structure with .md extensions and descriptions
*/
async function generateSitemapFromMenus(): Promise<string> {
try {
const menusPath = path.join(
process.cwd(),
'public/documentation/generated/manifests/menus.json'
);
if (!fs.existsSync(menusPath)) {
throw new Error(`Menus file not found at: ${menusPath}`);
}
const menus: MenuSection[] = JSON.parse(fs.readFileSync(menusPath, 'utf8'));
const descriptions = loadDocumentDescriptions();
let sitemap = '## Documentation Structure\n\n';
// Process main sections (nx, extending-nx, ci)
['nx', 'extending-nx', 'ci'].forEach((sectionId) => {
const section = menus.find((m) => m.id === sectionId);
if (!section) return;
// Add section header
sitemap += `- ${
sectionId === 'nx'
? 'Nx'
: sectionId === 'extending-nx'
? 'Extending-nx'
: sectionId === 'ci'
? 'CI'
: sectionId
}\n\n`;
// Process all menu items in the section
section.menu.forEach((item) => {
sitemap += generateMenuItemMarkdown(item, descriptions, 1);
});
sitemap += '\n';
});
return sitemap;
} catch (error) {
console.error('Error generating sitemap from menus:', error);
// Fall back to modifying existing sitemap
return await generateModifiedSitemap();
}
}
/**
* Reads the sitemap markdown file and modifies links adding .md extension
* and document descriptions
*/
async function generateModifiedSitemap(): Promise<string> {
try {
const sitemapPath = path.join(
process.cwd(),
'public/documentation/shared/reference/sitemap.md'
);
if (!fs.existsSync(sitemapPath)) {
throw new Error(`Sitemap file not found at: ${sitemapPath}`);
}
const originalContent = fs.readFileSync(sitemapPath, 'utf8');
const descriptions = loadDocumentDescriptions();
// Transform links from [Name](/path) to [Name](/path.md): Description
const modifiedContent = originalContent.replace(
/\[([^\]]+)\]\(([^)]+)\)/g,
(match, linkText, linkUrl) => {
// Skip external links
if (linkUrl.startsWith('http')) {
return match;
}
// Add .md extension to the link and place description after the link
const description = descriptions[linkUrl] || '';
return `[${linkText}](${linkUrl}.md)${
description ? ': ' + description : ''
}`;
}
);
return '## Documentation Structure\n\n' + modifiedContent;
} catch (error) {
console.error('Error generating modified sitemap:', error);
return `## Documentation Structure\n\nError generating sitemap: ${error.message}`;
}
}
// GET handler for the route
export async function GET(request: Request) {
try {
// Always start with the static content
let fullContent = llmsContent;
// Then append the sitemap
let sitemapContent;
try {
sitemapContent = await generateSitemapFromMenus();
} catch (error) {
sitemapContent = await generateModifiedSitemap();
}
fullContent += sitemapContent;
return new NextResponse(fullContent, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
},
});
} catch (error) {
return new NextResponse(`Error: ${error.message}`, {
status: 500,
headers: {
'Content-Type': 'text/plain; charset=utf-8',
},
});
}
}
// We can make this static since the content is generated at build time
export const dynamic = 'force-static';
+55
View File
@@ -0,0 +1,55 @@
import { NextResponse } from 'next/server';
import { nxDocumentationApi } from '../../../lib/nx.api';
import { rewriteMarkdownLinks } from '../../../lib/markdown-utils';
export async function GET(
request: Request,
{ params }: { params: { path: string[] } }
) {
try {
// Get the document using the document API
const document = nxDocumentationApi.getDocument(params.path);
// Process the content to rewrite local links with the /m prefix
const processedContent = rewriteMarkdownLinks(document.content);
// Return the processed markdown content with proper headers
return new NextResponse(processedContent, {
headers: {
'Content-Type': 'text/markdown; charset=utf-8',
'Content-Disposition': 'inline; filename="document.md"',
},
});
} catch (error) {
// Return a markdown 404 message
return new NextResponse(
`# Not Found\n\nThe requested document could not be found.`,
{
status: 404,
headers: {
'Content-Type': 'text/markdown; charset=utf-8',
},
}
);
}
}
// Define which paths should be statically generated at build time
export function generateStaticParams() {
const reservedPaths = ['/ci', '/nx-api', '/changelog'];
// Get all document paths and filter out reserved ones
const documentPaths = nxDocumentationApi
.getSlugsStaticDocumentPaths()
.filter(
(path) => !reservedPaths.some((reserved) => path.startsWith(reserved))
);
// Convert paths to segment arrays for Next.js App Router
return documentPaths.map((path) => ({
path: path.split('/').filter(Boolean),
}));
}
// Make this route static at build time for performance
export const dynamic = 'force-static';
+44
View File
@@ -0,0 +1,44 @@
import { NextResponse } from 'next/server';
import { changeLogApi } from '../../../lib/changelog.api';
import { rewriteMarkdownLinks } from '../../../lib/markdown-utils';
export async function GET(request: Request) {
try {
// Get the changelog entries
const entries = changeLogApi.getChangelogEntries();
// Create a markdown document from the entries
let content = '# Nx Changelog\n\n';
// Add entries to the content in markdown format
entries.forEach((entry) => {
content += `## ${entry.version}\n\n`;
if (entry.content) {
// Add processed content
content += rewriteMarkdownLinks(entry.content) + '\n\n';
}
});
// Return the processed markdown content with proper headers
return new NextResponse(content, {
headers: {
'Content-Type': 'text/markdown; charset=utf-8',
'Content-Disposition': 'inline; filename="changelog.md"',
},
});
} catch (error) {
// Return a markdown 404 message
return new NextResponse(
`# Not Found\n\nThe changelog could not be found.`,
{
status: 404,
headers: {
'Content-Type': 'text/markdown; charset=utf-8',
},
}
);
}
}
// Make this route static at build time for performance
export const dynamic = 'force-static';
+51
View File
@@ -0,0 +1,51 @@
import { NextResponse } from 'next/server';
import { ciApi } from '../../../../lib/ci.api';
import { rewriteMarkdownLinks } from '../../../../lib/markdown-utils';
export async function GET(
request: Request,
{ params }: { params: { path: string[] } }
) {
try {
// Get the document using the document API
const document = ciApi.getDocument(['ci', ...params.path]);
// Process the content to rewrite local links with the /m prefix
const processedContent = rewriteMarkdownLinks(document.content);
// Return the processed markdown content with proper headers
return new NextResponse(processedContent, {
headers: {
'Content-Type': 'text/markdown; charset=utf-8',
'Content-Disposition': 'inline; filename="document.md"',
},
});
} catch (error) {
// Return a markdown 404 message
return new NextResponse(
`# Not Found\n\nThe requested document could not be found.`,
{
status: 404,
headers: {
'Content-Type': 'text/markdown; charset=utf-8',
},
}
);
}
}
// Define which paths should be statically generated at build time
export function generateStaticParams() {
// Get all document paths for the CI section
const documentPaths = ciApi
.getSlugsStaticDocumentPaths()
.map((path) => path.replace(/^\/ci\//, '')); // Remove the /ci/ prefix
// Convert paths to segment arrays for Next.js App Router
return documentPaths.map((path) => ({
path: path.split('/').filter(Boolean),
}));
}
// Make this route static at build time for performance
export const dynamic = 'force-static';
@@ -0,0 +1,51 @@
import { NextResponse } from 'next/server';
import { nxPluginsApi } from '../../../../lib/plugins.api';
import { rewriteMarkdownLinks } from '../../../../lib/markdown-utils';
export async function GET(
request: Request,
{ params }: { params: { path: string[] } }
) {
try {
// Get the document using the document API
const document = nxPluginsApi.getDocument(['extending-nx', ...params.path]);
// Process the content to rewrite local links with the /m prefix
const processedContent = rewriteMarkdownLinks(document.content);
// Return the processed markdown content with proper headers
return new NextResponse(processedContent, {
headers: {
'Content-Type': 'text/markdown; charset=utf-8',
'Content-Disposition': 'inline; filename="document.md"',
},
});
} catch (error) {
// Return a markdown 404 message
return new NextResponse(
`# Not Found\n\nThe requested document could not be found.`,
{
status: 404,
headers: {
'Content-Type': 'text/markdown; charset=utf-8',
},
}
);
}
}
// Define which paths should be statically generated at build time
export function generateStaticParams() {
// Get all document paths for the extending-nx section
const documentPaths = nxPluginsApi
.getSlugsStaticDocumentPaths()
.map((path) => path.replace(/^\/extending-nx\//, '')); // Remove the /extending-nx/ prefix
// Convert paths to segment arrays for Next.js App Router
return documentPaths.map((path) => ({
path: path.split('/').filter(Boolean),
}));
}
// Make this route static at build time for performance
export const dynamic = 'force-static';
@@ -0,0 +1,61 @@
import { NextResponse } from 'next/server';
import { nxPackagesApi } from '../../../../../../lib/packages.api';
import { tagsApi } from '../../../../../../lib/tags.api';
import { rewriteMarkdownLinks } from '../../../../../../lib/markdown-utils';
import { readFileSync } from 'fs';
import { join } from 'path';
export async function GET(
request: Request,
{ params }: { params: { name: string; path: string[] } }
) {
try {
// Get the package documents and file path
const packageDocuments = nxPackagesApi.getPackageDocuments(params.name);
const segments = ['nx-api', params.name, 'documents', ...params.path];
const docPath = '/' + segments.join('/');
// Find the document in the package documents
const documentMeta = packageDocuments[docPath];
if (!documentMeta) {
throw new Error(`Document not found: ${docPath}`);
}
// Read the file content
const filePath = join('public/documentation', `${documentMeta.file}.md`);
const content = readFileSync(filePath, 'utf8');
// Process the content to rewrite local links with the /m prefix
const processedContent = rewriteMarkdownLinks(content);
// Return the processed markdown content with proper headers
return new NextResponse(processedContent, {
headers: {
'Content-Type': 'text/markdown; charset=utf-8',
'Content-Disposition': 'inline; filename="document.md"',
},
});
} catch (error) {
console.error('Error serving nx-api document:', error);
// Return a markdown 404 message
return new NextResponse(
`# Not Found\n\nThe requested document could not be found.`,
{
status: 404,
headers: {
'Content-Type': 'text/markdown; charset=utf-8',
},
}
);
}
}
// Define which paths should be statically generated at build time
export function generateStaticParams() {
// For simplicity, we don't pre-generate these - they'll be generated on-demand
return [];
}
// Make this route dynamic for on-demand generation
export const dynamic = 'force-dynamic';
+25
View File
@@ -426,4 +426,29 @@ export const champions: Champion[] = [
],
location: 'London, United Kingdom',
},
{
name: 'Kevin Oliveira',
expertise: 'Frontend Architect. Nx Plugins, DDD, Angular, NestJS, DevOps',
imageUrl: '/images/champions/kevin-oliveira.webp',
contact: [
{
label: '@koliveira',
link: 'https://twitter.com/koliveira',
},
],
location: 'Rhode Island, USA',
},
{
name: 'Guilherme Siquinelli',
expertise:
'Talks, Conferences, Community, Monorepos with NX / Angular / NestJS Architecture, Repository Structure, Writing Plugins, Dev Tools, Releases, Blog Posts, Youtube Videos, Open Source',
imageUrl: '/images/champions/guilherme-siquinelli.webp',
contact: [
{
label: 'email@guiseek.dev',
link: 'mailto:email@guiseek.dev',
},
],
location: 'Paraná, Brazil',
},
];
+44
View File
@@ -0,0 +1,44 @@
/**
* Rewrites local links in markdown content to include the /m prefix
* @param content Markdown content
* @returns Processed markdown content with rewritten links
*/
export function rewriteMarkdownLinks(content: string): string {
// Process regular markdown links [text](url)
let processedContent = content.replace(
/\[([^\]]+)\]\(([^)]+)\)/g,
(match, linkText, linkUrl) => {
// Skip external links and non-path links (like anchors)
if (linkUrl.startsWith('http') || linkUrl.startsWith('#')) {
return match;
}
// Add /m prefix to local links that start with /
if (linkUrl.startsWith('/')) {
return `[${linkText}](/m${linkUrl})`;
}
return match;
}
);
// Process image links ![alt](url)
processedContent = processedContent.replace(
/!\[([^\]]*)\]\(([^)]+)\)/g,
(match, altText, imageUrl) => {
// Skip external image links
if (imageUrl.startsWith('http')) {
return match;
}
// Add /m prefix to local image links that start with /
if (imageUrl.startsWith('/')) {
return `![${altText}](/m${imageUrl})`;
}
return match;
}
);
return processedContent;
}
@@ -92,11 +92,12 @@ export const getStaticProps: GetStaticProps = async ({
},
};
} catch (e) {
return {
notFound: true,
props: {
statusCode: 404,
},
};
throw e;
// return {
// notFound: true,
// props: {
// statusCode: 404,
// },
// };
}
};
+1 -1
View File
@@ -119,7 +119,7 @@ export default function Packages({
<img
className="h-5 w-5 object-cover opacity-75 dark:invert"
loading="lazy"
src={iconsMap[pkg.name]}
src={iconsMap[pkg.name] || iconsMap['nx']}
alt={pkg.name + ' illustration'}
aria-hidden="true"
/>
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 KiB

+3 -1
View File
@@ -18,7 +18,9 @@ export function Layout({
}}
/>
</div>
<main className="py-24 sm:py-32">{children}</main>
<main className="py-24 sm:py-32" data-document="main">
{children}
</main>
</div>
</div>
);
+4 -1
View File
@@ -31,7 +31,10 @@ export function DefaultLayout({
}}
/>
</div>
<main className={isHome || hideHeader ? '' : 'py-24 sm:py-32'}>
<main
data-document="main"
className={isHome || hideHeader ? '' : 'py-24 sm:py-32'}
>
{children}
</main>
</div>
@@ -1,5 +1,5 @@
'use client';
import { Fragment, ReactElement } from 'react';
import { Fragment, type MouseEvent, ReactElement } from 'react';
import { NxCloudAnimatedIcon, NxIcon } from '@nx/nx-dev/ui-icons';
import {
Bars3Icon,
@@ -74,6 +74,11 @@ export function DocumentationHeader({
!isChangelog &&
!isAiChat;
const handleContextMenu = (e: MouseEvent) => {
e.preventDefault();
router.push('/brands');
};
const sections = [
{ name: 'Nx', href: '/getting-started/intro', current: isNx },
{
@@ -217,8 +222,11 @@ export function DocumentationHeader({
href="/"
className="flex flex-grow items-center px-4 text-slate-900 lg:px-0 dark:text-white"
prefetch={false}
onContextMenu={handleContextMenu}
>
<span className="sr-only">Nx</span>
<span className="sr-only">
Nx Left-click: Home. Right-click: Brands.
</span>
<NxIcon aria-hidden="true" className="h-8 w-8" />
</Link>
<Link
+19 -2
View File
@@ -17,13 +17,20 @@ import {
} from '@heroicons/react/24/outline';
import cx from 'classnames';
import Link from 'next/link';
import { Fragment, ReactElement, useEffect, useState } from 'react';
import {
Fragment,
type MouseEvent,
ReactElement,
useEffect,
useState,
} from 'react';
import { ButtonLink, ButtonLinkProps } from '../button';
import { resourceMenuItems } from './menu-items';
import { MobileMenuItem } from './mobile-menu-item';
import { SectionsMenu } from './sections-menu';
import { AlgoliaSearch } from '@nx/nx-dev/feature-search';
import { GitHubIcon, NxIcon } from '@nx/nx-dev/ui-icons';
import { useRouter } from 'next/navigation';
interface HeaderProps {
ctaButtons?: ButtonLinkProps[];
@@ -32,6 +39,13 @@ interface HeaderProps {
export function Header({ ctaButtons }: HeaderProps): ReactElement {
let [isOpen, setIsOpen] = useState(false);
const router = useRouter();
const handleContextMenu = (e: MouseEvent) => {
e.preventDefault();
router.push('/brands');
};
// We need to close the popover if the route changes or the window is resized to prevent the popover from being stuck open.
const checkSizeAndClosePopover = () => {
const breakpoint = 1024; // This is the standard Tailwind lg breakpoint value
@@ -78,8 +92,11 @@ export function Header({ ctaButtons }: HeaderProps): ReactElement {
href="/"
className="mr-4 flex items-center text-slate-900 dark:text-white"
prefetch={false}
onContextMenu={handleContextMenu}
>
<span className="sr-only">Nx</span>
<span className="sr-only">
Nx Left-click: Home. Right-click: Brands.
</span>
<NxIcon aria-hidden="true" className="h-8 w-8" />
</Link>
<nav
+3 -1
View File
@@ -21,7 +21,9 @@ export function Layout({
}}
/>
</div>
<main className="py-24 sm:py-32">{children}</main>
<main className="py-24 sm:py-32" data-document="main">
{children}
</main>
</div>
<Footer />
</div>
@@ -7,7 +7,7 @@ export function EnterpriseLayout({
return (
<div className="w-full dark:bg-slate-950">
<Header />
<main>{children}</main>
<main data-document="main">{children}</main>
<Footer />
</div>
);
+12
View File
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
+18
View File
@@ -0,0 +1,18 @@
{
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
+7
View File
@@ -0,0 +1,7 @@
# nx-dev-ui-gradle
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test nx-dev-ui-gradle` to execute the unit tests via [Jest](https://jestjs.io).
+10
View File
@@ -0,0 +1,10 @@
/* eslint-disable */
export default {
displayName: 'nx-dev-ui-gradle',
preset: '../../jest.preset.js',
transform: {
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }],
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/nx-dev/ui-gradle',
};
+8
View File
@@ -0,0 +1,8 @@
{
"name": "nx-dev-ui-gradle",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "nx-dev/ui-gradle/src",
"projectType": "library",
"tags": [],
"targets": {}
}
+1
View File
@@ -0,0 +1 @@
export * from './lib';
+130
View File
@@ -0,0 +1,130 @@
import { ButtonLink, SectionHeading } from '@nx/nx-dev/ui-common';
import { GradleIcon } from '@nx/nx-dev/ui-icons';
import { cx } from '@nx/nx-dev/ui-primitives';
import React, { ReactElement } from 'react';
import Link from 'next/link';
export function CallToAction(): ReactElement {
return (
<section className="relative isolate px-6 py-32 sm:py-40 lg:px-8">
{/* Background pattern */}
<svg
className="absolute inset-0 -z-10 h-full w-full stroke-black/10 [mask-image:radial-gradient(100%_100%_at_top_right,white,transparent)] dark:stroke-white/10"
aria-hidden="true"
>
<defs>
<pattern
id="gradle-cta-pattern"
width={200}
height={200}
x="50%"
y={0}
patternUnits="userSpaceOnUse"
>
<path d="M.5 200V.5H200" fill="none" />
</pattern>
</defs>
<svg
x="50%"
y={0}
className="overflow-visible fill-slate-200/20 dark:fill-slate-800/20"
>
<path
d="M-200 0h201v201h-201Z M600 0h201v201h-201Z M-400 600h201v201h-201Z M200 800h201v201h-201Z"
strokeWidth={0}
/>
</svg>
<rect
width="100%"
height="100%"
strokeWidth={0}
fill="url(#gradle-cta-pattern)"
/>
</svg>
{/* Gradient background */}
<div
className="pointer-events-none absolute inset-x-0 top-10 -z-10 flex transform-gpu justify-center overflow-hidden blur-3xl"
aria-hidden="true"
>
<div
className="aspect-[1108/632] w-[69.25rem] flex-none bg-gradient-to-r from-blue-500 to-blue-600 opacity-20"
style={{
clipPath:
'polygon(73.6% 51.7%, 91.7% 11.8%, 100% 46.4%, 97.4% 82.2%, 92.5% 84.9%, 75.7% 64%, 55.3% 47.5%, 46.5% 49.4%, 45% 62.9%, 50.3% 87.2%, 21.3% 64.1%, 0.1% 100%, 5.4% 51.1%, 21.4% 63.9%, 58.9% 0.2%, 73.6% 51.7%)',
}}
/>
</div>
{/* Content */}
<div className="mx-auto max-w-2xl text-center">
<div className="mb-8 flex justify-center">
<GradleIcon className="h-16 w-16 text-slate-900 dark:text-white" />
</div>
<h2 className="text-3xl font-medium tracking-tight text-slate-950 sm:text-5xl dark:text-white">
Get Started With Gradle
</h2>
<p className="mt-8">
Check out the documentation for the <code>@nx/gradle</code> plugin to
learn more
</p>
<div className="mt-10 flex items-center justify-center gap-x-6">
<Link
href="/nx-api/gradle/documents/overview"
title="Get started with the tutorial"
prefetch={false}
className="rounded-md bg-slate-950 px-3.5 py-2.5 text-sm font-semibold text-slate-100 shadow-sm hover:bg-slate-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white dark:bg-white dark:text-slate-900 dark:hover:bg-slate-100"
>
Read the Docs{' '}
<span
aria-hidden="true"
className="inline-block transition group-hover:translate-x-1"
>
</span>
</Link>
{/* <Link
href="/packages/gradle/gradle-plugin"
title="Read the documentation"
prefetch={false}
className="group text-sm font-semibold leading-6 text-slate-950 dark:text-white"
>
Read documentation{' '}
<span
aria-hidden="true"
className="inline-block transition group-hover:translate-x-1"
>
</span>
</Link> */}
</div>
<h3 className="mt-16 text-3xl font-medium tracking-tight text-slate-950 dark:text-white">
Looking for Maven?
</h3>
<p className="mt-8"> Maven support is coming soon. </p>
<p className="mt-4">
Join the Nx Community Discord to be notified when it is released.{' '}
</p>
<div className="mt-10 flex items-center justify-center gap-x-6">
<Link
href="https://go.nx.dev/community"
title="Join the Nx Community Discord"
prefetch={false}
className="rounded-md bg-slate-950 px-3.5 py-2.5 text-sm font-semibold text-slate-100 shadow-sm hover:bg-slate-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white dark:bg-white dark:text-slate-900 dark:hover:bg-slate-100"
>
Join Us On Discord{' '}
<span
aria-hidden="true"
className="inline-block transition group-hover:translate-x-1"
>
</span>
</Link>
</div>
</div>
</section>
);
}
@@ -0,0 +1,137 @@
import { ReactElement } from 'react';
import { ButtonLink, SectionHeading } from '@nx/nx-dev/ui-common';
export function FeatureSections(): ReactElement {
return (
<section className="feature-sections py-16 sm:py-24">
<div className="mx-auto px-6 lg:max-w-7xl">
<SectionHeading id="nx-gradle-features" as="h2" variant="title">
Supercharge your Java Projects{' '}
<span className="rounded-lg bg-gradient-to-r from-cyan-500 to-blue-500 bg-clip-text text-transparent">
with Nx
</span>
</SectionHeading>
<div className="mt-10 grid grid-cols-1 gap-8 sm:mt-16 md:grid-cols-2 lg:grid-cols-3 ">
{/* Affected Section */}
<FeatureSection
title="Affected"
description="Run tasks only on projects affected by your changes, saving time and resources. Nx understands your project's dependency graph and automatically determines which projects need to be rebuilt."
imageSrc="/images/enterprise/nx-affected.avif"
alt="Nx Affected: Run tasks only on affected projects"
tag="Affected"
href="/ci/features/affected"
/>
{/* Remote Caching Section */}
<FeatureSection
title="Never build the same code twice"
description="Nx Replay (Remote Caching) ensures tasks are never rebuilt unnecessarily. Share cached results across your team and CI pipelines to dramatically reduce build times and resource usage."
imageSrc="/images/enterprise/nx-replay.avif"
alt="Nx Replay: Remote caching"
tag="Nx Replay"
href="/features/cache-task-results"
/>
{/* Distribution Section */}
<FeatureSection
title="Distribute tasks across machines"
description="Nx Agents intelligently distribute your Gradle tasks across multiple machines, significantly reducing build times. Dynamically allocate agents based on PR size to balance speed and cost."
imageSrc="/images/enterprise/nx-agents.avif"
alt="Nx Agents: Task distribution"
tag="Nx Agents"
href="/ci/features/distribute-task-execution"
/>
{/* Atomizer Section */}
<FeatureSection
title="Break tests down, speed execution up"
description="Atomizer automatically splits large Gradle test tasks into smaller, atomized units, enabling lightning fast parallel testing. No code changes required - just configure and run."
imageSrc="/images/enterprise/nx-atomizer.avif"
alt="Nx Atomizer: Split large test tasks"
tag="Atomizer"
href="/ci/features/split-e2e-tasks"
/>
{/* Flaky Test Retries Section */}
<FeatureSection
title="Automatically handle flaky tests"
description="Nx detects and automatically retries flaky tests, enhancing the reliability of your CI processes and minimizing time spent debugging intermittent failures."
imageSrc="/images/enterprise/nx-flaky-tasks-detection.avif"
alt="Nx flaky task detection & rerun"
tag="Flaky test retries"
href="/nx-api/gradle/documents/overview"
/>
</div>
</div>
</section>
);
}
interface FeatureSectionProps {
title: string;
description: string;
imageSrc: string;
alt: string;
tag: string;
href: string;
reversed?: boolean;
}
function FeatureSection({
title,
description,
imageSrc,
alt,
tag,
href,
}: FeatureSectionProps): ReactElement {
return (
<div
className={`feature-section flex flex-col overflow-hidden rounded-2xl bg-white shadow ring-1 ring-black/5 dark:bg-slate-950 dark:ring-white/10 `}
>
<div className="h-0 w-full md:h-72">
<img
alt={alt}
src={imageSrc}
className="h-full w-full object-cover object-center"
/>
</div>
<div className="relative flex flex-1 flex-col justify-center p-8 lg:p-10">
<div>
<span className="mb-3 inline-flex items-center rounded-full bg-slate-400/10 px-3 py-1 text-sm font-medium text-slate-600 ring-1 ring-inset ring-slate-400/20 dark:text-slate-300">
{tag}
</span>
<h3 className="mt-2 text-xl font-medium tracking-tight text-slate-950 lg:text-2xl dark:text-white">
{title}
</h3>
<p className="mt-4 text-base/relaxed text-slate-600 dark:text-slate-300">
{description}
</p>
<div className="mt-6">
<ButtonLink
href={href}
title={`Learn more about ${title}`}
variant="secondary"
size="default"
>
Learn more
<svg
className="ml-2 h-4 w-4"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M3 10a.75.75 0 01.75-.75h10.638L10.23 5.29a.75.75 0 111.04-1.08l5.5 5.25a.75.75 0 010 1.08l-5.5 5.25a.75.75 0 11-1.04-1.08l4.158-3.96H3.75A.75.75 0 013 10z"
clipRule="evenodd"
/>
</svg>
</ButtonLink>
</div>
</div>
</div>
</div>
);
}
+277
View File
@@ -0,0 +1,277 @@
import { SectionHeading } from '@nx/nx-dev/ui-common';
import { cx } from '@nx/nx-dev/ui-primitives';
import React, { ReactElement } from 'react';
export function Features(): ReactElement {
return (
<>
<SectionHeading as="h2" variant="title">
Features
</SectionHeading>
<p className="mt-4 text-lg text-slate-500 dark:text-slate-400">
The Nx Plugin for Java integrates seamlessly with your existing Gradle
builds, adding powerful capabilities to your workflow.
</p>
<div className="mt-12 grid grid-cols-1 gap-6 sm:gap-8 md:grid-cols-2 lg:grid-cols-3">
<FeatureCard
title="Task Caching"
description="Cache task results locally and remotely, avoiding redundant builds and speeding up your development workflow."
href="/features/cache-task-results"
icon={
<svg
className="h-6 w-6"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="2"
/>
<polyline
points="12 6 12 12 16 14"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
}
/>
<FeatureCard
title="Distributed Task Execution"
description="Run your project tasks across multiple machines, dramatically reducing build times for large repositories."
href="/ci/features/distribute-task-execution"
icon={
<svg
className="h-6 w-6"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
x="2"
y="7"
width="20"
height="14"
rx="2"
stroke="currentColor"
strokeWidth="2"
/>
<path
d="M6 7V4.5C6 3.67157 6.67157 3 7.5 3H16.5C17.3284 3 18 3.67157 18 4.5V7"
stroke="currentColor"
strokeWidth="2"
/>
<path
d="M12 14L12 10"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M9 12L15 12"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
}
/>
<FeatureCard
title="Affected Targets"
description="Run tasks only on projects affected by your changes, saving time and computing resources."
href="/ci/features/affected"
icon={
<svg
className="h-6 w-6"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M22 11.08V12C21.9988 14.1564 21.3005 16.2547 20.0093 17.9818C18.7182 19.709 16.9033 20.9725 14.8354 21.5839C12.7674 22.1953 10.5573 22.1219 8.53447 21.3746C6.51168 20.6273 4.78465 19.2461 3.61096 17.4371C2.43727 15.628 1.87979 13.4881 2.02168 11.3363C2.16356 9.18455 2.99721 7.13631 4.39828 5.49706C5.79935 3.85781 7.69279 2.71537 9.79619 2.24013C11.8996 1.7649 14.1003 1.98232 16.07 2.85999"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M22 4L12 14L9 11"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
}
/>
<FeatureCard
title="Project Graph"
description="Nx automatically infers your project graph from project's configuration, providing visualization and dependency analysis."
href="/features/explore-graph"
icon={
<svg
className="h-6 w-6"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="5"
cy="5"
r="2"
stroke="currentColor"
strokeWidth="2"
/>
<circle
cx="19"
cy="5"
r="2"
stroke="currentColor"
strokeWidth="2"
/>
<circle
cx="5"
cy="19"
r="2"
stroke="currentColor"
strokeWidth="2"
/>
<circle
cx="19"
cy="19"
r="2"
stroke="currentColor"
strokeWidth="2"
/>
<path d="M7 5H17" stroke="currentColor" strokeWidth="2" />
<path d="M5 7V17" stroke="currentColor" strokeWidth="2" />
<path d="M7 19H17" stroke="currentColor" strokeWidth="2" />
<path d="M19 7V17" stroke="currentColor" strokeWidth="2" />
</svg>
}
/>
<FeatureCard
title="Split E2E Tests"
description="Automatically split your E2E tests for faster parallel execution in CI environments with Atomizer."
href="/ci/features/split-e2e-tasks"
icon={
<svg
className="h-6 w-6"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14 3V7C14 7.55228 14.4477 8 15 8H19"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M17 21H7C5.89543 21 5 20.1046 5 19V5C5 3.89543 5.89543 3 7 3H14L19 8V19C19 20.1046 18.1046 21 17 21Z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M10 12L8 14L10 16"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M14 12L16 14L14 16"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
}
/>
<FeatureCard
title="Zero Configuration"
description="Add Nx to your project in minutes with no changes to your existing setup required."
href="/nx-api/gradle/documents/overview"
icon={
<svg
className="h-6 w-6"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 2L2 7L12 12L22 7L12 2Z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M2 17L12 22L22 17"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M2 12L12 17L22 12"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
}
/>
</div>
</>
);
}
interface FeatureCardProps {
title: string;
description: string;
icon: JSX.Element;
href?: string;
}
function FeatureCard({
title,
description,
icon,
href = '#',
}: FeatureCardProps): ReactElement {
return (
<a href={href} className="block h-full transform-gpu">
<div
className={cx(
'group relative h-full w-full overflow-hidden rounded-lg border border-slate-200 bg-white p-6',
'dark:border-slate-800 dark:bg-slate-900 dark:hover:border-blue-900 dark:hover:shadow-blue-900/20',
'before:absolute before:inset-0 before:z-0 before:bg-gradient-to-br before:from-blue-50 before:to-transparent before:opacity-0 before:transition-opacity',
'transition-all duration-300 ease-out',
'hover:-translate-y-2 hover:border-blue-200 hover:shadow-xl hover:shadow-blue-100/50',
'hover:before:opacity-100 dark:before:from-blue-950'
)}
>
<div className="relative z-10">
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded-lg bg-blue-50 text-blue-600 transition-transform duration-300 group-hover:scale-110 dark:bg-slate-800 dark:text-blue-400 dark:group-hover:bg-blue-900">
{icon}
</div>
<h3 className="mb-2 text-lg font-medium">{title}</h3>
<p className="text-slate-500 dark:text-slate-400">{description}</p>
</div>
</div>
</a>
);
}
+153
View File
@@ -0,0 +1,153 @@
/* eslint-disable @nx/enforce-module-boundaries */
import {
ButtonLink,
SectionHeading,
Strong,
SectionDescription,
} from '@nx/nx-dev/ui-common';
import { JavaIcon } from '@nx/nx-dev/ui-icons';
/* eslint-enable @nx/enforce-module-boundaries */
import { ReactElement } from 'react';
export function Hero(): ReactElement {
return (
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto max-w-2xl text-center">
{/* Logo displayed above the title */}
<div className="mb-6 flex justify-center">
<div className="rounded-full bg-slate-100 p-4 dark:bg-slate-800">
<JavaIcon className="h-20 w-20" />
</div>
</div>
<SectionHeading as="h1" variant="title" className="mt-6">
Supercharge Your Java Projects
</SectionHeading>
<SectionDescription as="h2" className="mt-6">
Get distributed tasks, intelligent caching, and target affected
packages to optimize your build process and CI pipeline. Support for{' '}
<Strong>Gradle</Strong> and <Strong>Maven</Strong> (coming soon)
</SectionDescription>
<div className="mt-10 flex flex-col items-center justify-center gap-6 sm:flex-row">
<ButtonLink
href="https://nx.dev/getting-started/tutorials/gradle-tutorial"
variant="primary"
size="default"
title="Get Started"
>
Get Started with Nx for Java
</ButtonLink>
<a
href="https://github.com/nrwl/nx-recipes/tree/main/gradle"
target="_blank"
rel="noopener noreferrer"
className="group inline-flex items-center text-sm font-semibold leading-6 text-slate-800 dark:text-white"
>
View Example Project{' '}
<span
aria-hidden="true"
className="ml-1 inline-block transition-transform group-hover:translate-x-1"
>
</span>
</a>
</div>
</div>
</div>
);
}
export function GettingStarted(): ReactElement {
return (
<section className="relative py-16 sm:py-24">
<div className="absolute inset-0 bg-white dark:bg-slate-900"></div>
<div className="relative mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto max-w-2xl text-center">
<h2 className="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl dark:text-white">
Add Nx To An Existing Project
</h2>
<p className="mt-8">
Currently Nx only supports Gradle. Support for Maven will come soon
</p>
</div>
<div className="mt-12 grid grid-cols-1 gap-6 sm:gap-8 md:grid-cols-2 ">
<GetStartedCard
title="Install Nx"
command="brew install nx"
description="Get the <code>nx</code> executable from homebrew"
/>
<GetStartedCard
title="Add Nx To Your Project"
command="nx init"
description="Select <code>@nx/gradle</code> when prompted."
/>
<GetStartedCard
title="Run Tasks With Nx"
command="nx build <project>"
description="Nx will automatically infers tasks from your projects."
/>
<GetStartedCard
title="Run Affected Tasks"
command="nx affected -t build"
description="Nx adds caching, distribution, and affected commands without changing your setup."
/>
</div>
{/* GitHub example project link */}
<div className="mt-12 flex justify-center">
<a
href="https://github.com/nrwl/nx-recipes/tree/main/gradle"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 rounded-lg border border-slate-200 bg-white px-6 py-3 font-medium text-slate-900 shadow-sm transition-all hover:-translate-y-1 hover:border-blue-400 hover:shadow-md dark:border-slate-700 dark:bg-slate-800 dark:text-white dark:hover:border-blue-500"
>
<svg
className="h-5 w-5"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 2C6.477 2 2 6.477 2 12C2 16.418 4.865 20.166 8.839 21.489C9.339 21.581 9.521 21.284 9.521 21.029C9.521 20.799 9.513 20.065 9.508 19.27C6.726 19.891 6.139 17.962 6.139 17.962C5.685 16.812 5.029 16.516 5.029 16.516C4.121 15.89 5.098 15.902 5.098 15.902C6.101 15.97 6.629 16.925 6.629 16.925C7.521 18.43 8.97 18.008 9.539 17.762C9.631 17.097 9.889 16.676 10.175 16.419C7.954 16.159 5.62 15.304 5.62 11.453C5.62 10.355 6.009 9.457 6.649 8.758C6.546 8.514 6.203 7.543 6.746 6.161C6.746 6.161 7.585 5.902 9.497 7.203C10.295 6.988 11.15 6.88 12 6.876C12.85 6.88 13.705 6.988 14.505 7.203C16.415 5.902 17.253 6.161 17.253 6.161C17.798 7.543 17.455 8.514 17.352 8.758C17.994 9.457 18.38 10.355 18.38 11.453C18.38 15.313 16.042 16.156 13.815 16.411C14.174 16.726 14.495 17.347 14.495 18.297C14.495 19.648 14.483 20.709 14.483 21.029C14.483 21.287 14.661 21.586 15.171 21.487C19.138 20.161 22 16.417 22 12C22 6.477 17.523 2 12 2Z"
fill="currentColor"
/>
</svg>
View Example Project on GitHub
</a>
</div>
</div>
</section>
);
}
interface GetStartedCardProps {
title: string;
command: string;
description: string;
}
function GetStartedCard({
title,
command,
description,
}: GetStartedCardProps): ReactElement {
return (
<div className="h-full rounded-xl border border-slate-200 bg-white p-6 shadow-sm transition-all hover:shadow-md dark:border-slate-700 dark:bg-slate-800">
<h3 className="text-lg font-medium text-slate-900 dark:text-white">
{title}
</h3>
<div className="mt-3 overflow-hidden rounded-md bg-slate-800 px-4 py-3 text-sm text-white dark:bg-slate-950">
<code style={{ whiteSpace: 'pre-line' }}>{command}</code>
</div>
<p
className="mt-3 text-sm text-slate-600 dark:text-slate-300"
dangerouslySetInnerHTML={{ __html: description }}
></p>
</div>
);
}
+5
View File
@@ -0,0 +1,5 @@
export * from './hero';
export * from './features';
export * from './resources';
export * from './call-to-action';
export * from './feature-sections';
+71
View File
@@ -0,0 +1,71 @@
import { ButtonLink, SectionHeading, TextLink } from '@nx/nx-dev/ui-common';
import { cx } from '@nx/nx-dev/ui-primitives';
import React, { ReactElement } from 'react';
export function Resources(): ReactElement {
return (
<>
<SectionHeading as="h2" variant="title">
Resources
</SectionHeading>
<p className="mt-4 text-lg text-slate-500 dark:text-slate-400">
Learn more about using Nx with Java projects with these resources.
</p>
<div className="mt-12 grid grid-cols-1 gap-6 sm:gap-8 md:grid-cols-3">
<ResourceCard
title="Gradle Tutorial"
description="Follow this step-by-step tutorial to add Nx to an existing Gradle project."
linkText="View tutorial"
href="/tutorials/gradle"
/>
<ResourceCard
title="Gradle Plugin Documentation"
description="Learn all the details about the Nx Gradle plugin configuration and usage."
linkText="View docs"
href="/packages/gradle/gradle-plugin"
/>
<ResourceCard
title="CI with Nx"
description="Learn how to configure Continuous Integration with Nx for faster builds."
linkText="View CI docs"
href="/ci/intro/ci-with-nx"
/>
</div>
</>
);
}
interface ResourceCardProps {
title: string;
description: string;
linkText: string;
href: string;
}
function ResourceCard({
title,
description,
linkText,
href,
}: ResourceCardProps): ReactElement {
return (
<div
className={cx(
'flex h-full flex-col justify-between rounded-lg border border-slate-200 bg-white p-6 shadow-sm transition-all hover:shadow-md dark:border-slate-800 dark:bg-slate-900'
)}
>
<div>
<h3 className="mb-2 text-lg font-medium">{title}</h3>
<p className="text-slate-500 dark:text-slate-400">{description}</p>
</div>
<div className="mt-6">
<ButtonLink href={href} size="small" variant="primary" title={linkText}>
{linkText}
</ButtonLink>
</div>
</div>
);
}
+20
View File
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../tsconfig.base.json"
}
+24
View File
@@ -0,0 +1,24 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": [
"node",
"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts"
],
"lib": ["DOM", "es2019"]
},
"exclude": [
"jest.config.ts",
"src/**/*.spec.ts",
"src/**/*.test.ts",
"src/**/*.spec.tsx",
"src/**/*.test.tsx",
"src/**/*.spec.js",
"src/**/*.test.js",
"src/**/*.spec.jsx",
"src/**/*.test.jsx"
],
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
}
+20
View File
@@ -0,0 +1,20 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
]
}
+16 -16
View File
@@ -105,7 +105,7 @@ export function PlansDisplay(): ReactElement {
</Link>
</span>
</li>
<li className="flex items-start justify-start gap-x-2 py-2.5">
<li className="flex items-center justify-start gap-x-2 py-2.5">
<CheckCircleIcon
aria-hidden="true"
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
@@ -178,28 +178,28 @@ export function PlansDisplay(): ReactElement {
<li className="py-2.5">
<span className="font-medium">Included for free</span>
</li>
<li className="flex items-start justify-start gap-x-2 py-2.5">
<li className="flex items-center justify-start gap-x-2 py-2.5">
<CheckCircleIcon
aria-hidden="true"
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
/>
<span>5 active contributors¹</span>
</li>
<li className="flex items-start justify-start gap-x-2 py-2.5">
<li className="flex items-center justify-start gap-x-2 py-2.5">
<CheckCircleIcon
aria-hidden="true"
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
/>
<span>50,000 monthly credits</span>
</li>
<li className="flex items-start justify-start gap-x-2 py-2.5">
<li className="flex items-center justify-start gap-x-2 py-2.5">
<CheckCircleIcon
aria-hidden="true"
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
/>
<span>10 concurrent CI connections</span>
</li>
<li className="flex items-start justify-start gap-x-2 py-2.5">
<li className="flex items-center justify-start gap-x-2 py-2.5">
<CheckCircleIcon
aria-hidden="true"
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
@@ -220,7 +220,7 @@ export function PlansDisplay(): ReactElement {
AI integrations
</Link>
</li>
<li className="flex items-start justify-start gap-x-2 py-2.5">
<li className="flex items-center justify-start gap-x-2 py-2.5">
<CheckCircleIcon
aria-hidden="true"
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
@@ -230,21 +230,21 @@ export function PlansDisplay(): ReactElement {
<li className="py-2.5">
<span className="font-medium">Add-ons</span>
</li>
<li className="flex items-start justify-start gap-x-2 py-2.5">
<li className="flex items-center justify-start gap-x-2 py-2.5">
<PlusIcon
aria-hidden="true"
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
/>
<span>$19 per active contributor¹ / month</span>
</li>
<li className="flex items-start justify-start gap-x-2 py-2.5">
<li className="flex items-center justify-start gap-x-2 py-2.5">
<PlusIcon
aria-hidden="true"
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
/>
<span>$5.50 per 10,000 credits</span>
</li>
<li className="flex items-start justify-start gap-x-2 py-2.5">
<li className="flex items-center justify-start gap-x-2 py-2.5">
<PlusIcon
aria-hidden="true"
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
@@ -297,21 +297,21 @@ export function PlansDisplay(): ReactElement {
<li className="py-2.5">
<span className="font-medium">Includes</span>
</li>
<li className="flex items-start justify-start gap-x-2 py-2.5">
<li className="flex items-center justify-start gap-x-2 py-2.5">
<CheckCircleIcon
aria-hidden="true"
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
/>
<span>Volume discounts on credits available</span>
</li>
<li className="flex items-start justify-start gap-x-2 py-2.5">
<li className="flex items-center justify-start gap-x-2 py-2.5">
<CheckCircleIcon
aria-hidden="true"
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
/>
<span>White glove onboarding</span>
</li>
<li className="flex items-start justify-start gap-x-2 py-2.5">
<li className="flex items-center justify-start gap-x-2 py-2.5">
<CheckCircleIcon
aria-hidden="true"
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
@@ -335,7 +335,7 @@ export function PlansDisplay(): ReactElement {
: a suite of premium extensions for the Nx CLI
</span>
</li>
<li className="flex items-start justify-start gap-x-2 py-2.5">
<li className="flex items-center justify-start gap-x-2 py-2.5">
<CheckCircleIcon
aria-hidden="true"
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
@@ -344,7 +344,7 @@ export function PlansDisplay(): ReactElement {
Work hand-in-hand with the Nx team for continual improvement
</span>
</li>
<li className="flex items-start justify-start gap-x-2 py-2.5">
<li className="flex items-center justify-start gap-x-2 py-2.5">
<CheckCircleIcon
aria-hidden="true"
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
@@ -354,14 +354,14 @@ export function PlansDisplay(): ReactElement {
self-contained, on-prem
</span>
</li>
<li className="flex items-start justify-start gap-x-2 py-2.5">
<li className="flex items-center justify-start gap-x-2 py-2.5">
<CheckCircleIcon
aria-hidden="true"
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
/>
<span>SSO / SAML Login</span>
</li>
<li className="flex items-start justify-start gap-x-2 py-2.5">
<li className="flex items-center justify-start gap-x-2 py-2.5">
<CheckCircleIcon
aria-hidden="true"
className="h-6 w-5 flex-none text-blue-600 dark:text-sky-500"
@@ -1,5 +1,7 @@
export const iconsMap: Record<string, string> = {
angular: '/images/icons/angular.svg',
'angular-rsbuild': '/images/icons/rsbuild.svg',
'angular-rspack': '/images/icons/rspack.svg',
cli: '/images/icons/nx.svg',
cypress: '/images/icons/cypress.svg',
detox: '/images/icons/react.svg',
@@ -32,6 +34,7 @@ export const iconsMap: Record<string, string> = {
'react-native': '/images/icons/react.svg',
remix: '/images/icons/remix.svg',
rollup: '/images/icons/rollup.svg',
rsbuild: '/images/icons/rsbuild.svg',
rspack: '/images/icons/rspack.svg',
storybook: '/images/icons/storybook.svg',
vite: '/images/icons/vite.svg',
+2 -1
View File
@@ -189,7 +189,7 @@
"esbuild": "0.25.0",
"eslint": "8.57.0",
"eslint-config-next": "14.2.16",
"eslint-config-prettier": "9.1.0",
"eslint-config-prettier": "^10.0.0",
"eslint-plugin-cypress": "2.14.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jsx-a11y": "6.10.1",
@@ -262,6 +262,7 @@
"parse-markdown-links": "^1.0.4",
"parse5": "4.0.0",
"picocolors": "^1.1.0",
"picomatch": "4.0.2",
"piscina": "^4.4.0",
"postcss": "8.4.38",
"postcss-import": "~14.1.0",
+12
View File
@@ -1341,6 +1341,10 @@
"version": "~19.0.0",
"alwaysAddToPackageJson": false
},
"@angular/google-maps": {
"version": "~19.0.0",
"alwaysAddToPackageJson": false
},
"ng-packagr": {
"version": "~19.0.0",
"alwaysAddToPackageJson": false
@@ -1537,6 +1541,10 @@
"version": "~19.1.0",
"alwaysAddToPackageJson": false
},
"@angular/google-maps": {
"version": "~19.1.0",
"alwaysAddToPackageJson": false
},
"ng-packagr": {
"version": "~19.1.0",
"alwaysAddToPackageJson": false
@@ -1598,6 +1606,10 @@
"version": "~19.2.1",
"alwaysAddToPackageJson": false
},
"@angular/google-maps": {
"version": "~19.2.1",
"alwaysAddToPackageJson": false
},
"ng-packagr": {
"version": "~19.2.0",
"alwaysAddToPackageJson": false
+1 -1
View File
@@ -15,7 +15,7 @@
"@typescript-eslint/",
"picocolors",
"ignore",
"minimatch",
"picomatch",
"rxjs-for-await",
"webpack-merge",
"ts-node",
+1 -1
View File
@@ -52,8 +52,8 @@
"@typescript-eslint/type-utils": "^8.0.0",
"enquirer": "~2.3.6",
"picocolors": "^1.1.0",
"picomatch": "4.0.2",
"magic-string": "~0.30.2",
"minimatch": "9.0.3",
"semver": "^7.5.3",
"tslib": "^2.3.0",
"webpack-merge": "^5.8.0",
@@ -2,7 +2,6 @@ import type { ExecutorContext } from '@nx/devkit';
import { joinPathFragments, parseTargetString, runExecutor } from '@nx/devkit';
import {
calculateProjectBuildableDependencies,
checkDependentProjectsHaveBeenBuilt,
createTmpTsConfig,
} from '@nx/js/src/utils/buildable-libs-utils';
import type { DelegateBuildExecutorSchema } from './schema';
@@ -27,17 +26,6 @@ export async function* delegateBuildExecutor(
dependencies
);
if (
!checkDependentProjectsHaveBeenBuilt(
context.root,
context.projectName,
context.targetName,
dependencies
)
) {
return { success: false };
}
const { buildTarget, ...targetOptions } = options;
const delegateTarget = parseTargetString(buildTarget, context);
@@ -2,7 +2,6 @@ import type { ExecutorContext } from '@nx/devkit';
import { eachValueFrom } from '@nx/devkit/src/utils/rxjs-for-await';
import {
calculateProjectBuildableDependencies,
checkDependentProjectsHaveBeenBuilt,
createTmpTsConfig,
type DependentBuildableProjectNode,
} from '@nx/js/src/utils/buildable-libs-utils';
@@ -65,25 +64,14 @@ export function createLibraryExecutor(
);
}
const { target, dependencies, topLevelDependencies } =
calculateProjectBuildableDependencies(
context.taskGraph,
context.projectGraph,
context.root,
context.projectName,
context.targetName,
context.configurationName
);
if (
!checkDependentProjectsHaveBeenBuilt(
context.root,
context.projectName,
context.targetName,
dependencies
)
) {
return Promise.resolve({ success: false });
}
const { dependencies } = calculateProjectBuildableDependencies(
context.taskGraph,
context.projectGraph,
context.root,
context.projectName,
context.targetName,
context.configurationName
);
if (options.watch) {
return yield* eachValueFrom(
@@ -484,4 +484,164 @@ describe('convert-to-rspack', () => {
"
`);
});
it('should configure ts-node in the tsconfig.json file', async () => {
const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app', {
root: 'apps/app',
sourceRoot: 'apps/app/src',
projectType: 'application',
targets: {
build: {
executor: '@angular-devkit/build-angular:browser',
options: {
outputPath: 'dist/apps/app',
index: 'apps/app/src/index.html',
main: 'apps/app/src/main.ts',
polyfills: ['tslib'], // zone.js is not in nx repo's node_modules so simulating it with a package that is
tsConfig: 'apps/app/tsconfig.app.json',
assets: [
'apps/app/src/favicon.ico',
'apps/app/src/assets',
{ input: 'apps/app/public', glob: '**/*' },
],
styles: ['apps/app/src/styles.scss'],
scripts: [],
},
},
},
});
writeJson(tree, 'apps/app/tsconfig.json', {});
updateJson(tree, 'package.json', (json) => {
json.scripts ??= {};
json.scripts.build = 'nx build';
return json;
});
await convertToRspack(tree, { project: 'app', skipFormat: true });
expect(tree.read('apps/app/tsconfig.json', 'utf-8')).toMatchInlineSnapshot(`
"{
"ts-node": {
"compilerOptions": {
"module": "CommonJS",
"moduleResolution": "Node10"
}
}
}
"
`);
});
it('should configure ts-node in the tsconfig.json file to unset "customConditions" when it is defined in the root tsconfig.json file', async () => {
const tree = createTreeWithEmptyWorkspace();
updateJson(tree, 'tsconfig.base.json', (json) => {
json.compilerOptions ??= {};
json.compilerOptions.customConditions = ['development'];
return json;
});
addProjectConfiguration(tree, 'app', {
root: 'apps/app',
sourceRoot: 'apps/app/src',
projectType: 'application',
targets: {
build: {
executor: '@angular-devkit/build-angular:browser',
options: {
outputPath: 'dist/apps/app',
index: 'apps/app/src/index.html',
main: 'apps/app/src/main.ts',
polyfills: ['tslib'], // zone.js is not in nx repo's node_modules so simulating it with a package that is
tsConfig: 'apps/app/tsconfig.app.json',
assets: [
'apps/app/src/favicon.ico',
'apps/app/src/assets',
{ input: 'apps/app/public', glob: '**/*' },
],
styles: ['apps/app/src/styles.scss'],
scripts: [],
},
},
},
});
writeJson(tree, 'apps/app/tsconfig.json', {});
updateJson(tree, 'package.json', (json) => {
json.scripts ??= {};
json.scripts.build = 'nx build';
return json;
});
await convertToRspack(tree, { project: 'app', skipFormat: true });
expect(tree.read('apps/app/tsconfig.json', 'utf-8')).toMatchInlineSnapshot(`
"{
"ts-node": {
"compilerOptions": {
"module": "CommonJS",
"moduleResolution": "Node10",
"customConditions": null
}
}
}
"
`);
});
it('should configure ts-node in the tsconfig.json file to unset "customConditions" when it is defined in the project tsconfig.json file', async () => {
const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app', {
root: 'apps/app',
sourceRoot: 'apps/app/src',
projectType: 'application',
targets: {
build: {
executor: '@angular-devkit/build-angular:browser',
options: {
outputPath: 'dist/apps/app',
index: 'apps/app/src/index.html',
main: 'apps/app/src/main.ts',
polyfills: ['tslib'], // zone.js is not in nx repo's node_modules so simulating it with a package that is
tsConfig: 'apps/app/tsconfig.app.json',
assets: [
'apps/app/src/favicon.ico',
'apps/app/src/assets',
{ input: 'apps/app/public', glob: '**/*' },
],
styles: ['apps/app/src/styles.scss'],
scripts: [],
},
},
},
});
writeJson(tree, 'apps/app/tsconfig.json', {
compilerOptions: {
customConditions: ['development'],
},
});
updateJson(tree, 'package.json', (json) => {
json.scripts ??= {};
json.scripts.build = 'nx build';
return json;
});
await convertToRspack(tree, { project: 'app', skipFormat: true });
expect(tree.read('apps/app/tsconfig.json', 'utf-8')).toMatchInlineSnapshot(`
"{
"compilerOptions": {
"customConditions": [
"development"
]
},
"ts-node": {
"compilerOptions": {
"module": "CommonJS",
"moduleResolution": "Node10",
"customConditions": null
}
}
}
"
`);
});
});
@@ -1,12 +1,26 @@
import { joinPathFragments, readJson, Tree, writeJson } from '@nx/devkit';
import { getRootTsConfigFileName } from '@nx/js';
export function updateTsconfig(tree: Tree, projectRoot: string) {
const tsconfigPath = joinPathFragments(projectRoot, 'tsconfig.json');
const tsconfig = readJson(tree, tsconfigPath);
tsconfig['ts-node'] = {
compilerOptions: {
module: 'CommonJS',
},
};
tsconfig['ts-node'] ??= {};
tsconfig['ts-node'].compilerOptions ??= {};
tsconfig['ts-node'].compilerOptions.module = 'CommonJS';
tsconfig['ts-node'].compilerOptions.moduleResolution = 'Node10';
if (tsconfig.compilerOptions?.customConditions) {
tsconfig['ts-node'].compilerOptions.customConditions = null;
} else {
const rootTsconfigFile = getRootTsConfigFileName(tree);
if (rootTsconfigFile) {
const rootTsconfigJson = readJson(tree, rootTsconfigFile);
if (rootTsconfigJson.compilerOptions?.customConditions) {
tsconfig['ts-node'].compilerOptions.customConditions = null;
}
}
}
writeJson(tree, tsconfigPath, tsconfig);
}
@@ -17,7 +17,7 @@ import {
import { getProjectEntryPoints } from '../utils/storybook-ast/entry-point';
import { getModuleFilePaths } from '../utils/storybook-ast/module-info';
import type { StoriesGeneratorOptions } from './schema';
import { minimatch } from 'minimatch';
import picomatch = require('picomatch');
import { nxVersion } from '../../utils/versions';
export async function angularStoriesGenerator(
@@ -37,13 +37,12 @@ export async function angularStoriesGenerator(
const componentInfos = componentsInfo.filter(
(f) =>
!options.ignorePaths?.some((pattern) => {
const shouldIgnorePath = minimatch(
const shouldIgnorePath = picomatch(pattern)(
joinPathFragments(
f.moduleFolderPath,
f.path,
`${f.componentFileName}.ts`
),
pattern
)
);
return shouldIgnorePath;
})
@@ -1,12 +1,12 @@
import { type Tree, formatFiles, visitNotIgnoredFiles } from '@nx/devkit';
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
import { minimatch } from 'minimatch';
import picomatch = require('picomatch');
import { tsquery } from '@phenomnomnominal/tsquery';
export default async function (tree: Tree) {
visitNotIgnoredFiles(tree, '', (path) => {
const webpackConfigGlob = '**/webpack*.config*.{js,ts,mjs,cjs}';
const result = minimatch(path, webpackConfigGlob);
const result = picomatch(webpackConfigGlob)(path);
if (!result) {
return;
}
@@ -35,6 +35,7 @@ interface BaseArguments extends CreateWorkspaceOptions {
linter?: 'none' | 'eslint';
formatter?: 'none' | 'prettier';
workspaces?: boolean;
useProjectJson?: boolean;
}
interface NoneArguments extends BaseArguments {
@@ -175,6 +176,10 @@ export const commandsObject: yargs.Argv<Arguments> = yargs
type: 'boolean',
default: true,
})
.option('useProjectJson', {
describe: chalk.dim`Use a 'project.json' file for the Nx configuration instead of a 'package.json' file. This defaults to 'true' when '--no-workspaces' is used. Otherwise, it defaults to 'false'.`,
type: 'boolean',
})
.option('formatter', {
describe: chalk.dim`Code formatter to use.`,
type: 'string',
@@ -288,6 +293,7 @@ async function normalizeArgsMiddleware(
});
argv.workspaces ??= true;
argv.useProjectJson ??= !argv.workspaces;
try {
argv.name = await determineFolder(argv);
@@ -167,6 +167,9 @@ function updatePackageJson(
esbuildOptions,
} = mergedTarget.options;
// the file must exist in the TS solution setup
const tsconfigBase = readJson(tree, 'tsconfig.base.json');
// can't use the declarationRootDir as rootDir because it only affects the typings,
// not the runtime entry point
packageJson = getUpdatedPackageJsonContent(packageJson, {
@@ -186,6 +189,10 @@ function updatePackageJson(
outputFileExtensionForEsm: getOutExtension('esm', {
userDefinedBuildOptions: esbuildOptions,
}),
skipDevelopmentExports:
!tsconfigBase.compilerOptions?.customConditions?.includes(
'development'
),
});
if (declarationRootDir !== dirname(main)) {
-1
View File
@@ -36,7 +36,6 @@
"typescript",
// Installed to workspace by plugins
"@typescript-eslint/parser",
"eslint-config-prettier",
"@angular-eslint/eslint-plugin",
"angular-eslint",
"typescript-eslint",
+1 -1
View File
@@ -26,7 +26,7 @@
"homepage": "https://nx.dev",
"peerDependencies": {
"@typescript-eslint/parser": "^6.13.2 || ^7.0.0 || ^8.0.0",
"eslint-config-prettier": "^9.0.0"
"eslint-config-prettier": "^10.0.0"
},
"peerDependenciesMeta": {
"eslint-config-prettier": {
+8
View File
@@ -182,6 +182,14 @@
"alwaysAddToPackageJson": false
}
}
},
"20.7.0": {
"version": "20.7.0-beta.4",
"packages": {
"eslint-config-prettier": {
"version": "^10.0.0"
}
}
}
}
}
@@ -69,7 +69,7 @@ describe('convert-to-flat-config generator', () => {
"@nx/eslint": "0.0.1",
"@nx/eslint-plugin": "0.0.1",
"eslint": "^9.8.0",
"eslint-config-prettier": "^9.0.0",
"eslint-config-prettier": "^10.0.0",
"typescript-eslint": "^8.19.0"
}
}
@@ -682,7 +682,7 @@ describe('convert-to-flat-config generator', () => {
"@nx/eslint": "0.0.1",
"@nx/eslint-plugin": "0.0.1",
"eslint": "^9.8.0",
"eslint-config-prettier": "^9.0.0",
"eslint-config-prettier": "^10.0.0",
"typescript-eslint": "^8.19.0"
}
}
+1 -1
View File
@@ -2,7 +2,7 @@ export const nxVersion = require('../../package.json').version;
export const eslintVersion = '~8.57.0';
export const eslintrcVersion = '^2.1.1';
export const eslintConfigPrettierVersion = '^9.0.0';
export const eslintConfigPrettierVersion = '^10.0.0';
export const typescriptESLintVersion = '^7.16.0';
export const jsoncEslintParserVersion = '^2.1.0';
@@ -467,6 +467,7 @@ describe('lib', () => {
compilerOptions: {
composite: true,
declaration: true,
customConditions: ['development'],
},
});
writeJson(appTree, 'tsconfig.json', {
@@ -630,13 +631,14 @@ describe('lib', () => {
{
"exports": {
".": {
"default": "./src/index.ts",
"import": "./src/index.ts",
"default": "./dist/index.cjs.js",
"development": "./src/index.ts",
"import": "./dist/index.esm.js",
"types": "./dist/index.esm.d.ts",
},
"./package.json": "./package.json",
},
"main": "./src/index.ts",
"main": "./dist/index.cjs.js",
"module": "./dist/index.esm.js",
"name": "@proj/my-lib",
"peerDependencies": {
@@ -649,6 +651,25 @@ describe('lib', () => {
`);
});
it('should not set the "development" condition in exports when it does not exist in tsconfig.base.json', async () => {
updateJson(appTree, 'tsconfig.base.json', (json) => {
delete json.compilerOptions.customConditions;
return json;
});
await expoLibraryGenerator(appTree, {
...defaultSchema,
buildable: true,
strict: false,
useProjectJson: false,
skipFormat: true,
});
expect(
readJson(appTree, 'my-lib/package.json').exports['.']
).not.toHaveProperty('development');
});
it('should set "nx.name" in package.json when the user provides a name that is different than the package name', async () => {
await expoLibraryGenerator(appTree, {
...defaultSchema,
@@ -295,6 +295,15 @@ function createFiles(host: Tree, options: NormalizedSchema) {
function determineEntryFields(
options: NormalizedSchema
): Pick<PackageJson, 'main' | 'types' | 'exports'> {
if (
options.buildable ||
options.publishable ||
!options.isUsingTsSolutionConfig
) {
// For buildable libraries, the entries are configured by the bundler (i.e. Rollup).
return undefined;
}
return {
main: options.js ? './src/index.js' : './src/index.ts',
types: options.js ? './src/index.js' : './src/index.ts',
@@ -23,7 +23,12 @@ export async function jestExecutor(
): Promise<{ success: boolean }> {
// Jest registers ts-node with module CJS https://github.com/SimenB/jest/blob/v29.6.4/packages/jest-config/src/readConfigFileAndSetRootDir.ts#L117-L119
// We want to support of ESM via 'module':'nodenext', we need to override the resolution until Jest supports it.
process.env.TS_NODE_COMPILER_OPTIONS ??= '{"moduleResolution":"node10"}';
const existingValue = process.env['TS_NODE_COMPILER_OPTIONS'];
process.env['TS_NODE_COMPILER_OPTIONS'] = JSON.stringify({
...(existingValue ? JSON.parse(existingValue) : {}),
moduleResolution: 'Node10',
customConditions: null,
});
const config = await parseJestConfig(options, context);
@@ -1 +1,7 @@
<% if(setupFile === 'react-native') { %>import '@testing-library/jest-native/extend-expect';<% } %>
<% if(setupFile === 'react-native') { %>import '@testing-library/jest-native/extend-expect';<% } %>
<%_ if(setupFile === 'react-router') { _%>
import { TextEncoder, TextDecoder as NodeTextDecoder } from "util";
global.TextEncoder = TextEncoder;
global.TextDecoder = NodeTextDecoder as typeof TextDecoder; // necessary because there is a mismatch between ts type and node type
<%_ } _%>
+6 -1
View File
@@ -6,7 +6,12 @@ export interface JestProjectSchema {
* @deprecated use setupFile instead
*/
skipSetupFile?: boolean;
setupFile?: 'angular' | 'web-components' | 'react-native' | 'none';
setupFile?:
| 'angular'
| 'web-components'
| 'react-native'
| 'react-router'
| 'none';
skipSerializers?: boolean;
testEnvironment?: 'node' | 'jsdom' | 'none';
/**
+11 -11
View File
@@ -98,7 +98,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
"options": {
"cwd": "proj",
"env": {
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
},
},
"outputs": [
@@ -179,7 +179,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
"options": {
"cwd": "proj",
"env": {
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
},
},
"outputs": [
@@ -249,7 +249,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
"options": {
"cwd": "proj",
"env": {
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
},
},
"outputs": [
@@ -318,7 +318,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
"options": {
"cwd": "proj",
"env": {
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
},
},
"outputs": [
@@ -398,7 +398,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
"options": {
"cwd": "proj",
"env": {
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
},
},
"outputs": [
@@ -482,7 +482,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
"options": {
"cwd": "proj",
"env": {
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
},
},
"outputs": [
@@ -552,7 +552,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
"options": {
"cwd": "proj",
"env": {
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
},
},
"outputs": [
@@ -632,7 +632,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
"options": {
"cwd": "proj",
"env": {
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
},
},
"outputs": [
@@ -702,7 +702,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
"options": {
"cwd": "proj",
"env": {
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
},
},
"outputs": [
@@ -782,7 +782,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
"options": {
"cwd": "proj",
"env": {
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
},
},
"outputs": [
@@ -852,7 +852,7 @@ describe.each([true, false])('@nx/jest/plugin', (disableJestRuntime) => {
"options": {
"cwd": "proj",
"env": {
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10"}",
"TS_NODE_COMPILER_OPTIONS": "{"moduleResolution":"node10","customConditions":null}",
},
},
"outputs": [
+11 -5
View File
@@ -253,13 +253,21 @@ async function buildJestTargets(
const targets: Record<string, TargetConfiguration> = {};
const namedInputs = getNamedInputs(projectRoot, context);
const existingTsNodeCompilerOptions = process.env['TS_NODE_COMPILER_OPTIONS'];
const tsNodeCompilerOptions = JSON.stringify({
...(existingTsNodeCompilerOptions
? JSON.parse(existingTsNodeCompilerOptions)
: {}),
moduleResolution: 'node10',
customConditions: null,
});
const target: TargetConfiguration = (targets[options.targetName] = {
command: 'jest',
options: {
cwd: projectRoot,
// Jest registers ts-node with module CJS https://github.com/SimenB/jest/blob/v29.6.4/packages/jest-config/src/readConfigFileAndSetRootDir.ts#L117-L119
// We want to support of ESM via 'module':'nodenext', we need to override the resolution until Jest supports it.
env: { TS_NODE_COMPILER_OPTIONS: '{"moduleResolution":"node10"}' },
env: { TS_NODE_COMPILER_OPTIONS: tsNodeCompilerOptions },
},
metadata: {
technologies: ['jest'],
@@ -341,7 +349,7 @@ async function buildJestTargets(
outputs,
options: {
cwd: projectRoot,
env: { TS_NODE_COMPILER_OPTIONS: '{"moduleResolution":"node10"}' },
env: { TS_NODE_COMPILER_OPTIONS: tsNodeCompilerOptions },
},
metadata: {
technologies: ['jest'],
@@ -481,9 +489,7 @@ async function buildJestTargets(
outputs,
options: {
cwd: projectRoot,
env: {
TS_NODE_COMPILER_OPTIONS: '{"moduleResolution":"node10"}',
},
env: { TS_NODE_COMPILER_OPTIONS: tsNodeCompilerOptions },
},
metadata: {
technologies: ['jest'],
-2
View File
@@ -60,8 +60,6 @@
"semver": "^7.5.3",
"source-map-support": "0.5.19",
"tinyglobby": "^0.2.12",
"ts-node": "10.9.1",
"tsconfig-paths": "^4.1.2",
"tslib": "^2.3.0"
},
"peerDependencies": {
@@ -17,6 +17,7 @@
"noUnusedLocals": true,
"skipLibCheck": true,
"strict": true,
"target": "es2022"
"target": "es2022",
"customConditions": ["development"]
}
}
+2 -1
View File
@@ -208,7 +208,8 @@ describe('js init generator', () => {
"noUnusedLocals": true,
"skipLibCheck": true,
"strict": true,
"target": "es2022"
"target": "es2022",
"customConditions": ["development"]
}
}
"
@@ -1817,6 +1817,7 @@ describe('lib', () => {
compilerOptions: {
composite: true,
declaration: true,
customConditions: ['development'],
},
});
writeJson(tree, 'tsconfig.json', {
@@ -1954,6 +1955,7 @@ describe('lib', () => {
"exports": {
".": {
"default": "./dist/index.js",
"development": "./src/index.ts",
"import": "./dist/index.js",
"types": "./dist/index.d.ts",
},
@@ -1987,6 +1989,7 @@ describe('lib', () => {
"exports": {
".": {
"default": "./dist/index.js",
"development": "./src/index.ts",
"import": "./dist/index.js",
"types": "./dist/index.d.ts",
},
@@ -2344,24 +2347,6 @@ describe('lib', () => {
]);
});
it('should add nx.addTypecheckTarget to tsconfig.json when using tsc to build to avoid duplicated typechecks', async () => {
await libraryGenerator(tree, {
...defaultOptions,
useProjectJson: false,
directory: 'my-ts-lib',
bundler: 'tsc',
unitTestRunner: 'none',
linter: 'none',
});
expect(readJson(tree, 'my-ts-lib/tsconfig.json').nx)
.toMatchInlineSnapshot(`
{
"addTypecheckTarget": false,
}
`);
});
it('should set "nx.name" in package.json when the user provides a name that is different than the package name and "useProjectJson" is "false"', async () => {
await libraryGenerator(tree, {
...defaultOptions,
@@ -2428,5 +2413,26 @@ describe('lib', () => {
expect(readJson(tree, 'my-lib/project.json').name).toBe('my-lib');
expect(readJson(tree, 'my-lib/package.json').nx).toBeUndefined();
});
it('should not set the "development" condition in exports when it does not exist in tsconfig.base.json', async () => {
updateJson(tree, 'tsconfig.base.json', (json) => {
delete json.compilerOptions.customConditions;
return json;
});
await libraryGenerator(tree, {
...defaultOptions,
directory: 'my-lib',
name: 'my-lib',
useProjectJson: true,
bundler: 'tsc',
addPlugin: true,
skipFormat: true,
});
expect(
readJson(tree, 'my-lib/package.json').exports['.']
).not.toHaveProperty('development');
});
});
});
+14 -12
View File
@@ -10,6 +10,7 @@ import {
names,
offsetFromRoot,
ProjectConfiguration,
readJson,
readNxJson,
readProjectConfiguration,
runTasksInSerial,
@@ -631,6 +632,9 @@ function createFiles(tree: Tree, options: NormalizedLibraryGeneratorOptions) {
options.isUsingTsSolutionConfig &&
!['none', 'rollup', 'vite'].includes(options.bundler)
) {
// the file must exist in the TS solution setup
const tsconfigBase = readJson(tree, 'tsconfig.base.json');
return getUpdatedPackageJsonContent(updatedPackageJson, {
main: join(options.projectRoot, 'src/index.ts'),
outputPath: joinPathFragments(options.projectRoot, 'dist'),
@@ -639,6 +643,10 @@ function createFiles(tree: Tree, options: NormalizedLibraryGeneratorOptions) {
generateExportsField: true,
packageJsonPath,
format: ['esm'],
skipDevelopmentExports:
!tsconfigBase.compilerOptions?.customConditions?.includes(
'development'
),
});
}
@@ -664,6 +672,8 @@ function createFiles(tree: Tree, options: NormalizedLibraryGeneratorOptions) {
options.isUsingTsSolutionConfig &&
!['none', 'rollup', 'vite'].includes(options.bundler)
) {
const tsconfigBase = readJson(tree, 'tsconfig.base.json');
packageJson = getUpdatedPackageJsonContent(packageJson, {
main: join(options.projectRoot, 'src/index.ts'),
outputPath: joinPathFragments(options.projectRoot, 'dist'),
@@ -672,6 +682,10 @@ function createFiles(tree: Tree, options: NormalizedLibraryGeneratorOptions) {
generateExportsField: true,
packageJsonPath,
format: ['esm'],
skipDevelopmentExports:
!tsconfigBase.compilerOptions?.customConditions?.includes(
'development'
),
});
}
@@ -1043,12 +1057,6 @@ function createProjectTsConfigs(
json.references.push({
path: './tsconfig.lib.json',
});
// If using `tsc` to build, then we do not want a typecheck target that duplicates the work, since both run `tsc`.
// This applies to `@nx/js/typescript` plugin only.
if (options.bundler === 'tsc') {
json['nx'] ??= {};
json['nx'].addTypecheckTarget = false;
}
return json;
});
} else {
@@ -1059,12 +1067,6 @@ function createProjectTsConfigs(
include: [],
references: [{ path: './tsconfig.lib.json' }],
};
// If using `tsc` to build, then we do not want a typecheck target that duplicates the work, since both run `tsc`.
// This applies to `@nx/js/typescript` plugin only.
if (options.bundler === 'tsc') {
tsconfig['nx'] ??= {};
tsconfig['nx'].addTypecheckTarget = false;
}
writeJson(
tree,
joinPathFragments(options.projectRoot, 'tsconfig.json'),
@@ -325,6 +325,11 @@ function updatePackageJson(
version: '0.0.1',
};
}
// the file must exist in the TS solution setup, which is the only case this
// function is called
const tsconfigBase = readJson(tree, 'tsconfig.base.json');
packageJson = getUpdatedPackageJsonContent(packageJson, {
main,
outputPath,
@@ -333,6 +338,8 @@ function updatePackageJson(
packageJsonPath,
rootDir,
format,
skipDevelopmentExports:
!tsconfigBase.compilerOptions?.customConditions?.includes('development'),
});
writeJson(tree, packageJsonPath, packageJson);
}
@@ -49,24 +49,28 @@ export function startLocalRegistry({
);
const registry = `http://${listenAddress}:${port}`;
const authToken = 'secretVerdaccioToken';
console.log(`Local registry started on ${registry}`);
process.env.npm_config_registry = registry;
execSync(
`npm config set //${listenAddress}:${port}/:_authToken "secretVerdaccioToken" --ws=false`,
`npm config set //${listenAddress}:${port}/:_authToken "${authToken}" --ws=false`,
{
windowsHide: false,
}
);
// bun
process.env.BUN_CONFIG_REGISTRY = registry;
process.env.BUN_CONFIG_TOKEN = authToken;
// yarnv1
process.env.YARN_REGISTRY = registry;
// yarnv2
process.env.YARN_NPM_REGISTRY_SERVER = registry;
process.env.YARN_UNSAFE_HTTP_WHITELIST = listenAddress;
console.log('Set npm and yarn config registry to ' + registry);
console.log('Set npm, bun, and yarn config registry to ' + registry);
resolve(() => {
childProcess.kill();
@@ -795,6 +795,307 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
`);
});
it('should normalize and add directories in `include` with the ts extensions', async () => {
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.json': JSON.stringify({
include: ['src'],
// set this to keep outputs smaller
compilerOptions: { outDir: 'dist' },
}),
'libs/my-lib/package.json': `{}`,
});
expect(await invokeCreateNodesOnMatchingFiles(context, {}))
.toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
"inputs": [
"{projectRoot}/package.json",
"{projectRoot}/tsconfig.json",
"{projectRoot}/src/**/*.ts",
"{projectRoot}/src/**/*.tsx",
"{projectRoot}/src/**/*.d.ts",
"{projectRoot}/src/**/*.cts",
"{projectRoot}/src/**/*.d.cts",
"{projectRoot}/src/**/*.mts",
"{projectRoot}/src/**/*.d.mts",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [
"{projectRoot}/dist",
],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
},
},
},
}
`);
});
it('should normalize and add directories in `include` with the ts and js extensions when `allowJs` is true', async () => {
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.json': JSON.stringify({
include: ['src'],
// set this to keep outputs smaller
compilerOptions: { outDir: 'dist', allowJs: true },
}),
'libs/my-lib/package.json': `{}`,
});
expect(await invokeCreateNodesOnMatchingFiles(context, {}))
.toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
"inputs": [
"{projectRoot}/package.json",
"{projectRoot}/tsconfig.json",
"{projectRoot}/src/**/*.ts",
"{projectRoot}/src/**/*.tsx",
"{projectRoot}/src/**/*.d.ts",
"{projectRoot}/src/**/*.js",
"{projectRoot}/src/**/*.jsx",
"{projectRoot}/src/**/*.cts",
"{projectRoot}/src/**/*.d.cts",
"{projectRoot}/src/**/*.cjs",
"{projectRoot}/src/**/*.mts",
"{projectRoot}/src/**/*.d.mts",
"{projectRoot}/src/**/*.mjs",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [
"{projectRoot}/dist",
],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
},
},
},
}
`);
});
it('should normalize and add directories in `include` with the ts and json extensions when `resolveJsonModule` is true', async () => {
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.json': JSON.stringify({
include: ['src'],
// set this to keep outputs smaller
compilerOptions: {
outDir: 'dist',
resolveJsonModule: true,
},
}),
'libs/my-lib/package.json': `{}`,
});
expect(await invokeCreateNodesOnMatchingFiles(context, {}))
.toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
"inputs": [
"{projectRoot}/package.json",
"{projectRoot}/tsconfig.json",
"{projectRoot}/src/**/*.ts",
"{projectRoot}/src/**/*.tsx",
"{projectRoot}/src/**/*.d.ts",
"{projectRoot}/src/**/*.cts",
"{projectRoot}/src/**/*.d.cts",
"{projectRoot}/src/**/*.mts",
"{projectRoot}/src/**/*.d.mts",
"{projectRoot}/src/**/*.json",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [
"{projectRoot}/dist",
],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
},
},
},
}
`);
});
it('should normalize and add directories in `include` with the ts, js and json extensions when `allowJs` and `resolveJsonModule` are true', async () => {
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.json': JSON.stringify({
include: ['src'],
// set this to keep outputs smaller
compilerOptions: {
outDir: 'dist',
allowJs: true,
resolveJsonModule: true,
},
}),
'libs/my-lib/package.json': `{}`,
});
expect(await invokeCreateNodesOnMatchingFiles(context, {}))
.toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
"inputs": [
"{projectRoot}/package.json",
"{projectRoot}/tsconfig.json",
"{projectRoot}/src/**/*.ts",
"{projectRoot}/src/**/*.tsx",
"{projectRoot}/src/**/*.d.ts",
"{projectRoot}/src/**/*.js",
"{projectRoot}/src/**/*.jsx",
"{projectRoot}/src/**/*.cts",
"{projectRoot}/src/**/*.d.cts",
"{projectRoot}/src/**/*.cjs",
"{projectRoot}/src/**/*.mts",
"{projectRoot}/src/**/*.d.mts",
"{projectRoot}/src/**/*.mjs",
"{projectRoot}/src/**/*.json",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [
"{projectRoot}/dist",
],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
},
},
},
}
`);
});
it('should add extended config files', async () => {
await applyFilesToTempFsAndContext(tempFs, context, {
'tsconfig.base.json': JSON.stringify({
@@ -1107,6 +1408,103 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
`);
});
it('should normalize and add directories in `include` from internal project references', async () => {
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.json': JSON.stringify({
files: [],
include: [],
references: [
{ path: './tsconfig.lib.json' },
{ path: './tsconfig.spec.json' },
],
// set this to keep outputs smaller
compilerOptions: { outDir: 'dist' },
}),
'libs/my-lib/tsconfig.lib.json': JSON.stringify({
include: ['src/**/*.ts'],
exclude: ['tests'], // should be ignored because another referenced internal project includes this same pattern
// set this to keep outputs smaller
compilerOptions: { outDir: 'dist' },
}),
'libs/my-lib/tsconfig.spec.json': JSON.stringify({
include: ['tests'], // directory pattern that should be normalized
references: [{ path: './tsconfig.lib.json' }],
compilerOptions: {
outDir: 'dist', // set this to keep outputs smaller
allowJs: true, // should result in including js extensions in the normalized include paths
},
}),
'libs/my-lib/package.json': `{}`,
});
expect(await invokeCreateNodesOnMatchingFiles(context, {}))
.toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
"inputs": [
"{projectRoot}/package.json",
"{projectRoot}/tsconfig.json",
"{projectRoot}/tsconfig.lib.json",
"{projectRoot}/tsconfig.spec.json",
"{projectRoot}/src/**/*.ts",
"{projectRoot}/tests/**/*.ts",
"{projectRoot}/tests/**/*.tsx",
"{projectRoot}/tests/**/*.d.ts",
"{projectRoot}/tests/**/*.js",
"{projectRoot}/tests/**/*.jsx",
"{projectRoot}/tests/**/*.cts",
"{projectRoot}/tests/**/*.d.cts",
"{projectRoot}/tests/**/*.cjs",
"{projectRoot}/tests/**/*.mts",
"{projectRoot}/tests/**/*.d.mts",
"{projectRoot}/tests/**/*.mjs",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [
"{projectRoot}/dist",
],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
},
},
},
}
`);
});
it('should only add exclude paths that are not part of other tsconfig files include paths', async () => {
// exact match
await applyFilesToTempFsAndContext(tempFs, context, {
@@ -2206,6 +2604,111 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
`);
});
it('should not create build target when the entry points point to source files', async () => {
// Sibling package.json
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.json': `{}`,
'libs/my-lib/tsconfig.lib.json': `{"compilerOptions": {"outDir": "dist"}}`,
'libs/my-lib/package.json': JSON.stringify({
exports: {
'.': {
default: './src/index.ts',
},
},
}),
});
expect(
await invokeCreateNodesOnMatchingFiles(context, {
// Reduce noise in build snapshots by disabling default typecheck target
typecheck: false,
build: true,
})
).toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {},
},
},
}
`);
});
it('should create build target when the entry points point to dist files', async () => {
// Sibling package.json
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.json': `{}`,
'libs/my-lib/tsconfig.lib.json': `{"compilerOptions": {"outDir": "dist"}}`,
'libs/my-lib/package.json': JSON.stringify({
exports: {
'.': {
// should ignore the fact that the development condition points to source
development: './src/index.ts',
types: './dist/index.d.ts',
default: './dist/index.js',
},
},
}),
});
expect(
await invokeCreateNodesOnMatchingFiles(context, {
// Reduce noise in build snapshots by disabling default typecheck target
typecheck: false,
build: true,
})
).toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Builds the project with \`tsc\`.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [
"{projectRoot}/dist",
],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
},
},
},
}
`);
});
it('should create a node with a build target when enabled, for a project level tsconfig.lib.json build file by default', async () => {
// Sibling package.json
await applyFilesToTempFsAndContext(tempFs, context, {
@@ -2740,6 +3243,320 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
`);
});
it('should normalize and add directories in `include` with the ts extensions', async () => {
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.lib.json': JSON.stringify({
compilerOptions: { outDir: 'dist' },
include: ['src'],
}),
'libs/my-lib/tsconfig.json': `{}`,
'libs/my-lib/package.json': `{"main": "dist/index.js"}`,
});
expect(
await invokeCreateNodesOnMatchingFiles(context, {
typecheck: false,
build: true,
})
).toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
"inputs": [
"{projectRoot}/package.json",
"{projectRoot}/tsconfig.lib.json",
"{projectRoot}/src/**/*.ts",
"{projectRoot}/src/**/*.tsx",
"{projectRoot}/src/**/*.d.ts",
"{projectRoot}/src/**/*.cts",
"{projectRoot}/src/**/*.d.cts",
"{projectRoot}/src/**/*.mts",
"{projectRoot}/src/**/*.d.mts",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Builds the project with \`tsc\`.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [
"{projectRoot}/dist",
],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
},
},
},
}
`);
});
it('should normalize and add directories in `include` with the ts and js extensions when `allowJs` is true', async () => {
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.lib.json': JSON.stringify({
compilerOptions: { outDir: 'dist', allowJs: true },
include: ['src'],
}),
'libs/my-lib/tsconfig.json': `{}`,
'libs/my-lib/package.json': `{"main": "dist/index.js"}`,
});
expect(
await invokeCreateNodesOnMatchingFiles(context, {
typecheck: false,
build: true,
})
).toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
"inputs": [
"{projectRoot}/package.json",
"{projectRoot}/tsconfig.lib.json",
"{projectRoot}/src/**/*.ts",
"{projectRoot}/src/**/*.tsx",
"{projectRoot}/src/**/*.d.ts",
"{projectRoot}/src/**/*.js",
"{projectRoot}/src/**/*.jsx",
"{projectRoot}/src/**/*.cts",
"{projectRoot}/src/**/*.d.cts",
"{projectRoot}/src/**/*.cjs",
"{projectRoot}/src/**/*.mts",
"{projectRoot}/src/**/*.d.mts",
"{projectRoot}/src/**/*.mjs",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Builds the project with \`tsc\`.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [
"{projectRoot}/dist",
],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
},
},
},
}
`);
});
it('should normalize and add directories in `include` with the ts and json extensions when `resolveJsonModule` is true', async () => {
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.lib.json': JSON.stringify({
compilerOptions: { outDir: 'dist', resolveJsonModule: true },
include: ['src'],
}),
'libs/my-lib/tsconfig.json': `{}`,
'libs/my-lib/package.json': `{"main": "dist/index.js"}`,
});
expect(
await invokeCreateNodesOnMatchingFiles(context, {
typecheck: false,
build: true,
})
).toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
"inputs": [
"{projectRoot}/package.json",
"{projectRoot}/tsconfig.lib.json",
"{projectRoot}/src/**/*.ts",
"{projectRoot}/src/**/*.tsx",
"{projectRoot}/src/**/*.d.ts",
"{projectRoot}/src/**/*.cts",
"{projectRoot}/src/**/*.d.cts",
"{projectRoot}/src/**/*.mts",
"{projectRoot}/src/**/*.d.mts",
"{projectRoot}/src/**/*.json",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Builds the project with \`tsc\`.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [
"{projectRoot}/dist",
],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
},
},
},
}
`);
});
it('should normalize and add directories in `include` with the ts, js and json extensions when `allowJs` and `resolveJsonModule` are true', async () => {
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.lib.json': JSON.stringify({
compilerOptions: {
outDir: 'dist',
allowJs: true,
resolveJsonModule: true,
},
include: ['src'],
}),
'libs/my-lib/tsconfig.json': `{}`,
'libs/my-lib/package.json': `{"main": "dist/index.js"}`,
});
expect(
await invokeCreateNodesOnMatchingFiles(context, {
typecheck: false,
build: true,
})
).toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
"inputs": [
"{projectRoot}/package.json",
"{projectRoot}/tsconfig.lib.json",
"{projectRoot}/src/**/*.ts",
"{projectRoot}/src/**/*.tsx",
"{projectRoot}/src/**/*.d.ts",
"{projectRoot}/src/**/*.js",
"{projectRoot}/src/**/*.jsx",
"{projectRoot}/src/**/*.cts",
"{projectRoot}/src/**/*.d.cts",
"{projectRoot}/src/**/*.cjs",
"{projectRoot}/src/**/*.mts",
"{projectRoot}/src/**/*.d.mts",
"{projectRoot}/src/**/*.mjs",
"{projectRoot}/src/**/*.json",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Builds the project with \`tsc\`.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [
"{projectRoot}/dist",
],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
},
},
},
}
`);
});
it('should be able to extended config files', async () => {
await applyFilesToTempFsAndContext(tempFs, context, {
'tsconfig.base.json': JSON.stringify({
@@ -2995,6 +3812,94 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
`);
});
it('should normalize and add directories in `include` from internal project references', async () => {
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.json': '{}',
'libs/my-lib/tsconfig.lib.json': JSON.stringify({
compilerOptions: { outDir: 'dist' },
include: ['src/**/*.ts'],
exclude: ['src/**/foo.ts'], // should be ignored because a referenced internal project includes this same pattern
references: [{ path: './tsconfig.other.json' }],
}),
'libs/my-lib/tsconfig.other.json': JSON.stringify({
include: ['other', 'src/**/foo.ts'],
compilerOptions: {
outDir: 'dist', // set this to keep outputs smaller
resolveJsonModule: true, // should result in including json extensions in the normalized include paths
},
}),
'libs/my-lib/package.json': `{"main": "dist/index.js"}`, // Should be defined so that the project is considered buildable
});
expect(
await invokeCreateNodesOnMatchingFiles(context, {
typecheck: false,
build: true,
})
).toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
"inputs": [
"{projectRoot}/package.json",
"{projectRoot}/tsconfig.lib.json",
"{projectRoot}/tsconfig.other.json",
"{projectRoot}/src/**/*.ts",
"{projectRoot}/other/**/*.ts",
"{projectRoot}/other/**/*.tsx",
"{projectRoot}/other/**/*.d.ts",
"{projectRoot}/other/**/*.cts",
"{projectRoot}/other/**/*.d.cts",
"{projectRoot}/other/**/*.mts",
"{projectRoot}/other/**/*.d.mts",
"{projectRoot}/other/**/*.json",
"{projectRoot}/src/**/foo.ts",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Builds the project with \`tsc\`.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [
"{projectRoot}/dist",
],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
},
},
},
}
`);
});
it('should only add exclude paths that are not part of other tsconfig files include paths', async () => {
// exact match
await applyFilesToTempFsAndContext(tempFs, context, {
+63 -8
View File
@@ -1,5 +1,4 @@
import {
CreateNodesContextV2,
createNodesFromFiles,
detectPackageManager,
getPackageManagerCommand,
@@ -11,18 +10,17 @@ import {
type CreateDependencies,
type CreateNodes,
type CreateNodesContext,
type CreateNodesContextV2,
type CreateNodesV2,
type NxJsonConfiguration,
type ProjectConfiguration,
type TargetConfiguration,
} from '@nx/devkit';
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
import picomatch = require('picomatch');
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
import {
basename,
dirname,
extname,
join,
normalize,
relative,
@@ -31,10 +29,11 @@ import {
} from 'node:path';
import * as posix from 'node:path/posix';
import { hashArray, hashFile, hashObject } from 'nx/src/hasher/file-hasher';
import picomatch = require('picomatch');
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { getLockFileName } from 'nx/src/plugins/js/lock-file/lock-file';
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
import type { ParsedCommandLine, System } from 'typescript';
import type { Extension, ParsedCommandLine, System } from 'typescript';
import {
addBuildAndWatchDepsTargets,
isValidPackageJsonBuildConfig,
@@ -83,6 +82,7 @@ type TsconfigCacheData = {
extendedFilesHash: string;
};
let ts: typeof import('typescript');
const pmc = getPackageManagerCommand();
let tsConfigCache: Record<string, TsconfigCacheData>;
@@ -320,6 +320,9 @@ async function getConfigFileHash(
lockFileHash,
optionsHash,
...(packageJson ? [hashObject(packageJson)] : []),
// change this to bust the cache when making changes that would yield
// different results for the same hash
hashObject({ bust: 1 }),
]);
}
@@ -534,12 +537,65 @@ function getInputs(
...Object.entries(internalProjectReferences),
];
const absoluteProjectRoot = join(workspaceRoot, projectRoot);
if (!ts) {
ts = require('typescript');
}
// https://github.com/microsoft/TypeScript/blob/19b777260b26aac5707b1efd34202054164d4a9d/src/compiler/utilities.ts#L9869
const supportedTSExtensions: readonly Extension[] = [
ts.Extension.Ts,
ts.Extension.Tsx,
ts.Extension.Dts,
ts.Extension.Cts,
ts.Extension.Dcts,
ts.Extension.Mts,
ts.Extension.Dmts,
];
// https://github.com/microsoft/TypeScript/blob/19b777260b26aac5707b1efd34202054164d4a9d/src/compiler/utilities.ts#L9878
const allSupportedExtensions: readonly Extension[] = [
ts.Extension.Ts,
ts.Extension.Tsx,
ts.Extension.Dts,
ts.Extension.Js,
ts.Extension.Jsx,
ts.Extension.Cts,
ts.Extension.Dcts,
ts.Extension.Cjs,
ts.Extension.Mts,
ts.Extension.Dmts,
ts.Extension.Mjs,
];
const normalizeInput = (
input: string,
config: ParsedTsconfigData
): string[] => {
const extensions = config.options.allowJs
? [...allSupportedExtensions]
: [...supportedTSExtensions];
if (config.options.resolveJsonModule) {
extensions.push(ts.Extension.Json);
}
const segments = input.split('/');
// An "includes" path "foo" is implicitly a glob "foo/**/*" if its last
// segment has no extension, and does not contain any glob characters
// itself.
// https://github.com/microsoft/TypeScript/blob/19b777260b26aac5707b1efd34202054164d4a9d/src/compiler/utilities.ts#L9577-L9585
if (!/[.*?]/.test(segments.at(-1))) {
return extensions.map((ext) => `${segments.join('/')}/**/*${ext}`);
}
return [input];
};
projectTsConfigFiles.forEach(([configPath, config]) => {
configFiles.add(configPath);
const offset = relative(absoluteProjectRoot, dirname(configPath));
(config.raw?.include ?? []).forEach((p: string) =>
includePaths.add(join(offset, p))
);
(config.raw?.include ?? []).forEach((p: string) => {
const normalized = normalizeInput(join(offset, p), config);
normalized.forEach((input) => includePaths.add(input));
});
if (config.raw?.exclude) {
/**
@@ -1071,7 +1127,6 @@ function getExtendedFilesHash(
return hashes.join('|');
}
let ts: typeof import('typescript');
function readTsConfig(
tsConfigPath: string,
workspaceRoot: string
+2 -2
View File
@@ -95,8 +95,8 @@ export function isValidPackageJsonBuildConfig(
return isPathSourceFile(value);
} else if (typeof value === 'object') {
return Object.entries(value).some(([currentKey, subValue]) => {
// Skip types field
if (currentKey === 'types') {
// Skip types and development conditions
if (currentKey === 'types' || currentKey === 'development') {
return false;
}
if (typeof subValue === 'string') {

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