nx 23.2.0-beta.5 → 23.2.0-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/src/command-line/init/init-v2.js +2 -0
  2. package/dist/src/command-line/migrate/migrate.js +44 -27
  3. package/dist/src/command-line/migrate/version-skew-guard.d.ts +3 -4
  4. package/dist/src/command-line/migrate/version-skew-guard.js +5 -6
  5. package/dist/src/command-line/reset/reset.js +6 -1
  6. package/dist/src/command-line/run/run-one.js +1 -0
  7. package/dist/src/command-line/run-many/run-many.js +1 -0
  8. package/dist/src/core/graph/main.js +1 -1
  9. package/dist/src/core/graph/styles.js +1 -1
  10. package/dist/src/daemon/client/client.d.ts +45 -1
  11. package/dist/src/daemon/client/client.js +128 -32
  12. package/dist/src/daemon/client/daemon-socket-messenger.js +4 -0
  13. package/dist/src/daemon/message-types/daemon-message.d.ts +32 -0
  14. package/dist/src/daemon/message-types/daemon-message.js +25 -0
  15. package/dist/src/daemon/server/server.js +22 -0
  16. package/dist/src/daemon/server/shutdown-utils.d.ts +5 -0
  17. package/dist/src/daemon/server/shutdown-utils.js +11 -4
  18. package/dist/src/daemon/socket-utils.d.ts +1 -0
  19. package/dist/src/daemon/socket-utils.js +24 -3
  20. package/dist/src/daemon/tmp-dir.d.ts +43 -3
  21. package/dist/src/daemon/tmp-dir.js +437 -15
  22. package/dist/src/devkit-internals.d.ts +2 -1
  23. package/dist/src/devkit-internals.js +14 -5
  24. package/dist/src/generators/tree.d.ts +4 -2
  25. package/dist/src/native/index.d.ts +10 -0
  26. package/dist/src/native/index.js +71 -28
  27. package/dist/src/native/native-bindings.js +1 -0
  28. package/dist/src/native/native-file-cache-location.d.ts +17 -1
  29. package/dist/src/native/native-file-cache-location.js +86 -12
  30. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  31. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  32. package/dist/src/project-graph/plugins/isolation/isolated-plugin.d.ts +1 -0
  33. package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +18 -1
  34. package/dist/src/project-graph/plugins/isolation/messaging.d.ts +16 -3
  35. package/dist/src/project-graph/plugins/isolation/messaging.js +3 -0
  36. package/dist/src/project-graph/plugins/isolation/plugin-worker.js +58 -8
  37. package/dist/src/project-graph/project-graph.js +20 -0
  38. package/dist/src/utils/child-process.d.ts +31 -1
  39. package/dist/src/utils/child-process.js +102 -1
  40. package/dist/src/utils/ignore.d.ts +128 -1
  41. package/dist/src/utils/ignore.js +182 -12
  42. package/dist/src/utils/nx-tmp-dir.d.ts +18 -0
  43. package/dist/src/utils/nx-tmp-dir.js +40 -0
  44. package/dist/src/utils/owned-private-dir.d.ts +132 -0
  45. package/dist/src/utils/owned-private-dir.js +323 -0
  46. package/dist/src/utils/plugins/core-plugins.js +4 -0
  47. package/dist/src/utils/shell-quoting.d.ts +15 -0
  48. package/dist/src/utils/shell-quoting.js +35 -0
  49. package/dist/src/utils/wait-for-socket-connection.d.ts +11 -0
  50. package/dist/src/utils/wait-for-socket-connection.js +14 -8
  51. package/package.json +12 -11
@@ -2,6 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PseudoTtyProcess = void 0;
4
4
  exports.getRunNxBaseCommand = getRunNxBaseCommand;
5
+ exports.getNxBin = getNxBin;
6
+ exports.readInstalledNxBin = readInstalledNxBin;
7
+ exports.runNxArgvSync = runNxArgvSync;
5
8
  exports.runNxSync = runNxSync;
6
9
  exports.runNxAsync = runNxAsync;
7
10
  const child_process_1 = require("child_process");
@@ -10,11 +13,14 @@ const path_1 = require("path");
10
13
  const package_manager_1 = require("./package-manager");
11
14
  const workspace_root_1 = require("./workspace-root");
12
15
  const exit_codes_1 = require("./exit-codes");
