@nx/devkit 23.2.0-beta.10 → 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.
@@ -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 {};