nx-lifecycle 0.2.0 → 1.0.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/.npmignore +20 -0
- package/README.md +203 -119
- package/WHY-NX-LIFECYCLE.md +64 -0
- package/dist/bin.d.ts +603 -0
- package/dist/bin.js +3 -2
- package/dist/cli.d.ts +1 -1
- package/dist/commands/index.d.ts +752 -2
- package/dist/commands/lib/dependencies.d.ts +7 -7
- package/dist/commands/lib/dependencies.js +1 -1
- package/dist/commands/lifecycle-command.d.ts +1 -1
- package/dist/executors/lifecycle/container.d.ts +603 -0
- package/dist/executors/lifecycle/container.js +8 -0
- package/dist/executors/lifecycle/index.js +2 -6
- package/dist/lifecycle/dependencies.d.ts +16 -0
- package/dist/lifecycle/{depedencies.js → dependencies.js} +1 -1
- package/dist/lifecycle/index.d.ts +754 -3
- package/dist/lifecycle/index.js +5 -5
- package/dist/lifecycle/lifecycle-internal.d.ts +3 -3
- package/dist/lifecycle/lifecycle-internal.js +26 -9
- package/dist/lifecycle/normalizer.d.ts +1 -1
- package/dist/lifecycle/processor.d.ts +41 -3
- package/dist/lifecycle/processor.js +36 -33
- package/dist/lifecycle/schema.d.ts +468 -66
- package/dist/lifecycle/schema.js +31 -22
- package/dist/plugin/container.d.ts +855 -0
- package/dist/plugin/container.js +13 -0
- package/dist/plugin/dependencies.d.ts +860 -0
- package/dist/plugin/dependencies.js +9 -0
- package/dist/plugin/index.d.ts +283 -0
- package/dist/plugin/index.js +6 -0
- package/dist/plugin/lifecycle-plugin.d.ts +21 -0
- package/dist/plugin/lifecycle-plugin.js +158 -0
- package/dist/plugin/schema.d.ts +353 -0
- package/dist/plugin/schema.js +21 -0
- package/dist/schemas/configurations.d.ts +5 -5
- package/dist/schemas/depend-on.d.ts +39 -9
- package/dist/schemas/depend-on.js +4 -2
- package/dist/schemas/empty.d.ts +2 -2
- package/dist/schemas/empty.js +2 -2
- package/dist/schemas/nx-json.d.ts +191 -18
- package/dist/schemas/nx-json.js +3 -3
- package/dist/schemas/project-json.d.ts +191 -18
- package/dist/schemas/project-json.js +3 -3
- package/dist/schemas/target.d.ts +79 -25
- package/executors.json +2 -2
- package/out/nx-executor/schema.json +148 -0
- package/out/schema.json +114 -0
- package/package.json +39 -40
- package/dist/executors/lifecycle/index.cjs +0 -15
- package/dist/executors/lifecycle/index.d.cts +0 -99
- package/dist/lifecycle/depedencies.d.ts +0 -15
- package/dist/schemas/lib/ajv.d.ts +0 -3
- package/dist/schemas/lib/ajv.js +0 -11
package/.npmignore
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
CHANGELOG.md
|
|
2
|
+
project.json
|
|
3
|
+
tsconfig.json
|
|
4
|
+
|
|
5
|
+
coverage/**
|
|
6
|
+
eslint.config.js
|
|
7
|
+
|
|
8
|
+
dist/tsconfig.tsbuildinfo
|
|
9
|
+
dist/**/*.map
|
|
10
|
+
dist-test/**
|
|
11
|
+
test/**
|
|
12
|
+
|
|
13
|
+
src/**
|
|
14
|
+
!src/executors/lifecycle/schema.json
|
|
15
|
+
|
|
16
|
+
dist/file-content.js
|
|
17
|
+
dist/file-content.d.ts
|
|
18
|
+
|
|
19
|
+
dist/executors/lifecycle/schema.js
|
|
20
|
+
dist/executors/lifecycle/schema.d.ts
|
package/README.md
CHANGED
|
@@ -8,92 +8,140 @@ An [Nx](https://nx.dev/) plugin that injects specific targets into high level wo
|
|
|
8
8
|
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
11
|
+
## Contents
|
|
12
|
+
|
|
13
|
+
- [Install](#install)
|
|
14
|
+
- [Plugin](#plugin)
|
|
15
|
+
- [Overriding inferred targets](#overriding-inferred-targets)
|
|
16
|
+
- [Limitations](#limitations)
|
|
17
|
+
- [Migrating from the executor](#migrating-from-the-executor)
|
|
18
|
+
- [Executor](#executor)
|
|
19
|
+
- [Configuration](#configuration)
|
|
20
|
+
- [stages](#stages)
|
|
21
|
+
- [bindings](#bindings)
|
|
16
22
|
- [check](#check)
|
|
17
23
|
- [dryRun](#dryrun)
|
|
24
|
+
- [CLI](#cli)
|
|
25
|
+
|
|
26
|
+
For the problem this solves and design rationale, see [WHY-NX-LIFECYCLE.md](./WHY-NX-LIFECYCLE.md).
|
|
18
27
|
|
|
19
|
-
##
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
npm i -D nx-lifecycle
|
|
32
|
+
```
|
|
20
33
|
|
|
21
|
-
|
|
34
|
+
`nx-lifecycle` wires targets in one of two ways:
|
|
22
35
|
|
|
23
|
-
|
|
36
|
+
- **[Plugin](#plugin)** (recommended): infers the lifecycle targets whenever Nx builds the project graph. Nothing is written to files.
|
|
37
|
+
- **[Executor](#executor)**: writes the lifecycle targets into `nx.json` and every `project.json`, to be committed. CI checks they are up to date.
|
|
24
38
|
|
|
25
|
-
|
|
39
|
+
Both take the same [stages](#stages) and [bindings](#bindings).
|
|
26
40
|
|
|
27
|
-
|
|
41
|
+
## Plugin
|
|
28
42
|
|
|
29
|
-
|
|
43
|
+
Add the plugin to `nx.json`, with stages and bindings as its options. Install `nx-lifecycle` in the workspace root `package.json`, where Nx resolves plugins.
|
|
30
44
|
|
|
31
45
|
```json
|
|
46
|
+
// nx.json
|
|
32
47
|
{
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
"plugins": [
|
|
49
|
+
{
|
|
50
|
+
"plugin": "nx-lifecycle/plugin",
|
|
51
|
+
"options": {
|
|
52
|
+
"stages": {
|
|
53
|
+
"build": {
|
|
54
|
+
"hooks": ["pre", "run", "post"],
|
|
55
|
+
"dependsOn": ["^build"]
|
|
56
|
+
},
|
|
57
|
+
"test": {
|
|
58
|
+
"hooks": ["run", "report"],
|
|
59
|
+
"dependsOn": ["build"]
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"bindings": {
|
|
63
|
+
"tsc": "build:run",
|
|
64
|
+
"mocha": "test:run"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
]
|
|
41
69
|
}
|
|
42
70
|
```
|
|
43
71
|
|
|
44
|
-
|
|
72
|
+
Every project with a `project.json` gets the [stage targets](#generated-targets). A bound target is wired in a project when its `project.json` declares it, e.g. `"tsc": {}`; the implementation can still come from `targetDefaults`. `nx show project <name>` lists the inferred targets.
|
|
45
73
|
|
|
46
|
-
|
|
74
|
+
### Overriding inferred targets
|
|
47
75
|
|
|
48
|
-
|
|
76
|
+
Nx applies `nx.json` `targetDefaults` and `project.json` on top of inferred targets. A `dependsOn` there replaces the inferred one rather than merging with it. So:
|
|
49
77
|
|
|
50
|
-
|
|
78
|
+
- Don't define the stage targets (`build`, `build:_`, `build:run`…) yourself.
|
|
79
|
+
- If a bound target sets its own `dependsOn`, include its lifecycle dependency: the hook before its own, or `<stage>:_` for the first hook.
|
|
51
80
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
81
|
+
```json
|
|
82
|
+
// project.json, with "e2e-test": "test:run"
|
|
83
|
+
{
|
|
84
|
+
"targets": {
|
|
85
|
+
"e2e-test": {
|
|
86
|
+
"dependsOn": [{ "target": "build", "projects": ["my-server"] }, "test:_"]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
59
91
|
|
|
60
|
-
|
|
61
|
-
An example would be our test suite may contain both unit-tests and functional tests, and then a final step to enforce the resulting code coverage achieves acceptable levels.
|
|
92
|
+
Every time Nx builds the project graph, the plugin checks the merged configuration, and fails with the entry to add if a bound target lost its lifecycle dependency.
|
|
62
93
|
|
|
63
|
-
|
|
94
|
+
### Limitations
|
|
64
95
|
|
|
65
|
-
|
|
96
|
+
- Only projects with a `project.json` get lifecycle targets.
|
|
97
|
+
- A target created by another Nx plugin is only wired if it is also declared in `project.json`.
|
|
98
|
+
- Nx must load the plugin before it can build anything, so a workspace can't use the plugin from its own unbuilt source. Depend on a published version.
|
|
66
99
|
|
|
67
|
-
|
|
100
|
+
### Migrating from the executor
|
|
68
101
|
|
|
69
|
-
|
|
102
|
+
1. Add the plugin to `nx.json`, with the options of the executor.
|
|
103
|
+
2. Remove the `lifecycle` target that runs the executor.
|
|
104
|
+
3. In `nx.json` `targetDefaults`, delete every target with a `__lifecycle` configuration, and remove lifecycle targets from the `dependsOn` of bound targets. Delete `dependsOn` if nothing else is left in it, as an empty list replaces the inferred one.
|
|
105
|
+
4. Optionally delete the lifecycle targets (`"build:_": {}`…) from each `project.json`. They are empty, so they don't override anything.
|
|
106
|
+
5. Run `nx show projects`. The plugin reports any target that still overrides its wiring.
|
|
70
107
|
|
|
71
|
-
|
|
108
|
+
## Executor
|
|
72
109
|
|
|
73
|
-
|
|
110
|
+
Register it as a target in a `project.json`. Because `lifecycle` manages dependencies for the rest of your targets, it should live in a project outside your normal build/test graph — for example, a root-level management package. See [Nx-lifecycle's own monorepo config](https://github.com/JacobLey/leyman/blob/main/leyman/main/lifecycle.json) for a real example.
|
|
74
111
|
|
|
75
|
-
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"targets": {
|
|
115
|
+
"lifecycle": {
|
|
116
|
+
"executor": "nx-lifecycle:lifecycle",
|
|
117
|
+
"options": {
|
|
118
|
+
"cwd": "{projectRoot}",
|
|
119
|
+
"configFile": "lifecycle.json"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
76
125
|
|
|
77
|
-
|
|
126
|
+
`nx-lifecycle` reads your stage and binding configuration, then writes `dependsOn` entries into `nx.json` and all relevant `project.json` files. The generated targets use the [noop](https://nx.dev/nx-api/nx/executors/noop) executor and should never be invoked directly or have their configuration edited by hand. Commit the generated output to version control, and rerun the executor whenever you add, remove, or rename targets.
|
|
78
127
|
|
|
79
|
-
|
|
128
|
+
You may declare any of your own targets as depending on a lifecycle-managed target. That is the intended use: your targets reference abstract stage targets, and `nx-lifecycle` ensures those stages wire up to the correct concrete implementations.
|
|
80
129
|
|
|
81
|
-
|
|
130
|
+
Configuration can be provided in two ways:
|
|
82
131
|
|
|
83
|
-
`
|
|
132
|
+
- **Inline** — pass `stages` and `bindings` directly in the executor `options` inside `project.json`
|
|
133
|
+
- **Config file** — point the executor at a separate JSON file (defaults to `lifecycle.json` in the project root)
|
|
84
134
|
|
|
85
|
-
Register it as a target in a `project.json`:
|
|
86
135
|
```json
|
|
136
|
+
// project.json — inline config
|
|
87
137
|
{
|
|
88
138
|
"targets": {
|
|
89
139
|
"lifecycle": {
|
|
90
140
|
"executor": "nx-lifecycle:lifecycle",
|
|
91
141
|
"options": {
|
|
92
|
-
"cwd": "{projectRoot}",
|
|
93
|
-
"configFile": "lifecycle.json"
|
|
94
142
|
"stages": {
|
|
95
|
-
"build": {
|
|
96
|
-
"hooks": ["pre", "run", "post"],
|
|
143
|
+
"build": {
|
|
144
|
+
"hooks": ["pre", "run", "post"],
|
|
97
145
|
"dependsOn": ["^build"]
|
|
98
146
|
},
|
|
99
147
|
"test": {
|
|
@@ -111,49 +159,27 @@ Register it as a target in a `project.json`:
|
|
|
111
159
|
}
|
|
112
160
|
```
|
|
113
161
|
|
|
114
|
-
Note that this target only needs to be run once (not once per project) so it is recommended to put this somewhere like a root `project.json` to own this. [Nx-lifecycle's own monorepo is managed in a non-published "main" package](https://github.com/JacobLey/leyman/blob/main/leyman/main/lifecycle.json).
|
|
115
|
-
|
|
116
|
-
## Usage
|
|
117
|
-
|
|
118
|
-
Because `lifecycle` manages target dependencies itself, it probably should not be executed along with targets that are normally executed, like the `build` and `test` targets in the example above.
|
|
119
|
-
|
|
120
|
-
For example, this project's use of `nx-lifecycle` is part of a [local-only project](https://github.com/JacobLey/leyman/blob/main/leyman/main/project.json#L15) that is never deployed anywhere, and exists only to manage the repo itself.
|
|
121
|
-
|
|
122
|
-
`nx-lifecycle` will produce additional targets in your `nx.json` and `project.json`s. These will all be backed by the [noop](https://nx.dev/nx-api/nx/executors/noop) executor, and have no need to invoke caching. In fact, you should never directly manage these commands, their dependencies, or their configurations. These changes should be included in your version control.
|
|
123
|
-
|
|
124
|
-
You may declare one-off targets that _depend_ on these targets though, that is the whole point! You should able to declare a target depends on the build step, without having to explicitly manage how the build script is implemented.
|
|
125
|
-
|
|
126
|
-
`nx-lifecycle` will use the `nx.json` to assign defaults, and will assign defaults to your targets that are assigned there. However that does not guarantee every target is actually executable by every project. You must declare the target in your `project.json` as well. Do not worry about copying `nx-lifecycle`'s targets over.
|
|
127
|
-
|
|
128
|
-
Instead of providing all the stages and bindings in the package.json file, it is also possible to manage that in a separate file, and point the plugin at it.
|
|
129
|
-
|
|
130
|
-
### As a plugin
|
|
131
|
-
|
|
132
|
-
Specify `nx-lifecycle:lifecycle` as the executor. You can pass the `lifecycle.json` config file there, or may provide the config JSON directly inline as the options.
|
|
133
|
-
|
|
134
162
|
```json
|
|
135
|
-
// project.json
|
|
163
|
+
// project.json — file-based config
|
|
136
164
|
{
|
|
137
165
|
"targets": {
|
|
138
166
|
"lifecycle": {
|
|
139
167
|
"executor": "nx-lifecycle:lifecycle",
|
|
140
168
|
"options": {
|
|
141
169
|
"cwd": "{projectRoot}",
|
|
142
|
-
"configFile": "my-lifecycle-config.json"
|
|
170
|
+
"configFile": "my-lifecycle-config.json"
|
|
143
171
|
}
|
|
144
172
|
}
|
|
145
173
|
}
|
|
146
174
|
}
|
|
147
175
|
```
|
|
148
176
|
|
|
149
|
-
Where the file is the options object
|
|
150
|
-
|
|
151
177
|
```json
|
|
152
|
-
// lifecycle.json
|
|
178
|
+
// lifecycle.json — the config file
|
|
153
179
|
{
|
|
154
180
|
"stages": {
|
|
155
|
-
"build": {
|
|
156
|
-
"hooks": ["pre", "run", "post"],
|
|
181
|
+
"build": {
|
|
182
|
+
"hooks": ["pre", "run", "post"],
|
|
157
183
|
"dependsOn": ["^build"]
|
|
158
184
|
},
|
|
159
185
|
"test": {
|
|
@@ -168,81 +194,139 @@ Where the file is the options object
|
|
|
168
194
|
}
|
|
169
195
|
```
|
|
170
196
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
Alternatively, you may invoke this plugin as a CLI command. Similar to above, it is _recommended_ to include this dependency in some project that is outside the scope of your "normal" packages. Although it could conceptually be installed globally as well.
|
|
197
|
+
## Configuration
|
|
174
198
|
|
|
175
|
-
|
|
176
|
-
`pnpx nx-lifecycle --config-file ./my-lifecycle.json --dry-run`
|
|
199
|
+
### `stages`
|
|
177
200
|
|
|
178
|
-
|
|
201
|
+
Required. No default.
|
|
179
202
|
|
|
180
|
-
|
|
203
|
+
Declares the abstract workflow stages that `nx-lifecycle` will own. Each key is a stage name; the value configures that stage's structure and upstream dependencies.
|
|
181
204
|
|
|
182
|
-
|
|
205
|
+
| Field | Type | Default | Description |
|
|
206
|
+
|-------|------|---------|-------------|
|
|
207
|
+
| `hooks` | `string[]` | — | Ordered list of sub-steps within the stage. The resulting target names are `stage:hook` (e.g. `build:run`). If omitted, the stage has no sub-steps and is referenced directly by name. |
|
|
208
|
+
| `dependsOn` | `dependsOn[]` | — | Upstream dependencies for this stage, using the same format as Nx's native [`dependsOn`](https://nx.dev/reference/project-configuration#dependson). Defines the relationship between this stage and others (or their upstream counterparts). |
|
|
183
209
|
|
|
184
|
-
|
|
210
|
+
**Note:** If a stage declares `hooks`, bindings must reference a specific hook (`build:run`), not the stage name alone (`build`).
|
|
185
211
|
|
|
186
|
-
|
|
212
|
+
```json
|
|
213
|
+
{
|
|
214
|
+
"stages": {
|
|
215
|
+
"build": {
|
|
216
|
+
"hooks": ["pre", "run", "post"],
|
|
217
|
+
"dependsOn": ["^build"]
|
|
218
|
+
},
|
|
219
|
+
"test": {
|
|
220
|
+
"hooks": ["run", "report"],
|
|
221
|
+
"dependsOn": ["build"]
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
187
226
|
|
|
188
|
-
|
|
227
|
+
A stage without hooks (referenced directly by name):
|
|
189
228
|
|
|
190
|
-
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"stages": {
|
|
232
|
+
"build": {
|
|
233
|
+
"dependsOn": ["^build"]
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
"bindings": {
|
|
237
|
+
"tsc": "build"
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
```
|
|
191
241
|
|
|
192
|
-
|
|
242
|
+
#### Generated targets
|
|
193
243
|
|
|
194
|
-
|
|
195
|
-
- A list of "sub-stages" to be executed in order. The resulting name of the target will be delimeted by a colon: `stage:step`.
|
|
196
|
-
- `dependsOn`
|
|
197
|
-
- Accepts the same values as normal Nx [dependsOn](https://nx.dev/reference/project-configuration#dependson). Used to define relationship between stages, and their dependencies.
|
|
244
|
+
Each stage generates a chain of `noop` targets. For `build` with hooks `pre`, `run`, `post`:
|
|
198
245
|
|
|
199
|
-
|
|
246
|
+
```
|
|
247
|
+
build:_ → build:pre → build:run → build:post → build
|
|
248
|
+
```
|
|
200
249
|
|
|
201
|
-
|
|
250
|
+
- `build:_` is the stage's **anchor**. It carries the stage's `dependsOn`, so every hook waits for it. Nothing can be bound to it.
|
|
251
|
+
- Each hook depends on the previous one, and a bound target depends on the hook before its own. Targets bound to the same hook run in parallel.
|
|
252
|
+
- `build` is the stage's public entry point. Other stages and your own targets should depend on it, not on its hooks.
|
|
202
253
|
|
|
203
|
-
|
|
254
|
+
### `bindings`
|
|
204
255
|
|
|
205
|
-
|
|
256
|
+
Required. No default.
|
|
206
257
|
|
|
207
|
-
|
|
258
|
+
Maps your project's concrete target names to lifecycle stage hooks. Each key is the name of an existing Nx target (e.g. `tsc`, `mocha`); the value is the stage or stage hook it should be bound to.
|
|
208
259
|
|
|
209
260
|
```json
|
|
210
|
-
// lifecycle.json
|
|
211
261
|
{
|
|
212
|
-
"stages": {
|
|
213
|
-
"build": {
|
|
214
|
-
"dependsOn": ["^build"]
|
|
215
|
-
},
|
|
216
|
-
"test": {
|
|
217
|
-
"hooks": ["run", "report"],
|
|
218
|
-
"dependsOn": ["build"]
|
|
219
|
-
}
|
|
220
|
-
},
|
|
221
262
|
"bindings": {
|
|
222
|
-
"tsc": "build",
|
|
263
|
+
"tsc": "build:run",
|
|
223
264
|
"mocha": "test:run"
|
|
224
265
|
}
|
|
225
266
|
}
|
|
226
267
|
```
|
|
227
268
|
|
|
228
|
-
|
|
269
|
+
Multiple targets from different languages or toolchains can bind to the same hook:
|
|
270
|
+
|
|
271
|
+
```json
|
|
272
|
+
{
|
|
273
|
+
"bindings": {
|
|
274
|
+
"tsc": "build:run",
|
|
275
|
+
"gradle": "build:run"
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
A binding applies to every project that defines the target, through `targetDefaults` or its own `project.json`. A project opts in by defining it (e.g. `"tsc": {}`). A warning is logged for a binding that no `project.json` declares, since it is usually a typo. Targets inferred by Nx plugins are not declared in `project.json`, so they trigger the warning too.
|
|
229
281
|
|
|
230
|
-
|
|
282
|
+
#### Project-specific dependencies
|
|
231
283
|
|
|
232
|
-
|
|
284
|
+
With the plugin, see [Overriding inferred targets](#overriding-inferred-targets) instead. With the executor, a project can't change a stage's `dependsOn`, but it can add dependencies to its own bound targets. `nx-lifecycle` keeps any `dependsOn` entry that isn't a lifecycle target and appends the hook dependency after it:
|
|
233
285
|
|
|
234
|
-
|
|
286
|
+
```json
|
|
287
|
+
// project.json
|
|
288
|
+
{
|
|
289
|
+
"targets": {
|
|
290
|
+
"e2e-test": {
|
|
291
|
+
"dependsOn": [{ "target": "build", "projects": ["my-server"] }]
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
235
296
|
|
|
236
|
-
|
|
297
|
+
Use this sparingly. If many projects need the same dependency, it belongs in the stage.
|
|
237
298
|
|
|
238
|
-
|
|
299
|
+
### `check`
|
|
239
300
|
|
|
240
|
-
|
|
301
|
+
Executor only.
|
|
241
302
|
|
|
242
|
-
|
|
303
|
+
| Type | Default |
|
|
304
|
+
|------|---------|
|
|
305
|
+
| `boolean` | `true` in CI, `false` otherwise |
|
|
243
306
|
|
|
244
|
-
|
|
307
|
+
When `true`, the executor fails if `nx.json` or any `project.json` is out of sync with what `nx-lifecycle` would generate. Use this in CI to verify that generated configs are committed and up to date before deployment proceeds.
|
|
245
308
|
|
|
246
|
-
###
|
|
309
|
+
### `dryRun`
|
|
310
|
+
|
|
311
|
+
Executor only.
|
|
312
|
+
|
|
313
|
+
| Type | Default |
|
|
314
|
+
|------|---------|
|
|
315
|
+
| `boolean` | `false` |
|
|
316
|
+
|
|
317
|
+
When `true`, performs all computation but skips writing any files. Can still fail when `check` is `true`.
|
|
318
|
+
|
|
319
|
+
## CLI
|
|
320
|
+
|
|
321
|
+
`nx-lifecycle` can also be invoked directly as a CLI command. Configuration must be provided via a config file — inline options are not supported from the CLI.
|
|
322
|
+
|
|
323
|
+
```sh
|
|
324
|
+
pnpx nx-lifecycle --help
|
|
325
|
+
pnpx nx-lifecycle --config-file ./my-lifecycle.json --dry-run
|
|
326
|
+
```
|
|
247
327
|
|
|
248
|
-
|
|
328
|
+
| Flag | Type | Default | Description |
|
|
329
|
+
|------|------|---------|-------------|
|
|
330
|
+
| `--config-file` | `string` | `lifecycle.json` | Path to the lifecycle config file. |
|
|
331
|
+
| `--dry-run` | `boolean` | `false` | Compute changes without writing files. |
|
|
332
|
+
| `--check` | `boolean` | `true` in CI | Fail if any file is out of sync. |
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<div style="text-align:center">
|
|
2
|
+
|
|
3
|
+
# Why nx-lifecycle?
|
|
4
|
+
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
## The Problem
|
|
8
|
+
|
|
9
|
+
Nx's `dependsOn` field is powerful — it lets you declare that a target must wait for other targets to complete before it runs. But as a monorepo grows, managing those dependencies by hand becomes a significant maintenance burden.
|
|
10
|
+
|
|
11
|
+
### Manual `dependsOn` doesn't scale
|
|
12
|
+
|
|
13
|
+
Consider a simple setup: a TypeScript build step (`tsc`) followed by a unit test runner (`mocha`).
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"targets": {
|
|
18
|
+
"tsc": {
|
|
19
|
+
"dependsOn": ["^tsc"]
|
|
20
|
+
},
|
|
21
|
+
"mocha": {
|
|
22
|
+
"dependsOn": ["tsc"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This is fine. `mocha` waits for `tsc`, and `tsc` waits for its upstream dependencies to build first.
|
|
29
|
+
|
|
30
|
+
Now you need to add a post-build step — say, a [`populate-files`](https://www.npmjs.com/package/load-populate-files) step that uses the freshly-compiled JS to generate a JSON config.
|
|
31
|
+
|
|
32
|
+
The new step must run after `tsc` but before `mocha`. So:
|
|
33
|
+
|
|
34
|
+
- `mocha` must now depend on `populate-files`, not `tsc`
|
|
35
|
+
- `tsc` must now depend on the upstream `populate-files` of _its_ dependencies (but only for packages that actually have that step)
|
|
36
|
+
|
|
37
|
+
Adding one new target required updating the dependency declarations on two existing targets — and conditionally, depending on what each dependency package implements. In a monorepo with dozens of packages and targets, every new step multiplies the update surface.
|
|
38
|
+
|
|
39
|
+
### The compounding problem
|
|
40
|
+
|
|
41
|
+
The situation gets worse as the repo grows:
|
|
42
|
+
|
|
43
|
+
- Different packages implement targets differently (TypeScript vs Java vs Go)
|
|
44
|
+
- Some packages have `populate-files`, others don't
|
|
45
|
+
- A new `lint`, `typecheck`, or `codegen` step in one package requires auditing every other package's `dependsOn` to see what needs updating
|
|
46
|
+
- There is no single place to reason about the _intended_ order of operations — it's spread across every `project.json`
|
|
47
|
+
|
|
48
|
+
The `dependsOn` fields become a tangled web of implementation details rather than a clear expression of intent.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## The Solution
|
|
53
|
+
|
|
54
|
+
`nx-lifecycle` separates the _what_ from the _how_:
|
|
55
|
+
|
|
56
|
+
1. **Stages** — You declare abstract, high-level workflows once (e.g. `build` with hooks `pre`, `run`, `post`; `test` with hooks `run`, `report`). These stages express the _intended_ order of operations, independent of any specific tooling.
|
|
57
|
+
|
|
58
|
+
2. **Bindings** — You declare once which concrete targets (e.g. `tsc`, `eslint`) map to which stage and hook. Each project opts in by defining those targets. The mapping lives with the tool, not scattered across every dependent.
|
|
59
|
+
|
|
60
|
+
3. **Wiring** — `nx-lifecycle` reads the stages and bindings, then derives every `dependsOn`: its Nx plugin infers them when Nx builds the project graph, or its executor writes them into `nx.json` and `project.json`.
|
|
61
|
+
|
|
62
|
+
The result: adding a new step means updating one binding. Every downstream `dependsOn` is recomputed. The generated configs are checked into version control, so CI can verify they're in sync before any deployment proceeds.
|
|
63
|
+
|
|
64
|
+
Now you can just run `nx run <project>:build` and let the projects determine what "building" means to it.
|