@nx/js 23.0.1 → 23.0.2

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.
@@ -11,7 +11,7 @@ const crypto_1 = require("crypto");
11
11
  const path = tslib_1.__importStar(require("path"));
12
12
  const path_1 = require("path");
13
13
  const buildable_libs_utils_1 = require("../../utils/buildable-libs-utils");
14
- const kill_tree_1 = require("./lib/kill-tree");
14
+ const native_1 = require("nx/src/native");
15
15
  const line_aware_writer_1 = require("./lib/line-aware-writer");
16
16
  const coalescing_debounce_1 = require("./lib/coalescing-debounce");
17
17
  const fileutils_1 = require("nx/src/utils/fileutils");
@@ -67,23 +67,26 @@ async function* nodeExecutor(options, context) {
67
67
  const previousTask = currentTask;
68
68
  const task = tasks.shift();
69
69
  if (previousTask && !previousTask.killed) {
70
- previousTask.killed = true;
71
- if (previousTask.childProcess?.connected) {
72
- previousTask.childProcess.disconnect();
73
- }
74
- previousTask.childProcess?.removeAllListeners();
70
+ // stop() marks the task killed, detaches listeners, and waits for the
71
+ // process tree to exit.
75
72
  await previousTask.stop('SIGTERM');
76
73
  await new Promise((resolve) => setImmediate(resolve));
77
74
  }
78
75
  currentTask = task;
79
76
  globalLineAwareWriter.setActiveProcess(task.id);
80
77
  await task.start();
78
+ // A file change may have queued another task while we waited for the
79
+ // previous process to stop. Its debounce trigger piggybacked on this
80
+ // in-flight run, so drain the queue now or it would sit unprocessed
81
+ // until the next change.
82
+ if (tasks.length > 0) {
83
+ await processQueue();
84
+ }
81
85
  };
82
86
  const debouncedProcessQueue = (0, coalescing_debounce_1.createCoalescingDebounce)(processQueue, options.debounce ?? 1_000);
