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
This commit is contained in:
Isaac Mann 2024-08-15 10:22:07 -04:00 committed by GitHub
parent cf5dd4da08
commit ba47c72981
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 1095 additions and 319 deletions

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"]
}
}

View File

@ -4946,7 +4946,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 +4957,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 +4965,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 +4990,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 +5011,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 +5059,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 +5092,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 +5158,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 +5191,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
}
]
},

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"
},
{
@ -589,7 +589,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"
}
],
@ -626,9 +626,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",

View File

@ -1457,7 +1457,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 +1465,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 +1486,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 +1522,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 +1547,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 +1555,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"
}
]
}

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 %}

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. -->

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

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.

View File

@ -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.

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

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.

View File

@ -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

View File

@ -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.

View File

@ -226,22 +226,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

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>
),
},
};

View File

@ -130,7 +130,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>{' '}

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',