nx 23.1.0 → 23.1.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.
Files changed (223) hide show
  1. package/dist/bin/nx.js +2 -1
  2. package/dist/release/changelog-renderer/index.js +4 -5
  3. package/dist/release/index.d.ts +1 -1
  4. package/dist/src/ai/configure-ai-agents-disclaimer.d.ts +5 -0
  5. package/dist/src/ai/configure-ai-agents-disclaimer.js +31 -0
  6. package/dist/src/analytics/analytics.d.ts +6 -0
  7. package/dist/src/analytics/analytics.js +16 -8
  8. package/dist/src/analytics/index.d.ts +1 -1
  9. package/dist/src/analytics/index.js +2 -1
  10. package/dist/src/command-line/add/add.js +13 -1
  11. package/dist/src/command-line/affected/affected.js +1 -0
  12. package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +2 -2
  13. package/dist/src/command-line/graph/graph.d.ts +1 -0
  14. package/dist/src/command-line/graph/graph.js +15 -6
  15. package/dist/src/command-line/import/import.js +2 -3
  16. package/dist/src/command-line/init/ai-agent-prompts.d.ts +1 -1
  17. package/dist/src/command-line/init/ai-agent-prompts.js +7 -6
  18. package/dist/src/command-line/init/command-object.js +10 -2
  19. package/dist/src/command-line/init/configure-plugins.js +3 -2
  20. package/dist/src/command-line/init/implementation/add-nx-to-monorepo.js +4 -2
  21. package/dist/src/command-line/init/implementation/add-nx-to-nest.js +4 -3
  22. package/dist/src/command-line/init/implementation/add-nx-to-npm-repo.js +4 -3
  23. package/dist/src/command-line/init/implementation/add-nx-to-turborepo.js +4 -3
  24. package/dist/src/command-line/init/implementation/angular/index.js +4 -2
  25. package/dist/src/command-line/init/implementation/angular/integrated-workspace.js +2 -5
  26. package/dist/src/command-line/init/implementation/angular/legacy-angular-versions.js +3 -0
  27. package/dist/src/command-line/init/implementation/dot-nx/add-nx-scripts.js +4 -1
  28. package/dist/src/command-line/init/implementation/utils.d.ts +3 -3
  29. package/dist/src/command-line/init/implementation/utils.js +22 -3
  30. package/dist/src/command-line/init/init-v2.js +4 -5
  31. package/dist/src/command-line/migrate/execute-migration.d.ts +72 -0
  32. package/dist/src/command-line/migrate/execute-migration.js +389 -0
  33. package/dist/src/command-line/migrate/migrate-ui-api.js +17 -12
  34. package/dist/src/command-line/migrate/migrate.d.ts +5 -45
  35. package/dist/src/command-line/migrate/migrate.js +122 -402
  36. package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +1 -1
  37. package/dist/src/command-line/nx-cloud/connect/view-logs.js +2 -0
  38. package/dist/src/command-line/release/changelog.js +5 -0
  39. package/dist/src/command-line/release/index.d.ts +1 -1
  40. package/dist/src/command-line/release/utils/git.js +7 -3
  41. package/dist/src/command-line/release/utils/release-graph.d.ts +19 -2
  42. package/dist/src/command-line/release/utils/release-graph.js +71 -19
  43. package/dist/src/command-line/release/utils/remote-release-clients/github.js +7 -1
  44. package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +1 -1
  45. package/dist/src/command-line/release/utils/shared.d.ts +2 -2
  46. package/dist/src/command-line/release/version/release-group-processor.d.ts +24 -0
  47. package/dist/src/command-line/release/version/release-group-processor.js +80 -6
  48. package/dist/src/command-line/release/version/version-actions.d.ts +27 -2
  49. package/dist/src/command-line/release/version/version-actions.js +9 -1
  50. package/dist/src/command-line/release/version.js +9 -5
  51. package/dist/src/command-line/reset/reset.js +6 -1
  52. package/dist/src/command-line/run/run-one.js +1 -0
  53. package/dist/src/command-line/run-many/run-many.js +1 -0
  54. package/dist/src/command-line/show/show-target/info.js +11 -2
  55. package/dist/src/command-line/show/show-target/inputs.js +50 -153
  56. package/dist/src/command-line/show/show-target/outputs.js +48 -173
  57. package/dist/src/command-line/show/show-target/utils.d.ts +28 -0
  58. package/dist/src/command-line/show/show-target/utils.js +83 -3
  59. package/dist/src/command-line/watch/watch.js +13 -0
  60. package/dist/src/command-line/yargs-utils/shared-options.d.ts +1 -1
  61. package/dist/src/commands-runner/create-command-graph.js +5 -0
  62. package/dist/src/config/nx-json.d.ts +5 -3
  63. package/dist/src/core/graph/main.js +1 -1
  64. package/dist/src/core/graph/styles.js +1 -1
  65. package/dist/src/daemon/client/client.d.ts +52 -3
  66. package/dist/src/daemon/client/client.js +177 -55
  67. package/dist/src/daemon/client/daemon-environment.d.ts +17 -0
  68. package/dist/src/daemon/client/daemon-environment.js +110 -2
  69. package/dist/src/daemon/client/daemon-socket-messenger.js +4 -0
  70. package/dist/src/daemon/message-types/daemon-message.d.ts +32 -0
  71. package/dist/src/daemon/message-types/daemon-message.js +25 -0
  72. package/dist/src/daemon/server/file-watching/file-watcher-sockets.d.ts +5 -0
  73. package/dist/src/daemon/server/file-watching/file-watcher-sockets.js +16 -0
  74. package/dist/src/daemon/server/handle-tasks-execution-hooks.d.ts +1 -1
  75. package/dist/src/daemon/server/latest-nx.js +5 -2
  76. package/dist/src/daemon/server/server.js +31 -4
  77. package/dist/src/daemon/server/shutdown-utils.d.ts +5 -0
  78. package/dist/src/daemon/server/shutdown-utils.js +11 -4
  79. package/dist/src/daemon/socket-utils.d.ts +1 -0
  80. package/dist/src/daemon/socket-utils.js +24 -3
  81. package/dist/src/daemon/tmp-dir.d.ts +43 -3
  82. package/dist/src/daemon/tmp-dir.js +437 -15
  83. package/dist/src/devkit-internals.d.ts +96 -3
  84. package/dist/src/devkit-internals.js +200 -4
  85. package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.d.ts +5 -0
  86. package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.js +9 -2
  87. package/dist/src/generators/tree.d.ts +4 -2
  88. package/dist/src/generators/utils/nx-json.js +13 -3
  89. package/dist/src/hasher/check-task-files.d.ts +86 -0
  90. package/dist/src/hasher/check-task-files.js +378 -0
  91. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.d.ts +12 -0
  92. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.js +250 -0
  93. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.md +50 -0
  94. package/dist/src/native/index.d.ts +31 -2
  95. package/dist/src/native/index.js +71 -28
  96. package/dist/src/native/native-bindings.js +3 -0
  97. package/dist/src/native/native-file-cache-location.d.ts +17 -1
  98. package/dist/src/native/native-file-cache-location.js +86 -12
  99. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  100. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  101. package/dist/src/nx-cloud/utilities/get-cloud-options.js +5 -2
  102. package/dist/src/nx-cloud/utilities/onboarding.js +5 -2
  103. package/dist/src/plugins/js/lock-file/bun-parser.js +43 -10
  104. package/dist/src/plugins/js/lock-file/npm-parser.js +71 -27
  105. package/dist/src/plugins/js/lock-file/pnpm-parser.js +110 -17
  106. package/dist/src/plugins/js/lock-file/project-graph-pruning.js +15 -9
  107. package/dist/src/plugins/js/lock-file/utils/pnpm-normalizer.js +3 -0
  108. package/dist/src/plugins/js/lock-file/yarn-parser.js +32 -5
  109. package/dist/src/plugins/js/project-graph/affected/npm-packages.js +51 -20
  110. package/dist/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +14 -4
  111. package/dist/src/plugins/js/utils/register.d.ts +18 -0
  112. package/dist/src/plugins/js/utils/register.js +81 -0
  113. package/dist/src/plugins/js/utils/typescript.d.ts +9 -0
  114. package/dist/src/plugins/js/utils/typescript.js +15 -0
  115. package/dist/src/project-graph/affected/affected-project-graph-models.d.ts +1 -1
  116. package/dist/src/project-graph/affected/affected-project-graph.d.ts +1 -1
  117. package/dist/src/project-graph/affected/affected-project-graph.js +2 -2
  118. package/dist/src/project-graph/affected/locators/project-glob-changes.js +5 -2
  119. package/dist/src/project-graph/build-project-graph.js +7 -1
  120. package/dist/src/project-graph/error-types.d.ts +12 -1
  121. package/dist/src/project-graph/error-types.js +68 -3
  122. package/dist/src/project-graph/file-utils.js +6 -1
  123. package/dist/src/project-graph/plugins/isolation/isolated-plugin.d.ts +1 -0
  124. package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +22 -1
  125. package/dist/src/project-graph/plugins/isolation/messaging.d.ts +16 -3
  126. package/dist/src/project-graph/plugins/isolation/messaging.js +3 -0
  127. package/dist/src/project-graph/plugins/isolation/plugin-worker.js +58 -8
  128. package/dist/src/project-graph/plugins/loaded-nx-plugin.js +1 -0
  129. package/dist/src/project-graph/plugins/transpiler.js +4 -0
  130. package/dist/src/project-graph/project-graph.js +23 -0
  131. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.d.ts +10 -12
  132. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.js +93 -63
  133. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.d.ts +5 -8
  134. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +16 -15
  135. package/dist/src/project-graph/utils/project-configuration/source-maps.d.ts +12 -5
  136. package/dist/src/project-graph/utils/project-configuration/source-maps.js +21 -7
  137. package/dist/src/project-graph/utils/project-configuration/target-defaults.d.ts +1 -1
  138. package/dist/src/project-graph/utils/project-configuration/target-defaults.js +26 -27
  139. package/dist/src/project-graph/utils/project-configuration/target-merging.js +50 -8
  140. package/dist/src/project-graph/utils/project-configuration/target-normalization.js +149 -6
  141. package/dist/src/project-graph/utils/project-configuration/utils.d.ts +15 -0
  142. package/dist/src/project-graph/utils/project-configuration/utils.js +42 -6
  143. package/dist/src/project-graph/utils/project-configuration-utils.d.ts +15 -12
  144. package/dist/src/project-graph/utils/project-configuration-utils.js +79 -101
  145. package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +11 -4
  146. package/dist/src/tasks-runner/life-cycles/performance-analysis.js +15 -6
  147. package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +5 -4
  148. package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +18 -8
  149. package/dist/src/tasks-runner/life-cycles/performance-report.js +66 -29
  150. package/dist/src/tasks-runner/run-command.js +4 -2
  151. package/dist/src/tasks-runner/task-env.d.ts +9 -0
  152. package/dist/src/tasks-runner/task-env.js +31 -4
  153. package/dist/src/tasks-runner/task-orchestrator.d.ts +4 -1
  154. package/dist/src/tasks-runner/task-orchestrator.js +23 -10
  155. package/dist/src/tasks-runner/utils.js +2 -6
  156. package/dist/src/utils/acknowledge-build-scripts.d.ts +13 -0
  157. package/dist/src/utils/acknowledge-build-scripts.js +102 -0
  158. package/dist/src/utils/analytics-prompt.d.ts +0 -6
  159. package/dist/src/utils/analytics-prompt.js +0 -51
  160. package/dist/src/utils/assert-workspace-validity.js +7 -2
  161. package/dist/src/utils/catalog/manager-utils.d.ts +1 -2
  162. package/dist/src/utils/catalog/manager-utils.js +22 -5
  163. package/dist/src/utils/catalog/pnpm-manager.d.ts +1 -0
  164. package/dist/src/utils/catalog/pnpm-manager.js +3 -15
  165. package/dist/src/utils/catalog/yarn-manager.d.ts +1 -0
  166. package/dist/src/utils/catalog/yarn-manager.js +3 -15
  167. package/dist/src/utils/command-line-utils.js +30 -20
  168. package/dist/src/utils/fileutils.d.ts +5 -0
  169. package/dist/src/utils/fileutils.js +7 -2
  170. package/dist/src/utils/git-revision.d.ts +12 -0
  171. package/dist/src/utils/git-revision.js +25 -0
  172. package/dist/src/utils/git-utils.d.ts +16 -3
  173. package/dist/src/utils/git-utils.js +146 -52
  174. package/dist/src/utils/git-worktrees.d.ts +30 -0
  175. package/dist/src/utils/git-worktrees.js +212 -0
  176. package/dist/src/utils/ignore.d.ts +128 -1
  177. package/dist/src/utils/ignore.js +182 -12
  178. package/dist/src/utils/long-running-target.d.ts +10 -0
  179. package/dist/src/utils/long-running-target.js +19 -0
  180. package/dist/src/utils/min-release-age/behavior/bun.js +7 -11
  181. package/dist/src/utils/min-release-age/behavior/npm.js +5 -2
  182. package/dist/src/utils/min-release-age/behavior/pnpm.js +9 -7
  183. package/dist/src/utils/min-release-age/packument.js +1 -1
  184. package/dist/src/utils/nx-tmp-dir.d.ts +18 -0
  185. package/dist/src/utils/nx-tmp-dir.js +40 -0
  186. package/dist/src/utils/owned-private-dir.d.ts +132 -0
  187. package/dist/src/utils/owned-private-dir.js +323 -0
  188. package/dist/src/utils/package-json.js +11 -7
  189. package/dist/src/utils/package-manager-config/bunfig.d.ts +14 -0
  190. package/dist/src/utils/package-manager-config/bunfig.js +49 -0
  191. package/dist/src/utils/package-manager-config/npmrc.d.ts +23 -0
  192. package/dist/src/utils/package-manager-config/npmrc.js +124 -0
  193. package/dist/src/utils/package-manager-config/pnpm-config.d.ts +17 -0
  194. package/dist/src/utils/package-manager-config/pnpm-config.js +60 -0
  195. package/dist/src/utils/package-manager.d.ts +23 -2
  196. package/dist/src/utils/package-manager.js +264 -43
  197. package/dist/src/utils/provenance.js +18 -16
  198. package/dist/src/utils/registry-config/bun.d.ts +2 -0
  199. package/dist/src/utils/registry-config/bun.js +257 -0
  200. package/dist/src/utils/registry-config/index.d.ts +13 -0
  201. package/dist/src/utils/registry-config/index.js +102 -0
  202. package/dist/src/utils/registry-config/pnpm.d.ts +2 -0
  203. package/dist/src/utils/registry-config/pnpm.js +1551 -0
  204. package/dist/src/utils/registry-config/utils.d.ts +195 -0
  205. package/dist/src/utils/registry-config/utils.js +496 -0
  206. package/dist/src/utils/registry-config/yarn-berry.d.ts +2 -0
  207. package/dist/src/utils/registry-config/yarn-berry.js +612 -0
  208. package/dist/src/utils/registry-config/yarn-classic.d.ts +2 -0
  209. package/dist/src/utils/registry-config/yarn-classic.js +1001 -0
  210. package/dist/src/utils/safe-spawn.d.ts +24 -0
  211. package/dist/src/utils/safe-spawn.js +104 -0
  212. package/dist/src/utils/shell-quoting.d.ts +15 -0
  213. package/dist/src/utils/shell-quoting.js +35 -0
  214. package/dist/src/utils/wait-for-socket-connection.d.ts +11 -0
  215. package/dist/src/utils/wait-for-socket-connection.js +14 -8
  216. package/dist/src/utils/workspace-id.d.ts +20 -0
  217. package/dist/src/utils/workspace-id.js +54 -0
  218. package/migrations.json +13 -1
  219. package/package.json +22 -17
  220. package/dist/src/migrations/update-17-3-0/nx-release-path.d.ts +0 -3
  221. package/dist/src/migrations/update-17-3-0/nx-release-path.js +0 -47
  222. package/dist/src/utils/min-release-age/npmrc.d.ts +0 -15
  223. package/dist/src/utils/min-release-age/npmrc.js +0 -45