83
87
  const addToQueue = async (childProcess, buildResult) => {
84
88
  for (const task of tasks) {
85
89
  if (!task.killed) {
86
- task.killed = true;
87
90
  await task.stop('SIGTERM');
88
91
  }
89
92
  }
@@ -156,7 +159,7 @@ async function* nodeExecutor(options, context) {
156
159
  },
157
160
  stop: async (signal = 'SIGTERM') => {
158
161
  task.killed = true;
159
- if (task.childProcess) {
162
+ if (task.childProcess?.pid) {
160
163
  if (task.childProcess.stdout) {
161
164
  task.childProcess.stdout.pause();
162
165
  }
@@ -167,7 +170,12 @@ async function* nodeExecutor(options, context) {
167
170
  task.childProcess.disconnect();
168
171
  }
169
172
  task.childProcess.removeAllListeners();
170
- await (0, kill_tree_1.killTree)(task.childProcess.pid, signal);
173
+ // Wait for the process tree to fully exit so the port is released
174
+ // before a watch-mode restart boots the next server (EADDRINUSE).
175
+ // Windows cannot deliver graceful signals through this API, so
176
+ // skip the grace period and force-kill immediately (matching the
177
+ // previous taskkill /F behavior).
178
+ await (0, native_1.killProcessTreeGraceful)(task.childProcess.pid, signal, process.platform === 'win32' ? 0 : undefined);
171
179
  }
172
180
  if (task.id === globalLineAwareWriter.currentProcessId) {
173
181
  globalLineAwareWriter.setActiveProcess(null);
@@ -1,5 +1,7 @@
1
1
  import { type ExecutorContext } from '@nx/devkit';
2
+ import { type PackageJson } from 'nx/src/utils/package-json';
2
3
  import { type PruneLockfileOptions } from './schema';
3
4
  export default function pruneLockfileExecutor(schema: PruneLockfileOptions, context: ExecutorContext): Promise<{
4
5
  success: boolean;
5
6
  }>;
7
+ export declare function resolveCatalogReferences(packageJson: PackageJson): PackageJson;
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = pruneLockfileExecutor;
4
+ exports.resolveCatalogReferences = resolveCatalogReferences;
4
5
  const devkit_1 = require("@nx/devkit");
6
+ const internal_1 = require("@nx/devkit/internal");
5
7
  const fs_1 = require("fs");
6
8
  const path_1 = require("path");
7
9
  const utils_1 = require("nx/src/tasks-runner/utils");
@@ -13,7 +15,8 @@ const strip_glob_to_base_dir_1 = require("../../utils/strip-glob-to-base-dir");
13
15
  async function pruneLockfileExecutor(schema, context) {
14
16
  devkit_1.logger.log('Pruning lockfile...');
15
17
  const outputDirectory = getOutputDir(schema, context);
16
- const packageJson = getPackageJson(schema, context);
18
+ const packageJson = resolveCatalogReferences(getPackageJson(schema, context));
19
+ mergeAllowScripts(packageJson);
17
20
  const packageManager = (0, devkit_1.detectPackageManager)(devkit_1.workspaceRoot);
18
21
  if (packageManager === 'bun') {
19
22
  devkit_1.logger.warn('Bun lockfile generation is not supported. Only package.json will be generated. Run "bun install" in the output directory if needed.');
@@ -48,6 +51,55 @@ function createPrunedLockfile(packageJson, graph) {
48
51
  lockFile,
49
52
  };
50
53
  }
54
+ /**
55
+ * npm reads the `allowScripts` install-script allowlist only from the install
56
+ * root, but `npm approve-scripts` writes it to the workspace root, so it never
57
+ * lives in the project package.json the prune output is built from. Carry the
58
+ * root allowlist over, with project-level entries preserved and winning on
59
+ * conflict. Mirrors the `pnpm.allowBuilds` handling in createPackageJson.
60
+ */
61
+ function mergeAllowScripts(packageJson) {
62
+ const rootPackageJson = (0, devkit_1.readJsonFile)((0, path_1.join)(devkit_1.workspaceRoot, 'package.json'));
63
+ if (!rootPackageJson.allowScripts) {
64
+ return;
65
+ }
66
+ packageJson.allowScripts = {
67
+ ...rootPackageJson.allowScripts,
68
+ ...packageJson.allowScripts,
69
+ };
70
+ }
71
+ function resolveCatalogReferences(packageJson) {
72
+ const manager = (0, internal_1.getCatalogManager)(devkit_1.workspaceRoot);
73
+ if (!manager) {
74
+ return packageJson;
75
+ }
76
+ const sections = [
77
+ 'dependencies',
78
+ 'optionalDependencies',
79
+ 'devDependencies',
80
+ 'peerDependencies',
81
+ ];
82
+ const resolved = { ...packageJson };
83
+ for (const section of sections) {
84
+ const deps = packageJson[section];
85
+ if (!deps) {
86
+ continue;
87
+ }
88
+ const resolvedDeps = { ...deps };
89
+ for (const [packageName, version] of Object.entries(deps)) {
90
+ if (!manager.isCatalogReference(version)) {
91
+ continue;
92
+ }
93
+ const resolvedVersion = manager.resolveCatalogReference(devkit_1.workspaceRoot, packageName, version);
94
+ if (!resolvedVersion) {
95
+ throw new Error(`Could not resolve catalog reference for package ${packageName}@${version}.`);
96
+ }
97
+ resolvedDeps[packageName] = resolvedVersion;
98
+ }
99
+ resolved[section] = resolvedDeps;
100
+ }
101
+ return resolved;
102
+ }
51
103
  function getPackageJson(schema, context) {
52
104
  const target = (0, devkit_1.parseTargetString)(schema.buildTarget, context);
53
105
  const project = context.projectGraph.nodes[target.project].data;
@@ -55,8 +55,12 @@ function createTypeScriptCompilationOptions(normalizedOptions, context) {
55
55
  outputPath: (0, devkit_1.joinPathFragments)(normalizedOptions.outputPath),
56
56
  projectName: context.projectName,
57
57
  projectRoot: normalizedOptions.projectRoot,
58
- rootDir: (0, devkit_1.joinPathFragments)(normalizedOptions.rootDir),
59
- tsConfig: (0, devkit_1.joinPathFragments)(normalizedOptions.tsConfig),
58
+ // Keep the Windows drive letter on rootDir/tsConfig (only forward-slash them).
59
+ // joinPathFragments strips it via normalizePath, leaving them drive-less while
60
+ // the generated path mappings stay absolute drive-full, so alias-resolved files
61
+ // fail TypeScript's rootDir containment check (TS6059) on Windows.
62
+ rootDir: normalizedOptions.rootDir.replace(/\\/g, '/'),
63
+ tsConfig: normalizedOptions.tsConfig.replace(/\\/g, '/'),
60
64
  watch: normalizedOptions.watch,
61
65
  deleteOutputPath: normalizedOptions.clean,
62
66
  getCustomTransformers: (0, lib_1.getCustomTrasformersFactory)(normalizedOptions.transformers),
@@ -282,7 +282,15 @@ function updatePaths(dependencies, paths) {
282
282
  const { root } = dep.node.data;
283
283
  // Update nested mappings to point to the dependency's output paths
284
284
  mappedPaths = mappedPaths.concat(paths[path].flatMap((p) => dep.outputs.flatMap((output) => {
285
- const basePath = p.replace(root, output);
285
+ // Re-map the root prefix to the output. Match root only as a
286
+ // leading segment (after an optional `./`) so a root that
287
+ // also appears later in the value (e.g. output `dist/libs/base`
288
+ // for root `base`) isn't doubled.
289
+ const dotPrefix = p.startsWith('./') ? './' : '';
290
+ const value = dotPrefix ? p.slice(2) : p;
291
+ const basePath = value === root || value.startsWith(`${root}/`)
292
+ ? `${dotPrefix}${output}${value.slice(root.length)}`
293
+ : p;
286
294
  return [
287
295
  // extension-less path to support compiled output
288
296
  basePath.replace(new RegExp(`${(0, path_1.extname)(basePath)}$`, 'gi'), ''),
@@ -5,14 +5,23 @@ exports.generatePrettierSetup = generatePrettierSetup;
5
5
  exports.resolvePrettierConfigPath = resolvePrettierConfigPath;
6
6
  const devkit_1 = require("@nx/devkit");
7
7
  const versions_1 = require("./versions");
8
- async function resolveUserExistingPrettierConfig() {
9
- let prettier;
8
+ // Prettier v3 (ESM) exposes its API as named exports; v2 (CJS) exposes it under
9
+ // `.default` when loaded via `import()`. Return whichever carries the API, or
10
+ // null if prettier isn't installed.
11
+ async function importPrettier() {
10
12
  try {
11
- prettier = await import('prettier');
13
+ const imported = await import('prettier');
14
+ return (imported.resolveConfig ? imported : imported.default);
12
15
  }
13
16
  catch {
14
17
  return null;
15
18
  }
19
+ }
20
+ async function resolveUserExistingPrettierConfig() {
21
+ const prettier = await importPrettier();
22
+ if (!prettier) {
23
+ return null;
24
+ }
16
25
  try {
17
26
  const filepath = await prettier.resolveConfigFile();
18
27
  if (!filepath) {
@@ -76,18 +85,13 @@ function generatePrettierSetup(tree, options) {
76
85
  : (0, devkit_1.addDependenciesToPackageJson)(tree, {}, { prettier: versions_1.prettierVersion });
77
86
  }
78
87
  async function resolvePrettierConfigPath(tree) {
79
- let prettier;
80
- try {
81
- prettier = await import('prettier');
82
- }
83
- catch {
88
+ const prettier = await importPrettier();
89
+ if (!prettier) {
84
90
  return null;
85
91
  }
86
- if (prettier) {
87
- const filePath = await prettier.resolveConfigFile();
88
- if (filePath) {
89
- return filePath;
90
- }
92
+ const configFilePath = await prettier.resolveConfigFile();
93
+ if (configFilePath) {
94
+ return configFilePath;
91
95
  }
92
96
  if (!tree) {
93
97
  return null;
@@ -44,7 +44,10 @@ function getTypeCheckOptions(normalizedOptions) {
44
44
  };
45
45
  if (watch) {
46
46
  typeCheckOptions.incremental = true;
47
- typeCheckOptions.cacheDir = devkit_1.cacheDir;
47
+ // Scope the incremental .tsbuildinfo per project (in its own subdir, not
48
+ // alongside Nx's cache files) so concurrent watch type-checks don't collide
49
+ // on a single file.
50
+ typeCheckOptions.cacheDir = (0, node_path_1.join)(devkit_1.cacheDir, 'swc', projectRoot);
48
51
  }
49
52
  if (normalizedOptions.isTsSolutionSetup && normalizedOptions.skipTypeCheck) {
50
53
  typeCheckOptions.ignoreDiagnostics = true;
@@ -43,6 +43,10 @@ async function runTypeCheck(options) {
43
43
  options: {
44
44
  ...compilerOptions,
45
45
  incremental: true,
46
+ // Set after the spread so it overrides any user-set tsBuildInfoFile.
47
+ // This is a dedicated type-check program with Nx-injected options that
48
+ // differ from the real build, so its build info must not share a file
49
+ // with the build, or it corrupts the build's incremental cache.
46
50
  tsBuildInfoFile: path.join(cacheDir, '.tsbuildinfo'),
47
51
  },
48
52
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/js",
3
- "version": "23.0.1",
3
+ "version": "23.0.2",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "files": [
@@ -144,11 +144,11 @@
144
144
  "source-map-support": "0.5.19",
145
145
  "tinyglobby": "^0.2.12",
146
146
  "tslib": "^2.3.0",
147
- "@nx/devkit": "23.0.1",
148
- "@nx/workspace": "23.0.1"
147
+ "@nx/devkit": "23.0.2",
148
+ "@nx/workspace": "23.0.2"
149
149
  },
150
150
  "devDependencies": {
151
- "nx": "23.0.1"
151
+ "nx": "23.0.2"
152
152
  },
153
153
  "peerDependencies": {
154
154
  "@swc/cli": ">=0.6.0 <0.9.0",
@@ -1 +0,0 @@
1
- export declare function killTree(pid: number, signal: NodeJS.Signals): Promise<void>;
@@ -1,113 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.killTree = killTree;
4
- // Adapted from https://raw.githubusercontent.com/pkrumins/node-tree-kill/deee138/index.js
5
- const child_process_1 = require("child_process");
6
- async function killTree(pid, signal) {
7
- const tree = {};
8
- const pidsToProcess = {};
9
- tree[pid] = [];
10
- pidsToProcess[pid] = 1;
11
- return new Promise((resolve, reject) => {
12
- const callback = (error) => {
13
- if (error) {
14
- reject(error);
15
- }
16
- else {
17
- resolve();
18
- }
19
- };
20
- switch (process.platform) {
21
- case 'win32':
22
- (0, child_process_1.exec)('taskkill /pid ' + pid + ' /T /F', {
23
- windowsHide: true,
24
- }, (error) => {
25
- // Ignore Fatal errors (128) because it might be due to the process already being killed.
26
- // On Linux/Mac we can check ESRCH (no such process), but on Windows we can't.
27
- callback(error?.code !== 128 ? error : null);
28
- });
29
- break;
30
- case 'darwin':
31
- buildProcessTree(pid, tree, pidsToProcess, function (parentPid) {
32
- return (0, child_process_1.spawn)('pgrep', ['-P', parentPid], {
33
- windowsHide: true,
34
- });
35
- }, function () {
36
- killAll(tree, signal, callback);
37
- });
38
- break;
39
- default: // Linux
40
- buildProcessTree(pid, tree, pidsToProcess, function (parentPid) {
41
- return (0, child_process_1.spawn)('ps', ['-o', 'pid', '--no-headers', '--ppid', parentPid], {
42
- windowsHide: true,
43
- });
44
- }, function () {
45
- killAll(tree, signal, callback);
46
- });
47
- break;
48
- }
49
- });
50
- }
51
- function killAll(tree, signal, callback) {
52
- const killed = {};
53
- try {
54
- Object.keys(tree).forEach(function (pid) {
55
- tree[pid].forEach(function (pidpid) {
56
- if (!killed[pidpid]) {
57
- killPid(pidpid, signal);
58
- killed[pidpid] = 1;
59
- }
60
- });
61
- if (!killed[pid]) {
62
- killPid(pid, signal);
63
- killed[pid] = 1;
64
- }
65
- });
66
- }
67
- catch (err) {
68
- if (callback) {
69
- return callback(err);
70
- }
71
- else {
72
- throw err;
73
- }
74
- }
75
- if (callback) {
76
- return callback();
77
- }
78
- }
79
- function killPid(pid, signal) {
80
- try {
81
- process.kill(parseInt(pid, 10), signal);
82
- }
83
- catch (err) {
84
- if (err.code !== 'ESRCH')
85
- throw err;
86
- }
87
- }
88
- function buildProcessTree(parentPid, tree, pidsToProcess, spawnChildProcessesList, cb) {
89
- const ps = spawnChildProcessesList(parentPid);
90
- let allData = '';
91
- ps.stdout.on('data', (data) => {
92
- data = data.toString('ascii');
93
- allData += data;
94
- });
95
- const onClose = function (code) {
96
- delete pidsToProcess[parentPid];
97
- if (code != 0) {
98
- // no more parent processes
99
- if (Object.keys(pidsToProcess).length == 0) {
100
- cb();
101
- }
102
- return;
103
- }
104
- allData.match(/\d+/g).forEach((_pid) => {
105
- const pid = parseInt(_pid, 10);
106
- tree[parentPid].push(pid);
107
- tree[pid] = [];
108
- pidsToProcess[pid] = 1;
109
- buildProcessTree(pid, tree, pidsToProcess, spawnChildProcessesList, cb);
110
- });
111
- };
112
- ps.on('close', onClose);
113
- }