nx 23.2.0-beta.8 → 23.2.0-beta.9

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 (71) hide show
  1. package/dist/src/ai/set-up-ai-agents/set-up-ai-agents.js +2 -2
  2. package/dist/src/command-line/format/format.js +84 -101
  3. package/dist/src/command-line/import/import.js +12 -0
  4. package/dist/src/command-line/init/configure-plugins.js +4 -1
  5. package/dist/src/command-line/init/implementation/add-nx-to-nest.js +9 -0
  6. package/dist/src/command-line/init/implementation/add-nx-to-turborepo.js +3 -17
  7. package/dist/src/command-line/init/implementation/angular/index.js +4 -0
  8. package/dist/src/command-line/init/implementation/angular/legacy-angular-versions.js +2 -0
  9. package/dist/src/command-line/init/implementation/angular/standalone-workspace.js +8 -2
  10. package/dist/src/command-line/init/implementation/check-compatible-with-plugins.js +2 -0
  11. package/dist/src/command-line/init/implementation/dot-nx/add-nx-scripts.js +5 -0
  12. package/dist/src/command-line/init/implementation/format.d.ts +18 -0
  13. package/dist/src/command-line/init/implementation/format.js +114 -0
  14. package/dist/src/command-line/init/implementation/utils.js +8 -0
  15. package/dist/src/command-line/init/init-v1.js +7 -0
  16. package/dist/src/command-line/init/init-v2.js +11 -0
  17. package/dist/src/command-line/migrate/migrate.d.ts +4 -0
  18. package/dist/src/command-line/migrate/migrate.js +64 -24
  19. package/dist/src/command-line/release/version.js +2 -2
  20. package/dist/src/daemon/client/client.js +11 -2
  21. package/dist/src/devkit-internals.d.ts +5 -2
  22. package/dist/src/devkit-internals.js +14 -6
  23. package/dist/src/generators/internal-utils/{format-changed-files-with-prettier-if-available.d.ts → format-changed-files.d.ts} +6 -6
  24. package/dist/src/generators/internal-utils/format-changed-files.js +163 -0
  25. package/dist/src/generators/testing-utils/create-tree-with-empty-workspace.d.ts +15 -0
  26. package/dist/src/generators/testing-utils/create-tree-with-empty-workspace.js +21 -3
  27. package/dist/src/migrations/update-15-0-0/prefix-outputs.js +2 -2
  28. package/dist/src/migrations/update-16-0-0/remove-nrwl-cli.js +2 -2
  29. package/dist/src/migrations/update-16-0-0/update-depends-on-to-tokens.js +2 -2
  30. package/dist/src/migrations/update-16-0-0/update-nx-cloud-runner.js +2 -2
  31. package/dist/src/migrations/update-16-2-0/remove-run-commands-output-path.js +2 -2
  32. package/dist/src/migrations/update-17-0-0/rm-default-collection-npm-scope.js +2 -2
  33. package/dist/src/migrations/update-17-0-0/use-minimal-config-for-tasks-runner-options.js +2 -2
  34. package/dist/src/migrations/update-17-2-0/move-default-base.js +2 -2
  35. package/dist/src/migrations/update-18-0-0/disable-crystal-for-existing-workspaces.js +2 -2
  36. package/dist/src/migrations/update-19-2-4/set-project-name.js +2 -2
  37. package/dist/src/migrations/update-20-0-0/move-use-daemon-process.js +2 -2
  38. package/dist/src/migrations/update-20-0-1/use-legacy-cache.js +2 -2
  39. package/dist/src/migrations/update-21-0-0/release-changelog-config-changes.js +2 -2
  40. package/dist/src/migrations/update-21-0-0/release-version-config-changes.js +2 -2
  41. package/dist/src/migrations/update-21-0-0/remove-custom-tasks-runner.js +2 -2
  42. package/dist/src/migrations/update-21-0-0/remove-legacy-cache.js +2 -2
  43. package/dist/src/migrations/update-22-0-0/consolidate-release-tag-config.js +2 -2
  44. package/dist/src/migrations/update-22-0-0/release-version-config-changes.js +2 -2
  45. package/dist/src/migrations/update-22-2-0/add-self-healing-to-gitignore.js +2 -2
  46. package/dist/src/migrations/update-22-6-0/add-claude-settings-local-to-git-ignore.js +2 -2
  47. package/dist/src/migrations/update-22-6-0/add-claude-worktrees-to-git-ignore.js +2 -2
  48. package/dist/src/migrations/update-22-7-0/add-polygraph-to-git-ignore.js +2 -2
  49. package/dist/src/migrations/update-23-0-0/add-migrate-runs-to-git-ignore.js +2 -2
  50. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.js +2 -2
  51. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  52. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  53. package/dist/src/nx-cloud/generators/connect-to-nx-cloud/connect-to-nx-cloud.js +3 -3
  54. package/dist/src/utils/chunkify.d.ts +7 -1
  55. package/dist/src/utils/chunkify.js +10 -3
  56. package/dist/src/utils/formatters/index.d.ts +13 -0
  57. package/dist/src/utils/formatters/index.js +74 -0
  58. package/dist/src/utils/formatters/oxfmt.d.ts +32 -0
  59. package/dist/src/utils/formatters/oxfmt.js +764 -0
  60. package/dist/src/utils/formatters/prettier.d.ts +26 -0
  61. package/dist/src/utils/formatters/prettier.js +201 -0
  62. package/dist/src/utils/formatters/shared.d.ts +5 -0
  63. package/dist/src/utils/formatters/shared.js +8 -0
  64. package/dist/src/utils/ignore.d.ts +47 -5
  65. package/dist/src/utils/ignore.js +93 -11
  66. package/dist/src/utils/write-formatted-json-file.d.ts +3 -2
  67. package/dist/src/utils/write-formatted-json-file.js +5 -4
  68. package/package.json +11 -11
  69. package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.js +0 -88
  70. package/dist/src/utils/is-using-prettier.d.ts +0 -3
  71. package/dist/src/utils/is-using-prettier.js +0 -62
