nx-lifecycle 1.1.0 → 1.1.2

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/README.md CHANGED
@@ -14,8 +14,6 @@ Declare your workflow once — `build` runs hooks `pre` → `run` → `post`, `t
14
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
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
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
17
  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
18
 
21
19
  ## Contents
@@ -33,6 +31,7 @@ For the problem this solves and design rationale, see [WHY-NX-LIFECYCLE.md](http
33
31
  - [dryRun](#dryrun)
34
32
  - [CLI](#cli)
35
33
  - [explain](#explain)
34
+ - [Compared to alternatives](#compared-to-alternatives)
36
35
 
37
36
  ## Install
38
37
 
@@ -368,3 +367,7 @@ my-lib
368
367
  ```
369
368
 
370
369
  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`.
370
+
371
+ ## Compared to alternatives
372
+
373
+ Without nx-lifecycle, `dependsOn` is written by hand in `targetDefaults` and each `project.json`. Adding a step between two existing ones then means editing every target that depended on the first, in every project that has it.
package/dist/bin.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- export { LifecycleCli as default } from './cli.js';
1
+ declare const _default: import("haywire-launcher").WrapperMain;
2
+ export default _default;
2
3
  //# sourceMappingURL=bin.d.ts.map
package/dist/bin.js CHANGED
@@ -1,3 +1,7 @@
1
- export { LifecycleCli as default } from './cli.js';
1
+ import { identifier } from 'haywire';
2
+ import { launch } from 'haywire-launcher';
3
+ import { LifecycleCli } from './cli.js';
4
+ import { cliContainer } from './container.js';
5
+ export default launch(cliContainer, identifier(LifecycleCli));
2
6
 
3
7
  //# sourceMappingURL=bin.js.map
package/dist/cli.d.ts CHANGED
@@ -1,11 +1,16 @@
1
- import { EntryScript } from 'entry-script';
1
+ import type { Main } from 'entry-script';
2
+ import type { RegisterCommand } from './commands/lib/types.js';
2
3
  /**
3
4
  * Lifecycle CLI. Run `./bin.mjs --help` for options.
4
5
  *
5
6
  * Uses `yargs` package for command line parsing and logic flow.
6
7
  */
7
- export declare class LifecycleCli extends EntryScript {
8
+ export declare class LifecycleCli implements Main {
8
9
  #private;
10
+ /**
11
+ * @param commands - every command the CLI runs
12
+ */
13
+ constructor(commands: readonly RegisterCommand[]);
9
14
  /**
10
15
  * Entry point to CLI script.
11
16
  *
@@ -13,6 +18,7 @@ export declare class LifecycleCli extends EntryScript {
13
18
  *
14
19
  * @param argv - process arguments
15
20
  */
16
- static main(argv: string[]): Promise<void>;
21
+ main(argv: string[]): Promise<void>;
17
22
  }
23
+ export declare const lifecycleCliBinding: import("haywire").Binding<import("haywire").OutputHaywireId<import("haywire").HaywireId<LifecycleCli, typeof LifecycleCli, null, false, false, false, false, false>>, [import("haywire").HaywireId<RegisterCommand, null, null, true, false, false, false, false>], false>;
18
24
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.js CHANGED
@@ -1,21 +1,28 @@
1
1
  import yargs from 'yargs';
2
- import { EntryScript } from "entry-script";
3
- import { explainCommand } from './commands/explain-command.js';
4
- import { lifecycleCommand } from './commands/lifecycle-command.js';
5
- export class LifecycleCli extends EntryScript {
6
- static async main(argv) {
2
+ import { bind } from 'haywire';
3
+ import { commandId } from './commands/lib/types.js';
4
+ export class LifecycleCli {
5
+ #commands;
6
+ constructor(commands){
7
+ this.#commands = commands;
8
+ }
9
+ async main(argv) {
7
10
  const packageJson = await import('../package.json', {
8
11
  with: {
9
12
  type: 'json'
10
13
  }
11
14
  });
12
- await yargs().scriptName('lifecycle').option({
15
+ let cli = yargs().scriptName('lifecycle').option({
13
16
  cwd: {
14
17
  type: 'string',
15
18
  default: '.',
16
19
  describe: 'Directory to resolve all other paths from'
17
20
  }
18
- }).strict().help().alias('help', 'info').version(packageJson.default.version).command(lifecycleCommand).command(explainCommand).parseAsync(argv, {}, LifecycleCli.#yargsOutput);
21
+ }).strict().help().alias('help', 'info').version(packageJson.default.version);
22
+ for (const register of this.#commands){
23
+ cli = register(cli);
24
+ }
25
+ await cli.parseAsync(argv, {}, LifecycleCli.#yargsOutput);
19
26
  }
20
27
  static #yargsOutput(e, _argv, log) {
21
28
  if (e) {
@@ -28,5 +35,8 @@ export class LifecycleCli extends EntryScript {
28
35
  }
29
36
  }
30
37
  }
38
+ export const lifecycleCliBinding = bind(LifecycleCli).withDependencies([
39
+ commandId
40
+ ]).withConstructorProvider();
31
41
 
32
42
  //# sourceMappingURL=cli.js.map
@@ -8,5 +8,6 @@ interface ExplainCommandExtendedInput extends LifecycleCommandInput {
8
8
  * Prints the stages of a project in the order they run, with the targets bound to each hook.
9
9
  */
10
10
  export declare const explainCommand: Command<ExplainCommandExtendedInput>;
11
+ export declare const explainCommandBinding: import("haywire").Binding<import("haywire").OutputHaywireId<import("haywire").HaywireId<import("./lib/types.js").RegisterCommand, null, null, true, false, false, false, false>>, [], false>;
11
12
  export {};
12
13
  //# sourceMappingURL=explain-command.d.ts.map
@@ -1,9 +1,12 @@
1
1
  import Path from 'node:path';
2
2
  import { createProjectGraphAsync, readJsonFile, workspaceRoot } from '@nx/devkit';
3
+ import { bind } from 'haywire';
4
+ import { getErrorMessage } from 'unknown-error';
3
5
  import { explainLifecycle, formatExplanation } from '../lifecycle/explain.js';
4
6
  import { loadOptions } from '../lifecycle/normalizer.js';
5
7
  import { planLifecycle } from '../lifecycle/processor.js';
6
8
  import { planPluginOptions } from '../plugin/lifecycle-plugin.js';
9
+ import { commandId } from './lib/types.js';
7
10
  const loadPlan = async ({ cwd, configFile })=>{
8
11
  const { plugins = [] } = readJsonFile(Path.join(workspaceRoot, 'nx.json'));
9
12
  const plugin = plugins.find((entry)=>typeof entry !== 'string' && entry.plugin === 'nx-lifecycle/plugin');
@@ -41,17 +44,18 @@ export const explainCommand = {
41
44
  plan,
42
45
  graph,
43
46
  project: options.project,
44
- ...options.stage === undefined ? {} : {
47
+ ...options.stage !== undefined && {
45
48
  stage: options.stage
46
49
  }
47
50
  });
48
51
  } catch (err) {
49
- console.error(err.message);
52
+ console.error(getErrorMessage(err));
50
53
  process.exitCode = 1;
51
54
  return;
52
55
  }
53
56
  console.log(formatExplanation(explanation));
54
57
  }
55
58
  };
59
+ export const explainCommandBinding = bind(commandId).withInstance((cli)=>cli.command(explainCommand));
56
60
 
57
61
  //# sourceMappingURL=explain-command.js.map
@@ -1,6 +1,19 @@
1
- import type { CommandModule } from 'yargs';
1
+ import type { Argv, CommandModule } from 'yargs';
2
2
  export interface LifecycleCommandInput {
3
3
  cwd: string;
4
4
  }
5
5
  export type Command<ExtendedInput extends LifecycleCommandInput> = Pick<CommandModule<LifecycleCommandInput, ExtendedInput>, 'builder' | 'command' | 'describe' | 'handler'>;
6
+ /**
7
+ * Adds a command to the CLI.
8
+ *
9
+ * Commands differ in the input their handler receives, so they cannot share an element type.
10
+ * Wrapping each in a function that registers it keeps every command type-checked against its own input.
11
+ */
12
+ export type RegisterCommand = (cli: Argv<LifecycleCommandInput>) => Argv<LifecycleCommandInput>;
13
+ /**
14
+ * Every command the CLI runs. Each command binds itself:
15
+ *
16
+ * `bind(commandId).withInstance(cli => cli.command(myCommand))`
17
+ */
18
+ export declare const commandId: import("haywire").HaywireId<RegisterCommand, null, null, true, false, false, false, false>;
6
19
  //# sourceMappingURL=types.d.ts.map
@@ -1,3 +1,4 @@
1
- export { };
1
+ import { identifier } from 'haywire';
2
+ export const commandId = identifier().list();
2
3
 
3
4
  //# sourceMappingURL=types.js.map
@@ -8,5 +8,6 @@ interface LifecycleCommandExtendedInput extends LifecycleCommandInput {
8
8
  * Main `nx-lifecycle` command
9
9
  */
10
10
  export declare const lifecycleCommand: Command<LifecycleCommandExtendedInput>;
11
+ export declare const lifecycleCommandBinding: import("haywire").Binding<import("haywire").OutputHaywireId<import("haywire").HaywireId<import("./lib/types.js").RegisterCommand, null, null, true, false, false, false, false>>, [], false>;
11
12
  export {};
12
13
  //# sourceMappingURL=lifecycle-command.d.ts.map
@@ -1,6 +1,8 @@
1
1
  import { createProjectGraphAsync, workspaceRoot } from '@nx/devkit';
2
2
  import { isCI } from 'ci-info';
3
+ import { bind } from 'haywire';
3
4
  import { lifecycleInternal } from '../lifecycle/lifecycle-internal.js';
5
+ import { commandId } from './lib/types.js';
4
6
  export const lifecycleCommand = {
5
7
  command: [
6
8
  '$0',
@@ -41,5 +43,6 @@ export const lifecycleCommand = {
41
43
  });
42
44
  }
43
45
  };
46
+ export const lifecycleCommandBinding = bind(commandId).withInstance((cli)=>cli.command(lifecycleCommand));
44
47
 
45
48
  //# sourceMappingURL=lifecycle-command.js.map
@@ -0,0 +1,3 @@
1
+ export declare const cliModule: import("haywire").Module<[import("haywire").NonExtendable<import("./cli.js").LifecycleCli, typeof import("./cli.js").LifecycleCli, null, false, false, false>] | [import("haywire").NonExtendable<import("./cli.js").LifecycleCli, typeof import("./cli.js").LifecycleCli, null, false, false, true>] | [import("haywire").NonExtendable<import("./cli.js").LifecycleCli, typeof import("./cli.js").LifecycleCli, null, false, true, false>] | [import("haywire").NonExtendable<import("./cli.js").LifecycleCli, typeof import("./cli.js").LifecycleCli, null, false, true, true>], import("haywire").CombineListOutputs<[import("haywire").NonExtendable<import("./commands/lib/types.js").RegisterCommand, null, null, true, false, false>] | [import("haywire").NonExtendable<import("./commands/lib/types.js").RegisterCommand, null, null, true, false, true>] | [import("haywire").NonExtendable<import("./commands/lib/types.js").RegisterCommand, null, null, true, true, false>] | [import("haywire").NonExtendable<import("./commands/lib/types.js").RegisterCommand, null, null, true, true, true>], [import("haywire").NonExtendable<import("./commands/lib/types.js").RegisterCommand, null, null, true, false, false>] | [import("haywire").NonExtendable<import("./commands/lib/types.js").RegisterCommand, null, null, true, false, true>] | [import("haywire").NonExtendable<import("./commands/lib/types.js").RegisterCommand, null, null, true, true, false>] | [import("haywire").NonExtendable<import("./commands/lib/types.js").RegisterCommand, null, null, true, true, true>]>, [import("haywire").NonExtendable<import("./commands/lib/types.js").RegisterCommand, null, null, true, false, false>], false>;
2
+ export declare const cliContainer: import("haywire").SyncContainer<[import("haywire").NonExtendable<import("./cli.js").LifecycleCli, typeof import("./cli.js").LifecycleCli, null, false, false, false>] | [import("haywire").NonExtendable<import("./cli.js").LifecycleCli, typeof import("./cli.js").LifecycleCli, null, false, false, true>] | [import("haywire").NonExtendable<import("./cli.js").LifecycleCli, typeof import("./cli.js").LifecycleCli, null, false, true, false>] | [import("haywire").NonExtendable<import("./cli.js").LifecycleCli, typeof import("./cli.js").LifecycleCli, null, false, true, true>] | import("haywire").CombineListOutputs<[import("haywire").NonExtendable<import("./commands/lib/types.js").RegisterCommand, null, null, true, false, false>] | [import("haywire").NonExtendable<import("./commands/lib/types.js").RegisterCommand, null, null, true, false, true>] | [import("haywire").NonExtendable<import("./commands/lib/types.js").RegisterCommand, null, null, true, true, false>] | [import("haywire").NonExtendable<import("./commands/lib/types.js").RegisterCommand, null, null, true, true, true>], [import("haywire").NonExtendable<import("./commands/lib/types.js").RegisterCommand, null, null, true, false, false>] | [import("haywire").NonExtendable<import("./commands/lib/types.js").RegisterCommand, null, null, true, false, true>] | [import("haywire").NonExtendable<import("./commands/lib/types.js").RegisterCommand, null, null, true, true, false>] | [import("haywire").NonExtendable<import("./commands/lib/types.js").RegisterCommand, null, null, true, true, true>]>>;
3
+ //# sourceMappingURL=container.d.ts.map
@@ -0,0 +1,8 @@
1
+ import { createContainer, createModule } from 'haywire';
2
+ import { lifecycleCliBinding } from './cli.js';
3
+ import { explainCommandBinding } from './commands/explain-command.js';
4
+ import { lifecycleCommandBinding } from './commands/lifecycle-command.js';
5
+ export const cliModule = createModule(lifecycleCliBinding).addBinding(lifecycleCommandBinding).addBinding(explainCommandBinding);
6
+ export const cliContainer = createContainer(cliModule);
7
+
8
+ //# sourceMappingURL=container.js.map
@@ -2,13 +2,10 @@ const normalizeDependency = (dependency)=>{
2
2
  if (typeof dependency !== 'string') {
3
3
  return dependency;
4
4
  }
5
- if (dependency.startsWith('^')) {
6
- return {
7
- target: dependency.slice(1),
8
- dependencies: true
9
- };
10
- }
11
- return {
5
+ return dependency.startsWith('^') ? {
6
+ target: dependency.slice(1),
7
+ dependencies: true
8
+ } : {
12
9
  target: dependency
13
10
  };
14
11
  };
@@ -18,13 +15,17 @@ const resolveUpstream = (graph, projectName, target)=>{
18
15
  projectName
19
16
  ]);
20
17
  const visit = (name)=>{
21
- for (const dependency of graph.dependencies[name] ?? []){
18
+ const dependencies = graph.dependencies[name];
19
+ if (!dependencies) {
20
+ return;
21
+ }
22
+ for (const dependency of dependencies){
22
23
  const node = graph.nodes[dependency.target];
23
24
  if (!node || visited.has(dependency.target)) {
24
25
  continue;
25
26
  }
26
27
  visited.add(dependency.target);
27
- if (node.data.targets && target in node.data.targets) {
28
+ if (node.data.targets && Object.hasOwn(node.data.targets, target)) {
28
29
  upstream.add(dependency.target);
29
30
  } else {
30
31
  visit(dependency.target);
@@ -85,9 +86,7 @@ export const explainLifecycle = ({ plan, graph, project, stage })=>{
85
86
  throw new Error(`Project ${project} not found`);
86
87
  }
87
88
  const targets = node.data.targets ?? {};
88
- const stageNames = [
89
- ...plan.lifecycleTargets.values()
90
- ].filter((target)=>target.kind === 'base').map((target)=>target.name);
89
+ const stageNames = plan.lifecycleTargets.values().filter((target)=>target.kind === 'base').map((target)=>target.name).toArray();
91
90
  if (stage !== undefined && !stageNames.includes(stage)) {
92
91
  throw new Error(`Stage ${stage} not found. Stages: ${stageNames.join(', ')}`);
93
92
  }
@@ -106,9 +105,7 @@ export const explainLifecycle = ({ plan, graph, project, stage })=>{
106
105
  const stages = orderStages(plan, stageDependencies, stage).map((stageName)=>{
107
106
  const base = plan.lifecycleTargets.get(stageName);
108
107
  const anchor = plan.lifecycleTargets.get(`${stageName}:_`);
109
- const hookTargets = [
110
- ...plan.lifecycleTargets.values()
111
- ].filter((target)=>target.stage === stageName && (target.kind === 'hook' || target.kind === 'base' && !target.hasHooks));
108
+ const hookTargets = plan.lifecycleTargets.values().filter((target)=>target.stage === stageName && (target.kind === 'hook' || target.kind === 'base' && !target.hasHooks)).toArray();
112
109
  return {
113
110
  name: stageName,
114
111
  dependsOn: anchor.dependsOn.map((dependency)=>explainDependency(graph, project, dependency)),
@@ -116,7 +113,7 @@ export const explainLifecycle = ({ plan, graph, project, stage })=>{
116
113
  name: hook.name,
117
114
  targets: [
118
115
  ...plan.registeredTargets
119
- ].filter(([targetName, lifecycleTarget])=>lifecycleTarget.name === hook.name && targetName in targets).map(([targetName, lifecycleTarget])=>explainTarget(targetName, lifecycleTarget.previousHook))
116
+ ].filter(([targetName, lifecycleTarget])=>lifecycleTarget.name === hook.name && Object.hasOwn(targets, targetName)).map(([targetName, lifecycleTarget])=>explainTarget(targetName, lifecycleTarget.previousHook))
120
117
  })),
121
118
  hasHooks: base.kind === 'base' && base.hasHooks
122
119
  };
@@ -60,12 +60,15 @@ const registerTargets = ({ lifecycleTargets, options })=>{
60
60
  };
61
61
  const calculateTargetsToRemove = ({ lifecycleTargets, nxJson })=>{
62
62
  const targetsToRemove = new Set();
63
- for (const [targetName, target] of lifecycleTargets.entries()){
63
+ for (const [targetName, target] of lifecycleTargets){
64
64
  if (target.kind !== 'base') {
65
65
  targetsToRemove.add(targetName);
66
66
  }
67
67
  }
68
- for (const [targetName, target] of Object.entries(nxJson.targetDefaults ?? {})){
68
+ if (!nxJson.targetDefaults) {
69
+ return targetsToRemove;
70
+ }
71
+ for (const [targetName, target] of Object.entries(nxJson.targetDefaults)){
69
72
  if (!lifecycleTargets.has(targetName) && '__lifecycle' in (target.configurations ?? {})) {
70
73
  targetsToRemove.add(targetName);
71
74
  }
@@ -77,12 +80,15 @@ const normalizeDependencyTarget = (dependencyTarget)=>dependencyTarget.replace(/
77
80
  const removeDependencyTargets = ({ target, targetsToRemove })=>(target.dependsOn ?? []).filter((dependency)=>!targetsToRemove.has(normalizeDependencyTarget(getDependencyTarget(dependency))));
78
81
  const validateLifecycleDependencies = ({ lifecycleTargets, options })=>{
79
82
  for (const [stageName, { dependsOn }] of Object.entries(options.stages)){
80
- for (const dependency of dependsOn ?? []){
83
+ if (!dependsOn) {
84
+ continue;
85
+ }
86
+ for (const dependency of dependsOn){
81
87
  const dependencyTarget = getDependencyTarget(dependency);
82
88
  const normalized = normalizeDependencyTarget(dependencyTarget);
83
89
  const lifecycleTarget = lifecycleTargets.get(normalized);
84
90
  if (lifecycleTarget && lifecycleTarget.kind !== 'base') {
85
- const suggested = dependencyTarget.replace(normalized, lifecycleTarget.stage);
91
+ const suggested = dependencyTarget.replace(normalized, ()=>lifecycleTarget.stage);
86
92
  throw new Error(`Lifecycle stage ${stageName} cannot depend on ${dependencyTarget}, which is internal to stage ${lifecycleTarget.stage}. Depend on ${suggested} instead`);
87
93
  }
88
94
  }
@@ -90,7 +96,10 @@ const validateLifecycleDependencies = ({ lifecycleTargets, options })=>{
90
96
  };
91
97
  const validateStaleDependencies = ({ options, targetsToRemove })=>{
92
98
  for (const [stageName, { dependsOn }] of Object.entries(options.stages)){
93
- for (const dependency of dependsOn ?? []){
99
+ if (!dependsOn) {
100
+ continue;
101
+ }
102
+ for (const dependency of dependsOn){
94
103
  const dependencyTarget = getDependencyTarget(dependency);
95
104
  if (targetsToRemove.has(normalizeDependencyTarget(dependencyTarget))) {
96
105
  throw new Error(`Lifecycle stage ${stageName} cannot depend on ${dependencyTarget}, which is a stale lifecycle target in nx.json`);
@@ -118,7 +127,7 @@ const processNxJson = ({ nxJson, lifecycleTargets, registeredTargets, targetsToR
118
127
  ...originalTargets
119
128
  };
120
129
  const lifecycles = {};
121
- for (const [targetName, target] of lifecycleTargets.entries()){
130
+ for (const [targetName, target] of lifecycleTargets){
122
131
  lifecycles[targetName] = {
123
132
  executor: NOOP_EXECUTOR,
124
133
  dependsOn: [
@@ -146,7 +155,7 @@ const processNxJson = ({ nxJson, lifecycleTargets, registeredTargets, targetsToR
146
155
  };
147
156
  }
148
157
  }
149
- for (const [targetName, lifecycleTarget] of registeredTargets.entries()){
158
+ for (const [targetName, lifecycleTarget] of registeredTargets){
150
159
  const registeredTarget = targetDefaults[targetName] ?? {};
151
160
  registeredTarget.dependsOn ??= [];
152
161
  registeredTarget.dependsOn.push(lifecycleTarget.previousHook);
@@ -155,11 +164,12 @@ const processNxJson = ({ nxJson, lifecycleTargets, registeredTargets, targetsToR
155
164
  }
156
165
  Object.assign(targetDefaults, lifecycles);
157
166
  for (const [targetName, target] of Object.entries(targetDefaults)){
158
- if (target.dependsOn.length === 0) {
159
- const originalTarget = originalTargets[targetName] ?? {};
160
- if (!originalTarget.dependsOn) {
161
- delete target.dependsOn;
162
- }
167
+ if (target.dependsOn.length > 0) {
168
+ continue;
169
+ }
170
+ const originalTarget = originalTargets[targetName] ?? {};
171
+ if (!originalTarget.dependsOn) {
172
+ delete target.dependsOn;
163
173
  }
164
174
  }
165
175
  return {