@@ -41,8 +41,13 @@ function readJsonFile(path, options) {
41
41
  */
42
42
  function readYamlFile(path, options) {
43
43
  const content = (0, node_fs_1.readFileSync)(path, 'utf-8');
44
- const { load } = require('@zkochan/js-yaml');
45
- return load(content, { ...options, filename: path });
44
+ const { load, FAILSAFE_SCHEMA } = require('@zkochan/js-yaml');
45
+ const { failsafe, ...loadOptions } = options ?? {};
46
+ return load(content, {
47
+ ...loadOptions,
48
+ ...(failsafe ? { schema: FAILSAFE_SCHEMA } : {}),
49
+ filename: path,
50
+ });
46
51
  }
47
52
  /**
48
53
  * Serializes the given data to JSON and writes it to a file.
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Git parses any argument beginning with `-` as an option rather than a
3
+ * revision, so a flag-shaped revision could smuggle options such as
4
+ * `--upload-pack` into the git commands Nx runs.
5
+ */
6
+ export declare function assertValidGitRevision(revision: string): void;
7
+ /**
8
+ * For refs Nx itself recorded from `git rev-parse` and later read back off
9
+ * disk, where anything other than a commit sha means the value was tampered
10
+ * with rather than that the user picked an unusual revision.
11
+ */
12
+ export declare function assertValidGitSha(sha: string): void;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assertValidGitRevision = assertValidGitRevision;
4
+ exports.assertValidGitSha = assertValidGitSha;
5
+ /**
6
+ * Git parses any argument beginning with `-` as an option rather than a
7
+ * revision, so a flag-shaped revision could smuggle options such as
8
+ * `--upload-pack` into the git commands Nx runs.
9
+ */
10
+ function assertValidGitRevision(revision) {
11
+ if (revision.startsWith('-')) {
12
+ throw new Error(`Invalid git revision: "${revision}". Git revisions cannot start with "-".`);
13
+ }
14
+ }
15
+ const COMMIT_SHA = /^[0-9a-f]{7,40}$/i;
16
+ /**
17
+ * For refs Nx itself recorded from `git rev-parse` and later read back off
18
+ * disk, where anything other than a commit sha means the value was tampered
19
+ * with rather than that the user picked an unusual revision.
20
+ */
21
+ function assertValidGitSha(sha) {
22
+ if (!COMMIT_SHA.test(sha)) {
23
+ throw new Error(`Invalid git commit sha: "${sha}". Expected a hexadecimal commit sha.`);
24
+ }
25
+ }
@@ -28,9 +28,7 @@ export declare class GitRepository {
28
28
  hasFilterRepoInstalled(): Promise<boolean>;
29
29
  filterRepo(source: string, destination: string): Promise<void>;
30
30
  filterBranch(source: string, destination: string, branchName: string): Promise<void>;
31
- private execAsync;
32
- private quotePath;
33
- private quoteArg;
31
+ private execGit;
34
32
  }
35
33
  export interface VcsRemoteInfo {
36
34
  domain: string;
@@ -38,6 +36,21 @@ export interface VcsRemoteInfo {
38
36
  }
39
37
  export declare function parseVcsRemoteUrl(url: string): VcsRemoteInfo | null;
40
38
  export declare function getVcsRemoteInfo(directory?: string): VcsRemoteInfo | null;
39
+ export declare function getGitRootPath(cwd?: string): string;
40
+ /**
41
+ * Path of `directory` relative to its git root, posix-separated so it is
42
+ * identical on every OS, and '' when the directory is the git root itself.
43
+ * Null outside a git repository.
44
+ */
45
+ export declare function getGitRootRelativePath(directory: string): string | null;
46
+ /** A shallow clone's truncated history has no stable root commit. */
47
+ export declare function isShallowRepository(directory?: string): boolean;
48
+ /**
49
+ * SHA of the repository's first commit. Merged unrelated histories leave
50
+ * several root commits — the sorted-first one is picked so every clone
51
+ * agrees. Null when there are no commits, or outside a git repository.
52
+ */
53
+ export declare function getFirstCommitSha(directory?: string): string | null;
41
54
  export declare function isGitRepository(directory?: string): boolean;
42
55
  export declare function hasUncommittedChanges(directory?: string): boolean;
43
56
  export declare function getUncommittedChangesSnapshot(directory?: string): string;
@@ -4,6 +4,10 @@ exports.GitRepository = void 0;
4
4
  exports.cloneFromUpstream = cloneFromUpstream;
5
5
  exports.parseVcsRemoteUrl = parseVcsRemoteUrl;
6
6
  exports.getVcsRemoteInfo = getVcsRemoteInfo;
7
+ exports.getGitRootPath = getGitRootPath;
8
+ exports.getGitRootRelativePath = getGitRootRelativePath;
9
+ exports.isShallowRepository = isShallowRepository;
10
+ exports.getFirstCommitSha = getFirstCommitSha;
7
11
  exports.isGitRepository = isGitRepository;
8
12
  exports.hasUncommittedChanges = hasUncommittedChanges;
9
13
  exports.getUncommittedChangesSnapshot = getUncommittedChangesSnapshot;
@@ -16,9 +20,9 @@ const crypto = tslib_1.__importStar(require("crypto"));
16
20
  const fs = tslib_1.__importStar(require("fs"));
17
21
  const path_1 = require("path");
18
22
  const logger_1 = require("./logger");
19
- function execAsync(command, execOptions) {
23
+ function execFileAsync(file, args, execOptions) {
20
24
  return new Promise((res, rej) => {
21
- (0, child_process_1.exec)(command, { ...execOptions, windowsHide: true }, (err, stdout, stderr) => {
25
+ (0, child_process_1.execFile)(file, args, { ...execOptions, windowsHide: true }, (err, stdout) => {
22
26
  if (err) {
23
27
  return rej(err);
24
28
  }
@@ -29,7 +33,14 @@ function execAsync(command, execOptions) {
29
33
  async function cloneFromUpstream(url, destination, { originName, depth } = {
30
34
  originName: 'origin',
31
35
  }) {
32
- await execAsync(`git clone ${url} ${destination} ${depth ? `--depth ${depth}` : ''} --origin ${originName}`, {
36
+ await execFileAsync('git', [
37
+ 'clone',
38
+ url,
39
+ destination,
40
+ ...(depth ? ['--depth', `${depth}`] : []),
41
+ '--origin',
42
+ originName,
43
+ ], {
33
44
  cwd: (0, path_1.dirname)(destination),
34
45
  maxBuffer: 10 * 1024 * 1024,
35
46
  });
@@ -41,25 +52,25 @@ class GitRepository {
41
52
  this.root = this.getGitRootPath(this.directory);
42
53
  }
43
54
  getGitRootPath(cwd) {
44
- return (0, child_process_1.execSync)('git rev-parse --show-toplevel', {
45
- cwd,
46
- windowsHide: true,
47
- })
48
- .toString()
49
- .trim();
55
+ return getGitRootPath(cwd);
50
56
  }
51
57
  async hasUncommittedChanges() {
52
- const data = await this.execAsync(`git status --porcelain`);
58
+ const data = await this.execGit(['status', '--porcelain']);
53
59
  return data.trim() !== '';
54
60
  }
55
61
  async addFetchRemote(remoteName, branch) {
56
- return await this.execAsync(`git config --add remote.${remoteName}.fetch "+refs/heads/${branch}:refs/remotes/${remoteName}/${branch}"`);
62
+ return await this.execGit([
63
+ 'config',
64
+ '--add',
65
+ `remote.${remoteName}.fetch`,
66
+ `+refs/heads/${branch}:refs/remotes/${remoteName}/${branch}`,
67
+ ]);
57
68
  }
58
69
  async showStat() {
59
- return await this.execAsync(`git show --stat`);
70
+ return await this.execGit(['show', '--stat']);
60
71
  }
61
72
  async listBranches() {
62
- return (await this.execAsync(`git ls-remote --heads --quiet`))
73
+ return (await this.execGit(['ls-remote', '--heads', '--quiet']))
63
74
  .trim()
64
75
  .split('\n')
65
76
  .map((s) => s
@@ -70,45 +81,58 @@ class GitRepository {
70
81
  async getGitFiles(path) {
71
82
  // Use -z to return file names exactly as they are stored in git, separated by NULL (\x00) character.
72
83
  // This avoids problems with special characters in file names.
73
- return (await this.execAsync(`git ls-files -z ${path}`))
84
+ return (await this.execGit(['ls-files', '-z', '--', path]))
74
85
  .trim()
75
86
  .split('\x00')
76
87
  .map((s) => s.trim())
77
88
  .filter(Boolean);
78
89
  }
79
90
  async reset(ref) {
80
- return await this.execAsync(`git reset ${ref} --hard`);
91
+ return await this.execGit(['reset', '--hard', ref]);
81
92
  }
82
93
  async mergeUnrelatedHistories(ref, message) {
83
- return await this.execAsync(`git merge ${ref} -X ours --allow-unrelated-histories -m "${message}"`);
94
+ return await this.execGit([
95
+ 'merge',
96
+ ref,
97
+ '-X',
98
+ 'ours',
99
+ '--allow-unrelated-histories',
100
+ '-m',
101
+ message,
102
+ ]);
84
103
  }
85
104
  async fetch(remote, ref) {
86
- return await this.execAsync(`git fetch ${remote}${ref ? ` ${ref}` : ''}`);
105
+ return await this.execGit(['fetch', remote, ...(ref ? [ref] : [])]);
87
106
  }
88
107
  async checkout(branch, opts) {
89
- return await this.execAsync(`git checkout ${opts.new ? '-b ' : ' '}${branch}${opts.base ? ' ' + opts.base : ''}`);
108
+ return await this.execGit([
109
+ 'checkout',
110
+ ...(opts.new ? ['-b'] : []),
111
+ branch,
112
+ ...(opts.base ? [opts.base] : []),
113
+ ]);
90
114
  }
91
115
  async move(path, destination) {
92
- return await this.execAsync(`git mv ${this.quotePath(path)} ${this.quotePath(destination)}`);
116
+ return await this.execGit(['mv', '--', path, destination]);
93
117
  }
94
118
  async push(ref, remoteName) {
95
- return await this.execAsync(`git push -u -f ${remoteName} ${ref}`);
119
+ return await this.execGit(['push', '-u', '-f', remoteName, ref]);
96
120
  }
97
121
  async commit(message) {
98
- return await this.execAsync(`git commit -am "${message}"`);
122
+ return await this.execGit(['commit', '-am', message]);
99
123
  }
100
124
  async amendCommit() {
101
- return await this.execAsync(`git commit --amend -a --no-edit`);
125
+ return await this.execGit(['commit', '--amend', '-a', '--no-edit']);
102
126
  }
103
127
  async deleteGitRemote(name) {
104
- return await this.execAsync(`git remote rm ${name}`);
128
+ return await this.execGit(['remote', 'rm', name]);
105
129
  }
106
130
  async addGitRemote(name, url) {
107
- return await this.execAsync(`git remote add ${name} ${url}`);
131
+ return await this.execGit(['remote', 'add', name, url]);
108
132
  }
109
133
  async hasFilterRepoInstalled() {
110
134
  try {
111
- await this.execAsync(`git filter-repo --help`);
135
+ await this.execGit(['filter-repo', '--help']);
112
136
  return true;
113
137
  }
114
138
  catch {
@@ -121,35 +145,57 @@ class GitRepository {
121
145
  // NOTE: filter-repo requires POSIX path to work
122
146
  const sourcePosixPath = source.split(path_1.sep).join(path_1.posix.sep);
123
147
  const destinationPosixPath = destination.split(path_1.sep).join(path_1.posix.sep);
124
- await this.execAsync(`git filter-repo -f ${source !== '' ? `--path ${this.quotePath(sourcePosixPath)}` : ''} ${source !== destination
125
- ? `--path-rename ${this.quotePath(sourcePosixPath, true)}:${this.quotePath(destinationPosixPath, true)}`
126
- : ''}`);
148
+ const sourcePath = ensureTrailingSlash(sourcePosixPath);
149
+ const destinationPath = ensureTrailingSlash(destinationPosixPath);
150
+ await this.execGit([
151
+ 'filter-repo',
152
+ '-f',
153
+ ...(source !== '' ? ['--path', sourcePosixPath] : []),
154
+ ...(source !== destination
155
+ ? ['--path-rename', `${sourcePath}:${destinationPath}`]
156
+ : []),
157
+ ]);
127
158
  }
128
159
  async filterBranch(source, destination, branchName) {
129
160
  // We need non-ASCII file names to not be quoted, or else filter-branch will exclude them.
130
- await this.execAsync(`git config core.quotepath false`);
161
+ await this.execGit(['config', 'core.quotepath', 'false']);
131
162
  // NOTE: filter-repo requires POSIX path to work
132
163
  const sourcePosixPath = source.split(path_1.sep).join(path_1.posix.sep);
133
164
  const destinationPosixPath = destination.split(path_1.sep).join(path_1.posix.sep);
134
165
  // First, if the source is not a root project, then only include commits relevant to the subdirectory.
135
166
  if (source !== '') {
136
- const indexFilterCommand = this.quoteArg(`node ${(0, path_1.join)(__dirname, 'git-utils.index-filter.js')}`);
137
- await this.execAsync(`git filter-branch -f --index-filter ${indexFilterCommand} --prune-empty -- ${branchName}`, {
167
+ const indexFilterCommand = `node ${quoteForShell((0, path_1.join)(__dirname, 'git-utils.index-filter.js'))}`;
168
+ await this.execGit([
169
+ 'filter-branch',
170
+ '-f',
171
+ '--index-filter',
172
+ indexFilterCommand,
173
+ '--prune-empty',
174
+ '--',
175
+ branchName,
176
+ ], {
138
177
  NX_IMPORT_SOURCE: sourcePosixPath,
139
178
  NX_IMPORT_DESTINATION: destinationPosixPath,
140
179
  });
141
180
  }
142
181
  // Then, move files to their new location if necessary.
143
182
  if (source === '' || source !== destination) {
144
- const treeFilterCommand = this.quoteArg(`node ${(0, path_1.join)(__dirname, 'git-utils.tree-filter.js')}`);
145
- await this.execAsync(`git filter-branch -f --tree-filter ${treeFilterCommand} -- ${branchName}`, {
183
+ const treeFilterCommand = `node ${quoteForShell((0, path_1.join)(__dirname, 'git-utils.tree-filter.js'))}`;
184
+ await this.execGit([
185
+ 'filter-branch',
186
+ '-f',
187
+ '--tree-filter',
188
+ treeFilterCommand,
189
+ '--',
190
+ branchName,
191
+ ], {
146
192
  NX_IMPORT_SOURCE: sourcePosixPath,
147
193
  NX_IMPORT_DESTINATION: destinationPosixPath,
148
194
  });
149
195
  }
150
196
  }
151
- execAsync(command, env) {
152
- return execAsync(command, {
197
+ execGit(args, env) {
198
+ return execFileAsync('git', args, {
153
199
  cwd: this.root,
154
200
  maxBuffer: 10 * 1024 * 1024,
155
201
  env: {
@@ -158,25 +204,14 @@ class GitRepository {
158
204
  },
159
205
  });
160
206
  }
161
- quotePath(path, ensureTrailingSlash) {
162
- return this.quoteArg(ensureTrailingSlash && path !== '' && !path.endsWith('/')
163
- ? `${path}/`
164
- : path);
165
- }
166
- quoteArg(arg) {
167
- return process.platform === 'win32'
168
- ? // Windows/CMD only understands double-quotes, single-quotes are treated as part of the file name
169
- // Bash and other shells will substitute `$` in file names with a variable value.
170
- `"${arg
171
- // Need to keep two slashes for Windows or else the path will be invalid.
172
- // e.g. 'C:\Users\bob\projects\repo' is invalid, but 'C:\\Users\\bob\\projects\\repo' is valid
173
- .replaceAll('\\', '\\\\')}"`
174
- : // e.g. `git mv "$$file.txt" "libs/a/$$file.txt"` will not work since `$$` is swapped with the PID of the last process.
175
- // Using single-quotes prevents this substitution.
176
- `'${arg}'`;
177
- }
178
207
  }
179
208
  exports.GitRepository = GitRepository;
209
+ function ensureTrailingSlash(path) {
210
+ return path !== '' && !path.endsWith('/') ? `${path}/` : path;
211
+ }
212
+ function quoteForShell(arg) {
213
+ return `'${arg.replaceAll("'", "'\"'\"'")}'`;
214
+ }
180
215
  function parseVcsRemoteUrl(url) {
181
216
  // Remove whitespace and handle common URL formats
182
217
  const cleanUrl = url.trim();
@@ -260,6 +295,65 @@ function getVcsRemoteInfo(directory) {
260
295
  return null;
261
296
  }
262
297
  }
298
+ function getGitRootPath(cwd) {
299
+ return (0, child_process_1.execFileSync)('git', ['rev-parse', '--show-toplevel'], {
300
+ cwd,
301
+ windowsHide: true,
302
+ })
303
+ .toString()
304
+ .trim();
305
+ }
306
+ /**
307
+ * Path of `directory` relative to its git root, posix-separated so it is
308
+ * identical on every OS, and '' when the directory is the git root itself.
309
+ * Null outside a git repository.
310
+ */
311
+ function getGitRootRelativePath(directory) {
312
+ try {
313
+ return (0, path_1.relative)(getGitRootPath(directory), directory)
314
+ .split(path_1.sep)
315
+ .join(path_1.posix.sep);
316
+ }
317
+ catch {
318
+ return null;
319
+ }
320
+ }
321
+ /** A shallow clone's truncated history has no stable root commit. */
322
+ function isShallowRepository(directory) {
323
+ try {
324
+ return ((0, child_process_1.execFileSync)('git', ['rev-parse', '--is-shallow-repository'], {
325
+ encoding: 'utf8',
326
+ stdio: 'pipe',
327
+ cwd: directory,
328
+ windowsHide: true,
329
+ }).trim() === 'true');
330
+ }
331
+ catch {
332
+ return false;
333
+ }
334
+ }
335
+ /**
336
+ * SHA of the repository's first commit. Merged unrelated histories leave
337
+ * several root commits — the sorted-first one is picked so every clone
338
+ * agrees. Null when there are no commits, or outside a git repository.
339
+ */
340
+ function getFirstCommitSha(directory) {
341
+ try {
342
+ const roots = (0, child_process_1.execFileSync)('git', ['rev-list', '--max-parents=0', 'HEAD'], {
343
+ encoding: 'utf8',
344
+ stdio: 'pipe',
345
+ cwd: directory,
346
+ windowsHide: true,
347
+ })
348
+ .trim()
349
+ .split(/\r?\n/)
350
+ .filter(Boolean);
351
+ return roots.sort()[0] ?? null;
352
+ }
353
+ catch {
354
+ return null;
355
+ }
356
+ }
263
357
  function isGitRepository(directory) {
264
358
  try {
265
359
  (0, child_process_1.execSync)('git rev-parse --is-inside-work-tree', {
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Roots of the git linked worktrees that live inside `workspaceRoot`,
3
+ * relative to it and separator-normalized.
4
+ *
5
+ * Reads git's own registry rather than probing the workspace, so it costs one
6
+ * `readdir` plus a small file per worktree. Worktrees outside the workspace
7
+ * are dropped - nothing walks them. Submodules use the same gitfile mechanism
8
+ * but register under `<git-dir>/modules`, so they never appear here.
9
+ */
10
+ export declare function nestedWorktreeRoots(workspaceRoot: string): string[];
11
+ /**
12
+ * Whether `path` sits inside `root`, comparing whole path segments so that
13
+ * `wt-other` is not read as living inside `wt`.
14
+ */
15
+ export declare function isInside(path: string, root: string): boolean;
16
+ /** Advice for duplicate project names that come from nested git worktrees. */
17
+ export interface WorktreeConflictAdvice {
18
+ /** Paths to add to `.gitignore`. */
19
+ ignoreTargets: string[];
20
+ /**
21
+ * Whether ignoring them settles every duplicate. When false the caller still
22
+ * owes the reader the ordinary advice for the ones left over.
23
+ */
24
+ explainsAllConflicts: boolean;
25
+ }
26
+ /**
27
+ * What to tell someone whose duplicate project names come from git worktrees
28
+ * nested in the workspace, or null when none of them do.
29
+ */
30
+ export declare function analyzeWorktreeConflicts(workspaceRoot: string, conflicts: Map<string, string[]>): WorktreeConflictAdvice | null;
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.nestedWorktreeRoots = nestedWorktreeRoots;
4
+ exports.isInside = isInside;
5
+ exports.analyzeWorktreeConflicts = analyzeWorktreeConflicts;
6
+ const node_fs_1 = require("node:fs");
7
+ const node_path_1 = require("node:path");
8
+ const GITDIR_PREFIX = 'gitdir:';
9
+ function readRecordedPath(file, base) {
10
+ let contents;
11
+ try {
12
+ contents = (0, node_fs_1.readFileSync)(file, 'utf-8').trim();
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ const raw = contents.startsWith(GITDIR_PREFIX)
18
+ ? contents.slice(GITDIR_PREFIX.length).trim()
19
+ : contents;
20
+ return raw ? (0, node_path_1.resolve)(base, raw) : null;
21
+ }
22
+ function isDirectory(path) {
23
+ try {
24
+ return (0, node_fs_1.statSync)(path).isDirectory();
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ }
30
+ /**
31
+ * The nearest directory at or above `from` holding a `.git`, or null when
32
+ * there is no repository above it.
33
+ *
34
+ * The workspace root and the repository root are often the same directory, but
35
+ * a workspace nested in a larger repo is ordinary - and its worktrees are
36
+ * registered against the repository, not against the workspace.
37
+ */
38
+ function findGitRoot(from) {
39
+ let current = (0, node_path_1.resolve)(from);
40
+ while (!(0, node_fs_1.existsSync)((0, node_path_1.join)(current, '.git'))) {
41
+ const parent = (0, node_path_1.dirname)(current);
42
+ if (parent === current) {
43
+ return null;
44
+ }
45
+ current = parent;
46
+ }
47
+ return current;
48
+ }
49
+ /**
50
+ * `<git-dir>/worktrees`, where git registers every linked worktree of the
51
+ * repository `workspaceRoot` belongs to - found by walking up, since the
52
+ * workspace need not be the repository root. Null when there is no `.git`, or
53
+ * when it is a gitfile that names nothing. The registry itself is not checked -
54
+ * reading it is what tells us whether anything is registered.
55
+ */
56
+ function worktreeRegistry(workspaceRoot) {
57
+ const gitRoot = findGitRoot(workspaceRoot);
58
+ if (!gitRoot) {
59
+ return null;
60
+ }
61
+ const dotGit = (0, node_path_1.join)(gitRoot, '.git');
62
+ let gitDir;
63
+ try {
64
+ gitDir = (0, node_fs_1.statSync)(dotGit).isDirectory()
65
+ ? dotGit
66
+ : readRecordedPath(dotGit, gitRoot);
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ if (!gitDir) {
72
+ return null;
73
+ }
74
+ // Running from inside a linked worktree lands on
75
+ // `<main>/.git/worktrees/<name>`, whose `commondir` names the real git dir.
76
+ // Ignored unless it names a directory that exists. That is a sanity check
77
+ // on a path out of a file we did not write, not a bound on where it may
78
+ // point - it can still name any directory on the machine.
79
+ const commonDir = readRecordedPath((0, node_path_1.join)(gitDir, 'commondir'), gitDir);
80
+ return (0, node_path_1.join)(commonDir && isDirectory(commonDir) ? commonDir : gitDir, 'worktrees');
81
+ }
82
+ /**
83
+ * Roots of the git linked worktrees that live inside `workspaceRoot`,
84
+ * relative to it and separator-normalized.
85
+ *
86
+ * Reads git's own registry rather than probing the workspace, so it costs one
87
+ * `readdir` plus a small file per worktree. Worktrees outside the workspace
88
+ * are dropped - nothing walks them. Submodules use the same gitfile mechanism
89
+ * but register under `<git-dir>/modules`, so they never appear here.
90
+ */
91
+ function nestedWorktreeRoots(workspaceRoot) {
92
+ const registry = worktreeRegistry(workspaceRoot);
93
+ if (!registry) {
94
+ return [];
95
+ }
96
+ let entries;
97
+ try {
98
+ entries = (0, node_fs_1.readdirSync)(registry);
99
+ }
100
+ catch {
101
+ return [];
102
+ }
103
+ const roots = [];
104
+ for (const entry of entries) {
105
+ // Points at the worktree's own `.git` gitfile, so its parent is the root.
106
+ const gitfile = readRecordedPath((0, node_path_1.join)(registry, entry, 'gitdir'), (0, node_path_1.join)(registry, entry));
107
+ if (!gitfile || !(0, node_fs_1.existsSync)(gitfile)) {
108
+ continue;
109
+ }
110
+ const root = (0, node_path_1.relative)(workspaceRoot, (0, node_path_1.dirname)(gitfile));
111
+ // Neither the workspace itself nor anything outside it is a nested
112
+ // worktree Nx would walk into. Compared by whole segments, because `..` is
113
+ // a traversal and `..hidden` is an ordinary directory name; and by
114
+ // absoluteness, because `relative` across Windows drives returns its
115
+ // second argument, which is outside by definition and carries no `..`.
116
+ if (!root || (0, node_path_1.isAbsolute)(root) || root.split(node_path_1.sep)[0] === '..') {
117
+ continue;
118
+ }
119
+ roots.push(root.split(node_path_1.sep).join('/'));
120
+ }
121
+ return roots;
122
+ }
123
+ /**
124
+ * Whether `path` sits inside `root`, comparing whole path segments so that
125
+ * `wt-other` is not read as living inside `wt`.
126
+ */
127
+ function isInside(path, root) {
128
+ return path === root || path.startsWith(`${root}/`);
129
+ }
130
+ /**
131
+ * What to tell someone whose duplicate project names come from git worktrees
132
+ * nested in the workspace, or null when none of them do.
133
+ */
134
+ function analyzeWorktreeConflicts(workspaceRoot, conflicts) {
135
+ const worktrees = nestedWorktreeRoots(workspaceRoot);
136
+ if (!worktrees.length) {
137
+ return null;
138
+ }
139
+ const offending = [];
140
+ let explainsAllConflicts = true;
141
+ for (const roots of conflicts.values()) {
142
+ const fromWorktrees = worktrees.filter((worktree) => roots.some((root) => isInside(root, worktree)));
143
+ // What would still be defined twice once the worktrees are out of the way.
144
+ const remaining = roots.filter((root) => !worktrees.some((worktree) => isInside(root, worktree)));
145
+ // Worth naming whenever a worktree is involved, even if ignoring it
146
+ // doesn't settle the whole conflict.
147
+ for (const worktree of fromWorktrees) {
148
+ if (!offending.includes(worktree)) {
149
+ offending.push(worktree);
150
+ }
151
+ }
152
+ if (!fromWorktrees.length || remaining.length > 1) {
153
+ explainsAllConflicts = false;
154
+ }
155
+ }
156
+ if (!offending.length) {
157
+ return null;
158
+ }
159
+ return {
160
+ ignoreTargets: ignoreTargetsFor(workspaceRoot, offending, worktrees),
161
+ explainsAllConflicts,
162
+ };
163
+ }
164
+ /**
165
+ * The worktree roots themselves, or the one directory holding them.
166
+ *
167
+ * Collapsing to the directory is only worth anything when it saves lines, and
168
+ * only safe when it holds nothing else. With a single worktree it saves
169
+ * nothing and risks everything: a lone worktree in `apps/` would have us name
170
+ * `apps/`, which holds only that worktree today and is where the reader will
171
+ * put projects tomorrow.
172
+ */
173
+ function ignoreTargetsFor(workspaceRoot, offending, worktrees) {
174
+ if (offending.length < 2) {
175
+ return offending.map(anchored);
176
+ }
177
+ const parent = commonParent(offending);
178
+ return (parent && holdsOnlyWorktrees(workspaceRoot, parent, worktrees)
179
+ ? [parent]
180
+ : offending).map(anchored);
181
+ }
182
+ /**
183
+ * A gitignore pattern rooted at the workspace rather than matched anywhere.
184
+ *
185
+ * Without the leading slash a single-segment path like `wt1` is a name, not a
186
+ * location - it would ignore every `wt1` at any depth. Applied to all of them
187
+ * so the emitted line reads the same way wherever it came from.
188
+ */
189
+ function anchored(root) {
190
+ return `/${root}`;
191
+ }
192
+ /**
193
+ * The directory every one of `roots` sits directly inside, or null when they
194
+ * don't share one - including when it would be the workspace root, which is
195
+ * never something to ignore.
196
+ */
197
+ function commonParent(roots) {
198
+ const parents = new Set(roots.map((root) => root.split('/').slice(0, -1).join('/')));
199
+ const [parent] = parents;
200
+ return parents.size === 1 && parent ? parent : null;
201
+ }
202
+ function holdsOnlyWorktrees(workspaceRoot, directory, worktrees) {
203
+ let entries;
204
+ try {
205
+ entries = (0, node_fs_1.readdirSync)((0, node_path_1.join)(workspaceRoot, directory));
206
+ }
207
+ catch {
208
+ return false;
209
+ }
210
+ return (entries.length > 0 &&
211
+ entries.every((entry) => worktrees.includes(`${directory}/${entry}`)));
212
+ }