@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.
- package/dist/internal.d.ts +9 -3
- package/dist/internal.js +28 -17
- package/dist/src/executors/copy-workspace-modules/copy-workspace-modules.js +122 -21
- package/dist/src/executors/node/lib/output-file.js +4 -4
- package/dist/src/executors/node/node.impl.js +9 -10
- package/dist/src/executors/prune-lockfile/prune-lockfile.d.ts +1 -1
- package/dist/src/executors/prune-lockfile/prune-lockfile.js +40 -33
- package/dist/src/executors/tsc/lib/batch/watch.js +3 -3
- package/dist/src/executors/tsc/tsc.batch-impl.d.ts +1 -1
- package/dist/src/executors/tsc/tsc.batch-impl.js +2 -2
- package/dist/src/executors/verdaccio/verdaccio.impl.js +8 -1
- package/dist/src/generators/init/init.js +32 -15
- package/dist/src/generators/init/schema.d.ts +1 -1
- package/dist/src/generators/init/schema.json +1 -1
- package/dist/src/generators/library/library.d.ts +1 -1
- package/dist/src/generators/library/library.js +34 -20
- package/dist/src/generators/library/schema.d.ts +11 -3
- package/dist/src/generators/library/schema.json +15 -4
- package/dist/src/generators/library/utils/add-release-config.js +2 -2
- package/dist/src/generators/setup-build/generator.js +2 -7
- package/dist/src/generators/typescript-sync/typescript-sync.d.ts +1 -1
- package/dist/src/generators/typescript-sync/typescript-sync.js +2 -2
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.js +8 -8
- package/dist/src/migrations/update-23-1-0/add-ignore-deprecations-for-ts6.d.ts +3 -2
- package/dist/src/migrations/update-23-1-0/add-ignore-deprecations-for-ts6.js +17 -11
- package/dist/src/migrations/update-23-2-0/add-pnpm-deploy-output-cache-inputs.d.ts +14 -0
- package/dist/src/migrations/update-23-2-0/add-pnpm-deploy-output-cache-inputs.js +242 -0
- package/dist/src/migrations/update-23-2-0/add-pnpm-deploy-output-cache-inputs.md +64 -0
- package/dist/src/migrations/update-23-2-0/add-pnpm-prune-lockfile-cache-config.d.ts +2 -0
- package/dist/src/migrations/update-23-2-0/add-pnpm-prune-lockfile-cache-config.js +305 -0
- package/dist/src/migrations/update-23-2-0/add-pnpm-prune-lockfile-cache-config.md +59 -0
- package/dist/src/plugins/typescript/plugin.js +9 -14
- package/dist/src/plugins/typescript/util.d.ts +1 -1
- package/dist/src/release/utils/update-lock-file.js +5 -7
- package/dist/src/release/version-actions.d.ts +12 -3
- package/dist/src/release/version-actions.js +236 -76
- package/dist/src/utils/add-linting-to-project.d.ts +46 -0
- package/dist/src/utils/add-linting-to-project.js +112 -0
- package/dist/src/utils/assets/copy-assets-handler.d.ts +1 -1
- package/dist/src/utils/assets/copy-assets-handler.js +2 -2
- package/dist/src/utils/buildable-libs-utils.js +10 -13
- package/dist/src/utils/find-npm-dependencies.js +7 -10
- package/dist/src/utils/formatter-setup.d.ts +26 -0
- package/dist/src/utils/formatter-setup.js +43 -0
- package/dist/src/utils/generate-globs.js +11 -14
- package/dist/src/utils/generator-prompts.d.ts +2 -1
- package/dist/src/utils/generator-prompts.js +23 -22
- package/dist/src/utils/get-main-file-dir.js +2 -2
- package/dist/src/utils/linter.d.ts +30 -0
- package/dist/src/utils/linter.js +43 -0
- package/dist/src/utils/npm-config.d.ts +1 -1
- package/dist/src/utils/nx-formatter-internals.d.ts +8 -0
- package/dist/src/utils/nx-formatter-internals.js +18 -0
- package/dist/src/utils/oxfmt.d.ts +4 -0
- package/dist/src/utils/oxfmt.js +35 -0
- package/dist/src/utils/package-json/update-package-json.d.ts +1 -1
- package/dist/src/utils/package-json/update-package-json.js +11 -22
- package/dist/src/utils/package-manager-workspaces.js +2 -2
- package/dist/src/utils/pnpm-deploy-output-cache-inputs.d.ts +29 -0
- package/dist/src/utils/pnpm-deploy-output-cache-inputs.js +177 -0
- package/dist/src/utils/pnpm-install-settings-inputs.d.ts +45 -0
- package/dist/src/utils/pnpm-install-settings-inputs.js +77 -0
- package/dist/src/utils/prettier.js +14 -33
- package/dist/src/utils/target-defaults-matching.d.ts +57 -0
- package/dist/src/utils/target-defaults-matching.js +126 -0
- package/dist/src/utils/typescript/configuration.js +3 -5
- package/dist/src/utils/typescript/create-ts-config.d.ts +1 -1
- package/dist/src/utils/typescript/create-ts-config.js +5 -5
- package/dist/src/utils/typescript/plugin.js +2 -2
- package/dist/src/utils/typescript/run-type-check.js +2 -2
- package/dist/src/utils/typescript/ts-config.d.ts +51 -1
- package/dist/src/utils/typescript/ts-config.js +114 -0
- package/dist/src/utils/typescript/ts-solution-setup.d.ts +1 -1
- package/dist/src/utils/typescript/ts-solution-setup.js +3 -3
- package/dist/src/utils/versions.d.ts +1 -0
- package/dist/src/utils/versions.js +2 -1
- package/dist/src/utils/watch-for-single-file-changes.js +2 -2
- package/dist/src/utils/workspace-module-sections.d.ts +16 -0
- package/dist/src/utils/workspace-module-sections.js +24 -0
- package/migrations.json +13 -1
- 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,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
|
+
}
|