nx-lifecycle 0.2.1 → 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.
Files changed (53) hide show
  1. package/.npmignore +20 -0
  2. package/README.md +203 -119
  3. package/WHY-NX-LIFECYCLE.md +64 -0
  4. package/dist/bin.d.ts +603 -0
  5. package/dist/bin.js +3 -2
  6. package/dist/cli.d.ts +1 -1
  7. package/dist/commands/index.d.ts +752 -2
  8. package/dist/commands/lib/dependencies.d.ts +7 -7
  9. package/dist/commands/lib/dependencies.js +1 -1
  10. package/dist/commands/lifecycle-command.d.ts +1 -1
  11. package/dist/executors/lifecycle/container.d.ts +603 -0
  12. package/dist/executors/lifecycle/container.js +8 -0
  13. package/dist/executors/lifecycle/index.js +2 -6
  14. package/dist/lifecycle/dependencies.d.ts +16 -0
  15. package/dist/lifecycle/{depedencies.js → dependencies.js} +1 -1
  16. package/dist/lifecycle/index.d.ts +754 -3
  17. package/dist/lifecycle/index.js +5 -5
  18. package/dist/lifecycle/lifecycle-internal.d.ts +3 -3
  19. package/dist/lifecycle/lifecycle-internal.js +26 -9
  20. package/dist/lifecycle/normalizer.d.ts +1 -1
  21. package/dist/lifecycle/processor.d.ts +41 -3
  22. package/dist/lifecycle/processor.js +36 -33
  23. package/dist/lifecycle/schema.d.ts +468 -66
  24. package/dist/lifecycle/schema.js +30 -21
  25. package/dist/plugin/container.d.ts +855 -0
  26. package/dist/plugin/container.js +13 -0
  27. package/dist/plugin/dependencies.d.ts +860 -0
  28. package/dist/plugin/dependencies.js +9 -0
  29. package/dist/plugin/index.d.ts +283 -0
  30. package/dist/plugin/index.js +6 -0
  31. package/dist/plugin/lifecycle-plugin.d.ts +21 -0
  32. package/dist/plugin/lifecycle-plugin.js +158 -0
  33. package/dist/plugin/schema.d.ts +353 -0
  34. package/dist/plugin/schema.js +21 -0
  35. package/dist/schemas/configurations.d.ts +5 -5
  36. package/dist/schemas/depend-on.d.ts +39 -9
  37. package/dist/schemas/depend-on.js +4 -2
  38. package/dist/schemas/empty.d.ts +2 -2
  39. package/dist/schemas/empty.js +2 -2
  40. package/dist/schemas/nx-json.d.ts +191 -18
  41. package/dist/schemas/nx-json.js +3 -3
  42. package/dist/schemas/project-json.d.ts +191 -18
  43. package/dist/schemas/project-json.js +3 -3
  44. package/dist/schemas/target.d.ts +79 -25
  45. package/executors.json +2 -2
  46. package/out/nx-executor/schema.json +148 -0
  47. package/out/schema.json +114 -0
  48. package/package.json +39 -40
  49. package/dist/executors/lifecycle/index.cjs +0 -15
  50. package/dist/executors/lifecycle/index.d.cts +0 -99
  51. package/dist/lifecycle/depedencies.d.ts +0 -15
  52. package/dist/schemas/lib/ajv.d.ts +0 -3
  53. package/dist/schemas/lib/ajv.js +0 -9
@@ -1,6 +1,6 @@
1
1
  import { bind, identifier } from 'haywire';
2
- import { isNxJson, isNxJsonIdentifier, isProjectJson, isProjectJsonIdentifier } from '#schemas';
3
- import { dependenciesModule, formatFileIdentifier, isCiIdentifier, loggerIdentifier, parseCwdId, readFileIdentifier, writeFileIdentifier } from './depedencies.js';
2
+ import { assertNxJson, assertNxJsonIdentifier, assertProjectJson, assertProjectJsonIdentifier } from '#schemas';
3
+ import { dependenciesModule, formatFileIdentifier, isCiIdentifier, loggerIdentifier, parseCwdId, readFileIdentifier, writeFileIdentifier } from './dependencies.js';
4
4
  import { LifecycleInternal } from './lifecycle-internal.js';
5
5
  import { Normalizer } from './normalizer.js';
