nx 23.1.0-beta.2 → 23.1.0-beta.4
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/command-line/migrate/migrate.d.ts +2 -0
- package/dist/src/command-line/migrate/migrate.js +32 -6
- package/dist/src/daemon/server/project-graph-incremental-recomputation.js +30 -11
- package/dist/src/native/index.d.ts +48 -1
- package/dist/src/native/native-bindings.js +1 -0
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/plugins/js/utils/register.d.ts +31 -10
- package/dist/src/plugins/js/utils/register.js +87 -18
- package/dist/src/tasks-runner/default-tasks-runner.js +6 -3
- package/dist/src/tasks-runner/init-tasks-runner.js +1 -1
- package/dist/src/tasks-runner/life-cycle.d.ts +10 -5
- package/dist/src/tasks-runner/life-cycle.js +4 -4
- package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +156 -0
- package/dist/src/tasks-runner/life-cycles/performance-analysis.js +486 -0
- package/dist/src/tasks-runner/life-cycles/performance-life-cycle.d.ts +59 -0
- package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +152 -0
- package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +62 -0
- package/dist/src/tasks-runner/life-cycles/performance-report.js +318 -0
- package/dist/src/tasks-runner/run-command.d.ts +1 -1
- package/dist/src/tasks-runner/run-command.js +12 -2
- package/dist/src/utils/min-release-age/behavior/pnpm.d.ts +6 -5
- package/dist/src/utils/min-release-age/behavior/pnpm.js +87 -339
- package/dist/src/utils/package-manager.d.ts +6 -3
- package/dist/src/utils/package-manager.js +11 -7
- package/dist/src/utils/terminal-link.d.ts +10 -6
- package/dist/src/utils/terminal-link.js +9 -11
- package/package.json +11 -11
|
@@ -85,6 +85,7 @@ export declare class Migrator {
|
|
|
85
85
|
}>;
|
|
86
86
|
private createMigrateJson;
|
|
87
87
|
private buildPackageJsonUpdates;
|
|
88
|
+
private resolveVersionForCascade;
|
|
88
89
|
private populatePackageJsonUpdatesAndGetPackagesToCheck;
|
|
89
90
|
private getPackageJsonUpdatesFromMigrationConfig;
|
|
90
91
|
/**
|
|
@@ -172,6 +173,7 @@ export declare function createFetcher(pmc: PackageManagerCommands): ((pkg: strin
|
|
|
172
173
|
stats?: MigrateFetchStats;
|
|
173
174
|
};
|
|
174
175
|
export { filterDowngradedUpdates };
|
|
176
|
+
export declare function generateMigrationsJsonAndUpdatePackageJson(root: string, opts: GenerateMigrations, fetch?: MigratorOptions['fetch']): Promise<void>;
|
|
175
177
|
/**
|
|
176
178
|
* Detects npm peer-dependency resolution failures. Keyed on the `ERESOLVE`
|
|
177
179
|
* error code, which npm consistently emits for this class of failure across
|
|
@@ -6,6 +6,7 @@ exports.resolveCanonicalNxPackage = resolveCanonicalNxPackage;
|
|
|
6
6
|
exports.resolveInclude = resolveInclude;
|
|
7
7
|
exports.parseMigrationsOptions = parseMigrationsOptions;
|
|
8
8
|
exports.createFetcher = createFetcher;
|
|
9
|
+
exports.generateMigrationsJsonAndUpdatePackageJson = generateMigrationsJsonAndUpdatePackageJson;
|
|
9
10
|
exports.isNpmPeerDepsError = isNpmPeerDepsError;
|
|
10
11
|
exports.resolveAgenticRunId = resolveAgenticRunId;
|
|
11
12
|
exports.formatSkippedPromptsNextStep = formatSkippedPromptsNextStep;
|
|
@@ -191,16 +192,37 @@ class Migrator {
|
|
|
191
192
|
!this.areIncompatiblePackagesPresent(packageUpdate.incompatibleWith) &&
|
|
192
193
|
(!this.interactive ||
|
|
193
194
|
(await this.runPackageJsonUpdatesConfirmationPrompt(packageUpdate, packageUpdateKey, packageToCheck.package)))) {
|
|
194
|
-
Object.entries(packageUpdate.packages)
|
|
195
|
+
const updateEntries = Object.entries(packageUpdate.packages);
|
|
196
|
+
// Validate all up front so invalid metadata fails fast, before any
|
|
197
|
+
// resolution does I/O.
|
|
198
|
+
for (const [name, update] of updateEntries) {
|
|
195
199
|
this.validatePackageUpdateVersion(packageToCheck.package, name, update);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
200
|
+
}
|
|
201
|
+
// Resolve serially: resolution can prompt (pnpm strict cooldown) and
|
|
202
|
+
// append to minimumReleaseAgeExclude, so a serial loop avoids
|
|
203
|
+
// overlapping prompts and keeps packageUpdates ordering stable.
|
|
204
|
+
for (const [name, update] of updateEntries) {
|
|
205
|
+
const resolvedUpdate = {
|
|
206
|
+
...update,
|
|
207
|
+
version: await this.resolveVersionForCascade(name, update.version),
|
|
208
|
+
};
|
|
209
|
+
filteredUpdates[name] = resolvedUpdate;
|
|
210
|
+
this.packageUpdates[name] = resolvedUpdate;
|
|
211
|
+
}
|
|
199
212
|
}
|
|
200
213
|
}
|
|
201
214
|
await Promise.all(Object.entries(filteredUpdates).map(([name, update]) => this.buildPackageJsonUpdates(name, update)));
|
|
202
215
|
}
|
|
203
216
|
}
|
|
217
|
+
async resolveVersionForCascade(packageName, version) {
|
|
218
|
+
// Already a fully-qualified semver (incl. prereleases) - nothing to resolve.
|
|
219
|
+
if ((0, semver_1.valid)(version)) {
|
|
220
|
+
return version;
|
|
221
|
+
}
|
|
222
|
+
// Otherwise resolve the spec (range/tag) through the min-release-age policy,
|
|
223
|
+
// which also honors any configured minimumReleaseAgeExclude entries.
|
|
224
|
+
return (0, resolve_package_version_1.resolvePackageVersionRespectingMinReleaseAge)(packageName, version);
|
|
225
|
+
}
|
|
204
226
|
async populatePackageJsonUpdatesAndGetPackagesToCheck(targetPackage, target) {
|
|
205
227
|
let targetVersion = target.version;
|
|
206
228
|
if (this.to[targetPackage]) {
|
|
@@ -1347,6 +1369,7 @@ function readNxVersion(packageJson, root) {
|
|
|
1347
1369
|
(0, package_json_1.getDependencyVersionFromPackageJson)('@nx/workspace', root, packageJson) ??
|
|
1348
1370
|
(0, package_json_1.getDependencyVersionFromPackageJson)('@nrwl/workspace', root, packageJson));
|
|
1349
1371
|
}
|
|
1372
|
+
// Exported for testing the optional-include orchestration seam (see NXC-4590).
|
|
1350
1373
|
async function generateMigrationsJsonAndUpdatePackageJson(root, opts, fetch) {
|
|
1351
1374
|
const pmc = (0, package_manager_1.getPackageManagerCommand)();
|
|
1352
1375
|
let phase = 'fetch_migrations';
|
|
@@ -1417,7 +1440,11 @@ async function generateMigrationsJsonAndUpdatePackageJson(root, opts, fetch) {
|
|
|
1417
1440
|
const writableUpdates = (0, update_filters_1.filterDowngradedUpdates)(packageUpdates, (0, catalog_1.resolveCatalogSpecifiers)(originalPackageJson), installedPackageVersions);
|
|
1418
1441
|
const wrotePackageJson = await updatePackageJson(root, writableUpdates);
|
|
1419
1442
|
const wroteNxJsonInstallation = await updateInstallationDetails(root, writableUpdates);
|
|
1420
|
-
|
|
1443
|
+
// Under `--include=optional` the target's own entry is filtered out of
|
|
1444
|
+
// `packageUpdates` (it's a required package), so resolve the version
|
|
1445
|
+
// defensively. Also reused by the completion analytics below.
|
|
1446
|
+
const resolvedTargetVersion = packageUpdates[walkedTargetPackage]?.version ?? opts.targetVersion;
|
|
1447
|
+
const promptMigrationFiles = (0, prompt_files_1.writePromptMigrationFiles)(root, migrations, promptContents ?? {}, resolvedTargetVersion);
|
|
1421
1448
|
if (migrations.length > 0) {
|
|
1422
1449
|
await createMigrationsFile(root, [
|
|
1423
1450
|
...addSplitConfigurationMigrationIfAvailable(from, writableUpdates),
|
|
@@ -1429,7 +1456,6 @@ async function generateMigrationsJsonAndUpdatePackageJson(root, opts, fetch) {
|
|
|
1429
1456
|
: include === 'optional'
|
|
1430
1457
|
? `- Processed optional dependency updates only (skipped required package updates).`
|
|
1431
1458
|
: null;
|
|
1432
|
-
const resolvedTargetVersion = packageUpdates[walkedTargetPackage]?.version ?? opts.targetVersion;
|
|
1433
1459
|
// The param expressions below evaluate before the report function is
|
|
1434
1460
|
// entered; `safeReport` keeps them inside the analytics boundary so a
|
|
1435
1461
|
// param-building throw can't surface here and convert an already
|
|
@@ -178,9 +178,26 @@ async function getCachedSerializedProjectGraphPromise(socket) {
|
|
|
178
178
|
}
|
|
179
179
|
function scheduleProjectGraphRecomputation(createdFiles, updatedFiles, deletedFiles) {
|
|
180
180
|
++fileChangeCounter;
|
|
181
|
-
|
|
181
|
+
// Hash the changed files up front and drop no-op rewrites before they can
|
|
182
|
+
// trigger an expensive recompute. Restoring a cached task output, a
|
|
183
|
+
// `git checkout` back to the same content, or a formatter that changes
|
|
184
|
+
// nothing all rewrite a file (new inode) the watcher reports as changed
|
|
185
|
+
// even though the bytes are identical. updateFilesInContext updates the
|
|
186
|
+
// workspace context and returns only the files whose content actually
|
|
187
|
+
// changed. Hashing here — once per watcher batch — rather than inside the
|
|
188
|
+
// recompute keeps it off the stale-retry path, which would otherwise see
|
|
189
|
+
// "no change" after the first pass already updated the context hashes.
|
|
190
|
+
perf_hooks_1.performance.mark('hash-watched-changes-start');
|
|
191
|
+
const changedFileHashes = createdFiles.length > 0 ||
|
|
192
|
+
updatedFiles.length > 0 ||
|
|
193
|
+
deletedFiles.length > 0
|
|
194
|
+
? ((0, workspace_context_1.updateFilesInContext)(workspace_root_1.workspaceRoot, [...createdFiles, ...updatedFiles], deletedFiles) ?? {})
|
|
195
|
+
: {};
|
|
196
|
+
perf_hooks_1.performance.mark('hash-watched-changes-end');
|
|
197
|
+
perf_hooks_1.performance.measure('hash changed files from watcher', 'hash-watched-changes-start', 'hash-watched-changes-end');
|
|
198
|
+
for (const [f, hash] of Object.entries(changedFileHashes)) {
|
|
182
199
|
collectedDeletedFiles.delete(f);
|
|
183
|
-
collectedUpdatedFiles.set(f, fileChangeCounter);
|
|
200
|
+
collectedUpdatedFiles.set(f, { version: fileChangeCounter, hash });
|
|
184
201
|
}
|
|
185
202
|
for (let f of deletedFiles) {
|
|
186
203
|
collectedUpdatedFiles.delete(f);
|
|
@@ -188,9 +205,7 @@ function scheduleProjectGraphRecomputation(createdFiles, updatedFiles, deletedFi
|
|
|
188
205
|
}
|
|
189
206
|
// The native watcher already coalesces a burst of events into one batch,
|
|
190
207
|
// so socket + listener notifications dispatch immediately.
|
|
191
|
-
if (
|
|
192
|
-
updatedFiles.length > 0 ||
|
|
193
|
-
deletedFiles.length > 0) {
|
|
208
|
+
if (Object.keys(changedFileHashes).length > 0 || deletedFiles.length > 0) {
|
|
194
209
|
(0, file_change_events_1.notifyFileChangeListeners)({ createdFiles, updatedFiles, deletedFiles });
|
|
195
210
|
(0, file_watcher_sockets_1.notifyFileWatcherSockets)(createdFiles, updatedFiles, deletedFiles);
|
|
196
211
|
// Bump generation synchronously so any in-flight compute fails its
|
|
@@ -284,14 +299,18 @@ async function processFilesAndCreateAndSerializeProjectGraph(separatedPlugins) {
|
|
|
284
299
|
return cachedSerializedProjectGraphPromise;
|
|
285
300
|
};
|
|
286
301
|
try {
|
|
287
|
-
perf_hooks_1.performance.mark('hash-watched-changes-start');
|
|
288
302
|
const updatedFilesSnapshot = new Map(collectedUpdatedFiles);
|
|
289
303
|
const deletedFilesSnapshot = new Map(collectedDeletedFiles);
|
|
290
304
|
const updatedFiles = [...updatedFilesSnapshot.keys()];
|
|
291
305
|
const deletedFiles = [...deletedFilesSnapshot.keys()];
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
306
|
+
// Hashes were already computed (and the workspace context updated) in
|
|
307
|
+
// scheduleProjectGraphRecomputation, which also dropped no-op rewrites.
|
|
308
|
+
// Reuse them so the context isn't re-hashed on every (possibly stale)
|
|
309
|
+
// recompute attempt.
|
|
310
|
+
const updatedFileHashes = {};
|
|
311
|
+
for (const [f, { hash }] of updatedFilesSnapshot) {
|
|
312
|
+
updatedFileHashes[f] = hash;
|
|
313
|
+
}
|
|
295
314
|
logger_1.serverLogger.requestLog(`Updated workspace context based on watched changes, recomputing project graph...`);
|
|
296
315
|
logger_1.serverLogger.requestLog(updatedFiles);
|
|
297
316
|
logger_1.serverLogger.requestLog(deletedFiles);
|
|
@@ -333,8 +352,8 @@ async function processFilesAndCreateAndSerializeProjectGraph(separatedPlugins) {
|
|
|
333
352
|
// from the daemon's view (project graph misses recently added files).
|
|
334
353
|
// Match version-stamps so a file modified mid-flight (higher version)
|
|
335
354
|
// stays in the queue for reprocessing.
|
|
336
|
-
for (const [f, version] of updatedFilesSnapshot) {
|
|
337
|
-
if (collectedUpdatedFiles.get(f) === version) {
|
|
355
|
+
for (const [f, { version }] of updatedFilesSnapshot) {
|
|
356
|
+
if (collectedUpdatedFiles.get(f)?.version === version) {
|
|
338
357
|
collectedUpdatedFiles.delete(f);
|
|
339
358
|
}
|
|
340
359
|
}
|
|
@@ -34,7 +34,7 @@ export declare class AppLifeCycle {
|
|
|
34
34
|
startTasks(tasks: Array<Task>, metadata: object): void
|
|
35
35
|
printTaskTerminalOutput(task: Task, status: string, output: string): void
|
|
36
36
|
endTasks(taskResults: Array<TaskResult>, metadata: object): void
|
|
37
|
-
endCommand(): void
|
|
37
|
+
endCommand(summary?: PerformanceSummaryPayload | undefined | null): void
|
|
38
38
|
__init(doneCallback: (() => unknown)): void
|
|
39
39
|
registerRunningTask(taskId: string, parserAndWriter: ExternalObject<[ParserArc, WriterArc]>): void
|
|
40
40
|
registerRunningTaskWithEmptyParser(taskId: string): void
|
|
@@ -275,6 +275,15 @@ export interface CachedResult {
|
|
|
275
275
|
size?: number
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Cache hits vs total; present only when there was a cache outcome. A bypassed
|
|
280
|
+
* cache is signalled separately by `cache_skipped`.
|
|
281
|
+
*/
|
|
282
|
+
export interface CacheStat {
|
|
283
|
+
hits: number
|
|
284
|
+
total: number
|
|
285
|
+
}
|
|
286
|
+
|
|
278
287
|
export declare function canInstallNxConsole(): Promise<boolean>
|
|
279
288
|
|
|
280
289
|
export declare function canInstallNxConsoleForEditor(editor: SupportedEditor): Promise<boolean>
|
|
@@ -375,6 +384,13 @@ export declare function findImports(projectFileMap: Record<string, Array<string>
|
|
|
375
384
|
*/
|
|
376
385
|
export declare function flushTelemetry(): void
|
|
377
386
|
|
|
387
|
+
/**
|
|
388
|
+
* The single duration formatter — used by the task list, terminal report, and TUI
|
|
389
|
+
* popup. Exposed to JS as `formatDuration` so all three share one implementation.
|
|
390
|
+
* 0 (or sub-millisecond) → "<1ms", then "470ms", "13.4s", "1m 30s".
|
|
391
|
+
*/
|
|
392
|
+
export declare function formatDuration(ms: number): string
|
|
393
|
+
|
|
378
394
|
export declare function getBinaryTarget(): string
|
|
379
395
|
|
|
380
396
|
export declare function getDefaultMaxCacheSize(cachePath: string): number
|
|
@@ -524,6 +540,15 @@ export declare function killProcessTree(rootPid: number, signal?: string | numbe
|
|
|
524
540
|
*/
|
|
525
541
|
export declare function killProcessTreeGraceful(rootPid: number, signal?: string | number | undefined | null, gracePeriodMs?: number | undefined | null): Promise<void>
|
|
526
542
|
|
|
543
|
+
/**
|
|
544
|
+
* A docs link rendered as an OSC 8 hyperlink. Both fields come from TS so the
|
|
545
|
+
* popup never hardcodes a URL.
|
|
546
|
+
*/
|
|
547
|
+
export interface Link {
|
|
548
|
+
text: string
|
|
549
|
+
href: string
|
|
550
|
+
}
|
|
551
|
+
|
|
527
552
|
export declare function logDebug(message: string): void
|
|
528
553
|
|
|
529
554
|
/** Combined metadata for groups and processes */
|
|
@@ -564,6 +589,28 @@ export interface NxWorkspaceFilesExternals {
|
|
|
564
589
|
|
|
565
590
|
export declare function parseTaskStatus(stringStatus: string): TaskStatus
|
|
566
591
|
|
|
592
|
+
/**
|
|
593
|
+
* Structured run report shown in the exit-countdown popup. The TUI builds the
|
|
594
|
+
* visual from these numbers rather than receiving a pre-formatted string.
|
|
595
|
+
*/
|
|
596
|
+
export interface PerformanceSummaryPayload {
|
|
597
|
+
runDurationMs: number
|
|
598
|
+
criticalPathMs: number
|
|
599
|
+
criticalPathTaskCount: number
|
|
600
|
+
recoverableMs: number
|
|
601
|
+
cache?: CacheStat
|
|
602
|
+
cacheSkipped: boolean
|
|
603
|
+
/** Already in display order; a multi-line entry embeds a task list. */
|
|
604
|
+
recommendations: Array<string>
|
|
605
|
+
/** The docs footer link, rendered as a bullet and hyperlinked. */
|
|
606
|
+
footer: Link
|
|
607
|
+
/**
|
|
608
|
+
* Phrases already in `recommendations` to hyperlink in place (e.g. the
|
|
609
|
+
* remote-cache CTA); empty when none apply.
|
|
610
|
+
*/
|
|
611
|
+
links: Array<Link>
|
|
612
|
+
}
|
|
613
|
+
|
|
567
614
|
/** Process metadata (static, doesn't change during process lifetime) */
|
|
568
615
|
export interface ProcessMetadata {
|
|
569
616
|
ppid: number
|
|
@@ -605,6 +605,7 @@ module.exports.EventType = nativeBinding.EventType
|
|
|
605
605
|
module.exports.expandOutputs = nativeBinding.expandOutputs
|
|
606
606
|
module.exports.findImports = nativeBinding.findImports
|
|
607
607
|
module.exports.flushTelemetry = nativeBinding.flushTelemetry
|
|
608
|
+
module.exports.formatDuration = nativeBinding.formatDuration
|
|
608
609
|
module.exports.getBinaryTarget = nativeBinding.getBinaryTarget
|
|
609
610
|
module.exports.getDefaultMaxCacheSize = nativeBinding.getDefaultMaxCacheSize
|
|
610
611
|
module.exports.getEventDimensions = nativeBinding.getEventDimensions
|
|
Binary file
|
|
Binary file
|
|
@@ -35,22 +35,43 @@ export declare function forceRegisterEsmLoader(): void;
|
|
|
35
35
|
* the importing module is itself TypeScript, and the specifier is relative, so
|
|
36
36
|
* it never hijacks resolution that would otherwise succeed.
|
|
37
37
|
*
|
|
38
|
+
* Used only as the fallback for runtimes without `module.registerHooks()`
|
|
39
|
+
* (Node < 22.15 / < 23.5); newer runtimes register the in-thread synchronous
|
|
40
|
+
* twin `nodeNextEsmResolveHook` instead. Keep the two in sync.
|
|
41
|
+
*
|
|
38
42
|
* Exported so the hook can be exercised directly in unit tests.
|
|
39
43
|
*/
|
|
40
44
|
export declare const NODENEXT_ESM_RESOLVER_SOURCE = "\nconst EXT_FALLBACK = { '.js': ['.ts', '.tsx'], '.mjs': ['.mts'], '.cjs': ['.cts'] };\nconst TS_PARENT_RE = /\\.(?:ts|tsx|mts|cts)(?:$|\\?)/;\nexport async function resolve(specifier, context, nextResolve) {\n try {\n return await nextResolve(specifier, context);\n } catch (err) {\n if (err?.code !== 'ERR_MODULE_NOT_FOUND') throw err;\n const parent = context.parentURL;\n if (!parent || !TS_PARENT_RE.test(parent)) throw err;\n if (!(specifier.startsWith('./') || specifier.startsWith('../') || specifier.startsWith('file:'))) throw err;\n const m = specifier.match(/(\\.(?:js|mjs|cjs))($|\\?)/);\n if (!m) throw err;\n const fallbacks = EXT_FALLBACK[m[1]];\n if (!fallbacks) throw err;\n const base = specifier.slice(0, m.index);\n const suffix = specifier.slice(m.index + m[1].length);\n for (const ext of fallbacks) {\n try { return await nextResolve(base + ext + suffix, context); } catch {}\n }\n throw err;\n }\n}\n";
|
|
41
45
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
* Synchronous in-thread twin of `NODENEXT_ESM_RESOLVER_SOURCE` for
|
|
47
|
+
* `module.registerHooks()`: `nextResolve` throws synchronously rather than
|
|
48
|
+
* rejecting, so this uses try/catch instead of `await`. Keep the two in sync.
|
|
49
|
+
* Exported for unit tests.
|
|
50
|
+
*/
|
|
51
|
+
export declare function nodeNextEsmResolveHook(specifier: string, context: {
|
|
52
|
+
parentURL?: string;
|
|
53
|
+
}, nextResolve: (specifier: string, context?: unknown) => {
|
|
54
|
+
url: string;
|
|
55
|
+
}): {
|
|
56
|
+
url: string;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Register an ESM resolution hook that rewrites TypeScript NodeNext-style
|
|
60
|
+
* `.js`/`.mjs`/`.cjs` relative specifiers to their `.ts`/`.mts`/`.cts` sources.
|
|
61
|
+
* This is the ESM counterpart to `ensureCjsResolverPatched`: Node's native type
|
|
62
|
+
* stripping loads the `.ts` file, but neither native strip nor Node's ESM
|
|
63
|
+
* resolver rewrites the extension, so `import './foo.js'` from a `.ts` source
|
|
64
|
+
* where only `foo.ts` exists fails with ERR_MODULE_NOT_FOUND without it.
|
|
49
65
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
66
|
+
* Prefers `module.registerHooks()` (Node 22.15+ / 23.5+), passing the in-thread
|
|
67
|
+
* `nodeNextEsmResolveHook`. Falls back to `module.register()` with the inlined
|
|
68
|
+
* `data:` module (`NODENEXT_ESM_RESOLVER_SOURCE`) on older runtimes that lack
|
|
69
|
+
* `registerHooks` - `module.register()` emits a runtime DeprecationWarning
|
|
70
|
+
* (DEP0205) on Node 25.9+/26+, so it is only used when nothing better exists.
|
|
71
|
+
* Either way the hook relies only on Node's default resolver - no
|
|
72
|
+
* ts-node/swc-node.
|
|
52
73
|
*
|
|
53
|
-
* Idempotent and best-effort: a no-op when
|
|
74
|
+
* Idempotent and best-effort: a no-op when no registration API is available,
|
|
54
75
|
* when a TypeScript transpiler is already preloaded (see
|
|
55
76
|
* `isTsTranspilerPreloaded`), or if registration fails.
|
|
56
77
|
*/
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.NODENEXT_ESM_RESOLVER_SOURCE = void 0;
|
|
4
4
|
exports.forceRegisterEsmLoader = forceRegisterEsmLoader;
|
|
5
|
+
exports.nodeNextEsmResolveHook = nodeNextEsmResolveHook;
|
|
5
6
|
exports.ensureNodeNextEsmResolverRegistered = ensureNodeNextEsmResolverRegistered;
|
|
6
7
|
exports.ensureCjsResolverPatched = ensureCjsResolverPatched;
|
|
7
8
|
exports.isNativeStripPreferred = isNativeStripPreferred;
|
|
@@ -100,6 +101,10 @@ function ensureEsmLoaderRegistered(opts) {
|
|
|
100
101
|
* the importing module is itself TypeScript, and the specifier is relative, so
|
|
101
102
|
* it never hijacks resolution that would otherwise succeed.
|
|
102
103
|
*
|
|
104
|
+
* Used only as the fallback for runtimes without `module.registerHooks()`
|
|
105
|
+
* (Node < 22.15 / < 23.5); newer runtimes register the in-thread synchronous
|
|
106
|
+
* twin `nodeNextEsmResolveHook` instead. Keep the two in sync.
|
|
107
|
+
*
|
|
103
108
|
* Exported so the hook can be exercised directly in unit tests.
|
|
104
109
|
*/
|
|
105
110
|
exports.NODENEXT_ESM_RESOLVER_SOURCE = `
|
|
@@ -126,20 +131,70 @@ export async function resolve(specifier, context, nextResolve) {
|
|
|
126
131
|
}
|
|
127
132
|
}
|
|
128
133
|
`;
|
|
134
|
+
const NODENEXT_EXT_FALLBACK = {
|
|
135
|
+
'.js': ['.ts', '.tsx'],
|
|
136
|
+
'.mjs': ['.mts'],
|
|
137
|
+
'.cjs': ['.cts'],
|
|
138
|
+
};
|
|
139
|
+
const NODENEXT_TS_PARENT_RE = /\.(?:ts|tsx|mts|cts)(?:$|\?)/;
|
|
140
|
+
/**
|
|
141
|
+
* Synchronous in-thread twin of `NODENEXT_ESM_RESOLVER_SOURCE` for
|
|
142
|
+
* `module.registerHooks()`: `nextResolve` throws synchronously rather than
|
|
143
|
+
* rejecting, so this uses try/catch instead of `await`. Keep the two in sync.
|
|
144
|
+
* Exported for unit tests.
|
|
145
|
+
*/
|
|
146
|
+
function nodeNextEsmResolveHook(specifier, context, nextResolve) {
|
|
147
|
+
try {
|
|
148
|
+
return nextResolve(specifier, context);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
if (err?.code !== 'ERR_MODULE_NOT_FOUND')
|
|
152
|
+
throw err;
|
|
153
|
+
const parent = context.parentURL;
|
|
154
|
+
if (!parent || !NODENEXT_TS_PARENT_RE.test(parent))
|
|
155
|
+
throw err;
|
|
156
|
+
if (!(specifier.startsWith('./') ||
|
|
157
|
+
specifier.startsWith('../') ||
|
|
158
|
+
specifier.startsWith('file:'))) {
|
|
159
|
+
throw err;
|
|
160
|
+
}
|
|
161
|
+
const m = specifier.match(/(\.(?:js|mjs|cjs))($|\?)/);
|
|
162
|
+
if (!m)
|
|
163
|
+
throw err;
|
|
164
|
+
const fallbacks = NODENEXT_EXT_FALLBACK[m[1]];
|
|
165
|
+
if (!fallbacks)
|
|
166
|
+
throw err;
|
|
167
|
+
const base = specifier.slice(0, m.index);
|
|
168
|
+
const suffix = specifier.slice(m.index + m[1].length);
|
|
169
|
+
for (const ext of fallbacks) {
|
|
170
|
+
try {
|
|
171
|
+
return nextResolve(base + ext + suffix, context);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// try the next fallback
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
throw err;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
129
180
|
let nodeNextEsmResolverRegistered = false;
|
|
130
181
|
/**
|
|
131
|
-
* Register
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* exists fails with ERR_MODULE_NOT_FOUND without it.
|
|
182
|
+
* Register an ESM resolution hook that rewrites TypeScript NodeNext-style
|
|
183
|
+
* `.js`/`.mjs`/`.cjs` relative specifiers to their `.ts`/`.mts`/`.cts` sources.
|
|
184
|
+
* This is the ESM counterpart to `ensureCjsResolverPatched`: Node's native type
|
|
185
|
+
* stripping loads the `.ts` file, but neither native strip nor Node's ESM
|
|
186
|
+
* resolver rewrites the extension, so `import './foo.js'` from a `.ts` source
|
|
187
|
+
* where only `foo.ts` exists fails with ERR_MODULE_NOT_FOUND without it.
|
|
138
188
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
189
|
+
* Prefers `module.registerHooks()` (Node 22.15+ / 23.5+), passing the in-thread
|
|
190
|
+
* `nodeNextEsmResolveHook`. Falls back to `module.register()` with the inlined
|
|
191
|
+
* `data:` module (`NODENEXT_ESM_RESOLVER_SOURCE`) on older runtimes that lack
|
|
192
|
+
* `registerHooks` - `module.register()` emits a runtime DeprecationWarning
|
|
193
|
+
* (DEP0205) on Node 25.9+/26+, so it is only used when nothing better exists.
|
|
194
|
+
* Either way the hook relies only on Node's default resolver - no
|
|
195
|
+
* ts-node/swc-node.
|
|
141
196
|
*
|
|
142
|
-
* Idempotent and best-effort: a no-op when
|
|
197
|
+
* Idempotent and best-effort: a no-op when no registration API is available,
|
|
143
198
|
* when a TypeScript transpiler is already preloaded (see
|
|
144
199
|
* `isTsTranspilerPreloaded`), or if registration fails.
|
|
145
200
|
*/
|
|
@@ -148,16 +203,16 @@ function ensureNodeNextEsmResolverRegistered() {
|
|
|
148
203
|
return;
|
|
149
204
|
nodeNextEsmResolverRegistered = true;
|
|
150
205
|
const module = require('node:module');
|
|
151
|
-
if (typeof module.register !== 'function')
|
|
152
|
-
return;
|
|
153
206
|
// Skip when a transpiler was preloaded via `--require`/`--import` (e.g.
|
|
154
207
|
// `--require ts-node/register`, which Nx uses only when it runs from `.ts`
|
|
155
|
-
// source). `module.register()` spins up a loader-hook
|
|
156
|
-
// Node re-runs those preloads, resolved relative to
|
|
157
|
-
// directory - and Nx plugin workers `chdir()` into the
|
|
158
|
-
// first. If that workspace can't resolve the preloaded
|
|
159
|
-
// worker throws and can leave module resolution in a bad
|
|
160
|
-
// avoid the call entirely; catching it is not a clean
|
|
208
|
+
// source). The `module.register()` fallback below spins up a loader-hook
|
|
209
|
+
// worker thread on which Node re-runs those preloads, resolved relative to
|
|
210
|
+
// the *current* working directory - and Nx plugin workers `chdir()` into the
|
|
211
|
+
// analyzed workspace first. If that workspace can't resolve the preloaded
|
|
212
|
+
// module, the loader worker throws and can leave module resolution in a bad
|
|
213
|
+
// state, so we must avoid the call entirely; catching it is not a clean
|
|
214
|
+
// recovery. `module.registerHooks()` runs in-thread with no such worker, but
|
|
215
|
+
// we keep the skip uniform so resolver coverage doesn't vary by Node version.
|
|
161
216
|
//
|
|
162
217
|
// Consequence: in that from-`.ts`-source invocation a `type: module` plugin
|
|
163
218
|
// using NodeNext `.js` specifiers won't get this resolver (a preloaded
|
|
@@ -165,6 +220,20 @@ function ensureNodeNextEsmResolverRegistered() {
|
|
|
165
220
|
// unaffected - its workers run compiled `.js` with no preload.
|
|
166
221
|
if (isTsTranspilerPreloaded())
|
|
167
222
|
return;
|
|
223
|
+
// Synchronous in-thread hooks (Node 22.15+ / 23.5+). Preferred because
|
|
224
|
+
// `module.register()` is deprecated (DEP0205) from Node 25.9+/26+.
|
|
225
|
+
const registerHooks = module.registerHooks;
|
|
226
|
+
if (typeof registerHooks === 'function') {
|
|
227
|
+
try {
|
|
228
|
+
registerHooks.call(module, { resolve: nodeNextEsmResolveHook });
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// Best-effort: leave Node's native handling in place rather than failing.
|
|
232
|
+
}
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (typeof module.register !== 'function')
|
|
236
|
+
return;
|
|
168
237
|
try {
|
|
169
238
|
module.register('data:text/javascript,' + encodeURIComponent(exports.NODENEXT_ESM_RESOLVER_SOURCE));
|
|
170
239
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.defaultTasksRunner = exports.RemoteCacheV2 = void 0;
|
|
4
4
|
const task_orchestrator_1 = require("./task-orchestrator");
|
|
5
|
+
const performance_life_cycle_1 = require("./life-cycles/performance-life-cycle");
|
|
5
6
|
const cache_directory_1 = require("../utils/cache-directory");
|
|
6
7
|
const promises_1 = require("fs/promises");
|
|
7
8
|
const path_1 = require("path");
|
|
@@ -54,13 +55,15 @@ class RemoteCacheV2 {
|
|
|
54
55
|
}
|
|
55
56
|
exports.RemoteCacheV2 = RemoteCacheV2;
|
|
56
57
|
const defaultTasksRunner = async (tasks, options, context) => {
|
|
57
|
-
const { total: threadCount } = (0, task_orchestrator_1.getThreadPoolSize)(options, context.taskGraph);
|
|
58
|
-
|
|
58
|
+
const { total: threadCount, discrete } = (0, task_orchestrator_1.getThreadPoolSize)(options, context.taskGraph);
|
|
59
|
+
// Total thread count drives the TUI; the discrete `--parallel` feeds the perf report.
|
|
60
|
+
await options.lifeCycle.startCommand(threadCount, discrete);
|
|
59
61
|
try {
|
|
60
62
|
return await runAllTasks(options, context);
|
|
61
63
|
}
|
|
62
64
|
finally {
|
|
63
|
-
|
|
65
|
+
// Hand the TUI exit popup the perf report (multi-task TUI runs); other modes flush it.
|
|
66
|
+
await options.lifeCycle.endCommand((0, performance_life_cycle_1.getPerformanceReport)(tasks.length));
|
|
64
67
|
}
|
|
65
68
|
};
|
|
66
69
|
exports.defaultTasksRunner = defaultTasksRunner;
|
|
@@ -15,7 +15,7 @@ async function createOrchestrator(tasks, projectGraph, fullTaskGraph, nxJson, li
|
|
|
15
15
|
const invokeRunnerTerminalLifecycle = new invoke_runner_terminal_output_life_cycle_1.InvokeRunnerTerminalOutputLifeCycle(tasks);
|
|
16
16
|
const taskResultsLifecycle = new task_results_life_cycle_1.TaskResultsLifeCycle();
|
|
17
17
|
const compositedLifeCycle = new life_cycle_1.CompositeLifeCycle([
|
|
18
|
-
...(0, run_command_1.constructLifeCycles)(invokeRunnerTerminalLifecycle),
|
|
18
|
+
...(0, run_command_1.constructLifeCycles)(invokeRunnerTerminalLifecycle, fullTaskGraph, nxJson),
|
|
19
19
|
taskResultsLifecycle,
|
|
20
20
|
lifeCycle,
|
|
21
21
|
]);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Task } from '../config/task-graph';
|
|
2
|
-
import { BatchInfo, BatchStatus, ExternalObject, TaskResult, TaskStatus as NativeTaskStatus } from '../native';
|
|
2
|
+
import { BatchInfo, BatchStatus, ExternalObject, PerformanceSummaryPayload, TaskResult, TaskStatus as NativeTaskStatus } from '../native';
|
|
3
3
|
import { TaskStatus } from './tasks-runner';
|
|
4
4
|
/**
|
|
5
5
|
* The result of a completed {@link Task}.
|
|
@@ -21,8 +21,13 @@ export interface TaskMetadata {
|
|
|
21
21
|
groupId: number;
|
|
22
22
|
}
|
|
23
23
|
export interface LifeCycle {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* @param threadCount total thread-pool size (drives the TUI display)
|
|
26
|
+
* @param parallel resolved `--parallel` (discrete slots), for the performance report
|
|
27
|
+
*/
|
|
28
|
+
startCommand?(threadCount?: number, parallel?: number): void | Promise<void>;
|
|
29
|
+
/** @param summary performance report payload for the TUI exit popup (TUI runs only) */
|
|
30
|
+
endCommand?(summary?: PerformanceSummaryPayload): void | Promise<void>;
|
|
26
31
|
scheduleTask?(task: Task): void | Promise<void>;
|
|
27
32
|
/**
|
|
28
33
|
* @deprecated use startTasks
|
|
@@ -53,8 +58,8 @@ export interface LifeCycle {
|
|
|
53
58
|
export declare class CompositeLifeCycle implements LifeCycle {
|
|
54
59
|
private readonly lifeCycles;
|
|
55
60
|
constructor(lifeCycles: LifeCycle[]);
|
|
56
|
-
startCommand(parallel?: number): Promise<void>;
|
|
57
|
-
endCommand(): Promise<void>;
|
|
61
|
+
startCommand(threadCount?: number, parallel?: number): Promise<void>;
|
|
62
|
+
endCommand(summary?: PerformanceSummaryPayload): Promise<void>;
|
|
58
63
|
scheduleTask(task: Task): Promise<void>;
|
|
59
64
|
startTask(task: Task): void;
|
|
60
65
|
endTask(task: Task, code: number): void;
|
|
@@ -5,17 +5,17 @@ class CompositeLifeCycle {
|
|
|
5
5
|
constructor(lifeCycles) {
|
|
6
6
|
this.lifeCycles = lifeCycles;
|
|
7
7
|
}
|
|
8
|
-
async startCommand(parallel) {
|
|
8
|
+
async startCommand(threadCount, parallel) {
|
|
9
9
|
for (let l of this.lifeCycles) {
|
|
10
10
|
if (l.startCommand) {
|
|
11
|
-
await l.startCommand(parallel);
|
|
11
|
+
await l.startCommand(threadCount, parallel);
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
async endCommand() {
|
|
15
|
+
async endCommand(summary) {
|
|
16
16
|
for (let l of this.lifeCycles) {
|
|
17
17
|
if (l.endCommand) {
|
|
18
|
-
await l.endCommand();
|
|
18
|
+
await l.endCommand(summary);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
}
|