nx-lifecycle 0.2.1 → 1.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/.npmignore +20 -0
- package/README.md +242 -120
- package/WHY-NX-LIFECYCLE.md +64 -0
- package/dist/bin.d.ts +1 -2
- package/dist/bin.js +1 -14
- package/dist/cli.d.ts +1 -5
- package/dist/cli.js +9 -22
- package/dist/commands/explain-command.d.ts +12 -0
- package/dist/commands/explain-command.js +57 -0
- package/dist/commands/lib/types.d.ts +0 -3
- package/dist/commands/lifecycle-command.d.ts +2 -12
- package/dist/commands/lifecycle-command.js +13 -22
- package/dist/executors/lifecycle/index.d.ts +1 -4
- package/dist/executors/lifecycle/index.js +1 -8
- package/dist/executors/lifecycle/lifecycle.d.ts +8 -9
- package/dist/executors/lifecycle/lifecycle.js +13 -20
- package/dist/lifecycle/explain.d.ts +68 -0
- package/dist/lifecycle/explain.js +160 -0
- package/dist/lifecycle/lifecycle-internal.d.ts +4 -12
- package/dist/lifecycle/lifecycle-internal.js +93 -93
- package/dist/lifecycle/normalizer.d.ts +14 -8
- package/dist/lifecycle/normalizer.js +29 -36
- package/dist/lifecycle/processor.d.ts +41 -5
- package/dist/lifecycle/processor.js +36 -35
- package/dist/lifecycle/schema.d.ts +468 -66
- package/dist/lifecycle/schema.js +30 -21
- package/dist/plugin/index.d.ts +7 -0
- package/dist/plugin/index.js +4 -0
- package/dist/plugin/lifecycle-plugin.d.ts +24 -0
- package/dist/plugin/lifecycle-plugin.js +145 -0
- package/dist/{executors/lifecycle/index.d.cts → plugin/schema.d.ts} +32 -55
- package/dist/plugin/schema.js +19 -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 +42 -20
- package/dist/schemas/nx-json.js +2 -4
- package/dist/schemas/project-json.d.ts +42 -20
- package/dist/schemas/project-json.js +2 -4
- 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 +43 -41
- package/dist/commands/index.d.ts +0 -5
- package/dist/commands/index.js +0 -14
- package/dist/commands/lib/dependencies.d.ts +0 -10
- package/dist/commands/lib/dependencies.js +0 -12
- package/dist/executors/lifecycle/index.cjs +0 -15
- package/dist/lifecycle/depedencies.d.ts +0 -15
- package/dist/lifecycle/depedencies.js +0 -14
- package/dist/lifecycle/index.d.ts +0 -5
- package/dist/lifecycle/index.js +0 -25
- package/dist/schemas/lib/ajv.d.ts +0 -3
- package/dist/schemas/lib/ajv.js +0 -9
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
|
@@ -1,99 +1,156 @@
|
|
|
1
1
|
<div style="text-align:center">
|
|
2
2
|
|
|
3
3
|
# nx-lifecycle
|
|
4
|
-
|
|
4
|
+
[Nx](https://nx.dev/) plugin that derives every `dependsOn` from declared lifecycle stages (build, test, …) and the targets bound to them.
|
|
5
5
|
|
|
6
6
|
[](https://www.npmjs.com/package/nx-lifecycle)
|
|
7
|
-
[](https://github.com/JacobLey/leyman/blob/main/
|
|
7
|
+
[](https://github.com/JacobLey/leyman/blob/main/apps/nx-lifecycle/LICENSE)
|
|
8
8
|
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Declare your workflow once — `build` runs hooks `pre` → `run` → `post`, `test` runs after `build` — then bind tools to hooks (`tsc` → `build:run`, `mocha` → `test:run`). Then `nx run <project>:build` runs the right steps, in the right order, in any project.
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
13
|
+
- **Add a step in one place** — binding a new target (codegen, lint, a post-build step) rewires every dependent automatically, across every project.
|
|
14
|
+
- **Per-project implementations** — each project opts into the bound targets it declares, so TypeScript, Go and Java projects can all answer to the same `build` and `test`.
|
|
15
|
+
- **Nothing to commit** — the plugin infers targets when Nx builds the project graph, and fails with the fix if a project's `dependsOn` breaks the wiring. An executor that writes the config to files is also available.
|
|
16
|
+
|
|
17
|
+
**Compared to** hand-written `dependsOn` in `targetDefaults` and each `project.json`: there, adding a step between two existing ones means editing every target that depended on the first, in every project that has it.
|
|
18
|
+
|
|
19
|
+
For the problem this solves and design rationale, see [WHY-NX-LIFECYCLE.md](https://github.com/JacobLey/leyman/blob/main/apps/nx-lifecycle/WHY-NX-LIFECYCLE.md).
|
|
20
|
+
|
|
21
|
+
## Contents
|
|
22
|
+
|
|
23
|
+
- [Install](#install)
|
|
24
|
+
- [Plugin](#plugin)
|
|
25
|
+
- [Overriding inferred targets](#overriding-inferred-targets)
|
|
26
|
+
- [Limitations](#limitations)
|
|
27
|
+
- [Migrating from the executor](#migrating-from-the-executor)
|
|
28
|
+
- [Executor](#executor)
|
|
29
|
+
- [Configuration](#configuration)
|
|
30
|
+
- [stages](#stages)
|
|
31
|
+
- [bindings](#bindings)
|
|
16
32
|
- [check](#check)
|
|
17
33
|
- [dryRun](#dryrun)
|
|
34
|
+
- [CLI](#cli)
|
|
35
|
+
- [explain](#explain)
|
|
18
36
|
|
|
19
|
-
##
|
|
37
|
+
## Install
|
|
20
38
|
|
|
21
|
-
|
|
39
|
+
```sh
|
|
40
|
+
npm i -D nx-lifecycle
|
|
41
|
+
```
|
|
22
42
|
|
|
23
|
-
|
|
43
|
+
`nx-lifecycle` wires targets in one of two ways:
|
|
24
44
|
|
|
25
|
-
|
|
45
|
+
- **[Plugin](#plugin)** (recommended): infers the lifecycle targets whenever Nx builds the project graph. Nothing is written to files.
|
|
46
|
+
- **[Executor](#executor)**: writes the lifecycle targets into `nx.json` and every `project.json`, to be committed. CI checks they are up to date.
|
|
26
47
|
|
|
27
|
-
|
|
48
|
+
Both take the same [stages](#stages) and [bindings](#bindings).
|
|
28
49
|
|
|
29
|
-
|
|
50
|
+
## Plugin
|
|
51
|
+
|
|
52
|
+
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
53
|
|
|
31
54
|
```json
|
|
55
|
+
// nx.json
|
|
32
56
|
{
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
57
|
+
"plugins": [
|
|
58
|
+
{
|
|
59
|
+
"plugin": "nx-lifecycle/plugin",
|
|
60
|
+
"options": {
|
|
61
|
+
"stages": {
|
|
62
|
+
"build": {
|
|
63
|
+
"hooks": ["pre", "run", "post"],
|
|
64
|
+
"dependsOn": ["^build"]
|
|
65
|
+
},
|
|
66
|
+
"test": {
|
|
67
|
+
"hooks": ["run", "report"],
|
|
68
|
+
"dependsOn": ["build"]
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"bindings": {
|
|
72
|
+
"tsc": "build:run",
|
|
73
|
+
"mocha": "test:run"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
]
|
|
41
78
|
}
|
|
42
79
|
```
|
|
43
80
|
|
|
44
|
-
|
|
81
|
+
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
82
|
|
|
46
|
-
|
|
83
|
+
### Overriding inferred targets
|
|
47
84
|
|
|
48
|
-
|
|
85
|
+
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
86
|
|
|
50
|
-
|
|
87
|
+
- Don't define the stage targets (`build`, `build:_`, `build:run`…) yourself.
|
|
88
|
+
- If a bound target sets its own `dependsOn`, include its lifecycle dependency: the hook before its own, or `<stage>:_` for the first hook.
|
|
51
89
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
90
|
+
```json
|
|
91
|
+
// project.json, with "e2e-test": "test:run"
|
|
92
|
+
{
|
|
93
|
+
"targets": {
|
|
94
|
+
"e2e-test": {
|
|
95
|
+
"dependsOn": [{ "target": "build", "projects": ["my-server"] }, "test:_"]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
59
100
|
|
|
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.
|
|
101
|
+
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
102
|
|
|
63
|
-
|
|
103
|
+
### Limitations
|
|
64
104
|
|
|
65
|
-
|
|
105
|
+
- Only projects with a `project.json` get lifecycle targets.
|
|
106
|
+
- A target created by another Nx plugin is only wired if it is also declared in `project.json`.
|
|
107
|
+
- 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
108
|
|
|
67
|
-
|
|
109
|
+
### Migrating from the executor
|
|
68
110
|
|
|
69
|
-
|
|
111
|
+
1. Add the plugin to `nx.json`, with the options of the executor.
|
|
112
|
+
2. Remove the `lifecycle` target that runs the executor.
|
|
113
|
+
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.
|
|
114
|
+
4. Optionally delete the lifecycle targets (`"build:_": {}`…) from each `project.json`. They are empty, so they don't override anything.
|
|
115
|
+
5. Run `nx show projects`. The plugin reports any target that still overrides its wiring.
|
|
70
116
|
|
|
71
|
-
|
|
117
|
+
## Executor
|
|
72
118
|
|
|
73
|
-
|
|
119
|
+
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
120
|
|
|
75
|
-
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"targets": {
|
|
124
|
+
"lifecycle": {
|
|
125
|
+
"executor": "nx-lifecycle:lifecycle",
|
|
126
|
+
"options": {
|
|
127
|
+
"cwd": "{projectRoot}",
|
|
128
|
+
"configFile": "lifecycle.json"
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
76
134
|
|
|
77
|
-
|
|
135
|
+
`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
136
|
|
|
79
|
-
|
|
137
|
+
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
138
|
|
|
81
|
-
|
|
139
|
+
Configuration can be provided in two ways:
|
|
82
140
|
|
|
83
|
-
`
|
|
141
|
+
- **Inline** — pass `stages` and `bindings` directly in the executor `options` inside `project.json`
|
|
142
|
+
- **Config file** — point the executor at a separate JSON file (defaults to `lifecycle.json` in the project root)
|
|
84
143
|
|
|
85
|
-
Register it as a target in a `project.json`:
|
|
86
144
|
```json
|
|
145
|
+
// project.json — inline config
|
|
87
146
|
{
|
|
88
147
|
"targets": {
|
|
89
148
|
"lifecycle": {
|
|
90
149
|
"executor": "nx-lifecycle:lifecycle",
|
|
91
150
|
"options": {
|
|
92
|
-
"cwd": "{projectRoot}",
|
|
93
|
-
"configFile": "lifecycle.json"
|
|
94
151
|
"stages": {
|
|
95
|
-
"build": {
|
|
96
|
-
"hooks": ["pre", "run", "post"],
|
|
152
|
+
"build": {
|
|
153
|
+
"hooks": ["pre", "run", "post"],
|
|
97
154
|
"dependsOn": ["^build"]
|
|
98
155
|
},
|
|
99
156
|
"test": {
|
|
@@ -111,49 +168,27 @@ Register it as a target in a `project.json`:
|
|
|
111
168
|
}
|
|
112
169
|
```
|
|
113
170
|
|
|
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
171
|
```json
|
|
135
|
-
// project.json
|
|
172
|
+
// project.json — file-based config
|
|
136
173
|
{
|
|
137
174
|
"targets": {
|
|
138
175
|
"lifecycle": {
|
|
139
176
|
"executor": "nx-lifecycle:lifecycle",
|
|
140
177
|
"options": {
|
|
141
178
|
"cwd": "{projectRoot}",
|
|
142
|
-
"configFile": "my-lifecycle-config.json"
|
|
179
|
+
"configFile": "my-lifecycle-config.json"
|
|
143
180
|
}
|
|
144
181
|
}
|
|
145
182
|
}
|
|
146
183
|
}
|
|
147
184
|
```
|
|
148
185
|
|
|
149
|
-
Where the file is the options object
|
|
150
|
-
|
|
151
186
|
```json
|
|
152
|
-
// lifecycle.json
|
|
187
|
+
// lifecycle.json — the config file
|
|
153
188
|
{
|
|
154
189
|
"stages": {
|
|
155
|
-
"build": {
|
|
156
|
-
"hooks": ["pre", "run", "post"],
|
|
190
|
+
"build": {
|
|
191
|
+
"hooks": ["pre", "run", "post"],
|
|
157
192
|
"dependsOn": ["^build"]
|
|
158
193
|
},
|
|
159
194
|
"test": {
|
|
@@ -168,81 +203,168 @@ Where the file is the options object
|
|
|
168
203
|
}
|
|
169
204
|
```
|
|
170
205
|
|
|
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.
|
|
206
|
+
## Configuration
|
|
174
207
|
|
|
175
|
-
|
|
176
|
-
`pnpx nx-lifecycle --config-file ./my-lifecycle.json --dry-run`
|
|
208
|
+
### `stages`
|
|
177
209
|
|
|
178
|
-
|
|
210
|
+
Required. No default.
|
|
179
211
|
|
|
180
|
-
|
|
212
|
+
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
213
|
|
|
182
|
-
|
|
214
|
+
| Field | Type | Default | Description |
|
|
215
|
+
|-------|------|---------|-------------|
|
|
216
|
+
| `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. |
|
|
217
|
+
| `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
218
|
|
|
184
|
-
|
|
219
|
+
**Note:** If a stage declares `hooks`, bindings must reference a specific hook (`build:run`), not the stage name alone (`build`).
|
|
185
220
|
|
|
186
|
-
|
|
221
|
+
```json
|
|
222
|
+
{
|
|
223
|
+
"stages": {
|
|
224
|
+
"build": {
|
|
225
|
+
"hooks": ["pre", "run", "post"],
|
|
226
|
+
"dependsOn": ["^build"]
|
|
227
|
+
},
|
|
228
|
+
"test": {
|
|
229
|
+
"hooks": ["run", "report"],
|
|
230
|
+
"dependsOn": ["build"]
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
187
235
|
|
|
188
|
-
|
|
236
|
+
A stage without hooks (referenced directly by name):
|
|
189
237
|
|
|
190
|
-
|
|
238
|
+
```json
|
|
239
|
+
{
|
|
240
|
+
"stages": {
|
|
241
|
+
"build": {
|
|
242
|
+
"dependsOn": ["^build"]
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
"bindings": {
|
|
246
|
+
"tsc": "build"
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
191
250
|
|
|
192
|
-
|
|
251
|
+
#### Generated targets
|
|
193
252
|
|
|
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.
|
|
253
|
+
Each stage generates a chain of `noop` targets. For `build` with hooks `pre`, `run`, `post`:
|
|
198
254
|
|
|
199
|
-
|
|
255
|
+
```
|
|
256
|
+
build:_ → build:pre → build:run → build:post → build
|
|
257
|
+
```
|
|
200
258
|
|
|
201
|
-
|
|
259
|
+
- `build:_` is the stage's **anchor**. It carries the stage's `dependsOn`, so every hook waits for it. Nothing can be bound to it.
|
|
260
|
+
- 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.
|
|
261
|
+
- `build` is the stage's public entry point. Other stages and your own targets should depend on it, not on its hooks.
|
|
202
262
|
|
|
203
|
-
|
|
263
|
+
### `bindings`
|
|
204
264
|
|
|
205
|
-
|
|
265
|
+
Required. No default.
|
|
206
266
|
|
|
207
|
-
|
|
267
|
+
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
268
|
|
|
209
269
|
```json
|
|
210
|
-
// lifecycle.json
|
|
211
270
|
{
|
|
212
|
-
"stages": {
|
|
213
|
-
"build": {
|
|
214
|
-
"dependsOn": ["^build"]
|
|
215
|
-
},
|
|
216
|
-
"test": {
|
|
217
|
-
"hooks": ["run", "report"],
|
|
218
|
-
"dependsOn": ["build"]
|
|
219
|
-
}
|
|
220
|
-
},
|
|
221
271
|
"bindings": {
|
|
222
|
-
"tsc": "build",
|
|
272
|
+
"tsc": "build:run",
|
|
223
273
|
"mocha": "test:run"
|
|
224
274
|
}
|
|
225
275
|
}
|
|
226
276
|
```
|
|
227
277
|
|
|
228
|
-
|
|
278
|
+
Multiple targets from different languages or toolchains can bind to the same hook:
|
|
229
279
|
|
|
230
|
-
|
|
280
|
+
```json
|
|
281
|
+
{
|
|
282
|
+
"bindings": {
|
|
283
|
+
"tsc": "build:run",
|
|
284
|
+
"gradle": "build:run"
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
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.
|
|
290
|
+
|
|
291
|
+
#### Project-specific dependencies
|
|
292
|
+
|
|
293
|
+
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:
|
|
294
|
+
|
|
295
|
+
```json
|
|
296
|
+
// project.json
|
|
297
|
+
{
|
|
298
|
+
"targets": {
|
|
299
|
+
"e2e-test": {
|
|
300
|
+
"dependsOn": [{ "target": "build", "projects": ["my-server"] }]
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Use this sparingly. If many projects need the same dependency, it belongs in the stage.
|
|
307
|
+
|
|
308
|
+
### `check`
|
|
309
|
+
|
|
310
|
+
Executor only.
|
|
311
|
+
|
|
312
|
+
| Type | Default |
|
|
313
|
+
|------|---------|
|
|
314
|
+
| `boolean` | `true` in CI, `false` otherwise |
|
|
315
|
+
|
|
316
|
+
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.
|
|
231
317
|
|
|
232
|
-
|
|
318
|
+
### `dryRun`
|
|
233
319
|
|
|
234
|
-
|
|
320
|
+
Executor only.
|
|
235
321
|
|
|
236
|
-
|
|
322
|
+
| Type | Default |
|
|
323
|
+
|------|---------|
|
|
324
|
+
| `boolean` | `false` |
|
|
237
325
|
|
|
238
|
-
`
|
|
326
|
+
When `true`, performs all computation but skips writing any files. Can still fail when `check` is `true`.
|
|
239
327
|
|
|
240
|
-
|
|
328
|
+
## CLI
|
|
241
329
|
|
|
242
|
-
|
|
330
|
+
`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.
|
|
243
331
|
|
|
244
|
-
|
|
332
|
+
```sh
|
|
333
|
+
pnpx nx-lifecycle --help
|
|
334
|
+
pnpx nx-lifecycle --config-file ./my-lifecycle.json --dry-run
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
| Flag | Type | Default | Description |
|
|
338
|
+
|------|------|---------|-------------|
|
|
339
|
+
| `--config-file` | `string` | `lifecycle.json` | Path to the lifecycle config file. |
|
|
340
|
+
| `--dry-run` | `boolean` | `false` | Compute changes without writing files. |
|
|
341
|
+
| `--check` | `boolean` | `true` in CI | Fail if any file is out of sync. |
|
|
342
|
+
|
|
343
|
+
### `explain`
|
|
344
|
+
|
|
345
|
+
Prints what runs, in which order, for the stages of a project: each stage after the stages it depends on, its hooks, and the bound targets the project declares. A `^` dependency lists the upstream projects it runs.
|
|
245
346
|
|
|
246
|
-
|
|
347
|
+
```sh
|
|
348
|
+
pnpx nx-lifecycle explain my-lib # every stage
|
|
349
|
+
pnpx nx-lifecycle explain my-lib test # test, and the stages it runs after
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
```
|
|
353
|
+
my-lib
|
|
354
|
+
|
|
355
|
+
1. build
|
|
356
|
+
after: ^build (my-utils)
|
|
357
|
+
build:pre (nothing bound)
|
|
358
|
+
build:run
|
|
359
|
+
tsc
|
|
360
|
+
|
|
361
|
+
2. test
|
|
362
|
+
after: build
|
|
363
|
+
test:run
|
|
364
|
+
mocha
|
|
365
|
+
e2e-test (also after: my-server:build)
|
|
366
|
+
test:report
|
|
367
|
+
coverage-report
|
|
368
|
+
```
|
|
247
369
|
|
|
248
|
-
|
|
370
|
+
Stages and bindings come from the [plugin](#plugin) options in `nx.json`, or else from `--config-file` (default `lifecycle.json`). Bound targets and their other dependencies come from the project as Nx resolves it, after `targetDefaults` and `project.json`.
|
|
@@ -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. With the plugin this happens every time Nx builds the project graph, so there is nothing to keep in sync. With the executor the generated configs are committed, and CI checks they are up to date.
|
|
63
|
+
|
|
64
|
+
Now you can just run `nx run <project>:build` and let the projects determine what "building" means to it.
|
package/dist/bin.d.ts
CHANGED
package/dist/bin.js
CHANGED
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
import { bind, singletonScope } from 'haywire';
|
|
3
|
-
import { launch } from 'haywire-launcher';
|
|
4
|
-
import { LifecycleCli } from './cli.js';
|
|
5
|
-
import { commandsId, commandsModule } from './commands/index.js';
|
|
6
|
-
import { consoleErrorId, consoleLogId, exitCodeId } from './commands/lib/dependencies.js';
|
|
7
|
-
export default launch(commandsModule.addBinding(bind(EntryScript).withDependencies([
|
|
8
|
-
LifecycleCli
|
|
9
|
-
]).withProvider((cli)=>cli).scoped(singletonScope)).addBinding(bind(LifecycleCli).withDependencies([
|
|
10
|
-
commandsId.supplier(),
|
|
11
|
-
consoleLogId,
|
|
12
|
-
consoleErrorId,
|
|
13
|
-
exitCodeId
|
|
14
|
-
]).withConstructorProvider().scoped(singletonScope)).toContainer());
|
|
1
|
+
export { LifecycleCli as default } from './cli.js';
|
|
15
2
|
|
|
16
3
|
//# sourceMappingURL=bin.js.map
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { EntryScript } from 'entry-script';
|
|
2
|
-
import type { Supplier } from 'haywire';
|
|
3
|
-
import type { ConsoleLog, ExitCode } from './commands/lib/dependencies.js';
|
|
4
|
-
import type { AbstractCommand } from './commands/lib/types.js';
|
|
5
2
|
/**
|
|
6
3
|
* Lifecycle CLI. Run `./bin.mjs --help` for options.
|
|
7
4
|
*
|
|
@@ -9,7 +6,6 @@ import type { AbstractCommand } from './commands/lib/types.js';
|
|
|
9
6
|
*/
|
|
10
7
|
export declare class LifecycleCli extends EntryScript {
|
|
11
8
|
#private;
|
|
12
|
-
constructor(getCommands: Supplier<AbstractCommand[]>, logger: ConsoleLog, errorLogger: ConsoleLog, exitCode: ExitCode);
|
|
13
9
|
/**
|
|
14
10
|
* Entry point to CLI script.
|
|
15
11
|
*
|
|
@@ -17,6 +13,6 @@ export declare class LifecycleCli extends EntryScript {
|
|
|
17
13
|
*
|
|
18
14
|
* @param argv - process arguments
|
|
19
15
|
*/
|
|
20
|
-
main(argv: string[]): Promise<void>;
|
|
16
|
+
static main(argv: string[]): Promise<void>;
|
|
21
17
|
}
|
|
22
18
|
//# sourceMappingURL=cli.d.ts.map
|