diff --git a/docs/generated/manifests/extending-nx.json b/docs/generated/manifests/extending-nx.json index 093f30de1f..62d4671565 100644 --- a/docs/generated/manifests/extending-nx.json +++ b/docs/generated/manifests/extending-nx.json @@ -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"] } } diff --git a/docs/generated/manifests/menus.json b/docs/generated/manifests/menus.json index 9717ae2d29..9ce8b5eedb 100644 --- a/docs/generated/manifests/menus.json +++ b/docs/generated/manifests/menus.json @@ -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 } ] }, diff --git a/docs/generated/manifests/tags.json b/docs/generated/manifests/tags.json index 1c6b2879a3..9a6cef3ba6 100644 --- a/docs/generated/manifests/tags.json +++ b/docs/generated/manifests/tags.json @@ -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", diff --git a/docs/map.json b/docs/map.json index 2ee5104e63..0c78167556 100644 --- a/docs/map.json +++ b/docs/map.json @@ -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" } ] } diff --git a/docs/shared/concepts/nx-plugins.md b/docs/shared/concepts/nx-plugins.md index ba51340c91..009f3b57cb 100644 --- a/docs/shared/concepts/nx-plugins.md +++ b/docs/shared/concepts/nx-plugins.md @@ -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 %} diff --git a/docs/shared/getting-started/intro.md b/docs/shared/getting-started/intro.md index 0957e6f114..807f8852df 100644 --- a/docs/shared/getting-started/intro.md +++ b/docs/shared/getting-started/intro.md @@ -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). diff --git a/docs/shared/plugins/create-plugin.md b/docs/shared/plugins/create-plugin.md deleted file mode 100644 index 140f0980e1..0000000000 --- a/docs/shared/plugins/create-plugin.md +++ /dev/null @@ -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. diff --git a/docs/shared/plugins/generator-options-ui.png b/docs/shared/plugins/generator-options-ui.png new file mode 100644 index 0000000000..1270f678f2 Binary files /dev/null and b/docs/shared/plugins/generator-options-ui.png differ diff --git a/docs/shared/plugins/intro.md b/docs/shared/plugins/intro.md index cbaae96741..a147598431 100644 --- a/docs/shared/plugins/intro.md +++ b/docs/shared/plugins/intro.md @@ -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. diff --git a/docs/shared/plugins/organization-specific-plugin.md b/docs/shared/plugins/organization-specific-plugin.md new file mode 100644 index 0000000000..c8af77cec2 --- /dev/null +++ b/docs/shared/plugins/organization-specific-plugin.md @@ -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. diff --git a/docs/shared/plugins/tooling-plugin.md b/docs/shared/plugins/tooling-plugin.md new file mode 100644 index 0000000000..4124c6d70d --- /dev/null +++ b/docs/shared/plugins/tooling-plugin.md @@ -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 = [ + 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!'); +--- + + + +

Hello, World!

+ + + +``` + +{% /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 diff --git a/docs/shared/recipes/plugins/create-preset.md b/docs/shared/recipes/plugins/create-preset.md index e1bb9c7a60..6cbebaca7c 100644 --- a/docs/shared/recipes/plugins/create-preset.md +++ b/docs/shared/recipes/plugins/create-preset.md @@ -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. diff --git a/docs/shared/recipes/plugins/local-executors.md b/docs/shared/recipes/plugins/local-executors.md index cb7fb50cda..ed30028512 100644 --- a/docs/shared/recipes/plugins/local-executors.md +++ b/docs/shared/recipes/plugins/local-executors.md @@ -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 diff --git a/docs/shared/plugins/maintain-published-plugin.md b/docs/shared/recipes/plugins/publish-plugin.md similarity index 63% rename from docs/shared/plugins/maintain-published-plugin.md rename to docs/shared/recipes/plugins/publish-plugin.md index e2c2f4bc8e..031956ac39 100644 --- a/docs/shared/plugins/maintain-published-plugin.md +++ b/docs/shared/recipes/plugins/publish-plugin.md @@ -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. diff --git a/docs/shared/reference/sitemap.md b/docs/shared/reference/sitemap.md index dc640d7a91..961fcc0696 100644 --- a/docs/shared/reference/sitemap.md +++ b/docs/shared/reference/sitemap.md @@ -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 diff --git a/graph/ui-icons/src/lib/framework-icons.tsx b/graph/ui-icons/src/lib/framework-icons.tsx index 53cdc5531b..ec1e15ee20 100644 --- a/graph/ui-icons/src/lib/framework-icons.tsx +++ b/graph/ui-icons/src/lib/framework-icons.tsx @@ -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< ), }, + office: { + image: ( + + + + ), + }, + tool: { + image: ( + + + + + ), + }, }; diff --git a/nx-dev/nx-dev/pages/plugin-registry.tsx b/nx-dev/nx-dev/pages/plugin-registry.tsx index 053d2b66de..81b4b6f4c6 100644 --- a/nx-dev/nx-dev/pages/plugin-registry.tsx +++ b/nx-dev/nx-dev/pages/plugin-registry.tsx @@ -130,7 +130,7 @@ export default function Browse(props: BrowseProps): JSX.Element { Are you a plugin author? You can{' '} add your plugin to the registry {' '} diff --git a/nx-dev/nx-dev/redirect-rules.js b/nx-dev/nx-dev/redirect-rules.js index 4151c1782e..18ccd1ce91 100644 --- a/nx-dev/nx-dev/redirect-rules.js +++ b/nx-dev/nx-dev/redirect-rules.js @@ -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',