6
6
  import { nxAndProjectJsonProcessorIdentifier, processNxAndProjectJsons } from './processor.js';
@@ -13,13 +13,13 @@ export const lifecycleInternalModule = dependenciesModule.addBinding(bind(lifecy
13
13
  writeFileIdentifier,
14
14
  formatFileIdentifier,
15
15
  nxAndProjectJsonProcessorIdentifier,
16
- isNxJsonIdentifier,
17
- isProjectJsonIdentifier,
16
+ assertNxJsonIdentifier,
17
+ assertProjectJsonIdentifier,
18
18
  loggerIdentifier
19
19
  ])).addBinding(bind(Normalizer).withDependencies([
20
20
  isCiIdentifier.supplier(),
21
21
  parseCwdId,
22
22
  readFileIdentifier
23
- ]).withConstructorProvider()).addBinding(bind(nxAndProjectJsonProcessorIdentifier).withInstance(processNxAndProjectJsons)).addBinding(bind(isNxJsonIdentifier).withInstance(isNxJson)).addBinding(bind(isProjectJsonIdentifier).withInstance(isProjectJson));
23
+ ]).withConstructorProvider()).addBinding(bind(nxAndProjectJsonProcessorIdentifier).withInstance(processNxAndProjectJsons)).addBinding(bind(assertNxJsonIdentifier).withInstance(assertNxJson)).addBinding(bind(assertProjectJsonIdentifier).withInstance(assertProjectJson));
24
24
 
25
25
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  import type { readFile as ReadFile, writeFile as WriteFile } from 'node:fs/promises';
2
2
  import type { FilesFormatter } from 'format-file';
3
- import type { IsNxJson, IsProjectJson } from '#schemas';
4
- import type { Logger } from './depedencies.js';
3
+ import type { AssertNxJson, AssertProjectJson } from '#schemas';
4
+ import type { Logger } from './dependencies.js';
5
5
  import type { Normalizer } from './normalizer.js';
6
6
  import type { NxAndProjectJsonProcessor } from './processor.js';
7
7
  import type { LifecycleOptionsOrConfig } from './schema.js';
@@ -17,6 +17,6 @@ export type ILifecycleInternal = (options: LifecycleOptionsOrConfig, context: Nx
17
17
  export declare class LifecycleInternal {
18
18
  #private;
19
19
  readonly lifecycleInternal: ILifecycleInternal;
20
- constructor(normalizer: Normalizer, readFile: typeof ReadFile, writeFile: typeof WriteFile, formatFiles: FilesFormatter, processNxAndProjectJsons: NxAndProjectJsonProcessor, isNxJson: IsNxJson, isProjectJson: IsProjectJson, logger: Logger);
20
+ constructor(normalizer: Normalizer, readFile: typeof ReadFile, writeFile: typeof WriteFile, formatFiles: FilesFormatter, processNxAndProjectJsons: NxAndProjectJsonProcessor, assertNxJson: AssertNxJson, assertProjectJson: AssertProjectJson, logger: Logger);
21
21
  }
22
22
  //# sourceMappingURL=lifecycle-internal.d.ts.map
@@ -5,24 +5,25 @@ export class LifecycleInternal {
5
5
  #writeFile;
6
6
  #formatFiles;
7
7
  #processNxAndProjectJsons;
8
- #isNxJson;
9
- #isProjectJson;
8
+ #assertNxJson;
9
+ #assertProjectJson;
10
10
  #logger;
11
11
  lifecycleInternal;
12
- constructor(normalizer, readFile, writeFile, formatFiles, processNxAndProjectJsons, isNxJson, isProjectJson, logger){
12
+ constructor(normalizer, readFile, writeFile, formatFiles, processNxAndProjectJsons, assertNxJson, assertProjectJson, logger){
13
13
  this.#normalizer = normalizer;
14
14
  this.#readFile = readFile;
15
15
  this.#writeFile = writeFile;
16
16
  this.#formatFiles = formatFiles;
17
17
  this.#processNxAndProjectJsons = processNxAndProjectJsons;
18
- this.#isNxJson = isNxJson;
19
- this.#isProjectJson = isProjectJson;
18
+ this.#assertNxJson = assertNxJson;
19
+ this.#assertProjectJson = assertProjectJson;
20
20
  this.#logger = logger;
21
21
  this.lifecycleInternal = this.#lifecycleInternal.bind(this);
22
22
  }
23
23
  async #lifecycleInternal(options, context) {
24
24
  const normalized = await this.#normalizer.normalizeOptions(options, context);
25
25
  const { nxJson, projectJsons } = await this.#loadJsonConfigs(normalized);
26
+ this.#warnUndeclaredBindings(normalized.bindings, projectJsons.map(({ data })=>data));
26
27
  const { processedNxJson, processedProjectJsons } = this.#processNxAndProjectJsons({
27
28
  nxJson: nxJson.data,
28
29
  projectJsons: projectJsons.map(({ data })=>data),
@@ -52,8 +53,12 @@ export class LifecycleInternal {
52
53
  }))
53
54
  ]);
