@leyman/nx-plugin 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +216 -2
- package/dist/executors/targets.d.ts +30 -0
- package/dist/executors/targets.js +31 -0
- package/dist/generators/init.d.ts +82 -0
- package/dist/generators/init.js +190 -0
- package/dist/generators/package.d.ts +79 -0
- package/dist/generators/package.js +208 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +12 -0
- package/dist/lib/infer.d.ts +45 -0
- package/dist/lib/infer.js +66 -0
- package/dist/lib/options.d.ts +154 -0
- package/dist/lib/options.js +75 -0
- package/dist/lib/plugin.d.ts +12 -0
- package/dist/lib/plugin.js +38 -0
- package/dist/lib/projects.d.ts +27 -0
- package/dist/lib/projects.js +44 -0
- package/dist/lib/schema.d.ts +83 -0
- package/dist/lib/schema.js +255 -0
- package/dist/lib/sync.d.ts +31 -0
- package/dist/lib/sync.js +109 -0
- package/dist/lib/targets.d.ts +17 -0
- package/dist/lib/targets.js +266 -0
- package/dist/lib/versions.d.ts +42 -0
- package/dist/lib/versions.js +65 -0
- package/executors.json +9 -0
- package/generators.json +14 -0
- package/out/executors/targets/schema.json +22 -0
- package/out/generators/init/schema.json +25 -0
- package/out/generators/package/schema.json +79 -0
- package/out/schema.json +624 -0
- package/package.json +86 -3
- package/scripts/biome-format.sh +10 -0
- package/scripts/clean-dist.sh +26 -0
- package/scripts/coverage-report.sh +22 -0
- package/scripts/swc-compile.sh +51 -0
- package/scripts/sync-injected.mjs +143 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jacob Ley
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,217 @@
|
|
|
1
|
-
|
|
1
|
+
<div style="text-align:center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# @leyman/nx-plugin
|
|
4
|
+
|
|
5
|
+
Nx plugin inferring the build, test, lint and package-check targets of TypeScript ESM packages: SWC and tsc builds, Mocha or Vitest under c8, ESLint, Biome and check-package.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@leyman/nx-plugin)
|
|
8
|
+
[](https://github.com/JacobLey/leyman/blob/main/leyman/nx-plugin/LICENSE)
|
|
9
|
+
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
- **Targets from files** — a project with `src/` and `tsconfig.json` gets `tsc`, one with `test/unit` and Mocha installed gets `mocha-unit-test`, a public one gets `check-package`, and so on.
|
|
13
|
+
- **Cached precisely** — each target hashes only what its result depends on: its sources, the shared configs, and the built output of its dependencies (not their source), so editing a dependency's tests reruns nothing downstream.
|
|
14
|
+
- **Builds that respect the support floor** — SWC compiles to the ES year of the oldest Node.js `engines.node` allows, and `tsc` checks types and writes declarations.
|
|
15
|
+
- **Coverage over all of a project's tests** — each test target runs under c8, and `coverage-report` checks the coverage of all of them together.
|
|
16
|
+
- **Overridable** — rename a target, turn it off, include or exclude projects, or override any part in `project.json`.
|
|
17
|
+
- **Generators** — `init` sets an Nx workspace up for the plugin (`nx add @leyman/nx-plugin` runs it), and `package` adds a package with the targets it gets already declared.
|
|
18
|
+
- **Or written out** — a workspace that can't load the plugin (because it builds it, say) writes the same targets into `nx.json` and each `project.json` with the `targets` executor, which fails on drift in CI.
|
|
19
|
+
|
|
20
|
+
It is the setup of the [leyman](https://github.com/JacobLey/leyman) monorepo, published as-is. Expect it to be opinionated and to change: see [Versioning](#versioning).
|
|
21
|
+
|
|
22
|
+
## Contents
|
|
23
|
+
|
|
24
|
+
- [Install](#install)
|
|
25
|
+
- [Example](#example)
|
|
26
|
+
- [Usage](#usage)
|
|
27
|
+
- [Targets](#targets)
|
|
28
|
+
- [Options](#options)
|
|
29
|
+
- [Generators](#generators)
|
|
30
|
+
- [The targets executor](#the-targets-executor)
|
|
31
|
+
- [API](#api)
|
|
32
|
+
- [Versioning](#versioning)
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
nx add @leyman/nx-plugin
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
That installs the plugin and runs its [`init`](#init) generator. Or install it yourself:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
npm i -D @leyman/nx-plugin @leyman/configs
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Each project then installs what its targets run: `@swc/cli`, `@swc/core` and `typescript` to build, `mocha` or `vitest` and `c8` to test, `eslint`, and `@biomejs/biome` at the workspace root. `check-package` runs [`@leyman/check-package`](https://www.npmjs.com/package/@leyman/check-package), and `update-ts-references` needs `@nx/js`.
|
|
47
|
+
|
|
48
|
+
## Example
|
|
49
|
+
|
|
50
|
+
`nx.json`:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"plugins": [
|
|
55
|
+
{
|
|
56
|
+
"plugin": "@leyman/nx-plugin",
|
|
57
|
+
"options": {
|
|
58
|
+
"checkPackage": { "profile": "esm-only" }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
A package with `package.json` (with `mocha` in `devDependencies`), `project.json`, `tsconfig.json`, `src/`, `test/tsconfig.json`, `test/unit/` and `eslint.config.js` gets `biome`, `eslint`, `check-package`, `update-ts-references`, `tsc`, `tsc-test`, `mocha-unit-test` and `coverage-report`:
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
nx run my-package:tsc
|
|
69
|
+
nx run-many -t mocha-unit-test
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Usage
|
|
73
|
+
|
|
74
|
+
The plugin infers targets for each project with a `project.json`. It says how each target runs and what it hashes, not when: wire the order yourself with `dependsOn` (in `nx.json` `targetDefaults` or `project.json`), or with [nx-lifecycle](https://www.npmjs.com/package/nx-lifecycle), binding each target to a stage. nx-lifecycle wires the targets a `project.json` declares, so declare them there as `{}`.
|
|
75
|
+
|
|
76
|
+
Targets run from the project's directory. The defaults read the shared configs from the project's own `node_modules/@leyman/configs`, so each project installs [`@leyman/configs`](https://www.npmjs.com/package/@leyman/configs), as its `tsconfig.json` extends it anyway.
|
|
77
|
+
|
|
78
|
+
Anything in `project.json` is merged over the inferred target, key by key, so a project can change one option of a target and keep the rest.
|
|
79
|
+
|
|
80
|
+
## Targets
|
|
81
|
+
|
|
82
|
+
| Target | Inferred when | Runs |
|
|
83
|
+
| ------------------------ | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
84
|
+
| `biome` | Always | `biome format --write .`, or only checks in CI. `-c check` only checks. |
|
|
85
|
+
| `eslint` | `eslint.config.js` | `eslint` on the project. `-c fix` fixes. |
|
|
86
|
+
| `check-package` | `tsc` is, and `package.json` isn't `private` | `check-package`, on the package as it would be published. |
|
|
87
|
+
| `update-ts-references` | `tsconfig.json` | Nx's TypeScript sync generator, which keeps `references` in step with dependencies. |
|
|
88
|
+
| `tsc` | `tsconfig.json` and `src/` | Clears `dist/`, compiles `src/` with SWC, then `tsc` for types and declarations. |
|
|
89
|
+
| `tsc-test` | `test/tsconfig.json` | Compiles `test/` into `dist-test/` with SWC, then type-checks it with `tsc -p ./test`. |
|
|
90
|
+
| `mocha-unit-test` | `test/unit/`, and `mocha` in `devDependencies` | Mocha on `dist-test/unit/**/*.spec.*js`, under c8. |
|
|
91
|
+
| `mocha-integration-test` | `test/integration/`, and `mocha` in `devDependencies` | Mocha on `dist-test/integration/**/*.spec.*js`, under c8, with a 10 second timeout. |
|
|
92
|
+
| `vitest-unit-test` | `test/unit/`, and `vitest` but not `mocha` in `devDependencies` | Vitest on the compiled tests, under c8. |
|
|
93
|
+
| `coverage-report` | `tsc` and a test target are | Checks the coverage the test targets wrote, together, against the c8 config's thresholds. |
|
|
94
|
+
| `sync-injected` | Another project's `package.json` depends on this one | Mirrors the package into the copies pnpm injected into its dependents (`injectWorkspacePackages`), after a build. |
|
|
95
|
+
|
|
96
|
+
`tsc`, `tsc-test`, `coverage-report`, `biome` and `sync-injected` run the package's scripts, as its bins:
|
|
97
|
+
|
|
98
|
+
| Bin | Usage |
|
|
99
|
+
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
100
|
+
| `leyman-clean-dist` | Clears `dist/`, keeping `tsc`'s build info and declarations while they are exactly those of the current sources, so `tsc` stays fast. |
|
|
101
|
+
| `leyman-swc-compile` | `leyman-swc-compile <source> <output> <swcrc>`: compiles with SWC, to the ES year of the package's support floor. |
|
|
102
|
+
| `leyman-coverage-report` | `leyman-coverage-report <c8rc> [c8 options...]` |
|
|
103
|
+
| `leyman-biome-format` | Formats with Biome; only checks in CI. |
|
|
104
|
+
| `leyman-sync-injected` | Mirrors the package into its injected copies, and writes the fields of `package.json` that dependents hash. |
|
|
105
|
+
|
|
106
|
+
The targets also define the named inputs `dependency-files`, `package-files`, `eslint-input`, `ts-source`, `dependency-builds` and `test-source` on each project.
|
|
107
|
+
|
|
108
|
+
## Options
|
|
109
|
+
|
|
110
|
+
All optional.
|
|
111
|
+
|
|
112
|
+
| Option | Default | Description |
|
|
113
|
+
| ---------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
114
|
+
| `targets.<target>` | `{}` | `false` to infer the target nowhere, or `{ name, include, exclude }`: its name, and roots of projects that always or never get it. |
|
|
115
|
+
| `configs.swc` | `node_modules/@leyman/configs/swcrc.jsonc` | SWC config, relative to each project. |
|
|
116
|
+
| `configs.c8` | `node_modules/@leyman/configs/c8rc.json` | c8 config, relative to each project. |
|
|
117
|
+
| `configs.vitest` | `node_modules/@leyman/configs/vitest.config.js` | Vitest config, relative to each project. |
|
|
118
|
+
| `coverage` | — | `{ branches, functions, lines, statements }`: thresholds passed to c8 over the c8 config's. |
|
|
119
|
+
| `checkPackage.profile` | check-package's, `strict` | `strict`, `node16` or `esm-only`. |
|
|
120
|
+
| `checkPackage.floorTsconfig` | `@leyman/configs/tsconfig.node-{major}.json` | The config a package's `tsconfig.json` must extend for its support floor, or `false`. |
|
|
121
|
+
| `checkPackage.inputs` | `[{ "externalDependencies": ["@leyman/check-package", "typescript"] }]` | What a change of check-package itself is hashed by. |
|
|
122
|
+
| `inputs.configs` | `[{ "externalDependencies": ["@leyman/configs"] }]` | What a change of the shared configs is hashed by. A workspace keeping them in a project of its own lists their paths instead. |
|
|
123
|
+
| `inputs.tools` | `[{ "externalDependencies": ["@leyman/nx-plugin"] }]` | What a change of the scripts is hashed by. |
|
|
124
|
+
| `scripts` | — | A directory to run the scripts from instead of the bins, such as `${NX_WORKSPACE_ROOT}/tools/nx-plugin/scripts` for a copy in the workspace. |
|
|
125
|
+
| `namedInputs.<name>` | — | Inputs added to the end of a named input, such as `{ "test-source": ["{projectRoot}/schema.sql"] }` for a file the tests read. |
|
|
126
|
+
|
|
127
|
+
The options are checked against [`out/schema.json`](./out/schema.json); an invalid one fails with its path.
|
|
128
|
+
|
|
129
|
+
## Generators
|
|
130
|
+
|
|
131
|
+
### `init`
|
|
132
|
+
|
|
133
|
+
```sh
|
|
134
|
+
nx g @leyman/nx-plugin:init
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Sets the workspace up for the plugin, leaving alone anything already set:
|
|
138
|
+
|
|
139
|
+
- `nx.json`: the plugin, and [nx-lifecycle](https://www.npmjs.com/package/nx-lifecycle)'s plugin with the stages install, prepare (generate, format), check (lint), build (run, post, sync), test (compile, run, report) and verify, and each target bound to one, under the name the options give it. `--lifecycle=false` leaves nx-lifecycle out.
|
|
140
|
+
- `devDependencies` of the root `package.json`: the plugin, `@leyman/configs`, `@biomejs/biome`, `@leyman/check-package`, `@nx/js` and `nx-lifecycle`.
|
|
141
|
+
- `biome.json`, extending `@leyman/configs/biome.base.json`.
|
|
142
|
+
- `pnpm-workspace.yaml`, if there is one: `injectWorkspacePackages: true` and `dedupeInjectedDeps: false`, so dependents get workspace packages as published, which `sync-injected` keeps current.
|
|
143
|
+
- `.claude/skills/principles/SKILL.md`, pointing coding agents at the [principles](https://jacobley.github.io/leyman/framework/principles) of the setup. `--skill=false` leaves it out.
|
|
144
|
+
|
|
145
|
+
Then it installs the dependencies, unless `--skipInstall`.
|
|
146
|
+
|
|
147
|
+
### `package`
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
nx g @leyman/nx-plugin:package packages/my-lib --description "Does things."
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Adds a TypeScript ESM package: `package.json`, `tsconfig.json` (extending `@leyman/configs`), `src/index.ts`, a first test with its `test/tsconfig.json`, `eslint.config.js`, a `README.md`, and a `project.json` declaring as `{}` the targets the plugin infers from those files, so nx-lifecycle wires them. Then it installs the dependencies, unless `--skipInstall`, and `nx sync` adds the `tsconfig.json` references.
|
|
154
|
+
|
|
155
|
+
| Option | Default | Description |
|
|
156
|
+
| ----------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
157
|
+
| `directory` | — | Where the package goes, relative to the workspace root. The first argument. |
|
|
158
|
+
| `description` | — | For `package.json` and the README. |
|
|
159
|
+
| `name` | The directory's last part | Name of the package. |
|
|
160
|
+
| `published` | `false` | Published to npm, rather than `private`: it gets `engines`, extends its support floor's `@leyman/configs/tsconfig.node-{floor}.json`, takes `@types/node` of that major, and the workspace's `LICENSE`. |
|
|
161
|
+
| `floor` | `22` | Major of the oldest Node.js a published package supports. |
|
|
162
|
+
| `test` | `mocha` | `mocha`, `vitest`, or `none`. |
|
|
163
|
+
| `repository` | — | URL of the workspace's GitHub repository, for a published package's `repository`, `homepage` and `bugs`. |
|
|
164
|
+
| `author` | — | `author` of `package.json`. |
|
|
165
|
+
| `license` | — | `license` of `package.json`. |
|
|
166
|
+
| `devDependencies` | — | More packages to add to `devDependencies`. |
|
|
167
|
+
| `config` | The options in `nx.json` | A file of the plugin [options](#options), for a workspace running the [targets executor](#the-targets-executor). |
|
|
168
|
+
| `skipInstall` | `false` | Don't install the dependencies. |
|
|
169
|
+
|
|
170
|
+
Set the ones a workspace always passes as defaults in `nx.json`:
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"generators": {
|
|
175
|
+
"@leyman/nx-plugin": {
|
|
176
|
+
"package": { "repository": "https://github.com/owner/repo", "license": "MIT" }
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Each dependency's version comes from the workspace if it can: `workspace:^` for one of its own packages, `catalog:` for one in the pnpm catalog (`catalog:node-{floor}` for a published package's `@types/node`), or the range another package already uses. Otherwise it is the version this plugin was published with, and a package with none of those fails.
|
|
183
|
+
|
|
184
|
+
## The targets executor
|
|
185
|
+
|
|
186
|
+
A workspace that can't load the plugin, such as one that builds it, writes the same targets out instead:
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"targets": {
|
|
191
|
+
"nx-plugin-targets": {
|
|
192
|
+
"executor": "@leyman/nx-plugin:targets",
|
|
193
|
+
"options": { "config": "tools/nx-plugin.json" }
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`config` is a file of the [options](#options), relative to the workspace root, which can name `./node_modules/@leyman/nx-plugin/out/schema.json` as its `$schema`. The executor writes the named inputs and target defaults into `nx.json`, keeping each target default's `dependsOn`, and into each `project.json` the targets the project gets, as `{}`. A declaration with options of its own is kept. With `check` (the default when `CI` is set), it fails instead of writing when a file is out of date.
|
|
200
|
+
|
|
201
|
+
## API
|
|
202
|
+
|
|
203
|
+
The plugin's pieces, for tools built on it:
|
|
204
|
+
|
|
205
|
+
- `createNodes`, `name`: the Nx plugin.
|
|
206
|
+
- `resolveOptions(options?)`: every option, with defaults.
|
|
207
|
+
- `targetConfigurations(resolved)`, `namedInputs(resolved)`: each target's configuration and each named input.
|
|
208
|
+
- `inferTargets(project, resolved)`: the targets a project gets, from `{ root, packageJson, isDependency, has(path) }`; `inferencePaths` are the paths `has` is asked about.
|
|
209
|
+
- `loadProjects(workspaceRoot, roots)`: reads that for each project.
|
|
210
|
+
- `syncTargets({ root, projects, options, optionsSource, check })`: what the executor does.
|
|
211
|
+
- `initGenerator(tree, options?)`, `packageGenerator(tree, options)`: the generators. `lifecycleStages` and `lifecycleBindings` are the nx-lifecycle options `init` writes.
|
|
212
|
+
- `readWorkspaceVersions(tree)`, `resolveVersion(name, versions)`: the version a generator gives a dependency.
|
|
213
|
+
- `assertOptions(options, where)`, `optionsJsonSchema`, `executorJsonSchema`, `initGeneratorJsonSchema`, `packageGeneratorJsonSchema`: option validation and the JSON schemas.
|
|
214
|
+
|
|
215
|
+
## Versioning
|
|
216
|
+
|
|
217
|
+
Before 1.0, a release that can make a passing workspace fail (a new target inferred, a stricter command, a changed default) is a minor, and other changes are patches.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ExecutorContext } from '@nx/devkit';
|
|
2
|
+
/**
|
|
3
|
+
* Where the plugin options are, and whether to only check them.
|
|
4
|
+
*/
|
|
5
|
+
export interface TargetsExecutorOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Path of the file holding the plugin options, relative to the workspace root.
|
|
8
|
+
*/
|
|
9
|
+
readonly config: string;
|
|
10
|
+
/**
|
|
11
|
+
* Fail rather than write when a file is out of date.
|
|
12
|
+
*
|
|
13
|
+
* @default true in CI (`CI` set to anything but `false` or `0`)
|
|
14
|
+
*/
|
|
15
|
+
readonly check?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Nx executor writing the plugin's named inputs and target defaults into `nx.json`, and the targets each project
|
|
19
|
+
* gets into its `project.json`, from the options in a file.
|
|
20
|
+
*
|
|
21
|
+
* @param options - where the plugin options are, and whether to only check
|
|
22
|
+
* @param context - Nx executor context
|
|
23
|
+
* @returns success, as failures reject
|
|
24
|
+
* @rejects {Error} when the options are invalid, or a file is out of date in `check` mode
|
|
25
|
+
*/
|
|
26
|
+
declare const targetsExecutor: (options: TargetsExecutorOptions, context: Pick<ExecutorContext, 'projectsConfigurations' | 'root'>) => Promise<{
|
|
27
|
+
success: boolean;
|
|
28
|
+
}>;
|
|
29
|
+
export default targetsExecutor;
|
|
30
|
+
//# sourceMappingURL=targets.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import Path from 'node:path';
|
|
3
|
+
import { syncTargets } from '../lib/sync.js';
|
|
4
|
+
const isCi = (ci)=>![
|
|
5
|
+
'',
|
|
6
|
+
'0',
|
|
7
|
+
'false',
|
|
8
|
+
undefined
|
|
9
|
+
].includes(ci);
|
|
10
|
+
const targetsExecutor = async (options, context)=>{
|
|
11
|
+
const config = Path.join(context.root, options.config);
|
|
12
|
+
const written = await syncTargets({
|
|
13
|
+
root: context.root,
|
|
14
|
+
projects: Object.entries(context.projectsConfigurations.projects).map(([name, { root }])=>({
|
|
15
|
+
name,
|
|
16
|
+
root
|
|
17
|
+
})),
|
|
18
|
+
options: JSON.parse(await readFile(config, 'utf8')),
|
|
19
|
+
optionsSource: options.config,
|
|
20
|
+
check: options.check ?? isCi(process.env.CI)
|
|
21
|
+
});
|
|
22
|
+
for (const path of written){
|
|
23
|
+
console.info(`Updated ${Path.relative(context.root, path)}`);
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
success: true
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export default targetsExecutor;
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=targets.js.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { GeneratorCallback, Tree } from '@nx/devkit';
|
|
2
|
+
/**
|
|
3
|
+
* What the `init` generator sets up.
|
|
4
|
+
*/
|
|
5
|
+
export interface InitGeneratorOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Wire the targets' order with nx-lifecycle.
|
|
8
|
+
*
|
|
9
|
+
* @default true
|
|
10
|
+
*/
|
|
11
|
+
readonly lifecycle?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Write a `.claude/skills/principles/SKILL.md` pointing coding agents at the principles behind the setup.
|
|
14
|
+
*
|
|
15
|
+
* @default true
|
|
16
|
+
*/
|
|
17
|
+
readonly skill?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Don't install dependencies afterwards.
|
|
20
|
+
*
|
|
21
|
+
* @default false
|
|
22
|
+
*/
|
|
23
|
+
readonly skipInstall?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The stages the plugin's targets run in, as nx-lifecycle options: install, prepare (generate, format), check (lint),
|
|
27
|
+
* build (run, post, sync), test (compile, run, report) and verify.
|
|
28
|
+
*/
|
|
29
|
+
export declare const lifecycleStages: {
|
|
30
|
+
readonly install: {
|
|
31
|
+
readonly dependsOn: readonly ['^build'];
|
|
32
|
+
};
|
|
33
|
+
readonly prepare: {
|
|
34
|
+
readonly hooks: readonly ['generate', 'format'];
|
|
35
|
+
readonly dependsOn: readonly ['install'];
|
|
36
|
+
};
|
|
37
|
+
readonly check: {
|
|
38
|
+
readonly hooks: readonly ['lint'];
|
|
39
|
+
readonly dependsOn: readonly ['^build', 'prepare', 'build'];
|
|
40
|
+
};
|
|
41
|
+
readonly build: {
|
|
42
|
+
readonly hooks: readonly ['run', 'post', 'sync'];
|
|
43
|
+
readonly dependsOn: readonly ['^build', 'prepare'];
|
|
44
|
+
};
|
|
45
|
+
readonly test: {
|
|
46
|
+
readonly hooks: readonly ['compile', 'run', 'report'];
|
|
47
|
+
readonly dependsOn: readonly ['build'];
|
|
48
|
+
};
|
|
49
|
+
readonly verify: {
|
|
50
|
+
readonly dependsOn: readonly ['check', 'test'];
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The stage hook each of the plugin's targets is bound to.
|
|
55
|
+
*/
|
|
56
|
+
export declare const lifecycleBindings: {
|
|
57
|
+
readonly biome: 'prepare:format';
|
|
58
|
+
readonly eslint: 'check:lint';
|
|
59
|
+
readonly 'check-package': 'check:lint';
|
|
60
|
+
readonly 'update-ts-references': 'prepare:generate';
|
|
61
|
+
readonly tsc: 'build:run';
|
|
62
|
+
readonly 'tsc-test': 'test:compile';
|
|
63
|
+
readonly 'mocha-unit-test': 'test:run';
|
|
64
|
+
readonly 'mocha-integration-test': 'test:run';
|
|
65
|
+
readonly 'vitest-unit-test': 'test:run';
|
|
66
|
+
readonly 'coverage-report': 'test:report';
|
|
67
|
+
readonly 'sync-injected': 'build:sync';
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Nx generator setting an Nx workspace up for the plugin: adds it (and nx-lifecycle, with stages and bindings for its
|
|
71
|
+
* targets) to `nx.json`, installs it and the tools its targets run at the root, extends the shared Biome config, has
|
|
72
|
+
* pnpm inject workspace packages, and points coding agents at the principles. Run by `nx add @leyman/nx-plugin`.
|
|
73
|
+
* Leaves alone what is already set.
|
|
74
|
+
*
|
|
75
|
+
* @param tree - Nx's view of the files
|
|
76
|
+
* @param options - what to set up
|
|
77
|
+
* @returns the task installing dependencies, unless `skipInstall`
|
|
78
|
+
* @rejects {Error} when the plugin's options in `nx.json` are invalid
|
|
79
|
+
*/
|
|
80
|
+
declare const initGenerator: (tree: Tree, options?: InitGeneratorOptions) => Promise<GeneratorCallback | undefined>;
|
|
81
|
+
export default initGenerator;
|
|
82
|
+
//# sourceMappingURL=init.d.ts.map
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { installPackagesTask, readJson, readNxJson, writeJson } from '@nx/devkit';
|
|
2
|
+
import { parse } from 'yaml';
|
|
3
|
+
import { resolveOptions } from '../lib/options.js';
|
|
4
|
+
import { name as pluginName } from '../lib/plugin.js';
|
|
5
|
+
import { assertOptions } from '../lib/schema.js';
|
|
6
|
+
import { readWorkspaceVersions, resolveVersion } from '../lib/versions.js';
|
|
7
|
+
export const lifecycleStages = {
|
|
8
|
+
install: {
|
|
9
|
+
dependsOn: [
|
|
10
|
+
'^build'
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
prepare: {
|
|
14
|
+
hooks: [
|
|
15
|
+
'generate',
|
|
16
|
+
'format'
|
|
17
|
+
],
|
|
18
|
+
dependsOn: [
|
|
19
|
+
'install'
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
check: {
|
|
23
|
+
hooks: [
|
|
24
|
+
'lint'
|
|
25
|
+
],
|
|
26
|
+
dependsOn: [
|
|
27
|
+
'^build',
|
|
28
|
+
'prepare',
|
|
29
|
+
'build'
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
build: {
|
|
33
|
+
hooks: [
|
|
34
|
+
'run',
|
|
35
|
+
'post',
|
|
36
|
+
'sync'
|
|
37
|
+
],
|
|
38
|
+
dependsOn: [
|
|
39
|
+
'^build',
|
|
40
|
+
'prepare'
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
test: {
|
|
44
|
+
hooks: [
|
|
45
|
+
'compile',
|
|
46
|
+
'run',
|
|
47
|
+
'report'
|
|
48
|
+
],
|
|
49
|
+
dependsOn: [
|
|
50
|
+
'build'
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
verify: {
|
|
54
|
+
dependsOn: [
|
|
55
|
+
'check',
|
|
56
|
+
'test'
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
export const lifecycleBindings = {
|
|
61
|
+
biome: 'prepare:format',
|
|
62
|
+
eslint: 'check:lint',
|
|
63
|
+
'check-package': 'check:lint',
|
|
64
|
+
'update-ts-references': 'prepare:generate',
|
|
65
|
+
tsc: 'build:run',
|
|
66
|
+
'tsc-test': 'test:compile',
|
|
67
|
+
'mocha-unit-test': 'test:run',
|
|
68
|
+
'mocha-integration-test': 'test:run',
|
|
69
|
+
'vitest-unit-test': 'test:run',
|
|
70
|
+
'coverage-report': 'test:report',
|
|
71
|
+
'sync-injected': 'build:sync'
|
|
72
|
+
};
|
|
73
|
+
const lifecyclePlugin = 'nx-lifecycle/plugin';
|
|
74
|
+
const rootDependencies = [
|
|
75
|
+
pluginName,
|
|
76
|
+
'@biomejs/biome',
|
|
77
|
+
'@leyman/check-package',
|
|
78
|
+
'@leyman/configs',
|
|
79
|
+
'@nx/js'
|
|
80
|
+
];
|
|
81
|
+
const principlesSkill = `---
|
|
82
|
+
name: principles
|
|
83
|
+
description: The principles behind this setup (the bar for packages, versioning, support floors, dependencies, lint, generated content, patches, and how work is committed and verified), and why
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
# Principles
|
|
87
|
+
|
|
88
|
+
Read the [principles](https://jacobley.github.io/leyman/framework/principles) of the setup this workspace follows before a design, a review or an audit, and decide anything no other skill covers by them.
|
|
89
|
+
`;
|
|
90
|
+
const pluginOf = (entry)=>typeof entry === 'string' ? entry : entry.plugin;
|
|
91
|
+
const injectSettings = `# Install workspace dependencies as copies of their published files, like a package from the registry.
|
|
92
|
+
# Builds keep the copies current (\`sync-injected\`).
|
|
93
|
+
injectWorkspacePackages: true
|
|
94
|
+
`;
|
|
95
|
+
const dedupeSettings = `# Otherwise pnpm symlinks to the source again whenever peers allow it
|
|
96
|
+
dedupeInjectedDeps: false
|
|
97
|
+
`;
|
|
98
|
+
const injectWorkspacePackages = (tree)=>{
|
|
99
|
+
const yaml = tree.read('pnpm-workspace.yaml', 'utf8');
|
|
100
|
+
if (yaml === null) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const settings = parse(yaml) ?? {};
|
|
104
|
+
const added = [
|
|
105
|
+
...Object.hasOwn(settings, 'injectWorkspacePackages') ? [] : [
|
|
106
|
+
injectSettings
|
|
107
|
+
],
|
|
108
|
+
...Object.hasOwn(settings, 'dedupeInjectedDeps') ? [] : [
|
|
109
|
+
dedupeSettings
|
|
110
|
+
]
|
|
111
|
+
];
|
|
112
|
+
if (added.length === 0) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const separator = yaml === '' || yaml.endsWith('\n') ? '' : '\n';
|
|
116
|
+
tree.write('pnpm-workspace.yaml', `${yaml}${separator}${added.join('')}`);
|
|
117
|
+
};
|
|
118
|
+
const initGenerator = async (tree, options = {})=>{
|
|
119
|
+
const { lifecycle = true, skill = true } = options;
|
|
120
|
+
const nxJson = readNxJson(tree) ?? {};
|
|
121
|
+
const plugins = [
|
|
122
|
+
...nxJson.plugins ?? []
|
|
123
|
+
];
|
|
124
|
+
const existing = plugins.find((entry)=>pluginOf(entry) === pluginName);
|
|
125
|
+
if (existing === undefined) {
|
|
126
|
+
plugins.push({
|
|
127
|
+
plugin: pluginName,
|
|
128
|
+
options: {}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const pluginOptions = typeof existing === 'object' ? existing.options ?? {} : {};
|
|
132
|
+
assertOptions(pluginOptions, 'nx.json');
|
|
133
|
+
const resolved = resolveOptions(pluginOptions);
|
|
134
|
+
if (lifecycle && plugins.every((entry)=>pluginOf(entry) !== lifecyclePlugin)) {
|
|
135
|
+
const bindings = Object.entries(lifecycleBindings).flatMap(([target, hook])=>{
|
|
136
|
+
const targetOptions = resolved.targets[target];
|
|
137
|
+
return targetOptions === false ? [] : [
|
|
138
|
+
[
|
|
139
|
+
targetOptions.name,
|
|
140
|
+
hook
|
|
141
|
+
]
|
|
142
|
+
];
|
|
143
|
+
});
|
|
144
|
+
plugins.push({
|
|
145
|
+
plugin: lifecyclePlugin,
|
|
146
|
+
options: {
|
|
147
|
+
stages: lifecycleStages,
|
|
148
|
+
bindings: Object.fromEntries(bindings)
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
writeJson(tree, 'nx.json', {
|
|
153
|
+
...nxJson,
|
|
154
|
+
plugins
|
|
155
|
+
});
|
|
156
|
+
const versions = await readWorkspaceVersions(tree);
|
|
157
|
+
const packageJson = readJson(tree, 'package.json');
|
|
158
|
+
const devDependencies = {
|
|
159
|
+
...packageJson.devDependencies
|
|
160
|
+
};
|
|
161
|
+
for (const dependency of [
|
|
162
|
+
...rootDependencies,
|
|
163
|
+
...lifecycle ? [
|
|
164
|
+
'nx-lifecycle'
|
|
165
|
+
] : []
|
|
166
|
+
]){
|
|
167
|
+
devDependencies[dependency] ??= resolveVersion(dependency, versions);
|
|
168
|
+
}
|
|
169
|
+
writeJson(tree, 'package.json', {
|
|
170
|
+
...packageJson,
|
|
171
|
+
devDependencies
|
|
172
|
+
});
|
|
173
|
+
if (!tree.exists('biome.json') && !tree.exists('biome.jsonc')) {
|
|
174
|
+
writeJson(tree, 'biome.json', {
|
|
175
|
+
$schema: './node_modules/@biomejs/biome/configuration_schema.json',
|
|
176
|
+
extends: [
|
|
177
|
+
'@leyman/configs/biome.base.json'
|
|
178
|
+
]
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
injectWorkspacePackages(tree);
|
|
182
|
+
const skillPath = '.claude/skills/principles/SKILL.md';
|
|
183
|
+
if (skill && !tree.exists(skillPath)) {
|
|
184
|
+
tree.write(skillPath, principlesSkill);
|
|
185
|
+
}
|
|
186
|
+
return options.skipInstall === true ? undefined : installPackagesTask.bind(undefined, tree);
|
|
187
|
+
};
|
|
188
|
+
export default initGenerator;
|
|
189
|
+
|
|
190
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { GeneratorCallback, Tree } from '@nx/devkit';
|
|
2
|
+
/**
|
|
3
|
+
* What the `package` generator adds, and where.
|
|
4
|
+
*/
|
|
5
|
+
export interface PackageGeneratorOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Where the package goes, relative to the workspace root, e.g. `packages/my-lib`.
|
|
8
|
+
*/
|
|
9
|
+
readonly directory: string;
|
|
10
|
+
/**
|
|
11
|
+
* A placeholder export, for the first test to import until the package has an API.
|
|
12
|
+
*
|
|
13
|
+
* @default the directory's last segment
|
|
14
|
+
*/
|
|
15
|
+
readonly name?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Description, for `package.json` and the README.
|
|
18
|
+
*/
|
|
19
|
+
readonly description: string;
|
|
20
|
+
/**
|
|
21
|
+
* Published to npm, rather than private.
|
|
22
|
+
*
|
|
23
|
+
* @default false
|
|
24
|
+
*/
|
|
25
|
+
readonly published?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Major of the oldest Node.js a published package supports.
|
|
28
|
+
*
|
|
29
|
+
* @default 22
|
|
30
|
+
*/
|
|
31
|
+
readonly floor?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Test runner, or `none` for no tests.
|
|
34
|
+
*
|
|
35
|
+
* @default 'mocha'
|
|
36
|
+
*/
|
|
37
|
+
readonly test?: 'mocha' | 'none' | 'vitest';
|
|
38
|
+
/**
|
|
39
|
+
* URL of the workspace's GitHub repository, for a published package's `repository`, `homepage` and `bugs`.
|
|
40
|
+
*/
|
|
41
|
+
readonly repository?: string;
|
|
42
|
+
/**
|
|
43
|
+
* `author` of `package.json`.
|
|
44
|
+
*/
|
|
45
|
+
readonly author?: string;
|
|
46
|
+
/**
|
|
47
|
+
* `license` of `package.json`. A published package also gets a copy of the workspace's `LICENSE`.
|
|
48
|
+
*/
|
|
49
|
+
readonly license?: string;
|
|
50
|
+
/**
|
|
51
|
+
* More packages to add to `devDependencies`.
|
|
52
|
+
*/
|
|
53
|
+
readonly devDependencies?: readonly string[];
|
|
54
|
+
/**
|
|
55
|
+
* File of the plugin options, relative to the workspace root, for a workspace running the `targets` executor.
|
|
56
|
+
*
|
|
57
|
+
* @default the options of the plugin in `nx.json`
|
|
58
|
+
*/
|
|
59
|
+
readonly config?: string;
|
|
60
|
+
/**
|
|
61
|
+
* Don't install dependencies afterwards.
|
|
62
|
+
*
|
|
63
|
+
* @default false
|
|
64
|
+
*/
|
|
65
|
+
readonly skipInstall?: boolean;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Nx generator adding a TypeScript ESM package: its `package.json`, configs, a first source file and test, and a
|
|
69
|
+
* `project.json` declaring the targets the plugin infers from them.
|
|
70
|
+
*
|
|
71
|
+
* @param tree - the workspace
|
|
72
|
+
* @param options - what to add
|
|
73
|
+
* @returns the task installing dependencies, unless `skipInstall`
|
|
74
|
+
* @rejects {Error} when the directory already holds a package, the plugin options are invalid, or a dependency has
|
|
75
|
+
* no version in the workspace
|
|
76
|
+
*/
|
|
77
|
+
declare const packageGenerator: (tree: Tree, options: PackageGeneratorOptions) => Promise<GeneratorCallback | undefined>;
|
|
78
|
+
export default packageGenerator;
|
|
79
|
+
//# sourceMappingURL=package.d.ts.map
|