@@ -0,0 +1,26 @@
1
+ import type { Tree } from '../../generators/tree';
2
+ /**
3
+ * Config filenames prettier discovers. Exported because generator setup must
4
+ * agree with detection - a format missing from one side gets a second,
5
+ * redundant config written next to it.
6
+ * https://prettier.io/docs/configuration
7
+ */
8
+ export declare const prettierConfigFiles: string[];
9
+ export declare function isUsingPrettier(root: string): boolean;
10
+ export declare function isUsingPrettierInTree(tree: Tree): boolean;
11
+ /**
12
+ * Keeps only the files prettier can format. oxfmt needs no equivalent - it
13
+ * silently skips file types it does not handle.
14
+ */
15
+ export declare function filterToPrettierSupportedFiles(files: string[]): Promise<string[]>;
16
+ export declare function writeWithPrettier(patterns: string[], cwd?: string): void;
17
+ export declare function checkWithPrettier(patterns: string[]): Promise<string[]>;
18
+ export declare function getPrettierPath(): string;
19
+ /**
20
+ * Quote a pattern for prettier's shell-based exec calls; oxfmt uses execFile
21
+ * and takes raw paths.
22
+ *
23
+ * Exported so `nx format` can size its chunks against the quoted length -
24
+ * patterns are chunked before they get here, and quoting grows each one.
25
+ */
26
+ export declare function quoteForShell(pattern: string): string;
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prettierConfigFiles = void 0;
4
+ exports.isUsingPrettier = isUsingPrettier;
5
+ exports.isUsingPrettierInTree = isUsingPrettierInTree;
6
+ exports.filterToPrettierSupportedFiles = filterToPrettierSupportedFiles;
7
+ exports.writeWithPrettier = writeWithPrettier;
8
+ exports.checkWithPrettier = checkWithPrettier;
9
+ exports.getPrettierPath = getPrettierPath;
10
+ exports.quoteForShell = quoteForShell;
11
+ const node_child_process_1 = require("node:child_process");
12
+ const node_fs_1 = require("node:fs");
13
+ const node_path_1 = require("node:path");
14
+ const semver_1 = require("semver");
15
+ const json_1 = require("../../generators/utils/json");
16
+ const fileutils_1 = require("../fileutils");
17
+ const handle_import_1 = require("../handle-import");
18
+ const package_json_1 = require("../package-json");
19
+ const shared_1 = require("./shared");
20
+ /**
21
+ * Config filenames prettier discovers. Exported because generator setup must
22
+ * agree with detection - a format missing from one side gets a second,
23
+ * redundant config written next to it.
24
+ * https://prettier.io/docs/configuration
25
+ */
26
+ exports.prettierConfigFiles = [
27
+ '.prettierrc',
28
+ '.prettierrc.json',
29
+ '.prettierrc.yml',
30
+ '.prettierrc.yaml',
31
+ '.prettierrc.json5',
32
+ '.prettierrc.js',
33
+ 'prettier.config.js',
34
+ '.prettierrc.ts',
35
+ 'prettier.config.ts',
36
+ '.prettierrc.mjs',
37
+ 'prettier.config.mjs',
38
+ '.prettierrc.mts',
39
+ 'prettier.config.mts',
40
+ '.prettierrc.cjs',
41
+ 'prettier.config.cjs',
42
+ '.prettierrc.cts',
43
+ 'prettier.config.cts',
44
+ '.prettierrc.toml',
45
+ ];
46
+ function isUsingPrettier(root) {
47
+ for (const file of exports.prettierConfigFiles) {
48
+ if ((0, node_fs_1.existsSync)((0, node_path_1.join)(root, file))) {
49
+ return true;
50
+ }
51
+ }
52
+ // Even if no file is present, it is possible the user is configuring prettier via their package.json
53
+ const packageJsonPath = (0, node_path_1.join)(root, 'package.json');
54
+ if ((0, node_fs_1.existsSync)(packageJsonPath)) {
55
+ const packageJson = (0, fileutils_1.readJsonFile)(packageJsonPath);
56
+ if (packageJson.prettier) {
57
+ return true;
58
+ }
59
+ }
60
+ return false;
61
+ }
62
+ function isUsingPrettierInTree(tree) {
63
+ for (const file of exports.prettierConfigFiles) {
64
+ if (tree.exists(file)) {
65
+ return true;
66
+ }
67
+ }
68
+ // Even if no file is present, it is possible the user is configuring prettier via their package.json
69
+ if (tree.exists('package.json')) {
70
+ const packageJson = (0, json_1.readJson)(tree, 'package.json');
71
+ if (packageJson.prettier) {
72
+ return true;
73
+ }
74
+ }
75
+ return false;
76
+ }
77
+ /**
78
+ * Keeps only the files prettier can format. oxfmt needs no equivalent - it
79
+ * silently skips file types it does not handle.
80
+ */
81
+ async function filterToPrettierSupportedFiles(files) {
82
+ const prettier = await (0, handle_import_1.handleImport)('prettier');
83
+ const supportInfo = await prettier.getSupportInfo();
84
+ const supportedExtensions = new Set(supportInfo.languages
85
+ .flatMap((language) => language.extensions)
86
+ .filter((extension) => !!extension));
87
+ // Prettier matches ~30 files by *name* rather than extension (`.swcrc`,
88
+ // `Jakefile`). It publishes that list next to the extensions, so read it
89
+ // rather than hardcoding.
90
+ const supportedFilenames = new Set(supportInfo.languages.flatMap((language) => language.filenames ?? []));
91
+ return files.filter((file) => supportedExtensions.has((0, node_path_1.extname)(file)) ||
92
+ supportedFilenames.has((0, node_path_1.basename)(file)));
93
+ }
94
+ function writeWithPrettier(patterns,
95
+ // Defaults to the caller's cwd. `nx init` sets it so it can pass paths
96
+ // relative to the repo root rather than absolute ones the user has to read.
97
+ cwd) {
98
+ if (patterns.length === 0) {
99
+ // Prettier with no file arguments reads stdin, and `stdio: [0, 1, 2]` hands
100
+ // it nx's own - from a terminal it blocks forever with nothing on screen. At
101
+ // EOF it exits 0 under `--list-different` and 2 (which `execSync` throws on)
102
+ // without it, so which failure you get depends on `shouldUseListDifferent`.
103
+ return;
104
+ }
105
+ const prettierPath = getPrettierPath();
106
+ const listDifferentArg = shouldUseListDifferent() ? '--list-different ' : '';
107
+ // No `--parser json` special case for `.swcrc`: prettier's own language table
108
+ // maps it to the json parser, so it formats correctly on its own (measured).
109
+ // Splitting the batch also meant one of the two spawns could receive zero
110
+ // files, which is the error above.
111
+ (0, node_child_process_1.execSync)(`node ${quoteForShell(prettierPath)} --write ${listDifferentArg}-- ${patterns
112
+ .map(quoteForShell)
113
+ .join(' ')}`, {
114
+ cwd,
115
+ stdio: [0, 1, 2],
116
+ windowsHide: true,
117
+ });
118
+ }
119
+ function checkWithPrettier(patterns) {
120
+ const prettierPath = getPrettierPath();
121
+ return new Promise((resolve, reject) => {
122
+ (0, node_child_process_1.exec)(`node ${quoteForShell(prettierPath)} --list-different -- ${patterns
123
+ .map(quoteForShell)
124
+ .join(' ')}`, { encoding: 'utf-8', windowsHide: true, maxBuffer: shared_1.FORMATTER_MAX_BUFFER }, (error, stdout) => {
125
+ if (error) {
126
+ // As the oxfmt sibling: a spawn failure, kill or maxBuffer overrun reports a
127
+ // string `code` or none. Prettier signals "files differ" numerically, so
128
+ // treating these as a file list would pass `format:check` on a formatter
129
+ // that never ran.
130
+ if (typeof error['code'] !== 'number') {
131
+ reject(new Error(`prettier could not be run to completion (${error['code'] ?? error.signal ?? 'unknown'}): ${error.message}`));
132
+ return;
133
+ }
134
+ // Only `Mismatch` means "files differ", as in `checkWithOxfmt`. `Failure`
135
+ // still prints the files prettier got through - measured, one differing plus
136
+ // one unparseable exits 2 with the differing file on stdout - so reading
137
+ // stdout there would swallow the syntax error.
138
+ if (error['code'] !== 1 /* PrettierExitCode.Mismatch */ ||
139
+ stdout.length === 0) {
140
+ reject(error);
141
+ return;
142
+ }
143
+ resolve(stdout.trim().split('\n'));
144
+ }
145
+ else {
146
+ resolve([]);
147
+ }
148
+ });
149
+ });
150
+ }
151
+ let prettierPath;
152
+ function getPrettierPath() {
153
+ if (prettierPath) {
154
+ return prettierPath;
155
+ }
156
+ const { packageJson, path: packageJsonPath } = (0, package_json_1.readModulePackageJson)('prettier');
157
+ const bin = packageJson.bin;
158
+ const binPath = typeof bin === 'string' ? bin : bin?.['prettier'];
159
+ if (!binPath) {
160
+ throw new Error(`Could not find prettier binary in ${packageJsonPath}`);
161
+ }
162
+ prettierPath = (0, node_path_1.resolve)((0, node_path_1.dirname)(packageJsonPath), binPath);
163
+ return prettierPath;
164
+ }
165
+ let useListDifferent;
166
+ /**
167
+ * Determines if --list-different should be used with --write.
168
+ * Prettier 4+ and 3.6.x with experimental CLI don't support combining these flags.
169
+ */
170
+ function shouldUseListDifferent() {
171
+ if (useListDifferent !== undefined) {
172
+ return useListDifferent;
173
+ }
174
+ try {
175
+ const { packageJson } = (0, package_json_1.readModulePackageJson)('prettier');
176
+ const prettierMajor = (0, semver_1.major)(packageJson.version);
177
+ const isExperimentalCli = process.env.PRETTIER_EXPERIMENTAL_CLI === '1';
178
+ useListDifferent = prettierMajor < 4 && !isExperimentalCli;
179
+ }
180
+ catch {
181
+ useListDifferent = false;
182
+ }
183
+ return useListDifferent;
184
+ }
185
+ /**
186
+ * Quote a pattern for prettier's shell-based exec calls; oxfmt uses execFile
187
+ * and takes raw paths.
188
+ *
189
+ * Exported so `nx format` can size its chunks against the quoted length -
190
+ * patterns are chunked before they get here, and quoting grows each one.
191
+ */
192
+ function quoteForShell(pattern) {
193
+ // Interpolated into a command string, so every character special *inside
194
+ // double quotes* needs escaping, not just `$`: a backtick substitutes, `"`
195
+ // closes the quoting, `\` escapes what follows. One pass over the original,
196
+ // since `String.replace` never re-scans what it inserted.
197
+ const escaped = process.platform !== 'win32'
198
+ ? pattern.replace(/([\\"`$])/g, '\\$1')
199
+ : pattern;
200
+ return `"${escaped}"`;
201
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Formatters list every mismatching path on stdout, which overruns Node's 1MB
3
+ * default on a large workspace.
4
+ */
5
+ export declare const FORMATTER_MAX_BUFFER: number;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FORMATTER_MAX_BUFFER = void 0;
4
+ /**
5
+ * Formatters list every mismatching path on stdout, which overruns Node's 1MB
6
+ * default on a large workspace.
7
+ */
8
+ exports.FORMATTER_MAX_BUFFER = 50 * 1024 * 1024;
@@ -62,7 +62,7 @@ export declare function createIgnoreChainResolver(read: (path: string) => string
62
62
  * that same note. Here they are simply the entry's matchers: any one excluding
63
63
  * wins, and a negation counts only if none excluded.
64
64
  *
65
- * Two preconditions, both satisfied by a pruning walk and neither enforced:
65
+ * Two preconditions, neither enforced here:
66
66
  *
67
67
  * - `filePath` is workspace-relative POSIX and must sit under every `dir` in the
68
68
  * chain, which holds when the chain came from that file's own directory.
@@ -70,11 +70,28 @@ export declare function createIgnoreChainResolver(read: (path: string) => string
70
70
  * re-include a file inside an excluded directory, and this does not implement
71
71
  * that rule: asked directly about `dist/keep.ts` with a root `dist/` and a
72
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.
73
+ * git says ignored (measured). A pruning walk like `visitNotIgnoredFiles`
74
+ * satisfies it by never asking about anything under `dist/`; a per-file
75
+ * caller cannot, so it goes through `createAncestorAwareIgnoreChecker`,
76
+ * which checks the ancestors before asking the chain.
76
77
  */
77
78
  export declare function isIgnoredByChain(chain: ScopedIgnoreMatcher[], filePath: string): boolean;
79
+ /**
80
+ * The chain's answers plus git's excluded-ancestor rule: nothing inside an
81
+ * ignored directory can be re-included, so a nested negation must not
82
+ * resurrect a file whose ancestor an outer file excluded - the case
83
+ * `isIgnoredByChain` alone gets wrong (see its second precondition). The
84
+ * oxfmt CLI follows the same rule (measured against 0.60.0: a scan skips a
85
+ * nested `!keep.ts` under a root-ignored `dist/`), so every per-file caller -
86
+ * the cascading tree checkers and the disk-backed resolver in
87
+ * `formatters/oxfmt.ts` - goes through here.
88
+ *
89
+ * A directory's own ignore files cannot un-ignore the directory itself, so
90
+ * its verdict comes from its parent's chain - probed with a trailing slash,
91
+ * which is what makes a directory-only pattern like `dist/` match. Verdicts
92
+ * are memoized per directory, so a batch of files shares its ancestor walks.
93
+ */
94
+ export declare function createAncestorAwareIgnoreChecker(resolve: (dir: string) => ScopedIgnoreMatcher[]): TreeIgnoreChecker;
78
95
  /**
79
96
  * Directories that should never be walked, whatever the workspace's own ignore
80
97
  * files say - `node_modules`, `.git`, the nx caches.
@@ -88,7 +105,7 @@ export declare function isIgnoredByChain(chain: ScopedIgnoreMatcher[], filePath:
88
105
  */
89
106
  export declare function isAlwaysIgnored(path: string): boolean;
90
107
  /**
91
- * The chain bound to a tree, as predicates over workspace-relative POSIX paths.
108
+ * A chain's answers as predicates over workspace-relative POSIX paths.
92
109
  *
93
110
  * Files and directories are asked separately because the answers differ: a
94
111
  * pattern is only directory-only if it ends in a slash, and `ignore` will not
@@ -123,6 +140,31 @@ export declare function createGitIgnoreChecker(tree: Tree): TreeIgnoreChecker;
123
140
  * Reads from the tree rather than disk, as above.
124
141
  */
125
142
  export declare function createPrettierIgnoreChecker(tree: Tree): TreeIgnoreChecker;
143
+ /**
144
+ * What oxfmt ignores: prettier's two files, but resolved from each file's own
145
+ * directory upwards rather than the workspace root - measured against the
146
+ * oxfmt 0.60.0 CLI, which differs from prettier on exactly that axis. Still
147
+ * one matcher per file rather than merged.
148
+ *
149
+ * A config's `ignorePatterns` is not an ignore file and is not read here;
150
+ * `formatFilesWithOxfmt` applies it rooted at that config's directory.
151
+ */
152
+ export declare function createOxfmtIgnoreChecker(tree: Tree): TreeIgnoreChecker;
153
+ /**
154
+ * Exported, unlike git's and prettier's, because oxfmt has two consumers:
155
+ * this tree-backed checker and the disk-backed resolver in
156
+ * `formatters/oxfmt.ts`. A shared value is the only thing that keeps them
157
+ * agreeing, so do not restate these three anywhere.
158
+ *
159
+ * `satisfies` rather than an annotation keeps the values literal - an
160
+ * annotation widens `cascade` and `merge` to `boolean` (measured in the
161
+ * declaration emit).
162
+ */
163
+ export declare const OXFMT_IGNORE_OPTIONS: {
164
+ filenames: string[];
165
+ cascade: true;
166
+ merge: false;
167
+ };
126
168
  /**
127
169
  * `path.dirname` for the workspace-relative POSIX paths the chain is keyed by,
128
170
  * except that the workspace root is `''` rather than `.` - that is the key
@@ -1,11 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OXFMT_IGNORE_OPTIONS = void 0;
3
4
  exports.getIgnoreObject = getIgnoreObject;
4
5
  exports.createIgnoreChainResolver = createIgnoreChainResolver;
5
6
  exports.isIgnoredByChain = isIgnoredByChain;
7
+ exports.createAncestorAwareIgnoreChecker = createAncestorAwareIgnoreChecker;
6
8
  exports.isAlwaysIgnored = isAlwaysIgnored;
7
9
  exports.createGitIgnoreChecker = createGitIgnoreChecker;
8
10
  exports.createPrettierIgnoreChecker = createPrettierIgnoreChecker;
11
+ exports.createOxfmtIgnoreChecker = createOxfmtIgnoreChecker;
9
12
  exports.posixDirname = posixDirname;
10
13
  exports.addEntryToGitIgnore = addEntryToGitIgnore;
11
14
  const ignore = require("ignore");
@@ -93,7 +96,7 @@ function createIgnoreChainResolver(read, filenames, merge) {
93
96
  * that same note. Here they are simply the entry's matchers: any one excluding
94
97
  * wins, and a negation counts only if none excluded.
95
98
  *
96
- * Two preconditions, both satisfied by a pruning walk and neither enforced:
99
+ * Two preconditions, neither enforced here:
97
100
  *
98
101
  * - `filePath` is workspace-relative POSIX and must sit under every `dir` in the
99
102
  * chain, which holds when the chain came from that file's own directory.
@@ -101,9 +104,10 @@ function createIgnoreChainResolver(read, filenames, merge) {
101
104
  * re-include a file inside an excluded directory, and this does not implement
102
105
  * that rule: asked directly about `dist/keep.ts` with a root `dist/` and a
103
106
  * 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
+ * git says ignored (measured). A pruning walk like `visitNotIgnoredFiles`
108
+ * satisfies it by never asking about anything under `dist/`; a per-file
109
+ * caller cannot, so it goes through `createAncestorAwareIgnoreChecker`,
110
+ * which checks the ancestors before asking the chain.
107
111
  */
108
112
  function isIgnoredByChain(chain, filePath) {
109
113
  for (const { dir, matchers } of chain) {
@@ -122,6 +126,45 @@ function isIgnoredByChain(chain, filePath) {
122
126
  }
123
127
  return false;
124
128
  }
129
+ /**
130
+ * The chain's answers plus git's excluded-ancestor rule: nothing inside an
131
+ * ignored directory can be re-included, so a nested negation must not
132
+ * resurrect a file whose ancestor an outer file excluded - the case
133
+ * `isIgnoredByChain` alone gets wrong (see its second precondition). The
134
+ * oxfmt CLI follows the same rule (measured against 0.60.0: a scan skips a
135
+ * nested `!keep.ts` under a root-ignored `dist/`), so every per-file caller -
136
+ * the cascading tree checkers and the disk-backed resolver in
137
+ * `formatters/oxfmt.ts` - goes through here.
138
+ *
139
+ * A directory's own ignore files cannot un-ignore the directory itself, so
140
+ * its verdict comes from its parent's chain - probed with a trailing slash,
141
+ * which is what makes a directory-only pattern like `dist/` match. Verdicts
142
+ * are memoized per directory, so a batch of files shares its ancestor walks.
143
+ */
144
+ function createAncestorAwareIgnoreChecker(resolve) {
145
+ const ignoredDirs = new Map();
146
+ const isIgnoredDirectory = (dir) => {
147
+ if (dir === '') {
148
+ return false;
149
+ }
150
+ const cached = ignoredDirs.get(dir);
151
+ if (cached !== undefined) {
152
+ return cached;
153
+ }
154
+ const parent = posixDirname(dir);
155
+ const ignored = isIgnoredDirectory(parent) ||
156
+ isIgnoredByChain(resolve(parent), `${dir}/`);
157
+ ignoredDirs.set(dir, ignored);
158
+ return ignored;
159
+ };
160
+ return {
161
+ isIgnoredFile: (filePath) => {
162
+ const dir = posixDirname(filePath);
163
+ return (isIgnoredDirectory(dir) || isIgnoredByChain(resolve(dir), filePath));
164
+ },
165
+ isIgnoredDirectory,
166
+ };
167
+ }
125
168
  let alwaysIgnored;
126
169
  /**
127
170
  * Directories that should never be walked, whatever the workspace's own ignore
@@ -174,16 +217,55 @@ function createPrettierIgnoreChecker(tree) {
174
217
  merge: false,
175
218
  });
176
219
  }
220
+ /**
221
+ * What oxfmt ignores: prettier's two files, but resolved from each file's own
222
+ * directory upwards rather than the workspace root - measured against the
223
+ * oxfmt 0.60.0 CLI, which differs from prettier on exactly that axis. Still
224
+ * one matcher per file rather than merged.
225
+ *
226
+ * A config's `ignorePatterns` is not an ignore file and is not read here;
227
+ * `formatFilesWithOxfmt` applies it rooted at that config's directory.
228
+ */
229
+ function createOxfmtIgnoreChecker(tree) {
230
+ return createTreeIgnoreChecker(tree, exports.OXFMT_IGNORE_OPTIONS);
231
+ }
232
+ /**
233
+ * Exported, unlike git's and prettier's, because oxfmt has two consumers:
234
+ * this tree-backed checker and the disk-backed resolver in
235
+ * `formatters/oxfmt.ts`. A shared value is the only thing that keeps them
236
+ * agreeing, so do not restate these three anywhere.
237
+ *
238
+ * `satisfies` rather than an annotation keeps the values literal - an
239
+ * annotation widens `cascade` and `merge` to `boolean` (measured in the
240
+ * declaration emit).
241
+ */
242
+ exports.OXFMT_IGNORE_OPTIONS = {
243
+ filenames: ['.gitignore', '.prettierignore'],
244
+ cascade: true,
245
+ merge: false,
246
+ };
177
247
  function createTreeIgnoreChecker(tree, { filenames, cascade, merge }) {
178
248
  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);
249
+ if (cascade) {
250
+ // Cascading resolution and the excluded-ancestor rule travel together:
251
+ // both come from git, and the one cascading tool that is not git - oxfmt -
252
+ // applies both (measured, see `createAncestorAwareIgnoreChecker`).
253
+ const checker = createAncestorAwareIgnoreChecker(resolve);
254
+ return {
255
+ isIgnoredFile: (path) => isAlwaysIgnored(path) || checker.isIgnoredFile(path),
256
+ // The trailing slash is what makes a directory-only pattern like `dist/`
257
+ // match - `ignore` will not match it against the bare `dist`.
258
+ isIgnoredDirectory: (path) => isAlwaysIgnored(`${path}/`) || checker.isIgnoredDirectory(path),
259
+ };
260
+ }
261
+ // Root-only: the whole chain is the root's, so no ancestor holds ignore
262
+ // files of its own, and within one matcher `ignore` itself refuses to
263
+ // re-include under an excluded directory (measured). Nothing is left for an
264
+ // ancestor walk to add.
265
+ const check = (probe) => isAlwaysIgnored(probe) || isIgnoredByChain(resolve(''), probe);
184
266
  return {
185
- isIgnoredFile: (path) => check(path, path),
186
- isIgnoredDirectory: (path) => check(path, `${path}/`),
267
+ isIgnoredFile: (path) => check(path),
268
+ isIgnoredDirectory: (path) => check(`${path}/`),
187
269
  };
188
270
  }
189
271
  /**
@@ -1,6 +1,7 @@
1
1
  import { type JsonWriteOptions } from './fileutils';
2
2
  /**
3
- * Writes a JSON file, formatting with Prettier if available, otherwise
4
- * falling back to standard JSON serialization.
3
+ * Writes a JSON file, formatting it with whichever formatter the workspace is
4
+ * configured for, and falling back to standard JSON serialization when there
5
+ * is none or it cannot format the file.
5
6
  */
6
7
  export declare function writeFormattedJsonFile<T extends object = object>(filePath: string, content: T, options?: JsonWriteOptions): Promise<void>;
@@ -2,16 +2,17 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.writeFormattedJsonFile = writeFormattedJsonFile;
4
4
  const node_fs_1 = require("node:fs");
5
- const format_changed_files_with_prettier_if_available_1 = require("../generators/internal-utils/format-changed-files-with-prettier-if-available");
5
+ const format_changed_files_1 = require("../generators/internal-utils/format-changed-files");
6
6
  const json_1 = require("./json");
7
7
  const fileutils_1 = require("./fileutils");
8
8
  const workspace_root_1 = require("./workspace-root");
9
9
  /**
10
- * Writes a JSON file, formatting with Prettier if available, otherwise
11
- * falling back to standard JSON serialization.
10
+ * Writes a JSON file, formatting it with whichever formatter the workspace is
11
+ * configured for, and falling back to standard JSON serialization when there
12
+ * is none or it cannot format the file.
12
13
  */
13
14
  async function writeFormattedJsonFile(filePath, content, options) {
14
- const formattedContent = await (0, format_changed_files_with_prettier_if_available_1.formatFilesWithPrettierIfAvailable)([{ path: filePath, content: (0, json_1.serializeJson)(content) }], workspace_root_1.workspaceRoot, { silent: true });
15
+ const formattedContent = await (0, format_changed_files_1.formatFileContents)([{ path: filePath, content: (0, json_1.serializeJson)(content) }], workspace_root_1.workspaceRoot, { silent: true });
15
16
  if (formattedContent.has(filePath)) {
16
17
  (0, node_fs_1.writeFileSync)(filePath, formattedContent.get(filePath), {
17
18
  encoding: 'utf-8',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nx",
3
- "version": "23.2.0-beta.8",
3
+ "version": "23.2.0-beta.9",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "description": "The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.",
@@ -185,16 +185,16 @@
185
185
  }
186
186
  },
187
187
  "optionalDependencies": {
188
- "@nx/nx-darwin-arm64": "23.2.0-beta.8",
189
- "@nx/nx-darwin-x64": "23.2.0-beta.8",
190
- "@nx/nx-freebsd-x64": "23.2.0-beta.8",
191
- "@nx/nx-linux-arm-gnueabihf": "23.2.0-beta.8",
192
- "@nx/nx-linux-arm64-gnu": "23.2.0-beta.8",
193
- "@nx/nx-linux-arm64-musl": "23.2.0-beta.8",
194
- "@nx/nx-linux-x64-gnu": "23.2.0-beta.8",
195
- "@nx/nx-linux-x64-musl": "23.2.0-beta.8",
196
- "@nx/nx-win32-arm64-msvc": "23.2.0-beta.8",
197
- "@nx/nx-win32-x64-msvc": "23.2.0-beta.8"
188
+ "@nx/nx-darwin-arm64": "23.2.0-beta.9",
189
+ "@nx/nx-darwin-x64": "23.2.0-beta.9",
190
+ "@nx/nx-freebsd-x64": "23.2.0-beta.9",
191
+ "@nx/nx-linux-arm-gnueabihf": "23.2.0-beta.9",
192
+ "@nx/nx-linux-arm64-gnu": "23.2.0-beta.9",
193
+ "@nx/nx-linux-arm64-musl": "23.2.0-beta.9",
194
+ "@nx/nx-linux-x64-gnu": "23.2.0-beta.9",
195
+ "@nx/nx-linux-x64-musl": "23.2.0-beta.9",
196
+ "@nx/nx-win32-arm64-msvc": "23.2.0-beta.9",
197
+ "@nx/nx-win32-x64-msvc": "23.2.0-beta.9"
198
198
  },
199
199
  "nx-migrations": {
200
200
  "migrations": "./migrations.json",
@@ -1,88 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatChangedFilesWithPrettierIfAvailable = formatChangedFilesWithPrettierIfAvailable;
4
- exports.formatFilesWithPrettierIfAvailable = formatFilesWithPrettierIfAvailable;
5
- const tslib_1 = require("tslib");
6
- const path = tslib_1.__importStar(require("path"));
7
- const is_using_prettier_1 = require("../../utils/is-using-prettier");
8
- const path_1 = require("../../utils/path");
9
- const installation_directory_1 = require("../../utils/installation-directory");
10
- /**
11
- * Formats all the created or updated files using Prettier
12
- * @param tree - the file system tree
13
- *
14
- * @remarks
15
- * Set the environment variable `NX_SKIP_FORMAT` to `true` to skip Prettier
16
- * formatting. This is useful for repositories that use alternative formatters
17
- * like Biome, dprint, or have custom formatting requirements.
18
- */
19
- async function formatChangedFilesWithPrettierIfAvailable(tree, options) {
20
- if (process.env.NX_SKIP_FORMAT === 'true') {
21
- return;
22
- }
23
- const excludedPaths = options?.excludePaths
24
- ? new Set(Array.from(options.excludePaths, path_1.normalizePath))
25
- : undefined;
26
- const files = new Set(tree
27
- .listChanges()
28
- .filter((file) => file.type !== 'DELETE' &&
29
- !excludedPaths?.has((0, path_1.normalizePath)(file.path))));
30
- const results = await formatFilesWithPrettierIfAvailable(Array.from(files), tree.root, options);
31
- for (const [path, content] of results) {
32
- tree.write(path, content);
33
- }
34
- }
35
- async function formatFilesWithPrettierIfAvailable(files, root, options) {
36
- const results = new Map();
37
- // Check here as well for direct callers of this function
38
- if (process.env.NX_SKIP_FORMAT === 'true' || files.length === 0) {
39
- return results;
40
- }
41
- let prettier;
42
- try {
43
- const prettierPath = require.resolve('prettier', {
44
- paths: [...(0, installation_directory_1.getNxRequirePaths)(root), __dirname],
45
- });
46
- prettier = require(prettierPath);
47
- /**
48
- * Even after we discovered prettier in node_modules, we need to be sure that the user is intentionally using prettier
49
- * before proceeding to format with it.
50
- */
51
- if (!(0, is_using_prettier_1.isUsingPrettier)(root)) {
52
- return results;
53
- }
54
- }
55
- catch { }
56
- if (!prettier) {
57
- return results;
58
- }
59
- await Promise.all(Array.from(files).map(async (file) => {
60
- try {
61
- const systemPath = path.join(root, file.path);
62
- let options = {
63
- filepath: systemPath,
64
- };
65
- const resolvedOptions = await prettier.resolveConfig(systemPath, {
66
- editorconfig: true,
67
- });
68
- if (!resolvedOptions) {
69
- return;
70
- }
71
- options = {
72
- ...options,
73
- ...resolvedOptions,
74
- };
75
- const support = await prettier.getFileInfo(systemPath, options);
76
- if (support.ignored || !support.inferredParser) {
77
- return;
78
- }
79
- results.set(file.path, await prettier.format(file.content.toString('utf-8'), options));
80
- }
81
- catch (e) {
82
- if (!options?.silent) {
83
- console.warn(`Could not format ${file.path}. Error: "${e.message}"`);
84
- }
85
- }
86
- }));
87
- return results;
88
- }
@@ -1,3 +0,0 @@
1
- import type { Tree } from '../generators/tree';
2
- export declare function isUsingPrettier(root: string): boolean;
3
- export declare function isUsingPrettierInTree(tree: Tree): boolean;