Compare commits

...

37 Commits

Author SHA1 Message Date
Leosvel Pérez Espinosa 303d124655 fix(angular): bump ngrx version to 18.0.2 (#27506)
Bump the NgRx package versions to ensure the `@ngrx/signals` package is
migrated correctly. That package version was out of sync with the rest
of the NgRx packages. Starting on v18.0.2, the package version is in
sync with the rest.

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

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

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

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

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

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

Fixes #

(cherry picked from commit 54f70c7587)
2024-08-19 10:20:25 -04:00
Tine Kondo fb6ddb7b3d fix(nx-dev): fix use key instead of env to reference cache key in… (#26644)
… `launch-templates`

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

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

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

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

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

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

Fixes #

(cherry picked from commit b91d78809e)
2024-08-19 10:20:20 -04:00
mathis 40307dcd2e chore(core): update axios to ^1.7.4 (#27420)
Closes CVE-2024-39338

## Related Issue(s)

Fixes #27419

(cherry picked from commit 8295275dc6)
2024-08-19 10:20:17 -04:00
Kamenskih Dmitriy eb87866dbb fix(node): build-esbuild-options.ts browser user define envs by config (#27480)
## Current Behavior
When you use esbuild in 'browser' mode you have to provide all 'define'
attributes as environment variables and only with "NX_PUBLIC_" prefix

## Expected Behavior
Esbuild configuration has to work as it is described in docs. And prefix
feature has to work as an additional functionality

## Related Issue(s)
This issue will be fixed: https://github.com/nrwl/nx/issues/19106

Co-authored-by: Dmitrii Kamenskikh <dkamenskikh@devexperts.com>
(cherry picked from commit 4941be6197)
2024-08-19 10:20:16 -04:00
Wei Liang d2ee81b2e8 feat(core): add shutdown lifecycle hook to node executor (#27354)
<!-- 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 -->
When the application are received a shutdown signal, the application
doesn't execute before shutdown functions and directly shutdown whole
application. The situation cannot execute before shutdown functions like
[NestJS Lifecycle
Events](https://docs.nestjs.com/fundamentals/lifecycle-events) and
custom shutdown hooks.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
The application can run shutdown hooks like below output:

 NX   Successfully ran target build for project nest-test (5s)

Debugger listening on
ws://localhost:9229/e4bd44c0-9a6a-468a-8b46-b6fef1cef1c7
For help, see: https://nodejs.org/en/docs/inspector

```
 NX   Successfully ran target build for project nest-test (4s)

Debugger listening on ws://localhost:9229/75c8449b-43a4-4d8b-88c0-231761d7248c
For help, see: https://nodejs.org/en/docs/inspector

To exit the process with SIGINT, press Ctrl+C
[Nest] 393107  - 08/09/2024, 6:31:26 PM     LOG [NestFactory] Starting Nest application...
[Nest] 393107  - 08/09/2024, 6:31:26 PM     LOG [InstanceLoader] AppModule dependencies initialized +10ms
[Nest] 393107  - 08/09/2024, 6:31:26 PM     LOG [RoutesResolver] AppController {/api}: +7ms
[Nest] 393107  - 08/09/2024, 6:31:26 PM     LOG [RouterExplorer] Mapped {/api, GET} route +3ms
[Nest] 393107  - 08/09/2024, 6:31:26 PM     LOG [NestApplication] Nest application successfully started +2ms
[Nest] 393107  - 08/09/2024, 6:31:26 PM     LOG 🚀 Application is running on: http://localhost:3000/api
[Nest] 393107  - 08/09/2024, 6:31:29 PM     LOG onModuleDestroy
onModuleDestroy: 5.001s
[Nest] 393107  - 08/09/2024, 6:31:34 PM     LOG beforeApplicationShutdown SIGINT
beforeApplicationShutdown: 5.004s
[Nest] 393107  - 08/09/2024, 6:31:39 PM     LOG onApplicationShutdown SIGINT
onApplicationShutdown: 5.005s

 NX  Process exited with code 130, waiting for changes to restart...
```

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

Fixes #9237 and #18037

---------

Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
(cherry picked from commit b5a93364c5)
2024-08-19 10:20:14 -04:00
Benjamin Cabanes 25e4bbef74 chore(nx-dev): change help section titles for clarity (#27465)
Renamed "Sales" to "Talk to Sales" and "Engineers" to "Learn more about Nx Cloud".

(cherry picked from commit 958f188bd0)
2024-08-19 10:20:12 -04:00
Benjamin Cabanes 974e022020 feat(nx-dev): add Monorepo World banner to hero (#27482)
Integrate a new Monorepo World banner with animations in the hero section using framer-motion. Updated the UI icons library to include Monorepo World icon and relevant SVG file.

(cherry picked from commit 289b23bfdf)
2024-08-19 10:20:11 -04:00
Isaac Mann e4d1a81e0b docs(core): link blog to parallelism reference (#27483)
Link the parallelism blog post to the actual reference document.
Currently the blog post shows up in a search for `parallelism` but the
actual reference does not. This link can help while we work on the
search.

(cherry picked from commit 15bc7ac69b)
2024-08-19 10:20:08 -04:00
Jack Hsu 4d478a5b4e docs(core): add scroll_25, scroll_50, scroll_75, and scroll_90 events to track engagement (#27461)
This PR adds events to track engagement in our docs. Since we use a
scrollable `<div>` in our docs, the normal `scroll` events in GA do not
work.

A new `<ScrollableContent>` component is added that will do two things:
- Send `scroll_25`, `scroll_50`, `scroll_75`, and `scroll_90` events
whenever the user scrolls to 25%, 50%, 75%, of 90% of the content
- Optionally reset scroll top to zero whenever router changes (existing
behavior)

All of the places where we have content in a scrollable `<div>` is
replaced with `<ScrollableContent>`.

Note: 90% means user has reached the bottom, since it's not usually
possible to get to 100%.

(cherry picked from commit 9dca7c7025)
2024-08-19 10:20:05 -04:00
Leosvel Pérez Espinosa 7d59927ed4 fix(linter): update the @nx/dependency-checks rule to read the package.json content from the rule context (#27476)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

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

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

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

The `@nx/dependency-checks` rule sometimes doesn't apply all identified
fixes and wrongly succeeds. ESLint performs multiple passes to try to
apply fixes, and in each pass the rule receives the updated source code
with the previously applied fixes. This allows merging different fixes,
but the `@nx/dependency-checks` rule always reads the `package.json`
file from the filesystem, caches it globally, and mutates it. It never
uses the updated source code provided in the rule context.

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

The `@nx/dependency-checks` rule should apply all identified fixes [as
long as they don't
conflict](https://eslint.org/docs/latest/extend/custom-rules#conflicting-fixes).

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

Fixes #27412

(cherry picked from commit b699207070)
2024-08-19 10:20:04 -04:00
Isaac Mann 81439245c8 docs(core): plugin atomizer set up (#27478)
Update plugin overview pages to describe atomizer set up

(cherry picked from commit d378f2c449)
2024-08-19 10:20:03 -04:00
Isaac Mann 3d0bdf023f docs(core): fix installation tutorial link (#27479)
Fix a link on the installation page

(cherry picked from commit c28148b70b)
2024-08-19 10:20:02 -04:00
Leosvel Pérez Espinosa 791a8ef7e1 fix(js): handle arbitrary nested ts path mappings when re-mapping them to the outputs (#27429)
<!-- 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 -->

When re-mapping a TS path mapping like `"@foo/lib1/plugins/some-file":
["packages/lib1/src/plugins/some-file.ts"]`, it results in:

```json
"@foo/lib1/plugins/some-file": [
  "dist/packages/lib1/plugins/some-file",
  "dist/packages/lib1/src/plugins/some-file.ts"
]
```

The first path is wrong because it's missing the `src` directory, and
the second one has a file extension that's not in the output.

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

When re-mapping a TS path mapping like `"@foo/lib1/plugins/some-file":
["packages/lib1/src/plugins/some-file.ts"]`, it should result in:

```json
"@foo/lib1/plugins/some-file": [
  "dist/packages/lib1/plugins/some-file",
  "dist/packages/lib1/src/plugins/some-file",
  "dist/packages/lib1/src/plugins/some-file.ts"
]
```

In this case, the second path would correctly point to the output. It
doesn't have an extension, which allows the compiler to pick up the
correct one.

Note that while the first and third paths are still not valid for this
specific use case, they could still be valid for other use cases, and in
any case, they're still kept for backward compatibility. The util to
re-map these paths is currently very generic and generates potentially
valid paths. The invalid paths for a given use case won't throw an error
as long as there's one that's valid.

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

Fixes #21699

(cherry picked from commit 89f6ad4d8d)
2024-08-19 10:19:59 -04:00
Colum Ferry b6214549d6 fix(module-federation): ensure target defaults are set correctly #27448 (#27472)
<!-- 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 -->
Target Defaults set up for the Module Federation builds is incorrect or
missing.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Target Defaults for Module Federation builds is set up correctly

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

Fixes #27448

(cherry picked from commit 396a5deea9)
2024-08-19 10:19:58 -04:00
@NgDaddy 0c4857d637 fix(react): enable vitejs-plugin-react-swc for swc compiler (#27457)
## Current Behavior
see #27433

## Expected Behavior
see #27433

## Related Issue(s)
see #27433

Fixes #
- if compiler is `swc`, `@vitejs/plugin-react-swc` is being imported;
otherwise `@vitejs/plugin-react` is being imported

Co-authored-by: Emily Xiong <xiongemi@gmail.com>
(cherry picked from commit 7f2c5565de)
2024-08-19 10:19:56 -04:00
Colum Ferry 289c1cb470 fix(angular): module-federation-ssr-dev-server should call correct builder (#27477)
<!-- 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 -->
`module-federation-ssr-dev-server` is calling `dev-server` builder
instead of `ssr-dev-server` builder

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
`module-federation-ssr-dev-server` calls the correct builder

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

Fixes #

(cherry picked from commit 829f5fa758)
2024-08-19 10:19:54 -04:00
Leosvel Pérez Espinosa c009c7480e fix(angular): generate @nx/angular in devDependencies and move to dependencies when using runtime helpers (#27405)
<!-- 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 -->

New workspaces are generated with `@nx/angular` as a production
dependency even though the generated code does not use any runtime
helper from it.

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

New workspaces should be generated with `@nx/angular` as a development
dependency. When generating a new MF host or dynamic MF application
(host or remote), the `@nx/angular` package should be moved to the
production dependencies because the generated code uses some runtime
helpers from it.

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

Fixes #27333

(cherry picked from commit 1ae3c2d8f9)
2024-08-19 10:19:53 -04:00
satanluimm 022754c96b docs(misc): fix react monorepo tutorial link in installation page (#27388)
docs(react): fix link to react monorepo tutorial

(cherry picked from commit 44068b58c0)
2024-08-19 10:19:52 -04:00
Isaac Mann 519ec11a14 fix(nx-dev): bad link from home page (#27475)
Fixes #27417

(cherry picked from commit e7a9f36762)
2024-08-19 10:19:50 -04:00
Benjamin Cabanes 7675f010f4 chore(nx-dev): remove redundant period in plan table (#27464)
Corrects a minor formatting issue by removing an unnecessary period in
the explanation text for failed task outputs.

(cherry picked from commit 0b2f4527d9)
2024-08-19 10:19:48 -04:00
Leosvel Pérez Espinosa 6ae5cdd915 fix(testing): fix misc issues in migrations (#27471)
(cherry picked from commit e368cd4ae1)
2024-08-19 10:19:47 -04:00
robertIsaac 7d1d14fa03 cleanup(js): fix test cases on windows (#27300)
## Current Behavior
running `nx test js` fail on windows

## Expected Behavior
running `nx test js` succeed

## side notes
1. I was skipping `packages/js/src/plugins/typescript/plugin.spec.ts`
because it makes all tests fail, and even in wsl it's very flaky, it
fail 90% of the times, but I couldn't figure out why
I think it's worth looking at from someone with more experience with the
repo
2. for some of the cases that I fixed I'm not sure if I should change
the code to always return `/` or should change the test to adapt `/` in
linux and `\` in windows, so please if I mistaken one of them let me
know and I will do it the other way around but I believe it should be
fine since in windows `foo/bar` does work as a path still

(cherry picked from commit 69c989ed9b)
2024-08-19 10:19:45 -04:00
Colum Ferry f57780d3a5 fix(react): ensure @vitejs/plugin-react is installed for storybook (#27463)
(cherry picked from commit ce2dff303a)
2024-08-19 10:19:42 -04:00
Zachary DeRose c4a1de4fae Adding podcast episodes (#27462)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

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

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

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

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

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

Fixes #

(cherry picked from commit f7eab14f6b)
2024-08-19 10:19:39 -04:00
Jason Jean 60e301140d fix(repo): add dependsOn to native build (#27446)
<!-- 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 -->

`nx:build-native` updates the typings which `graph-client:build-client`
uses. Not running it in the right order could result in a compilation
error.

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

`nx:build-native` updates the typings which `graph-client:build-client`
uses so it depends on `nx:build-native`

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

Fixes #

(cherry picked from commit 01985c11db)
2024-08-19 10:19:36 -04:00
Nicholas Cunningham 560daa55a2 Podcast page (#26971)
(cherry picked from commit 6d7cdd7d41)
2024-08-19 10:19:35 -04:00
Craigory Coppola b9cdcf30d7 chore(repo): docs generation should work on windows (#27444)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

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

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

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

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

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

Fixes #

(cherry picked from commit 4f1db3e07a)
2024-08-19 10:19:34 -04:00
Nicholas Cunningham 12e21bc7c1 fix(nx-dev): Og image path generation (#27456)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

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

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

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

The og images for the blogs are not being generated.

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

The og images for all blogs should be generated.

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

(cherry picked from commit 2e48ebf433)
2024-08-19 10:19:32 -04:00
Juri 0c31f0b067 docs(nx-cloud): update remote caching feature page
(cherry picked from commit 5c890de630)
2024-08-19 10:19:30 -04:00
EGonz1PCTY dbce9f5214 fix(misc): ensure custom reporters are usable with @nx/playwright:playwright (#27443)
<!-- 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
Custom reporters are not usable because we enforce an enum on the schema

## Expected Behavior
Custom reporters can be specified

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

Fixes #

---------

Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
(cherry picked from commit 8c7e3619a9)
2024-08-19 10:19:28 -04:00
James Henry 8529d665b4 docs(release): file based versioning, version plans (#27428)
<!-- 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 -->

Version plans, stable in the imminent `19.6.0` release, is undocumented.

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

Version plans is a documented feature.

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

Fixes #

---------

Co-authored-by: Isaac Mann <isaacplmann@users.noreply.github.com>
(cherry picked from commit f1ecad0a8e)
2024-08-19 10:19:27 -04:00
Benjamin Cabanes 81f2ddbcd0 feat(nx-dev): add Explain with AI to Enterprise and Pro Plans (#27455)
This commit integrates the "Explain with AI" feature into the Enterprise
and Pro plans for providing detailed explanations and insights for
failed task outputs. It also remove the Nx Workflows mention of the
pricing page. It also updates various links within the Hobby plan and
modifies plan table descriptions to reflect the accurate feature names.

(cherry picked from commit 96b8133d85)
2024-08-19 10:19:26 -04:00
Colum Ferry bccd543695 docs(module-federation): update host serve diagram (#27454)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

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

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

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

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

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

Fixes #

(cherry picked from commit d8de0cd792)
2024-08-19 10:19:24 -04:00
Jack Hsu 4758714b87 docs(core): add a recipe for convert-to-inferred migrations (#27442)
This PR adds a recipe for converting executor-based configuration to
inferred configuration.

The goal of the recipe is to guide users on what commands to run,
understand what changes were applied, and how to verify the migrations.

Preview:
https://nx-dev-git-docs-inferred-migration-recipe-nrwl.vercel.app/recipes/running-tasks/convert-to-inferred

[x] Link to this from Project Crystal concept page
[x] Mark recipe as related to Migration feature page

<!-- Please make sure you have read the submission guidelines before
posting an PR -->

<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

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

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

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

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

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

Fixes #

---------

Co-authored-by: Isaac Mann <isaacplmann@gmail.com>
(cherry picked from commit 99b0557fb9)
2024-08-19 10:19:23 -04:00
Isaac Mann 0c82ac8560 docs(core): add organization plugin tutorial (#27279)
Updates the Extending Nx section.

- New intro content
- Enforce Organizational Best Practices tutorial
- Tooling Plugin tutorial
    - Flexible base generators
    - Inferred tasks
    - Executors
    - Migration generators
    - Publishing and listing the plugin
    - E2E testing with Verdaccio

In a separate PR:
- Repository Structure Plugin tutorial
    - Presets
    - Create-* script
    - Migration generators

(cherry picked from commit ba47c72981)
2024-08-19 10:19:20 -04:00
MaxKless 42e1684c7d chore(repo): add comments to make sure code stays manually synced with nx console (#27410)
this code is needed in a version-independent way in Nx Console and right
now there's no good place to share it.
So for now we will duplicate it

(cherry picked from commit cf5dd4da08)
2024-08-19 10:19:19 -04:00
Jonathan Cammisuli dfd5385553 feat(docs): update OpenAI model to use gpt-4o-mini (#27434)
<!-- 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 -->
Current docs chat uses `gpt-3.5-turbo`

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Docs are now using `gpt-4o-mini`

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

Fixes #

(cherry picked from commit 5cd08e2828)
2024-08-19 10:19:18 -04:00
174 changed files with 3735 additions and 1391 deletions
+1
View File
@@ -172,6 +172,7 @@ rust-toolchain @nrwl/nx-native-reviewers
/CONTRIBUTING.md @FrozenPandaz @isaacplmann
/CODE_OF_CONDUCT.md @FrozenPandaz @isaacplmann
/CODEOWNERS @FrozenPandaz @AgentEnder
/packages/nx/src/nx-cloud/utilities/url-shorten.ts @MaxKless
# Scripts
/scripts/documentation @nrwl/nx-docs-reviewers
@@ -0,0 +1,18 @@
---
title: 'Nx Enterprise Podcast Episode 1: Hicham El Hammouchi'
slug: 'hicham-el-hammouchi-podcast-1'
authors: ['Zack DeRose']
tags: [podcast]
cover_image: /blog/images/2024-06-18/ep-1-hicham.png
podcastYoutubeId: 8iiLB_2djZ8
podcastSpotifyId: 24yagCNpu9EGj0fCwSDQkj
podcastAmazonUrl: https://music.amazon.com/podcasts/a221fdad-36fd-4695-a5b4-038d7b99d284/episodes/899a2e4c-2e56-4dfa-a3e3-e69eb216f2b0/the-enterprise-software-podcast-by-nx-the-enterprise-software-podcast-by-nx-1-hicham-el-hamouchi
podcastAppleUrl: https://podcasters.spotify.com/pod/show/enterprise-software/episodes/The-Enterprise-Software-Podcast-By-Nx-1--Hicham-El-Hamouchi-e2l0302
podcastIHeartUrl: https://www.iheart.com/podcast/269-the-enterprise-software-po-186891508/episode/the-enterprise-software-podcast-by-nx-186891511/
---
In this episode, Zack DeRose from Nx chats with Hicham El Hammouchi, a veteran in enterprise software. Hicham dives into his background, sharing his career journey and the wealth of experience he's gathered along the way.
They tackle the tough challenges that companies face in software development. Hicham offers practical insights and real-life examples from his own career, making complex issues easier to understand. He also shares some tried-and-true strategies for overcoming these hurdles, emphasizing the importance of teamwork and effective project management.
As the chat continues, Zack and Hicham explore the latest tech trends and innovations that are shaking up the industry. Hicham's forward-thinking views provide a sneak peek into the future of enterprise software. They also discuss detailed case studies of successful projects, highlighting the importance of staying focused on customer needs and feedback.
@@ -0,0 +1,18 @@
---
title: 'Nx Enterprise Podcast Episode 2: Tine Kondo'
slug: 'tine-kondo-podcast-2'
authors: ['Zack DeRose']
tags: [podcast]
cover_image: /blog/images/2024-07-19/ep-2-tine.png
podcastYoutubeId: Nzf3BmymfEo
podcastSpotifyId: 0CCQaWCln7rvwkkVvsyxsk
podcastAmazonUrl: https://music.amazon.com/podcasts/a221fdad-36fd-4695-a5b4-038d7b99d284/episodes/53934cd9-c521-441e-8523-8b947ed207ca/the-enterprise-software-podcast-by-nx-the-enterprise-software-podcast-by-nx-2-tine-kondo
podcastAppleUrl: https://podcasts.apple.com/us/podcast/the-enterprise-software-podcast-by-nx-2-tine-kondo/id1752704996?i=1000662764990
podcastIHeartUrl: https://www.iheart.com/podcast/269-the-enterprise-software-po-186891508/episode/the-enterprise-software-podcast-by-nx-197335640/
---
In this episode, welcome Nx Champion and Nx Expert, Tine Kondo. Our discussion highlights how Nx is poised to make an impact in the Java space.
We touch on the difficulties larger organizations face when merging PRs in a timely manner, emphasizing how a monorepo alleviates the headache of coordinating multiple PRs across various repositories, and share firsthand experiences and frustrations with traditional multi-repo setups, underscoring the benefits of a unified approach.
In particular, Nx's new plugin: [@nx/gradle](/nx-api/gradle) is of interest, and Tine provides some valuable insight on the impact this plugin can make, as well as the importance for Maven support.
+4 -1
View File
@@ -10,6 +10,8 @@ tags: [nx, release]
In this blog post:
- [Table of Contents](#table-of-contents)
- [Video Summary](#video-summary)
- [Announcing Nx Cloud Hobby Tier](#announcing-nx-cloud-hobby-tier)
- [StackBlitz Support](#stackblitz-support)
- [Bun and Pnpm v9 Support](#bun-and-pnpm-v9-support)
@@ -25,6 +27,7 @@ In this blog post:
- [Support for React 19 (rc) and Angular 18.1](#support-for-react-19-rc-and-angular-181)
- [Automatically Update Nx](#automatically-update-nx)
- [Monorepo World Conference Speakers Announced!!](#monorepo-world-conference-speakers-announced)
- [Learn more](#learn-more)
## Video Summary
@@ -142,7 +145,7 @@ To accommodate port collisions in end-to-end tests, we've long adjusted our gene
Unfortunately, this approach is more imperative, as it instructs how to run and order your CI rather than defining what should be run. Additionally, these instructions cause `lint`, `test`, and `build` targets to run first and wait until they all complete before running `e2e-ci`, leading to inefficiencies.
To address this, all tasks now support a `parallelism` property. By setting this property to `false`, you can instruct the Nx task runner not to run a specific task in parallel. This allows us to define parallelism as a task property, making our CI configuration more declarative.
To address this, all tasks now support a [`parallelism` property](/reference/project-configuration#parallelism). By setting this property to `false`, you can instruct the Nx task runner not to run a specific task in parallel. This allows us to define parallelism as a task property, making our CI configuration more declarative.
Both our `@nx/playwright` and `@nx/cypress` plugins will now automatically set `targetDefaults` for atomized tests to disable parallelism:
@@ -0,0 +1,16 @@
---
title: 'Nx Enterprise Podcast Episode 3: Ahmed Elsakaan'
slug: 'ahmed-elsakaan-podcast-3'
authors: ['Zack DeRose']
tags: [podcast]
cover_image: /blog/images/2024-08-14/ep-3-ahmed.png
podcastYoutubeId: l_b6EOXqYRg
podcastSpotifyId: 4d4oE8B3y9BmECZ3P4uvDP
podcastAmazonUrl: https://music.amazon.com/podcasts/a221fdad-36fd-4695-a5b4-038d7b99d284/episodes/28209cf9-1b88-48b5-a798-7b24c843e9b1/the-enterprise-software-podcast-by-nx-the-enterprise-software-podcast-by-nx-3-ahmed-elsakaan
podcastAppleUrl: https://podcasts.apple.com/us/podcast/the-enterprise-software-podcast-by-nx-3-ahmed-elsakaan/id1752704996?i=1000665363260
podcastIHeartUrl: https://www.iheart.com/podcast/269-the-enterprise-software-po-186891508/episode/the-enterprise-software-podcast-by-nx-205664230/
---
In this episode we welcome Nx Champion and creator of [noodle](https://noodle.run) and [OrbitKit](https://orbitkit.dev/): Ahmed Elsakaan.
As a monorepo enthusiast, we explore Ahmed's thoughts on monorepos and how these tools can add value to developers - both in the space of setting up your monorepo (like Nx does) and in the space of creating reusable customizable modules (like OrbitKit is attempting).
Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 KiB

+89 -67
View File
@@ -8,7 +8,7 @@
"itemList": [
{
"id": "getting-started",
"name": "Getting Started with Plugins",
"name": "Extending Nx with Plugins",
"description": "Learn how to extend Nx by creating and releasing your own Nx plugin.",
"mediaImage": "",
"file": "shared/plugins/intro",
@@ -24,7 +24,7 @@
},
"/extending-nx/intro/getting-started": {
"id": "getting-started",
"name": "Getting Started with Plugins",
"name": "Extending Nx with Plugins",
"description": "Learn how to extend Nx by creating and releasing your own Nx plugin.",
"mediaImage": "",
"file": "shared/plugins/intro",
@@ -35,31 +35,31 @@
},
"/extending-nx/tutorials": {
"id": "tutorials",
"name": "5 Min Tutorials",
"name": "Tutorials",
"description": "Get started with plugins",
"mediaImage": "",
"file": "",
"itemList": [
{
"id": "create-plugin",
"name": "Create a Local Plugin",
"id": "organization-specific-plugin",
"name": "Enforce Organizational Best Practices",
"description": "",
"mediaImage": "",
"file": "shared/plugins/create-plugin",
"file": "shared/plugins/organization-specific-plugin",
"itemList": [],
"isExternal": false,
"path": "/extending-nx/tutorials/create-plugin",
"path": "/extending-nx/tutorials/organization-specific-plugin",
"tags": []
},
{
"id": "publish-plugin",
"name": "Maintain a Published Plugin",
"id": "tooling-plugin",
"name": "Create a Tooling Plugin",
"description": "",
"mediaImage": "",
"file": "shared/plugins/maintain-published-plugin",
"file": "shared/plugins/tooling-plugin",
"itemList": [],
"isExternal": false,
"path": "/extending-nx/tutorials/publish-plugin",
"path": "/extending-nx/tutorials/tooling-plugin",
"tags": []
}
],
@@ -67,26 +67,26 @@
"path": "/extending-nx/tutorials",
"tags": []
},
"/extending-nx/tutorials/create-plugin": {
"id": "create-plugin",
"name": "Create a Local Plugin",
"/extending-nx/tutorials/organization-specific-plugin": {
"id": "organization-specific-plugin",
"name": "Enforce Organizational Best Practices",
"description": "",
"mediaImage": "",
"file": "shared/plugins/create-plugin",
"file": "shared/plugins/organization-specific-plugin",
"itemList": [],
"isExternal": false,
"path": "/extending-nx/tutorials/create-plugin",
"path": "/extending-nx/tutorials/organization-specific-plugin",
"tags": []
},
"/extending-nx/tutorials/publish-plugin": {
"id": "publish-plugin",
"name": "Maintain a Published Plugin",
"/extending-nx/tutorials/tooling-plugin": {
"id": "tooling-plugin",
"name": "Create a Tooling Plugin",
"description": "",
"mediaImage": "",
"file": "shared/plugins/maintain-published-plugin",
"file": "shared/plugins/tooling-plugin",
"itemList": [],
"isExternal": false,
"path": "/extending-nx/tutorials/publish-plugin",
"path": "/extending-nx/tutorials/tooling-plugin",
"tags": []
},
"/extending-nx/recipes": {
@@ -96,28 +96,6 @@
"mediaImage": "",
"file": "",
"itemList": [
{
"id": "local-executors",
"name": "Write a Simple Executor",
"description": "",
"mediaImage": "",
"file": "shared/recipes/plugins/local-executors",
"itemList": [],
"isExternal": false,
"path": "/extending-nx/recipes/local-executors",
"tags": []
},
{
"id": "compose-executors",
"name": "Compose Executors",
"description": "",
"mediaImage": "",
"file": "shared/recipes/plugins/compose-executors",
"itemList": [],
"isExternal": false,
"path": "/extending-nx/recipes/compose-executors",
"tags": []
},
{
"id": "local-generators",
"name": "Write a Simple Generator",
@@ -184,6 +162,28 @@
"path": "/extending-nx/recipes/migration-generators",
"tags": ["create-your-own-plugin"]
},
{
"id": "local-executors",
"name": "Write a Simple Executor",
"description": "",
"mediaImage": "",
"file": "shared/recipes/plugins/local-executors",
"itemList": [],
"isExternal": false,
"path": "/extending-nx/recipes/local-executors",
"tags": []
},
{
"id": "compose-executors",
"name": "Compose Executors",
"description": "",
"mediaImage": "",
"file": "shared/recipes/plugins/compose-executors",
"itemList": [],
"isExternal": false,
"path": "/extending-nx/recipes/compose-executors",
"tags": []
},
{
"id": "create-preset",
"name": "Create a Preset",
@@ -208,7 +208,7 @@
},
{
"id": "project-graph-plugins",
"name": "Modify the Project Graph",
"name": "Infer Tasks or Projects",
"description": "",
"mediaImage": "",
"file": "shared/recipes/plugins/project-graph-plugins",
@@ -216,34 +216,23 @@
"isExternal": false,
"path": "/extending-nx/recipes/project-graph-plugins",
"tags": ["create-your-own-plugin", "explore-graph", "inferred-tasks"]
},
{
"id": "publish-plugin",
"name": "Publish a Plugin",
"description": "",
"mediaImage": "",
"file": "shared/recipes/plugins/publish-plugin",
"itemList": [],
"isExternal": false,
"path": "/extending-nx/recipes/publish-plugin",
"tags": ["create-your-own-plugin"]
}
],
"isExternal": false,
"path": "/extending-nx/recipes",
"tags": []
},
"/extending-nx/recipes/local-executors": {
"id": "local-executors",
"name": "Write a Simple Executor",
"description": "",
"mediaImage": "",
"file": "shared/recipes/plugins/local-executors",
"itemList": [],
"isExternal": false,
"path": "/extending-nx/recipes/local-executors",
"tags": []
},
"/extending-nx/recipes/compose-executors": {
"id": "compose-executors",
"name": "Compose Executors",
"description": "",
"mediaImage": "",
"file": "shared/recipes/plugins/compose-executors",
"itemList": [],
"isExternal": false,
"path": "/extending-nx/recipes/compose-executors",
"tags": []
},
"/extending-nx/recipes/local-generators": {
"id": "local-generators",
"name": "Write a Simple Generator",
@@ -310,6 +299,28 @@
"path": "/extending-nx/recipes/migration-generators",
"tags": ["create-your-own-plugin"]
},
"/extending-nx/recipes/local-executors": {
"id": "local-executors",
"name": "Write a Simple Executor",
"description": "",
"mediaImage": "",
"file": "shared/recipes/plugins/local-executors",
"itemList": [],
"isExternal": false,
"path": "/extending-nx/recipes/local-executors",
"tags": []
},
"/extending-nx/recipes/compose-executors": {
"id": "compose-executors",
"name": "Compose Executors",
"description": "",
"mediaImage": "",
"file": "shared/recipes/plugins/compose-executors",
"itemList": [],
"isExternal": false,
"path": "/extending-nx/recipes/compose-executors",
"tags": []
},
"/extending-nx/recipes/create-preset": {
"id": "create-preset",
"name": "Create a Preset",
@@ -334,7 +345,7 @@
},
"/extending-nx/recipes/project-graph-plugins": {
"id": "project-graph-plugins",
"name": "Modify the Project Graph",
"name": "Infer Tasks or Projects",
"description": "",
"mediaImage": "",
"file": "shared/recipes/plugins/project-graph-plugins",
@@ -342,5 +353,16 @@
"isExternal": false,
"path": "/extending-nx/recipes/project-graph-plugins",
"tags": ["create-your-own-plugin", "explore-graph", "inferred-tasks"]
},
"/extending-nx/recipes/publish-plugin": {
"id": "publish-plugin",
"name": "Publish a Plugin",
"description": "",
"mediaImage": "",
"file": "shared/recipes/plugins/publish-plugin",
"itemList": [],
"isExternal": false,
"path": "/extending-nx/recipes/publish-plugin",
"tags": ["create-your-own-plugin"]
}
}
+113 -49
View File
@@ -1177,6 +1177,14 @@
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Migrate to Inferred Tasks (Project Crystal)",
"path": "/recipes/running-tasks/convert-to-inferred",
"id": "convert-to-inferred",
"isExternal": false,
"children": [],
"disableCollapsible": false
}
],
"disableCollapsible": false
@@ -1962,6 +1970,14 @@
"children": [],
"disableCollapsible": false
},
{
"name": "File Based Versioning (Version Plans)",
"path": "/recipes/nx-release/file-based-versioning-version-plans",
"id": "file-based-versioning-version-plans",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Configure Custom Registries",
"path": "/recipes/nx-release/configure-custom-registries",
@@ -2175,6 +2191,14 @@
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Migrate to Inferred Tasks (Project Crystal)",
"path": "/recipes/running-tasks/convert-to-inferred",
"id": "convert-to-inferred",
"isExternal": false,
"children": [],
"disableCollapsible": false
}
],
"disableCollapsible": false
@@ -2267,6 +2291,14 @@
"children": [],
"disableCollapsible": false
},
{
"name": "Migrate to Inferred Tasks (Project Crystal)",
"path": "/recipes/running-tasks/convert-to-inferred",
"id": "convert-to-inferred",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Adopting Nx",
"path": "/recipes/adopting-nx",
@@ -3681,6 +3713,14 @@
"children": [],
"disableCollapsible": false
},
{
"name": "File Based Versioning (Version Plans)",
"path": "/recipes/nx-release/file-based-versioning-version-plans",
"id": "file-based-versioning-version-plans",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Configure Custom Registries",
"path": "/recipes/nx-release/configure-custom-registries",
@@ -3772,6 +3812,14 @@
"children": [],
"disableCollapsible": false
},
{
"name": "File Based Versioning (Version Plans)",
"path": "/recipes/nx-release/file-based-versioning-version-plans",
"id": "file-based-versioning-version-plans",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Configure Custom Registries",
"path": "/recipes/nx-release/configure-custom-registries",
@@ -4946,7 +4994,7 @@
"isExternal": false,
"children": [
{
"name": "Getting Started with Plugins",
"name": "Extending Nx with Plugins",
"path": "/extending-nx/intro/getting-started",
"id": "getting-started",
"isExternal": false,
@@ -4957,7 +5005,7 @@
"disableCollapsible": false
},
{
"name": "Getting Started with Plugins",
"name": "Extending Nx with Plugins",
"path": "/extending-nx/intro/getting-started",
"id": "getting-started",
"isExternal": false,
@@ -4965,23 +5013,23 @@
"disableCollapsible": false
},
{
"name": "5 Min Tutorials",
"name": "Tutorials",
"path": "/extending-nx/tutorials",
"id": "tutorials",
"isExternal": false,
"children": [
{
"name": "Create a Local Plugin",
"path": "/extending-nx/tutorials/create-plugin",
"id": "create-plugin",
"name": "Enforce Organizational Best Practices",
"path": "/extending-nx/tutorials/organization-specific-plugin",
"id": "organization-specific-plugin",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Maintain a Published Plugin",
"path": "/extending-nx/tutorials/publish-plugin",
"id": "publish-plugin",
"name": "Create a Tooling Plugin",
"path": "/extending-nx/tutorials/tooling-plugin",
"id": "tooling-plugin",
"isExternal": false,
"children": [],
"disableCollapsible": false
@@ -4990,17 +5038,17 @@
"disableCollapsible": false
},
{
"name": "Create a Local Plugin",
"path": "/extending-nx/tutorials/create-plugin",
"id": "create-plugin",
"name": "Enforce Organizational Best Practices",
"path": "/extending-nx/tutorials/organization-specific-plugin",
"id": "organization-specific-plugin",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Maintain a Published Plugin",
"path": "/extending-nx/tutorials/publish-plugin",
"id": "publish-plugin",
"name": "Create a Tooling Plugin",
"path": "/extending-nx/tutorials/tooling-plugin",
"id": "tooling-plugin",
"isExternal": false,
"children": [],
"disableCollapsible": false
@@ -5011,22 +5059,6 @@
"id": "recipes",
"isExternal": false,
"children": [
{
"name": "Write a Simple Executor",
"path": "/extending-nx/recipes/local-executors",
"id": "local-executors",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Compose Executors",
"path": "/extending-nx/recipes/compose-executors",
"id": "compose-executors",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Write a Simple Generator",
"path": "/extending-nx/recipes/local-generators",
@@ -5075,6 +5107,22 @@
"children": [],
"disableCollapsible": false
},
{
"name": "Write a Simple Executor",
"path": "/extending-nx/recipes/local-executors",
"id": "local-executors",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Compose Executors",
"path": "/extending-nx/recipes/compose-executors",
"id": "compose-executors",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Create a Preset",
"path": "/extending-nx/recipes/create-preset",
@@ -5092,32 +5140,24 @@
"disableCollapsible": false
},
{
"name": "Modify the Project Graph",
"name": "Infer Tasks or Projects",
"path": "/extending-nx/recipes/project-graph-plugins",
"id": "project-graph-plugins",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Publish a Plugin",
"path": "/extending-nx/recipes/publish-plugin",
"id": "publish-plugin",
"isExternal": false,
"children": [],
"disableCollapsible": false
}
],
"disableCollapsible": false
},
{
"name": "Write a Simple Executor",
"path": "/extending-nx/recipes/local-executors",
"id": "local-executors",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Compose Executors",
"path": "/extending-nx/recipes/compose-executors",
"id": "compose-executors",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Write a Simple Generator",
"path": "/extending-nx/recipes/local-generators",
@@ -5166,6 +5206,22 @@
"children": [],
"disableCollapsible": false
},
{
"name": "Write a Simple Executor",
"path": "/extending-nx/recipes/local-executors",
"id": "local-executors",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Compose Executors",
"path": "/extending-nx/recipes/compose-executors",
"id": "compose-executors",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Create a Preset",
"path": "/extending-nx/recipes/create-preset",
@@ -5183,12 +5239,20 @@
"disableCollapsible": false
},
{
"name": "Modify the Project Graph",
"name": "Infer Tasks or Projects",
"path": "/extending-nx/recipes/project-graph-plugins",
"id": "project-graph-plugins",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Publish a Plugin",
"path": "/extending-nx/recipes/publish-plugin",
"id": "publish-plugin",
"isExternal": false,
"children": [],
"disableCollapsible": false
}
]
},
+66
View File
@@ -1608,6 +1608,17 @@
"isExternal": false,
"path": "/recipes/running-tasks/skipping-cache",
"tags": []
},
{
"id": "convert-to-inferred",
"name": "Migrate to Inferred Tasks (Project Crystal)",
"description": "",
"mediaImage": "",
"file": "shared/recipes/running-tasks/convert-to-inferred",
"itemList": [],
"isExternal": false,
"path": "/recipes/running-tasks/convert-to-inferred",
"tags": ["inferred-tasks", "automate-updating-dependencies"]
}
],
"isExternal": false,
@@ -2681,6 +2692,17 @@
"path": "/recipes/nx-release/customize-conventional-commit-types",
"tags": ["nx-release"]
},
{
"id": "file-based-versioning-version-plans",
"name": "File Based Versioning (Version Plans)",
"description": "",
"mediaImage": "",
"file": "shared/recipes/nx-release/file-based-versioning-version-plans",
"itemList": [],
"isExternal": false,
"path": "/recipes/nx-release/file-based-versioning-version-plans",
"tags": ["nx-release"]
},
{
"id": "configure-custom-registries",
"name": "Configure Custom Registries",
@@ -2974,6 +2996,17 @@
"isExternal": false,
"path": "/recipes/running-tasks/skipping-cache",
"tags": []
},
{
"id": "convert-to-inferred",
"name": "Migrate to Inferred Tasks (Project Crystal)",
"description": "",
"mediaImage": "",
"file": "shared/recipes/running-tasks/convert-to-inferred",
"itemList": [],
"isExternal": false,
"path": "/recipes/running-tasks/convert-to-inferred",
"tags": ["inferred-tasks", "automate-updating-dependencies"]
}
],
"isExternal": false,
@@ -3101,6 +3134,17 @@
"path": "/recipes/running-tasks/skipping-cache",
"tags": []
},
"/recipes/running-tasks/convert-to-inferred": {
"id": "convert-to-inferred",
"name": "Migrate to Inferred Tasks (Project Crystal)",
"description": "",
"mediaImage": "",
"file": "shared/recipes/running-tasks/convert-to-inferred",
"itemList": [],
"isExternal": false,
"path": "/recipes/running-tasks/convert-to-inferred",
"tags": ["inferred-tasks", "automate-updating-dependencies"]
},
"/recipes/adopting-nx": {
"id": "adopting-nx",
"name": "Adopting Nx",
@@ -5038,6 +5082,17 @@
"path": "/recipes/nx-release/customize-conventional-commit-types",
"tags": ["nx-release"]
},
{
"id": "file-based-versioning-version-plans",
"name": "File Based Versioning (Version Plans)",
"description": "",
"mediaImage": "",
"file": "shared/recipes/nx-release/file-based-versioning-version-plans",
"itemList": [],
"isExternal": false,
"path": "/recipes/nx-release/file-based-versioning-version-plans",
"tags": ["nx-release"]
},
{
"id": "configure-custom-registries",
"name": "Configure Custom Registries",
@@ -5164,6 +5219,17 @@
"path": "/recipes/nx-release/customize-conventional-commit-types",
"tags": ["nx-release"]
},
"/recipes/nx-release/file-based-versioning-version-plans": {
"id": "file-based-versioning-version-plans",
"name": "File Based Versioning (Version Plans)",
"description": "",
"mediaImage": "",
"file": "shared/recipes/nx-release/file-based-versioning-version-plans",
"itemList": [],
"isExternal": false,
"path": "/recipes/nx-release/file-based-versioning-version-plans",
"tags": ["nx-release"]
},
"/recipes/nx-release/configure-custom-registries": {
"id": "configure-custom-registries",
"name": "Configure Custom Registries",
+31 -3
View File
@@ -269,7 +269,7 @@
"description": "",
"file": "shared/recipes/plugins/project-graph-plugins",
"id": "project-graph-plugins",
"name": "Modify the Project Graph",
"name": "Infer Tasks or Projects",
"path": "/extending-nx/recipes/project-graph-plugins"
},
{
@@ -353,6 +353,13 @@
"name": "Automate Updating Dependencies",
"path": "/features/automate-updating-dependencies"
},
{
"description": "",
"file": "shared/recipes/running-tasks/convert-to-inferred",
"id": "convert-to-inferred",
"name": "Migrate to Inferred Tasks (Project Crystal)",
"path": "/recipes/running-tasks/convert-to-inferred"
},
{
"description": "",
"file": "shared/recipes/tips-n-tricks/keep-nx-versions-in-sync",
@@ -490,6 +497,13 @@
"name": "Customize Conventional Commit Types",
"path": "/recipes/nx-release/customize-conventional-commit-types"
},
{
"description": "",
"file": "shared/recipes/nx-release/file-based-versioning-version-plans",
"id": "file-based-versioning-version-plans",
"name": "File Based Versioning (Version Plans)",
"path": "/recipes/nx-release/file-based-versioning-version-plans"
},
{
"description": "",
"file": "shared/recipes/nx-release/configure-custom-registries",
@@ -585,11 +599,18 @@
"name": "Inferred Tasks",
"path": "/concepts/inferred-tasks"
},
{
"description": "",
"file": "shared/recipes/running-tasks/convert-to-inferred",
"id": "convert-to-inferred",
"name": "Migrate to Inferred Tasks (Project Crystal)",
"path": "/recipes/running-tasks/convert-to-inferred"
},
{
"description": "",
"file": "shared/recipes/plugins/project-graph-plugins",
"id": "project-graph-plugins",
"name": "Modify the Project Graph",
"name": "Infer Tasks or Projects",
"path": "/extending-nx/recipes/project-graph-plugins"
}
],
@@ -626,9 +647,16 @@
"description": "",
"file": "shared/recipes/plugins/project-graph-plugins",
"id": "project-graph-plugins",
"name": "Modify the Project Graph",
"name": "Infer Tasks or Projects",
"path": "/extending-nx/recipes/project-graph-plugins"
},
{
"description": "",
"file": "shared/recipes/plugins/publish-plugin",
"id": "publish-plugin",
"name": "Publish a Plugin",
"path": "/extending-nx/recipes/publish-plugin"
},
{
"description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, Playwright and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.",
"file": "generated/packages/angular/documents/nx-devkit-angular-devkit",
@@ -86,7 +86,7 @@ The `targetName`, `ciTargetName`, `componentTestingTargetName`, and `open-cypres
### Splitting E2E tasks by file
The `@nx/cypress/plugin` will automatically split your e2e tasks by file. You can read more about this feature [here](/ci/features/split-e2e-tasks).
The `@nx/cypress/plugin` will automatically split your e2e tasks by file. You can read more about the Atomizer feature [here](/ci/features/split-e2e-tasks).
To enable e2e task splitting, make sure there is a `ciWebServerCommand` property set in your `cypress.config.ts` file. It will look something like this:
@@ -68,7 +68,8 @@ The `@nx/gradle` is configured in the `plugins` array in `nx.json`.
"options": {
"testTargetName": "test",
"classesTargetName": "classes",
"buildTargetName": "build"
"buildTargetName": "build",
"ciTargetName": "test-ci"
}
}
]
@@ -77,6 +78,23 @@ The `@nx/gradle` is configured in the `plugins` array in `nx.json`.
Once a Gradle configuration file has been identified, the targets are created with the name you specify under `testTargetName`, `classesTargetName` or `buildTargetName` in the `nx.json` `plugins` array. The default names for the inferred targets are `test`, `classes` and `build`.
### Splitting E2E Tests
The `@nx/gradle` plugin will automatically split your e2e tasks by file if you provide a `ciTargetName`. You can read more about the Atomizer feature [here](/ci/features/split-e2e-tasks). This will create a target with that name which can be used in CI to run the tests for each file in a distributed fashion.
```json {% fileName="nx.json" highlightLines=[6] %}
{
"plugins": [
{
"plugin": "@nx/gradle",
"options": {
"ciTargetName": "test-ci"
}
}
]
}
```
## View Inferred Tasks
To view inferred tasks for a project, open the [project details view](/features/explore-graph#explore-projects-in-your-workspace) in Nx Console or run `nx show project my-project --web` in the command line.
@@ -70,7 +70,7 @@ within the same workspace. In this case, you can configure the `@nx/jest/plugin`
### Splitting E2E Tests
If Jest is used to run E2E tests, you can enable [splitting the tasks](/ci/features/split-e2e-tasks) by file to get
improved caching, distribution, and retrying flaky tests. Enable this, by providing a `ciTargetName`. This will create a
improved caching, distribution, and retrying flaky tests. Enable this Atomizer feature by providing a `ciTargetName`. This will create a
target with that name which can be used in CI to run the tests for each file in a distributed fashion.
```json {% fileName="nx.json" %}
@@ -65,6 +65,24 @@ The `@nx/playwright/plugin` is configured in the `plugins` array in `nx.json`.
The `targetName` and `ciTargetName` options control the name of the inferred Playwright tasks. The default names are `e2e` and `e2e-ci`.
### Splitting E2E Tests
The `@nx/playwright/plugin` will automatically split your e2e tasks by file if you provide a `ciTargetName`. You can read more about the Atomizer feature [here](/ci/features/split-e2e-tasks). This will create a target with that name which can be used in CI to run the tests for each file in a distributed fashion.
```json {% fileName="nx.json" %}
{
"plugins": [
{
"plugin": "@nx/playwright/plugin",
"options": {
"targetName": "e2e",
"ciTargetName": "e2e-ci"
}
}
]
}
```
### Splitting E2E tasks by file
The `@nx/playwright/plugin` will automatically split your e2e tasks by file. You can read more about this feature [here](/ci/features/split-e2e-tasks).
@@ -104,18 +104,7 @@
},
"reporter": {
"type": "string",
"enum": [
"list",
"line",
"dot",
"json",
"junit",
"null",
"github",
"html",
"blob"
],
"description": "Reporter to use, comma-separated, can be 'list', 'line', 'dot', 'json', 'junit', 'null', 'github', 'html', 'blob'. To configure reporter options, use the playwright configuration."
"description": "Common Reporter values to use, comma-separated, 'list', 'line', 'dot', 'json', 'junit', 'null', 'github', 'html', 'blob'. To configure reporter options, use the playwright configuration."
},
"retries": {
"type": "number",
+39 -21
View File
@@ -431,6 +431,12 @@
"name": "Skip Task Caching",
"id": "skipping-cache",
"file": "shared/recipes/running-tasks/skipping-cache"
},
{
"name": "Migrate to Inferred Tasks (Project Crystal)",
"id": "convert-to-inferred",
"tags": ["inferred-tasks", "automate-updating-dependencies"],
"file": "shared/recipes/running-tasks/convert-to-inferred"
}
]
},
@@ -1015,6 +1021,12 @@
"tags": ["nx-release"],
"file": "shared/recipes/nx-release/customize-conventional-commit-types"
},
{
"name": "File Based Versioning (Version Plans)",
"id": "file-based-versioning-version-plans",
"tags": ["nx-release"],
"file": "shared/recipes/nx-release/file-based-versioning-version-plans"
},
{
"name": "Configure Custom Registries",
"id": "configure-custom-registries",
@@ -1457,7 +1469,7 @@
"description": "Learn about plugins.",
"itemList": [
{
"name": "Getting Started with Plugins",
"name": "Extending Nx with Plugins",
"id": "getting-started",
"description": "Learn how to extend Nx by creating and releasing your own Nx plugin.",
"file": "shared/plugins/intro"
@@ -1465,19 +1477,19 @@
]
},
{
"name": "5 Min Tutorials",
"name": "Tutorials",
"id": "tutorials",
"description": "Get started with plugins",
"itemList": [
{
"name": "Create a Local Plugin",
"id": "create-plugin",
"file": "shared/plugins/create-plugin"
"name": "Enforce Organizational Best Practices",
"id": "organization-specific-plugin",
"file": "shared/plugins/organization-specific-plugin"
},
{
"name": "Maintain a Published Plugin",
"id": "publish-plugin",
"file": "shared/plugins/maintain-published-plugin"
"name": "Create a Tooling Plugin",
"id": "tooling-plugin",
"file": "shared/plugins/tooling-plugin"
}
]
},
@@ -1486,18 +1498,6 @@
"id": "recipes",
"description": "Focused instructions to complete a specific task",
"itemList": [
{
"name": "Write a Simple Executor",
"id": "local-executors",
"tags": [],
"file": "shared/recipes/plugins/local-executors"
},
{
"name": "Compose Executors",
"id": "compose-executors",
"tags": [],
"file": "shared/recipes/plugins/compose-executors"
},
{
"name": "Write a Simple Generator",
"id": "local-generators",
@@ -1534,6 +1534,18 @@
"tags": ["create-your-own-plugin"],
"file": "shared/recipes/plugins/migration-generators"
},
{
"name": "Write a Simple Executor",
"id": "local-executors",
"tags": [],
"file": "shared/recipes/plugins/local-executors"
},
{
"name": "Compose Executors",
"id": "compose-executors",
"tags": [],
"file": "shared/recipes/plugins/compose-executors"
},
{
"name": "Create a Preset",
"id": "create-preset",
@@ -1547,7 +1559,7 @@
"file": "shared/recipes/plugins/create-install-package"
},
{
"name": "Modify the Project Graph",
"name": "Infer Tasks or Projects",
"id": "project-graph-plugins",
"tags": [
"create-your-own-plugin",
@@ -1555,6 +1567,12 @@
"inferred-tasks"
],
"file": "shared/recipes/plugins/project-graph-plugins"
},
{
"name": "Publish a Plugin",
"id": "publish-plugin",
"tags": ["create-your-own-plugin"],
"file": "shared/recipes/plugins/publish-plugin"
}
]
}
+1 -1
View File
@@ -11,7 +11,7 @@ You could manually address these problems by splitting your e2e tests into small
## Set up
To enable atomized tasks, you need to turn on [inferred tasks](/concepts/inferred-tasks#existing-nx-workspaces) for the [@nx/cypress](/nx-api/cypress#splitting-e2e-tasks-by-file), [@nx/playwright](/nx-api/playwright#splitting-e2e-tasks-by-file), [@nx/jest](/nx-api/jest#splitting-e2e-tests) or [@nx/gradle](/nx-api/gradle) plugins. Run this command to set up inferred tasks:
To enable atomized tasks, you need to turn on [inferred tasks](/concepts/inferred-tasks#existing-nx-workspaces) for the [@nx/cypress](/nx-api/cypress#splitting-e2e-tasks-by-file), [@nx/playwright](/nx-api/playwright#splitting-e2e-tests), [@nx/jest](/nx-api/jest#splitting-e2e-tests) or [@nx/gradle](/nx-api/gradle#splitting-e2e-tests) plugins. Run this command to set up inferred tasks:
{% tabs %}
{% tab label="Cypress" %}
+1 -1
View File
@@ -1,6 +1,6 @@
# Nx CLI and Access Tokens
The permissions and membership define what developers can access on nx.app. They don't affect what happens when you run Nx commands locally or on CI. To manage that, you need to provision access tokens. To do that, go to Workspace Options / Manage Access Tokens.
The permissions and membership define what developers can access on [Nx Cloud](https://nx.app). They don't affect what happens when you run Nx commands locally or on CI. To manage that, you need to provision access tokens. To do that, go to Workspace Options / Manage Access Tokens.
## Types of Access Tokens
+6 -6
View File
@@ -188,7 +188,7 @@ launch-templates:
- name: Restore Node Modules Cache
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/cache/main.yaml'
inputs:
env: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
key: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
paths: 'node_modules'
base_branch: 'main'
```
@@ -221,13 +221,13 @@ launch-templates:
# the cache step requires configuration via env vars
# https://github.com/nrwl/nx-cloud-workflows/tree/main/workflow-steps/cache#options
inputs:
env: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
key: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
paths: 'node_modules'
base_branch: 'main'
- name: Restore Browser Binary Cache
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/cache/main.yaml'
inputs:
env: 'package-lock.json|yarn.lock|pnpm-lock.yaml|"browsers"'
key: 'package-lock.json|yarn.lock|pnpm-lock.yaml|"browsers"'
paths: |
'~/.cache/Cypress'
'~/.cache/ms-playwright'
@@ -272,13 +272,13 @@ launch-templates:
- name: Restore Node Modules Cache
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/cache/main.yaml'
inputs:
env: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
key: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
paths: 'node_modules'
base_branch: 'main'
- name: Restore Browser Binary Cache
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/cache/main.yaml'
inputs:
env: 'package-lock.json|yarn.lock|pnpm-lock.yaml|"browsers"'
key: 'package-lock.json|yarn.lock|pnpm-lock.yaml|"browsers"'
paths: |
'~/.cache/Cypress'
'~/.cache/ms-playwright'
@@ -310,7 +310,7 @@ launch-templates:
- name: Restore Node Modules Cache
uses: 'nrwl/nx-cloud-workflows/v4/workflow-steps/cache/main.yaml'
inputs:
env: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
key: 'package-lock.json|yarn.lock|pnpm-lock.yaml'
paths: 'node_modules'
base_branch: 'main'
- name: Install Node Modules
+4 -4
View File
@@ -54,11 +54,11 @@ For more information on the different types of inputs and how to configure input
## What is Cached
Nx works on the process level. Regardless of the tools used to build/test/lint/etc.. your project, the results are cached.
Nx cache works on the process level. Regardless of the tools used to build/test/lint/etc.. your project, the results are cached. This includes:
It collects terminal output when running tasks. All the terminal output is cached and then replayed during a cache hit.
Nx can also cache the files generated by a task. The list of files/folders is listed in the `outputs` property of the project's `package.json` or `project.json`:
- **Terminal output:** The terminal output generated when running a task. This includes logs, warnings, and errors.
- **Task artifacts:** The output files of a task defined in the [`outputs` property of your project configuration](/recipes/running-tasks/configure-outputs). For example the build output, test results, or linting reports.
- **Hash:** The hash of the inputs to the computation. The inputs include the source code, runtime values, and command line arguments. Note that the hash is included in the cache, but the actual inputs are not.
{% tabs %}
{% tab label="package.json" %}
+3 -5
View File
@@ -74,15 +74,13 @@ More details about how to override task configuration is available in these reci
## Existing Nx Workspaces
If you have an existing Nx Workspace and upgrade to Nx 18, the migration generator will automatically add `NX_ADD_PLUGINS=false` to your `.env` file. This environment variable allows you to continue to use Nx without inferred tasks. Alternatively, you can set the `useInferencePlugins` property to `false` in `nx.json`.
If you have an existing Nx Workspace and upgrade to the latest Nx version, a migration will automatically set `useInferencePlugins` to `false` in `nx.json`. This property allows you to continue to use Nx without inferred tasks.
We are working on creating migrations for existing workspaces to start switching to inferred tasks, where desired.
When `NX_ADD_PLUGINS` or `useInferencePlugins` is `false`:
When `useInferencePlugins` is `false`:
1. A newly generated project will have all targets defined with executors - not with inferred tasks.
2. Running `nx add @nx/some-plugin` will not create a plugin entry for `@nx/some-plugin` in the `nx.json` file. (So that plugin will not create inferred tasks.)
If your `.env` file is ignored by git, ensure that everyone in your organization sets `NX_ADD_PLUGINS=false`. This could be done by updating a shared `.env` file or setting the environment variable in everyone's `.bashrc` file.
If you want to **migrate** your projects to use inferred tasks, follow the recipe for [migrating to inferred tasks](/recipes/running-tasks/convert-to-inferred).
Even once a repository has fully embraced inferred tasks, `project.json` and executors will still be useful. The `project.json` file is needed to modify inferred task options and to define tasks that can not be inferred. Some executors perform tasks that can not be accomplished by running a tool directly from the command line (i.e. [TypeScript batch mode](/recipes/tips-n-tricks/enable-tsc-batch-mode)).
Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 114 KiB

+1 -1
View File
@@ -23,5 +23,5 @@ For example, plugins can accomplish the following:
{% cards %}
{% card title="Official Plugins" description="The API documentation for Nx Plugins maintained by the Nx core team" type="documentation" url="/nx-api" /%}
{% card title="Community Plugins" description="Browse the plugin registry to discover plugins created by the community" type="documentation" url="/plugin-registry" /%}
{% card title="Build Your Own Plugin" description="Build your own plugin to use internally or share with the community" type="documentation" url="/extending-nx/tutorials/create-plugin" /%}
{% card title="Build Your Own Plugin" description="Build your own plugin to use internally or share with the community" type="documentation" url="/extending-nx/tutorials/organization-specific-plugin" /%}
{% /cards %}
+47 -63
View File
@@ -1,87 +1,71 @@
# Use Remote Caching (Nx Replay)
By default Nx [caches task computations locally](/features/cache-task-results). However, to benefit from the cache across your team and in particular on CI, the computation cache can also be distributed across multiple machines.
The **Nx Replay** feature of Nx Cloud is a fast, secure and zero-config implementation of remote caching.
Repeatedly rebuilding and retesting the same code is costly — not just in terms of wasted resources, but also in terms of developer time. To solve this, Nx includes a sophisticated computation caching system that ensures **code is never rebuilt twice**, saving you both time and resources.
![Diagram showing Teika sharing his cache with CI, Kimiko and James](/shared/images/dte/distributed-caching.svg)
In this diagram, Teika runs the build once on his machine, then CI, Kimiko and James can use the cached artifact from Teika instead of re-executing the same work.
By default, Nx [caches task computations locally](/features/cache-task-results), but the biggest benefit comes from **sharing this cache across your team and in CI**.
## Setting Up Nx Cloud
- **Zero config** and **secure** by default
- Drastically **speeds up task execution times** during local development, and more critically in CI
- **Saves money on CI/CD costs** by reducing the number of tasks that need to be executed (we observed 30-70% faster CI & half the cost)
To use **Nx Replay** you need to connect your workspace to Nx Cloud. See the [connect to Nx Cloud recipe](/ci/intro/connect-to-nx-cloud).
Nx **restores terminal output, along with the files and artifacts** created from running the task (e.g., your build or dist directory). If you want to learn more about the conceptual model behind Nx's caching, read [How Caching Works](/concepts/how-caching-works).
## See Remote Caching in Action
## Configure Remote Caching
To see the remote cache in action, run:
To use **Nx Replay**, you need to connect your workspace to Nx Cloud.
```{% command="nx build header && nx reset && nx build header"%}
> nx run header:build
> header@0.0.0 build
> rimraf dist && rollup --config
src/index.tsx → dist...
created dist in 786ms
—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
NX Successfully ran target build for project header (2s)
See logs and investigate cache misses at https://cloud.nx.app/runs/k0HDHACpL8
NX Resetting the Nx workspace cache and stopping the Nx Daemon.
This might take a few minutes.
NX Daemon Server - Stopped
NX Successfully reset the Nx workspace.
> nx run header:build [remote cache]
> header@0.0.0 build
> rimraf dist && rollup --config
src/index.tsx → dist...
created dist in 786ms
—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
NX Successfully ran target build for project header (664ms)
Nx read the output from the cache instead of running the command for 1 out of 1 tasks.
Nx Cloud made it possible to reuse header: https://nx.app/runs/P0X6ZGTkqZ
```shell
npx nx connect
```
## Benefits of Nx Replay
See the [connect to Nx Cloud recipe](/ci/intro/connect-to-nx-cloud) for all the details.
There are two ways that Nx Replay directly benefits your organization.
## Why use Remote Caching (Nx Replay)?
### 1. Speed Up CI Pipelines for Modified PRs
Nx Replay directly benefits your organization by:
The first time a PR goes through the CI pipeline, the [affected](/ci/features/affected) command provides most of the time savings. The `affected` command compares the PR against the `main` branch and only runs tasks for projects that could have been affected by the code changes. Unfortunately, all the projects affected by the first CI pipeline for a PR will continue to be affected by all future commits to that PR. This is because `affected` compares the current commit with the `main` branch every time.
- **Speeding up CI pipelines:** With Nx Replay, tasks that have already been executed in a PRs initial CI pipeline run can **reuse cached results in subsequent runs**. This reduces the need to re-run unaffected tasks, significantly speeding up the CI process for modified PRs. This benefit complements the [affected command](/ci/features/affected), which optimizes pipelines by only running tasks for projects that could be impacted by code changes.
With Nx Replay enabled, any tasks that were run during the first pipeline and not affected by the second commit would reuse the cached results from the first pipeline instead of re-running the task. This gives subsequent pipeline runs a mechanism similar to affected that will [reduce the wasted time in CI](/ci/concepts/reduce-waste).
- **Boosting local developer efficiency:** Depending on [how cache permissions](/ci/recipes/security/access-tokens) are set for your workspace, developers can reuse cached results from CI on their local machines. As a result, tasks like builds and tests can complete instantly if they were already executed in CI. This accelerates developer workflows without any extra steps required.
### 2. Reuse Cached Results from CI on Developer Machines
- **Enabling Nx Agents:** Nx Replay is crucial for [Nx Agents](/ci/features/distribute-task-execution) to function efficiently. Nx Agents leverage remote caching as a **transport mechanism** for transferring task artifacts between machines as it distributes tasks. When a task depends on another task that may have been executed on a different agent, Nx Replay ensures the necessary artifacts are transferred seamlessly. This allows each agent to execute only its assigned tasks while relying on cached results for dependencies, ensuring tasks run only once and are shared across all agents. [Learn more about Nx Agents](/ci/features/distribute-task-execution).
If a task has been executed in CI, a developer running that same task locally can reuse the task result instead of actually running the task. Here are a couple common scenarios where this happens:
## What gets stored?
1. A developer pulls the latest code from `main` and rebuilds an application. The build finishes instantly and they're ready to start working.
2. A developer checks out someone else's PR branch to help troubleshoot a problem. They run the tests and all the successful tests finish instantly. They can focus their debugging time on the few tests that are still failing.
Nx Cloud stores the following:
The best part about Nx Replay is that developers will experience the benefits of it without needing to remember to use it. Some of their tasks will just finish much faster than they normally do.
- **Terminal output:** The terminal output generated when running a task. This includes logs, warnings, and errors.
- **Task artifacts:** The output files of a task defined in the [`outputs` property of your project configuration](/recipes/running-tasks/configure-outputs). For example, the build output, test results, or linting reports.
- **Hash:** The hash of the inputs to the computation. The inputs include the source code, runtime values, and command line arguments. Note that the hash is included in the cache, but the actual inputs are not.
## Nx Replay Enables Nx Agents
Learn more about [how caching works](/concepts/how-caching-works#what-is-cached).
One more indirect benefit of Nx Replay is that it is critical to the way [Nx Agents](/ci/features/distribute-task-execution) is built. Nx Agents relies heavily on the remote cache in order to ensure that all task artifacts are present on the agent machines where they are needed. Each agent can naively run the dependencies for the tasks it is assigned and rely on Nx Replay to retrieve the cached tasks results for those tasks. Nx Replay ensures that each task will only run on one agent and the results of that task will be shared with every agent that needs them.
## Security in Remote Caching
## Skipping Cloud Cache
Since we work with many large corporations (including banks, insurance companies, and governments), we take security very seriously. Nx Cloud provides several features to ensure your data remains safe and secure:
Similar to how `--skip-nx-cache` will instruct Nx not to use the local cache, passing `--no-cloud` will tell Nx not to use the remote cache from Nx Cloud.
- **Immutability:** Each cache entry is immutable, meaning once an entry is created, it cannot be altered. This ensures that cached results cannot be tampered with by malicious parties, preventing the injection of vulnerabilities into your build process.
- **Access Control via Tokens:** Nx Cloud allows you to [control who can read from and write to the cache](/ci/recipes/security/access-tokens). For example, you can configure these settings to restrict cache write access to your CI pipeline while allowing all developers to only read.
- **End-to-End Encryption:** Nx Cloud supports end-to-end encryption to protect your data. Task artifacts are encrypted before being sent to the remote cache and decrypted when retrieved. This ensures that even if someone gains access to Nx Cloud servers, they cannot view your stored artifacts. For more details, visit the [encryption documentation](/ci/recipes/security/encryption).
- **Nx Enterprise (Self-Hosting and EU Regions):** For organizations with specific compliance or data residency requirements, Nx Enterprise offers the option to self-host Nx Cloud on your own infrastructure. Additionally, you can choose to host in EU regions, ensuring that your data complies with regional data protection laws. This is available to our [Nx Enterprise customers](/enterprise).
- **SOC Certification:** Nx and Nx Cloud are SOC Type 1 and Type 2 certified, providing an additional layer of assurance that your data is handled according to industry-standard security practices. For more details, you can visit our [security page](https://security.nx.app).
### Configure Caching Access
Caching access can be restricted in terms of read/write access. You can configure this in your [Nx Cloud dashboard](https://nx.app). Learn more about it [here](/ci/recipes/security/access-tokens).
## FAQ
### What if the remote cache is offline?
Nx Replay automatically syncs the remote cache to the local cache folder. As such, if the remote cache is not available, it will automatically fall back to the local cache or just run the task if it is not cached.
### Skipping Cloud Cache
To learn more about how to temporarily skip task caching, head over to [our corresponding docs page](/recipes/running-tasks/skipping-cache#skip-remote-caching-from-nx-cloud).
+2 -2
View File
@@ -140,8 +140,8 @@ Try one of these tutorials for a full walkthrough of what to do after you instal
- [Single React App Tutorial](/getting-started/tutorials/react-standalone-tutorial)
- [Single Angular App Tutorial](/getting-started/tutorials/angular-standalone-tutorial)
- [Single Vue App Tutorial](/getting-started/tutorials/vue-standalone-tutorial)
- [React Monorepo Tutorial](/getting-started/tutorials/react-standalone-tutorial)
- [Angular Monorepo Tutorial](/getting-started/tutorials/angular-standalone-tutorial)
- [React Monorepo Tutorial](/getting-started/tutorials/react-monorepo-tutorial)
- [Angular Monorepo Tutorial](/getting-started/tutorials/angular-monorepo-tutorial)
## More Documentation
+1 -1
View File
@@ -9,7 +9,7 @@ Nx is a powerful open-source build system that provides tools and techniques for
- **Cache Locally & Remotely**: With [local](/features/cache-task-results) and [remote caching](/ci/features/remote-cache), Nx prevents unnecessary re-runs of tasks, saving you valuable dev time.
- **Split E2E Tests and Rerun Flaky Tests**: Nx [automatically splits](/ci/features/split-e2e-tasks) large e2e tests to distribute them across VMs. Nx can also automatically [identify and rerun flaky e2e tests](/ci/features/flaky-tasks).
- **Automate Dependency Updates**: if you leverage [Nx plugins](/concepts/nx-plugins) you gain additional features such as [code generation](/features/generate-code) and tools to [automatically upgrade](features/automate-updating-dependencies) your codebase and dependencies.
- **Make it Your Own**: Nx is highly customizable and extensible. Fine-tune it by [creating your own plugins](/extending-nx/intro/getting-started) and optionally [share them with the community](/extending-nx/tutorials/publish-plugin#publish-your-nx-plugin).
- **Make it Your Own**: Nx is highly customizable and extensible. Fine-tune it by [creating a plugin for your organization](/extending-nx/tutorials/organization-specific-plugin) or [creating a tooling plugin](/extending-nx/tutorials/tooling-plugin).
<!-- - **Monorepo and Single Projects**: Nx supports both, monorepos as well as single-project (standalone) workspaces. -->
@@ -86,7 +86,7 @@ The `targetName`, `ciTargetName`, `componentTestingTargetName`, and `open-cypres
### Splitting E2E tasks by file
The `@nx/cypress/plugin` will automatically split your e2e tasks by file. You can read more about this feature [here](/ci/features/split-e2e-tasks).
The `@nx/cypress/plugin` will automatically split your e2e tasks by file. You can read more about the Atomizer feature [here](/ci/features/split-e2e-tasks).
To enable e2e task splitting, make sure there is a `ciWebServerCommand` property set in your `cypress.config.ts` file. It will look something like this:
+19 -1
View File
@@ -68,7 +68,8 @@ The `@nx/gradle` is configured in the `plugins` array in `nx.json`.
"options": {
"testTargetName": "test",
"classesTargetName": "classes",
"buildTargetName": "build"
"buildTargetName": "build",
"ciTargetName": "test-ci"
}
}
]
@@ -77,6 +78,23 @@ The `@nx/gradle` is configured in the `plugins` array in `nx.json`.
Once a Gradle configuration file has been identified, the targets are created with the name you specify under `testTargetName`, `classesTargetName` or `buildTargetName` in the `nx.json` `plugins` array. The default names for the inferred targets are `test`, `classes` and `build`.
### Splitting E2E Tests
The `@nx/gradle` plugin will automatically split your e2e tasks by file if you provide a `ciTargetName`. You can read more about the Atomizer feature [here](/ci/features/split-e2e-tasks). This will create a target with that name which can be used in CI to run the tests for each file in a distributed fashion.
```json {% fileName="nx.json" highlightLines=[6] %}
{
"plugins": [
{
"plugin": "@nx/gradle",
"options": {
"ciTargetName": "test-ci"
}
}
]
}
```
## View Inferred Tasks
To view inferred tasks for a project, open the [project details view](/features/explore-graph#explore-projects-in-your-workspace) in Nx Console or run `nx show project my-project --web` in the command line.
+1 -1
View File
@@ -70,7 +70,7 @@ within the same workspace. In this case, you can configure the `@nx/jest/plugin`
### Splitting E2E Tests
If Jest is used to run E2E tests, you can enable [splitting the tasks](/ci/features/split-e2e-tasks) by file to get
improved caching, distribution, and retrying flaky tests. Enable this, by providing a `ciTargetName`. This will create a
improved caching, distribution, and retrying flaky tests. Enable this Atomizer feature by providing a `ciTargetName`. This will create a
target with that name which can be used in CI to run the tests for each file in a distributed fashion.
```json {% fileName="nx.json" %}
@@ -65,6 +65,24 @@ The `@nx/playwright/plugin` is configured in the `plugins` array in `nx.json`.
The `targetName` and `ciTargetName` options control the name of the inferred Playwright tasks. The default names are `e2e` and `e2e-ci`.
### Splitting E2E Tests
The `@nx/playwright/plugin` will automatically split your e2e tasks by file if you provide a `ciTargetName`. You can read more about the Atomizer feature [here](/ci/features/split-e2e-tasks). This will create a target with that name which can be used in CI to run the tests for each file in a distributed fashion.
```json {% fileName="nx.json" %}
{
"plugins": [
{
"plugin": "@nx/playwright/plugin",
"options": {
"targetName": "e2e",
"ciTargetName": "e2e-ci"
}
}
]
}
```
### Splitting E2E tasks by file
The `@nx/playwright/plugin` will automatically split your e2e tasks by file. You can read more about this feature [here](/ci/features/split-e2e-tasks).
-92
View File
@@ -1,92 +0,0 @@
# Create a Local Plugin
To get started with building a local Nx Plugin, install the `@nx/plugin` package and generate a plugin:
```shell
nx g @nx/plugin:plugin my-plugin --directory=tools/my-plugin
```
This will create a `my-plugin` project that contains all your plugin code and `my-plugin-e2e` for e2e tests.
> If you want to create a new workspace for your plugin, run `npx create-nx-plugin my-plugin`. This command will create a new workspace with `my-plugin` and `e2e` projects set up for you.
## Generator
To create a new generator run:
```shell
nx generate @nx/plugin:generator my-generator --directory="tools/my-plugin/src/generators/my-generator"
```
The new generator is located in `/src/generators/my-generator`. The `my-generator.ts` file contains the code that runs the generator. This generator creates a new project using a folder of template files.
For more information about this sample generator, read the [simple generator recipe](/extending-nx/recipes/local-generators).
### Generator options
The `schema.d.ts` file contains all the options that the generator supports. By default, it includes `directory`, `tags`, and `name` as the options. If more options need to be added, please update this file and the `schema.json` file.
{% callout type="note" title="More details" %}
The `schema.d.ts` file is used for type checking inside the implementation file. It should match the properties in `schema.json`.
{% /callout %}
### Generator Testing
The generator spec file includes boilerplate to help get started with testing. This includes setting up an empty workspace.
These tests should ensure that files within the tree (created with `createTreeWithEmptyWorkspace`) are in the correct place, and contain the right content.
Full E2Es are supported and will run everything on the file system like a user would.
## Executor
To create a new executor run:
```shell
nx generate @nx/plugin:executor my-executor --project=my-plugin
```
The new executor is located in `/src/executors/my-executor`. The `my-executor.ts` file contains the code that runs the executor. This executor emits a console log, but executors can compile code, deploy an app, publish to NPM and much more.
For more information about this sample executor, read the [simple executor recipe](/extending-nx/recipes/local-executors).
### Executor testing
The executor spec file contains boilerplate to run the default exported function from the executor.
These tests should make sure that the executor is executing and calling the functions that it relies on. Typically, unit tests are more useful for generators and e2e tests are more useful for executors.
## Testing your plugin
One of the biggest benefits that the Nx Plugin package provides is support for E2E and unit testing.
When the E2E app runs, a temporary E2E directory is created in the root of the workspace. This directory is a blank Nx workspace, and will have the plugin's built package installed locally.
### E2E Testing file
When the plugin is generated, a test file is created in the `my-plugin-e2e` app. Inside this test file, there is a disabled test that gives you a starting point for writing your own tests. To enable the test, change `xit` to `it`.
We'll go over a few parts of a test file below:
```typescript
beforeAll(() => {
ensureNxProject('my-plugin', 'dist/./.');
});
xit('should be able to build generated projects', async () => {
const name = 'proj';
const generator = 'PLACEHOLDER';
await runNxCommandAsync(`generate my-plugin:${generator} --name ${name}`);
expect(() => runNxCommand('build ${proj}')).not.toThrow();
expect(() => checkFilesExist(`dist/${name}/index.js`)).not.toThrow();
});
```
- The `ensureNxProject` is the function that will create the temporary directory. It takes two arguments, the plugin package name and the dist directory of when it's built.
- The `runNxCommandAsync` function will execute a `nx` command in the E2E directory.
There are additional functions that the `@nx/plugin/testing` package exports. Most of them are file utilities to manipulate and read files in the E2E directory.
## Using your Nx Plugin Locally
To use your plugin, simply list it in `nx.json` or use its generators and executors as you would for any other plugin. This could look like `nx g @my-org/my-plugin:lib` for generators or `"executor": "@my-org/my-plugin:build"` for executors. It should be usable in all of the same ways as published plugins in your local workspace immediately after generating it.
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+86 -36
View File
@@ -1,58 +1,108 @@
# Getting Started with Plugins
# Extending Nx with Plugins
Nx plugins contain [generators](/features/generate-code) and [executors](/concepts/executors-and-configurations) that extend the capabilities of an Nx workspace. They can be shared as npm packages or referenced locally within the same repo.
Nx's core functionality focuses on task running and understanding your project and task graph. Nx plugins leverage that functionality to enforce best practices, seamlessly integrate tooling and allow developers to get up and running quickly.
{% cards cols="4" %}
As your repository grows, you'll discover more reasons to create your own plugin
{% link-card title="Use a Plugin" url="#use-a-plugin" /%}
{% link-card title="Create a Plugin" url="#create-a-local-plugin" /%}
{% link-card title="Maintain a Published Plugin" url="#maintain-a-published-plugin" /%}
{% link-card title="Advanced Plugins" url="#advanced-plugins" /%}
- You can help encourage your coworkers to consistently follow best practices by creating [code generators](/features/generate-code) that are custom built for your repository.
- You can remove duplicate configuration and ensure accurate caching settings by writing your own [inferred tasks](/concepts/inferred-tasks).
- For organizations with multiple monorepos, you can encourage consistency across repositories by providing a repository [preset](/extending-nx/recipes/create-preset) and writing [migrations](/extending-nx/recipes/migration-generators) that will help keep every project in sync.
- You can write a plugin that integrates a tool or framework into Nx and then [share your plugin](/extending-nx/recipes/publish-plugin) with the broader community.
{% /cards %}
## Create Your Own Plugin
## Use a Plugin
Get started developing your own plugin with a few terminal commands:
Nx plugins help you scaffold new projects, pre-configure tooling, follow best practices, and modularize your codebase.
{% side-by-side %}
{% cards cols="3" %}
```shell {% title="Create a plugin in a new workspace" %}
npx create-nx-plugin my-plugin
```
{% card title="Browse Existing Plugins" description="Find a plugin to use" url="/plugin-registry" /%}
{% card title="Use Task Executors" description="Run operations on your code" url="/concepts/executors-and-configurations" /%}
{% card title="Generate Code" description="Create or modify code" url="/features/generate-code" /%}
```shell {% title="Add a plugin to an existing workspace" %}
npx nx add @nx/plugin
npx nx g plugin my-plugin
```
{% /cards %}
{% /side-by-side %}
## Create a Local Plugin
## Learn by Doing
Local plugins allow you to automate repository specific tasks and enforce best practices (e.g., generating projects or components, running third-party tools).
You can follow along with one of the step by step tutorials below that is focused on a particular use-case. These tutorials expect you to already have the following skills:
{% cards cols="3" %}
{% card title="Create a Plugin" description="Set up a new plugin" url="/extending-nx/tutorials/create-plugin" /%}
{% card title="Local Generators" description="Add a generator to your plugin" url="/extending-nx/recipes/local-generators" /%}
{% card title="Local Executors" description="Add an executor to your plugin" url="/extending-nx/recipes/local-executors" /%}
{% /cards %}
## Maintain a Published Plugin
If your plugin has functionality that would be useful in more than just your repo, you can publish it to npm and register it on the nx.dev site for others to find.
- [Run tasks](/features/run-tasks) with Nx and configure Nx to [infers tasks for you](/concepts/inferred-tasks)
- [Use code generators](/features/generate-code)
- Understand the [project graph](/features/explore-graph)
- Write [TypeScript](https://www.typescriptlang.org/) code
{% cards cols="2" %}
{% card title="Share Your Plugin" description="Submit your plugin to the Nx plugin registry" url="/extending-nx/tutorials/publish-plugin" /%}
{% card title="Migration Generators" description="Update repos when you introduce breaking changes" url="/extending-nx/recipes/migration-generators" /%}
{% link-card title="Enforce Best Practices in Your Repository" type="tutorial" url="/extending-nx/tutorials/organization-specific-plugin" icon="office" /%}
{% link-card title="Integrate a Tool Into an Nx Repository" type="tutorial" url="/extending-nx/tutorials/tooling-plugin" icon="tool" /%}
{% /cards %}
## Advanced Plugins
## Create Your First Code Generator
You can also hook into the way Nx works and modify it to suit your needs
Wire up a new generator with this terminal command:
{% cards cols="2" %}
```shell
npx nx g generator library-with-readme --directory=my-plugin/src/generators/library-with-readme
```
{% card title="Scaffold a New Workspace" description="Set up a new repo" url="/extending-nx/recipes/create-preset" /%}
{% card title="Project Graph Plugins" description="Modify the Nx graph" url="/extending-nx/recipes/project-graph-plugins" /%}
### Understand the Generator Functionality
{% /cards %}
This command will register the generator in the plugin's `generators.json` file and create some default generator code in the `library-with-readme` folder. The `libraryWithReadmeGenerator` function in the `generator.ts` file is where the generator functionality is defined.
```typescript {% fileName="my-plugin/src/generators/library-with-readme/generator.ts" %}
export async function libraryWithReadmeGenerator(
tree: Tree,
options: LibraryWithReadmeGeneratorSchema
) {
const projectRoot = `libs/${options.name}`;
addProjectConfiguration(tree, options.name, {
root: projectRoot,
projectType: 'library',
sourceRoot: `${projectRoot}/src`,
targets: {},
});
generateFiles(tree, path.join(__dirname, 'files'), projectRoot, options);
await formatFiles(tree);
}
```
This generator calls the following functions:
- `addProjectConfiguration` - Create a new project configured for TypeScript code.
- `generateFiles` - Create files in the new project based on the template files in the `files` folder.
- `formatFiles` - Format the newly created files with Prettier.
You can find more helper functions in the [Nx Devkit reference documentation](/nx-api/devkit/documents/nx_devkit).
### Create a README Template File
We can remove the generated `index.ts.template` file and add our own `README.md.template` file in the `files` folder.
```typescript {% fileName="my-plugin/src/generators/library-with-readme/files/README.md.template" %}
# <%= name %>
This was generated by the `library-with-readme` generator!
```
The template files that are used in the `generateFiles` function can inject variables and functionality using the EJS syntax. Our README template will replace `<%= name %>` with the name specified in the generator. Read more about the EJS syntax in the [creating files with a generator recipe](/extending-nx/recipes/creating-files).
### Run Your Generator
You can test your generator in dry-run mode with the following command:
```shell
npx nx g my-plugin:library-with-readme mylib --dry-run
```
If you're happy with the files that are generated, you can actually run the generator by removing the `--dry-run` flag.
## Next Steps
- [Browse the plugin registry](/plugin-registry) to find one that suits your needs.
- [Sign up for Nx Enterprise](/enterprise) to get dedicated support from Nx team members.
- [Collaborate on the Nx Discord](https://go.nx.dev/community) to work with other plugin authors.
@@ -0,0 +1,325 @@
# Enforce Organizational Best Practices with a Local Plugin
Every repository has a unique set of conventions and best practices that developers need to learn in order to write code that integrates well with the rest of the code base. It is important to document those best practices, but developers don't always read the documentation and even if they have read the documentation, they don't consistently follow the documentation every time they perform a task. Nx allows you to encode these best practices in code generators that have been tailored to your specific repository.
In this tutorial, we will create a generator that helps enforce the follow best practices:
- Every project in this repository should use Vitest for unit tests.
- Every project in this repository should be tagged with a `scope:*` tag that is chosen from the list of available scopes.
- Projects should be placed in folders that match the scope that they are assigned.
- Vitest should clear mocks before running tests.
## Get Started
Let's first create a new workspace with the `create-nx-workspace` command:
```shell
npx create-nx-workspace myorg --preset=react-integrated --ci=github
```
Then we , install the `@nx/plugin` package and generate a plugin:
```shell
npx nx add @nx/plugin
npx nx g @nx/plugin:plugin recommended --directory=tools/recommended
```
This will create a `recommended` project that contains all your plugin code.
## Create a Customized Library Generator
To create a new generator run:
```shell
npx nx generate @nx/plugin:generator library --directory="tools/recommended/src/generators/library"
```
The new generator is located in `tools/recommended/src/generators/library`. The `generator.ts` file contains the code that runs the generator. We can delete the `files` directory since we won't be using it and update the `generator.ts` file with the following code:
```ts {% fileName="tools/recommended/src/generators/library/generator.ts" %}
import { Tree } from '@nx/devkit';
import { Linter } from '@nx/eslint';
import { libraryGenerator as reactLibraryGenerator } from '@nx/react';
import { LibraryGeneratorSchema } from './schema';
export async function libraryGenerator(
tree: Tree,
options: LibraryGeneratorSchema
) {
const callbackAfterFilesUpdated = await reactLibraryGenerator(tree, {
...options,
projectNameAndRootFormat: 'as-provided',
linter: Linter.EsLint,
style: 'css',
unitTestRunner: 'vitest',
});
return callbackAfterFilesUpdated;
}
export default libraryGenerator;
```
Notice how this generator is calling the `@nx/react` plugin's `library` generator with a predetermined list of options. This helps developers to always create projects with the recommended settings.
We're returning the `callbackAfterFilesUpdated` function because the `@nx/react:library` generator sometimes needs to install packages from NPM after the file system has been updated by the generator. You can provide your own callback function instead, if you have tasks that rely on actual files being present.
To try out the generator in dry-run mode, use the following command:
```shell
npx nx g @myorg/recommended:library test-library --dry-run
```
Remove the `--dry-run` flag to actually create a new project.
### Add Generator Options
The `schema.d.ts` file contains all the options that the generator supports. By default, it includes only a `name` option. Let's add a directory option to pass on to the `@nx/react` generator.
{% tabs %}
{% tab label="schema.d.ts" %}
```ts {% fileName="tools/recommended/src/generators/library/schema.d.ts" %}
export interface LibraryGeneratorSchema {
name: string;
directory?: string;
}
```
{% /tab %}
{% tab label="schema.json" %}
```json {% fileName="tools/recommended/src/generators/library/schema.json" %}
{
"$schema": "https://json-schema.org/schema",
"$id": "Library",
"title": "",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "What name would you like to use?"
},
"directory": {
"type": "string",
"description": ""
}
},
"required": ["name"]
}
```
{% /tab %}
{% /tabs %}
{% callout type="note" title="More details" %}
The `schema.d.ts` file is used for type checking inside the implementation file. It should match the properties in `schema.json`.
{% /callout %}
The schema files not only provide structure to the CLI, but also allow [Nx Console](/getting-started/editor-setup) to show an accurate UI for the generator.
![Nx Console UI for the library generator](/shared/plugins/generator-options-ui.png)
Notice how we made the `description` argument optional in both the JSON and type files. If we call the generator without passing a directory, the project will be created in a directory with same name as the project. We can test the changes to the generator with the following command:
```shell
npx nx g @myorg/recommended:library test-library --directory=nested/directory/test-library --dry-run
```
### Choose a Scope
It can be helpful to tag a library with a scope that matches the application it should be associated with. With these tags in place, you can [set up rules](/features/enforce-module-boundaries) for how projects can depend on each other. For our repository, let's say the scopes can be `store`, `api` or `shared` and the default directory structure should match the chosen scope. We can update the generator to encourage developers to maintain this structure.
{% tabs %}
{% tab label="schema.d.ts" %}
```ts {% fileName="tools/recommended/src/generators/library/schema.d.ts" %}
export interface LibraryGeneratorSchema {
name: string;
scope: string;
directory?: string;
}
```
{% /tab %}
{% tab label="schema.json" %}
```json {% fileName="tools/recommended/src/generators/library/schema.json" %}
{
"$schema": "https://json-schema.org/schema",
"$id": "Library",
"title": "",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "What name would you like to use?"
},
"scope": {
"type": "string",
"description": "The scope of your library.",
"enum": ["api", "store", "shared"],
"x-prompt": {
"message": "What is the scope of this library?",
"type": "list",
"items": [
{
"value": "store",
"label": "store"
},
{
"value": "api",
"label": "api"
},
{
"value": "shared",
"label": "shared"
}
]
}
},
"directory": {
"type": "string",
"description": ""
}
},
"required": ["name"]
}
```
{% /tab %}
{% tab label="generator.ts" %}
```ts {% fileName="tools/recommended/src/generators/library/generator.ts" %}
import { Tree } from '@nx/devkit';
import { Linter } from '@nx/eslint';
import { libraryGenerator as reactLibraryGenerator } from '@nx/react';
import { LibraryGeneratorSchema } from './schema';
export async function libraryGenerator(
tree: Tree,
options: LibraryGeneratorSchema
) {
const callbackAfterFilesUpdated = await reactLibraryGenerator(tree, {
...options,
tags: `scope:${options.scope}`,
directory: options.directory || `${options.scope}/${options.name}`,
projectNameAndRootFormat: 'as-provided',
linter: Linter.EsLint,
style: 'css',
unitTestRunner: 'vitest',
});
return callbackAfterFilesUpdated;
}
export default libraryGenerator;
```
{% /tab %}
{% /tabs %}
We can check that the scope logic is being applied correctly by running the generator again and specifying a scope.
```shell
npx nx g @myorg/recommended:library test-library --scope=shared --dry-run
```
This should create the `test-library` in the `shared` folder.
## Configure Tasks
You can also use your Nx plugin to configure how your tasks are run. Usually, organization focused plugins configure tasks by modifying the configuration files for each project. If you have developed your own tooling scripts for your organization, you may want to create an executor or infer tasks, but that process is covered in more detail in the tooling plugin tutorial.
Let's update our library generator to set the `clearMocks` property to `true` in the `vitest` configuration. First we'll run the `reactLibraryGenerator` and then we'll modify the created files.
```ts {% fileName="tools/recommended/src/generators/library/generator.ts" %}
import { formatFiles, Tree, runTasksInSerial } from '@nx/devkit';
import { Linter } from '@nx/eslint';
import { libraryGenerator as reactLibraryGenerator } from '@nx/react';
import { LibraryGeneratorSchema } from './schema';
export async function libraryGenerator(
tree: Tree,
options: LibraryGeneratorSchema
) {
const directory = options.directory || `${options.scope}/${options.name}`;
const tasks = [];
tasks.push(
await reactLibraryGenerator(tree, {
...options,
tags: `scope:${options.scope}`,
directory,
projectNameAndRootFormat: 'as-provided',
linter: Linter.EsLint,
style: 'css',
unitTestRunner: 'vitest',
})
);
updateViteConfiguration(tree, directory);
await formatFiles(tree);
return runTasksInSerial(...tasks);
}
function updateViteConfiguration(tree, directory) {
// Read the vite configuration file
let viteConfiguration =
tree.read(`${directory}/vite.config.ts`)?.toString() || '';
// Modify the configuration
// This is done with a naive search and replace, but could be done in a more robust way using AST nodes.
viteConfiguration = viteConfiguration.replace(
`globals: true,`,
`globals: true,\n clearMocks:true,`
);
// Write the modified configuration back to the file
tree.write(`${directory}/vite.config.ts`, viteConfiguration);
}
export default libraryGenerator;
```
We updated the generator to use some new helper functions from the Nx devkit. Here are a few functions you may find useful. See the [full API reference](/nx-api/devkit/documents/nx_devkit) for all the options.
- [`runTasksInSerial`](/nx-api/devkit/documents/runTasksInSerial) - Allows you to collect many callbacks and return them all at the end of the generator.
- [`formatFiles`](/nx-api/devkit/documents/formatFiles) - Run Prettier on the repository
- [`readProjectConfiguration`](/nx-api/devkit/documents/readProjectConfiguration) - Get the calculated project configuration for a single project
- [`updateNxJson`](/nx-api/devkit/documents/updateNxJson) - Update the `nx.json` file
Now let's check to make sure that the `clearMocks` property is set correctly by the generator. First, we'll commit our changes so far. Then, we'll run the generator without the `--dry-run` flag so we can inspect the file contents.
```shell
git add .
git commit -am "library generator"
npx nx g @myorg/recommended:library store-test --scope=store
```
## Next Steps
Now that we have a working library generator, here are some more topics you may want to investigate.
- [Generate files](/extending-nx/recipes/creating-files) from EJS templates
- [Modify files](/extending-nx/recipes/modifying-files) with string replacement or AST transformations
## Encourage Adoption
Once you have a set of generators in place in your organization's plugin, the rest of the work is all communication. Let your developers know that the plugin is available and encourage them to use it. These are the most important points to communicate to your developers:
- Whenever there are multiple plugins that provide a generator with the same name, use the `recommended` version
- If there are repetitive or error prone processes that they identify, ask the plugin team to write a generator for that process
Now you can go through all the README files in the repository and replace any multiple step instructions with a single line calling a generator.
+428
View File
@@ -0,0 +1,428 @@
# Integrate a New Tool into an Nx Repository with a Tooling Plugin
Nx Plugins can be used to easily integrate a tool or framework into an Nx repository. If there is no plugin available for your favorite tool or framework, you can write your own.
In this tutorial, we'll create a plugin that helps to integrate the [Astro]() framework. `Astro` is a JavaScript web framework optimized for building fast, content-driven websites. We'll call our plugin `nx-astro`.
To create a plugin in a brand new repository, use the `create-nx-plugin` command:
```shell
npx create-nx-plugin nx-astro
```
Skip the `create-*` package prompt, since we won't be creating a preset.
## Understand Tooling Configuration Files
When integrating your tool into an Nx repository, you first need to have a clear understanding of how your tool works. Pay special attention to all the possible formats for configuration files, so that your plugin can process any valid configuration options.
For our `nx-astro` plugin, we'll read information from the `astro.config.mjs` or `astro.config.ts` file. We'll mainly be interested in the `srcDir`, `publicDir` and `outDir` properties specified in the `defineConfig` object. `srcDir` and `publicDir` define input files that are used in the build process and `outDir` defines what the build output will be created.
```js {% fileName="astro.config.mjs" %}
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
srcDir: './src',
publicDir: './public',
outDir: './dist',
});
```
## Create an Inferred Task
The easiest way for people integrate your tool into their repository is for them to use inferred tasks. When leveraging inferred tasks, all your users need to do is install your plugin and the tool configuration file to their projects. Your plugin will take care of registering tasks with Nx and setting up the correct caching settings.
Once the inferred task logic is written, we want to be able to automatically create a task for any project that has a `astro.config.*` file defined in the root of the project. We'll name the task based on our plugin configuration in the `nx.json` file:
```json {% fileName="nx.json" %}
{
"plugins": [
{
"plugin": "nx-astro",
"options": {
"buildTargetName": "build",
"devTargetName": "dev"
}
}
]
}
```
If the `astro.config.mjs` for a project looks like our example in the previous section, then the inferred configuration for the `build` task should look like this:
```json
{
"command": "astro build",
"cache": true,
"inputs": [
"{projectRoot}/astro.config.mjs",
"{projectRoot}/src/**/*",
"{projectRoot}/public/**/*",
{
"externalDependencies": ["astro"]
}
],
"outputs": ["{projectRoot}/dist"]
}
```
To create an inferred task, we need to export a `createNodesV2` function from the plugin's `index.ts` file. The entire file is shown below with inline comments to explain what is happening in each section.
```ts {% fileName="src/index.ts" %}
import {
CreateNodesV2,
TargetConfiguration,
createNodesFromFiles,
joinPathFragments,
readJsonFile,
} from '@nx/devkit';
import { readdirSync, readFileSync } from 'fs';
import { dirname, join, resolve } from 'path';
// Expected format of the plugin options defined in nx.json
export interface AstroPluginOptions {
buildTargetName?: string;
devTargetName?: string;
}
// File glob to find all the configuration files for this plugin
const astroConfigGlob = '**/astro.config.{mjs,ts}';
// Entry function that Nx calls to modify the graph
export const createNodesV2: CreateNodesV2<AstroPluginOptions> = [
astroConfigGlob,
async (configFiles, options, context) => {
return await createNodesFromFiles(
(configFile, options, context) =>
createNodesInternal(configFile, options, context),
configFiles,
options,
context
);
},
];
async function createNodesInternal(configFilePath, options, context) {
const projectRoot = dirname(configFilePath);
// Do not create a project if package.json or project.json isn't there.
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
if (
!siblingFiles.includes('package.json') &&
!siblingFiles.includes('project.json')
) {
return {};
}
// Contents of the astro config file
const astroConfigContent = readFileSync(
resolve(context.workspaceRoot, configFilePath)
).toString();
// Read config values using Regex.
// There are better ways to read config values, but this works for the tutorial
function getConfigValue(propertyName: string, defaultValue: string) {
const result = new RegExp(`${propertyName}: '(.*)'`).exec(
astroConfigContent
);
if (result && result[1]) {
return result[1];
}
return defaultValue;
}
const srcDir = getConfigValue('srcDir', './src');
const publicDir = getConfigValue('publicDir', './public');
const outDir = getConfigValue('outDir', './dist');
// Inferred task final output
const buildTarget: TargetConfiguration = {
command: `astro build`,
options: { cwd: projectRoot },
cache: true,
inputs: [
'{projectRoot}/astro.config.mjs',
joinPathFragments('{projectRoot}', srcDir, '**', '*'),
joinPathFragments('{projectRoot}', publicDir, '**', '*'),
{
externalDependencies: ['astro'],
},
],
outputs: [`{projectRoot}/${outDir}`],
};
const devTarget: TargetConfiguration = {
command: `astro dev`,
options: { cwd: projectRoot },
};
// Project configuration to be merged into the rest of the Nx configuration
return {
projects: {
[projectRoot]: {
targets: {
[options.buildTargetName]: buildTarget,
[options.devTargetName]: devTarget,
},
},
},
};
}
```
We'll test out this inferred task a little later in the tutorial.
Inferred tasks work well for getting users started using your tool quickly, but you can also provide users with [executors](/extending-nx/recipes/local-executors), which are another way of encapsulating a task script for easy use in an Nx workspace. Without inferred tasks, executors must be explicitly configured for each task.
## Create an Init Generator
You'll want to create generators to automate the common coding tasks for developers that use your tool. The most obvious coding task is the initial setup of the plugin. We'll create an `init` generator to automatically register the `nx-astro` plugin and start inferring tasks.
If you create a generator named `init`, Nx will automatically run that generator when someone installs your plugin with the `nx add nx-astro` command. This generator should provide a good default set up for using your plugin. In our case, we need to register the plugin in the `nx.json` file.
To create the generator run the following command:
```shell
npx nx g generator init --directory=src/generators/init
```
Then we can edit the `generator.ts` file to define the generator functionality:
```ts {% fileName="src/generators/init/generator.ts" %}
import { formatFiles, readNxJson, Tree, updateNxJson } from '@nx/devkit';
import { InitGeneratorSchema } from './schema';
export async function initGenerator(tree: Tree, options: InitGeneratorSchema) {
const nxJson = readNxJson(tree) || {};
const hasPlugin = nxJson.plugins?.some((p) =>
typeof p === 'string' ? p === 'nx-astro' : p.plugin === 'nx-astro'
);
if (!hasPlugin) {
if (!nxJson.plugins) {
nxJson.plugins = [];
}
nxJson.plugins = [
...nxJson.plugins,
{
plugin: 'nx-astro',
options: {
buildTargetName: 'build',
devTargetName: 'dev',
},
},
];
}
updateNxJson(tree, nxJson);
await formatFiles(tree);
}
export default initGenerator;
```
This will automatically add the plugin configuration to the `nx.json` file if the plugin is not already registered.
We need to remove the generated `name` option from the generator schema files so that the `init` generator can be executed without passing any arguments.
{% tabs %}
{% tab label="schema.d.ts" %}
```ts {% fileName="src/generators/init/schema.d.ts" %}
export interface InitGeneratorSchema {}
```
{% /tab %}
{% tab label="schema.json" %}
```json {% fileName="src/generators/init/schema.json" %}
{
"$schema": "https://json-schema.org/schema",
"$id": "Init",
"title": "",
"type": "object",
"properties": {},
"required": []
}
```
{% /tab %}
{% /tabs %}
## Create an Application Generator
Let's make one more generator to automatically create a simple Astro application. First we'll create the generator:
```shell
npx nx g generator application --directory=src/generators/application
```
Then we'll update the `generator.ts` file to define the generator functionality:
```ts {% fileName="src/generators/application/generator.ts" %}
import {
addProjectConfiguration,
formatFiles,
generateFiles,
Tree,
} from '@nx/devkit';
import * as path from 'path';
import { ApplicationGeneratorSchema } from './schema';
export async function applicationGenerator(
tree: Tree,
options: ApplicationGeneratorSchema
) {
const projectRoot = `${options.name}`;
addProjectConfiguration(tree, options.name, {
root: projectRoot,
projectType: 'application',
sourceRoot: `${projectRoot}/src`,
targets: {},
});
generateFiles(tree, path.join(__dirname, 'files'), projectRoot, options);
await formatFiles(tree);
}
export default applicationGenerator;
```
The `generateFiles` function will use the template files in the `files` folder to add files to the generated project.
{% tabs %}
{% tab label="package.json__templ__" %}
```json {% fileName="src/generators/application/files/package.json__templ__" %}
{
"name": "<%= name %>",
"dependencies": {}
}
```
{% /tab %}
{% tab label="astro.config.mjs" %}
```js {% fileName="src/generators/application/files/astro.config.mjs" %}
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({});
```
{% /tab %}
{% tab label="index.astro" %}
```{% fileName="src/generators/application/files/src/pages/index.astro" %}
---
// Welcome to Astro! Everything between these triple-dash code fences
// is your "component frontmatter". It never runs in the browser.
console.log('This runs in your terminal, not the browser!');
---
<!-- Below is your "component template." It's just HTML, but with
some magic sprinkled in to help you build great templates. -->
<html>
<body>
<h1>Hello, World!</h1>
</body>
</html>
<style>
h1 {
color: orange;
}
</style>
```
{% /tab %}
{% tab label="robots.txt" %}
```json {% fileName="src/generators/application/files/public/robots.txt" %}
# Example: Allow all bots to scan and index your site.
# Full syntax: https://developers.google.com/search/docs/advanced/robots/create-robots-txt
User-agent: *
Allow: /
```
{% /tab %}
{% /tabs %}
The generator options in the schema files can be left unchanged.
## Test Your Plugin
The plugin is generated with a default e2e test (`e2e/src/nx-astro.spec.ts`) that:
1. Launches a local npm registry with Verdaccio
2. Publishes the current version of the `nx-astro` plugin to the local registry
3. Creates an empty Nx workspace
4. Installs `nx-astro` in the Nx workspace
Let's update the e2e tests to make sure that the inferred tasks are working correctly. We'll update the `beforeAll` function to use `nx add` to add the `nx-astro` plugin and call our `application` generator.
```ts {% fileName="e2e/src/nx-astro.spec.ts" %}
beforeAll(() => {
projectDirectory = createTestProject();
// The plugin has been built and published to a local registry in the jest globalSetup
// Install the plugin built with the latest source code into the test repo
execSync('npx nx add nx-astro@e2e', {
cwd: projectDirectory,
stdio: 'inherit',
env: process.env,
});
execSync('npx nx g nx-astro:application my-lib', {
cwd: projectDirectory,
stdio: 'inherit',
env: process.env,
});
});
```
Now we can add a new test that verifies the inferred task configuration:
```ts {% fileName="e2e/src/nx-astro.spec.ts" %}
it('should infer tasks', () => {
const projectDetails = JSON.parse(
execSync('nx show project my-lib --json', {
cwd: projectDirectory,
}).toString()
);
expect(projectDetails).toMatchObject({
name: 'my-lib',
root: 'my-lib',
sourceRoot: 'my-lib/src',
targets: {
build: {
cache: true,
executor: 'nx:run-commands',
inputs: [
'{projectRoot}/astro.config.mjs',
'{projectRoot}/src/**/*',
'{projectRoot}/public/**/*',
{
externalDependencies: ['astro'],
},
],
options: {
command: 'astro build',
cwd: 'my-lib',
},
outputs: ['{projectRoot}/./dist'],
},
dev: {
executor: 'nx:run-commands',
options: {
command: 'astro dev',
cwd: 'my-lib',
},
},
},
});
});
```
## Next Steps
Now that you have a working plugin, here are a few other topics you may want to investigate:
- [Publish your Nx plugin](/extending-nx/recipes/publish-plugin) to npm and the Nx plugin registry
- [Write migration generators](/extending-nx/recipes/migration-generators) to automatically account for breaking changes
- [Create a preset](/extending-nx/recipes/create-preset) to scaffold out an entire new repository
@@ -0,0 +1,120 @@
# File Based Versioning ("Version Plans")
Tools such as Changesets and Beachball helped popularize the concept of tracking the desired semver version bump in a separate file on disk (which is committed to your repository alongside your code changes). This has the advantage of separating the desired bump from your git commits themselves, which can be very useful if you are not able to enforce that all contributors follow a strict commit message format ([e.g. Conventional Commits](/recipes/nx-release/automatically-version-with-conventional-commits)), or if you want multiple commits to be included in the same version bump and therefore not map commits 1:1 with changelog entries.
Nx release supports file based versioning as a first class use-case through a feature called "version plans". The idea behind the name is that you are creating a _plan_ to version; a plan which will be _applied_ sometime in the future when you actually invoke the `nx release` CLI or programmatic API. Therefore you can think about version plans as having two main processes: creating version plans and applying version plans. We will cover both in this recipe, but first we need to enable the feature itself.
## Enable Version Plans
To enable version plans as a feature in your workspace, set `release.versionPlans` to `true` in `nx.json`:
```jsonc
{
"release": {
"versionPlans": true
// other release config such as projects to include in releases etc
// projects: ["packages/**/*"]
// ...
}
}
```
You can also enable or disable this for specific release groups by setting the property at the group level if you don't want to apply it to all matching projects in your workspace.
## Create Version Plans
Version plan files live in the `.nx/version-plans/` directory within your workspace (which needs to be tracked by git, so ensure that you are not ignoring the whole `.nx` directory, and instead only the `.nx/workspace-data` and `.nx/cache` directories).
The files themselves are written in markdown (`.md` files) and contain Front Matter YAML metadata at the top of the file. The Front Matter YAML section is denoted via triple dashes `---` at the start and end of the section. For example:
```md {% fileName=".nx/version-plans/version-plan-1723732065047.md" %}
---
#
# FRONT MATTER YAML HERE
#
---
#
# Regular markdown here
#
```
We leverage the Front Matter YAML section to store a mapping of project or release group names to desired semver bump types. The general markdown section represents the description of the change(s) made that will be used in any relevant `CHANGELOG.md` files that are generated later at release time.
For example, the following Front Matter YAML section specifies that the `my-app` project should have a `minor` version bump and describes the changes (again, note that there are no constraints on the format of the description, it can contain multiple lines, paragraphs etc):
```md {% fileName=".nx/version-plans/version-plan-1723732065047.md" %}
---
my-app: minor
---
This is an awesome change!
A new paragraph describing the change in greater detail. All of this will be included in the CHANGELOG.md. All of this structure within the markdown section is optional and flexible.
```
Any number of different projects and different desired semver bumps can be combined within a single version plan file (which represents one change and therefore changelog entry, if applicable). For example:
```md {% fileName=".nx/version-plans/version-plan-1723732065047.md" %}
---
my-app: minor
my-lib: patch
release-group-a: major
---
One change that affects multiple projects and release groups.
```
The project or release group names specified in the Front Matter YAML section must match the names of the projects and/or release groups in your workspace. If a project or release group is not found, an error will be thrown when applying the version plan as part of running `nx release`.
Because these are just files, they can be created manually or by any custom scripts you may wish to write. They simply have to follow the guidance above around structure, location (`./.nx/version-plans/`) and naming (`.md` extension). The exact file name does not matter, it just needs to be unique within the `.nx/version-plans/` directory.
To make things easier, Nx release comes with a built in command to help you generate valid version plan files:
```sh
nx release plan
```
When you run this command you will receive a series of interactive prompts which guide you through the process of creating a version plan file. It will generate a unique name for you and ensure it is written to the correct location.
## Apply Version Plans at Release Time
Using version plans does not change how versioning, changelog generation and publishing is invoked, you can still use the `nx release` CLI or programmatic API as you would for any other versioning strategy.
The only difference is that Nx release will know to reference your version plan files as the source of truth for the desired version bumps. You still retain the same control around resolving the current version (disk vs registry vs git tags) however you want, and other configuration options around things like git operations are all still applicable.
When you run `nx release` or use the programmatic API, Nx will look for version plan files in the `.nx/version-plans/` directory and apply the desired version bumps to the projects and release groups specified in the Front Matter YAML section of each file. If a project or release group is not found, an error will be thrown and the release will not proceed. Once a particular version plan has been applied it will be deleted from the `.nx/version-plans/` directory so that it does not inadvertently get applied again in the future. The deleted file will be staged and committed alongside your other changed files that were modified directly as part of the release command (depending on your Nx release configuration).
## Ensure That Version Plans Exist for Relevant Changes
When making changes to your codebase and using version plans as your versioning strategy it is likely that you will want to ensure that a version plan file exists for the changes you are making.
Attempting to keep track of this manually as a part of pull request reviews can be error prone and time consuming, therefore Nx release provides a `nx release plan:check` command which can be used to ensure that a version plan file exists for the changes you are making.
```sh
nx release plan:check
```
Running this command will analyze the changed files (supporting the same options you may be familiar with from `nx affected`, such as `--base`, `--head`, `--files`, `--uncommitted`, etc) and then determine which projects have been "touched" as a result. Note that it is specifically touched projects, and not affected in this case, because only directly changed projects are relevant for versioning. The side-effects of versioning independently released dependents are handled by the release process itself (controllable via the `version.generatorOptions.updatedDependents` option).
Nx release will compare the touched projects to the projects and release groups that are specified in the version plan files in the `.nx/version-plans/` directory. If a version plan file does not exist, the command will print an error message and return a non-zero exit code, which can be used to fail CI builds or other automation.
By default, all files that have changed are considered, but you may not want all files under a project to require a version plan be created for them. For example, you may wish to ignore test only files from consideration from this check. The way you can achieve this is by setting version plans to be a configuration object instead of a boolean, and set the `"ignorePatternsForPlanCheck"` property to an array of glob patterns that should be ignored when checking for version plans. For example:
````jsonc
{
"release": {
"versionPlans": {
"ignorePatternsForPlanCheck": ["**/*.spec.ts"]
}
}
}
```
To see more details about the changed files that were detected and the filtering logic that was used to determine the ultimately changed projects behind the scenes, you can pass `--verbose` to the command:
```sh
nx release plan:check --verbose
````
+1 -1
View File
@@ -13,7 +13,7 @@ title="Develop a Nx Preset for your Nx Plugin"
At its core, a preset is a special [generator](/features/generate-code) that is shipped as part of an Nx Plugin package.
All first-party Nx presets are built into Nx itself, but you can [create your own plugin](/extending-nx/intro/getting-started) and create a generator with the magic name: `preset`. Once you've [published your plugin](/extending-nx/tutorials/publish-plugin) on npm, you can now run the `create-nx-workspace` command with the preset option set to the name of your published package.
All first-party Nx presets are built into Nx itself, but you can [create your own plugin](/extending-nx/intro/getting-started) and create a generator with the magic name: `preset`. Once you've [published your plugin](/extending-nx/tutorials/tooling-plugin) on npm, you can now run the `create-nx-workspace` command with the preset option set to the name of your published package.
To use a concrete example, let's look at the [`qwik-nx`](https://www.npmjs.com/package/qwik-nx) Nx community plugin. They include a [preset generator](https://github.com/qwikifiers/qwik-nx/tree/main/packages/qwik-nx/src/generators/preset) that you can use to create a new Nx workspace with Qwik support.
@@ -1,4 +1,4 @@
# Local Executors
# Write a Simple Executor
Creating Executors for your workspace standardizes scripts that are run during your development/building/deploying tasks in order to provide guidance in the terminal with `--help` and when invoking with [Nx Console](/getting-started/editor-setup)
@@ -6,7 +6,7 @@ This guide shows you how to create, run, and customize executors within your Nx
## Creating an executor
If you don't already have a local plugin, use Nx to generate one:
If you don't already have a plugin, use Nx to generate one:
```shell {% skipRescope=true %}
nx add @nx/plugin
@@ -1,45 +1,17 @@
# Maintain a Published Plugins
To create a plugin, see the [create a local plugin tutorial](/extending-nx/tutorials/create-plugin).
## Publish your Nx Plugin
# Publish your Nx Plugin
In order to use your plugin in other workspaces or share it with the community, you will need to publish it to an npm registry. To publish your plugin follow these steps:
1. `nx publish my-plugin --ver=1.0.0` which automatically builds `my-plugin`
1. `nx nx-release-publish nx-cfonts`
2. Follow the prompts from npm.
3. That's it!
{% callout type="warning" title="Version bump" %}
Currently you will have to modify the `package.json` version by yourself or with a tool.
{% /callout %}
After that, you can then install your plugin like any other npm package -
{% tabs %}
{% tab label="npm" %}
After that, you can then install your plugin like any other Nx plugin -
```shell
npm add -D @my-org/my-plugin
nx add nx-cfonts
```
{% /tab %}
{% tab label="yarn" %}
```shell
yarn add -D @my-org/my-plugin
```
{% /tab %}
{% tab label="pnpm" %}
```shell
pnpm add -D @my-org/my-plugin
```
{% /tab %}
{% /tabs %}
## List your Nx Plugin
Nx provides a utility (`nx list`) that lists both core and community plugins. You can submit your plugin to be added to this list, but it needs to meet a few criteria first:
@@ -72,7 +44,3 @@ Once those criteria are met, you can submit your plugin by following the steps b
> The `yarn submit-plugin` command automatically opens the GitHub pull request process with the correct template.
We will then verify the plugin, offer suggestions or merge the pull request!
## Write Migrations
Once other repos are using your plugin, it would help them if you write migrations to automatically update their configuration files whenever you make breaking changes. Read the [migration generators guide](/extending-nx/recipes/migration-generators) to find out how.
@@ -0,0 +1,204 @@
# Migrate to Inferred Tasks (Project Crystal)
In this recipe, you'll learn how to migrate an existing Nx workspace from using executors in `project.json` to using [inferred tasks](/concepts/inferred-tasks).
The main benefits of migrating to inferred tasks are
- reducing the amount of configuration needed in `project.json`
- inferring the correct cache settings based on the tool configuration files
- [splitting tasks (Atomizer)](/ci/features/split-e2e-tasks) for plugins that support it
{% youtube
src="https://youtu.be/wADNsVItnsM"
title="Project Crystal"
/%}
For the best experience, we recommend that you [migrate](/features/automate-updating-dependencies) to the latest Nx version before continuing. At minimum, you should be on Nx version 19.6.
```shell
npx nx migrate latest
```
## Migrate a Plugin
Most of the official plugins come with a `convert-to-inferred` generator. This generator will
- register the inference plugin in the `plugins` section of `nx.json`
- migrate executor options into the tool's configuration files (where applicable)
- clean up `project.json` to remove targets and options that are unnecessary
To get started, run `nx g convert-to-inferred`, and you'll be prompted to choose a plugin to migrate.
```text {% command="npx nx g convert-to-inferred" %}
? Which generator would you like to use? …
@nx/eslint:convert-to-inferred
@nx/playwright:convert-to-inferred
@nx/vite:convert-to-inferred
None of the above
```
{% callout type="note" title="Third-party plugins" %}
For third-party plugins that provide `convert-to-inferred` generators, you should pick the `None of the above` option and type in the name of the package manually. Alternatively, you can also provide the package explicitly with `nx g <plugin>:convert-to-inferred`.
{% /callout %}
We recommend that you migrate the plugins one at a time, and check that the configurations are correct before continuing to the next plugin. If you only want to try it on a single project, pass the `--project` option.
## Understand the Migration Process
The `convert-to-inferred` generator removes uses of executors from the corresponding plugin. For example, if `@nx/vite` is migrated, then uses of [`@nx/vite:build`](/nx-api/vite/executors/build), [`@nx/vite:dev-server`](/nx-api/vite/executors/dev-server), [`@nx/vite:preview-server`](/nx-api/vite/executors/preview-server), and [`@nx/vite:test`](/nx-api/vite/executors/test) executors will be removed.
Target and configuration names are maintained for each project in their `project.json` files. A target may be removed from `project.json` if everything is inferred--that is, options and configurations are not customized. To get the full project details (including all inferred tasks), run:
```shell
npx nx show project <project-name>
```
For example, if we migrated the `@nx/vite` plugin for a single app (i.e. `nx g @nx/vite:convert-to-inferred --project demo`), then running `nx show project demo` will show a screen similar to the following.
{% project-details title="Test" height="100px" %}
```json
{
"project": {
"name": "demo",
"data": {
"root": " apps/demo",
"projectType": "application",
"targets": {
"serve": {
"executor": "nx:run-commands",
"options": {
"command": "vite dev"
}
},
"build": {
"executor": "nx:run-commands",
"inputs": ["production", "^production"],
"outputs": ["{projectRoot}/dist"],
"options": {
"command": "vite build"
}
}
}
}
},
"sourceMap": {
"targets": ["apps/demo/vite.config.ts", "@nx/vite"],
"targets.serve": ["apps/demo/vite.config.ts", "@nx/vite"],
"targets.build": ["apps/demo/vite.config.ts", "@nx/vite"]
}
}
```
{% /project-details %}
You'll notice that the `serve` and `build` tasks are running the [Vite CLI](https://vitejs.dev/guide/cli.html) and there are no references to Nx executors. Since the targets directly invoke the Vite CLI, any options that may be passed to it can be passed via Nx commands. e.g. `nx serve demo --cors --port 8888` enables CORs and uses port `8888` using [Vite CLI options](https://vitejs.dev/guide/cli.html#options)
The same CLI setup applies to other plugins as well.
- `@nx/cypess` calls the [Cypress CLI](https://docs.cypress.io/guides/guides/command-line)
- `@nx/playwright` calls the [Playwright CLI](https://playwright.dev/docs/test-cli)
- `@nx/webpack` calls the [Webpack CLI](https://webpack.js.org/api/cli/)
- etc.
Read the recipe on [passing args to commands](/recipes/running-tasks/pass-args-to-commands) for more information.
### Configuration File Changes
There may also be changes to the configuration files used by the underlying tool. The changes come with comments to explain them, and may also provide next steps for you to take. One common change is to add support for different configuration options. For example, if we have an existing Vite app with the following build target:
```json {% fileName="project.json" %}
"build": {
"executor": "@nx/vite:build",
"options": {
"mode": "development"
},
"defaultConfiguration": "production",
"configurations": {
"development": {},
"production": {},
"ci": {}
}
}
```
Where we have `development`, `production`, and `ci` configurations. Then running `nx g @nx/vite:convert-to-inferred` will result in these lines added to `vite.config.ts`.
```ts {% fileName="vite.config.ts" highlightLines=["6-15"] %}
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
// These options were migrated by @nx/vite:convert-to-inferred from the project.json file.
const configValues = { default: {}, development: {}, production: {}, ci: {} };
// Determine the correct configValue to use based on the configuration
const nxConfiguration = process.env.NX_TASK_TARGET_CONFIGURATION ?? 'default';
const options = {
...configValues.default,
...(configValues[nxConfiguration] ?? {}),
};
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/apps/demo',
// ...
});
```
The configuration changes ensure that passing `--configuration` still work for the target. Differences in options can be added to the `configValues` object, and the right value is determined using the `NX_TASK_TARGET_CONFIGURATION` [environment variable](/reference/environment-variables). Again, there may be other types of changes so read the comments to understand them.
### Register the Plugin with Nx
Lastly, you can inspect the `nx.json` file to see a new `plugins` entry. For `@nx/vite`, there should be an entry like this:
```json {% fileName="nx.json" %}
{
"plugin": "@nx/vite/plugin",
"options": {
"buildTargetName": "build",
"serveTargetName": "serve",
"previewTargetName": "preview",
"testTargetName": "test",
"serveStaticTargetName": "serve-static"
}
}
```
You may change the target name options to change how Nx adds them to the project. For example, if you use `"serveTargetName": "dev"` then you would run `nx dev demo` rather than `nx serve demo` for your Vite project.
## Verify the Migration
The migrations maintain the same targets and configurations for each project, thus to verify it you should run the affected targets.
For example
- for `@nx/vite` you should check the `build`, `serve`, and `test` targets
- for `@nx/playwright` you should check the `e2e` targets
- for `@nx/eslint` you should check the `lint` target
- etc.
Remember that the target names are defined in the plugin configuration in `nx.json`.
Make sure that the tasks are all passing before migrating another plugin.
## Enable Atomizer (task splitting)
These plugins come with the [Atomizer](/ci/features/split-e2e-tasks) feature.
- `@nx/cypress`
- `@nx/jest`
- `@nx/gradle`
- `@nx/playwright`
The Atomizer splits potentially slow tasks into separate tasks per file. This feature along with [task distribution](/ci/features/distribute-task-execution) can speed up CI by distributing the split tasks among many agents.
To enable Atomizer, make sure that you are [connected to Nx Cloud](/ci/intro/connect-to-nx-cloud), and that you have distribution enabled in CI. Some plugins require extra configuration to enable Atomizer, so check the [individual plugin documentation page](/nx-api) for more details.
{% call-to-action title="Connect to Nx Cloud" icon="nxcloud" description="Enable task distribution and Atomizer" url="/ci/intro/connect-to-nx-cloud" /%}
## Troubleshooting
If you run into any issues during the migration, refer to the [troubleshooting guide](/troubleshooting/convert-to-inferred).
+10 -7
View File
@@ -64,6 +64,7 @@
- [Reduce Repetitive Configuration](/recipes/running-tasks/reduce-repetitive-configuration)
- [Change Cache Location](/recipes/running-tasks/change-cache-location)
- [Skip Task Caching](/recipes/running-tasks/skipping-cache)
- [Migrate to Inferred Tasks (Project Crystal)](/recipes/running-tasks/convert-to-inferred)
- [Adopting Nx](/recipes/adopting-nx)
- [NPM/Yarn/PNPM workspaces](/recipes/adopting-nx/adding-to-monorepo)
- [Migrate From Turborepo](/recipes/adopting-nx/from-turborepo)
@@ -160,6 +161,7 @@
- [Release Projects Independently](/recipes/nx-release/release-projects-independently)
- [Automatically Version with Conventional Commits](/recipes/nx-release/automatically-version-with-conventional-commits)
- [Customize Conventional Commit Types](/recipes/nx-release/customize-conventional-commit-types)
- [File Based Versioning (Version Plans)](/recipes/nx-release/file-based-versioning-version-plans)
- [Configure Custom Registries](/recipes/nx-release/configure-custom-registries)
- [Publish in CI/CD](/recipes/nx-release/publish-in-ci-cd)
- [Automate GitHub Releases](/recipes/nx-release/automate-github-releases)
@@ -226,22 +228,23 @@
- Extending-nx
- [Intro](/extending-nx/intro)
- [Getting Started with Plugins](/extending-nx/intro/getting-started)
- [5 Min Tutorials](/extending-nx/tutorials)
- [Create a Local Plugin](/extending-nx/tutorials/create-plugin)
- [Maintain a Published Plugin](/extending-nx/tutorials/publish-plugin)
- [Extending Nx with Plugins](/extending-nx/intro/getting-started)
- [Tutorials](/extending-nx/tutorials)
- [Enforce Organizational Best Practices](/extending-nx/tutorials/organization-specific-plugin)
- [Create a Tooling Plugin](/extending-nx/tutorials/tooling-plugin)
- [Recipes](/extending-nx/recipes)
- [Write a Simple Executor](/extending-nx/recipes/local-executors)
- [Compose Executors](/extending-nx/recipes/compose-executors)
- [Write a Simple Generator](/extending-nx/recipes/local-generators)
- [Compose Generators](/extending-nx/recipes/composing-generators)
- [Provide Options for Generators](/extending-nx/recipes/generator-options)
- [Create Files](/extending-nx/recipes/creating-files)
- [Modify Files](/extending-nx/recipes/modifying-files)
- [Write a Migration](/extending-nx/recipes/migration-generators)
- [Write a Simple Executor](/extending-nx/recipes/local-executors)
- [Compose Executors](/extending-nx/recipes/compose-executors)
- [Create a Preset](/extending-nx/recipes/create-preset)
- [Create an Install Package](/extending-nx/recipes/create-install-package)
- [Modify the Project Graph](/extending-nx/recipes/project-graph-plugins)
- [Infer Tasks or Projects](/extending-nx/recipes/project-graph-plugins)
- [Publish a Plugin](/extending-nx/recipes/publish-plugin)
- Ci
+10 -2
View File
@@ -478,13 +478,21 @@ describe('Linter', () => {
`import { names } from '@nx/devkit';\n\n` +
content.replace(/=> .*;/, `=> names('${mylib}').className;`)
);
// intentionally set an obsolete dependency
updateJson(`libs/${mylib}/package.json`, (json) => {
json.dependencies['@nx/js'] = nxVersion;
return json;
});
// output should now report missing dependency
// output should now report missing dependency and obsolete dependency
out = runCLI(`lint ${mylib}`, { silenceError: true });
expect(out).toContain('they are missing');
expect(out).toContain('@nx/devkit');
expect(out).toContain(
`The "@nx/js" package is not used by "${mylib}" project`
);
// should fix the missing dependency issue
// should fix the missing and obsolete dependency issues
out = runCLI(`lint ${mylib} --fix`, { silenceError: true });
expect(out).toContain(
`Successfully ran target lint for project ${mylib}`
+1
View File
@@ -31,6 +31,7 @@
}
},
"build-client": {
"dependsOn": ["nx:build-native"],
"configurations": {
"dev": {},
"nx-console": {},
+42 -1
View File
@@ -54,7 +54,9 @@ export type Framework =
| 'planetscale'
| 'mongodb'
| 'mfe'
| 'eslint';
| 'eslint'
| 'office'
| 'tool';
export const frameworkIcons: Record<
Framework,
@@ -1842,4 +1844,43 @@ export const frameworkIcons: Record<
</svg>
),
},
office: {
image: (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M2.25 21h19.5m-18-18v18m10.5-18v18m6-13.5V21M6.75 6.75h.75m-.75 3h.75m-.75 3h.75m3-6h.75m-.75 3h.75m-.75 3h.75M6.75 21v-3.375c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21M3 3h12m-.75 4.5H21m-3.75 3.75h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008v-.008Z"
/>
</svg>
),
},
tool: {
image: (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M21.75 6.75a4.5 4.5 0 0 1-4.884 4.484c-1.076-.091-2.264.071-2.95.904l-7.152 8.684a2.548 2.548 0 1 1-3.586-3.586l8.684-7.152c.833-.686.995-1.874.904-2.95a4.5 4.5 0 0 1 6.336-4.486l-3.276 3.276a3.004 3.004 0 0 0 2.25 2.25l3.276-3.276c.256.565.398 1.192.398 1.852Z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M4.867 19.125h.008v.008h-.008v-.008Z"
/>
</svg>
),
},
};
@@ -1,4 +1,4 @@
import { readFileSync, readdirSync, accessSync, constants } from 'fs';
import { readFileSync, accessSync, constants } from 'fs';
import { join, basename, parse, resolve } from 'path';
import { extractFrontmatter } from '@nx/nx-dev/ui-markdoc';
import { sortPosts } from './blog.util';
@@ -19,7 +19,6 @@ export class BlogApi {
throw new Error('public blog root cannot be undefined');
}
}
async getBlogTags(): Promise<string[]> {
const blogs = await this.getBlogs();
const tags = new Set<string>();
@@ -28,8 +27,15 @@ export class BlogApi {
});
return Array.from(tags);
}
async getBlogs(
filterFn?: (post: BlogPostDataEntry) => boolean
): Promise<BlogPostDataEntry[]> {
return await this.getAllBlogs(filterFn);
}
async getBlogs(): Promise<BlogPostDataEntry[]> {
async getAllBlogs(
filterFn?: (post: BlogPostDataEntry) => boolean
): Promise<BlogPostDataEntry[]> {
const files: string[] = await readdir(this.options.blogRoot);
const authors = JSON.parse(
readFileSync(join(this.options.blogRoot, 'authors.json'), 'utf8')
@@ -62,66 +68,22 @@ export class BlogApi {
ogImageType: type,
filePath,
slug,
podcastYoutubeId: frontmatter.podcastYoutubeId,
podcastSpotifyId: frontmatter.podcastSpotifyId,
podcastIHeartUrl: frontmatter.podcastIHeartUrl,
podcastAppleUrl: frontmatter.podcastAppleUrl,
podcastAmazonUrl: frontmatter.podcastAmazonUrl,
};
if (!frontmatter.draft || process.env.NODE_ENV === 'development') {
const isDevelopment = process.env.NODE_ENV === 'development';
const shouldIncludePost = !frontmatter.draft || isDevelopment;
if (shouldIncludePost && (!filterFn || filterFn(post))) {
allPosts.push(post);
}
}
return sortPosts(allPosts);
}
getBlogPosts(): BlogPostDataEntry[] {
const files: string[] = readdirSync(this.options.blogRoot);
const authors = JSON.parse(
readFileSync(join(this.options.blogRoot, 'authors.json'), 'utf8')
);
const allPosts: BlogPostDataEntry[] = [];
for (const file of files) {
const filePath = join(this.options.blogRoot, file);
// filter out directories (e.g. images)
if (!filePath.endsWith('.md')) continue;
const content = readFileSync(filePath, 'utf8');
const frontmatter = extractFrontmatter(content);
const slug = this.calculateSlug(filePath, frontmatter);
const { image, type } = this.determineOgImage(frontmatter.cover_image);
const post = {
content,
title: frontmatter.title ?? null,
description: frontmatter.description ?? null,
authors: authors.filter((author) =>
frontmatter.authors.includes(author.name)
),
date: this.calculateDate(file, frontmatter),
cover_image: frontmatter.cover_image
? `/documentation${frontmatter.cover_image}` // Match the prefix used by markdown parser
: null,
tags: frontmatter.tags ?? [],
reposts: frontmatter.reposts ?? [],
pinned: frontmatter.pinned ?? false,
ogImage: image,
ogImageType: type,
filePath,
slug,
};
if (!frontmatter.draft || process.env.NODE_ENV === 'development') {
allPosts.push(post);
}
}
return sortPosts(allPosts);
}
getBlogPost(slug: string): BlogPostDataEntry {
const blogs = this.getBlogPosts();
const blog = blogs.find((b) => b.slug === slug);
if (!blog) {
throw new Error(`Could not find blog post with slug: ${slug}`);
}
return blog;
}
// Optimize this so we don't read the FS multiple times
async getBlogPostBySlug(
slug: string | null
@@ -171,6 +133,7 @@ export class BlogApi {
if (!imagePath) {
return { image: defaultImage, type: defaultType };
}
const { ext } = parse(imagePath);
if (!allowedExtensions.includes(ext)) {
@@ -190,10 +153,14 @@ export class BlogApi {
}
return {
image: imagePath.replace(ext, foundExt),
image: join('documentation', imagePath.replace(ext, foundExt)),
type: foundExt.replace('.', ''),
};
}
return { image: imagePath, type: ext.replace('.', '') };
return {
image: join('documentation', imagePath),
type: ext.replace('.', ''),
};
}
}
@@ -11,8 +11,11 @@ export type BlogPostDataEntry = {
pinned?: boolean;
filePath: string;
slug: string;
ogImage: string;
ogImageType: string;
podcastYoutubeId?: string;
podcastSpotifyId?: string;
podcastAmazonUrl?: string;
podcastAppleUrl?: string;
podcastIHeartUrl?: string;
};
export type BlogAuthor = {
@@ -0,0 +1,16 @@
import { BlogApi } from './blog.api';
import { PodcastDataEntry } from './podcast.model';
export class PodcastApi {
_blogApi: BlogApi;
constructor(options: { blogApi: BlogApi }) {
this._blogApi = options.blogApi;
}
async getPodcastBlogs(): Promise<PodcastDataEntry[]> {
return await this._blogApi.getBlogs((post) =>
post.tags.map((t) => t.toLowerCase()).includes('podcast')
);
}
}
@@ -0,0 +1,5 @@
import { BlogPostDataEntry } from './blog.model';
export interface PodcastDataEntry extends BlogPostDataEntry {
duration?: string;
}
@@ -4,3 +4,5 @@ export * from './lib/blog.util';
export * from './lib/blog.api';
export * from './lib/blog.model';
export * from './lib/tags.api';
export * from './lib/podcast.model';
export * from './lib/podcast.api';
+1 -1
View File
@@ -26,7 +26,7 @@ export const metadata: Metadata = {
},
};
async function getBlogs() {
return await blogApi.getBlogPosts();
return await blogApi.getBlogs();
}
async function getBlogTags() {
+39
View File
@@ -0,0 +1,39 @@
import { Metadata } from 'next';
import { podcastApi } from '../../lib/podcast.api';
import { DefaultLayout } from '@nx/nx-dev/ui-common';
import { Hero, PodcastList } from '@nx/nx-dev/ui-podcast';
export const metadata: Metadata = {
title: 'Nx Podcast - Updates from the Nx & Nx Cloud team',
description: 'Latest podcasts from the Nx & Nx Cloud core team',
openGraph: {
url: 'https://nx.dev/podcast',
title: 'Nx Podcast - Updates from the Nx & Nx Cloud team',
description:
'Stay updated with the latest podcasts from the Nx & Nx Cloud team.',
images: [
{
url: 'https://nx.dev/socials/nx-media.png',
width: 800,
height: 421,
alt: 'Nx: Smart Monorepos · Fast CI',
type: 'image/jpeg',
},
],
siteName: 'NxDev',
type: 'website',
},
};
async function getPodcasts() {
return await podcastApi.getPodcastBlogs();
}
export default async function Page() {
const podcasts = await getPodcasts();
return (
<DefaultLayout>
<Hero />
<PodcastList podcasts={podcasts} />
</DefaultLayout>
);
}
+4
View File
@@ -0,0 +1,4 @@
import { blogApi } from './blog.api';
import { PodcastApi } from '@nx/nx-dev/data-access-documents/node-only';
export const podcastApi = new PodcastApi({ blogApi });
+3 -27
View File
@@ -4,13 +4,12 @@ import { ProcessedDocument, RelatedDocument } from '@nx/nx-dev/models-document';
import { MenuItem } from '@nx/nx-dev/models-menu';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { GetStaticPaths, GetStaticProps } from 'next';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { menusApi } from '../lib/menus.api';
import { useNavToggle } from '../lib/navigation-toggle.effect';
import { nxDocumentationApi } from '../lib/nx.api';
import { tagsApi } from '../lib/tags.api';
import { fetchGithubStarCount } from '../lib/githubStars.api';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function NxDocumentation({
document,
@@ -23,25 +22,7 @@ export default function NxDocumentation({
relatedDocuments: RelatedDocument[];
widgetData: { githubStarsCount: number };
}) {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const menuWithSections = {
sections: [getBasicNxSection(menu)],
@@ -62,18 +43,13 @@ export default function NxDocumentation({
toggleNav={toggleNav}
navIsOpen={navIsOpen}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<DocViewer
document={document}
relatedDocuments={relatedDocuments}
widgetData={widgetData}
/>
</div>
</ScrollableContent>
</main>
</div>
);
+1 -1
View File
@@ -129,7 +129,7 @@ export default async function handler(request: NextRequest) {
const response: Stream<OpenAI.Chat.Completions.ChatCompletionChunk> =
await openai.chat.completions.create({
model: 'gpt-3.5-turbo-16k',
model: 'gpt-4o-mini',
messages: chatMessages,
temperature: 0,
stream: true,
+3 -27
View File
@@ -4,13 +4,12 @@ import { ProcessedDocument, RelatedDocument } from '@nx/nx-dev/models-document';
import { Menu, MenuItem } from '@nx/nx-dev/models-menu';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { GetStaticPaths, GetStaticProps } from 'next';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { ciApi } from '../../lib/ci.api';
import { menusApi } from '../../lib/menus.api';
import { useNavToggle } from '../../lib/navigation-toggle.effect';
import { tagsApi } from '../../lib/tags.api';
import { fetchGithubStarCount } from '../../lib/githubStars.api';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function Pages({
document,
@@ -23,25 +22,7 @@ export default function Pages({
relatedDocuments: RelatedDocument[];
widgetData: { githubStarsCount: number };
}) {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: {
document: ProcessedDocument;
@@ -70,18 +51,13 @@ export default function Pages({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<DocViewer
document={vm.document}
relatedDocuments={vm.relatedDocuments}
widgetData={widgetData}
/>
</div>
</ScrollableContent>
</main>
</div>
);
+3 -27
View File
@@ -4,13 +4,12 @@ import { ProcessedDocument, RelatedDocument } from '@nx/nx-dev/models-document';
import { Menu, MenuItem } from '@nx/nx-dev/models-menu';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { GetStaticProps } from 'next';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { ciApi } from '../../lib/ci.api';
import { menusApi } from '../../lib/menus.api';
import { useNavToggle } from '../../lib/navigation-toggle.effect';
import { tagsApi } from '../../lib/tags.api';
import { fetchGithubStarCount } from '../../lib/githubStars.api';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function CloudRoot({
document,
@@ -23,25 +22,7 @@ export default function CloudRoot({
relatedDocuments: RelatedDocument[];
widgetData: { githubStarsCount: number };
}) {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: {
document: ProcessedDocument;
@@ -70,18 +51,13 @@ export default function CloudRoot({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<DocViewer
document={document}
relatedDocuments={vm.relatedDocuments}
widgetData={widgetData}
/>
</div>
</ScrollableContent>
</main>
</div>
);
@@ -11,6 +11,7 @@ import { useNavToggle } from '../../lib/navigation-toggle.effect';
import { nxPluginsApi } from '../../lib/plugins.api';
import { tagsApi } from '../../lib/tags.api';
import { fetchGithubStarCount } from '../../lib/githubStars.api';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function Pages({
document,
@@ -23,25 +24,7 @@ export default function Pages({
relatedDocuments: RelatedDocument[];
widgetData: { githubStarsCount: number };
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: {
document: ProcessedDocument;
@@ -70,18 +53,13 @@ export default function Pages({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<DocViewer
document={vm.document}
relatedDocuments={vm.relatedDocuments}
widgetData={widgetData}
/>
</div>
</ScrollableContent>
</main>
</div>
);
+3 -27
View File
@@ -4,13 +4,12 @@ import { ProcessedDocument, RelatedDocument } from '@nx/nx-dev/models-document';
import { Menu, MenuItem } from '@nx/nx-dev/models-menu';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { GetStaticProps } from 'next';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { menusApi } from '../../lib/menus.api';
import { useNavToggle } from '../../lib/navigation-toggle.effect';
import { nxPluginsApi } from '../../lib/plugins.api';
import { tagsApi } from '../../lib/tags.api';
import { fetchGithubStarCount } from '../../lib/githubStars.api';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function PluginsRoot({
document,
@@ -23,25 +22,7 @@ export default function PluginsRoot({
relatedDocuments: RelatedDocument[];
widgetData: { githubStarsCount: number };
}) {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: {
document: ProcessedDocument;
@@ -70,18 +51,13 @@ export default function PluginsRoot({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<DocViewer
document={document}
relatedDocuments={vm.relatedDocuments}
widgetData={widgetData}
/>
</div>
</ScrollableContent>
</main>
</div>
);
@@ -7,13 +7,12 @@ import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { GetStaticPaths } from 'next';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { menusApi } from '../../../../lib/menus.api';
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
import { nxPackagesApi } from '../../../../lib/packages.api';
import { tagsApi } from '../../../../lib/tags.api';
import { fetchGithubStarCount } from '../../../../lib/githubStars.api';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function PackageDocument({
document,
@@ -27,25 +26,7 @@ export default function PackageDocument({
relatedDocuments: RelatedDocument[];
widgetData: { githubStarsCount: number };
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: {
document: ProcessedDocument;
@@ -77,18 +58,13 @@ export default function PackageDocument({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<DocViewer
document={vm.document}
relatedDocuments={vm.relatedDocuments}
widgetData={widgetData}
/>
</div>
</ScrollableContent>
</main>
</div>
);
@@ -4,12 +4,11 @@ import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { GetStaticPaths } from 'next';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { PackageSchemaSubList } from '@nx/nx-dev/feature-package-schema-viewer/src/lib/package-schema-sub-list';
import { menusApi } from '../../../../lib/menus.api';
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
import { nxPackagesApi } from '../../../../lib/packages.api';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function DocumentsIndex({
menu,
@@ -18,25 +17,7 @@ export default function DocumentsIndex({
menu: MenuItem[];
pkg: ProcessedPackageMetadata;
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
menu: {
@@ -69,14 +50,9 @@ export default function DocumentsIndex({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<PackageSchemaSubList pkg={vm.package} type={'document'} />
</div>
</ScrollableContent>
</main>
</div>
);
@@ -92,6 +68,7 @@ export const getStaticPaths: GetStaticPaths = () => {
fallback: 'blocking',
};
};
export async function getStaticProps({
params,
}: {
@@ -8,11 +8,10 @@ import {
} from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { GetStaticPaths } from 'next';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { menusApi } from '../../../../lib/menus.api';
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
import { nxPackagesApi } from '../../../../lib/packages.api';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function PackageExecutor({
menu,
@@ -23,25 +22,7 @@ export default function PackageExecutor({
pkg: ProcessedPackageMetadata;
schema: SchemaMetadata;
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: {
menu: Menu;
@@ -79,14 +60,9 @@ export default function PackageExecutor({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
</div>
</ScrollableContent>
</main>
</div>
);
@@ -124,6 +100,7 @@ function getData(
menu: menusApi.getMenu('nx-api', 'nx-api'),
};
}
export async function getStaticProps({
params,
}: {
@@ -4,12 +4,11 @@ import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { GetStaticPaths } from 'next';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { PackageSchemaSubList } from '@nx/nx-dev/feature-package-schema-viewer/src/lib/package-schema-sub-list';
import { menusApi } from '../../../../lib/menus.api';
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
import { nxPackagesApi } from '../../../../lib/packages.api';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function ExecutorsIndex({
menu,
@@ -18,25 +17,7 @@ export default function ExecutorsIndex({
menu: MenuItem[];
pkg: ProcessedPackageMetadata;
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
menu: {
@@ -69,14 +50,9 @@ export default function ExecutorsIndex({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<PackageSchemaSubList pkg={vm.package} type={'executor'} />
</div>
</ScrollableContent>
</main>
</div>
);
@@ -92,6 +68,7 @@ export const getStaticPaths: GetStaticPaths = () => {
fallback: 'blocking',
};
};
export async function getStaticProps({
params,
}: {
@@ -8,11 +8,10 @@ import {
} from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { GetStaticPaths } from 'next';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { menusApi } from '../../../../lib/menus.api';
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
import { nxPackagesApi } from '../../../../lib/packages.api';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function PackageGenerator({
menu,
@@ -23,26 +22,7 @@ export default function PackageGenerator({
pkg: ProcessedPackageMetadata;
schema: SchemaMetadata;
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: {
menu: Menu;
package: ProcessedPackageMetadata;
@@ -79,14 +59,9 @@ export default function PackageGenerator({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
</div>
</ScrollableContent>
</main>
</div>
);
@@ -124,6 +99,7 @@ function getData(
menu: menusApi.getMenu('nx-api', 'nx-api'),
};
}
export async function getStaticProps({
params,
}: {
@@ -4,12 +4,11 @@ import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { GetStaticPaths } from 'next';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { PackageSchemaSubList } from '@nx/nx-dev/feature-package-schema-viewer/src/lib/package-schema-sub-list';
import { menusApi } from '../../../../lib/menus.api';
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
import { nxPackagesApi } from '../../../../lib/packages.api';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function GeneratorsIndex({
menu,
@@ -18,25 +17,7 @@ export default function GeneratorsIndex({
menu: MenuItem[];
pkg: ProcessedPackageMetadata;
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
menu: {
@@ -69,14 +50,9 @@ export default function GeneratorsIndex({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<PackageSchemaSubList pkg={vm.package} type={'generator'} />
</div>
</ScrollableContent>
</main>
</div>
);
@@ -92,6 +68,7 @@ export const getStaticPaths: GetStaticPaths = () => {
fallback: 'blocking',
};
};
export async function getStaticProps({
params,
}: {
+4 -27
View File
@@ -6,12 +6,11 @@ import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { GetStaticPaths } from 'next';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { menusApi } from '../../../lib/menus.api';
import { useNavToggle } from '../../../lib/navigation-toggle.effect';
import { nxPackagesApi } from '../../../lib/packages.api';
import { tagsApi } from '../../../lib/tags.api';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function Package({
overview,
@@ -22,25 +21,7 @@ export default function Package({
overview: string;
pkg: ProcessedPackageMetadata;
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
menu: {
@@ -73,14 +54,9 @@ export default function Package({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<PackageSchemaList pkg={vm.package} overview={overview} />
</div>
</ScrollableContent>
</main>
</div>
);
@@ -129,6 +105,7 @@ function getData(packageName: string): {
pkg,
};
}
export async function getStaticProps({ params }: { params: { name: string } }) {
try {
return { props: getData(params.name) };
+3 -6
View File
@@ -20,6 +20,7 @@ import { useMemo } from 'react';
import { menusApi } from '../../lib/menus.api';
import { useNavToggle } from '../../lib/navigation-toggle.effect';
import { nxPackagesApi } from '../../lib/packages.api';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function Packages({
packages,
@@ -97,11 +98,7 @@ export default function Packages({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent>
<div className="mx-auto w-full grow items-stretch px-4 sm:px-6 lg:px-8 2xl:max-w-6xl">
<div id="content-wrapper" className="w-full flex-auto flex-col">
<div className="mb-6 pt-8">
@@ -152,7 +149,7 @@ export default function Packages({
</div>
</div>
<Footer />
</div>
</ScrollableContent>
</main>
</div>
</>
@@ -3,12 +3,11 @@ import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { PackageSchemaSubList } from '@nx/nx-dev/feature-package-schema-viewer/src/lib/package-schema-sub-list';
import { menusApi } from '../../../../lib/menus.api';
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
import { pkg } from '../../../../lib/rspack/pkg';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function DocumentsIndex({
menu,
@@ -17,25 +16,7 @@ export default function DocumentsIndex({
menu: MenuItem[];
pkg: ProcessedPackageMetadata;
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
menu: {
@@ -68,14 +49,9 @@ export default function DocumentsIndex({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<PackageSchemaSubList pkg={vm.package} type={'document'} />
</div>
</ScrollableContent>
</main>
</div>
);
@@ -5,13 +5,12 @@ import { ProcessedDocument, RelatedDocument } from '@nx/nx-dev/models-document';
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { menusApi } from '../../../../lib/menus.api';
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
import { content } from '../../../../lib/rspack/content/overview';
import { pkg } from '../../../../lib/rspack/pkg';
import { fetchGithubStarCount } from '../../../../lib/githubStars.api';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function Overview({
document,
@@ -25,25 +24,7 @@ export default function Overview({
relatedDocuments: RelatedDocument[];
widgetData: { githubStarsCount: number };
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: {
document: ProcessedDocument;
@@ -75,18 +56,13 @@ export default function Overview({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<DocViewer
document={vm.document}
relatedDocuments={vm.relatedDocuments}
widgetData={widgetData}
/>
</div>
</ScrollableContent>
</main>
</div>
);
@@ -5,13 +5,12 @@ import { ProcessedDocument, RelatedDocument } from '@nx/nx-dev/models-document';
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { menusApi } from '../../../../lib/menus.api';
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
import { content } from '../../../../lib/rspack/content/rspack-config-setup';
import { pkg } from '../../../../lib/rspack/pkg';
import { fetchGithubStarCount } from '../../../../lib/githubStars.api';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function RspackConfigSetup({
document,
@@ -25,25 +24,7 @@ export default function RspackConfigSetup({
relatedDocuments: RelatedDocument[];
widgetData: { githubStarsCount: number };
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: {
document: ProcessedDocument;
@@ -75,18 +56,13 @@ export default function RspackConfigSetup({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<DocViewer
document={vm.document}
relatedDocuments={vm.relatedDocuments}
widgetData={widgetData}
/>
</div>
</ScrollableContent>
</main>
</div>
);
@@ -5,13 +5,12 @@ import { ProcessedDocument, RelatedDocument } from '@nx/nx-dev/models-document';
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { menusApi } from '../../../../lib/menus.api';
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
import { content } from '../../../../lib/rspack/content/rspack-plugin';
import { pkg } from '../../../../lib/rspack/pkg';
import { fetchGithubStarCount } from '../../../../lib/githubStars.api';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function RspackPlugins({
document,
@@ -25,25 +24,7 @@ export default function RspackPlugins({
relatedDocuments: RelatedDocument[];
widgetData: { githubStarsCount: number };
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: {
document: ProcessedDocument;
@@ -75,18 +56,13 @@ export default function RspackPlugins({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<DocViewer
document={vm.document}
relatedDocuments={vm.relatedDocuments}
widgetData={widgetData}
/>
</div>
</ScrollableContent>
</main>
</div>
);
@@ -7,12 +7,11 @@ import {
SchemaMetadata,
} from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { menusApi } from '../../../../lib/menus.api';
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
import { schema } from '../../../../lib/rspack/schema/executors/dev-server';
import { pkg } from '../../../../lib/rspack/pkg';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function DevServerExecutor({
menu,
@@ -23,25 +22,7 @@ export default function DevServerExecutor({
pkg: ProcessedPackageMetadata;
schema: SchemaMetadata;
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: {
menu: Menu;
@@ -79,14 +60,9 @@ export default function DevServerExecutor({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
</div>
</ScrollableContent>
</main>
</div>
);
@@ -3,12 +3,11 @@ import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { PackageSchemaSubList } from '@nx/nx-dev/feature-package-schema-viewer/src/lib/package-schema-sub-list';
import { menusApi } from '../../../../lib/menus.api';
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
import { pkg } from '../../../../lib/rspack/pkg';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function ExecutorsIndex({
menu,
@@ -17,25 +16,7 @@ export default function ExecutorsIndex({
menu: MenuItem[];
pkg: ProcessedPackageMetadata;
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
menu: {
@@ -68,14 +49,9 @@ export default function ExecutorsIndex({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<PackageSchemaSubList pkg={vm.package} type={'executor'} />
</div>
</ScrollableContent>
</main>
</div>
);
@@ -7,12 +7,11 @@ import {
SchemaMetadata,
} from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { menusApi } from '../../../../lib/menus.api';
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
import { schema } from '../../../../lib/rspack/schema/executors/rspack';
import { pkg } from '../../../../lib/rspack/pkg';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function RspackExecutor({
menu,
@@ -23,25 +22,7 @@ export default function RspackExecutor({
pkg: ProcessedPackageMetadata;
schema: SchemaMetadata;
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: {
menu: Menu;
@@ -79,14 +60,9 @@ export default function RspackExecutor({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
</div>
</ScrollableContent>
</main>
</div>
);
@@ -7,12 +7,11 @@ import {
SchemaMetadata,
} from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { menusApi } from '../../../../lib/menus.api';
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
import { schema } from '../../../../lib/rspack/schema/generators/application';
import { pkg } from '../../../../lib/rspack/pkg';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function ApplicationGenerator({
menu,
@@ -23,25 +22,7 @@ export default function ApplicationGenerator({
pkg: ProcessedPackageMetadata;
schema: SchemaMetadata;
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: {
menu: Menu;
@@ -79,14 +60,9 @@ export default function ApplicationGenerator({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
</div>
</ScrollableContent>
</main>
</div>
);
@@ -7,12 +7,11 @@ import {
SchemaMetadata,
} from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { menusApi } from '../../../../lib/menus.api';
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
import { schema } from '../../../../lib/rspack/schema/generators/configuration';
import { pkg } from '../../../../lib/rspack/pkg';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function ConfigurationGenerator({
menu,
@@ -23,25 +22,7 @@ export default function ConfigurationGenerator({
pkg: ProcessedPackageMetadata;
schema: SchemaMetadata;
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: {
menu: Menu;
@@ -79,14 +60,9 @@ export default function ConfigurationGenerator({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
</div>
</ScrollableContent>
</main>
</div>
);
@@ -3,12 +3,11 @@ import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { PackageSchemaSubList } from '@nx/nx-dev/feature-package-schema-viewer/src/lib/package-schema-sub-list';
import { menusApi } from '../../../../lib/menus.api';
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
import { pkg } from '../../../../lib/rspack/pkg';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function GeneratorsIndex({
menu,
@@ -17,25 +16,7 @@ export default function GeneratorsIndex({
menu: MenuItem[];
pkg: ProcessedPackageMetadata;
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
menu: {
@@ -68,14 +49,9 @@ export default function GeneratorsIndex({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<PackageSchemaSubList pkg={vm.package} type={'generator'} />
</div>
</ScrollableContent>
</main>
</div>
);
@@ -7,12 +7,11 @@ import {
SchemaMetadata,
} from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { menusApi } from '../../../../lib/menus.api';
import { useNavToggle } from '../../../../lib/navigation-toggle.effect';
import { schema } from '../../../../lib/rspack/schema/generators/init';
import { pkg } from '../../../../lib/rspack/pkg';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function InitGenerator({
menu,
@@ -23,25 +22,7 @@ export default function InitGenerator({
pkg: ProcessedPackageMetadata;
schema: SchemaMetadata;
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: {
menu: Menu;
@@ -79,14 +60,9 @@ export default function InitGenerator({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<PackageSchemaViewer pkg={vm.package} schema={vm.schema} />
</div>
</ScrollableContent>
</main>
</div>
);
+3 -27
View File
@@ -4,12 +4,11 @@ import { sortCorePackagesFirst } from '@nx/nx-dev/data-access-packages';
import { Menu, MenuItem, MenuSection } from '@nx/nx-dev/models-menu';
import { ProcessedPackageMetadata } from '@nx/nx-dev/models-package';
import { DocumentationHeader, SidebarContainer } from '@nx/nx-dev/ui-common';
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
import { menusApi } from '../../../lib/menus.api';
import { useNavToggle } from '../../../lib/navigation-toggle.effect';
import { content } from '../../../lib/rspack/content/overview';
import { pkg } from '../../../lib/rspack/pkg';
import { ScrollableContent } from '@nx/ui-scrollable-content';
export default function RspackIndex({
overview,
@@ -20,25 +19,7 @@ export default function RspackIndex({
overview: string;
pkg: ProcessedPackageMetadata;
}): JSX.Element {
const router = useRouter();
const { toggleNav, navIsOpen } = useNavToggle();
const wrapperElement = useRef(null);
useEffect(() => {
const handleRouteChange = (url: string) => {
if (url.includes('#')) return;
if (!wrapperElement) return;
(wrapperElement as any).current.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [router, wrapperElement]);
const vm: { menu: Menu; package: ProcessedPackageMetadata } = {
menu: {
@@ -71,14 +52,9 @@ export default function RspackIndex({
navIsOpen={navIsOpen}
toggleNav={toggleNav}
/>
<div
ref={wrapperElement}
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent resetScrollOnNavigation={true}>
<PackageSchemaList pkg={vm.package} overview={overview} />
</div>
</ScrollableContent>
</main>
</div>
);
+4 -7
View File
@@ -12,6 +12,7 @@ import { useRouter } from 'next/router';
import { menusApi } from '../lib/menus.api';
import { useNavToggle } from '../lib/navigation-toggle.effect';
import { nxPackagesApi } from '../lib/packages.api';
import { ScrollableContent } from '@nx/ui-scrollable-content';
declare const fetch: any;
let qualityIndicators = require('./quality-indicators.json');
@@ -112,11 +113,7 @@ export default function Browse(props: BrowseProps): JSX.Element {
toggleNav={toggleNav}
/>
</div>
<div
id="wrapper"
data-testid="wrapper"
className="relative flex flex-grow flex-col items-stretch justify-start overflow-y-scroll"
>
<ScrollableContent>
<div className="mx-auto w-full grow items-stretch px-4 sm:px-6 lg:px-8 2xl:max-w-6xl">
<div id="content-wrapper" className="w-full flex-auto flex-col">
<div className="mb-6 pt-8">
@@ -130,7 +127,7 @@ export default function Browse(props: BrowseProps): JSX.Element {
Are you a plugin author? You can{' '}
<a
className="underline"
href="/extending-nx/tutorials/publish-plugin#list-your-nx-plugin"
href="/extending-nx/tutorials/tooling-plugin#list-your-nx-plugin"
>
add your plugin to the registry
</a>{' '}
@@ -141,7 +138,7 @@ export default function Browse(props: BrowseProps): JSX.Element {
</div>
<Footer />
</div>
</ScrollableContent>
</main>
</div>
</>
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+5 -1
View File
@@ -805,7 +805,11 @@ const nested5minuteTutorialUrls = {
const pluginUrls = {
'/plugin-features/create-your-own-plugin':
'/extending-nx/tutorials/create-plugin',
'/extending-nx/tutorials/organization-specific-plugin',
'/extending-nx/tutorials/create-plugin':
'/extending-nx/tutorials/organization-specific-plugin',
'/extending-nx/tutorials/publish-plugin':
'/extending-nx/tutorials/tooling-plugin',
'/recipes/advanced-plugins': '/extending-nx/recipes',
'/recipes/advanced-plugins/create-preset':
'/extending-nx/recipes/create-preset',
+21 -8
View File
@@ -4,6 +4,7 @@ import Image from 'next/image';
import { BlogAuthors } from './authors';
import { ChevronLeftIcon } from '@heroicons/react/24/outline';
import { renderMarkdown } from '@nx/nx-dev/ui-markdoc';
import { EpisodePlayer } from './episode-player';
export interface BlogDetailsProps {
post: BlogPostDataEntry;
@@ -65,16 +66,28 @@ export function BlogDetails({ post }: BlogDetailsProps) {
{post.title}
</h1>
</header>
{post.cover_image && (
<div className="mx-auto mb-16 aspect-[1.7] w-full max-w-screen-md">
<Image
className="h-full w-full object-cover md:rounded-md"
src={post.cover_image}
alt={post.title}
width={1400}
height={735}
{post.podcastYoutubeId && post.podcastSpotifyId ? (
<div className="mx-auto mb-16 w-full max-w-screen-md">
<EpisodePlayer
podcastYoutubeId={post.podcastYoutubeId}
podcastSpotifyId={post.podcastSpotifyId}
amazonUrl={post.podcastAmazonUrl}
appleUrl={post.podcastAppleUrl}
iHeartUrl={post.podcastIHeartUrl}
/>
</div>
) : (
post.cover_image && (
<div className="mx-auto mb-16 aspect-[1.7] w-full max-w-screen-md">
<Image
className="h-full w-full object-cover md:rounded-md"
src={post.cover_image}
alt={post.title}
width={1400}
height={735}
/>
</div>
)
)}
<div className="mx-auto min-w-0 max-w-3xl flex-auto px-4 pb-24 lg:px-0 lg:pb-16">
<div className="relative">
+149
View File
@@ -0,0 +1,149 @@
'use client';
import { useState } from 'react';
import {
AmazonMusicIcon,
ApplePodcastsIcon,
IHeartRadioIcon,
SpotifyIcon,
} from '@nx/nx-dev/ui-icons';
import Link from 'next/link';
export function EpisodePlayer({
podcastYoutubeId,
podcastSpotifyId,
amazonUrl,
appleUrl,
iHeartUrl,
}: {
podcastYoutubeId: string;
podcastSpotifyId: string;
amazonUrl?: string;
appleUrl?: string;
iHeartUrl?: string;
}) {
const [viewType, setViewType] = useState<ViewMode>('audio');
return (
<div className="flex basis-2/3 flex-col items-center justify-center gap-2">
{viewType === 'audio' ? (
<>
<iframe
style={{ borderRadius: '12px' }}
src={`https://open.spotify.com/embed/episode/${podcastSpotifyId}?utm_source=generator&theme=0`}
width="100%"
height="152"
frameBorder="0"
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
loading="lazy"
key="audio"
></iframe>
</>
) : (
<iframe
style={{ borderRadius: '12px' }}
width="100%"
height="400"
src={`https://www.youtube.com/embed/${podcastYoutubeId}?si=8rkgAzJfLfd-hxAA`}
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerPolicy="strict-origin-when-cross-origin"
allowFullScreen
key="video"
></iframe>
)}
<div className="container my-1 flex">
<div className="basis-1/2">
{viewType === 'audio' && (
<PlatformLinks
amazonUrl={amazonUrl}
appleUrl={appleUrl}
iHeartUrl={iHeartUrl}
podcastSpotifyId={podcastSpotifyId}
/>
)}
</div>
<div className="flex basis-1/2 flex-wrap justify-end gap-6 sm:gap-4">
<button
className="flex shrink-0 items-center gap-2 text-slate-400 hover:text-slate-800 dark:text-slate-600 dark:hover:text-slate-200"
onClick={() => setViewType(getOpposite(viewType))}
>
Switch to {getOpposite(viewType) === 'audio' ? 'Audio' : 'Video'}
</button>
<Link
className="flex shrink-0 items-center gap-2 text-slate-400 hover:text-slate-800 dark:text-slate-600 dark:hover:text-slate-200"
href="/podcast"
>
More Podcasts
</Link>
</div>
</div>
</div>
);
}
export function PlatformLinks({
amazonUrl,
appleUrl,
iHeartUrl,
podcastSpotifyId,
}: {
amazonUrl?: string;
appleUrl?: string;
iHeartUrl?: string;
podcastSpotifyId: string;
}): JSX.Element {
const platforms = [
{
name: 'Amazon Music',
url: amazonUrl,
icon: AmazonMusicIcon,
},
{
name: 'Apple Podcasts',
url: appleUrl,
icon: ApplePodcastsIcon,
},
{
name: 'iHeartRadio',
url: iHeartUrl,
icon: IHeartRadioIcon,
},
{
name: 'Spotify',
url: `https://open.spotify.com/episode/${podcastSpotifyId}?si=Nqd7F40hQXugagH8oDxxpA`,
icon: SpotifyIcon,
},
];
return (
<ul className="flex flex-wrap gap-6 sm:gap-4">
{platforms
.filter((platform) => !!platform.url)
.map((platform) => {
return (
<li
key={platform.name}
className="inline-block cursor-pointer place-items-center rounded-2xl border border-slate-100 bg-white p-4 text-slate-600 transition-all hover:scale-[1.02] hover:text-slate-950 dark:border-slate-800/60 dark:bg-slate-950 dark:text-slate-400 dark:hover:text-white"
>
<a
href={platform.url}
target="_blank"
rel="noopener noreferrer"
className="flex h-full w-full items-center justify-center"
>
<platform.icon className="h-6 w-6 shrink-0" />
</a>
</li>
);
})}
</ul>
);
}
type ViewMode = 'audio' | 'video';
function getOpposite(viewMode: ViewMode): ViewMode {
if (viewMode === 'audio') return 'video';
return 'audio';
}
+1 -1
View File
@@ -9,7 +9,7 @@ export interface CTAProps {
export function CallToAction({
mainActionTitle = 'Get started with Nx',
mainActionLinkText = 'Get started',
mainActionLink = '/docs',
mainActionLink = '/getting-started/intro',
}: CTAProps): JSX.Element {
return (
<section className="relative isolate px-6 py-32 sm:py-40 lg:px-8">
+21 -3
View File
@@ -13,6 +13,8 @@ import {
UserGroupIcon,
ComputerDesktopIcon,
GlobeAltIcon,
MicrophoneIcon,
VideoCameraIcon,
} from '@heroicons/react/24/outline';
import { FC, SVGProps } from 'react';
import { DiscordIcon } from '../discord-icon';
@@ -172,6 +174,22 @@ export const learnItems: MenuItem[] = [
isNew: false,
isHighlight: false,
},
{
name: 'Podcasts',
description: null,
href: '/podcast',
icon: MicrophoneIcon,
isNew: false,
isHighlight: false,
},
{
name: 'Webinars',
description: null,
href: 'https://go.nx.dev/webinar',
icon: ComputerDesktopIcon,
isNew: false,
isHighlight: false,
},
{
name: 'Video tutorials',
description: null,
@@ -215,10 +233,10 @@ export const eventItems: MenuItem[] = [
isHighlight: false,
},
{
name: 'Webinars',
name: 'Live Streams',
description: null,
href: 'https://go.nx.dev/webinar',
icon: ComputerDesktopIcon,
href: 'https://www.youtube.com/@nxdevtools/streams',
icon: VideoCameraIcon,
isNew: false,
isHighlight: false,
},
@@ -10,7 +10,7 @@ export function SectionsMenu({
<div className="flex flex-col gap-2 overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-slate-200 dark:bg-slate-900 dark:ring-slate-800">
<div className="divide-y divide-slate-200 dark:divide-slate-800">
{Object.keys(sections).map((section) => (
<div>
<div key={section}>
<h5 className="px-4 pt-6 text-sm text-slate-500 dark:text-slate-400">
{section}
</h5>
@@ -23,7 +23,7 @@ export function HowCanWeHelp(): JSX.Element {
className="h-5 w-5 shrink-0"
/>
<h3 className="text-xl font-medium text-slate-700 dark:text-slate-300">
Sales
Talk to Sales
</h3>
</div>
<p className="mt-4">
@@ -47,7 +47,7 @@ export function HowCanWeHelp(): JSX.Element {
className="h-5 w-5 shrink-0"
/>
<h3 className="text-xl font-medium text-slate-700 dark:text-slate-300">
Engineers
Learn more about Nx Cloud
</h3>
</div>
<p className="mt-4">
+54 -18
View File
@@ -6,6 +6,8 @@ import { Theme, useTheme } from '@nx/nx-dev/ui-theme';
import { useState } from 'react';
import Link from 'next/link';
import { useIsomorphicLayoutEffect } from '@nx/nx-dev/ui-primitives';
import { MonorepoWorldIcon } from '@nx/nx-dev/ui-icons';
import { motion, MotionConfig } from 'framer-motion';
export function Hero(): JSX.Element {
return (
@@ -23,23 +25,6 @@ export function Hero(): JSX.Element {
</div>
<div className="z-20 mx-auto grid h-screen max-w-6xl grid-cols-1 place-items-center text-center">
<div className="container">
<div className="-mt-16 hidden sm:mb-8 sm:flex sm:justify-center">
<div className="relative rounded-full bg-white px-3 py-1 text-sm leading-6 ring-1 ring-slate-900/10 transition-all hover:ring-slate-900/20 dark:bg-slate-950 dark:ring-slate-100/10 dark:hover:ring-slate-100/20">
<span className="text-blue-500 dark:text-sky-500">
Monorepo World
</span>{' '}
- The conf for monorepos and dev tooling.{' '}
<Link
href="https://monorepo.world"
title="Discover Nx Agents"
className="font-semibold text-blue-500 dark:text-sky-500"
prefetch={false}
>
<span className="absolute inset-0" aria-hidden="true"></span>
Find out more <span aria-hidden="true"></span>
</Link>
</div>
</div>
<SectionHeading as="h1" variant="display" data-cy="primary-heading">
<span className="rounded-lg bg-gradient-to-r from-cyan-500 to-blue-500 bg-clip-text text-transparent">
Smart
@@ -63,7 +48,6 @@ export function Hero(): JSX.Element {
variant="subtitle"
className="mx-auto mt-6 max-w-4xl"
>
{/*Structured, maintainable and efficient monorepos. Locally and on CI, easy as that.*/}
<Strong>Build system</Strong>, optimized for monorepos, with plugins
for popular frameworks and tools and{' '}
<Strong>advanced CI capabilities</Strong> including caching and
@@ -96,6 +80,58 @@ export function Hero(): JSX.Element {
Contact us
</ButtonLink>
</div>
<div className="mt-12 flex justify-center">
<MotionConfig reducedMotion="user">
<motion.div
initial={{
y: 16,
opacity: 0,
}}
whileInView={{
y: 0,
opacity: 1,
}}
viewport={{ once: true }}
transition={{
ease: 'easeOut',
duration: 0.225,
delay: 1,
}}
className="pointer-events-auto relative mx-auto w-full max-w-lg overflow-hidden rounded-lg bg-slate-950 text-left text-white shadow-lg ring-1 ring-white ring-opacity-5 transition hover:bg-slate-800"
>
<div className="p-4">
<div className="flex items-start gap-6">
<div className="-m-4 hidden flex-shrink-0 sm:block">
<img
src="/images/conferences/monorepoworld-vertical-banner.avif"
alt="MonorepoWorld conference image"
className="size-32"
/>
</div>
<div className="w-0 flex-1 pt-1">
<p className="text-base font-semibold">
<a
href="https://monorepo.world/?utm_source=nxdev&utm_medium=website&utm_campaign=homepage_banner&utm_campaign=monorepoworld"
title="Join us at Monorepo World"
target="_blank"
>
<span className="absolute inset-0" />
The Nx team will be at Monorepo World!
</a>
</p>
<p className="mt-1 text-sm text-slate-300">
The ultimate conference for{' '}
<span className="text-[#DDFB24]">monorepos</span> and
developer{' '}
<span className="text-[#DDFB24]">tooling</span> on 0ct.
7. 2024, Mountain View, CA.
</p>
</div>
</div>
</div>
</motion.div>
</MotionConfig>
</div>
</div>
</div>
</div>
@@ -66,7 +66,7 @@ export function TeamAndCommunity(): JSX.Element {
<div className="group/item relative flex items-center gap-2 rounded-xl border border-slate-100 p-4 transition hover:text-slate-950 dark:border-slate-800/60 dark:hover:text-white">
<DocumentIcon aria-hidden="true" className="size-6 shrink-0" />
<Link
href="/docs?utm_medium=website&utm_campaign=homepage_links&utm_content=cta_team_and_community"
href="/getting-started/intro?utm_medium=website&utm_campaign=homepage_links&utm_content=cta_team_and_community"
className="grow text-base"
prefetch={false}
>
+9
View File
@@ -73,6 +73,15 @@ export * from './lib/customers/zipari';
export * from './lib/editors/jetbrains';
export * from './lib/editors/visual-studio-code';
// OTHERS
export * from './lib/monorepo-world';
// PODCASTS
export * from './lib/podcasts/amazon-music';
export * from './lib/podcasts/apple-podcasts';
export * from './lib/podcasts/i-heart-radio';
export * from './lib/podcasts/spotify';
// PRODUCTS
export * from './lib/products';
@@ -0,0 +1,14 @@
import { ComponentPropsWithoutRef } from 'react';
export function MonorepoWorldIcon(props: ComponentPropsWithoutRef<'svg'>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
{...props}
>
<path d="M4.95951 15.2616v-4.7654c0-.82631.64074-1.49877 1.45095-1.49877.77235 0 1.43154.67246 1.43154 1.49877v3.296l1.96008-.9991v-2.2969c0-.82631.65942-1.49877 1.45092-1.49877.7724 0 1.4129.67246 1.4129 1.49877v.8369l1.9746-1.0066C14.5545 8.46636 13.0638 7 11.2536 7c-.9797 0-1.82771.44126-2.44924 1.13332C8.2568 7.44126 7.37148 7 6.41027 7 4.52607 7 3 8.55635 3 10.4962v5.7643l1.95951-.9989ZM17.5251 9.72944v4.76546c0 .8263-.6408 1.4988-1.451 1.4988-.7723 0-1.4315-.6725-1.4315-1.4988v-3.296l-1.9601.9991v2.2969c0 .8263-.6594 1.4988-1.451 1.4988-.7723 0-1.41287-.6725-1.41287-1.4988v-.8369L7.8441 14.6646c.08596 1.8601 1.57666 3.3265 3.3869 3.3265.9796 0 1.8277-.4413 2.4492-1.1333.5476.692 1.4329 1.1333 2.3941 1.1333 1.8842 0 3.4103-1.5564 3.4103-3.4962V8.73073l-1.9595.99871ZM21.0205 18c.541 0 .9795-.4472.9795-.9989 0-.5517-.4385-.9989-.9795-.9989-.5409 0-.9794.4472-.9794.9989 0 .5517.4385.9989.9794.9989Z" />
</svg>
);
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,20 @@
import { FC, SVGProps } from 'react';
/**
* Color: #9933CC
*/
export const ApplePodcastsIcon: FC<SVGProps<SVGSVGElement>> = (props) => {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
{...props}
>
<title>Apple Podcasts</title>
<path d="M5.34 0A5.328 5.328 0 000 5.34v13.32A5.328 5.328 0 005.34 24h13.32A5.328 5.328 0 0024 18.66V5.34A5.328 5.328 0 0018.66 0zm6.525 2.568c2.336 0 4.448.902 6.056 2.587 1.224 1.272 1.912 2.619 2.264 4.392.12.59.12 2.2.007 2.864a8.506 8.506 0 01-3.24 5.296c-.608.46-2.096 1.261-2.336 1.261-.088 0-.096-.091-.056-.46.072-.592.144-.715.48-.856.536-.224 1.448-.874 2.008-1.435a7.644 7.644 0 002.008-3.536c.208-.824.184-2.656-.048-3.504-.728-2.696-2.928-4.792-5.624-5.352-.784-.16-2.208-.16-3 0-2.728.56-4.984 2.76-5.672 5.528-.184.752-.184 2.584 0 3.336.456 1.832 1.64 3.512 3.192 4.512.304.2.672.408.824.472.336.144.408.264.472.856.04.36.03.464-.056.464-.056 0-.464-.176-.896-.384l-.04-.03c-2.472-1.216-4.056-3.274-4.632-6.012-.144-.706-.168-2.392-.03-3.04.36-1.74 1.048-3.1 2.192-4.304 1.648-1.737 3.768-2.656 6.128-2.656zm.134 2.81c.409.004.803.04 1.106.106 2.784.62 4.76 3.408 4.376 6.174-.152 1.114-.536 2.03-1.216 2.88-.336.43-1.152 1.15-1.296 1.15-.023 0-.048-.272-.048-.603v-.605l.416-.496c1.568-1.878 1.456-4.502-.256-6.224-.664-.67-1.432-1.064-2.424-1.246-.64-.118-.776-.118-1.448-.008-1.02.167-1.81.562-2.512 1.256-1.72 1.704-1.832 4.342-.264 6.222l.413.496v.608c0 .336-.027.608-.06.608-.03 0-.264-.16-.512-.36l-.034-.011c-.832-.664-1.568-1.842-1.872-2.997-.184-.698-.184-2.024.008-2.72.504-1.878 1.888-3.335 3.808-4.019.41-.145 1.133-.22 1.814-.211zm-.13 2.99c.31 0 .62.06.844.178.488.253.888.745 1.04 1.259.464 1.578-1.208 2.96-2.72 2.254h-.015c-.712-.331-1.096-.956-1.104-1.77 0-.733.408-1.371 1.112-1.745.224-.117.534-.176.844-.176zm-.011 4.728c.988-.004 1.706.349 1.97.97.198.464.124 1.932-.218 4.302-.232 1.656-.36 2.074-.68 2.356-.44.39-1.064.498-1.656.288h-.003c-.716-.257-.87-.605-1.164-2.644-.341-2.37-.416-3.838-.218-4.302.262-.616.974-.966 1.97-.97z" />
</svg>
);
};
@@ -0,0 +1,20 @@
import { FC, SVGProps } from 'react';
/**
* Color: #C6002B
*/
export const IHeartRadioIcon: FC<SVGProps<SVGSVGElement>> = (props) => {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
{...props}
>
<title>iHeartRadio</title>
<path d="M4.403 21.983c.597 0 1.023-.306 1.023-.817v-.012c0-.489-.375-.784-1.017-.784H3.182v1.613zm-1.67-1.8c0-.125.102-.228.221-.228h1.489c.488 0 .88.148 1.13.398.193.193.307.472.307.784v.011c0 .654-.443 1.034-1.062 1.154l.988 1.272c.046.051.074.102.074.164 0 .12-.114.222-.227.222-.091 0-.16-.05-.21-.12l-1.12-1.453H3.183v1.346a.228.228 0 01-.228.227.227.227 0 01-.221-.227v-3.55m6.674 2.29l-.914-2.035-.915 2.034zm-2.812 1.164l1.614-3.528c.056-.125.142-.2.284-.2h.022c.137 0 .228.075.279.2l1.613 3.522a.31.31 0 01.029.113c0 .12-.097.216-.216.216-.108 0-.182-.074-.222-.165l-.415-.914H7.402l-.415.926c-.04.097-.113.153-.216.153a.204.204 0 01-.204-.204.26.26 0 01.028-.12m6.078-.118c1.005 0 1.647-.682 1.647-1.563v-.011c0-.88-.642-1.574-1.647-1.574h-.932v3.148zm-1.38-3.335c0-.125.102-.228.221-.228h1.16c1.249 0 2.112.858 2.112 1.977v.012c0 1.119-.863 1.988-2.113 1.988h-1.159a.226.226 0 01-.221-.227v-3.522m4.481-.029c0-.124.103-.227.222-.227.125 0 .227.103.227.227v3.579a.228.228 0 01-.227.227.227.227 0 01-.222-.227v-3.579m5.027 1.801v-.011c0-.904-.659-1.642-1.568-1.642s-1.556.727-1.556 1.63v.012c0 .903.659 1.642 1.567 1.642.91 0 1.557-.728 1.557-1.631zm-3.59 0v-.011c0-1.097.824-2.057 2.033-2.057 1.21 0 2.023.949 2.023 2.045v.012c0 1.096-.824 2.056-2.034 2.056s-2.022-.949-2.022-2.045m2.03-17.192c0 1.397-.754 2.773-2.242 4.092a.345.345 0 01-.458-.517c1.333-1.182 2.01-2.385 2.01-3.575v-.016c0-.966-.606-2.103-1.38-2.588a.345.345 0 11.367-.586c.97.61 1.703 1.974 1.703 3.174zM14.76 7.677a.345.345 0 11-.337-.602c.799-.448 1.336-1.318 1.339-2.167a2.096 2.096 0 00-1.124-1.855.345.345 0 11.321-.611 2.785 2.785 0 011.493 2.46v.011c-.004 1.09-.683 2.199-1.692 2.764zm-2.772-1.015a1.498 1.498 0 11.001-2.997 1.498 1.498 0 01-.001 2.997zm-2.303.882a.345.345 0 01-.47.133c-1.009-.565-1.688-1.674-1.692-2.764v-.01a2.785 2.785 0 011.493-2.461.346.346 0 01.321.611 2.096 2.096 0 00-1.124 1.855c.003.849.54 1.719 1.34 2.166a.345.345 0 01.132.47zM7.464 8.825a.344.344 0 01-.488.03C5.49 7.536 4.734 6.16 4.734 4.763v-.016c0-1.2.732-2.564 1.703-3.174a.346.346 0 01.367.586c-.774.485-1.38 1.622-1.38 2.588v.016c0 1.19.677 2.393 2.01 3.575a.345.345 0 01.03.487zM16.152 0c-1.727 0-3.27.915-4.164 2.252C11.094.915 9.55 0 7.823 0A4.982 4.982 0 002.84 4.983c0 1.746 1.106 3.005 2.261 4.17l4.518 4.272a.371.371 0 00.626-.27V9.827c0-.963.78-1.743 1.743-1.745a1.745 1.745 0 011.742 1.745v3.328c0 .326.39.493.626.27l4.518-4.272c1.155-1.165 2.261-2.424 2.261-4.17A4.982 4.982 0 0016.152 0M4.582 14.766h1.194v1.612h1.532v-1.612H8.5v4.307H7.308v-1.637H5.776v1.637H4.582v-4.307m6.527 2.353a.563.563 0 00-.578-.587c-.308 0-.55.238-.578.587zm-2.264.305v-.012c0-.972.696-1.741 1.68-1.741 1.15 0 1.68.842 1.68 1.82 0 .075 0 .16-.007.24H9.971c.093.364.357.549.72.549.277 0 .498-.105.738-.34l.647.536c-.32.406-.782.677-1.447.677-1.045 0-1.784-.695-1.784-1.729m7.29-1.68h1.17v.67c.19-.454.498-.75 1.051-.725v1.23h-.098c-.609 0-.954.351-.954 1.12v1.034h-1.168v-3.329m2.95 2.295v-1.353h-.393v-.942h.393v-.842h1.17v.842h.775v.942h-.775v1.126c0 .234.105.332.32.332.153 0 .301-.043.442-.11v.916c-.209.117-.485.19-.812.19-.7 0-1.12-.307-1.12-1.1m-15.65-3.584a.62.62 0 100 1.24.62.62 0 000-1.24m10.502 3.952c-.303.013-.483-.161-.483-.371 0-.203.16-.307.454-.307h.667v.036c-.004.137-.06.617-.638.642zm1.746-1.008c0-1.033-.739-1.729-1.784-1.729-.665 0-1.126.271-1.447.677l.647.536c.24-.234.461-.34.738-.34.359 0 .621.182.716.537l.001.025-.77.003c-.956.013-1.458.37-1.458 1.045 0 .65.464.999 1.262.999.432 0 .764-.17.987-.401v.32h1.106v-1.628l.002-.032V17.4M3.458 15.99h-.043a.61.61 0 00-.61.61v2.474h1.263v-2.474a.61.61 0 00-.61-.61" />
</svg>
);
};
@@ -0,0 +1,20 @@
import { FC, SVGProps } from 'react';
/**
* Color: #1DB954
*/
export const SpotifyIcon: FC<SVGProps<SVGSVGElement>> = (props) => {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
{...props}
>
<title>Spotify</title>
<path d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z" />
</svg>
);
};
+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": {}
}
]
}

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