@nx/js 23.2.0-beta.1 → 23.2.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/internal.d.ts +9 -3
  2. package/dist/internal.js +28 -17
  3. package/dist/src/executors/copy-workspace-modules/copy-workspace-modules.js +122 -21
  4. package/dist/src/executors/node/lib/output-file.js +4 -4
  5. package/dist/src/executors/node/node.impl.js +9 -10
  6. package/dist/src/executors/prune-lockfile/prune-lockfile.d.ts +1 -1
  7. package/dist/src/executors/prune-lockfile/prune-lockfile.js +40 -33
  8. package/dist/src/executors/tsc/lib/batch/watch.js +3 -3
  9. package/dist/src/executors/tsc/tsc.batch-impl.d.ts +1 -1
  10. package/dist/src/executors/tsc/tsc.batch-impl.js +2 -2
  11. package/dist/src/executors/verdaccio/verdaccio.impl.js +8 -1
  12. package/dist/src/generators/init/init.js +32 -15
  13. package/dist/src/generators/init/schema.d.ts +1 -1
  14. package/dist/src/generators/init/schema.json +1 -1
  15. package/dist/src/generators/library/library.d.ts +1 -1
  16. package/dist/src/generators/library/library.js +34 -20
  17. package/dist/src/generators/library/schema.d.ts +11 -3
  18. package/dist/src/generators/library/schema.json +15 -4
  19. package/dist/src/generators/library/utils/add-release-config.js +2 -2
  20. package/dist/src/generators/setup-build/generator.js +2 -7
  21. package/dist/src/generators/typescript-sync/typescript-sync.d.ts +1 -1
  22. package/dist/src/generators/typescript-sync/typescript-sync.js +2 -2
  23. package/dist/src/index.d.ts +3 -2
  24. package/dist/src/index.js +8 -8
  25. package/dist/src/migrations/update-23-1-0/add-ignore-deprecations-for-ts6.d.ts +3 -2
  26. package/dist/src/migrations/update-23-1-0/add-ignore-deprecations-for-ts6.js +17 -11
  27. package/dist/src/migrations/update-23-2-0/add-pnpm-deploy-output-cache-inputs.d.ts +14 -0
  28. package/dist/src/migrations/update-23-2-0/add-pnpm-deploy-output-cache-inputs.js +242 -0
  29. package/dist/src/migrations/update-23-2-0/add-pnpm-deploy-output-cache-inputs.md +64 -0
  30. package/dist/src/migrations/update-23-2-0/add-pnpm-prune-lockfile-cache-config.d.ts +2 -0
  31. package/dist/src/migrations/update-23-2-0/add-pnpm-prune-lockfile-cache-config.js +305 -0
  32. package/dist/src/migrations/update-23-2-0/add-pnpm-prune-lockfile-cache-config.md +59 -0
  33. package/dist/src/plugins/typescript/plugin.js +9 -14
  34. package/dist/src/plugins/typescript/util.d.ts +1 -1
  35. package/dist/src/release/utils/update-lock-file.js +5 -7
  36. package/dist/src/release/version-actions.d.ts +12 -3
  37. package/dist/src/release/version-actions.js +236 -76
  38. package/dist/src/utils/add-linting-to-project.d.ts +46 -0
  39. package/dist/src/utils/add-linting-to-project.js +112 -0
  40. package/dist/src/utils/assets/copy-assets-handler.d.ts +1 -1
  41. package/dist/src/utils/assets/copy-assets-handler.js +2 -2
  42. package/dist/src/utils/buildable-libs-utils.js +10 -13
  43. package/dist/src/utils/find-npm-dependencies.js +7 -10
  44. package/dist/src/utils/formatter-setup.d.ts +26 -0
  45. package/dist/src/utils/formatter-setup.js +43 -0
  46. package/dist/src/utils/generate-globs.js +11 -14
  47. package/dist/src/utils/generator-prompts.d.ts +2 -1
  48. package/dist/src/utils/generator-prompts.js +23 -22
  49. package/dist/src/utils/get-main-file-dir.js +2 -2
  50. package/dist/src/utils/linter.d.ts +30 -0
  51. package/dist/src/utils/linter.js +43 -0
  52. package/dist/src/utils/npm-config.d.ts +1 -1
  53. package/dist/src/utils/nx-formatter-internals.d.ts +8 -0
  54. package/dist/src/utils/nx-formatter-internals.js +18 -0
  55. package/dist/src/utils/oxfmt.d.ts +4 -0
  56. package/dist/src/utils/oxfmt.js +35 -0
  57. package/dist/src/utils/package-json/update-package-json.d.ts +1 -1
  58. package/dist/src/utils/package-json/update-package-json.js +11 -22
  59. package/dist/src/utils/package-manager-workspaces.js +2 -2
  60. package/dist/src/utils/pnpm-deploy-output-cache-inputs.d.ts +29 -0
  61. package/dist/src/utils/pnpm-deploy-output-cache-inputs.js +177 -0
  62. package/dist/src/utils/pnpm-install-settings-inputs.d.ts +45 -0
  63. package/dist/src/utils/pnpm-install-settings-inputs.js +77 -0
  64. package/dist/src/utils/prettier.js +14 -33
  65. package/dist/src/utils/target-defaults-matching.d.ts +57 -0
  66. package/dist/src/utils/target-defaults-matching.js +126 -0
  67. package/dist/src/utils/typescript/configuration.js +3 -5
  68. package/dist/src/utils/typescript/create-ts-config.d.ts +1 -1
  69. package/dist/src/utils/typescript/create-ts-config.js +5 -5
  70. package/dist/src/utils/typescript/plugin.js +2 -2
  71. package/dist/src/utils/typescript/run-type-check.js +2 -2
  72. package/dist/src/utils/typescript/ts-config.d.ts +51 -1
  73. package/dist/src/utils/typescript/ts-config.js +114 -0
  74. package/dist/src/utils/typescript/ts-solution-setup.d.ts +1 -1
  75. package/dist/src/utils/typescript/ts-solution-setup.js +3 -3
  76. package/dist/src/utils/versions.d.ts +1 -0
  77. package/dist/src/utils/versions.js +2 -1
  78. package/dist/src/utils/watch-for-single-file-changes.js +2 -2
  79. package/dist/src/utils/workspace-module-sections.d.ts +16 -0
  80. package/dist/src/utils/workspace-module-sections.js +24 -0
  81. package/migrations.json +13 -1
  82. package/package.json +12 -5
