@nx/devkit 23.1.0-pr.36127.e594f53 → 23.1.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@ export { migrateProjectExecutorsToPlugin, migrateProjectExecutorsToPluginV1, NoT
7
7
  export { processTargetOutputs, deleteMatchingProperties, toProjectRelativePath, } from './src/generators/plugin-migrations/plugin-migration-utils';
8
8
  export { determineProjectNameAndRootOptions, ensureRootProjectName, resolveImportPath, } from './src/generators/project-name-and-root-utils';
9
9
  export { promptWhenInteractive } from './src/generators/prompt';
10
- export { addBuildTargetDefaults, addE2eCiTargetDefaults, } from './src/generators/target-defaults-utils';
10
+ export { addBuildTargetDefaults, addE2eCiTargetDefaults, findTargetDefault, readTargetDefaultsForTarget, updateTargetDefault, upsertTargetDefault, } from './src/generators/target-defaults-utils';
11
11
  export { addPlugin } from './src/utils/add-plugin';
12
12
  export { getDeclaredPackageVersion, getInstalledPackageVersion, } from './src/utils/installed-version';
13
13
  export { assertSupportedInstalledPackageVersion, assertSupportedPackageVersion, } from './src/utils/version-floor';
package/dist/internal.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.dasherize = exports.classify = exports.capitalize = exports.camelize = exports.checkAndCleanWithSemver = exports.eachValueFrom = exports.logShowProjectCommand = exports.getNamedInputs = exports.findPluginForConfigFile = exports.clearRequireCache = exports.loadConfigFile = exports.getCatalogManager = exports.calculateHashesForCreateNodes = exports.calculateHashForCreateNodes = exports.mapAsyncIterable = exports.combineAsyncIterables = exports.createAsyncIterable = exports.assertSupportedPackageVersion = exports.assertSupportedInstalledPackageVersion = exports.getInstalledPackageVersion = exports.getDeclaredPackageVersion = exports.addPlugin = exports.addE2eCiTargetDefaults = exports.addBuildTargetDefaults = exports.promptWhenInteractive = exports.resolveImportPath = exports.ensureRootProjectName = exports.determineProjectNameAndRootOptions = exports.toProjectRelativePath = exports.deleteMatchingProperties = exports.processTargetOutputs = exports.NoTargetsToMigrateError = exports.migrateProjectExecutorsToPluginV1 = exports.migrateProjectExecutorsToPlugin = exports.AggregatedLog = exports.forEachExecutorOptions = exports.getE2EWebServerInfo = exports.getRelativeCwd = exports.determineArtifactNameAndDirectoryOptions = exports.PromptResolutionError = exports.resolvePrompt = exports.SchemaResolutionError = exports.ImplementationResolutionError = exports.resolveSchema = exports.resolveImplementation = exports.emitPluginWorkerLog = exports.safeWriteFileCache = exports.PluginCache = exports.createProjectRootMappingsFromProjectConfigurations = exports.signalToCode = void 0;
3
+ exports.checkAndCleanWithSemver = exports.eachValueFrom = exports.logShowProjectCommand = exports.getNamedInputs = exports.findPluginForConfigFile = exports.clearRequireCache = exports.loadConfigFile = exports.getCatalogManager = exports.calculateHashesForCreateNodes = exports.calculateHashForCreateNodes = exports.mapAsyncIterable = exports.combineAsyncIterables = exports.createAsyncIterable = exports.assertSupportedPackageVersion = exports.assertSupportedInstalledPackageVersion = exports.getInstalledPackageVersion = exports.getDeclaredPackageVersion = exports.addPlugin = exports.upsertTargetDefault = exports.updateTargetDefault = exports.readTargetDefaultsForTarget = exports.findTargetDefault = exports.addE2eCiTargetDefaults = exports.addBuildTargetDefaults = exports.promptWhenInteractive = exports.resolveImportPath = exports.ensureRootProjectName = exports.determineProjectNameAndRootOptions = exports.toProjectRelativePath = exports.deleteMatchingProperties = exports.processTargetOutputs = exports.NoTargetsToMigrateError = exports.migrateProjectExecutorsToPluginV1 = exports.migrateProjectExecutorsToPlugin = exports.AggregatedLog = exports.forEachExecutorOptions = exports.getE2EWebServerInfo = exports.getRelativeCwd = exports.determineArtifactNameAndDirectoryOptions = exports.PromptResolutionError = exports.resolvePrompt = exports.SchemaResolutionError = exports.ImplementationResolutionError = exports.resolveSchema = exports.resolveImplementation = exports.emitPluginWorkerLog = exports.safeWriteFileCache = exports.PluginCache = exports.createProjectRootMappingsFromProjectConfigurations = exports.signalToCode = void 0;
4
+ exports.dasherize = exports.classify = exports.capitalize = exports.camelize = void 0;
4
5
  var devkit_internals_1 = require("nx/src/devkit-internals");
5
6
  Object.defineProperty(exports, "signalToCode", { enumerable: true, get: function () { return devkit_internals_1.signalToCode; } });
6
7
  Object.defineProperty(exports, "createProjectRootMappingsFromProjectConfigurations", { enumerable: true, get: function () { return devkit_internals_1.createProjectRootMappingsFromProjectConfigurations; } });
@@ -40,6 +41,10 @@ Object.defineProperty(exports, "promptWhenInteractive", { enumerable: true, get:
40
41
  var target_defaults_utils_1 = require("./src/generators/target-defaults-utils");
41
42
  Object.defineProperty(exports, "addBuildTargetDefaults", { enumerable: true, get: function () { return target_defaults_utils_1.addBuildTargetDefaults; } });
42
43
  Object.defineProperty(exports, "addE2eCiTargetDefaults", { enumerable: true, get: function () { return target_defaults_utils_1.addE2eCiTargetDefaults; } });
44
+ Object.defineProperty(exports, "findTargetDefault", { enumerable: true, get: function () { return target_defaults_utils_1.findTargetDefault; } });
45
+ Object.defineProperty(exports, "readTargetDefaultsForTarget", { enumerable: true, get: function () { return target_defaults_utils_1.readTargetDefaultsForTarget; } });
46
+ Object.defineProperty(exports, "updateTargetDefault", { enumerable: true, get: function () { return target_defaults_utils_1.updateTargetDefault; } });
47
+ Object.defineProperty(exports, "upsertTargetDefault", { enumerable: true, get: function () { return target_defaults_utils_1.upsertTargetDefault; } });
43
48
  // Utils
44
49
  var add_plugin_1 = require("./src/utils/add-plugin");
45
50
  Object.defineProperty(exports, "addPlugin", { enumerable: true, get: function () { return add_plugin_1.addPlugin; } });
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getE2EWebServerInfo = getE2EWebServerInfo;
4
4
  const devkit_exports_1 = require("nx/src/devkit-exports");
5
+ const target_defaults_utils_1 = require("./target-defaults-utils");
5
6
  const find_plugin_for_config_file_1 = require("../utils/find-plugin-for-config-file");
6
7
  async function getE2EWebServerInfo(tree, projectName, pluginOptions, defaultValues, isPluginBeingAdded) {
7
8
  const pm = (0, devkit_exports_1.getPackageManagerCommand)((0, devkit_exports_1.detectPackageManager)(tree.root));
@@ -33,23 +34,18 @@ async function getE2EWebServerInfoForPlugin(tree, projectName, pluginOptions, de
33
34
  }
34
35
  const nxJson = (0, devkit_exports_1.readNxJson)(tree);
35
36
  let e2ePort = defaultValues.defaultE2EPort ?? 4200;
36
- if (nxJson.targetDefaults?.[foundPlugin.options[pluginOptions.serveTargetName] ??
37
- defaultValues.defaultServeTargetName] &&
38
- nxJson.targetDefaults?.[foundPlugin.options[pluginOptions.serveTargetName] ??
39
- defaultValues.defaultServeTargetName].options?.port) {
40
- e2ePort =
41
- nxJson.targetDefaults?.[foundPlugin.options[pluginOptions.serveTargetName] ??
42
- defaultValues.defaultServeTargetName].options?.port;
43
- }
37
+ const serveTargetName = foundPlugin.options[pluginOptions.serveTargetName] ??
38
+ defaultValues.defaultServeTargetName;
39
+ e2ePort =
40
+ (0, target_defaults_utils_1.readTargetDefaultsForTarget)(serveTargetName, nxJson.targetDefaults)?.options
41
+ ?.port ?? e2ePort;
44
42
  const e2eWebServerAddress = defaultValues.defaultE2EWebServerAddress.replace(/:\d+/, `:${e2ePort}`);
45
43
  return {
46
44
  e2eWebServerAddress,
47
- e2eWebServerCommand: `${pm.exec} nx run ${projectName}:${foundPlugin.options[pluginOptions.serveTargetName] ??
48
- defaultValues.defaultServeTargetName}`,
45
+ e2eWebServerCommand: `${pm.exec} nx run ${projectName}:${serveTargetName}`,
49
46
  e2eCiWebServerCommand: `${pm.exec} nx run ${projectName}:${foundPlugin.options[pluginOptions.serveStaticTargetName] ??
50
47
  defaultValues.defaultServeStaticTargetName}`,
51
48
  e2eCiBaseUrl: defaultValues.defaultE2ECiBaseUrl,
52
- e2eDevServerTarget: `${projectName}:${foundPlugin.options[pluginOptions.serveTargetName] ??
53
- defaultValues.defaultServeTargetName}`,
49
+ e2eDevServerTarget: `${projectName}:${serveTargetName}`,
54
50
  };
55
51
  }
@@ -5,6 +5,18 @@ const tslib_1 = require("tslib");
5
5
  const devkit_exports_1 = require("nx/src/devkit-exports");
6
6
  const devkit_internals_1 = require("nx/src/devkit-internals");
7
7
  const path = tslib_1.__importStar(require("path"));
8
+ // Prettier v3 (ESM) exposes its API as named exports; v2 (CJS) exposes it under
9
+ // `.default` when loaded via `import()`. Return whichever carries the API, or
10
+ // null if prettier isn't installed.
11
+ async function importPrettier() {
12
+ try {
13
+ const imported = await import('prettier');
14
+ return (imported.resolveConfig ? imported : imported.default);
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ }
8
20
  /**
9
21
  * Formats all the created or updated files using Prettier
10
22
  * @param tree - the file system tree
@@ -32,7 +44,7 @@ async function formatFiles(tree, options = {}) {
32
44
  }
33
45
  let prettier;
34
46
  try {
35
- prettier = await import('prettier');
47
+ prettier = await importPrettier();
36
48
  /**
37
49
  * Even after we discovered prettier in node_modules, we need to be sure that the user is intentionally using prettier
38
50
  * before proceeding to format with it.
@@ -1,5 +1,5 @@
1
1
  import type { ProjectConfiguration } from 'nx/src/config/workspace-json-project-json';
2
- import { type CreateNodes, type ProjectGraph, type TargetConfiguration, type Tree } from 'nx/src/devkit-exports';
2
+ import { type CreateNodes, type NxJsonConfiguration, type ProjectGraph, type TargetConfiguration, type Tree } from 'nx/src/devkit-exports';
3
3
  import { logger as devkitLogger } from 'nx/src/devkit-exports';
4
4
  export type InferredTargetConfiguration = TargetConfiguration & {
5
5
  name: string;
@@ -13,6 +13,7 @@ type SkipProjectFilter = (projectConfiguration: ProjectConfiguration) => false |
13
13
  export declare class NoTargetsToMigrateError extends Error {
14
14
  constructor();
15
15
  }
16
+ export declare function readTargetDefaultsForExecutor(executor: string, targetDefaults: NxJsonConfiguration['targetDefaults'] | undefined): Partial<TargetConfiguration> | undefined;
16
17
  export declare function migrateProjectExecutorsToPlugin<T>(tree: Tree, projectGraph: ProjectGraph, pluginPath: string, createNodesV2: CreateNodes<T>, defaultPluginOptions: T, migrations: Array<{
17
18
  executors: string[];
18
19
  targetPluginOptionMapper: (targetName: string) => Partial<T>;
@@ -2,6 +2,7 @@
2
2
  var _ExecutorToPluginMigrator_instances, _ExecutorToPluginMigrator_projectGraph, _ExecutorToPluginMigrator_executor, _ExecutorToPluginMigrator_pluginPath, _ExecutorToPluginMigrator_pluginOptionsBuilder, _ExecutorToPluginMigrator_postTargetTransformer, _ExecutorToPluginMigrator_skipTargetFilter, _ExecutorToPluginMigrator_skipProjectFilter, _ExecutorToPluginMigrator_specificProjectToMigrate, _ExecutorToPluginMigrator_logger, _ExecutorToPluginMigrator_nxJson, _ExecutorToPluginMigrator_targetDefaultsForExecutor, _ExecutorToPluginMigrator_targetAndProjectsToMigrate, _ExecutorToPluginMigrator_createNodes, _ExecutorToPluginMigrator_createNodesV2, _ExecutorToPluginMigrator_createNodesResultsForTargets, _ExecutorToPluginMigrator_skippedProjects, _ExecutorToPluginMigrator_init, _ExecutorToPluginMigrator_migrateTarget, _ExecutorToPluginMigrator_migrateProject, _ExecutorToPluginMigrator_mergeInputs, _ExecutorToPluginMigrator_getTargetAndProjectsToMigrate, _ExecutorToPluginMigrator_getTargetDefaultsForExecutor, _ExecutorToPluginMigrator_getCreatedTargetForProjectRoot, _ExecutorToPluginMigrator_getCreateNodesResults;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.NoTargetsToMigrateError = void 0;
5
+ exports.readTargetDefaultsForExecutor = readTargetDefaultsForExecutor;
5
6
  exports.migrateProjectExecutorsToPlugin = migrateProjectExecutorsToPlugin;
6
7
  exports.migrateProjectExecutorsToPluginV1 = migrateProjectExecutorsToPluginV1;
7
8
  const tslib_1 = require("tslib");
@@ -11,6 +12,7 @@ const posix_1 = require("node:path/posix");
11
12
  const devkit_exports_1 = require("nx/src/devkit-exports");
12
13
  const devkit_internals_1 = require("nx/src/devkit-internals");
13
14
  const executor_options_utils_1 = require("../executor-options-utils");
15
+ const target_defaults_utils_1 = require("../target-defaults-utils");
14
16
  const plugin_migration_utils_1 = require("./plugin-migration-utils");
15
17
  const devkit_exports_2 = require("nx/src/devkit-exports");
16
18
  class ExecutorToPluginMigrator {
@@ -169,7 +171,7 @@ _ExecutorToPluginMigrator_projectGraph = new WeakMap(), _ExecutorToPluginMigrato
169
171
  }
170
172
  });
171
173
  }, _ExecutorToPluginMigrator_getTargetDefaultsForExecutor = function _ExecutorToPluginMigrator_getTargetDefaultsForExecutor() {
172
- tslib_1.__classPrivateFieldSet(this, _ExecutorToPluginMigrator_targetDefaultsForExecutor, structuredClone(tslib_1.__classPrivateFieldGet(this, _ExecutorToPluginMigrator_nxJson, "f").targetDefaults?.[tslib_1.__classPrivateFieldGet(this, _ExecutorToPluginMigrator_executor, "f")]), "f");
174
+ tslib_1.__classPrivateFieldSet(this, _ExecutorToPluginMigrator_targetDefaultsForExecutor, structuredClone(readTargetDefaultsForExecutor(tslib_1.__classPrivateFieldGet(this, _ExecutorToPluginMigrator_executor, "f"), tslib_1.__classPrivateFieldGet(this, _ExecutorToPluginMigrator_nxJson, "f").targetDefaults) ?? {}), "f");
173
175
  }, _ExecutorToPluginMigrator_getCreatedTargetForProjectRoot = function _ExecutorToPluginMigrator_getCreatedTargetForProjectRoot(targetName, projectRoot) {
174
176
  const entry = Object.entries(tslib_1.__classPrivateFieldGet(this, _ExecutorToPluginMigrator_createNodesResultsForTargets, "f").get(targetName)?.projects ?? {}).find(([root]) => root === projectRoot);
175
177
  if (!entry) {
@@ -204,6 +206,22 @@ class NoTargetsToMigrateError extends Error {
204
206
  }
205
207
  }
206
208
  exports.NoTargetsToMigrateError = NoTargetsToMigrateError;
209
+ function readTargetDefaultsForExecutor(executor, targetDefaults) {
210
+ // Preserve the legacy record-shape semantics this migrator used before
211
+ // array support: only an unfiltered default keyed directly by executor
212
+ // applies here. Target-scoped or filtered array entries remain opt-in
213
+ // behaviors for callers that can evaluate them in project context.
214
+ const entry = (0, target_defaults_utils_1.findTargetDefault)(targetDefaults, { executor });
215
+ if (!entry) {
216
+ return undefined;
217
+ }
218
+ const config = { ...entry };
219
+ delete config.target;
220
+ delete config.executor;
221
+ delete config.projects;
222
+ delete config.plugin;
223
+ return config;
224
+ }
207
225
  async function migrateProjectExecutorsToPlugin(tree, projectGraph, pluginPath, createNodesV2, defaultPluginOptions, migrations, specificProjectToMigrate, logger) {
208
226
  const projects = await migrateProjects(tree, projectGraph, pluginPath, undefined, createNodesV2, defaultPluginOptions, migrations, specificProjectToMigrate, logger);
209
227
  return projects;
@@ -1,3 +1,120 @@
1
- import { type TargetConfiguration, type Tree } from 'nx/src/devkit-exports';
1
+ import { type NxJsonConfiguration, type TargetConfiguration, type TargetDefaultEntry, type TargetDefaultFilter, type TargetDefaults, type Tree } from 'nx/src/devkit-exports';
2
+ import { readTargetDefaultsForTarget as readTargetDefaultsForTargetFromNx } from 'nx/src/devkit-internals';
3
+ /**
4
+ * Upsert a `targetDefaults` entry on the provided `nxJson`. Mutates
5
+ * `nxJson` in place and returns it so the caller can chain or batch
6
+ * other edits before persisting via `updateNxJson` exactly once.
7
+ *
8
+ * The input is the logical {@link TargetDefaultEntry} shape
9
+ * (`{ target?, executor?, projects?, plugin?, ...config }`). The `projects` /
10
+ * `plugin` filter is *context* describing what the caller is configuring, used
11
+ * to pick which existing entry to merge into — it is never authored as a new
12
+ * filtered entry. For the key (`target` ?? `executor`):
13
+ *
14
+ * - No value yet → create the generic (plain object) value.
15
+ * - Only a generic value → merge into it.
16
+ * - A filtered entry matching the context filter exists → merge into that one
17
+ * (so a caller updating a project the user already scoped a default for edits
18
+ * that scoped entry rather than clobbering the workspace baseline).
19
+ * - Otherwise → merge into the generic, creating one if needed.
20
+ *
21
+ * A lone unfiltered entry is always stored as a plain object, never a
22
+ * single-element array. The entry must set at least one of `target` /
23
+ * `executor`.
24
+ */
25
+ export declare function upsertTargetDefault(tree: Tree, nxJson: NxJsonConfiguration, options: TargetDefaultEntry): NxJsonConfiguration;
26
+ /**
27
+ * Find a `targetDefaults` entry by its logical locator tuple
28
+ * `(target, executor, projects, plugin)`. Locator keys default to
29
+ * `undefined`, matching only entries that also leave them unset — same
30
+ * semantics as `upsertTargetDefault`.
31
+ *
32
+ * Throws when called with an empty locator (no `target`, `executor`,
33
+ * `projects`, or `plugin`). An empty locator is almost always a bug — the
34
+ * caller intended to find a specific entry but forgot to populate the lookup.
35
+ */
36
+ export declare function findTargetDefault(targetDefaults: TargetDefaults | undefined, locator: Pick<TargetDefaultEntry, 'target' | 'executor' | 'projects' | 'plugin'>): TargetDefaultEntry | undefined;
37
+ /**
38
+ * The subset of a project graph node that the `projects` narrowing reads —
39
+ * names come from the map keys, `root`/`tags` drive `findMatchingProjects`.
40
+ * Mirrors `MatcherProjectNode` without importing it, so the signature stays
41
+ * stable across the supported nx version range.
42
+ */
43
+ type ContextProjectNode = {
44
+ data: {
45
+ root: string;
46
+ tags?: string[];
47
+ };
48
+ };
49
+ /**
50
+ * What the caller is updating, used to pick which `targetDefaults` entries the
51
+ * callback runs against. At least one of `executor` / `projects` is required.
52
+ */
53
+ export interface UpdateTargetDefaultContext {
54
+ /**
55
+ * Visit only entries that resolve to this executor — matched against the map
56
+ * key (executor-keyed form), an entry's `executor` field, or its
57
+ * `filter.executor`. When omitted, entries are not filtered by executor.
58
+ */
59
+ executor?: string;
60
+ /**
61
+ * Project nodes the update is scoped to, keyed by project name. When set, a
62
+ * project-filtered entry is visited only if its `filter.projects` covers
63
+ * *every* one of these projects; unfiltered entries always match. When
64
+ * omitted, project filters are not a constraint and every (executor-matching)
65
+ * entry is visited — the workspace-wide case.
66
+ */
67
+ projects?: Record<string, ContextProjectNode>;
68
+ }
69
+ /**
70
+ * Describes the entry a callback invocation is operating on, so the callback
71
+ * can tell the executor-keyed form from the target-keyed form and read the
72
+ * entry's filter.
73
+ */
74
+ export interface UpdateTargetDefaultEntryInfo {
75
+ /** The `targetDefaults` map key the entry lives under. */
76
+ key: string;
77
+ /** Logical target name — `undefined` for executor-keyed entries. */
78
+ target?: string;
79
+ /** Executor the entry resolves to (from the key, `executor` field, or filter). */
80
+ executor?: string;
81
+ /** The entry's filter, present only for filtered array entries. */
82
+ filter?: TargetDefaultFilter;
83
+ }
84
+ /**
85
+ * Called once per matching entry. Receives the entry's flat config (everything
86
+ * but `filter`) and may mutate it in place. Return a new config to replace the
87
+ * entry's payload, return `null` to drop the entry, or return nothing to keep
88
+ * the (possibly mutated) config.
89
+ */
90
+ export type UpdateTargetDefaultCallback = (config: Partial<TargetConfiguration>, info: UpdateTargetDefaultEntryInfo) => Partial<TargetConfiguration> | null | void;
91
+ /**
92
+ * Walk the `targetDefaults` entries matching `context` and run `callback`
93
+ * against each, mutating `nxJson` in place and returning it. This is the
94
+ * read-modify-write counterpart to {@link upsertTargetDefault}, meant for
95
+ * generators and migrations that need to edit *existing* defaults across both
96
+ * value forms (plain object and filtered array) without re-implementing the
97
+ * normalize → edit → collapse dance by hand.
98
+ *
99
+ * Matching, per the `context`:
100
+ * - `executor` selects entries that reference it as the map key, an `executor`
101
+ * config field, or `filter.executor`.
102
+ * - `projects` narrows to entries whose `filter.projects` covers the scoped
103
+ * projects (unfiltered entries always match). With no `projects` context,
104
+ * project filters are ignored and every executor-matching entry is visited.
105
+ *
106
+ * Removal and collapsing follow the storage-form rules:
107
+ * - Array entry whose callback returns `null` → dropped from the array.
108
+ * - An array that collapses to a single unfiltered entry → stored as the plain
109
+ * object form; an emptied array → the whole key is deleted.
110
+ * - Object-form value whose callback returns `null` → the whole key is deleted.
111
+ * - An emptied `targetDefaults` map → removed from `nxJson` entirely.
112
+ *
113
+ * Throws when neither `executor` nor `projects` is provided — an empty context
114
+ * would match everything and is almost always a bug.
115
+ */
116
+ export declare function updateTargetDefault(nxJson: NxJsonConfiguration, context: UpdateTargetDefaultContext, callback: UpdateTargetDefaultCallback): NxJsonConfiguration;
117
+ export declare function readTargetDefaultsForTarget(targetName: string, targetDefaults: TargetDefaults | undefined, executor?: string, opts?: Parameters<typeof readTargetDefaultsForTargetFromNx>[3]): Partial<TargetConfiguration> | null;
2
118
  export declare function addBuildTargetDefaults(tree: Tree, executorName: string, buildTargetName?: string, extraInputs?: TargetConfiguration['inputs']): void;
3
119
  export declare function addE2eCiTargetDefaults(tree: Tree, e2ePlugin: string, buildTarget: string, pathToE2EConfigFile: string): Promise<void>;
120
+ export {};
@@ -1,14 +1,362 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.upsertTargetDefault = upsertTargetDefault;
4
+ exports.findTargetDefault = findTargetDefault;
5
+ exports.updateTargetDefault = updateTargetDefault;
6
+ exports.readTargetDefaultsForTarget = readTargetDefaultsForTarget;
3
7
  exports.addBuildTargetDefaults = addBuildTargetDefaults;
4
8
  exports.addE2eCiTargetDefaults = addE2eCiTargetDefaults;
5
9
  const devkit_exports_1 = require("nx/src/devkit-exports");
6
10
  const devkit_internals_1 = require("nx/src/devkit-internals");
7
11
  const minimatch_1 = require("minimatch");
12
+ /**
13
+ * Upsert a `targetDefaults` entry on the provided `nxJson`. Mutates
14
+ * `nxJson` in place and returns it so the caller can chain or batch
15
+ * other edits before persisting via `updateNxJson` exactly once.
16
+ *
17
+ * The input is the logical {@link TargetDefaultEntry} shape
18
+ * (`{ target?, executor?, projects?, plugin?, ...config }`). The `projects` /
19
+ * `plugin` filter is *context* describing what the caller is configuring, used
20
+ * to pick which existing entry to merge into — it is never authored as a new
21
+ * filtered entry. For the key (`target` ?? `executor`):
22
+ *
23
+ * - No value yet → create the generic (plain object) value.
24
+ * - Only a generic value → merge into it.
25
+ * - A filtered entry matching the context filter exists → merge into that one
26
+ * (so a caller updating a project the user already scoped a default for edits
27
+ * that scoped entry rather than clobbering the workspace baseline).
28
+ * - Otherwise → merge into the generic, creating one if needed.
29
+ *
30
+ * A lone unfiltered entry is always stored as a plain object, never a
31
+ * single-element array. The entry must set at least one of `target` /
32
+ * `executor`.
33
+ */
34
+ function upsertTargetDefault(tree, nxJson, options) {
35
+ const { target, executor, projects, plugin, ...config } = options;
36
+ const key = target ?? executor;
37
+ if (key === undefined) {
38
+ throw new Error('upsertTargetDefault requires at least one of `target` or `executor` to be set.');
39
+ }
40
+ // `executor` is the map key itself when no `target` is given; when a `target`
41
+ // is present it is a configuration field (a default executor) written onto
42
+ // the value.
43
+ const valueConfig = target !== undefined && executor !== undefined
44
+ ? { executor, ...config }
45
+ : config;
46
+ const targetDefaults = (nxJson.targetDefaults ??= {});
47
+ targetDefaults[key] = collapse(upsertValue(targetDefaults[key], valueConfig, {
48
+ tree,
49
+ projects,
50
+ plugin,
51
+ executor,
52
+ }));
53
+ return nxJson;
54
+ }
55
+ /**
56
+ * Merge `config` into the right entry of a key's value, per the algorithm
57
+ * documented on {@link upsertTargetDefault}. Returns the new value (always an
58
+ * array here; {@link collapse} downgrades a lone unfiltered one to an object).
59
+ */
60
+ function upsertValue(existing, config, context) {
61
+ // No value yet → create the generic.
62
+ if (existing === undefined) {
63
+ return { ...config };
64
+ }
65
+ const entries = Array.isArray(existing)
66
+ ? [...existing]
67
+ : [existing];
68
+ // A filtered entry that covers what's being configured → merge into it,
69
+ // preserving its filter.
70
+ const matchIdx = entries.findIndex((e) => e.filter !== undefined && filterCoversContext(e.filter, context));
71
+ if (matchIdx >= 0) {
72
+ entries[matchIdx] = { ...entries[matchIdx], ...config };
73
+ return entries;
74
+ }
75
+ // Otherwise merge into the generic (catch-all) entry, creating one if absent.
76
+ const genericIdx = entries.findIndex((e) => e.filter === undefined);
77
+ if (genericIdx >= 0) {
78
+ const { filter: _filter, ...rest } = entries[genericIdx];
79
+ entries[genericIdx] = { ...rest, ...config };
80
+ }
81
+ else {
82
+ entries.push({ ...config });
83
+ }
84
+ return entries;
85
+ }
86
+ /**
87
+ * Whether an entry's `filter` applies to what the caller is configuring:
88
+ * `plugin` / `executor` must match exactly, and `projects` is evaluated as
89
+ * *coverage* — every configured project must match the filter's patterns
90
+ * (names, globs, `tag:`, directories, negation) via `findMatchingProjects`,
91
+ * resolved against each project's tags/root in the tree. A `projects` filter
92
+ * can't be confirmed when no project context is supplied (or the project
93
+ * isn't in the tree), so it falls through to the generic.
94
+ */
95
+ function filterCoversContext(filter, context) {
96
+ if (filter.plugin !== undefined && filter.plugin !== context.plugin) {
97
+ return false;
98
+ }
99
+ if (filter.executor !== undefined && filter.executor !== context.executor) {
100
+ return false;
101
+ }
102
+ if (filter.projects !== undefined) {
103
+ const configured = context.projects ?? [];
104
+ const nodes = contextProjectNodes(context.tree, configured);
105
+ if (!projectsFilterCovers(filter.projects, nodes, configured)) {
106
+ return false;
107
+ }
108
+ }
109
+ return true;
110
+ }
111
+ /**
112
+ * Whether a `filter.projects` pattern set covers *every* required project name:
113
+ * each must be returned by `findMatchingProjects` against `nodes`. An empty
114
+ * `requiredNames` can never be "covered", so the entry falls through to the
115
+ * generic baseline. Shared by the upsert and update paths so the coverage
116
+ * semantics live in one place.
117
+ */
118
+ function projectsFilterCovers(patterns, nodes, requiredNames) {
119
+ if (requiredNames.length === 0) {
120
+ return false;
121
+ }
122
+ const matched = (0, devkit_internals_1.findMatchingProjects)([...patterns], nodes);
123
+ return requiredNames.every((name) => matched.includes(name));
124
+ }
125
+ function contextProjectNodes(tree, projectNames) {
126
+ const projects = (0, devkit_exports_1.getProjects)(tree);
127
+ const nodes = {};
128
+ for (const name of projectNames) {
129
+ const config = projects.get(name);
130
+ if (config) {
131
+ nodes[name] = { data: { root: config.root, tags: config.tags } };
132
+ }
133
+ }
134
+ return nodes;
135
+ }
136
+ /**
137
+ * Collapse a key's value back to the plain object form when it has degenerated
138
+ * to a single entry with no filter — at that point the array wrapper carries no
139
+ * information the object form doesn't, and the object form is the tidier,
140
+ * record-shape-compatible representation.
141
+ */
142
+ function collapse(value) {
143
+ if (Array.isArray(value) &&
144
+ value.length === 1 &&
145
+ value[0].filter === undefined) {
146
+ // Drop the (already-undefined) `filter` key so the result matches the
147
+ // object value form, which forbids `filter`.
148
+ const { filter: _filter, ...config } = value[0];
149
+ return config;
150
+ }
151
+ return value;
152
+ }
153
+ /**
154
+ * Find a `targetDefaults` entry by its logical locator tuple
155
+ * `(target, executor, projects, plugin)`. Locator keys default to
156
+ * `undefined`, matching only entries that also leave them unset — same
157
+ * semantics as `upsertTargetDefault`.
158
+ *
159
+ * Throws when called with an empty locator (no `target`, `executor`,
160
+ * `projects`, or `plugin`). An empty locator is almost always a bug — the
161
+ * caller intended to find a specific entry but forgot to populate the lookup.
162
+ */
163
+ function findTargetDefault(targetDefaults, locator) {
164
+ if (locator.target === undefined &&
165
+ locator.executor === undefined &&
166
+ locator.projects === undefined &&
167
+ locator.plugin === undefined) {
168
+ throw new Error('findTargetDefault requires at least one of `target`, `executor`, `projects`, or `plugin` on the locator.');
169
+ }
170
+ for (const entry of logicalTargetDefaultEntries(targetDefaults)) {
171
+ if (entry.target === locator.target &&
172
+ entry.executor === locator.executor &&
173
+ projectsEqual(entry.projects, locator.projects) &&
174
+ entry.plugin === locator.plugin) {
175
+ return entry;
176
+ }
177
+ }
178
+ return undefined;
179
+ }
180
+ /**
181
+ * Expand the nested `targetDefaults` map into its logical {@link
182
+ * TargetDefaultEntry} view, one entry at a time. The key becomes `target` (or
183
+ * `executor` for executor-shaped keys) and any `filter` is un-nested into the
184
+ * flat `projects`/`plugin`/`executor` siblings. Lazy so callers that find a
185
+ * match early stop iterating. Internal to this module — the flat shape is a
186
+ * lookup convenience, never a `targetDefaults` value form.
187
+ */
188
+ function* logicalTargetDefaultEntries(targetDefaults) {
189
+ for (const key of Object.keys(targetDefaults ?? {})) {
190
+ const locator = isExecutorLikeKey(key)
191
+ ? { executor: key }
192
+ : { target: key };
193
+ const value = targetDefaults[key];
194
+ const entries = Array.isArray(value)
195
+ ? value
196
+ : [value ?? {}];
197
+ for (const entry of entries) {
198
+ const { filter, ...config } = entry;
199
+ yield {
200
+ ...locator,
201
+ ...(filter?.projects !== undefined
202
+ ? { projects: filter.projects }
203
+ : {}),
204
+ ...(filter?.plugin !== undefined ? { plugin: filter.plugin } : {}),
205
+ ...(filter?.executor !== undefined
206
+ ? { executor: filter.executor }
207
+ : {}),
208
+ ...config,
209
+ };
210
+ }
211
+ }
212
+ }
213
+ // Mirrors `isGlobPattern` from `nx/src/utils/globs.ts`, which isn't on the
214
+ // devkit-exports surface guaranteed across the supported nx version range.
215
+ //
216
+ // Known limitation: a `:` is also legal in a target name, so a target literally
217
+ // named e.g. `e2e:ci` is classified as an executor key here. This matches how
218
+ // the rest of the target-defaults stack disambiguates keys (executor-shaped
219
+ // keys win), and such target names are vanishingly rare — accepted rather than
220
+ // worked around.
221
+ const GLOB_CHARACTERS = new Set(['*', '|', '{', '}', '(', ')', '[']);
222
+ function isExecutorLikeKey(key) {
223
+ if (!key.includes(':'))
224
+ return false;
225
+ for (const c of key)
226
+ if (GLOB_CHARACTERS.has(c))
227
+ return false;
228
+ return true;
229
+ }
230
+ /**
231
+ * Walk the `targetDefaults` entries matching `context` and run `callback`
232
+ * against each, mutating `nxJson` in place and returning it. This is the
233
+ * read-modify-write counterpart to {@link upsertTargetDefault}, meant for
234
+ * generators and migrations that need to edit *existing* defaults across both
235
+ * value forms (plain object and filtered array) without re-implementing the
236
+ * normalize → edit → collapse dance by hand.
237
+ *
238
+ * Matching, per the `context`:
239
+ * - `executor` selects entries that reference it as the map key, an `executor`
240
+ * config field, or `filter.executor`.
241
+ * - `projects` narrows to entries whose `filter.projects` covers the scoped
242
+ * projects (unfiltered entries always match). With no `projects` context,
243
+ * project filters are ignored and every executor-matching entry is visited.
244
+ *
245
+ * Removal and collapsing follow the storage-form rules:
246
+ * - Array entry whose callback returns `null` → dropped from the array.
247
+ * - An array that collapses to a single unfiltered entry → stored as the plain
248
+ * object form; an emptied array → the whole key is deleted.
249
+ * - Object-form value whose callback returns `null` → the whole key is deleted.
250
+ * - An emptied `targetDefaults` map → removed from `nxJson` entirely.
251
+ *
252
+ * Throws when neither `executor` nor `projects` is provided — an empty context
253
+ * would match everything and is almost always a bug.
254
+ */
255
+ function updateTargetDefault(nxJson, context, callback) {
256
+ const { executor, projects } = context;
257
+ if (executor === undefined && projects === undefined) {
258
+ throw new Error('updateTargetDefault requires at least one of `executor` or `projects` on the context to locate entries to update.');
259
+ }
260
+ const targetDefaults = nxJson.targetDefaults;
261
+ if (!targetDefaults) {
262
+ return nxJson;
263
+ }
264
+ const projectNames = projects ? Object.keys(projects) : [];
265
+ for (const key of Object.keys(targetDefaults)) {
266
+ const value = targetDefaults[key];
267
+ const entries = Array.isArray(value)
268
+ ? value
269
+ : [value];
270
+ const kept = [];
271
+ for (const entry of entries) {
272
+ if (!entryMatchesContext(key, entry, executor, projects, projectNames)) {
273
+ kept.push(entry);
274
+ continue;
275
+ }
276
+ const { filter, ...config } = entry;
277
+ const result = callback(config, {
278
+ key,
279
+ target: isExecutorLikeKey(key) ? undefined : key,
280
+ executor: isExecutorLikeKey(key)
281
+ ? key
282
+ : (config.executor ?? filter?.executor),
283
+ filter,
284
+ });
285
+ // `null` drops the entry; anything else keeps the (mutated or replaced)
286
+ // config, re-attaching the filter so a filtered entry stays filtered.
287
+ if (result === null) {
288
+ continue;
289
+ }
290
+ const nextConfig = result ?? config;
291
+ kept.push(filter !== undefined ? { filter, ...nextConfig } : nextConfig);
292
+ }
293
+ if (kept.length === 0) {
294
+ delete targetDefaults[key];
295
+ }
296
+ else {
297
+ targetDefaults[key] = collapse(kept);
298
+ }
299
+ }
300
+ if (Object.keys(targetDefaults).length === 0) {
301
+ delete nxJson.targetDefaults;
302
+ }
303
+ return nxJson;
304
+ }
305
+ /**
306
+ * Whether `callback` should run against this entry: it must reference the
307
+ * context's `executor` (when given), and — when the context is scoped to
308
+ * `projects` — any `filter.projects` it carries must cover every scoped
309
+ * project. Unfiltered entries always pass the project check.
310
+ */
311
+ function entryMatchesContext(key, entry, executor, projects, projectNames) {
312
+ if (executor !== undefined) {
313
+ const referencesExecutor = key === executor ||
314
+ entry.executor === executor ||
315
+ entry.filter?.executor === executor;
316
+ if (!referencesExecutor) {
317
+ return false;
318
+ }
319
+ }
320
+ if (projects !== undefined && entry.filter?.projects !== undefined) {
321
+ const patterns = Array.isArray(entry.filter.projects)
322
+ ? entry.filter.projects
323
+ : [entry.filter.projects];
324
+ if (!projectsFilterCovers(patterns, projects, projectNames)) {
325
+ return false;
326
+ }
327
+ }
328
+ return true;
329
+ }
330
+ function readTargetDefaultsForTarget(targetName, targetDefaults, executor, opts) {
331
+ // `targetDefaults` is the nested map; the nx reader resolves either the
332
+ // object or array value form for `targetName` natively.
333
+ return (0, devkit_internals_1.readTargetDefaultsForTarget)(targetName, targetDefaults, executor, opts);
334
+ }
335
+ // Order-insensitive equality so re-upserts with reordered patterns merge into
336
+ // the same entry rather than appending a duplicate. Comparing sorted copies
337
+ // keeps duplicates significant (`['a','a']` ≠ `['a']`) without special-casing.
338
+ function projectsEqual(a, b) {
339
+ if (a === b)
340
+ return true;
341
+ if (!a || !b)
342
+ return false;
343
+ if (a.length !== b.length)
344
+ return false;
345
+ const sortedA = [...a].sort();
346
+ const sortedB = [...b].sort();
347
+ return sortedA.every((value, i) => value === sortedB[i]);
348
+ }
8
349
  function addBuildTargetDefaults(tree, executorName, buildTargetName = 'build', extraInputs = []) {
9
- const nxJson = (0, devkit_exports_1.readNxJson)(tree);
10
- nxJson.targetDefaults ??= {};
11
- nxJson.targetDefaults[executorName] ??= {
350
+ const nxJson = (0, devkit_exports_1.readNxJson)(tree) ?? {};
351
+ // Only skip when an *unfiltered* entry already covers this executor.
352
+ // A filtered entry (`projects:` or `plugin:`) only applies to a subset
353
+ // of targets, so it doesn't satisfy the workspace-wide default this
354
+ // helper writes — we still need to add the unfiltered baseline.
355
+ if (findTargetDefault(nxJson.targetDefaults, { executor: executorName })) {
356
+ return;
357
+ }
358
+ upsertTargetDefault(tree, nxJson, {
359
+ executor: executorName,
12
360
  cache: true,
13
361
  dependsOn: [`^${buildTargetName}`],
14
362
  inputs: [
@@ -17,12 +365,12 @@ function addBuildTargetDefaults(tree, executorName, buildTargetName = 'build', e
17
365
  : ['default', '^default']),
18
366
  ...extraInputs,
19
367
  ],
20
- };
368
+ });
21
369
  (0, devkit_exports_1.updateNxJson)(tree, nxJson);
22
370
  }
23
371
  async function addE2eCiTargetDefaults(tree, e2ePlugin, buildTarget, pathToE2EConfigFile) {
24
372
  const nxJson = (0, devkit_exports_1.readNxJson)(tree);
25
- if (!nxJson.plugins) {
373
+ if (!nxJson?.plugins) {
26
374
  return;
27
375
  }
28
376
  const e2ePluginRegistrations = nxJson.plugins.filter((p) => typeof p === 'string' ? p === e2ePlugin : p.plugin === e2ePlugin);
@@ -58,18 +406,13 @@ async function addE2eCiTargetDefaults(tree, e2ePlugin, buildTarget, pathToE2ECon
58
406
  ? 'e2e-ci'
59
407
  : (foundPluginForApplication.options?.ciTargetName ?? 'e2e-ci');
60
408
  const ciTargetNameGlob = `${ciTargetName}--**/**`;
61
- nxJson.targetDefaults ??= {};
62
- const e2eCiTargetDefaults = nxJson.targetDefaults[ciTargetNameGlob];
63
- if (!e2eCiTargetDefaults) {
64
- nxJson.targetDefaults[ciTargetNameGlob] = {
65
- dependsOn: [buildTarget],
66
- };
67
- }
68
- else {
69
- e2eCiTargetDefaults.dependsOn ??= [];
70
- if (!e2eCiTargetDefaults.dependsOn.includes(buildTarget)) {
71
- e2eCiTargetDefaults.dependsOn.push(buildTarget);
72
- }
409
+ const existing = findTargetDefault(nxJson.targetDefaults, {
410
+ target: ciTargetNameGlob,
411
+ });
412
+ const dependsOn = [...(existing?.dependsOn ?? [])];
413
+ if (!dependsOn.includes(buildTarget)) {
414
+ dependsOn.push(buildTarget);
73
415
  }
416
+ upsertTargetDefault(tree, nxJson, { target: ciTargetNameGlob, dependsOn });
74
417
  (0, devkit_exports_1.updateNxJson)(tree, nxJson);
75
418
  }
@@ -0,0 +1,9 @@
1
+ import { type Tree } from 'nx/src/devkit-exports';
2
+ import type { CatalogDefinitions } from './types';
3
+ export declare function readCatalogConfigFromFs(filename: string, fullPath: string): CatalogDefinitions | null;
4
+ export declare function readCatalogConfigFromTree(filename: string, tree: Tree): CatalogDefinitions | null;
5
+ export declare function updateCatalogVersionsInFile(filename: string, treeOrRoot: Tree | string, updates: Array<{
6
+ packageName: string;
7
+ version: string;
8
+ catalogName?: string;
9
+ }>): void;
@@ -0,0 +1,215 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readCatalogConfigFromFs = readCatalogConfigFromFs;
4
+ exports.readCatalogConfigFromTree = readCatalogConfigFromTree;
5
+ exports.updateCatalogVersionsInFile = updateCatalogVersionsInFile;
6
+ // Keep in sync with packages/nx/src/utils/catalog/manager-utils.ts; the body below
7
+ // the imports is duplicated because @nx/devkit supports a range of nx majors and
8
+ // this logic isn't part of the nx surface it can import across that range.
9
+ const js_yaml_1 = require("@zkochan/js-yaml");
10
+ const node_fs_1 = require("node:fs");
11
+ const node_path_1 = require("node:path");
12
+ const devkit_exports_1 = require("nx/src/devkit-exports");
13
+ const devkit_internals_1 = require("nx/src/devkit-internals");
14
+ const yaml_1 = require("yaml");
15
+ // Walks `path` resolving aliases at every step so structural checks can
16
+ // see through `key: *anchor` references. Neither `doc.getIn` nor
17
+ // `doc.hasIn` traverse aliases on their own. Returns the resolved node, or
18
+ // `undefined` if any ancestor isn't a map.
19
+ function resolveAt(doc, path) {
20
+ let node = doc.contents;
21
+ for (let i = 0; i < path.length; i++) {
22
+ if ((0, yaml_1.isAlias)(node))
23
+ node = node.resolve(doc);
24
+ if (!(0, yaml_1.isMap)(node))
25
+ return undefined;
26
+ node = node.get(path[i], true);
27
+ }
28
+ if ((0, yaml_1.isAlias)(node))
29
+ node = node.resolve(doc);
30
+ return node;
31
+ }
32
+ function isMapAt(doc, path) {
33
+ return (0, yaml_1.isMap)(resolveAt(doc, path));
34
+ }
35
+ function existsAt(doc, path) {
36
+ return resolveAt(doc, path) !== undefined;
37
+ }
38
+ // `doc.getIn` does not traverse aliases. For aliased paths it returns
39
+ // undefined even when the resolved node has a value. Use the alias-aware
40
+ // walk and unwrap scalar nodes to their JS value.
41
+ function getValueAt(doc, path) {
42
+ const node = resolveAt(doc, path);
43
+ return (0, yaml_1.isScalar)(node) ? node.value : node;
44
+ }
45
+ // Walks `targetPath` resolving aliases at every step and mutates the
46
+ // resolved map directly so anchors are preserved. When a step is missing
47
+ // or holds a null placeholder (`key:` with no value), creates a fresh map
48
+ // inside the current parent and carries over the placeholder's comments
49
+ // and anchor so they aren't dropped. A dropped anchor would leave any
50
+ // alias referencing it unresolved and make `String(doc)` throw. Falls back
51
+ // to `doc.setIn` when the current parent isn't a map: an empty-document
52
+ // root gets bootstrapped, while a non-map value where a catalog map was
53
+ // expected makes `setIn` throw, surfacing the malformed config.
54
+ function setThroughAliases(doc, targetPath, value) {
55
+ let parent = doc.contents;
56
+ if ((0, yaml_1.isAlias)(parent))
57
+ parent = parent.resolve(doc);
58
+ for (let i = 0; i < targetPath.length - 1; i++) {
59
+ if (!(0, yaml_1.isMap)(parent)) {
60
+ doc.setIn(targetPath, value);
61
+ return;
62
+ }
63
+ let next = parent.get(targetPath[i], true);
64
+ const nextWasAlias = (0, yaml_1.isAlias)(next);
65
+ if ((0, yaml_1.isAlias)(next))
66
+ next = next.resolve(doc);
67
+ const placeholder = (0, yaml_1.isScalar)(next) && next.value === null ? next : undefined;
68
+ if (next === undefined || placeholder) {
69
+ let cur = parent;
70
+ for (let j = i; j < targetPath.length - 1; j++) {
71
+ const fresh = new yaml_1.YAMLMap();
72
+ if (j === i && placeholder) {
73
+ if (placeholder.comment)
74
+ fresh.comment = placeholder.comment;
75
+ if (placeholder.commentBefore)
76
+ fresh.commentBefore = placeholder.commentBefore;
77
+ // Copy the anchor only for a directly-held placeholder. When it
78
+ // was reached through an alias, the anchor still lives on the
79
+ // definition node, so re-emitting it here would duplicate it.
80
+ if (!nextWasAlias && placeholder.anchor)
81
+ fresh.anchor = placeholder.anchor;
82
+ }
83
+ cur.set(targetPath[j], fresh);
84
+ cur = fresh;
85
+ }
86
+ cur.set(targetPath[targetPath.length - 1], value);
87
+ return;
88
+ }
89
+ parent = next;
90
+ }
91
+ if (!(0, yaml_1.isMap)(parent)) {
92
+ doc.setIn(targetPath, value);
93
+ return;
94
+ }
95
+ parent.set(targetPath[targetPath.length - 1], value);
96
+ }
97
+ function readCatalogConfigFromFs(filename, fullPath) {
98
+ try {
99
+ return (0, devkit_internals_1.readYamlFile)(fullPath);
100
+ }
101
+ catch (error) {
102
+ devkit_exports_1.output.warn({
103
+ title: `Unable to parse ${filename}`,
104
+ bodyLines: [error.toString()],
105
+ });
106
+ return null;
107
+ }
108
+ }
109
+ function readCatalogConfigFromTree(filename, tree) {
110
+ const content = tree.read(filename, 'utf-8');
111
+ try {
112
+ return (0, js_yaml_1.load)(content, { filename });
113
+ }
114
+ catch (error) {
115
+ devkit_exports_1.output.warn({
116
+ title: `Unable to parse ${filename}`,
117
+ bodyLines: [error.toString()],
118
+ });
119
+ return null;
120
+ }
121
+ }
122
+ function updateCatalogVersionsInFile(filename, treeOrRoot, updates) {
123
+ let checkExists;
124
+ let readYaml;
125
+ let writeYaml;
126
+ if (typeof treeOrRoot === 'string') {
127
+ const configPath = (0, node_path_1.join)(treeOrRoot, filename);
128
+ checkExists = () => (0, node_fs_1.existsSync)(configPath);
129
+ readYaml = () => (0, node_fs_1.readFileSync)(configPath, 'utf-8');
130
+ writeYaml = (content) => (0, node_fs_1.writeFileSync)(configPath, content, 'utf-8');
131
+ }
132
+ else {
133
+ checkExists = () => treeOrRoot.exists(filename);
134
+ readYaml = () => treeOrRoot.read(filename, 'utf-8');
135
+ writeYaml = (content) => treeOrRoot.write(filename, content);
136
+ }
137
+ if (!checkExists()) {
138
+ devkit_exports_1.output.warn({
139
+ title: `No ${filename} found`,
140
+ bodyLines: [
141
+ `Cannot update catalog versions without a ${filename} file.`,
142
+ `Create a ${filename} file to use catalogs.`,
143
+ ],
144
+ });
145
+ return;
146
+ }
147
+ try {
148
+ // parseDocument keeps comments and anchors so a catalog bump doesn't
149
+ // rewrite the user's config file.
150
+ const lineCounter = new yaml_1.LineCounter();
151
+ const doc = (0, yaml_1.parseDocument)(readYaml(), { lineCounter });
152
+ // parseDocument collects syntax errors instead of throwing; surface them
153
+ // now, with their line/column detail, rather than failing later in
154
+ // `String(doc)` with a generic message or skipping a no-op write on a
155
+ // malformed file (the previous js-yaml `load()` threw here).
156
+ if (doc.errors.length > 0) {
157
+ throw new Error(doc.errors.map((e) => e.message).join('\n'));
158
+ }
159
+ // A dangling alias (`*ref` with no matching `&ref`) is not a syntax error,
160
+ // so parseDocument leaves it out of doc.errors. Surface it here with the
161
+ // same line/column detail the old js-yaml `load()` reported; otherwise a
162
+ // broken reference is silently overwritten or written back untouched.
163
+ const unresolvedAliases = [];
164
+ (0, yaml_1.visit)(doc, {
165
+ Alias(_key, node) {
166
+ if (node.resolve(doc) !== undefined)
167
+ return;
168
+ const pos = node.range ? lineCounter.linePos(node.range[0]) : undefined;
169
+ unresolvedAliases.push(pos
170
+ ? `Unresolved alias "${node.source}" at line ${pos.line}, column ${pos.col}`
171
+ : `Unresolved alias "${node.source}"`);
172
+ },
173
+ });
174
+ if (unresolvedAliases.length > 0) {
175
+ throw new Error(unresolvedAliases.join('\n'));
176
+ }
177
+ let hasChanges = false;
178
+ for (const update of updates) {
179
+ const { packageName, version, catalogName } = update;
180
+ const normalizedCatalogName = catalogName === 'default' ? undefined : catalogName;
181
+ let targetPath;
182
+ if (!normalizedCatalogName) {
183
+ // An empty `catalog:` placeholder must not claim the default route
184
+ // when `catalogs.default` is populated; that would create a
185
+ // duplicate-default config rejected by pnpm.
186
+ if (isMapAt(doc, ['catalog'])) {
187
+ targetPath = ['catalog', packageName];
188
+ }
189
+ else if (existsAt(doc, ['catalogs', 'default'])) {
190
+ targetPath = ['catalogs', 'default', packageName];
191
+ }
192
+ else {
193
+ targetPath = ['catalog', packageName];
194
+ }
195
+ }
196
+ else {
197
+ targetPath = ['catalogs', normalizedCatalogName, packageName];
198
+ }
199
+ if (getValueAt(doc, targetPath) !== version) {
200
+ setThroughAliases(doc, targetPath, version);
201
+ hasChanges = true;
202
+ }
203
+ }
204
+ if (hasChanges) {
205
+ writeYaml(String(doc));
206
+ }
207
+ }
208
+ catch (error) {
209
+ devkit_exports_1.output.error({
210
+ title: 'Failed to update catalog versions',
211
+ bodyLines: [error instanceof Error ? error.message : String(error)],
212
+ });
213
+ throw error;
214
+ }
215
+ }
@@ -1,12 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PnpmCatalogManager = void 0;
4
- const js_yaml_1 = require("@zkochan/js-yaml");
5
4
  const node_fs_1 = require("node:fs");
6
5
  const node_path_1 = require("node:path");
7
- const devkit_exports_1 = require("nx/src/devkit-exports");
8
- const devkit_internals_1 = require("nx/src/devkit-internals");
9
6
  const manager_1 = require("./manager");
7
+ const manager_utils_1 = require("./manager-utils");
10
8
  const PNPM_WORKSPACE_FILENAME = 'pnpm-workspace.yaml';
11
9
  /**
12
10
  * PNPM-specific catalog manager implementation
@@ -40,13 +38,13 @@ class PnpmCatalogManager {
40
38
  if (!(0, node_fs_1.existsSync)(configPath)) {
41
39
  return null;
42
40
  }
43
- return readConfigFromFs(configPath);
41
+ return (0, manager_utils_1.readCatalogConfigFromFs)(PNPM_WORKSPACE_FILENAME, configPath);
44
42
  }
45
43
  else {
46
44
  if (!treeOrRoot.exists(PNPM_WORKSPACE_FILENAME)) {
47
45
  return null;
48
46
  }
49
- return readConfigFromTree(treeOrRoot, PNPM_WORKSPACE_FILENAME);
47
+ return (0, manager_utils_1.readCatalogConfigFromTree)(PNPM_WORKSPACE_FILENAME, treeOrRoot);
50
48
  }
51
49
  }
52
50
  resolveCatalogReference(treeOrRoot, packageName, version) {
@@ -147,103 +145,7 @@ class PnpmCatalogManager {
147
145
  }
148
146
  }
149
147
  updateCatalogVersions(treeOrRoot, updates) {
150
- let checkExists;
151
- let readYaml;
152
- let writeYaml;
153
- if (typeof treeOrRoot === 'string') {
154
- const configPath = (0, node_path_1.join)(treeOrRoot, PNPM_WORKSPACE_FILENAME);
155
- checkExists = () => (0, node_fs_1.existsSync)(configPath);
156
- readYaml = () => (0, node_fs_1.readFileSync)(configPath, 'utf-8');
157
- writeYaml = (content) => (0, node_fs_1.writeFileSync)(configPath, content, 'utf-8');
158
- }
159
- else {
160
- checkExists = () => treeOrRoot.exists(PNPM_WORKSPACE_FILENAME);
161
- readYaml = () => treeOrRoot.read(PNPM_WORKSPACE_FILENAME, 'utf-8');
162
- writeYaml = (content) => treeOrRoot.write(PNPM_WORKSPACE_FILENAME, content);
163
- }
164
- if (!checkExists()) {
165
- devkit_exports_1.output.warn({
166
- title: `No ${PNPM_WORKSPACE_FILENAME} found`,
167
- bodyLines: [
168
- `Cannot update catalog versions without a ${PNPM_WORKSPACE_FILENAME} file.`,
169
- `Create a ${PNPM_WORKSPACE_FILENAME} file to use catalogs.`,
170
- ],
171
- });
172
- return;
173
- }
174
- try {
175
- const configContent = readYaml();
176
- const configData = (0, js_yaml_1.load)(configContent) || {};
177
- let hasChanges = false;
178
- for (const update of updates) {
179
- const { packageName, version, catalogName } = update;
180
- const normalizedCatalogName = catalogName === 'default' ? undefined : catalogName;
181
- let targetCatalog;
182
- if (!normalizedCatalogName) {
183
- // Default catalog - update whichever exists, prefer catalog over catalogs.default
184
- if (configData.catalog) {
185
- targetCatalog = configData.catalog;
186
- }
187
- else if (configData.catalogs?.default) {
188
- targetCatalog = configData.catalogs.default;
189
- }
190
- else {
191
- // Neither exists, create catalog (shorthand syntax)
192
- configData.catalog ??= {};
193
- targetCatalog = configData.catalog;
194
- }
195
- }
196
- else {
197
- // Named catalog
198
- configData.catalogs ??= {};
199
- configData.catalogs[normalizedCatalogName] ??= {};
200
- targetCatalog = configData.catalogs[normalizedCatalogName];
201
- }
202
- if (targetCatalog[packageName] !== version) {
203
- targetCatalog[packageName] = version;
204
- hasChanges = true;
205
- }
206
- }
207
- if (hasChanges) {
208
- writeYaml((0, js_yaml_1.dump)(configData, {
209
- indent: 2,
210
- quotingType: '"',
211
- forceQuotes: true,
212
- }));
213
- }
214
- }
215
- catch (error) {
216
- devkit_exports_1.output.error({
217
- title: 'Failed to update catalog versions',
218
- bodyLines: [error instanceof Error ? error.message : String(error)],
219
- });
220
- throw error;
221
- }
148
+ (0, manager_utils_1.updateCatalogVersionsInFile)(PNPM_WORKSPACE_FILENAME, treeOrRoot, updates);
222
149
  }
223
150
  }
224
151
  exports.PnpmCatalogManager = PnpmCatalogManager;
225
- function readConfigFromFs(path) {
226
- try {
227
- return (0, devkit_internals_1.readYamlFile)(path);
228
- }
229
- catch (error) {
230
- devkit_exports_1.output.warn({
231
- title: `Unable to parse ${PNPM_WORKSPACE_FILENAME}`,
232
- bodyLines: [error.toString()],
233
- });
234
- return null;
235
- }
236
- }
237
- function readConfigFromTree(tree, path) {
238
- const content = tree.read(path, 'utf-8');
239
- try {
240
- return (0, js_yaml_1.load)(content, { filename: path });
241
- }
242
- catch (error) {
243
- devkit_exports_1.output.warn({
244
- title: `Unable to parse ${PNPM_WORKSPACE_FILENAME}`,
245
- bodyLines: [error.toString()],
246
- });
247
- return null;
248
- }
249
- }
@@ -1,12 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.YarnCatalogManager = void 0;
4
- const js_yaml_1 = require("@zkochan/js-yaml");
5
4
  const node_fs_1 = require("node:fs");
6
5
  const node_path_1 = require("node:path");
7
- const devkit_exports_1 = require("nx/src/devkit-exports");
8
- const devkit_internals_1 = require("nx/src/devkit-internals");
9
6
  const manager_1 = require("./manager");
7
+ const manager_utils_1 = require("./manager-utils");
10
8
  const YARNRC_FILENAME = '.yarnrc.yml';
11
9
  /**
12
10
  * Yarn Berry (v4+) catalog manager implementation
@@ -40,13 +38,13 @@ class YarnCatalogManager {
40
38
  if (!(0, node_fs_1.existsSync)(configPath)) {
41
39
  return null;
42
40
  }
43
- return readConfigFromFs(configPath);
41
+ return (0, manager_utils_1.readCatalogConfigFromFs)(YARNRC_FILENAME, configPath);
44
42
  }
45
43
  else {
46
44
  if (!treeOrRoot.exists(YARNRC_FILENAME)) {
47
45
  return null;
48
46
  }
49
- return readConfigFromTree(treeOrRoot, YARNRC_FILENAME);
47
+ return (0, manager_utils_1.readCatalogConfigFromTree)(YARNRC_FILENAME, treeOrRoot);
50
48
  }
51
49
  }
52
50
  resolveCatalogReference(treeOrRoot, packageName, version) {
@@ -147,103 +145,7 @@ class YarnCatalogManager {
147
145
  }
148
146
  }
149
147
  updateCatalogVersions(treeOrRoot, updates) {
150
- let checkExists;
151
- let readYaml;
152
- let writeYaml;
153
- if (typeof treeOrRoot === 'string') {
154
- const configPath = (0, node_path_1.join)(treeOrRoot, YARNRC_FILENAME);
155
- checkExists = () => (0, node_fs_1.existsSync)(configPath);
156
- readYaml = () => (0, node_fs_1.readFileSync)(configPath, 'utf-8');
157
- writeYaml = (content) => (0, node_fs_1.writeFileSync)(configPath, content, 'utf-8');
158
- }
159
- else {
160
- checkExists = () => treeOrRoot.exists(YARNRC_FILENAME);
161
- readYaml = () => treeOrRoot.read(YARNRC_FILENAME, 'utf-8');
162
- writeYaml = (content) => treeOrRoot.write(YARNRC_FILENAME, content);
163
- }
164
- if (!checkExists()) {
165
- devkit_exports_1.output.warn({
166
- title: `No ${YARNRC_FILENAME} found`,
167
- bodyLines: [
168
- `Cannot update catalog versions without a ${YARNRC_FILENAME} file.`,
169
- `Create a ${YARNRC_FILENAME} file to use catalogs.`,
170
- ],
171
- });
172
- return;
173
- }
174
- try {
175
- const configContent = readYaml();
176
- const configData = (0, js_yaml_1.load)(configContent) || {};
177
- let hasChanges = false;
178
- for (const update of updates) {
179
- const { packageName, version, catalogName } = update;
180
- const normalizedCatalogName = catalogName === 'default' ? undefined : catalogName;
181
- let targetCatalog;
182
- if (!normalizedCatalogName) {
183
- // Default catalog - update whichever exists, prefer catalog over catalogs.default
184
- if (configData.catalog) {
185
- targetCatalog = configData.catalog;
186
- }
187
- else if (configData.catalogs?.default) {
188
- targetCatalog = configData.catalogs.default;
189
- }
190
- else {
191
- // Neither exists, create catalog (shorthand syntax)
192
- configData.catalog ??= {};
193
- targetCatalog = configData.catalog;
194
- }
195
- }
196
- else {
197
- // Named catalog
198
- configData.catalogs ??= {};
199
- configData.catalogs[normalizedCatalogName] ??= {};
200
- targetCatalog = configData.catalogs[normalizedCatalogName];
201
- }
202
- if (targetCatalog[packageName] !== version) {
203
- targetCatalog[packageName] = version;
204
- hasChanges = true;
205
- }
206
- }
207
- if (hasChanges) {
208
- writeYaml((0, js_yaml_1.dump)(configData, {
209
- indent: 2,
210
- quotingType: '"',
211
- forceQuotes: true,
212
- }));
213
- }
214
- }
215
- catch (error) {
216
- devkit_exports_1.output.error({
217
- title: 'Failed to update catalog versions',
218
- bodyLines: [error instanceof Error ? error.message : String(error)],
219
- });
220
- throw error;
221
- }
148
+ (0, manager_utils_1.updateCatalogVersionsInFile)(YARNRC_FILENAME, treeOrRoot, updates);
222
149
  }
223
150
  }
224
151
  exports.YarnCatalogManager = YarnCatalogManager;
225
- function readConfigFromFs(path) {
226
- try {
227
- return (0, devkit_internals_1.readYamlFile)(path);
228
- }
229
- catch (error) {
230
- devkit_exports_1.output.warn({
231
- title: `Unable to parse ${YARNRC_FILENAME}`,
232
- bodyLines: [error.toString()],
233
- });
234
- return null;
235
- }
236
- }
237
- function readConfigFromTree(tree, path) {
238
- const content = tree.read(path, 'utf-8');
239
- try {
240
- return (0, js_yaml_1.load)(content, { filename: path });
241
- }
242
- catch (error) {
243
- devkit_exports_1.output.warn({
244
- title: `Unable to parse ${YARNRC_FILENAME}`,
245
- bodyLines: [error.toString()],
246
- });
247
- return null;
248
- }
249
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/devkit",
3
- "version": "23.1.0-pr.36127.e594f53",
3
+ "version": "23.1.0-rc.0",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "files": [
@@ -56,11 +56,12 @@
56
56
  "semver": "^7.6.3",
57
57
  "yargs-parser": "21.1.1",
58
58
  "minimatch": "10.2.5",
59
- "enquirer": "~2.3.6"
59
+ "enquirer": "~2.3.6",
60
+ "yaml": "^2.8.3"
60
61
  },
61
62
  "devDependencies": {
62
63
  "jest": "30.3.0",
63
- "nx": "23.1.0-pr.36127.e594f53"
64
+ "nx": "23.1.0-rc.0"
64
65
  },
65
66
  "peerDependencies": {
66
67
  "nx": ">= 22 <= 24 || ^23.0.0-0"