nx 22.7.0 → 23.0.0-beta.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.
- package/dist/src/config/workspace-json-project-json.d.ts +10 -0
- package/dist/src/daemon/server/handle-hash-tasks.js +1 -1
- package/dist/src/daemon/server/project-graph-incremental-recomputation.d.ts +1 -4
- package/dist/src/daemon/server/project-graph-incremental-recomputation.js +11 -20
- package/dist/src/executors/utils/convert-nx-executor.js +2 -2
- package/dist/src/hasher/create-task-hasher.js +1 -1
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/project-graph/build-project-graph.d.ts +2 -4
- package/dist/src/project-graph/build-project-graph.js +2 -7
- package/dist/src/project-graph/file-map-utils.d.ts +2 -4
- package/dist/src/project-graph/file-map-utils.js +0 -3
- package/dist/src/project-graph/plugins/get-plugins.d.ts +15 -0
- package/dist/src/project-graph/plugins/get-plugins.js +21 -3
- package/dist/src/project-graph/project-graph.js +7 -6
- package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.d.ts +40 -64
- package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.js +182 -411
- package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.d.ts +10 -4
- package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +22 -8
- package/dist/src/project-graph/utils/project-configuration/source-maps.d.ts +4 -61
- package/dist/src/project-graph/utils/project-configuration/source-maps.js +14 -59
- package/dist/src/project-graph/utils/project-configuration/target-defaults.d.ts +16 -0
- package/dist/src/project-graph/utils/project-configuration/target-defaults.js +117 -0
- package/dist/src/project-graph/utils/project-configuration/target-merging.d.ts +1 -4
- package/dist/src/project-graph/utils/project-configuration/target-merging.js +261 -136
- package/dist/src/project-graph/utils/project-configuration/target-normalization.js +0 -7
- package/dist/src/project-graph/utils/project-configuration/utils.d.ts +23 -0
- package/dist/src/project-graph/utils/project-configuration/utils.js +164 -0
- package/dist/src/project-graph/utils/project-configuration-utils.d.ts +33 -9
- package/dist/src/project-graph/utils/project-configuration-utils.js +153 -65
- package/dist/src/project-graph/utils/retrieve-workspace-files.d.ts +6 -3
- package/dist/src/project-graph/utils/retrieve-workspace-files.js +32 -13
- package/package.json +11 -11
|
@@ -5,13 +5,10 @@ exports.resolveCommandSyntacticSugar = resolveCommandSyntacticSugar;
|
|
|
5
5
|
exports.mergeMetadata = mergeMetadata;
|
|
6
6
|
exports.mergeTargetConfigurations = mergeTargetConfigurations;
|
|
7
7
|
exports.isCompatibleTarget = isCompatibleTarget;
|
|
8
|
-
exports.mergeTargetDefaultWithTargetDefinition = mergeTargetDefaultWithTargetDefinition;
|
|
9
8
|
exports.resolveNxTokensInOptions = resolveNxTokensInOptions;
|
|
10
|
-
exports.readTargetDefaultsForTarget = readTargetDefaultsForTarget;
|
|
11
9
|
const logger_1 = require("../../../utils/logger");
|
|
12
|
-
const globs_1 = require("../../../utils/globs");
|
|
13
10
|
const source_maps_1 = require("./source-maps");
|
|
14
|
-
const
|
|
11
|
+
const utils_1 = require("./utils");
|
|
15
12
|
function deepClone(obj) {
|
|
16
13
|
return structuredClone(obj);
|
|
17
14
|
}
|
|
@@ -91,40 +88,154 @@ function mergeMetadata(sourceMap, sourceInformation, baseSourceMapPath, metadata
|
|
|
91
88
|
}
|
|
92
89
|
return result;
|
|
93
90
|
}
|
|
94
|
-
function mergeOptions(newOptions, baseOptions, projectConfigSourceMap, sourceInformation, targetIdentifier) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
91
|
+
function mergeOptions(newOptions, baseOptions, projectConfigSourceMap, sourceInformation, targetIdentifier, deferSpreadsWithoutBase) {
|
|
92
|
+
// `'...'` at the options level uses object-spread semantics.
|
|
93
|
+
if (newOptions?.[utils_1.NX_SPREAD_TOKEN] === true) {
|
|
94
|
+
return (0, utils_1.getMergeValueResult)(baseOptions, newOptions, projectConfigSourceMap
|
|
95
|
+
? {
|
|
96
|
+
sourceMap: projectConfigSourceMap,
|
|
97
|
+
key: `${targetIdentifier}.options`,
|
|
98
|
+
sourceInformation,
|
|
99
|
+
}
|
|
100
|
+
: undefined, deferSpreadsWithoutBase);
|
|
101
|
+
}
|
|
102
|
+
const mergedOptionKeys = (0, utils_1.uniqueKeysInObjects)(baseOptions ?? {}, newOptions ?? {});
|
|
103
|
+
const mergedOptions = {};
|
|
104
|
+
for (const optionKey of mergedOptionKeys) {
|
|
105
|
+
mergedOptions[optionKey] = (0, utils_1.getMergeValueResult)(baseOptions ? baseOptions[optionKey] : undefined, newOptions ? newOptions[optionKey] : undefined, projectConfigSourceMap
|
|
106
|
+
? {
|
|
107
|
+
sourceMap: projectConfigSourceMap,
|
|
108
|
+
key: `${targetIdentifier}.options.${optionKey}`,
|
|
109
|
+
sourceInformation,
|
|
110
|
+
}
|
|
111
|
+
: undefined, deferSpreadsWithoutBase);
|
|
105
112
|
}
|
|
106
113
|
return mergedOptions;
|
|
107
114
|
}
|
|
108
|
-
|
|
115
|
+
// Merges a single named configuration, keyed under its own identifier
|
|
116
|
+
// (e.g. `targets.build.configurations.prod`) rather than under `.options`.
|
|
117
|
+
// Source-map correctness for the spread case is handled inside
|
|
118
|
+
// `getMergeValueResult`'s object-spread path — no post-merge fix-up needed.
|
|
119
|
+
function mergeConfigurationValue(newConfig, baseConfig, projectConfigSourceMap, sourceInformation, configIdentifier, deferSpreadsWithoutBase) {
|
|
120
|
+
if (newConfig?.[utils_1.NX_SPREAD_TOKEN] === true) {
|
|
121
|
+
return (0, utils_1.getMergeValueResult)(baseConfig, newConfig, projectConfigSourceMap && configIdentifier
|
|
122
|
+
? {
|
|
123
|
+
sourceMap: projectConfigSourceMap,
|
|
124
|
+
key: configIdentifier,
|
|
125
|
+
sourceInformation,
|
|
126
|
+
}
|
|
127
|
+
: undefined, deferSpreadsWithoutBase);
|
|
128
|
+
}
|
|
129
|
+
const mergedKeys = (0, utils_1.uniqueKeysInObjects)(baseConfig ?? {}, newConfig ?? {});
|
|
130
|
+
const merged = {};
|
|
131
|
+
for (const key of mergedKeys) {
|
|
132
|
+
merged[key] = (0, utils_1.getMergeValueResult)(baseConfig ? baseConfig[key] : undefined, newConfig ? newConfig[key] : undefined, projectConfigSourceMap && configIdentifier
|
|
133
|
+
? {
|
|
134
|
+
sourceMap: projectConfigSourceMap,
|
|
135
|
+
key: `${configIdentifier}.${key}`,
|
|
136
|
+
sourceInformation,
|
|
137
|
+
}
|
|
138
|
+
: undefined, deferSpreadsWithoutBase);
|
|
139
|
+
}
|
|
140
|
+
return merged;
|
|
141
|
+
}
|
|
142
|
+
function mergeConfigurations(newConfigurations, baseConfigurations, projectConfigSourceMap, sourceInformation, targetIdentifier, deferSpreadsWithoutBase) {
|
|
109
143
|
const mergedConfigurations = {};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
144
|
+
// Keys before '...' let base win for shared names; keys after '...'
|
|
145
|
+
// (or when there's no spread) merge normally with new winning.
|
|
146
|
+
const newKeys = Object.keys(newConfigurations ?? {});
|
|
147
|
+
const spreadPosInNew = newKeys.indexOf(utils_1.NX_SPREAD_TOKEN);
|
|
148
|
+
const hasSpread = spreadPosInNew >= 0;
|
|
149
|
+
const keysBeforeSpread = hasSpread
|
|
150
|
+
? new Set(newKeys.slice(0, spreadPosInNew))
|
|
151
|
+
: new Set();
|
|
152
|
+
// Integer-like keys get hoisted to newKeys[0], making their position
|
|
153
|
+
// relative to '...' unrecoverable.
|
|
154
|
+
if (hasSpread && newKeys[0] && utils_1.INTEGER_LIKE_KEY_PATTERN.test(newKeys[0])) {
|
|
155
|
+
throw new utils_1.IntegerLikeSpreadKeyError(newKeys[0], targetIdentifier
|
|
156
|
+
? `Configurations at "${targetIdentifier}.configurations"`
|
|
157
|
+
: 'Configurations');
|
|
158
|
+
}
|
|
159
|
+
// Preserving the unresolved `'...'` sentinel in authored position lets
|
|
160
|
+
// a later merge layer (which actually has a base) classify the keys as
|
|
161
|
+
// pre/post-spread correctly.
|
|
162
|
+
const preserveSpreadSentinel = hasSpread && deferSpreadsWithoutBase && baseConfigurations === undefined;
|
|
163
|
+
const processConfigName = (configName) => {
|
|
164
|
+
const configIdentifier = targetIdentifier
|
|
165
|
+
? `${targetIdentifier}.configurations.${configName}`
|
|
166
|
+
: undefined;
|
|
167
|
+
const baseHasConfig = configName in (baseConfigurations ?? {});
|
|
168
|
+
const newHasConfig = !!newConfigurations && configName in newConfigurations;
|
|
169
|
+
if (hasSpread && keysBeforeSpread.has(configName)) {
|
|
170
|
+
// Before '...': base wins for shared names. Keep base's source-map
|
|
171
|
+
// entries when it owns the config.
|
|
172
|
+
if (baseHasConfig) {
|
|
173
|
+
mergedConfigurations[configName] = baseConfigurations[configName];
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
mergedConfigurations[configName] = mergeConfigurationValue(newConfigurations?.[configName], undefined, projectConfigSourceMap, sourceInformation, configIdentifier, deferSpreadsWithoutBase);
|
|
177
|
+
if (projectConfigSourceMap && configIdentifier) {
|
|
178
|
+
projectConfigSourceMap[configIdentifier] = sourceInformation;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
mergedConfigurations[configName] = mergeConfigurationValue(newConfigurations?.[configName], baseConfigurations?.[configName], projectConfigSourceMap, sourceInformation, configIdentifier, deferSpreadsWithoutBase);
|
|
184
|
+
// Only reattribute the config name when the new plugin introduced it.
|
|
185
|
+
if (projectConfigSourceMap &&
|
|
186
|
+
configIdentifier &&
|
|
187
|
+
newHasConfig &&
|
|
188
|
+
!baseHasConfig) {
|
|
189
|
+
projectConfigSourceMap[configIdentifier] = sourceInformation;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
if (hasSpread) {
|
|
193
|
+
// Authored positions of new's own keys relative to `'...'` drive
|
|
194
|
+
// pre/post-spread classification, so those keys go in authored order.
|
|
195
|
+
// Base-only keys land right before `'...'` — they weren't authored by
|
|
196
|
+
// the new layer, so default semantics places them with the pre-spread
|
|
197
|
+
// keys (the "base layer" slot).
|
|
198
|
+
const baseOnlyKeys = baseConfigurations
|
|
199
|
+
? Object.keys(baseConfigurations).filter((k) => k !== utils_1.NX_SPREAD_TOKEN && !(k in (newConfigurations ?? {})))
|
|
200
|
+
: [];
|
|
201
|
+
let baseOnlyInserted = false;
|
|
202
|
+
const insertBaseOnlyKeys = () => {
|
|
203
|
+
if (baseOnlyInserted)
|
|
204
|
+
return;
|
|
205
|
+
baseOnlyInserted = true;
|
|
206
|
+
for (const configName of baseOnlyKeys)
|
|
207
|
+
processConfigName(configName);
|
|
118
208
|
};
|
|
209
|
+
for (const configName of newKeys) {
|
|
210
|
+
if (configName === utils_1.NX_SPREAD_TOKEN) {
|
|
211
|
+
insertBaseOnlyKeys();
|
|
212
|
+
if (preserveSpreadSentinel) {
|
|
213
|
+
mergedConfigurations[utils_1.NX_SPREAD_TOKEN] =
|
|
214
|
+
true;
|
|
215
|
+
}
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
processConfigName(configName);
|
|
219
|
+
}
|
|
220
|
+
insertBaseOnlyKeys();
|
|
119
221
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
for (const
|
|
125
|
-
|
|
222
|
+
else {
|
|
223
|
+
// No spread — classic `{ ...base, ...new }` ordering: base keys
|
|
224
|
+
// first, new-only keys after. Shared configs stay at base's position.
|
|
225
|
+
if (baseConfigurations) {
|
|
226
|
+
for (const configName of Object.keys(baseConfigurations)) {
|
|
227
|
+
if (configName === utils_1.NX_SPREAD_TOKEN)
|
|
228
|
+
continue;
|
|
229
|
+
processConfigName(configName);
|
|
126
230
|
}
|
|
127
231
|
}
|
|
232
|
+
for (const configName of newKeys) {
|
|
233
|
+
if (configName === utils_1.NX_SPREAD_TOKEN)
|
|
234
|
+
continue;
|
|
235
|
+
if (configName in mergedConfigurations)
|
|
236
|
+
continue;
|
|
237
|
+
processConfigName(configName);
|
|
238
|
+
}
|
|
128
239
|
}
|
|
129
240
|
return mergedConfigurations;
|
|
130
241
|
}
|
|
@@ -141,7 +252,7 @@ function mergeConfigurations(newConfigurations, baseConfigurations, projectConfi
|
|
|
141
252
|
* @param targetIdentifier The identifier for the target to merge, used for source map
|
|
142
253
|
* @returns A merged target configuration
|
|
143
254
|
*/
|
|
144
|
-
function mergeTargetConfigurations(target, baseTarget, projectConfigSourceMap, sourceInformation, targetIdentifier) {
|
|
255
|
+
function mergeTargetConfigurations(target, baseTarget, projectConfigSourceMap, sourceInformation, targetIdentifier, deferSpreadsWithoutBase) {
|
|
145
256
|
const { configurations: defaultConfigurations, options: defaultOptions, ...baseTargetProperties } = baseTarget ?? {};
|
|
146
257
|
// Target is "compatible", e.g. executor is defined only once or is the same
|
|
147
258
|
// in both places. This means that it is likely safe to merge
|
|
@@ -156,28 +267,136 @@ function mergeTargetConfigurations(target, baseTarget, projectConfigSourceMap, s
|
|
|
156
267
|
}
|
|
157
268
|
}
|
|
158
269
|
// merge top level properties if they're compatible
|
|
159
|
-
const result = {
|
|
160
|
-
|
|
161
|
-
|
|
270
|
+
const result = {};
|
|
271
|
+
const mergeBase = isCompatible ? baseTargetProperties : {};
|
|
272
|
+
// Keys before '...' let base win; keys after '...' (or when there's no
|
|
273
|
+
// spread) merge normally with target winning.
|
|
274
|
+
const targetKeys = Object.keys(target);
|
|
275
|
+
const spreadPosInTarget = targetKeys.indexOf(utils_1.NX_SPREAD_TOKEN);
|
|
276
|
+
const hasSpread = isCompatible && spreadPosInTarget >= 0;
|
|
277
|
+
const keysBeforeSpread = hasSpread
|
|
278
|
+
? new Set(targetKeys.slice(0, spreadPosInTarget))
|
|
279
|
+
: new Set();
|
|
280
|
+
// Integer-like keys get hoisted to targetKeys[0], making their position
|
|
281
|
+
// relative to '...' unrecoverable.
|
|
282
|
+
if (hasSpread &&
|
|
283
|
+
targetKeys[0] &&
|
|
284
|
+
utils_1.INTEGER_LIKE_KEY_PATTERN.test(targetKeys[0])) {
|
|
285
|
+
throw new utils_1.IntegerLikeSpreadKeyError(targetKeys[0], targetIdentifier ? `Target at "${targetIdentifier}"` : 'Target');
|
|
286
|
+
}
|
|
287
|
+
// Preserving the unresolved `'...'` sentinel in authored position lets a
|
|
288
|
+
// later merge layer (which actually has a base) classify sibling keys as
|
|
289
|
+
// pre/post-spread correctly.
|
|
290
|
+
const preserveSpreadSentinel = spreadPosInTarget >= 0 &&
|
|
291
|
+
deferSpreadsWithoutBase &&
|
|
292
|
+
baseTarget === undefined;
|
|
293
|
+
const skipForOwnMerge = new Set([
|
|
294
|
+
'options',
|
|
295
|
+
'configurations',
|
|
296
|
+
utils_1.NX_SPREAD_TOKEN,
|
|
297
|
+
]);
|
|
298
|
+
const processKey = (key) => {
|
|
299
|
+
if (skipForOwnMerge.has(key))
|
|
300
|
+
return;
|
|
301
|
+
if (hasSpread && keysBeforeSpread.has(key)) {
|
|
302
|
+
// Before '...': base wins; fall through to target only if base lacks it.
|
|
303
|
+
result[key] =
|
|
304
|
+
key in mergeBase
|
|
305
|
+
? mergeBase[key]
|
|
306
|
+
: (0, utils_1.getMergeValueResult)(undefined, target[key], projectConfigSourceMap
|
|
307
|
+
? {
|
|
308
|
+
sourceMap: projectConfigSourceMap,
|
|
309
|
+
key: `${targetIdentifier}.${key}`,
|
|
310
|
+
sourceInformation,
|
|
311
|
+
}
|
|
312
|
+
: undefined, deferSpreadsWithoutBase);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (key in target) {
|
|
316
|
+
result[key] = (0, utils_1.getMergeValueResult)(mergeBase[key], target[key], projectConfigSourceMap
|
|
317
|
+
? {
|
|
318
|
+
sourceMap: projectConfigSourceMap,
|
|
319
|
+
key: `${targetIdentifier}.${key}`,
|
|
320
|
+
sourceInformation,
|
|
321
|
+
}
|
|
322
|
+
: undefined, deferSpreadsWithoutBase);
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
result[key] = mergeBase[key];
|
|
326
|
+
}
|
|
162
327
|
};
|
|
163
|
-
|
|
328
|
+
if (isCompatible) {
|
|
329
|
+
if (hasSpread) {
|
|
330
|
+
// Authored positions of the target's own keys relative to `'...'`
|
|
331
|
+
// drive pre/post-spread classification, so those keys go in
|
|
332
|
+
// authored order. Base-only keys land right before `'...'` — they
|
|
333
|
+
// weren't authored, so default semantics ("base layer that yields
|
|
334
|
+
// to a higher-priority layer") places them with the rest of the
|
|
335
|
+
// pre-spread keys.
|
|
336
|
+
const baseOnlyKeys = Object.keys(baseTargetProperties).filter((k) => !skipForOwnMerge.has(k) && !(k in target));
|
|
337
|
+
let baseOnlyInserted = false;
|
|
338
|
+
const insertBaseOnlyKeys = () => {
|
|
339
|
+
if (baseOnlyInserted)
|
|
340
|
+
return;
|
|
341
|
+
baseOnlyInserted = true;
|
|
342
|
+
for (const key of baseOnlyKeys)
|
|
343
|
+
processKey(key);
|
|
344
|
+
};
|
|
345
|
+
for (const key of targetKeys) {
|
|
346
|
+
if (key === utils_1.NX_SPREAD_TOKEN) {
|
|
347
|
+
insertBaseOnlyKeys();
|
|
348
|
+
if (preserveSpreadSentinel) {
|
|
349
|
+
result[utils_1.NX_SPREAD_TOKEN] = true;
|
|
350
|
+
}
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (skipForOwnMerge.has(key))
|
|
354
|
+
continue;
|
|
355
|
+
processKey(key);
|
|
356
|
+
}
|
|
357
|
+
// Safety for a sentinel-less iteration (shouldn't happen when
|
|
358
|
+
// hasSpread is true, but keeps the base-only keys emitted).
|
|
359
|
+
insertBaseOnlyKeys();
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
// No spread — classic `{ ...base, ...target }` ordering: base keys
|
|
363
|
+
// first (preserving their own-key order), target-only keys after.
|
|
364
|
+
// Shared keys stay at base's position with per-key merged value.
|
|
365
|
+
const mergedKeys = (0, utils_1.uniqueKeysInObjects)(baseTargetProperties, target);
|
|
366
|
+
for (const key of mergedKeys) {
|
|
367
|
+
if (skipForOwnMerge.has(key))
|
|
368
|
+
continue;
|
|
369
|
+
processKey(key);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
for (const key of targetKeys) {
|
|
375
|
+
if (skipForOwnMerge.has(key))
|
|
376
|
+
continue;
|
|
377
|
+
processKey(key);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// Update source map once after loop
|
|
164
381
|
if (projectConfigSourceMap) {
|
|
165
382
|
projectConfigSourceMap[targetIdentifier] = sourceInformation;
|
|
166
|
-
// record root level target properties to source map
|
|
167
|
-
for (const targetProperty in target) {
|
|
168
|
-
const targetPropertyId = `${targetIdentifier}.${targetProperty}`;
|
|
169
|
-
projectConfigSourceMap[targetPropertyId] = sourceInformation;
|
|
170
|
-
}
|
|
171
383
|
}
|
|
172
384
|
// merge options if there are any
|
|
173
385
|
// if the targets aren't compatible, we simply discard the old options during the merge
|
|
174
386
|
if (target.options || defaultOptions) {
|
|
175
|
-
result.options = mergeOptions(target.options, isCompatible ? defaultOptions : undefined, projectConfigSourceMap, sourceInformation, targetIdentifier);
|
|
387
|
+
result.options = mergeOptions(target.options, isCompatible ? defaultOptions : undefined, projectConfigSourceMap, sourceInformation, targetIdentifier, deferSpreadsWithoutBase);
|
|
388
|
+
if (projectConfigSourceMap && target.options) {
|
|
389
|
+
projectConfigSourceMap[`${targetIdentifier}.options`] = sourceInformation;
|
|
390
|
+
}
|
|
176
391
|
}
|
|
177
392
|
// merge configurations if there are any
|
|
178
393
|
// if the targets aren't compatible, we simply discard the old configurations during the merge
|
|
179
394
|
if (target.configurations || defaultConfigurations) {
|
|
180
|
-
result.configurations = mergeConfigurations(target.configurations, isCompatible ? defaultConfigurations : undefined, projectConfigSourceMap, sourceInformation, targetIdentifier);
|
|
395
|
+
result.configurations = mergeConfigurations(target.configurations, isCompatible ? defaultConfigurations : undefined, projectConfigSourceMap, sourceInformation, targetIdentifier, deferSpreadsWithoutBase);
|
|
396
|
+
if (projectConfigSourceMap && target.configurations) {
|
|
397
|
+
projectConfigSourceMap[`${targetIdentifier}.configurations`] =
|
|
398
|
+
sourceInformation;
|
|
399
|
+
}
|
|
181
400
|
}
|
|
182
401
|
if (target.metadata) {
|
|
183
402
|
result.metadata = mergeMetadata(projectConfigSourceMap, sourceInformation, `${targetIdentifier}.metadata`, target.metadata, baseTarget?.metadata);
|
|
@@ -216,73 +435,6 @@ function isCompatibleTarget(a, b) {
|
|
|
216
435
|
}
|
|
217
436
|
return true;
|
|
218
437
|
}
|
|
219
|
-
function targetDefaultShouldBeApplied(key, sourceMap) {
|
|
220
|
-
const sourceInfo = sourceMap[key];
|
|
221
|
-
if (!sourceInfo) {
|
|
222
|
-
return true;
|
|
223
|
-
}
|
|
224
|
-
// The defined value of the target is from a plugin that
|
|
225
|
-
// isn't part of Nx's core plugins, so target defaults are
|
|
226
|
-
// applied on top of it.
|
|
227
|
-
const [, plugin] = sourceInfo;
|
|
228
|
-
return !plugin?.startsWith('nx/');
|
|
229
|
-
}
|
|
230
|
-
function mergeTargetDefaultWithTargetDefinition(targetName, project, targetDefault, sourceMap) {
|
|
231
|
-
const targetDefinition = project.targets[targetName] ?? {};
|
|
232
|
-
const result = deepClone(targetDefinition);
|
|
233
|
-
for (const key in targetDefault) {
|
|
234
|
-
switch (key) {
|
|
235
|
-
case 'options': {
|
|
236
|
-
const normalizedDefaults = resolveNxTokensInOptions(targetDefault.options, project, targetName);
|
|
237
|
-
for (const optionKey in normalizedDefaults) {
|
|
238
|
-
const sourceMapKey = (0, source_maps_1.targetOptionSourceMapKey)(targetName, optionKey);
|
|
239
|
-
if (targetDefinition.options[optionKey] === undefined ||
|
|
240
|
-
targetDefaultShouldBeApplied(sourceMapKey, sourceMap)) {
|
|
241
|
-
result.options[optionKey] = targetDefault.options[optionKey];
|
|
242
|
-
sourceMap[sourceMapKey] = ['nx.json', 'nx/target-defaults'];
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
break;
|
|
246
|
-
}
|
|
247
|
-
case 'configurations': {
|
|
248
|
-
if (!result.configurations) {
|
|
249
|
-
result.configurations = {};
|
|
250
|
-
sourceMap[(0, source_maps_1.targetConfigurationsSourceMapKey)(targetName)] = [
|
|
251
|
-
'nx.json',
|
|
252
|
-
'nx/target-defaults',
|
|
253
|
-
];
|
|
254
|
-
}
|
|
255
|
-
for (const configuration in targetDefault.configurations) {
|
|
256
|
-
if (!result.configurations[configuration]) {
|
|
257
|
-
result.configurations[configuration] = {};
|
|
258
|
-
sourceMap[(0, source_maps_1.targetConfigurationsSourceMapKey)(targetName, configuration)] = ['nx.json', 'nx/target-defaults'];
|
|
259
|
-
}
|
|
260
|
-
const normalizedConfigurationDefaults = resolveNxTokensInOptions(targetDefault.configurations[configuration], project, targetName);
|
|
261
|
-
for (const configurationKey in normalizedConfigurationDefaults) {
|
|
262
|
-
const sourceMapKey = (0, source_maps_1.targetConfigurationsSourceMapKey)(targetName, configuration, configurationKey);
|
|
263
|
-
if (targetDefinition.configurations?.[configuration]?.[configurationKey] === undefined ||
|
|
264
|
-
targetDefaultShouldBeApplied(sourceMapKey, sourceMap)) {
|
|
265
|
-
result.configurations[configuration][configurationKey] =
|
|
266
|
-
targetDefault.configurations[configuration][configurationKey];
|
|
267
|
-
sourceMap[sourceMapKey] = ['nx.json', 'nx/target-defaults'];
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
break;
|
|
272
|
-
}
|
|
273
|
-
default: {
|
|
274
|
-
const sourceMapKey = `targets.${targetName}.${key}`;
|
|
275
|
-
if (targetDefinition[key] === undefined ||
|
|
276
|
-
targetDefaultShouldBeApplied(sourceMapKey, sourceMap)) {
|
|
277
|
-
result[key] = targetDefault[key];
|
|
278
|
-
sourceMap[sourceMapKey] = ['nx.json', 'nx/target-defaults'];
|
|
279
|
-
}
|
|
280
|
-
break;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
return result;
|
|
285
|
-
}
|
|
286
438
|
function resolveNxTokensInOptions(object, project, key) {
|
|
287
439
|
const result = Array.isArray(object) ? [...object] : { ...object };
|
|
288
440
|
for (let [opt, value] of Object.entries(object ?? {})) {
|
|
@@ -303,30 +455,3 @@ function resolveNxTokensInOptions(object, project, key) {
|
|
|
303
455
|
}
|
|
304
456
|
return result;
|
|
305
457
|
}
|
|
306
|
-
function readTargetDefaultsForTarget(targetName, targetDefaults, executor) {
|
|
307
|
-
if (executor && targetDefaults?.[executor]) {
|
|
308
|
-
// If an executor is defined in project.json, defaults should be read
|
|
309
|
-
// from the most specific key that matches that executor.
|
|
310
|
-
// e.g. If executor === run-commands, and the target is named build:
|
|
311
|
-
// Use, use nx:run-commands if it is present
|
|
312
|
-
// If not, use build if it is present.
|
|
313
|
-
return targetDefaults?.[executor];
|
|
314
|
-
}
|
|
315
|
-
else if (targetDefaults?.[targetName]) {
|
|
316
|
-
// If the executor is not defined, the only key we have is the target name.
|
|
317
|
-
return targetDefaults?.[targetName];
|
|
318
|
-
}
|
|
319
|
-
let matchingTargetDefaultKey = null;
|
|
320
|
-
for (const key in targetDefaults ?? {}) {
|
|
321
|
-
if ((0, globs_1.isGlobPattern)(key) && (0, minimatch_1.minimatch)(targetName, key)) {
|
|
322
|
-
if (!matchingTargetDefaultKey ||
|
|
323
|
-
matchingTargetDefaultKey.length < key.length) {
|
|
324
|
-
matchingTargetDefaultKey = key;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
if (matchingTargetDefaultKey) {
|
|
329
|
-
return targetDefaults[matchingTargetDefaultKey];
|
|
330
|
-
}
|
|
331
|
-
return null;
|
|
332
|
-
}
|
|
@@ -73,13 +73,6 @@ projects) {
|
|
|
73
73
|
const targetErrorMessage = [];
|
|
74
74
|
for (const targetName in project.targets) {
|
|
75
75
|
project.targets[targetName] = normalizeTarget(project.targets[targetName], project, workspaceRoot, projects, [project.root, targetName].join(':'));
|
|
76
|
-
const projectSourceMaps = sourceMaps[project.root];
|
|
77
|
-
const targetConfig = project.targets[targetName];
|
|
78
|
-
const targetDefaults = (0, target_merging_1.deepClone)((0, target_merging_1.readTargetDefaultsForTarget)(targetName, nxJsonConfiguration.targetDefaults, targetConfig.executor));
|
|
79
|
-
// We only apply defaults if they exist
|
|
80
|
-
if (targetDefaults && (0, target_merging_1.isCompatibleTarget)(targetConfig, targetDefaults)) {
|
|
81
|
-
project.targets[targetName] = (0, target_merging_1.mergeTargetDefaultWithTargetDefinition)(targetName, project, normalizeTarget(targetDefaults, project, workspaceRoot, projects, ['nx.json[targetDefaults]', targetName].join(':')), projectSourceMaps);
|
|
82
|
-
}
|
|
83
76
|
const target = project.targets[targetName];
|
|
84
77
|
if (
|
|
85
78
|
// If the target has no executor or command, it doesn't do anything
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type SourceInformation } from './source-maps';
|
|
2
|
+
export declare const NX_SPREAD_TOKEN = "...";
|
|
3
|
+
/**
|
|
4
|
+
* Returns the union of keys across every provided object.
|
|
5
|
+
*/
|
|
6
|
+
export declare function uniqueKeysInObjects(...objs: Array<object | null | undefined>): Set<string>;
|
|
7
|
+
export declare const INTEGER_LIKE_KEY_PATTERN: RegExp;
|
|
8
|
+
export declare class IntegerLikeSpreadKeyError extends Error {
|
|
9
|
+
constructor(key: string, context: string);
|
|
10
|
+
}
|
|
11
|
+
type SourceMapContext = {
|
|
12
|
+
sourceMap: Record<string, SourceInformation>;
|
|
13
|
+
key: string;
|
|
14
|
+
sourceInformation: SourceInformation;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* `"..."` in `newValue` (as an array element or a key set to `true`)
|
|
18
|
+
* expands the base at that position; otherwise `newValue` replaces
|
|
19
|
+
* `baseValue`. With `deferSpreadsWithoutBase`, an unresolvable spread is
|
|
20
|
+
* preserved so a later merge layer can expand it.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getMergeValueResult<T>(baseValue: unknown, newValue: T | undefined, sourceMapContext?: SourceMapContext, deferSpreadsWithoutBase?: boolean): T | undefined;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IntegerLikeSpreadKeyError = exports.INTEGER_LIKE_KEY_PATTERN = exports.NX_SPREAD_TOKEN = void 0;
|
|
4
|
+
exports.uniqueKeysInObjects = uniqueKeysInObjects;
|
|
5
|
+
exports.getMergeValueResult = getMergeValueResult;
|
|
6
|
+
const source_maps_1 = require("./source-maps");
|
|
7
|
+
exports.NX_SPREAD_TOKEN = '...';
|
|
8
|
+
/**
|
|
9
|
+
* Returns the union of keys across every provided object.
|
|
10
|
+
*/
|
|
11
|
+
function uniqueKeysInObjects(...objs) {
|
|
12
|
+
const keys = new Set();
|
|
13
|
+
for (const obj of objs) {
|
|
14
|
+
if (obj) {
|
|
15
|
+
for (const key of Object.keys(obj)) {
|
|
16
|
+
keys.add(key);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return keys;
|
|
21
|
+
}
|
|
22
|
+
// Integer-like string keys (`"0"`, `"42"`) are enumerated before
|
|
23
|
+
// insertion-order keys, so we can't tell if they were authored before or
|
|
24
|
+
// after `'...'`. Spread sites reject them instead of guessing.
|
|
25
|
+
exports.INTEGER_LIKE_KEY_PATTERN = /^(0|[1-9]\d*)$/;
|
|
26
|
+
class IntegerLikeSpreadKeyError extends Error {
|
|
27
|
+
constructor(key, context) {
|
|
28
|
+
super(`${context} uses an integer-like key (${JSON.stringify(key)}) alongside the '...' spread token. Integer-like keys are enumerated before other keys regardless of authored order, so their position relative to '...' is ambiguous. Rename the key (e.g. add a non-numeric prefix) or restructure the object.`);
|
|
29
|
+
this.name = 'IntegerLikeSpreadKeyError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.IntegerLikeSpreadKeyError = IntegerLikeSpreadKeyError;
|
|
33
|
+
/**
|
|
34
|
+
* `"..."` in `newValue` (as an array element or a key set to `true`)
|
|
35
|
+
* expands the base at that position; otherwise `newValue` replaces
|
|
36
|
+
* `baseValue`. With `deferSpreadsWithoutBase`, an unresolvable spread is
|
|
37
|
+
* preserved so a later merge layer can expand it.
|
|
38
|
+
*/
|
|
39
|
+
function getMergeValueResult(baseValue, newValue, sourceMapContext, deferSpreadsWithoutBase) {
|
|
40
|
+
if (newValue === undefined && baseValue !== undefined) {
|
|
41
|
+
return baseValue;
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(newValue)) {
|
|
44
|
+
return mergeArrayValue(baseValue, newValue, sourceMapContext, deferSpreadsWithoutBase);
|
|
45
|
+
}
|
|
46
|
+
if (isObject(newValue) && newValue[exports.NX_SPREAD_TOKEN] === true) {
|
|
47
|
+
return mergeObjectWithSpread(baseValue, newValue, sourceMapContext, deferSpreadsWithoutBase);
|
|
48
|
+
}
|
|
49
|
+
// Scalar / null / plain object replace — newValue fully wins.
|
|
50
|
+
writeTopLevelSourceMap(sourceMapContext);
|
|
51
|
+
return newValue;
|
|
52
|
+
}
|
|
53
|
+
function mergeArrayValue(baseValue, newValue, sourceMapContext, deferSpreadsWithoutBase) {
|
|
54
|
+
const newSpreadIndex = newValue.findIndex((v) => v === exports.NX_SPREAD_TOKEN);
|
|
55
|
+
if (newSpreadIndex === -1) {
|
|
56
|
+
// No spread: newValue replaces baseValue entirely.
|
|
57
|
+
if (sourceMapContext) {
|
|
58
|
+
for (let i = 0; i < newValue.length; i++) {
|
|
59
|
+
sourceMapContext.sourceMap[`${sourceMapContext.key}.${i}`] =
|
|
60
|
+
sourceMapContext.sourceInformation;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
writeTopLevelSourceMap(sourceMapContext);
|
|
64
|
+
return newValue;
|
|
65
|
+
}
|
|
66
|
+
const baseArray = Array.isArray(baseValue) ? baseValue : [];
|
|
67
|
+
// Snapshot per-index base sources before we start writing — the loop
|
|
68
|
+
// writes into the same `${key}.${i}` entries it needs to read back when
|
|
69
|
+
// the spread expands. Unlike object spread, array spreads can overwrite
|
|
70
|
+
// indices during their own expansion (when new authors a prefix before
|
|
71
|
+
// `'...'`), so lazy capture isn't sufficient here.
|
|
72
|
+
const basePerIndexSources = sourceMapContext
|
|
73
|
+
? baseArray.map((_, i) => (0, source_maps_1.readArrayItemSourceInfo)(sourceMapContext.sourceMap, sourceMapContext.key, i))
|
|
74
|
+
: [];
|
|
75
|
+
const result = [];
|
|
76
|
+
const recordAt = (resultIdx, info) => {
|
|
77
|
+
if (sourceMapContext && info) {
|
|
78
|
+
sourceMapContext.sourceMap[`${sourceMapContext.key}.${resultIdx}`] = info;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
for (let newValueIndex = 0; newValueIndex < newValue.length; newValueIndex++) {
|
|
82
|
+
const element = newValue[newValueIndex];
|
|
83
|
+
if (element === exports.NX_SPREAD_TOKEN) {
|
|
84
|
+
if (deferSpreadsWithoutBase && baseValue === undefined) {
|
|
85
|
+
recordAt(result.length, sourceMapContext?.sourceInformation);
|
|
86
|
+
result.push(exports.NX_SPREAD_TOKEN);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
for (let baseIndex = 0; baseIndex < baseArray.length; baseIndex++) {
|
|
90
|
+
recordAt(result.length, basePerIndexSources[baseIndex]);
|
|
91
|
+
result.push(baseArray[baseIndex]);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
recordAt(result.length, sourceMapContext?.sourceInformation);
|
|
97
|
+
result.push(element);
|
|
98
|
+
}
|
|
99
|
+
writeTopLevelSourceMap(sourceMapContext);
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
function mergeObjectWithSpread(baseValue, newValue, sourceMapContext, deferSpreadsWithoutBase) {
|
|
103
|
+
const baseObj = isObject(baseValue) ? baseValue : {};
|
|
104
|
+
const result = {};
|
|
105
|
+
const errorContext = sourceMapContext?.key
|
|
106
|
+
? `Object at "${sourceMapContext.key}"`
|
|
107
|
+
: 'Object';
|
|
108
|
+
const newKeys = Object.keys(newValue);
|
|
109
|
+
// Integer-like keys are hoisted to the front of enumeration, so if one
|
|
110
|
+
// exists alongside `'...'` it must be newKeys[0].
|
|
111
|
+
if (newKeys[0] && exports.INTEGER_LIKE_KEY_PATTERN.test(newKeys[0])) {
|
|
112
|
+
throw new IntegerLikeSpreadKeyError(newKeys[0], errorContext);
|
|
113
|
+
}
|
|
114
|
+
// Base per-key sources captured lazily — only for shared keys the new
|
|
115
|
+
// object overwrites before `'...'`, since writing their new source
|
|
116
|
+
// clobbers the base entry the spread will need to restore.
|
|
117
|
+
const capturedBaseSources = {};
|
|
118
|
+
for (const newKey of newKeys) {
|
|
119
|
+
if (newKey === exports.NX_SPREAD_TOKEN) {
|
|
120
|
+
if (deferSpreadsWithoutBase && baseValue === undefined) {
|
|
121
|
+
// Keep the sentinel for a later merge layer to resolve.
|
|
122
|
+
result[exports.NX_SPREAD_TOKEN] = true;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
for (const baseKey of Object.keys(baseObj)) {
|
|
126
|
+
result[baseKey] = baseObj[baseKey];
|
|
127
|
+
if (sourceMapContext) {
|
|
128
|
+
// If we captured a shared key pre-spread, use that; otherwise
|
|
129
|
+
// the source map still holds the base entry untouched.
|
|
130
|
+
const baseSource = Object.prototype.hasOwnProperty.call(capturedBaseSources, baseKey)
|
|
131
|
+
? capturedBaseSources[baseKey]
|
|
132
|
+
: (0, source_maps_1.readObjectPropertySourceInfo)(sourceMapContext.sourceMap, sourceMapContext.key, baseKey);
|
|
133
|
+
if (baseSource) {
|
|
134
|
+
sourceMapContext.sourceMap[`${sourceMapContext.key}.${baseKey}`] =
|
|
135
|
+
baseSource;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
// About to overwrite a base key's source — capture it first so the
|
|
142
|
+
// spread can restore it.
|
|
143
|
+
if (sourceMapContext &&
|
|
144
|
+
newKey in baseObj &&
|
|
145
|
+
!Object.prototype.hasOwnProperty.call(capturedBaseSources, newKey)) {
|
|
146
|
+
capturedBaseSources[newKey] = (0, source_maps_1.readObjectPropertySourceInfo)(sourceMapContext.sourceMap, sourceMapContext.key, newKey);
|
|
147
|
+
}
|
|
148
|
+
result[newKey] = newValue[newKey];
|
|
149
|
+
if (sourceMapContext) {
|
|
150
|
+
sourceMapContext.sourceMap[`${sourceMapContext.key}.${newKey}`] =
|
|
151
|
+
sourceMapContext.sourceInformation;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
writeTopLevelSourceMap(sourceMapContext);
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
function writeTopLevelSourceMap(ctx) {
|
|
158
|
+
if (ctx) {
|
|
159
|
+
ctx.sourceMap[ctx.key] = ctx.sourceInformation;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function isObject(value) {
|
|
163
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
164
|
+
}
|