16
+ const fileutils_1 = require("./fileutils");
17
+ const shell_quoting_1 = require("./shell-quoting");
13
18
  function getRunNxBaseCommand(packageManagerCommand, cwd = process.cwd()) {
14
19
  if ((0, fs_1.existsSync)((0, path_1.join)(workspace_root_1.workspaceRoot, 'package.json'))) {
15
20
  if (!packageManagerCommand) {
16
21
  // `readLocalNxVersion` (command-line/migrate/migrate.ts) mirrors this
17
- // selector to predict which nx the spawn will run; keep the two in sync.
22
+ // selector to predict which nx a spawn will run, for the workspaces
23
+ // `getNxBin` declines to resolve; keep the two in sync.
18
24
  const pm = (0, package_manager_1.detectPackageManager)(workspace_root_1.workspaceRoot);
19
25
  packageManagerCommand = (0, package_manager_1.getPackageManagerCommand)(pm, workspace_root_1.workspaceRoot);
20
26
  }
@@ -30,6 +36,101 @@ function getRunNxBaseCommand(packageManagerCommand, cwd = process.cwd()) {
30
36
  }
31
37
  }
32
38
  }
39
+ /**
40
+ * Locate an nx entry point to spawn for the workspace at `root`, so a caller
41
+ * can run it directly instead of going through a shell. `findInstalledNxBin`
42
+ * decides which one.
43
+ *
44
+ * Null means nothing may be spawned directly, leaving the caller to fall back
45
+ * to `getRunNxBaseCommand`. Null is therefore always safe: it costs the
46
+ * argument fidelity a direct spawn buys, never the ability to run.
47
+ */
48
+ function getNxBin(root = workspace_root_1.workspaceRoot) {
49
+ // A workspace with no root package.json runs nx through the `./nx` wrapper,
50
+ // which reinstalls `.nx/installation` whenever it drifts from nx.json's
51
+ // `installation.version`. Spawning the resolved entry point would skip that
52
+ // sync, and a migration is precisely when the version changes.
53
+ if (!(0, fs_1.existsSync)((0, path_1.join)(root, 'package.json'))) {
54
+ return null;
55
+ }
56
+ return findInstalledNxBin(root);
57
+ }
58
+ /**
59
+ * The entry point the nx installed directly under `dir` names, with no ascent
60
+ * to `dir`'s ancestors. For an installation that declares nx itself, such as
61
+ * the temp CLI `nx migrate` builds, an ancestor's nx is never the right answer.
62
+ */
63
+ function readInstalledNxBin(dir) {
64
+ const packageDir = (0, path_1.join)(dir, 'node_modules', 'nx');
65
+ const manifest = (0, path_1.join)(packageDir, 'package.json');
66
+ if (!(0, fs_1.existsSync)(manifest)) {
67
+ return null;
68
+ }
69
+ let bin;
70
+ try {
71
+ ({ bin } = (0, fileutils_1.readJsonFile)(manifest));
72
+ }
73
+ catch {
74
+ return null;
75
+ }
76
+ // npm accepts both the single-entry shorthand and the map form.
77
+ const entry = typeof bin === 'string' ? bin : bin?.nx;
78
+ return typeof entry === 'string' ? (0, path_1.join)(packageDir, entry) : null;
79
+ }
80
+ // Ascend to the nearest installed nx and take the entry point its `bin` field
81
+ // names, which is the file a package manager links into `node_modules/.bin`.
82
+ // Deliberately npx-shaped: npx and bun ascend unconditionally while pnpm and
83
+ // yarn stop at an outer workspace, so this can name an nx those two would
84
+ // decline to run.
85
+ //
86
+ // Deliberately not a resolver. Resolvers answer from NODE_PATH once their
87
+ // explicit paths miss, and `nxCliPath` (command-line/migrate/migrate.ts) points
88
+ // NODE_PATH at the temp installation before spawning it; they also answer
89
+ // through package self-reference, which hands back the running nx whatever
90
+ // `paths` they are given. Either one lets the temp installation hand off to
91
+ // itself, which for `--run-migrations` re-enters the same hand-off and respawns
92
+ // without end.
93
+ function findInstalledNxBin(root) {
94
+ for (let dir = root;; dir = (0, path_1.dirname)(dir)) {
95
+ // The nearest install wins, so an unusable manifest there ends the search
96
+ // rather than deferring to an ancestor.
97
+ if ((0, fs_1.existsSync)((0, path_1.join)(dir, 'node_modules', 'nx', 'package.json'))) {
98
+ return readInstalledNxBin(dir);
99
+ }
100
+ if (dir === (0, path_1.dirname)(dir)) {
101
+ return null;
102
+ }
103
+ }
104
+ }
105
+ /**
106
+ * Run a nx command, passing the arguments through as an argv array.
107
+ *
108
+ * When `getNxBin` names an entry point, the child is spawned directly with no
109
+ * shell in between, so every argument reaches the child exactly as provided:
110
+ * shell metacharacters (`(`, `%`, `^`, spaces, quotes) are data, not syntax.
111
+ * Otherwise falls back to the package-manager + shell path, where every
112
+ * argument goes through `quoteShellArg` and the Windows limits it documents
113
+ * apply.
114
+ */
115
+ function runNxArgvSync(argv, options) {
116
+ let { nxBin, ...spawnOptions } = options ?? {};
117
+ spawnOptions.cwd ??= process.cwd();
118
+ spawnOptions.windowsHide ??= true;
119
+ nxBin ??= getNxBin((0, workspace_root_1.workspaceRootInner)(spawnOptions.cwd, null) ?? workspace_root_1.workspaceRoot);
120
+ if (!nxBin) {
121
+ runNxSync(argv.map(shell_quoting_1.quoteShellArg).join(' '), spawnOptions);
122
+ return;
123
+ }
124
+ const result = (0, child_process_1.spawnSync)(process.execPath, [nxBin, ...argv], spawnOptions);
125
+ if (result.error) {
126
+ throw result.error;
127
+ }
128
+ if (result.status !== 0) {
129
+ const error = new Error(`Command failed: nx ${argv.join(' ')} (exit code ${result.status})`);
130
+ error.status = result.status ?? 1;
131
+ throw error;
132
+ }
133
+ }
33
134
  function runNxSync(cmd, options) {
34
135
  let { packageManagerCommand, ...execSyncOptions } = options ?? {};
35
136
  execSyncOptions.cwd ??= process.cwd();
@@ -1,7 +1,134 @@
1
1
  import ignore = require('ignore');
2
2
  import { Tree } from '../generators/tree';
3
3
  export declare function getIgnoreObject(root?: string): ReturnType<typeof ignore>;
4
- export declare function getIgnoreObjectForTree(tree: Tree): ignore.Ignore;
4
+ /** One directory's ignore files, and the directory its patterns are rooted at. */
5
+ export type ScopedIgnoreMatcher = {
6
+ /** Workspace-relative POSIX directory, `''` for the workspace root. */
7
+ dir: string;
8
+ /** How they relate is decided at build time - see `merge`. */
9
+ matchers: ReturnType<typeof ignore>[];
10
+ };
11
+ /**
12
+ * Resolves the ignore files that apply to a directory: its own and every one
13
+ * above it, up to the workspace root.
14
+ *
15
+ * Ignore files cascade - a `.gitignore` covers its own directory and below, and
16
+ * its patterns are relative to *itself*, not to the workspace root. Reading only
17
+ * the root file, which is what `getIgnoreObject` does, silently misses every
18
+ * nested one.
19
+ *
20
+ * A directory's answer is its own files plus its parent's, so every directory on
21
+ * the way up is memoized rather than only the one asked for: sibling leaves
22
+ * share the whole trunk, and a later walk stops at the first directory already
23
+ * known.
24
+ *
25
+ * `read` decides where the files come from - `tree.read` for a generator, disk
26
+ * for a caller with no tree - and returns an empty string or null when there is
27
+ * no such file. Paths handed to it are workspace-relative POSIX.
28
+ *
29
+ * `merge` decides how the files *within one directory* relate, and the two
30
+ * consumers genuinely need different rules:
31
+ *
32
+ * - `false` is prettier's: one matcher per file, any of them excluding wins, and
33
+ * a negation counts only if none excluded. `createIsIgnoredFunction` builds an
34
+ * ignorer per `--ignore-path` and ORs them, so a `!x` in `.prettierignore`
35
+ * cannot re-include an `x` that `.gitignore` excluded.
36
+ * - `true` is git's and the native walker's: all files in one matcher, so
37
+ * `.nxignore`'s `!x` removes `.gitignore`'s exclusion of `x` outright. It has
38
+ * to be a merge rather than a precedence check between separate matchers,
39
+ * because a lone `!x` in its own matcher reports an opinion on `x/` but *none*
40
+ * on `x/a.ts` (measured), so the exclusion would still reach the children.
41
+ * The merge only removes the exclusion within that one directory - a negation
42
+ * in a nested file still loses to an ancestor's exclusion.
43
+ *
44
+ * When `merge` is true, `filenames` order matters: they go into one matcher in
45
+ * order and the last matching pattern decides, so list them lowest-authority
46
+ * first.
47
+ */
48
+ export declare function createIgnoreChainResolver(read: (path: string) => string | null | undefined, filenames: string[], merge: boolean): (dir: string) => ScopedIgnoreMatcher[];
49
+ /**
50
+ * True when the file is ignored, resolving the chain nearest file first.
51
+ *
52
+ * Each matcher is tested against the path relative to its own directory, which
53
+ * is what makes a nested pattern like `/build` mean that directory's `build`
54
+ * rather than the workspace's.
55
+ *
56
+ * Nearest directory with an *opinion* wins, not the first match: a nested
57
+ * `!keep.log` must override the root's `*.log`, which is git's rule for files.
58
+ * A nested negation of a *directory* does not reach its children - see the
59
+ * `merge` note on `createIgnoreChainResolver`.
60
+ *
61
+ * How the files of one directory relate is decided when the chain is built - see
62
+ * that same note. Here they are simply the entry's matchers: any one excluding
63
+ * wins, and a negation counts only if none excluded.
64
+ *
65
+ * Two preconditions, both satisfied by a pruning walk and neither enforced:
66
+ *
67
+ * - `filePath` is workspace-relative POSIX and must sit under every `dir` in the
68
+ * chain, which holds when the chain came from that file's own directory.
69
+ * - No ancestor directory of `filePath` may itself be ignored. git refuses to
70
+ * re-include a file inside an excluded directory, and this does not implement
71
+ * that rule: asked directly about `dist/keep.ts` with a root `dist/` and a
72
+ * nested `dist/.gitignore` holding `!keep.ts`, it answers "not ignored" where
73
+ * git says ignored (measured). `visitNotIgnoredFiles` never asks, because it
74
+ * prunes `dist/` before descending - which is what makes its answers match
75
+ * git, and why that pruning is load-bearing for correctness rather than speed.
76
+ */
77
+ export declare function isIgnoredByChain(chain: ScopedIgnoreMatcher[], filePath: string): boolean;
78
+ /**
79
+ * Directories that should never be walked, whatever the workspace's own ignore
80
+ * files say - `node_modules`, `.git`, the nx caches.
81
+ *
82
+ * The list comes from the native walker rather than a second copy here, so a
83
+ * filesystem walk and a tree walk cannot drift apart.
84
+ *
85
+ * Checked ahead of the cascading chain rather than folded into it: these are not
86
+ * re-includable, and as ordinary patterns a nested negation could resurrect
87
+ * `node_modules`.
88
+ */
89
+ export declare function isAlwaysIgnored(path: string): boolean;
90
+ /**
91
+ * The chain bound to a tree, as predicates over workspace-relative POSIX paths.
92
+ *
93
+ * Files and directories are asked separately because the answers differ: a
94
+ * pattern is only directory-only if it ends in a slash, and `ignore` will not
95
+ * match `dist/` against the path `dist`. Callers must not have to know that, so
96
+ * the slash is appended inside and never leaves this module.
97
+ */
98
+ export type TreeIgnoreChecker = {
99
+ isIgnoredFile: (path: string) => boolean;
100
+ isIgnoredDirectory: (path: string) => boolean;
101
+ };
102
+ /**
103
+ * What git ignores, which is also what the native walker ignores.
104
+ *
105
+ * `.nxignore` outranks `.gitignore` - `walker.rs` registers it with
106
+ * `add_custom_ignore_filename` - which a merge with `.nxignore` last reproduces.
107
+ * git itself does not read it.
108
+ *
109
+ * Reads from the tree rather than disk because a generator can create or amend
110
+ * an ignore file in the same run, which would leave the on-disk copy stale.
111
+ */
112
+ export declare function createGitIgnoreChecker(tree: Tree): TreeIgnoreChecker;
113
+ /**
114
+ * What prettier ignores: the workspace root only, and one ignorer per
115
+ * `--ignore-path` ORed rather than merged (both measured), so a `!` in
116
+ * `.prettierignore` cannot re-include what `.gitignore` excluded. That is the
117
+ * CLI `nx format:check` shells out to.
118
+ *
119
+ * Not an exact match for that command: `isAlwaysIgnored` is wider than
120
+ * prettier's built-ins, and `format.ts` filters its own patterns through
121
+ * `.nxignore`, which this does not read.
122
+ *
123
+ * Reads from the tree rather than disk, as above.
124
+ */
125
+ export declare function createPrettierIgnoreChecker(tree: Tree): TreeIgnoreChecker;
126
+ /**
127
+ * `path.dirname` for the workspace-relative POSIX paths the chain is keyed by,
128
+ * except that the workspace root is `''` rather than `.` - that is the key
129
+ * `createIgnoreChainResolver` terminates on.
130
+ */
131
+ export declare function posixDirname(relativePath: string): string;
5
132
  /**
6
133
  * Adds an entry to a .gitignore file if it's not already covered by existing patterns.
7
134
  * Creates the file if it doesn't exist.
@@ -1,9 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getIgnoreObject = getIgnoreObject;
4
- exports.getIgnoreObjectForTree = getIgnoreObjectForTree;
4
+ exports.createIgnoreChainResolver = createIgnoreChainResolver;
5
+ exports.isIgnoredByChain = isIgnoredByChain;
6
+ exports.isAlwaysIgnored = isAlwaysIgnored;
7
+ exports.createGitIgnoreChecker = createGitIgnoreChecker;
8
+ exports.createPrettierIgnoreChecker = createPrettierIgnoreChecker;
9
+ exports.posixDirname = posixDirname;
5
10
  exports.addEntryToGitIgnore = addEntryToGitIgnore;
6
11
  const ignore = require("ignore");
12
+ const index_1 = require("../native/index");
7
13
  const fileutils_1 = require("./fileutils");
8
14
  const workspace_root_1 = require("./workspace-root");
9
15
  function getIgnoreObject(root = workspace_root_1.workspaceRoot) {
@@ -12,18 +18,182 @@ function getIgnoreObject(root = workspace_root_1.workspaceRoot) {
12
18
  ig.add((0, fileutils_1.readFileIfExisting)(`${root}/.nxignore`));
13
19
  return ig;
14
20
  }
15
- function getIgnoreObjectForTree(tree) {
16
- let ig;
17
- if (tree.exists('.gitignore')) {
18
- ig = ignore();
19
- ig.add('.git');
20
- ig.add(tree.read('.gitignore', 'utf-8'));
21
- }
22
- if (tree.exists('.nxignore')) {
23
- ig ??= ignore();
24
- ig.add(tree.read('.nxignore', 'utf-8'));
21
+ /**
22
+ * Resolves the ignore files that apply to a directory: its own and every one
23
+ * above it, up to the workspace root.
24
+ *
25
+ * Ignore files cascade - a `.gitignore` covers its own directory and below, and
26
+ * its patterns are relative to *itself*, not to the workspace root. Reading only
27
+ * the root file, which is what `getIgnoreObject` does, silently misses every
28
+ * nested one.
29
+ *
30
+ * A directory's answer is its own files plus its parent's, so every directory on
31
+ * the way up is memoized rather than only the one asked for: sibling leaves
32
+ * share the whole trunk, and a later walk stops at the first directory already
33
+ * known.
34
+ *
35
+ * `read` decides where the files come from - `tree.read` for a generator, disk
36
+ * for a caller with no tree - and returns an empty string or null when there is
37
+ * no such file. Paths handed to it are workspace-relative POSIX.
38
+ *
39
+ * `merge` decides how the files *within one directory* relate, and the two
40
+ * consumers genuinely need different rules:
41
+ *
42
+ * - `false` is prettier's: one matcher per file, any of them excluding wins, and
43
+ * a negation counts only if none excluded. `createIsIgnoredFunction` builds an
44
+ * ignorer per `--ignore-path` and ORs them, so a `!x` in `.prettierignore`
45
+ * cannot re-include an `x` that `.gitignore` excluded.
46
+ * - `true` is git's and the native walker's: all files in one matcher, so
47
+ * `.nxignore`'s `!x` removes `.gitignore`'s exclusion of `x` outright. It has
48
+ * to be a merge rather than a precedence check between separate matchers,
49
+ * because a lone `!x` in its own matcher reports an opinion on `x/` but *none*
50
+ * on `x/a.ts` (measured), so the exclusion would still reach the children.
51
+ * The merge only removes the exclusion within that one directory - a negation
52
+ * in a nested file still loses to an ancestor's exclusion.
53
+ *
54
+ * When `merge` is true, `filenames` order matters: they go into one matcher in
55
+ * order and the last matching pattern decides, so list them lowest-authority
56
+ * first.
57
+ */
58
+ function createIgnoreChainResolver(read, filenames, merge) {
59
+ const cache = new Map();
60
+ const resolve = (dir) => {
61
+ const cached = cache.get(dir);
62
+ if (cached) {
63
+ return cached;
64
+ }
65
+ const contents = filenames
66
+ .map((name) => read(dir ? `${dir}/${name}` : name))
67
+ .filter((c) => !!c);
68
+ const matchers = merge
69
+ ? contents.length > 0
70
+ ? [contents.reduce((m, c) => m.add(c), ignore())]
71
+ : []
72
+ : contents.map((c) => ignore().add(c));
73
+ const inherited = dir === '' ? [] : resolve(posixDirname(dir));
74
+ const chain = matchers.length > 0 ? [{ dir, matchers }, ...inherited] : inherited;
75
+ cache.set(dir, chain);
76
+ return chain;
77
+ };
78
+ return resolve;
79
+ }
80
+ /**
81
+ * True when the file is ignored, resolving the chain nearest file first.
82
+ *
83
+ * Each matcher is tested against the path relative to its own directory, which
84
+ * is what makes a nested pattern like `/build` mean that directory's `build`
85
+ * rather than the workspace's.
86
+ *
87
+ * Nearest directory with an *opinion* wins, not the first match: a nested
88
+ * `!keep.log` must override the root's `*.log`, which is git's rule for files.
89
+ * A nested negation of a *directory* does not reach its children - see the
90
+ * `merge` note on `createIgnoreChainResolver`.
91
+ *
92
+ * How the files of one directory relate is decided when the chain is built - see
93
+ * that same note. Here they are simply the entry's matchers: any one excluding
94
+ * wins, and a negation counts only if none excluded.
95
+ *
96
+ * Two preconditions, both satisfied by a pruning walk and neither enforced:
97
+ *
98
+ * - `filePath` is workspace-relative POSIX and must sit under every `dir` in the
99
+ * chain, which holds when the chain came from that file's own directory.
100
+ * - No ancestor directory of `filePath` may itself be ignored. git refuses to
101
+ * re-include a file inside an excluded directory, and this does not implement
102
+ * that rule: asked directly about `dist/keep.ts` with a root `dist/` and a
103
+ * nested `dist/.gitignore` holding `!keep.ts`, it answers "not ignored" where
104
+ * git says ignored (measured). `visitNotIgnoredFiles` never asks, because it
105
+ * prunes `dist/` before descending - which is what makes its answers match
106
+ * git, and why that pruning is load-bearing for correctness rather than speed.
107
+ */
108
+ function isIgnoredByChain(chain, filePath) {
109
+ for (const { dir, matchers } of chain) {
110
+ const relative = dir === '' ? filePath : filePath.slice(dir.length + 1);
111
+ let unignored = false;
112
+ for (const matcher of matchers) {
113
+ const result = matcher.test(relative);
114
+ if (result.ignored) {
115
+ return true;
116
+ }
117
+ unignored ||= result.unignored;
118
+ }
119
+ if (unignored) {
120
+ return false;
121
+ }
25
122
  }
26
- return ig;
123
+ return false;
124
+ }
125
+ let alwaysIgnored;
126
+ /**
127
+ * Directories that should never be walked, whatever the workspace's own ignore
128
+ * files say - `node_modules`, `.git`, the nx caches.
129
+ *
130
+ * The list comes from the native walker rather than a second copy here, so a
131
+ * filesystem walk and a tree walk cannot drift apart.
132
+ *
133
+ * Checked ahead of the cascading chain rather than folded into it: these are not
134
+ * re-includable, and as ordinary patterns a nested negation could resurrect
135
+ * `node_modules`.
136
+ */
137
+ function isAlwaysIgnored(path) {
138
+ alwaysIgnored ??= ignore().add((0, index_1.getHardcodedIgnorePatterns)());
139
+ return alwaysIgnored.ignores(path);
140
+ }
141
+ /**
142
+ * What git ignores, which is also what the native walker ignores.
143
+ *
144
+ * `.nxignore` outranks `.gitignore` - `walker.rs` registers it with
145
+ * `add_custom_ignore_filename` - which a merge with `.nxignore` last reproduces.
146
+ * git itself does not read it.
147
+ *
148
+ * Reads from the tree rather than disk because a generator can create or amend
149
+ * an ignore file in the same run, which would leave the on-disk copy stale.
150
+ */
151
+ function createGitIgnoreChecker(tree) {
152
+ return createTreeIgnoreChecker(tree, {
153
+ filenames: ['.gitignore', '.nxignore'],
154
+ cascade: true,
155
+ merge: true,
156
+ });
157
+ }
158
+ /**
159
+ * What prettier ignores: the workspace root only, and one ignorer per
160
+ * `--ignore-path` ORed rather than merged (both measured), so a `!` in
161
+ * `.prettierignore` cannot re-include what `.gitignore` excluded. That is the
162
+ * CLI `nx format:check` shells out to.
163
+ *
164
+ * Not an exact match for that command: `isAlwaysIgnored` is wider than
165
+ * prettier's built-ins, and `format.ts` filters its own patterns through
166
+ * `.nxignore`, which this does not read.
167
+ *
168
+ * Reads from the tree rather than disk, as above.
169
+ */
170
+ function createPrettierIgnoreChecker(tree) {
171
+ return createTreeIgnoreChecker(tree, {
172
+ filenames: ['.gitignore', '.prettierignore'],
173
+ cascade: false,
174
+ merge: false,
175
+ });
176
+ }
177
+ function createTreeIgnoreChecker(tree, { filenames, cascade, merge }) {
178
+ const resolve = createIgnoreChainResolver((path) => tree.read(path, 'utf-8'), filenames, merge);
179
+ // `probe` may carry a trailing slash; `path` never does. The chain is keyed by
180
+ // real directories, and `posixDirname('dist/')` is `'dist'` rather than the
181
+ // parent, so the lookup always uses the slash-less form.
182
+ const check = (path, probe) => isAlwaysIgnored(probe) ||
183
+ isIgnoredByChain(resolve(cascade ? posixDirname(path) : ''), probe);
184
+ return {
185
+ isIgnoredFile: (path) => check(path, path),
186
+ isIgnoredDirectory: (path) => check(path, `${path}/`),
187
+ };
188
+ }
189
+ /**
190
+ * `path.dirname` for the workspace-relative POSIX paths the chain is keyed by,
191
+ * except that the workspace root is `''` rather than `.` - that is the key
192
+ * `createIgnoreChainResolver` terminates on.
193
+ */
194
+ function posixDirname(relativePath) {
195
+ const separator = relativePath.lastIndexOf('/');
196
+ return separator === -1 ? '' : relativePath.slice(0, separator);
27
197
  }
28
198
  /**
29
199
  * Adds an entry to a .gitignore file if it's not already covered by existing patterns.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Stable root for Nx runtime artifacts that need an OS tmp location. On POSIX a
3
+ * literal `/tmp`, not `os.tmpdir()`, which honours `$TMPDIR` — per-user on
4
+ * macOS, rewritten by sandboxes, stripped from the daemon env, so client and
5
+ * daemon would disagree. Windows has no `/tmp`, and `%TMP%` is already per-user
6
+ * and stable, so it keeps `os.tmpdir()`.
7
+ *
8
+ * Consumed by the native binding loader, so keep this file limited to local
9
+ * helpers that themselves use Node builtins only.
10
+ */
11
+ export declare const NX_TMP_DIR: string;
12
+ /**
13
+ * Owner-only runtime root for the current user. No user segment on Windows —
14
+ * `%TMP%` is already per-account, and the segment would only cost path length.
15
+ */
16
+ export declare const NX_USER_TMP_DIR: string;
17
+ /** Runtime root under the user's home, used when the shared container cannot be established. */
18
+ export declare const NX_HOME_TMP_DIR: string;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NX_HOME_TMP_DIR = exports.NX_USER_TMP_DIR = exports.NX_TMP_DIR = void 0;
4
+ const node_os_1 = require("node:os");
5
+ const node_path_1 = require("node:path");
6
+ const owned_private_dir_1 = require("./owned-private-dir");
7
+ /**
8
+ * Stable root for Nx runtime artifacts that need an OS tmp location. On POSIX a
9
+ * literal `/tmp`, not `os.tmpdir()`, which honours `$TMPDIR` — per-user on
10
+ * macOS, rewritten by sandboxes, stripped from the daemon env, so client and
11
+ * daemon would disagree. Windows has no `/tmp`, and `%TMP%` is already per-user
12
+ * and stable, so it keeps `os.tmpdir()`.
13
+ *
14
+ * Consumed by the native binding loader, so keep this file limited to local
15
+ * helpers that themselves use Node builtins only.
16
+ */
17
+ exports.NX_TMP_DIR = (0, node_os_1.platform)() === 'win32' ? (0, node_path_1.join)((0, node_os_1.tmpdir)(), '.nx') : '/tmp/.nx';
18
+ /**
19
+ * Owner-only runtime root for the current user. No user segment on Windows —
20
+ * `%TMP%` is already per-account, and the segment would only cost path length.
21
+ */
22
+ exports.NX_USER_TMP_DIR = (0, node_os_1.platform)() === 'win32' ? exports.NX_TMP_DIR : (0, node_path_1.join)(exports.NX_TMP_DIR, (0, owned_private_dir_1.getUserSegment)());
23
+ /** Runtime root under the user's home, used when the shared container cannot be established. */
24
+ exports.NX_HOME_TMP_DIR = resolveHomeTmpDir();
25
+ /**
26
+ * Absolute, not merely non-empty: a relative `$HOME` would make `join` return
27
+ * `.nx`, putting sockets under the cwd and aiming `removeSocketDir`'s recursive
28
+ * delete at it. A rootless container has no `$HOME` and no passwd entry, so
29
+ * `homedir()` throws or returns empty — caught here because this runs at module
30
+ * scope and the native binding loader imports this file.
31
+ */
32
+ function resolveHomeTmpDir() {
33
+ try {
34
+ const home = (0, node_os_1.homedir)();
35
+ return home && (0, node_path_1.isAbsolute)(home) ? (0, node_path_1.join)(home, '.nx') : undefined;
36
+ }
37
+ catch {
38
+ return undefined;
39
+ }
40
+ }
@@ -0,0 +1,132 @@
1
+ declare const safeSharedRootBrand: unique symbol;
2
+ declare const sharedRootEstablishedBrand: unique symbol;
3
+ declare const ownedRealDirBrand: unique symbol;
4
+ declare const ownedPrivateDirBrand: unique symbol;
5
+ /** Verified safe to keep an owner-only directory under. Not created. */
6
+ export type SafeSharedRoot = string & {
7
+ readonly [safeSharedRootBrand]: true;
8
+ };
9
+ /** Created if absent, then verified safe as above. */
10
+ export type EstablishedSharedRoot = string & {
11
+ readonly [sharedRootEstablishedBrand]: true;
12
+ };
13
+ /** An existing real directory owned by us. Mode is *not* checked. */
14
+ export type OwnedRealDir = string & {
15
+ readonly [ownedRealDirBrand]: true;
16
+ };
17
+ /**
18
+ * POSIX: created if absent, owned by us, and carrying no group or other bits —
19
+ * re-locked first if it was looser. The mode is checked on whichever branch
20
+ * produced it, so the brand does not depend on who created the directory.
21
+ *
22
+ * Windows: only *is a real directory*. `getuid` is unavailable there, so
23
+ * neither ownership nor mode is checked; `%TMP%` is already per-account.
24
+ */
25
+ export type OwnedPrivateDir = string & {
26
+ readonly [ownedPrivateDirBrand]: true;
27
+ };
28
+ /** Why a guard refused a directory. Data, not a sentence: `remedyFor` decides on it. */
29
+ export type DirRefusal = {
30
+ kind: 'not-created';
31
+ dir: string;
32
+ code?: string;
33
+ } | {
34
+ kind: 'not-inspectable';
35
+ dir: string;
36
+ code?: string;
37
+ } | {
38
+ kind: 'not-a-directory';
39
+ dir: string;
40
+ symlink?: true;
41
+ } | {
42
+ kind: 'foreign-owner';
43
+ dir: string;
44
+ uid: number;
45
+ } | {
46
+ kind: 'foreign-shared-container';
47
+ dir: string;
48
+ uid: number;
49
+ } | {
50
+ kind: 'not-tightenable';
51
+ dir: string;
52
+ mode: number;
53
+ } | {
54
+ kind: 'peer-writable-not-sticky';
55
+ dir: string;
56
+ mode: number;
57
+ };
58
+ /** A guard's verdict. */
59
+ export type GuardResult<T> = {
60
+ status: 'ok';
61
+ path: T;
62
+ } | {
63
+ status: 'refused';
64
+ refusal: DirRefusal;
65
+ };
66
+ /** The user-facing sentence for a refusal. The only place wording is decided. */
67
+ export declare function describeRefusal(r: DirRefusal): string;
68
+ /** What the user can do about a refusal, or `undefined` when there is nothing. */
69
+ export declare function remedyFor(r: DirRefusal): string | undefined;
70
+ /** One refusal as an `Error`, so several can travel in an `AggregateError`. */
71
+ export declare class DirectoryRefusedError extends Error {
72
+ readonly refusal: DirRefusal;
73
+ constructor(refusal: DirRefusal);
74
+ }
75
+ /**
76
+ * Whether a shared container is safe to keep an owner-only directory under.
77
+ *
78
+ * On POSIX, a container writable by other users must be sticky, and it must be
79
+ * owned by either root or the current user. Windows short-circuits after the
80
+ * directory test — the OS temp root is already scoped to one account, so there
81
+ * is no shared level whose ownership could matter.
82
+ *
83
+ * Sticky directories still let the directory's own owner rename entries, so a
84
+ * container owned by another unprivileged user could replace a previously
85
+ * verified private directory beneath it.
86
+ */
87
+ export declare function isSafeSharedRoot(dir: string): GuardResult<SafeSharedRoot>;
88
+ /**
89
+ * Whether other users on this machine can write into `dir`. Gates whether a
90
+ * refusal message may cite other users, so it must not over-report: `false` on
91
+ * Windows, where libuv synthesizes `st_mode` from the READONLY attribute and
92
+ * reports an ordinary per-account directory as `0666`, and `false` on a path
93
+ * that cannot be inspected.
94
+ *
95
+ * `statSync`, not `lstatSync`: the question is about the directory that will be
96
+ * used, not the link pointing at it. Link modes mislead in both directions —
97
+ * Linux creates symlinks `0777`, macOS applies the umask. Nothing here decides
98
+ * whether a path is accepted, so following the link costs nothing the guards
99
+ * do not already re-check.
100
+ */
101
+ export declare function isPeerWritable(dir: string): boolean;
102
+ /**
103
+ * Create a shared container as sticky + world-writable if it does not exist,
104
+ * and report whether the resulting path is safe for the current user.
105
+ *
106
+ * **A container that already exists is never modified — only judged.** Do not
107
+ * chmod before deciding trust: `CAP_FOWNER` (root's default in Docker and most
108
+ * CI images) can chmod a directory it does not own, so a peer-owned root would
109
+ * be widened to `1777` and then refused, and an operator who deliberately
110
+ * tightened this root would have it re-widened on every `nx` process.
111
+ */
112
+ export declare function ensureSafeSharedRoot(dir: string): GuardResult<EstablishedSharedRoot>;
113
+ /**
114
+ * Whether `dir` is an existing real directory owned by us. Unlike
115
+ * `ensureOwnedPrivateDir` it creates nothing and repairs nothing — for callers
116
+ * that only want to know whether a path is safe to act on, such as deleting.
117
+ */
118
+ export declare function isOwnedRealDirectory(dir: string): OwnedRealDir | null;
119
+ /** The path segment separating one user's Nx runtime state from another's. */
120
+ export declare function getUserSegment(): string;
121
+ /**
122
+ * Ensure `dir` exists, is a real directory owned by us, and carries no group or
123
+ * other bits at all — read and search alone reach a socket inside it, so 0755 is
124
+ * re-locked rather than accepted. A `refused` status carries which check said
125
+ * no — usually `foreign-owner` for a directory another user planted, but also
126
+ * `not-created`/`not-inspectable` for a filesystem error, `not-a-directory`, or
127
+ * `not-tightenable` when the re-lock did not land.
128
+ *
129
+ * Node builtins only: reached from the native binding loader.
130
+ */
131
+ export declare function ensureOwnedPrivateDir(dir: string): GuardResult<OwnedPrivateDir>;
132
+ export {};