Creating an Install Package

Starting a new project should be as seamless as possible. In the JavaScript ecosystem, the idea of bootstrapping a new project with a single command has become a must-have for providing a good DX. So much that all the major package managers have a dedicated feature already built-in: if you publish a package named create-{x}, it can be invoked via any of the following:

  • npx create-{x}
  • yarn create {x}
  • npm init {x}
  • pnpm init {x}

These packages are used to set up a new project in some form.

Customizing your initial project setup is already possible with an Nx Preset generator. By creating and shipping a generator named preset in your Nx plugin, you can then pass it via the --preset flag to the create-nx-workspace command:

npx create-nx-workspace --preset my-plugin

This allows you to take full control over the shape of the generated Nx workspace. You might however want to have your own create-{x} package, whether that is for marketing purposes, branding or better discoverability. Starting with Nx 16.5 you can now have such a create-{x} package generated for you.

Generating a "Create Package"

There are a few methods to create a package that will work with create-nx-workspace's public API to setup a new workspace that uses your Nx plugin.

You can setup a new Nx plugin workspace and immediately pass the --create-package-name:

npx create-nx-plugin my-plugin --create-package-name create-my-plugin

Alternatively, if you already have an existing Nx plugin workspace, you can run the following generator to set up a new create package:

nx g create-package create-my-plugin --project my-plugin

Customize your create package

You'll have 2 packages that are relevant in your workspace:

  • The create package (e.g. create-my-plugin)
  • The plugin package (e.g. my-plugin)

Let's take a look at the code that was scaffolded out for your create-my-plugin package:

packages/create-my-plugin/bin/index.ts
1#!/usr/bin/env node 2 3import { createWorkspace } from 'create-nx-workspace'; 4 5async function main() { 6 const name = process.argv[2]; // TODO: use libraries like yargs or enquirer to set your workspace name 7 if (!name) { 8 throw new Error('Please provide a name for the workspace'); 9 } 10 11 console.log(`Creating the workspace: ${name}`); 12 13 // This assumes "my-plugin" and "create-my-plugin" are at the same version 14 // eslint-disable-next-line @typescript-eslint/no-var-requires 15 const presetVersion = require('../package.json').version; 16 17 // TODO: update below to customize the workspace 18 const { directory } = await createWorkspace(`my-plugin@${presetVersion}`, { 19 name, 20 nxCloud: false, 21 packageManager: 'npm', 22 }); 23 24 console.log(`Successfully created the workspace: ${directory}.`); 25} 26 27main(); 28

This is a plain node script at this point, and you can use any dependencies you wish to handle things like prompting or argument parsing. Keeping dependencies small and splitting out the command line tool from the Nx plugin is recommended, and will help keep your CLI feeling fast and snappy.

Note the following code snippet:

1const { directory } = await createWorkspace(`my-plugin@${presetVersion}`, { 2 name, 3 nxCloud: false, 4 packageManager: 'npm', 5}); 6

This will invoke the my-plugin package's preset generator, which will contain the logic for setting up the workspace. This preset generator will be invoked when running either npx create-nx-workspace --preset my-plugin or npx create-my-plugin. For more information about customizing your preset, see: Creating a Preset.

Testing your create package

Because your create-my-plugin package will install your plugin package at runtime, both packages must be published in order to run them and see the results. To test your packages without making them publicly available, a local-registry target should be present on project in your workspace's root.

project.json
1{ 2 ... 3 "targets": { 4 "local-registry": { 5 "executor": "@nx/js:verdaccio", 6 "options": { 7 "port": 4873, 8 "config": ".verdaccio/config.yml", 9 "storage": "tmp/local-registry/storage" 10 } 11 } 12 } 13} 14
Nx 15 and lower use @nrwl/ instead of @nx/

(If you don't have such a local-registry target, refer to the following docs page to generate one)

By running

npx nx local-registry

...a local instance of Verdaccio will be launched at http://localhost:4873 and the NPM, Yarn and PNPM registries will be configured to point to it. This means that you can safely publish, without hitting npm, and test as if you were an end user of your package.

Registry Cleanup & Reset

Note, after terminating the terminal window where the nx local-registry command is running (e.g. using CTRL+c or CMD+c) the registry will be stopped, previously installed packages will be cleaned up and the npm/yarn/pnpm registries will be restored to their original state, pointing to the real NPM servers again.

Next, you can publish your packages to your new local registry. All of the generated packages have a publish target, so you can simply run:

npx nx run-many --targets publish --ver 1.0.0 --tag latest

Once the packages are published, you should be able to test the behavior of your "create package" as follows:

npx create-my-plugin test-workspace

Writing and running e2e tests

When setting up the workspace, you should also have gotten a my-plugin-e2e package. This package contains the e2e tests for your plugin, and can be run with the following command:

npx nx e2e my-plugin-e2e

Have a look at some of the example tests that were generated for you. When running these tests,

  • the local registry will be started automatically
  • a new version of the packages will be deployed
  • then your test commands will be run (usually triggering processes that setup the workspace, just like the user would type into a command line interface)
  • once the test commands have finished, the local registry will be stopped again and cleaned up

Publishing your create package

Your plugin and create package will both need to be published to NPM to be useable. Publishing your packages is exactly the same as described previously, except that you don't run the local-registry task so that the publish task will publish to the real NPM servers.

Further Reading