54
55
  const parsedNxJson = JSON.parse(rawNxJson);
55
- if (!this.#isNxJson(parsedNxJson)) {
56
- throw new Error(`Failed to parse nx.json: ${JSON.stringify(this.#isNxJson.errors, null, 2)}`);
56
+ try {
57
+ this.#assertNxJson(parsedNxJson);
58
+ } catch (err) {
59
+ throw new Error('Failed to parse nx.json', {
60
+ cause: err
61
+ });
57
62
  }
58
63
  return {
59
64
  nxJson: {
@@ -63,8 +68,12 @@ export class LifecycleInternal {
63
68
  },
64
69
  projectJsons: rawProjectJsons.map(({ name, path, rawData })=>{
65
70
  const data = JSON.parse(rawData);
66
- if (!this.#isProjectJson(data)) {
67
- throw new Error(`Failed to parse ${path}: ${JSON.stringify(this.#isProjectJson.errors, null, 2)}`);
71
+ try {
72
+ this.#assertProjectJson(data);
73
+ } catch (err) {
74
+ throw new Error(`Failed to parse ${path}`, {
75
+ cause: err
76
+ });
68
77
  }
69
78
  return {
70
79
  name,
@@ -74,6 +83,14 @@ export class LifecycleInternal {
74
83
  })
75
84
  };
76
85
  }
86
+ #warnUndeclaredBindings(bindings, projectJsons) {
87
+ const declaredTargets = new Set(projectJsons.flatMap((projectJson)=>Object.keys(projectJson.targets ?? {})));
88
+ for (const targetName of Object.keys(bindings)){
89
+ if (!declaredTargets.has(targetName)) {
90
+ this.#logger.warn(`Bound target ${targetName} is not declared in any project.json. Is it a typo?`);
91
+ }
92
+ }
93
+ }
77
94
  async #saveJsonConfigs({ jsons, options }) {
78
95
  const filesToUpdate = [];
79
96
  for (const { path, data, processed } of jsons){
@@ -1,6 +1,6 @@
1
1
  import type { readFile as ReadFile } from 'node:fs/promises';
2
2
  import type { ParseCwd } from 'parse-cwd';
3
- import { type LifecycleOptions, type LifecycleOptionsOrConfig } from './schema.js';
3
+ import type { LifecycleOptions, LifecycleOptionsOrConfig } from './schema.js';
4
4
  import type { NxContext } from './types.js';
5
5
  export interface NormalizedOptions {
6
6
  check: boolean;
@@ -1,6 +1,44 @@
1
- import { type NxJson, type ProjectJson } from '#schemas';
1
+ import type { DependsOn, NxJson, ProjectJson } from '#schemas';
2
2
  import type { NormalizedOptions } from './normalizer.js';
3
- type ProcessorOptions = Pick<NormalizedOptions, 'bindings' | 'stages'>;
3
+ export type ProcessorOptions = Pick<NormalizedOptions, 'bindings' | 'stages'>;
4
+ export type LifecycleTarget = {
5
+ name: string;
6
+ stage: string;
7
+ dependsOn: DependsOn;
8
+ } & ({
9
+ kind: 'anchor';
10
+ } | ({
11
+ previousHook: string;
12
+ } & ({
13
+ kind: 'base';
14
+ hasHooks: boolean;
15
+ } | {
16
+ kind: 'hook';
17
+ })));
18
+ export type LifecycleTargets = Map<string, LifecycleTarget>;
19
+ type LifecycleTargetWithHooks = Extract<LifecycleTarget, {
20
+ previousHook: string;
21
+ }>;
22
+ export type RegisteredTargets = Map<string, LifecycleTargetWithHooks>;
23
+ export interface LifecyclePlan {
24
+ /**
25
+ * Every anchor, hook and stage target by name, with the dependencies its stage declares.
26
+ */
27
+ lifecycleTargets: LifecycleTargets;
28
+ /**
29
+ * Bound target names mapped to the lifecycle target they run in.
30
+ */
31
+ registeredTargets: RegisteredTargets;
32
+ }
33
+ /**
34
+ * Calculates the lifecycle targets, and the hook each bound target runs in.
35
+ * Shared by the executor, which writes the result to files, and the plugin, which infers it.
36
+ *
37
+ * @param options - stages and bindings
38
+ * @returns lifecycle targets and bound targets
39
+ * @throws if stages or bindings are inconsistent
40
+ */
41
+ export declare const planLifecycle: (options: ProcessorOptions) => LifecyclePlan;
4
42
  export type NxAndProjectJsonProcessor = ({ nxJson, projectJsons, options, }: {
5
43
  nxJson: NxJson;
6
44
  projectJsons: ProjectJson[];
@@ -9,7 +47,7 @@ export type NxAndProjectJsonProcessor = ({ nxJson, projectJsons, options, }: {
9
47
  processedNxJson: NxJson;
10
48
  processedProjectJsons: ProjectJson[];
11
49
  };
12
- export declare const nxAndProjectJsonProcessorIdentifier: import("haywire").HaywireId<NxAndProjectJsonProcessor, null, null, false, false, false, false>;
50
+ export declare const nxAndProjectJsonProcessorIdentifier: import("haywire").HaywireId<NxAndProjectJsonProcessor, null, null, false, false, false, false, false>;
13
51
  export declare const processNxAndProjectJsons: NxAndProjectJsonProcessor;
14
52
  export {};
15
53
  //# sourceMappingURL=processor.d.ts.map
@@ -7,6 +7,7 @@ const calculateTargets = ({ stages })=>{
7
7
  const prefixHook = `${stageName}:_`;
8
8
  lifecycleTargets.set(prefixHook, {
9
9
  name: prefixHook,
10
+ stage: stageName,
10
11
  kind: 'anchor',
11
12
  dependsOn: stage.dependsOn ?? []
12
13
  });
@@ -16,6 +17,7 @@ const calculateTargets = ({ stages })=>{
16
17
  const hookName = `${stageName}:${hook}`;
17
18
  lifecycleTargets.set(hookName, {
18
19
  name: hookName,
20
+ stage: stageName,
19
21
  kind: 'hook',
20
22
  dependsOn: [
21
23
  previousHook
@@ -26,6 +28,7 @@ const calculateTargets = ({ stages })=>{
26
28
  }
27
29
  lifecycleTargets.set(stageName, {
28
30
  name: stageName,
31
+ stage: stageName,
29
32
  kind: 'base',
30
33
  dependsOn: [
31
34
  previousHook
@@ -70,39 +73,46 @@ const calculateTargetsToRemove = ({ lifecycleTargets, nxJson })=>{
70
73
  }
71
74
  return targetsToRemove;
72
75
  };
73
- const removeDependencyTargets = ({ target, lifecycleTargets, targetsToRemove })=>{
74
- if (target.dependsOn) {
75
- return target.dependsOn.filter((dependency)=>{
76
- const dependencyTarget = typeof dependency === 'string' ? dependency : dependency.target;
77
- const normalized = dependencyTarget.replace(/^\^/u, '');
78
- if (targetsToRemove.has(normalized)) {
79
- return false;
80
- }
76
+ const getDependencyTarget = (dependency)=>typeof dependency === 'string' ? dependency : dependency.target;
77
+ const normalizeDependencyTarget = (dependencyTarget)=>dependencyTarget.replace(/^\^/u, '');
78
+ const removeDependencyTargets = ({ target, targetsToRemove })=>(target.dependsOn ?? []).filter((dependency)=>!targetsToRemove.has(normalizeDependencyTarget(getDependencyTarget(dependency))));
79
+ const validateLifecycleDependencies = ({ lifecycleTargets, options })=>{
80
+ for (const [stageName, { dependsOn }] of Object.entries(options.stages)){
81
+ for (const dependency of dependsOn ?? []){
82
+ const dependencyTarget = getDependencyTarget(dependency);
83
+ const normalized = normalizeDependencyTarget(dependencyTarget);
81
84
  const lifecycleTarget = lifecycleTargets.get(normalized);
82
- if (lifecycleTarget) {
83
- return lifecycleTarget.kind === 'base';
85
+ if (lifecycleTarget && lifecycleTarget.kind !== 'base') {
86
+ const suggested = dependencyTarget.replace(normalized, lifecycleTarget.stage);
87
+ throw new Error(`Lifecycle stage ${stageName} cannot depend on ${dependencyTarget}, which is internal to stage ${lifecycleTarget.stage}. Depend on ${suggested} instead`);
84
88
  }
85
- return true;
86
- });
89
+ }
87
90
  }
88
- return [];
89
91
  };
90
- const validateLifecycleDependencies = ({ lifecycleTargets, options, targetsToRemove })=>{
92
+ const validateStaleDependencies = ({ options, targetsToRemove })=>{
91
93
  for (const [stageName, { dependsOn }] of Object.entries(options.stages)){
92
- if (dependsOn) {
93
- const filtered = removeDependencyTargets({
94
- target: {
95
- dependsOn
96
- },
97
- lifecycleTargets,
98
- targetsToRemove
99
- });
100
- if (filtered.length !== dependsOn.length) {
101
- throw new Error(`Invalid dependency detected on lifecycle stage ${stageName}`);
94
+ for (const dependency of dependsOn ?? []){
95
+ const dependencyTarget = getDependencyTarget(dependency);
96
+ if (targetsToRemove.has(normalizeDependencyTarget(dependencyTarget))) {
97
+ throw new Error(`Lifecycle stage ${stageName} cannot depend on ${dependencyTarget}, which is a stale lifecycle target in nx.json`);
102
98
  }
103
99
  }
104
100
  }
105
101
  };
102
+ export const planLifecycle = (options)=>{
103
+ const lifecycleTargets = calculateTargets(options);
104
+ validateLifecycleDependencies({
105
+ lifecycleTargets,
106
+ options
107
+ });
108
+ return {
109
+ lifecycleTargets,
110
+ registeredTargets: registerTargets({
111
+ options,
112
+ lifecycleTargets
113
+ })
114
+ };
115
+ };
106
116
  const processNxJson = ({ nxJson, lifecycleTargets, registeredTargets, targetsToRemove })=>{
107
117
  const originalTargets = nxJson.targetDefaults ?? {};
108
118
  const targetDefaults = {
@@ -132,7 +142,6 @@ const processNxJson = ({ nxJson, lifecycleTargets, registeredTargets, targetsToR
132
142
  ...originalTarget,
133
143
  dependsOn: removeDependencyTargets({
134
144
  target: originalTarget,
135
- lifecycleTargets,
136
145
  targetsToRemove
137
146
  })
138
147
  };
@@ -178,7 +187,6 @@ const processProjectJson = ({ projectJson, lifecycleTargets, registeredTargets,
178
187
  targets[targetName] = processedTarget;
179
188
  processedTarget.dependsOn = removeDependencyTargets({
180
189
  target,
181
- lifecycleTargets,
182
190
  targetsToRemove
183
191
  });
184
192
  const lifecycleTarget = registeredTargets.get(targetName);
@@ -201,20 +209,15 @@ const processProjectJson = ({ projectJson, lifecycleTargets, registeredTargets,
201
209
  };
202
210
  export const nxAndProjectJsonProcessorIdentifier = identifier();
203
211
  export const processNxAndProjectJsons = ({ nxJson, projectJsons, options })=>{
204
- const lifecycleTargets = calculateTargets(options);
212
+ const { lifecycleTargets, registeredTargets } = planLifecycle(options);
205
213
  const targetsToRemove = calculateTargetsToRemove({
206
214
  lifecycleTargets,
207
215
  nxJson
208
216
  });
209
- validateLifecycleDependencies({
210
- lifecycleTargets,
217
+ validateStaleDependencies({
211
218
  options,
212
219
  targetsToRemove
213
220
  });
214
- const registeredTargets = registerTargets({
215
- options,
216
- lifecycleTargets
217
- });
218
221
  const processedNxJson = processNxJson({
219
222
  nxJson,
220
223
  lifecycleTargets,