@@ -0,0 +1,242 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = update;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const internal_1 = require("@nx/devkit/internal");
6
+ const path_1 = require("path");
7
+ const pnpm_deploy_output_cache_inputs_1 = require("../../utils/pnpm-deploy-output-cache-inputs");
8
+ const target_defaults_matching_1 = require("../../utils/target-defaults-matching");
9
+ // The executors that emit the pruned pnpm deploy output (lockfile,
10
+ // settings-only pnpm-workspace.yaml, patches, vendored local paths), each with
11
+ // the option that turns the emission on. webpack, rspack, vite and esbuild
12
+ // gate it on `generatePackageJson`; next, remix, tsc and swc emit the manifest
13
+ // regardless and gate only the deploy output on `generateLockfile`.
14
+ const DEPLOY_OUTPUT_EXECUTORS = {
15
+ '@nx/webpack:webpack': 'generatePackageJson',
16
+ '@nx/rspack:rspack': 'generatePackageJson',
17
+ '@nx/vite:build': 'generatePackageJson',
18
+ '@nx/esbuild:esbuild': 'generatePackageJson',
19
+ '@nx/next:build': 'generateLockfile',
20
+ '@nx/remix:build': 'generateLockfile',
21
+ '@nx/js:tsc': 'generateLockfile',
22
+ '@nx/js:swc': 'generateLockfile',
23
+ };
24
+ // The config-file plugins whose inferred build target can emit the deploy
25
+ // output (NxAppWebpackPlugin/NxAppRspackPlugin with generatePackageJson).
26
+ // The globs mirror each plugin's own createNodes pattern; the commands are
27
+ // what each plugin sets on the build target it creates.
28
+ const INFERRED_DEPLOY_PLUGINS = {
29
+ '@nx/webpack/plugin': {
30
+ configGlob: '**/webpack.config.{js,ts,mjs,cjs}',
31
+ command: 'webpack-cli build',
32
+ },
33
+ '@nx/rspack/plugin': {
34
+ configGlob: '**/rspack.config.{js,ts,mjs,mts,cjs,cts}',
35
+ command: 'rspack build',
36
+ },
37
+ };
38
+ /**
39
+ * Adds the workspace-root pnpm install settings sources to the `inputs` of
40
+ * build targets that emit the pruned pnpm deploy output. The build approvals
41
+ * and `supportedArchitectures` those outputs carry reach no lockfile, so
42
+ * without these a revoked approval leaves the task hash unchanged and a cached
43
+ * run replays an output that still grants it. Inferred targets are covered by
44
+ * the `@nx/webpack` and `@nx/rspack` plugins themselves; this migration covers
45
+ * the explicitly configured executor targets, plus overlays of inferred
46
+ * targets (a project-level entry, under the exact name or a glob-pattern key
47
+ * matching it, or a matching `targetDefaults` entry) whose replacing `inputs`
48
+ * array discards the plugin-generated one.
49
+ */
50
+ async function update(tree) {
51
+ // The deploy output's install settings are pnpm-specific; other package
52
+ // managers' outputs hash everything they are built from already.
53
+ if (!tree.exists('pnpm-lock.yaml')) {
54
+ return;
55
+ }
56
+ const nxJson = (0, devkit_1.readNxJson)(tree);
57
+ const targetDefaults = nxJson?.targetDefaults;
58
+ const projects = new Map();
59
+ const changedProjects = new Set();
60
+ let defaultsChanged = false;
61
+ for (const [projectName, project] of (0, devkit_1.getProjects)(tree)) {
62
+ projects.set(projectName, project);
63
+ const projectNode = {
64
+ name: projectName,
65
+ type: 'lib',
66
+ data: { root: project.root, tags: project.tags },
67
+ };
68
+ for (const [targetName, target] of Object.entries(project.targets ?? {})) {
69
+ // The executor may come from a matching targetDefaults entry rather
70
+ // than the target itself. A `command` target resolves to nx:run-commands
71
+ // before defaults apply, so a default can never re-identify it.
72
+ const executor = target.executor ??
73
+ (target.command
74
+ ? 'nx:run-commands'
75
+ : (0, target_defaults_matching_1.resolveDefaultsExecutor)(targetName, projectName, projectNode, targetDefaults));
76
+ if (!executor || !DEPLOY_OUTPUT_EXECUTORS[executor]) {
77
+ continue;
78
+ }
79
+ const ref = {
80
+ targetName,
81
+ projectName,
82
+ projectNode,
83
+ matcherExecutor: target.executor,
84
+ target,
85
+ };
86
+ const selectedKey = targetDefaults
87
+ ? (0, target_defaults_matching_1.selectDefaultsKey)(ref, targetDefaults, executor)
88
+ : null;
89
+ const entries = (0, target_defaults_matching_1.compatibleDefaultsEntries)(ref, selectedKey, targetDefaults);
90
+ const defaults = entries.length
91
+ ? (0, internal_1.readTargetDefaultsForTarget)(targetName, { [selectedKey]: entries }, ref.matcherExecutor, { projectName, projectNode })
92
+ : null;
93
+ // The effective view, not the target's own: the gating option may be
94
+ // supplied by a matching defaults entry or set only in a configuration.
95
+ const effective = (0, internal_1.mergeTargetConfigurations)(target, defaults ?? {});
96
+ if (!emitsDeployOutput(effective, DEPLOY_OUTPUT_EXECUTORS[executor])) {
97
+ continue;
98
+ }
99
+ const changed = (0, pnpm_deploy_output_cache_inputs_1.addPnpmDeployOutputCacheInputs)(ref, targetDefaults, executor);
100
+ if (changed === 'target') {
101
+ changedProjects.add(projectName);
102
+ }
103
+ else if (changed === 'defaults') {
104
+ defaultsChanged = true;
105
+ }
106
+ }
107
+ }
108
+ // Overlays of plugin-inferred build targets carry no executor, so the loop
109
+ // above cannot see them, yet a replacing `inputs` array in one discards the
110
+ // settings inputs the plugin generates. The plugin adds those inputs without
111
+ // knowing whether the config enables generatePackageJson (that lives inside
112
+ // the user's config file), so the overlay repair is equally unconditional.
113
+ // It appends the runtime probe even where the plugin omits it under a valid
114
+ // pnpm pin: the repair runs once and the pin can be removed later.
115
+ for (const ref of await inferredDeployTargetOverlayRefs(tree, nxJson, projects)) {
116
+ const changed = (0, pnpm_deploy_output_cache_inputs_1.addPnpmDeployOutputCacheInputsToInferredTargetOverlay)(ref, targetDefaults);
117
+ if (changed === 'target') {
118
+ changedProjects.add(ref.projectName);
119
+ }
120
+ else if (changed === 'defaults') {
121
+ defaultsChanged = true;
122
+ }
123
+ }
124
+ for (const projectName of changedProjects) {
125
+ (0, devkit_1.updateProjectConfiguration)(tree, projectName, projects.get(projectName));
126
+ }
127
+ if (defaultsChanged) {
128
+ (0, devkit_1.updateNxJson)(tree, nxJson);
129
+ }
130
+ await (0, devkit_1.formatFiles)(tree);
131
+ }
132
+ /**
133
+ * One ref per (project, inferred build target) the registered `@nx/webpack` or
134
+ * `@nx/rspack` config-file plugins would create, resolved the way the plugins
135
+ * do: their createNodes glob filtered by each registration's include/exclude,
136
+ * the config file's directory as the project root, and the registration's
137
+ * `buildTargetName` option. The overlaying project-level entry may sit under
138
+ * the exact target name or a glob-pattern key matching it; one that replaces
139
+ * what the target runs disqualifies the target instead.
140
+ */
141
+ async function inferredDeployTargetOverlayRefs(tree, nxJson, projects) {
142
+ const rootToProject = new Map();
143
+ for (const [projectName, project] of projects) {
144
+ rootToProject.set(project.root, projectName);
145
+ }
146
+ const refs = [];
147
+ for (const registration of nxJson?.plugins ?? []) {
148
+ const pluginName = typeof registration === 'string' ? registration : registration.plugin;
149
+ const inferredPlugin = INFERRED_DEPLOY_PLUGINS[pluginName];
150
+ if (!inferredPlugin) {
151
+ continue;
152
+ }
153
+ const options = typeof registration === 'string'
154
+ ? undefined
155
+ : registration.options;
156
+ const targetName = options?.buildTargetName ?? 'build';
157
+ const configFiles = (0, internal_1.findMatchingConfigFiles)(await (0, devkit_1.globAsync)(tree, [inferredPlugin.configGlob]), typeof registration === 'string' ? undefined : registration.include, typeof registration === 'string' ? undefined : registration.exclude);
158
+ for (const configFile of configFiles) {
159
+ const projectName = rootToProject.get((0, path_1.dirname)(configFile));
160
+ if (!projectName) {
161
+ continue;
162
+ }
163
+ const project = projects.get(projectName);
164
+ const target = inferredTargetOverlayEntry(project.targets, targetName);
165
+ // An entry that changes what the target runs is not an overlay; the
166
+ // executor loop covers the deploy executors among those.
167
+ if (target &&
168
+ replacesInferredRunCommandsIdentity(target, inferredPlugin.command, project.root)) {
169
+ continue;
170
+ }
171
+ refs.push({
172
+ targetName,
173
+ projectName,
174
+ projectNode: {
175
+ name: projectName,
176
+ type: 'lib',
177
+ data: { root: project.root, tags: project.tags },
178
+ },
179
+ // the runtime matcher sees the plugin target's effective identity:
180
+ // its `command` payload resolves to nx:run-commands
181
+ matcherExecutor: 'nx:run-commands',
182
+ // the runtime drops the plugin attribution as soon as the project
183
+ // entry authors an executor or command, restated or not, so a
184
+ // plugin-filtered defaults entry no longer applies to the target
185
+ sourcePlugin: target?.executor !== undefined || target?.command !== undefined
186
+ ? undefined
187
+ : pluginName,
188
+ target: target ?? {},
189
+ });
190
+ }
191
+ }
192
+ return refs;
193
+ }
194
+ /**
195
+ * The project-level entry that overlays the inferred `targetName`. Its key is
196
+ * the exact name or a glob pattern the name matches; the inferred target
197
+ * exists before any project-level entry merges, so a matching glob key
198
+ * overlays it too. When several keys match, each entry merges against the
199
+ * inferred target independently and the last write wins, so only the last
200
+ * one applies. A glob key matching an earlier same-file sibling never
201
+ * surfaces here: the project read already collapsed it onto that sibling and
202
+ * dropped the key.
203
+ */
204
+ function inferredTargetOverlayEntry(targets, targetName) {
205
+ const matching = Object.entries(targets ?? {}).filter(([key]) => key === targetName ||
206
+ (0, internal_1.findMatchingTargetNames)(key, [targetName]).length > 0);
207
+ return matching[matching.length - 1]?.[1];
208
+ }
209
+ /**
210
+ * Whether the entry changes what the inferred run-commands target runs when
211
+ * merged, resolved with nx's own merge so `"..."` placement and restated
212
+ * identities land the way the runtime lands them: a merged executor other
213
+ * than nx:run-commands, an effective command differing from the plugin's, or
214
+ * a `command`/`commands` clash the run-commands schema rejects. An entry
215
+ * restating the identity the plugin infers is still an overlay.
216
+ */
217
+ function replacesInferredRunCommandsIdentity(target, inferredCommand, projectRoot) {
218
+ // `command` alongside `executor` fails graph construction outright;
219
+ // there is nothing running to repair.
220
+ if (target.command && target.executor) {
221
+ return true;
222
+ }
223
+ const merged = (0, internal_1.mergeTargetConfigurations)((0, internal_1.resolveCommandSyntacticSugar)(target, projectRoot), { executor: 'nx:run-commands', options: { command: inferredCommand } });
224
+ if (merged.executor !== 'nx:run-commands') {
225
+ return true;
226
+ }
227
+ // Both present fails the run-commands schema's oneOf; the target cannot run.
228
+ if (merged.options?.command && merged.options?.commands) {
229
+ return true;
230
+ }
231
+ const command = merged.options?.command ?? merged.options?.commands?.join(' && ');
232
+ return command !== inferredCommand;
233
+ }
234
+ /**
235
+ * Whether the target ships the deploy output in any of its runs: the gating
236
+ * option can sit in `options` or in any configuration, and `inputs` cannot
237
+ * vary per configuration, so one enabled configuration is enough.
238
+ */
239
+ function emitsDeployOutput(effective, gate) {
240
+ return (Boolean(effective.options?.[gate]) ||
241
+ Object.values(effective.configurations ?? {}).some((configuration) => Boolean(configuration?.[gate])));
242
+ }
@@ -0,0 +1,64 @@
1
+ #### Add the pnpm install settings sources to the inputs of build targets that emit the pruned deploy output
2
+
3
+ In a pnpm workspace, build executors that generate a deployable `package.json` also emit the pruned deploy output next to it: the pruned `pnpm-lock.yaml`, a settings-only `pnpm-workspace.yaml`, the `.patch` files of a `pnpm patch` workspace, and vendored local-path dependencies. webpack, rspack, vite and esbuild builds do this when `generatePackageJson` is enabled; next, remix, tsc and swc builds when `generateLockfile` is.
4
+
5
+ The build-script approvals and `supportedArchitectures` those artifacts carry come from the workspace root `pnpm-workspace.yaml` and the `pnpm` field of the root `package.json`, and are recorded in no lockfile, so nothing in the build target's default task hash moves when they change. Revoking an approval after a package turns out to be malicious would replay the previous build from cache and ship an output that still grants it.
6
+
7
+ The migration adds three inputs to the affected targets: the root `pnpm-workspace.yaml` as a fileset; a `json` input hashing only the fields of the root `package.json` the output is built from (`packageManager`, which selects whether the settings land in the emitted `pnpm-workspace.yaml` or the emitted `package.json`, plus `pnpm.onlyBuiltDependencies`, `pnpm.neverBuiltDependencies`, `pnpm.allowBuilds`, `pnpm.supportedArchitectures` and `pnpm.patchedDependencies`), so ordinary dependency bumps in the root manifest do not invalidate every build; and a `runtime` input probing the pnpm major, which decides that same file selection when the `packageManager` field is absent. The probe prints only the major, so pnpm patch and minor releases do not move the hash, and a sentinel when no pnpm binary is available, so it is safe in every workspace. It is added even when the field currently pins a pnpm version: the migration runs once, and the pin can be removed later.
8
+
9
+ The contents of vendored non-workspace local-path dependencies (`file:` directories and tarballs, `link:` targets) also ship in the deploy output but are not covered by these inputs: their set is derived from the lockfile at build time, so no statically written input list can stay correct as dependencies change.
10
+
11
+ A target's own `inputs` array replaces the defaults' rather than merging with it, so the inputs are added to the layer whose array the runtime actually uses: the target's own `inputs` when it declares them, otherwise the `targetDefaults` entry supplying the array it inherits, and otherwise the target itself with nx's own default spelled out first, so nothing that was hashed stops being hashed. The gating option is read from the merged view, so one supplied by a matching `targetDefaults` entry or set only in a configuration still counts. A source the target already hashes, including via a whole-file root `package.json` fileset or an existing `json` input covering the settings fields, is left alone. The migration never authors a `"..."` of its own.
12
+
13
+ Targets inferred by the `@nx/webpack` and `@nx/rspack` plugins declare the `pnpm-workspace.yaml` and `json` inputs themselves on every graph build, and the `runtime` probe while the root `package.json` has no `packageManager` field pinning a pnpm version, but a project-level target entry (under the exact name or a matching glob-pattern key) or a matching `targetDefaults` entry that supplies its own `inputs` array without `"..."` replaces the plugin-generated one and discards them. The migration repairs those overlays the same way, appending the complete set of three inputs to the replacing array, because it runs once and the pin can be removed later; when several entries match, only the last one applies, and it takes the repair. An inferred target with no such overlay is left alone, as is one whose entry changes what the target runs once merged: a different executor or effective command means the plugin's build no longer runs there. An entry that merely restates the identity the plugin infers is still an overlay and gets the repair.
14
+
15
+ #### Sample code changes
16
+
17
+ ##### Before
18
+
19
+ ```json title="apps/app1/project.json"
20
+ {
21
+ "targets": {
22
+ "build": {
23
+ "executor": "@nx/webpack:webpack",
24
+ "options": {
25
+ "generatePackageJson": true
26
+ }
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ ##### After
33
+
34
+ ```json title="apps/app1/project.json"
35
+ {
36
+ "targets": {
37
+ "build": {
38
+ "executor": "@nx/webpack:webpack",
39
+ "inputs": [
40
+ "default",
41
+ "^default",
42
+ "{workspaceRoot}/pnpm-workspace.yaml",
43
+ {
44
+ "json": "{workspaceRoot}/package.json",
45
+ "fields": [
46
+ "packageManager",
47
+ "pnpm.onlyBuiltDependencies",
48
+ "pnpm.neverBuiltDependencies",
49
+ "pnpm.allowBuilds",
50
+ "pnpm.supportedArchitectures",
51
+ "pnpm.patchedDependencies"
52
+ ]
53
+ },
54
+ {
55
+ "runtime": "node -e \"try{console.log('pnpm major '+require('child_process').execSync('pnpm --version',{stdio:['ignore','pipe','ignore']}).toString().trim().split('.')[0])}catch{console.log('pnpm major unavailable')}\""
56
+ }
57
+ ],
58
+ "options": {
59
+ "generatePackageJson": true
60
+ }
61
+ }
62
+ }
63
+ }
64
+ ```
@@ -0,0 +1,2 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ export default function update(tree: Tree): Promise<void>;
@@ -0,0 +1,305 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = update;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const internal_1 = require("@nx/devkit/internal");
6
+ const pnpm_install_settings_inputs_1 = require("../../utils/pnpm-install-settings-inputs");
7
+ const target_defaults_matching_1 = require("../../utils/target-defaults-matching");
8
+ const PRUNE_LOCKFILE_EXECUTOR = '@nx/js:prune-lockfile';
9
+ const PNPM_LOCKFILE = 'pnpm-lock.yaml';
10
+ // Artifacts the executor emits next to the pruned lockfile; without them in
11
+ // `outputs`, a cache replay restores only the manifest and the lockfile.
12
+ const PNPM_PRUNE_ARTIFACTS = [
13
+ 'pnpm-workspace.yaml',
14
+ 'patches',
15
+ 'local_path_modules',
16
+ ];
17
+ // The workspace root files those artifacts are built from. Neither the pnpm
18
+ // build approvals nor `supportedArchitectures` are recorded in the lockfile, so
19
+ // nothing else in the hash moves when they change. The pnpm major decides
20
+ // which emitted file carries them, and the ambient binary supplies it when the
21
+ // manifest's `packageManager` field is absent, so the major probe is added
22
+ // alongside.
23
+ const PNPM_ROOT_SETTINGS_SOURCES = [
24
+ '{workspaceRoot}/pnpm-workspace.yaml',
25
+ '{workspaceRoot}/package.json',
26
+ ];
27
+ async function update(tree) {
28
+ const nxJson = (0, devkit_1.readNxJson)(tree);
29
+ const targetDefaults = nxJson?.targetDefaults;
30
+ const pruneTargets = [];
31
+ const projects = new Map();
32
+ const changedProjects = new Set();
33
+ for (const [projectName, project] of (0, devkit_1.getProjects)(tree)) {
34
+ projects.set(projectName, project);
35
+ const projectNode = {
36
+ name: projectName,
37
+ type: 'lib',
38
+ data: { root: project.root, tags: project.tags },
39
+ };
40
+ for (const [targetName, target] of Object.entries(project.targets ?? {})) {
41
+ // The executor may come from a matching targetDefaults entry rather
42
+ // than the target itself. A `command` target resolves to nx:run-commands
43
+ // before defaults apply, so a default can never make it a prune target.
44
+ const executor = target.executor ??
45
+ (target.command
46
+ ? 'nx:run-commands'
47
+ : (0, target_defaults_matching_1.resolveDefaultsExecutor)(targetName, projectName, projectNode, targetDefaults, PRUNE_LOCKFILE_EXECUTOR));
48
+ if (executor !== PRUNE_LOCKFILE_EXECUTOR) {
49
+ continue;
50
+ }
51
+ pruneTargets.push({
52
+ targetName,
53
+ projectName,
54
+ projectNode,
55
+ matcherExecutor: target.executor,
56
+ target,
57
+ });
58
+ if (appendPnpmPruneOutputs(target)) {
59
+ changedProjects.add(projectName);
60
+ }
61
+ }
62
+ }
63
+ let defaultsChanged = false;
64
+ // runtime applies only the first key that resolves for a target (executor,
65
+ // exact name, then longest glob), so anchor each prune target to its
66
+ // selected key
67
+ const keyedPruneTargets = pruneTargets.map((ref) => ({
68
+ ref,
69
+ selectedKey: targetDefaults
70
+ ? (0, target_defaults_matching_1.selectDefaultsKey)(ref, targetDefaults, PRUNE_LOCKFILE_EXECUTOR)
71
+ : null,
72
+ }));
73
+ if (targetDefaults) {
74
+ for (const [key, value] of Object.entries(targetDefaults)) {
75
+ // a value may be the plain object form or the filtered array form
76
+ for (const config of Array.isArray(value) ? value : [value]) {
77
+ if (!appliesToPruneLockfileTargets(key, config, keyedPruneTargets)) {
78
+ continue;
79
+ }
80
+ defaultsChanged = appendPnpmPruneOutputs(config) || defaultsChanged;
81
+ }
82
+ }
83
+ }
84
+ // `inputs` is decided separately from `outputs`, because the two can be
85
+ // authored on different layers and a target's own `inputs` replaces the
86
+ // defaults' array rather than merging with it. The sources are only ever
87
+ // appended to an array that already exists, or written with nx's default as
88
+ // the base when no layer declares one. The migration never authors a `'...'`
89
+ // of its own: whether the entry it would expand against survives depends on
90
+ // the document-order identity resets in nx's own merge, and a spread that
91
+ // ends up with nothing to expand narrows the target to the two root files.
92
+ for (const { ref, selectedKey } of keyedPruneTargets) {
93
+ const entries = (0, target_defaults_matching_1.compatibleDefaultsEntries)(ref, selectedKey, targetDefaults);
94
+ const defaults = entries.length
95
+ ? (0, internal_1.readTargetDefaultsForTarget)(ref.targetName, { [selectedKey]: entries }, ref.matcherExecutor, { projectName: ref.projectName, projectNode: ref.projectNode })
96
+ : null;
97
+ // The effective outputs, not the target's own array: a target inheriting
98
+ // its lockfile entry, or spreading the defaults' outputs into its own, is
99
+ // still a pnpm prune target.
100
+ const effective = (0, internal_1.mergeTargetConfigurations)(ref.target, defaults ?? {});
101
+ if (!declaresPnpmLockfile(effective.outputs)) {
102
+ continue;
103
+ }
104
+ if (ref.target.inputs !== undefined) {
105
+ // A target's own array wins outright, so nothing else can carry them.
106
+ if (addRootSettingsInputs(ref.target, effective.inputs)) {
107
+ changedProjects.add(ref.projectName);
108
+ }
109
+ continue;
110
+ }
111
+ // Otherwise they belong to the entry supplying the array the target
112
+ // inherits, which is the last matching one declaring `inputs`.
113
+ const supplier = (0, target_defaults_matching_1.lastMatchingInputsSupplier)(ref, selectedKey, entries);
114
+ if (supplier) {
115
+ defaultsChanged =
116
+ addRootSettingsInputs(supplier, effective.inputs) || defaultsChanged;
117
+ }
118
+ else if (addRootSettingsInputs(ref.target, effective.inputs)) {
119
+ changedProjects.add(ref.projectName);
120
+ }
121
+ }
122
+ // A workspace configuring its prune targets purely through `targetDefaults`
123
+ // has no concrete target to carry the decision above.
124
+ if (targetDefaults && pruneTargets.length === 0) {
125
+ for (const [key, value] of Object.entries(targetDefaults)) {
126
+ const entries = Array.isArray(value) ? value : [value];
127
+ // The key qualifies only when it locates the executor without a
128
+ // concrete target to resolve against: the key itself, an entry pinning
129
+ // it, or an entry filtering on it. Every identity-compatible sibling
130
+ // still merges for such a target, an identity-neutral one included.
131
+ const compatible = entries.filter(entryIdentityCompatible);
132
+ const locatesExecutor = key === PRUNE_LOCKFILE_EXECUTOR ||
133
+ compatible.some((config) => config.executor === PRUNE_LOCKFILE_EXECUTOR ||
134
+ config.filter?.executor === PRUNE_LOCKFILE_EXECUTOR);
135
+ if (!locatesExecutor ||
136
+ !compatible.some((config) => declaresPnpmLockfile(config.outputs))) {
137
+ continue;
138
+ }
139
+ // Every compatible entry declaring `inputs` can be the array a future
140
+ // target inherits (a later filtered one replaces it for its projects),
141
+ // so each carries the sources itself; a sibling's array covers nothing
142
+ // unless a `'...'` spread pulls it in, in which case only the strictly
143
+ // filter-less earlier entries are the base it expands against: a
144
+ // filtered one supplies nothing to the targets its filter excludes,
145
+ // an executor filter failing for a target whose executor a sibling
146
+ // supplies (earlier entries are updated first, so the base is checked
147
+ // with the sources already added).
148
+ const suppliers = compatible.filter((config) => config.inputs !== undefined);
149
+ for (const [index, config] of compatible.entries()) {
150
+ if (config.inputs === undefined) {
151
+ continue;
152
+ }
153
+ const reference = config.inputs.includes('...')
154
+ ? [
155
+ ...config.inputs,
156
+ ...compatible
157
+ .slice(0, index)
158
+ .filter((earlier) => !earlier.filter)
159
+ .flatMap((earlier) => earlier.inputs ?? []),
160
+ ]
161
+ : config.inputs;
162
+ defaultsChanged =
163
+ addRootSettingsInputs(config, reference) || defaultsChanged;
164
+ }
165
+ // Targets no filtered supplier matches fall back to nx's own default
166
+ // when no filter-less entry declares an array, so the spelled-out
167
+ // default is prepended as its own entry: prepending keeps it from
168
+ // replacing a later supplier's array. A prepended carrier must match
169
+ // only targets an existing entry already matched, or the key starts
170
+ // winning selection for targets that previously fell through to their
171
+ // name or glob defaults. That holds under an executor key with a
172
+ // filter-less entry (a neutral carrier), and under a name or glob key
173
+ // with a filter-less pin (a pinned carrier, which a same-name target of
174
+ // another executor cannot inherit). An all-filtered array gets no
175
+ // fallback: any carrier would widen the key. The object form has no
176
+ // room to prepend; its single filter-less entry carries it instead.
177
+ if (!suppliers.some((supplier) => !supplier.filter)) {
178
+ const canCarry = key === PRUNE_LOCKFILE_EXECUTOR
179
+ ? compatible.some((config) => !config.filter)
180
+ : compatible.some((config) => !config.filter && config.executor === PRUNE_LOCKFILE_EXECUTOR);
181
+ if (canCarry) {
182
+ if (Array.isArray(value)) {
183
+ const fallback = key === PRUNE_LOCKFILE_EXECUTOR
184
+ ? {}
185
+ : { executor: PRUNE_LOCKFILE_EXECUTOR };
186
+ addRootSettingsInputs(fallback, []);
187
+ value.unshift(fallback);
188
+ defaultsChanged = true;
189
+ }
190
+ else {
191
+ defaultsChanged =
192
+ addRootSettingsInputs(value, []) || defaultsChanged;
193
+ }
194
+ }
195
+ }
196
+ }
197
+ }
198
+ for (const projectName of changedProjects) {
199
+ (0, devkit_1.updateProjectConfiguration)(tree, projectName, projects.get(projectName));
200
+ }
201
+ if (defaultsChanged) {
202
+ (0, devkit_1.updateNxJson)(tree, nxJson);
203
+ }
204
+ await (0, devkit_1.formatFiles)(tree);
205
+ }
206
+ function declaresPnpmLockfile(outputs) {
207
+ return pnpmLockfileEntry(outputs) !== undefined;
208
+ }
209
+ function pnpmLockfileEntry(outputs) {
210
+ return outputs?.find((output) => typeof output === 'string' &&
211
+ (output === PNPM_LOCKFILE || output.endsWith(`/${PNPM_LOCKFILE}`)));
212
+ }
213
+ /**
214
+ * Adds the workspace root files the pnpm install settings live in, and the
215
+ * pnpm major probe, skipping any the target already hashes: a `'...'` in the
216
+ * owner's array can carry one in from the layer below, where a check against
217
+ * the owner's own entries cannot see it. An absent `inputs` means nx's own
218
+ * default, so that is spelled out before adding to it rather than narrowing
219
+ * the configuration to the settings sources alone.
220
+ */
221
+ function addRootSettingsInputs(config, referenceInputs = []) {
222
+ const alreadyHashed = (0, target_defaults_matching_1.namedFilesets)(referenceInputs);
223
+ const missing = PNPM_ROOT_SETTINGS_SOURCES.filter((source) => !alreadyHashed.has(source) && !config.inputs?.includes(source));
224
+ if (!hasPnpmMajorProbe(referenceInputs) &&
225
+ !hasPnpmMajorProbe(config.inputs)) {
226
+ missing.push(pnpm_install_settings_inputs_1.PNPM_MAJOR_RUNTIME_INPUT);
227
+ }
228
+ if (missing.length === 0) {
229
+ return false;
230
+ }
231
+ config.inputs = [...(config.inputs ?? target_defaults_matching_1.DEFAULT_INPUTS), ...missing];
232
+ return true;
233
+ }
234
+ function hasPnpmMajorProbe(inputs) {
235
+ return (inputs ?? []).some((existing) => typeof existing === 'object' &&
236
+ existing !== null &&
237
+ 'runtime' in existing &&
238
+ existing.runtime === pnpm_install_settings_inputs_1.PNPM_MAJOR_RUNTIME_INPUT.runtime);
239
+ }
240
+ /**
241
+ * Whether a `targetDefaults` entry applies to `@nx/js:prune-lockfile` targets,
242
+ * mirroring nx's runtime matching: the entry's `filter` and merge-time
243
+ * payload compatibility are evaluated first, then the entry must sit under a
244
+ * prune target's selected key and match its context. Entries locating the
245
+ * executor (key, `executor` field, or `filter.executor`) match without a
246
+ * concrete target so a defaults-only workspace still gets the update. The
247
+ * in-key merge accumulation (identity replacement, outputs replacement, `...`
248
+ * spreads) is deliberately not replicated: an entry the merge ends up
249
+ * discarding for the prune target may still get the artifacts appended, which
250
+ * is additive-only for the target's own replay.
251
+ */
252
+ function appliesToPruneLockfileTargets(key, config, pruneTargets) {
253
+ if (!entryIdentityCompatible(config)) {
254
+ return false;
255
+ }
256
+ const filter = config.filter;
257
+ if (pruneTargets.length === 0) {
258
+ const locatesExecutor = key === PRUNE_LOCKFILE_EXECUTOR ||
259
+ config.executor === PRUNE_LOCKFILE_EXECUTOR ||
260
+ filter?.executor === PRUNE_LOCKFILE_EXECUTOR;
261
+ return locatesExecutor && !filter?.projects;
262
+ }
263
+ return pruneTargets.some(({ ref, selectedKey }) => selectedKey === key &&
264
+ (0, internal_1.readTargetDefaultsForTarget)(ref.targetName, { [key]: [config] }, ref.matcherExecutor, { projectName: ref.projectName, projectNode: ref.projectNode }) !== null);
265
+ }
266
+ /**
267
+ * Whether a `targetDefaults` entry can merge into an `@nx/js:prune-lockfile`
268
+ * target at all: a filter or payload pinning another executor or a command
269
+ * identity never does (isCompatibleTarget), and prune-lockfile targets are
270
+ * always explicitly configured, never inferred, so a plugin-filtered entry
271
+ * never applies either.
272
+ */
273
+ function entryIdentityCompatible(config) {
274
+ const filter = config.filter;
275
+ if (filter?.executor && filter.executor !== PRUNE_LOCKFILE_EXECUTOR) {
276
+ return false;
277
+ }
278
+ if (filter?.plugin) {
279
+ return false;
280
+ }
281
+ return !((config.executor && config.executor !== PRUNE_LOCKFILE_EXECUTOR) ||
282
+ config.command);
283
+ }
284
+ /**
285
+ * Appends the pnpm prune artifacts next to an existing pnpm-lock.yaml entry,
286
+ * reusing that entry's own path spelling. Configurations with no `outputs` or no
287
+ * pnpm lockfile entry are left alone: the lockfile entry is what identifies the
288
+ * target as pnpm's, and one without `outputs` caches nothing to begin with.
289
+ */
290
+ function appendPnpmPruneOutputs(config) {
291
+ const lockfileEntry = pnpmLockfileEntry(config.outputs);
292
+ if (!lockfileEntry) {
293
+ return false;
294
+ }
295
+ const prefix = lockfileEntry.slice(0, -PNPM_LOCKFILE.length);
296
+ let changed = false;
297
+ for (const artifact of PNPM_PRUNE_ARTIFACTS) {
298
+ const entry = `${prefix}${artifact}`;
299
+ if (!config.outputs.includes(entry)) {
300
+ config.outputs.push(entry);
301
+ changed = true;
302
+ }
303
+ }
304
+ return changed;
305
+ }