@nx/devkit 23.2.0-beta.11 → 23.2.0-beta.12
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/dist/internal.d.ts +4 -2
- package/dist/internal.js +16 -5
- package/dist/src/generators/e2e-web-server-info-utils.d.ts +6 -0
- package/dist/src/generators/e2e-web-server-info-utils.js +8 -4
- package/dist/src/generators/plugin-migrations/batch-conversion-finalize.d.ts +19 -0
- package/dist/src/generators/plugin-migrations/batch-conversion-finalize.js +658 -0
- package/dist/src/generators/plugin-migrations/batch-conversion-session.d.ts +131 -0
- package/dist/src/generators/plugin-migrations/batch-conversion-session.js +141 -0
- package/dist/src/generators/plugin-migrations/conversion-warnings.d.ts +28 -0
- package/dist/src/generators/plugin-migrations/conversion-warnings.js +72 -0
- package/dist/src/generators/plugin-migrations/executor-to-plugin-migrator.d.ts +250 -1
- package/dist/src/generators/plugin-migrations/executor-to-plugin-migrator.js +1594 -227
- package/dist/src/generators/target-defaults-utils.d.ts +8 -0
- package/dist/src/generators/target-defaults-utils.js +17 -5
- package/dist/src/utils/config-utils.d.ts +3 -0
- package/dist/src/utils/config-utils.js +197 -27
- package/package.json +2 -2
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { type CreateNodes, type ExpandedPluginConfiguration, type ProjectGraphProjectNode, type Tree, logger as devkitLogger } from 'nx/src/devkit-exports';
|
|
2
|
+
import type { ResidualByProject } from './executor-to-plugin-migrator';
|
|
3
|
+
/**
|
|
4
|
+
* The evidence one deferred engine invocation stages for the batch finalize
|
|
5
|
+
* pass. Everything is captured by value at staging time (see `stagePlan`)
|
|
6
|
+
* because later children in the batch keep mutating the Tree and their own
|
|
7
|
+
* caches; the only live references kept are the plugin's `createNodes`
|
|
8
|
+
* functions, which the finalize verification pass needs to run.
|
|
9
|
+
*/
|
|
10
|
+
export interface DeferredConversionPlan {
|
|
11
|
+
pluginPath: string;
|
|
12
|
+
createNodes: CreateNodes | undefined;
|
|
13
|
+
createNodesV2: CreateNodes | undefined;
|
|
14
|
+
logger: typeof devkitLogger | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Whether the plugin was registered in nx.json before this conversion wrote
|
|
17
|
+
* its registrations. Gates dead executor-keyed target-default cleanup, as in
|
|
18
|
+
* the inline engine path.
|
|
19
|
+
*/
|
|
20
|
+
pluginPreRegistered: boolean;
|
|
21
|
+
/** Per-(project, target) residuals + equivalence-oracle baselines (Phase 2). */
|
|
22
|
+
residualByProject: ResidualByProject;
|
|
23
|
+
/** project name -> project root for every migrated project. */
|
|
24
|
+
rootByProject: Map<string, string>;
|
|
25
|
+
/**
|
|
26
|
+
* project name -> pre-migration graph node for every migrated project. The
|
|
27
|
+
* finalize target-default preflight resolves `filter.projects` entries
|
|
28
|
+
* through the production reader, which matches against the project's node.
|
|
29
|
+
*/
|
|
30
|
+
graphNodeByProject: Map<string, ProjectGraphProjectNode>;
|
|
31
|
+
/**
|
|
32
|
+
* Every project root in the pre-migration graph. With the plans' Phase 1
|
|
33
|
+
* inferred roots, these are the known owners the finalize pass attributes
|
|
34
|
+
* errored config files to (closest root wins).
|
|
35
|
+
*/
|
|
36
|
+
graphRoots: Set<string>;
|
|
37
|
+
/**
|
|
38
|
+
* `"<project>\t<target>"` -> the effective executor the pair resolves to
|
|
39
|
+
* after migration (the inferred target's; a `command` resolves to
|
|
40
|
+
* `nx:run-commands`). Input to the finalize target-default preflight.
|
|
41
|
+
*/
|
|
42
|
+
inferredExecutorByPair: Map<string, string | undefined>;
|
|
43
|
+
/** Every effective executor the plugin's Phase 1 inference emitted. */
|
|
44
|
+
inferredExecutors: Set<string>;
|
|
45
|
+
/** Every root the plugin's Phase 1 inference produced a project for. */
|
|
46
|
+
inferredRoots: Set<string>;
|
|
47
|
+
/** Config files matched by the plugin's glob and owned by an inferred root. */
|
|
48
|
+
matchedConfigFiles: string[];
|
|
49
|
+
/** Config files the Phase 1 inference could not load. */
|
|
50
|
+
erroredConfigFiles: string[];
|
|
51
|
+
/** The migrated executors (Phase 0 scope): dead-default cleanup candidates. */
|
|
52
|
+
migratedExecutors: string[];
|
|
53
|
+
/**
|
|
54
|
+
* `"<project>\t<target>"` -> executor from the pre-migration project graph.
|
|
55
|
+
* Feeds the batch-global liveness scan: a pair no plan migrated still
|
|
56
|
+
* resolves its graph executor, keeping that executor's defaults live.
|
|
57
|
+
*/
|
|
58
|
+
graphExecutorByPair: Map<string, string>;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* One child generator run inside the batch: the `nx.json` `plugins` snapshots
|
|
62
|
+
* around it and the plans its engine invocations staged. A registration delta
|
|
63
|
+
* that no staged plan's plugin accounts for is an opaque barrier for the
|
|
64
|
+
* finalize planner (e.g. a converter that bypasses the engine entirely, or
|
|
65
|
+
* registers an unrelated plugin).
|
|
66
|
+
*/
|
|
67
|
+
export interface BatchChildRecord {
|
|
68
|
+
pluginsBefore: (string | ExpandedPluginConfiguration)[];
|
|
69
|
+
pluginsAfter: (string | ExpandedPluginConfiguration)[];
|
|
70
|
+
plans: DeferredConversionPlan[];
|
|
71
|
+
}
|
|
72
|
+
/** The engine-facing slice of the session: stage a plan for the running child. */
|
|
73
|
+
export interface BatchConversionStaging {
|
|
74
|
+
stagePlan(plan: DeferredConversionPlan): void;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A batch of convert-to-inferred generator runs against one Tree
|
|
78
|
+
* (`infer-targets` with several plugins selected). While a child runs inside
|
|
79
|
+
* `runChild`, the engine defers centralization: it writes full residuals,
|
|
80
|
+
* skips the hoist / dead-default cleanup / verification pass, and stages a
|
|
81
|
+
* {@link DeferredConversionPlan} here instead. The staged evidence is committed
|
|
82
|
+
* only when the child generator resolves, so a failed child contributes
|
|
83
|
+
* nothing. A finalize pass consumes the committed records after the batch loop.
|
|
84
|
+
*
|
|
85
|
+
* Open with {@link openBatchConversionSession} and always `close()` in a
|
|
86
|
+
* `finally` so the Tree's engine invocations return to the inline path.
|
|
87
|
+
*/
|
|
88
|
+
export declare class BatchConversionSession {
|
|
89
|
+
private readonly tree;
|
|
90
|
+
private readonly children;
|
|
91
|
+
private pendingPlans;
|
|
92
|
+
private closed;
|
|
93
|
+
constructor(tree: Tree);
|
|
94
|
+
/** The committed child records, in batch order. */
|
|
95
|
+
get records(): readonly BatchChildRecord[];
|
|
96
|
+
/**
|
|
97
|
+
* Run one child generator with deferred centralization. Commits the plans
|
|
98
|
+
* its engine invocations staged only when `fn` resolves; a rejection (a
|
|
99
|
+
* failed child, or `NoTargetsToMigrateError`) discards them.
|
|
100
|
+
*/
|
|
101
|
+
runChild<T>(fn: () => T | Promise<T>): Promise<T>;
|
|
102
|
+
/**
|
|
103
|
+
* Stage a deferred plan for the running child (engine-facing; reach it via
|
|
104
|
+
* {@link getActiveBatchStaging}). Clones every mutable structure so the
|
|
105
|
+
* staged evidence is immune to later Tree/cache mutations; the `createNodes`
|
|
106
|
+
* references are kept live for the finalize verification pass.
|
|
107
|
+
*/
|
|
108
|
+
stagePlan(plan: DeferredConversionPlan): void;
|
|
109
|
+
private hasRunningChild;
|
|
110
|
+
/**
|
|
111
|
+
* End the session: engine invocations on the Tree return to the inline path.
|
|
112
|
+
* Rejected while a child is running; otherwise a still-running child would
|
|
113
|
+
* fall back to inline centralization mid-batch, or stage its plan into a
|
|
114
|
+
* session opened after this one. `runChild` always settles its child before
|
|
115
|
+
* returning or throwing, so a `finally { session.close() }` never hits this.
|
|
116
|
+
*/
|
|
117
|
+
close(): void;
|
|
118
|
+
/** @internal module-level accessor for {@link getActiveBatchStaging}. */
|
|
119
|
+
static activeStagingFor(tree: Tree): BatchConversionStaging | undefined;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Open a batch conversion session for `tree`. Throws when one is already open:
|
|
123
|
+
* sessions do not nest (each child in a batch must observe the same session).
|
|
124
|
+
*/
|
|
125
|
+
export declare function openBatchConversionSession(tree: Tree): BatchConversionSession;
|
|
126
|
+
/**
|
|
127
|
+
* The staging handle for `tree`, or `undefined` when no batch child is
|
|
128
|
+
* currently running (no session, or the session is between children). The
|
|
129
|
+
* engine checks this to decide between the inline path and deferred staging.
|
|
130
|
+
*/
|
|
131
|
+
export declare function getActiveBatchStaging(tree: Tree): BatchConversionStaging | undefined;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BatchConversionSession = void 0;
|
|
4
|
+
exports.openBatchConversionSession = openBatchConversionSession;
|
|
5
|
+
exports.getActiveBatchStaging = getActiveBatchStaging;
|
|
6
|
+
const devkit_exports_1 = require("nx/src/devkit-exports");
|
|
7
|
+
// Keyed by Tree identity so a session cannot leak into another generator
|
|
8
|
+
// invocation. At most one session per Tree.
|
|
9
|
+
const activeSessions = new WeakMap();
|
|
10
|
+
/**
|
|
11
|
+
* A batch of convert-to-inferred generator runs against one Tree
|
|
12
|
+
* (`infer-targets` with several plugins selected). While a child runs inside
|
|
13
|
+
* `runChild`, the engine defers centralization: it writes full residuals,
|
|
14
|
+
* skips the hoist / dead-default cleanup / verification pass, and stages a
|
|
15
|
+
* {@link DeferredConversionPlan} here instead. The staged evidence is committed
|
|
16
|
+
* only when the child generator resolves, so a failed child contributes
|
|
17
|
+
* nothing. A finalize pass consumes the committed records after the batch loop.
|
|
18
|
+
*
|
|
19
|
+
* Open with {@link openBatchConversionSession} and always `close()` in a
|
|
20
|
+
* `finally` so the Tree's engine invocations return to the inline path.
|
|
21
|
+
*/
|
|
22
|
+
class BatchConversionSession {
|
|
23
|
+
constructor(tree) {
|
|
24
|
+
this.tree = tree;
|
|
25
|
+
this.children = [];
|
|
26
|
+
this.closed = false;
|
|
27
|
+
}
|
|
28
|
+
/** The committed child records, in batch order. */
|
|
29
|
+
get records() {
|
|
30
|
+
return this.children;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Run one child generator with deferred centralization. Commits the plans
|
|
34
|
+
* its engine invocations staged only when `fn` resolves; a rejection (a
|
|
35
|
+
* failed child, or `NoTargetsToMigrateError`) discards them.
|
|
36
|
+
*/
|
|
37
|
+
async runChild(fn) {
|
|
38
|
+
if (this.closed) {
|
|
39
|
+
throw new Error('The convert-to-inferred batch session has been closed; open a new one to run more conversions.');
|
|
40
|
+
}
|
|
41
|
+
if (this.pendingPlans) {
|
|
42
|
+
throw new Error('A convert-to-inferred batch child is already running; batch children must run sequentially.');
|
|
43
|
+
}
|
|
44
|
+
const pluginsBefore = structuredClone((0, devkit_exports_1.readNxJson)(this.tree)?.plugins ?? []);
|
|
45
|
+
this.pendingPlans = [];
|
|
46
|
+
try {
|
|
47
|
+
const result = await fn();
|
|
48
|
+
this.children.push({
|
|
49
|
+
pluginsBefore,
|
|
50
|
+
pluginsAfter: structuredClone((0, devkit_exports_1.readNxJson)(this.tree)?.plugins ?? []),
|
|
51
|
+
plans: this.pendingPlans,
|
|
52
|
+
});
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
this.pendingPlans = undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Stage a deferred plan for the running child (engine-facing; reach it via
|
|
61
|
+
* {@link getActiveBatchStaging}). Clones every mutable structure so the
|
|
62
|
+
* staged evidence is immune to later Tree/cache mutations; the `createNodes`
|
|
63
|
+
* references are kept live for the finalize verification pass.
|
|
64
|
+
*/
|
|
65
|
+
stagePlan(plan) {
|
|
66
|
+
if (!this.pendingPlans) {
|
|
67
|
+
throw new Error('Cannot stage a conversion plan: no batch child is running.');
|
|
68
|
+
}
|
|
69
|
+
const residualByProject = new Map();
|
|
70
|
+
for (const [projectName, targetMap] of plan.residualByProject) {
|
|
71
|
+
const clonedTargetMap = new Map();
|
|
72
|
+
for (const [targetName, entry] of targetMap) {
|
|
73
|
+
clonedTargetMap.set(targetName, structuredClone(entry));
|
|
74
|
+
}
|
|
75
|
+
residualByProject.set(projectName, clonedTargetMap);
|
|
76
|
+
}
|
|
77
|
+
const graphNodeByProject = new Map();
|
|
78
|
+
for (const [projectName, node] of plan.graphNodeByProject) {
|
|
79
|
+
graphNodeByProject.set(projectName, structuredClone(node));
|
|
80
|
+
}
|
|
81
|
+
this.pendingPlans.push({
|
|
82
|
+
...plan,
|
|
83
|
+
residualByProject,
|
|
84
|
+
rootByProject: new Map(plan.rootByProject),
|
|
85
|
+
graphNodeByProject,
|
|
86
|
+
graphRoots: new Set(plan.graphRoots),
|
|
87
|
+
inferredExecutorByPair: new Map(plan.inferredExecutorByPair),
|
|
88
|
+
inferredExecutors: new Set(plan.inferredExecutors),
|
|
89
|
+
inferredRoots: new Set(plan.inferredRoots),
|
|
90
|
+
matchedConfigFiles: [...plan.matchedConfigFiles],
|
|
91
|
+
erroredConfigFiles: [...plan.erroredConfigFiles],
|
|
92
|
+
migratedExecutors: [...plan.migratedExecutors],
|
|
93
|
+
graphExecutorByPair: new Map(plan.graphExecutorByPair),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
hasRunningChild() {
|
|
97
|
+
return this.pendingPlans !== undefined;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* End the session: engine invocations on the Tree return to the inline path.
|
|
101
|
+
* Rejected while a child is running; otherwise a still-running child would
|
|
102
|
+
* fall back to inline centralization mid-batch, or stage its plan into a
|
|
103
|
+
* session opened after this one. `runChild` always settles its child before
|
|
104
|
+
* returning or throwing, so a `finally { session.close() }` never hits this.
|
|
105
|
+
*/
|
|
106
|
+
close() {
|
|
107
|
+
if (this.pendingPlans) {
|
|
108
|
+
throw new Error('Cannot close the convert-to-inferred batch session while a child conversion is running.');
|
|
109
|
+
}
|
|
110
|
+
this.closed = true;
|
|
111
|
+
if (activeSessions.get(this.tree) === this) {
|
|
112
|
+
activeSessions.delete(this.tree);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/** @internal module-level accessor for {@link getActiveBatchStaging}. */
|
|
116
|
+
static activeStagingFor(tree) {
|
|
117
|
+
const session = activeSessions.get(tree);
|
|
118
|
+
return session?.hasRunningChild() ? session : undefined;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
exports.BatchConversionSession = BatchConversionSession;
|
|
122
|
+
/**
|
|
123
|
+
* Open a batch conversion session for `tree`. Throws when one is already open:
|
|
124
|
+
* sessions do not nest (each child in a batch must observe the same session).
|
|
125
|
+
*/
|
|
126
|
+
function openBatchConversionSession(tree) {
|
|
127
|
+
if (activeSessions.has(tree)) {
|
|
128
|
+
throw new Error('A convert-to-inferred batch session is already open for this Tree.');
|
|
129
|
+
}
|
|
130
|
+
const session = new BatchConversionSession(tree);
|
|
131
|
+
activeSessions.set(tree, session);
|
|
132
|
+
return session;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The staging handle for `tree`, or `undefined` when no batch child is
|
|
136
|
+
* currently running (no session, or the session is between children). The
|
|
137
|
+
* engine checks this to decide between the inline path and deferred staging.
|
|
138
|
+
*/
|
|
139
|
+
function getActiveBatchStaging(tree) {
|
|
140
|
+
return BatchConversionSession.activeStagingFor(tree);
|
|
141
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Warning messages the inline hoist (`executor-to-plugin-migrator`) and the
|
|
3
|
+
* batch finalize (`batch-conversion-finalize`) emit for the same outcomes. One
|
|
4
|
+
* builder per message so both paths stay word for word identical.
|
|
5
|
+
*/
|
|
6
|
+
import type { TargetConfiguration } from 'nx/src/devkit-exports';
|
|
7
|
+
/** Which package.json signal authors a target's identity. */
|
|
8
|
+
export type PackageJsonIdentitySource = 'script' | 'nxTargets' | 'unparseable';
|
|
9
|
+
/** A migrated target was left untouched because package.json authors its identity. */
|
|
10
|
+
export declare function keptPreMigrationTargetWarning(targetName: string, projectName: string, source: PackageJsonIdentitySource): string;
|
|
11
|
+
/** A target's centralization was skipped before writing anything. */
|
|
12
|
+
export declare function retainedResidualsWarning(targetNames: string[], reason: string): string;
|
|
13
|
+
/** Projects whose target identity lives outside the plugin were not hoisted. */
|
|
14
|
+
export declare function excludedProjectsWarning(projectNames: string[], targetNames: string[]): string;
|
|
15
|
+
/** A hoisted target was reverted because it reached a non-migrated root. */
|
|
16
|
+
export declare function revertedTargetsWarning(targetNames: string[], errors: string[]): string;
|
|
17
|
+
/** A pair the verification pass did not infer at all. */
|
|
18
|
+
export interface MissingPair {
|
|
19
|
+
pair: string;
|
|
20
|
+
root: string;
|
|
21
|
+
/** The target no longer exists once the residual is restored. */
|
|
22
|
+
removed: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** Whether Nx drops an explicit target with this configuration (target normalization). */
|
|
25
|
+
export declare function isDroppedTarget(target: TargetConfiguration): boolean;
|
|
26
|
+
export declare function unverifiedPairsWarning(divergent: string[], missing: MissingPair[], errors: string[]): string;
|
|
27
|
+
/** Verification errors that neither a revert nor a fallback warning carried. */
|
|
28
|
+
export declare function verificationErrorsWarning(errors: string[], anyFallback: boolean): string;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Warning messages the inline hoist (`executor-to-plugin-migrator`) and the
|
|
4
|
+
* batch finalize (`batch-conversion-finalize`) emit for the same outcomes. One
|
|
5
|
+
* builder per message so both paths stay word for word identical.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.keptPreMigrationTargetWarning = keptPreMigrationTargetWarning;
|
|
9
|
+
exports.retainedResidualsWarning = retainedResidualsWarning;
|
|
10
|
+
exports.excludedProjectsWarning = excludedProjectsWarning;
|
|
11
|
+
exports.revertedTargetsWarning = revertedTargetsWarning;
|
|
12
|
+
exports.isDroppedTarget = isDroppedTarget;
|
|
13
|
+
exports.unverifiedPairsWarning = unverifiedPairsWarning;
|
|
14
|
+
exports.verificationErrorsWarning = verificationErrorsWarning;
|
|
15
|
+
/** A migrated target was left untouched because package.json authors its identity. */
|
|
16
|
+
function keptPreMigrationTargetWarning(targetName, projectName, source) {
|
|
17
|
+
const cause = {
|
|
18
|
+
script: `an included package.json script named "${targetName}" would replace the inferred target with nx:run-script once the explicit executor is removed. Rename or exclude the script`,
|
|
19
|
+
nxTargets: `the package.json nx.targets entry for "${targetName}" (next to project.json) would replace the inferred target once the explicit executor is removed. Remove that entry`,
|
|
20
|
+
unparseable: `its package.json could not be parsed, so a same-name script that would replace the inferred target cannot be ruled out. Fix the file`,
|
|
21
|
+
}[source];
|
|
22
|
+
return `convert-to-inferred kept the pre-migration configuration of target "${targetName}" in project "${projectName}": ${cause}, then rerun the migration to convert it. The target keeps the same behavior as before the migration.`;
|
|
23
|
+
}
|
|
24
|
+
function withCauses(errors) {
|
|
25
|
+
return errors.length > 0
|
|
26
|
+
? ` The verification pass reported errors: ${errors.join('; ')}`
|
|
27
|
+
: '';
|
|
28
|
+
}
|
|
29
|
+
/** A target's centralization was skipped before writing anything. */
|
|
30
|
+
function retainedResidualsWarning(targetNames, reason) {
|
|
31
|
+
return `convert-to-inferred retained full per-project configuration for target(s) ${targetNames.join(', ')} because ${reason}; no configuration was lost, but shared configuration remains duplicated.`;
|
|
32
|
+
}
|
|
33
|
+
/** Projects whose target identity lives outside the plugin were not hoisted. */
|
|
34
|
+
function excludedProjectsWarning(projectNames, targetNames) {
|
|
35
|
+
return `convert-to-inferred kept per-project configuration for ${projectNames.length} project(s) (${projectNames.join(', ')}) on target(s) ${targetNames.join(', ')} instead of centralizing it: their target identity is authored outside the plugin (a project.json executor/command, or a package.json script/nx.targets entry), so a plugin-scoped default would not resolve for them. Those projects keep their full per-project configuration; review them if you expected shared configuration.`;
|
|
36
|
+
}
|
|
37
|
+
/** A hoisted target was reverted because it reached a non-migrated root. */
|
|
38
|
+
function revertedTargetsWarning(targetNames, errors) {
|
|
39
|
+
return `convert-to-inferred kept per-project configuration for target(s) ${targetNames.join(', ')} instead of centralizing it: other projects inferred by this plugin would have inherited the centralized configuration (or the verification pass could not confirm they would not). The migrated projects keep the same output as before centralization.${withCauses(errors)}`;
|
|
40
|
+
}
|
|
41
|
+
/** Whether Nx drops an explicit target with this configuration (target normalization). */
|
|
42
|
+
function isDroppedTarget(target) {
|
|
43
|
+
return (!target.executor &&
|
|
44
|
+
!target.command &&
|
|
45
|
+
!(target.dependsOn && target.dependsOn.length > 0));
|
|
46
|
+
}
|
|
47
|
+
function unverifiedPairsWarning(divergent, missing, errors) {
|
|
48
|
+
const pairs = [...divergent, ...missing.map((m) => m.pair)];
|
|
49
|
+
let message = `convert-to-inferred restored the pre-centralization migration output for ${pairs.length} target(s) that could not be verified as equivalent after migration: ${pairs.join(', ')}.`;
|
|
50
|
+
if (divergent.length > 0) {
|
|
51
|
+
message += ` Centralized nx.json defaults are shadowed where their keys overlap, but the live inferred configuration may differ from the pre-migration behavior for ${divergent.join(', ')}.`;
|
|
52
|
+
}
|
|
53
|
+
if (missing.length > 0) {
|
|
54
|
+
message += ` The plugin did not infer ${missing
|
|
55
|
+
.map((m) => `${m.pair} (root ${m.root})`)
|
|
56
|
+
.join(', ')} on the verification pass.`;
|
|
57
|
+
const removed = missing.filter((m) => m.removed);
|
|
58
|
+
if (removed.length > 0) {
|
|
59
|
+
message += ` The following targets no longer exist because each restored configuration has no executor, command or dependsOn: ${removed
|
|
60
|
+
.map((m) => m.pair)
|
|
61
|
+
.join(', ')}.`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return `${message} Review these targets manually.${withCauses(errors)}`;
|
|
65
|
+
}
|
|
66
|
+
/** Verification errors that neither a revert nor a fallback warning carried. */
|
|
67
|
+
function verificationErrorsWarning(errors, anyFallback) {
|
|
68
|
+
const outcome = anyFallback
|
|
69
|
+
? ' Review any workspace configuration the errors reference.'
|
|
70
|
+
: ' The migrated targets matched their pre-migration output, but review any workspace configuration the errors reference.';
|
|
71
|
+
return `convert-to-inferred could not fully verify the migration: the verification inference pass reported errors: ${errors.join('; ')}.${outcome}`;
|
|
72
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ProjectConfiguration } from 'nx/src/config/workspace-json-project-json';
|
|
2
|
-
import { type CreateNodes, type NxJsonConfiguration, type ProjectGraph, type TargetConfiguration, type Tree, logger as devkitLogger } from 'nx/src/devkit-exports';
|
|
2
|
+
import { type CreateNodes, type ExpandedPluginConfiguration, type NxJsonConfiguration, type ProjectGraph, type ProjectGraphProjectNode, type TargetConfiguration, type TargetDefaultArrayEntry, type Tree, logger as devkitLogger } from 'nx/src/devkit-exports';
|
|
3
|
+
import { ProjectConfigurationsError } from 'nx/src/devkit-internals';
|
|
4
|
+
import { type PackageJsonIdentitySource } from './conversion-warnings';
|
|
3
5
|
export type InferredTargetConfiguration = TargetConfiguration & {
|
|
4
6
|
name: string;
|
|
5
7
|
};
|
|
@@ -9,6 +11,185 @@ type PostTargetTransformer = (targetConfiguration: TargetConfiguration, tree: Tr
|
|
|
9
11
|
}, inferredTargetConfiguration: InferredTargetConfiguration) => TargetConfiguration | Promise<TargetConfiguration>;
|
|
10
12
|
type SkipTargetFilter = (targetOptions: Record<string, unknown>, projectConfiguration: ProjectConfiguration) => false | string;
|
|
11
13
|
type SkipProjectFilter = (projectConfiguration: ProjectConfiguration) => false | string;
|
|
14
|
+
type MigrationDefinition<T> = {
|
|
15
|
+
executors: string[];
|
|
16
|
+
targetPluginOptionMapper: (targetName: string) => Partial<T>;
|
|
17
|
+
postTargetTransformer: PostTargetTransformer;
|
|
18
|
+
skipProjectFilter?: SkipProjectFilter;
|
|
19
|
+
skipTargetFilter?: SkipTargetFilter;
|
|
20
|
+
/**
|
|
21
|
+
* Let several targets of a project map different target names through the
|
|
22
|
+
* same plugin option (last write wins) instead of keeping the later targets
|
|
23
|
+
* executor-based. Only for migrations that reconcile the resulting
|
|
24
|
+
* registration afterwards, like the detox one.
|
|
25
|
+
*/
|
|
26
|
+
allowSharedOptionOverwrite?: boolean;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* A distinct plugin-option set used to infer targets (Phase 1). `options` is the
|
|
30
|
+
* value passed to the plugin's `createNodes` (i.e. `targetPluginOptionMapper`'s
|
|
31
|
+
* output) and `targetNames` are the migrated target names that option set is
|
|
32
|
+
* responsible for producing.
|
|
33
|
+
*/
|
|
34
|
+
interface InferenceOptionSet<T> {
|
|
35
|
+
/** Stable id used to keep inference results isolated by option set. */
|
|
36
|
+
id: number;
|
|
37
|
+
/**
|
|
38
|
+
* The object handed to the plugin's `createNodes`: the raw
|
|
39
|
+
* `targetPluginOptionMapper` output. The engine never merges its own
|
|
40
|
+
* `defaultPluginOptions` into it (that is why `derivePluginFilledDefaults`
|
|
41
|
+
* skips keys already in the defaults). NOTE: the plugin itself may mutate this
|
|
42
|
+
* object in place during Phase 1 (e.g. `options.devTargetName ??= 'dev'`);
|
|
43
|
+
* `derivePluginFilledDefaults` relies on exactly that mutation, so after
|
|
44
|
+
* Phase 1 this can carry the plugin's own fills too.
|
|
45
|
+
*/
|
|
46
|
+
options: Partial<T>;
|
|
47
|
+
targetNames: Set<string>;
|
|
48
|
+
/**
|
|
49
|
+
* Roots of the projects migrated under this option set. Phase 1 retains
|
|
50
|
+
* cloned inferred targets only for these roots (Phase 2 reads no others), so
|
|
51
|
+
* retention scales with the migrated projects instead of every inferred root
|
|
52
|
+
* times every option set.
|
|
53
|
+
*/
|
|
54
|
+
migratedRoots: Set<string>;
|
|
55
|
+
}
|
|
56
|
+
interface ExecutorScope<T> {
|
|
57
|
+
executor: string;
|
|
58
|
+
migration: MigrationDefinition<T>;
|
|
59
|
+
targetAndProjects: Map<string, Set<string>>;
|
|
60
|
+
inferenceOptionSetIdsByTarget: Map<string, number>;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The result of Phase 0 (Collect). Built by folding `forEachExecutorOptions`
|
|
64
|
+
* over every migration/executor into one scope object, replacing the
|
|
65
|
+
* per-executor scope derivation the migrator used to do internally.
|
|
66
|
+
*/
|
|
67
|
+
export interface MigrationScope<T> {
|
|
68
|
+
/** project -> resolved plugin registration options (defaults + mappers) */
|
|
69
|
+
pluginOptionsByProject: Map<string, T>;
|
|
70
|
+
/** distinct inference option sets paired with the target names they infer */
|
|
71
|
+
optionSetGroups: InferenceOptionSet<T>[];
|
|
72
|
+
/** per (migration, executor) slice used to drive residual computation */
|
|
73
|
+
executorScopes: ExecutorScope<T>[];
|
|
74
|
+
}
|
|
75
|
+
export declare function stableStringify(value: unknown): string;
|
|
76
|
+
/**
|
|
77
|
+
* Phase 0: Collect (once). Fold `forEachExecutorOptions` over every
|
|
78
|
+
* migration/executor into a single scope object, applying the skip filters with
|
|
79
|
+
* the exact same warn-vs-throw semantics the migrator used before (a
|
|
80
|
+
* `specificProjectToMigrate` skip throws instead of warning). This is the single
|
|
81
|
+
* authority for filtering; downstream phases only read the returned maps.
|
|
82
|
+
*/
|
|
83
|
+
export declare function collectMigrationScope<T>(tree: Tree, projectGraph: ProjectGraph, migrations: MigrationDefinition<T>[], defaultPluginOptions: T, specificProjectToMigrate?: string, logger?: typeof devkitLogger): MigrationScope<T>;
|
|
84
|
+
/** The per-project residual and the equivalence oracle baseline for a target. */
|
|
85
|
+
export interface ResidualEntry {
|
|
86
|
+
/** Byte-for-byte what the previous engine writes into project.json. */
|
|
87
|
+
residual: TargetConfiguration;
|
|
88
|
+
/**
|
|
89
|
+
* The migrated (command-based) effective config the previous engine yields:
|
|
90
|
+
* the full inferred target with the residual layered on top. Used in Phase 4
|
|
91
|
+
* as the equivalence oracle.
|
|
92
|
+
*/
|
|
93
|
+
baselineFinal: TargetConfiguration;
|
|
94
|
+
/**
|
|
95
|
+
* The explicit target as authored before the migration. Restored when
|
|
96
|
+
* package.json turns out to author the target's identity (see
|
|
97
|
+
* `writeResidualTarget`, and the batch finalize for identities that appear
|
|
98
|
+
* after the write).
|
|
99
|
+
*/
|
|
100
|
+
preMigrationTarget: TargetConfiguration;
|
|
101
|
+
/**
|
|
102
|
+
* Set by the write phase when the pre-migration target was kept untouched
|
|
103
|
+
* (see `writeResidualTarget`); the verification phase then leaves it alone.
|
|
104
|
+
*/
|
|
105
|
+
keptPreMigration?: boolean;
|
|
106
|
+
}
|
|
107
|
+
/** project name -> (target name -> residual entry) */
|
|
108
|
+
export type ResidualByProject = Map<string, Map<string, ResidualEntry>>;
|
|
109
|
+
export type InferredTargetsByRoot = Map<string, Map<string, TargetConfiguration>>;
|
|
110
|
+
export type InferredTargetsByOptionSet = Map<number, InferredTargetsByRoot>;
|
|
111
|
+
/**
|
|
112
|
+
* Phase 2: Per-project residual (in-memory, no inference). For each
|
|
113
|
+
* `(project, target)` computes the residual exactly as the previous engine did
|
|
114
|
+
* (`mergeTargetConfigurations` with the executor target defaults ->
|
|
115
|
+
* `deleteMatchingProperties` -> input merge -> the plugin's
|
|
116
|
+
* `postTargetTransformer`), plus `baselineFinal = merge(residual, inferred)` as
|
|
117
|
+
* the equivalence oracle. Does NOT write project.json.
|
|
118
|
+
*/
|
|
119
|
+
export declare function computeResidualByProject<T>(tree: Tree, projectGraph: ProjectGraph, scope: MigrationScope<T>, inferredTargetsByOptionSet: InferredTargetsByOptionSet, nxJson: NxJsonConfiguration, projectConfigsByName?: Map<string, ProjectConfiguration>): Promise<ResidualByProject>;
|
|
120
|
+
/**
|
|
121
|
+
* Phase 3: the strict-common residual across ALL migrated projects for a
|
|
122
|
+
* target: the values that are deep-equal across every project's residual.
|
|
123
|
+
* Granularity: whole value for top-level target props (`inputs`, `outputs`,
|
|
124
|
+
* `cache`, `dependsOn`, `configurations`, ...); per-key for `options`. A key is
|
|
125
|
+
* common only when EVERY residual carries it with an identical value.
|
|
126
|
+
*/
|
|
127
|
+
export declare function computeStrictCommon(residuals: TargetConfiguration[]): TargetConfiguration;
|
|
128
|
+
/** `residual` with every property that the strict-common config carries removed. */
|
|
129
|
+
export declare function subtractCommon(residual: TargetConfiguration, common: TargetConfiguration): TargetConfiguration;
|
|
130
|
+
/**
|
|
131
|
+
* Remove the now-dead executor-keyed target default that Phase 2 inlined into
|
|
132
|
+
* every migrated project (mirrors `readTargetDefaultsForExecutor`'s match: the
|
|
133
|
+
* unfiltered entry keyed directly by the executor string).
|
|
134
|
+
*/
|
|
135
|
+
export declare function removeDeadExecutorTargetDefault(nxJson: NxJsonConfiguration, executor: string): void;
|
|
136
|
+
/**
|
|
137
|
+
* Append the hoisted common as a plugin-scoped entry after whatever value the
|
|
138
|
+
* key already holds. Existing entries, the workspace catch-all and any
|
|
139
|
+
* user-authored filtered entries, are never modified, so targets outside this
|
|
140
|
+
* plugin resolve exactly what they resolved before the migration. The entry is
|
|
141
|
+
* appended (never merged into an existing one) so the verification pass can
|
|
142
|
+
* revert precisely this entry and nothing else.
|
|
143
|
+
*/
|
|
144
|
+
export declare function appendPluginScopedTargetDefault(nxJson: NxJsonConfiguration, targetName: string, pluginPath: string, common: TargetConfiguration): TargetDefaultArrayEntry;
|
|
145
|
+
/**
|
|
146
|
+
* Remove a previously appended plugin-scoped entry, collapsing the value back
|
|
147
|
+
* to the plain object form when only a lone unfiltered entry remains. The
|
|
148
|
+
* appended entry survives an `updateNxJson`/`readNxJson` round trip only by
|
|
149
|
+
* value, so the last deep-equal occurrence (append order puts ours last) is
|
|
150
|
+
* the one removed.
|
|
151
|
+
*/
|
|
152
|
+
export declare function removeHoistedTargetDefault(nxJson: NxJsonConfiguration, targetName: string, entry: TargetDefaultArrayEntry): void;
|
|
153
|
+
/**
|
|
154
|
+
* Which `package.json` signal, if any, authors an identity for `targetName` in
|
|
155
|
+
* the DEFAULT plugin layer. The package-json plugin turns every included script into
|
|
156
|
+
* an `nx:run-script` target and honors `nx.targets`; either way the target gains
|
|
157
|
+
* an `executor`/`command` in a default layer, which makes Nx's
|
|
158
|
+
* `resolveSourcePlugin` refuse a `filter: { plugin }` targetDefault for it.
|
|
159
|
+
* The hoist uses this to keep the full residual per project instead of silently
|
|
160
|
+
* dropping the centralized keys; the residual write uses it to keep the
|
|
161
|
+
* pre-migration target instead of letting the package.json identity take the
|
|
162
|
+
* target over.
|
|
163
|
+
*
|
|
164
|
+
* Read through the Tree so this sees the same in-memory package.json the rest of
|
|
165
|
+
* the generator reads and writes, rather than a possibly-stale copy on disk.
|
|
166
|
+
*/
|
|
167
|
+
export declare function packageJsonAuthorsTargetIdentity(tree: Tree, root: string | undefined, targetName: string): PackageJsonIdentitySource | undefined;
|
|
168
|
+
export declare function isRegistrationOfPlugin(registration: string | ExpandedPluginConfiguration, pluginPath: string): boolean;
|
|
169
|
+
/**
|
|
170
|
+
* Whether appending the plugin-scoped `targetDefaults[targetName]` entry would
|
|
171
|
+
* change what the existing target defaults resolve to for any eligible migrated
|
|
172
|
+
* pair. Two hazards, both invisible to the Phase 4 verification (it merges no
|
|
173
|
+
* target defaults):
|
|
174
|
+
*
|
|
175
|
+
* - Key displacement: an exact target-name key takes precedence over a glob
|
|
176
|
+
* key (`build-*`), so appending one can silently stop a glob default from
|
|
177
|
+
* contributing to the migrated targets.
|
|
178
|
+
* - Executor masking: an executor-keyed default for the plugin's INFERRED
|
|
179
|
+
* executor (e.g. `nx:run-commands` for command-based inferred targets) takes
|
|
180
|
+
* precedence over the exact key, so the appended entry would never resolve
|
|
181
|
+
* and its keys would be silently dropped.
|
|
182
|
+
*
|
|
183
|
+
* The check resolves the defaults for each pair without and with the
|
|
184
|
+
* hypothetical entry, through the production reader. The hoist is a pure
|
|
185
|
+
* "residual moved into a default" only when the with-entry resolution equals
|
|
186
|
+
* the without-entry resolution with the common merged on top; anything else
|
|
187
|
+
* changes behavior, so the target keeps its full residuals.
|
|
188
|
+
*/
|
|
189
|
+
export declare function hoistChangesExistingTargetDefaults(targetDefaults: NxJsonConfiguration['targetDefaults'], targetName: string, common: TargetConfiguration, pluginPath: string, eligiblePairs: {
|
|
190
|
+
projectName: string;
|
|
191
|
+
inferredExecutor: string | undefined;
|
|
192
|
+
}[], projectNodesByName: Record<string, ProjectGraphProjectNode>): boolean;
|
|
12
193
|
export declare class NoTargetsToMigrateError extends Error {
|
|
13
194
|
constructor();
|
|
14
195
|
}
|
|
@@ -19,6 +200,7 @@ export declare function migrateProjectExecutorsToPlugin<T>(tree: Tree, projectGr
|
|
|
19
200
|
postTargetTransformer: PostTargetTransformer;
|
|
20
201
|
skipProjectFilter?: SkipProjectFilter;
|
|
21
202
|
skipTargetFilter?: SkipTargetFilter;
|
|
203
|
+
allowSharedOptionOverwrite?: boolean;
|
|
22
204
|
}>, specificProjectToMigrate?: string, logger?: typeof devkitLogger): Promise<Map<string, Record<string, string>>>;
|
|
23
205
|
export declare function migrateProjectExecutorsToPluginV1<T>(tree: Tree, projectGraph: ProjectGraph, pluginPath: string, createNodes: CreateNodes<T>, defaultPluginOptions: T, migrations: Array<{
|
|
24
206
|
executors: string[];
|
|
@@ -26,5 +208,72 @@ export declare function migrateProjectExecutorsToPluginV1<T>(tree: Tree, project
|
|
|
26
208
|
postTargetTransformer: PostTargetTransformer;
|
|
27
209
|
skipProjectFilter?: SkipProjectFilter;
|
|
28
210
|
skipTargetFilter?: SkipTargetFilter;
|
|
211
|
+
allowSharedOptionOverwrite?: boolean;
|
|
29
212
|
}>, specificProjectToMigrate?: string): Promise<Map<string, Record<string, string>>>;
|
|
213
|
+
/**
|
|
214
|
+
* Phase 1: Infer (once per distinct option set). Runs a whole-workspace
|
|
215
|
+
* inference per distinct plugin-option set (usually one) instead of once per
|
|
216
|
+
* target and once per project. Builds `inferredTargetsByOptionSet` (option set
|
|
217
|
+
* id -> project root -> target name -> FULL inferred target; residual
|
|
218
|
+
* computation strips `command` / `options.cwd` at the point of use), which
|
|
219
|
+
* Phase 2 (`computeResidualByProject`) reads to compute residuals and
|
|
220
|
+
* `baselineFinal`; plus the matched config files owned by an inferred project
|
|
221
|
+
* root, which Phase 3's registration step reads for analytic include coverage.
|
|
222
|
+
*/
|
|
223
|
+
export declare function inferOncePerOptionSet<T>(tree: Tree, pluginPath: string, createNodes: CreateNodes<T> | undefined, createNodesV2: CreateNodes<T> | undefined, nxJson: NxJsonConfiguration, scope: MigrationScope<T>): Promise<{
|
|
224
|
+
inferredTargetsByOptionSet: InferredTargetsByOptionSet;
|
|
225
|
+
matchedConfigFiles: string[];
|
|
226
|
+
erroredConfigFiles: string[];
|
|
227
|
+
rawMatchedConfigFiles: string[];
|
|
228
|
+
inferredExecutors: Set<string>;
|
|
229
|
+
inferredRoots: Set<string>;
|
|
230
|
+
}>;
|
|
231
|
+
/**
|
|
232
|
+
* The set of project roots a generated `include` list scopes to, or `undefined`
|
|
233
|
+
* if any entry is not one of the two shapes this generator emits: `*` (the root
|
|
234
|
+
* project) or a literal root followed by a trailing globstar segment (a nested
|
|
235
|
+
* root). A user-authored include or any `exclude` falls back to the glob engine.
|
|
236
|
+
*
|
|
237
|
+
* The globstar branch only qualifies when the root prefix is a LITERAL path.
|
|
238
|
+
* A prefix carrying glob metacharacters (a wildcard, brace, or extglob segment
|
|
239
|
+
* before the trailing globstar) is not a shape this generator emits and can't be
|
|
240
|
+
* reduced to root ownership by string equality, so it falls back to the glob
|
|
241
|
+
* engine.
|
|
242
|
+
*/
|
|
243
|
+
export declare function generatedIncludeRoots(include: string[]): Set<string> | undefined;
|
|
244
|
+
/** One harvested verification error, attributable to a plugin registration. */
|
|
245
|
+
export interface HarvestedConfigurationError {
|
|
246
|
+
message: string;
|
|
247
|
+
/** The config files the error names (empty for a file-less error). */
|
|
248
|
+
files: string[];
|
|
249
|
+
/**
|
|
250
|
+
* The `nx.json` `plugins` index of the registration that produced the error,
|
|
251
|
+
* when the plugins were constructed with one (the batch finalize pass does);
|
|
252
|
+
* `undefined` otherwise.
|
|
253
|
+
*/
|
|
254
|
+
pluginIndex: number | undefined;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Harvest the diagnostic messages and the errored config-file paths from a
|
|
258
|
+
* `ProjectConfigurationsError`. `ProjectConfigurationsError.errors` is a closed
|
|
259
|
+
* 5-member union; the two members that name a failing config file are
|
|
260
|
+
* `AggregateCreateNodesError` (a `[file, error]` list) and `MergeNodesError` (a
|
|
261
|
+
* single `.file`). `ProjectsWithNoNameError` / `MultipleProjectsWithSameNameError`
|
|
262
|
+
* are artifacts of running with no `project.json` layer (nothing supplies names),
|
|
263
|
+
* so they are dropped; `WorkspaceValidityError` carries no file and is exempt.
|
|
264
|
+
*/
|
|
265
|
+
export declare function harvestConfigurationErrors(e: ProjectConfigurationsError): {
|
|
266
|
+
messages: string[];
|
|
267
|
+
erroredConfigFiles: string[];
|
|
268
|
+
entries: HarvestedConfigurationError[];
|
|
269
|
+
};
|
|
270
|
+
/**
|
|
271
|
+
* Attribution map for errored-config ownership: every root the engine knows
|
|
272
|
+
* owns a project, i.e. graph project roots plus the roots the plugin inference
|
|
273
|
+
* produced (a project discovered from a config file alone has no graph node;
|
|
274
|
+
* graph roots win a collision). Keys are normalized for the
|
|
275
|
+
* `findProjectForPath` walk; values are the raw roots the per-target
|
|
276
|
+
* migrated-root sets hold.
|
|
277
|
+
*/
|
|
278
|
+
export declare function buildOwnerRootByPath(graphRoots: Iterable<string>, inferredRoots: Iterable<string>): Map<string, string>;
|
|
30
279
|
export {};
|