nx 22.7.0-rc.2 → 22.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/nx.js CHANGED
@@ -109,10 +109,10 @@ async function main() {
109
109
  warnIfUsingOutdatedGlobalInstall(GLOBAL_NX_VERSION, LOCAL_NX_VERSION);
110
110
  if (localNx.includes('.nx')) {
111
111
  const nxWrapperPath = localNx.replace(/\.nx.*/, '.nx/') + 'nxw.js';
112
- await import(nxWrapperPath);
112
+ require(nxWrapperPath);
113
113
  }
114
114
  else {
115
- await import(localNx);
115
+ require(localNx);
116
116
  }
117
117
  }
118
118
  }
@@ -15,38 +15,30 @@ const package_json_1 = require("../../utils/package-json");
15
15
  const ignore_1 = require("../../utils/ignore");
16
16
  const provenance_1 = require("../../utils/provenance");
17
17
  const workspace_root_1 = require("../../utils/workspace-root");
18
+ const installed_nx_version_1 = require("../../utils/installed-nx-version");
18
19
  const constants_1 = require("../constants");
19
20
  const clone_ai_config_repo_1 = require("../clone-ai-config-repo");
20
21
  const utils_1 = require("../utils");
21
22
  const handle_import_1 = require("../../utils/handle-import");
22
23
  /**
23
- * Get the installed Nx version, with fallback to workspace package.json or default version.
24
+ * Best-effort fallback when `getInstalledNxVersion()` can't find an
25
+ * installed nx — read the version declared in the workspace's
26
+ * `package.json` (devDependencies/dependencies), stripping any semver
27
+ * range prefix, and finally a sane default.
24
28
  */
25
- function getNxVersion() {
29
+ function getDeclaredNxVersionOrDefault() {
26
30
  try {
27
- // Try to read from node_modules first
28
- const { packageJson: { version }, } = (0, package_json_1.readModulePackageJson)('nx');
29
- return version;
31
+ const workspacePackageJson = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(workspace_root_1.workspaceRoot, 'package.json'), 'utf-8'));
32
+ const declared = workspacePackageJson.devDependencies?.nx ||
33
+ workspacePackageJson.dependencies?.nx;
34
+ if (declared) {
35
+ return declared.replace(/^[\^~>=<]+/, '');
36
+ }
30
37
  }
31
38
  catch {
32
- try {
33
- // Fallback: try to read from workspace package.json
34
- const workspacePackageJson = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(workspace_root_1.workspaceRoot, 'package.json'), 'utf-8'));
35
- // Check devDependencies first, then dependencies
36
- const nxVersion = workspacePackageJson.devDependencies?.nx ||
37
- workspacePackageJson.dependencies?.nx;
38
- if (nxVersion) {
39
- // Remove any semver range characters (^, ~, >=, etc.)
40
- return nxVersion.replace(/^[\^~>=<]+/, '');
41
- }
42
- throw new Error('Nx not found in package.json');
43
- }
44
- catch {
45
- // If we can't determine the version, default to the newer format
46
- // This handles cases where nx might not be installed or is globally installed
47
- return '22.0.0';
48
- }
39
+ // fall through to default
49
40
  }
41
+ return '22.0.0';
50
42
  }
51
43
  async function setupAiAgentsGenerator(tree, options, inner = false) {
52
44
  const normalizedOptions = normalizeOptions(options);
@@ -77,7 +69,7 @@ function normalizeOptions(options) {
77
69
  }
78
70
  async function setupAiAgentsGeneratorImpl(tree, options) {
79
71
  const hasAgent = (agent) => options.agents.includes(agent);
80
- const nxVersion = getNxVersion();
72
+ const nxVersion = (0, installed_nx_version_1.getInstalledNxVersion)() ?? getDeclaredNxVersionOrDefault();
81
73
  const agentsMd = (0, constants_1.agentsMdPath)(options.directory);
82
74
  // write AGENTS.md for most agents
83
75
  if (hasAgent('cursor') ||
@@ -64,7 +64,19 @@ function generateDotNxSetup(version) {
64
64
  (0, tree_1.flushChanges)(host.root, changes);
65
65
  // Ensure that the dot-nx installation is available.
66
66
  // This is needed when using a global nx with dot-nx, otherwise running any nx command using global command will fail due to missing modules.
67
- (0, child_process_1.execSync)('./nx --version', { stdio: 'ignore', windowsHide: true });
67
+ // Pipe stderr so failures surface in telemetry instead of bare "Command failed: ./nx --version".
68
+ try {
69
+ (0, child_process_1.execSync)('./nx --version', {
70
+ stdio: ['ignore', 'ignore', 'pipe'],
71
+ encoding: 'utf8',
72
+ windowsHide: true,
73
+ });
74
+ }
75
+ catch (e) {
76
+ if (e?.stderr)
77
+ process.stderr.write(e.stderr);
78
+ throw e;
79
+ }
68
80
  }
69
81
  function normalizeVersionForNxJson(pkg, version) {
70
82
  if (!(0, semver_1.valid)(version)) {
@@ -60,7 +60,8 @@ function performInstallation(currentInstallation, nxJson) {
60
60
  },
61
61
  }));
62
62
  try {
63
- cp.execSync('npm i', {
63
+ // --include=dev forces install even if consumer env sets NODE_ENV=production / omit=dev.
64
+ cp.execSync('npm i --include=dev', {
64
65
  cwd: path.dirname(installationPath),
65
66
  stdio: 'inherit',
66
67
  windowsHide: true,
@@ -61,6 +61,11 @@ function withMigrationOptions(yargs) {
61
61
  describe: 'Exclude migrations that should have been applied on previous updates. To be used with --from.',
62
62
  type: 'boolean',
63
63
  default: false,
64
+ })
65
+ .option('skipInstall', {
66
+ describe: 'Skip installing packages before running migrations. Useful when the installation needs to be performed manually (e.g., to resolve peer dependency conflicts).',
67
+ type: 'boolean',
68
+ default: false,
64
69
  })
65
70
  .check(({ createCommits, commitPrefix, from, excludeAppliedMigrations }) => {
66
71
  if (!createCommits && commitPrefix !== defaultCommitPrefix) {
@@ -104,12 +104,20 @@ type RunMigrations = {
104
104
  export declare function parseMigrationsOptions(options: {
105
105
  [k: string]: any;
106
106
  }): Promise<GenerateMigrations | RunMigrations>;
107
+ /**
108
+ * Detects npm peer-dependency resolution failures. Keyed on the `ERESOLVE`
109
+ * error code, which npm consistently emits for this class of failure across
110
+ * v7+ (`npm ERR! code ERESOLVE` / `npm error code ERESOLVE`). Falls back to a
111
+ * small set of stable phrases in case the code line is missing from the
112
+ * captured output.
113
+ */
114
+ export declare function isNpmPeerDepsError(stderr: string): boolean;
107
115
  export declare function executeMigrations(root: string, migrations: {
108
116
  package: string;
109
117
  name: string;
110
118
  description?: string;
111
119
  version: string;
112
- }[], isVerbose: boolean, shouldCreateCommits: boolean, commitPrefix: string): Promise<{
120
+ }[], isVerbose: boolean, shouldCreateCommits: boolean, commitPrefix: string, shouldSkipInstall?: boolean): Promise<{
113
121
  migrationsWithNoChanges: {
114
122
  package: string;
115
123
  name: string;
@@ -123,7 +131,7 @@ export declare function runNxOrAngularMigration(root: string, migration: {
123
131
  name: string;
124
132
  description?: string;
125
133
  version: string;
126
- }, isVerbose: boolean, shouldCreateCommits: boolean, commitPrefix: string, installDepsIfChanged?: () => void, handleInstallDeps?: boolean): Promise<{
134
+ }, isVerbose: boolean, shouldCreateCommits: boolean, commitPrefix: string, installDepsIfChanged?: () => Promise<void>, handleInstallDeps?: boolean): Promise<{
127
135
  changes: FileChange[];
128
136
  nextSteps: string[];
129
137
  }>;
@@ -4,6 +4,7 @@ exports.Migrator = void 0;
4
4
  exports.formatCommandFailure = formatCommandFailure;
5
5
  exports.normalizeVersion = normalizeVersion;
6
6
  exports.parseMigrationsOptions = parseMigrationsOptions;
7
+ exports.isNpmPeerDepsError = isNpmPeerDepsError;
7
8
  exports.executeMigrations = executeMigrations;
8
9
  exports.runNxOrAngularMigration = runNxOrAngularMigration;
9
10
  exports.migrate = migrate;
@@ -1064,28 +1065,120 @@ function showConnectToCloudMessage() {
1064
1065
  return false;
1065
1066
  }
1066
1067
  }
1067
- function runInstall(nxWorkspaceRoot) {
1068
- let packageManager;
1069
- let pmCommands;
1070
- if (nxWorkspaceRoot) {
1071
- packageManager = (0, package_manager_1.detectPackageManager)(nxWorkspaceRoot);
1072
- pmCommands = (0, package_manager_1.getPackageManagerCommand)(packageManager, nxWorkspaceRoot);
1073
- }
1074
- else {
1075
- pmCommands = (0, package_manager_1.getPackageManagerCommand)();
1076
- }
1068
+ function runInstall(nxWorkspaceRoot, phase = 'pre-migration') {
1069
+ const cwd = nxWorkspaceRoot ?? process.cwd();
1070
+ const packageManager = (0, package_manager_1.detectPackageManager)(cwd);
1071
+ const pmCommands = (0, package_manager_1.getPackageManagerCommand)(packageManager, cwd);
1077
1072
  const installCommand = `${pmCommands.install} ${pmCommands.ignoreScriptsFlag ?? ''}`;
1078
1073
  output_1.output.log({
1079
1074
  title: `Running '${installCommand}' to make sure necessary packages are installed`,
1080
1075
  });
1081
- (0, child_process_1.execSync)(installCommand, {
1082
- stdio: [0, 1, 2],
1083
- windowsHide: true,
1084
- cwd: nxWorkspaceRoot ?? process.cwd(),
1076
+ return new Promise((resolve, reject) => {
1077
+ // For npm, pipe stderr so we can detect peer dependency errors while still
1078
+ // mirroring it live to the user's terminal. Other package managers inherit
1079
+ // stderr directly since we don't need to inspect their output.
1080
+ const shouldCaptureStderr = packageManager === 'npm';
1081
+ const child = (0, child_process_1.spawn)(installCommand, {
1082
+ shell: true,
1083
+ stdio: ['inherit', 'inherit', shouldCaptureStderr ? 'pipe' : 'inherit'],
1084
+ windowsHide: true,
1085
+ cwd,
1086
+ });
1087
+ const stderrChunks = [];
1088
+ child.stderr?.on('data', (chunk) => {
1089
+ process.stderr.write(chunk);
1090
+ stderrChunks.push(chunk);
1091
+ });
1092
+ child.on('error', reject);
1093
+ child.on('close', (code) => {
1094
+ if (code === 0) {
1095
+ resolve();
1096
+ return;
1097
+ }
1098
+ if (shouldCaptureStderr) {
1099
+ const stderr = Buffer.concat(stderrChunks).toString().trim();
1100
+ if (isNpmPeerDepsError(stderr)) {
1101
+ // Log the remediation guidance here so every caller of `runInstall`
1102
+ // (CLI migrate, `nx repair`, single-migration runner, etc.) surfaces
1103
+ // it consistently. Top-level callers catch `NpmPeerDepsInstallError`
1104
+ // and return a non-zero exit code without re-logging.
1105
+ logNpmPeerDepsError(phase);
1106
+ reject(new NpmPeerDepsInstallError());
1107
+ return;
1108
+ }
1109
+ }
1110
+ reject(new Error(`Command failed: ${installCommand}`));
1111
+ });
1085
1112
  });
1086
1113
  }
1087
- async function executeMigrations(root, migrations, isVerbose, shouldCreateCommits, commitPrefix) {
1088
- const changedDepInstaller = new ChangedDepInstaller(root);
1114
+ class NpmPeerDepsInstallError extends Error {
1115
+ constructor() {
1116
+ super('npm install failed due to peer dependency conflicts.');
1117
+ this.name = 'NpmPeerDepsInstallError';
1118
+ }
1119
+ }
1120
+ /**
1121
+ * Detects npm peer-dependency resolution failures. Keyed on the `ERESOLVE`
1122
+ * error code, which npm consistently emits for this class of failure across
1123
+ * v7+ (`npm ERR! code ERESOLVE` / `npm error code ERESOLVE`). Falls back to a
1124
+ * small set of stable phrases in case the code line is missing from the
1125
+ * captured output.
1126
+ */
1127
+ function isNpmPeerDepsError(stderr) {
1128
+ if (/\bERESOLVE\b/.test(stderr)) {
1129
+ return true;
1130
+ }
1131
+ const lowerStderr = stderr.toLowerCase();
1132
+ return (lowerStderr.includes('unable to resolve dependency tree') ||
1133
+ lowerStderr.includes('could not resolve dependency') ||
1134
+ lowerStderr.includes('conflicting peer dependency'));
1135
+ }
1136
+ function logNpmPeerDepsError(phase) {
1137
+ const peerDepsResolutionSteps = [
1138
+ 'Recommended approaches (in order of preference):',
1139
+ '',
1140
+ '1. Use "overrides" in package.json to force compatible versions across the dependency tree.',
1141
+ ' See https://docs.npmjs.com/cli/configuring-npm/package-json#overrides',
1142
+ '2. Persist legacy peer deps resolution in the project ".npmrc":',
1143
+ ' npm config set legacy-peer-deps=true --location=project',
1144
+ ' (bypasses peer dependency resolution; use with caution)',
1145
+ '3. As a last resort, force the installation by running "npm install --force".',
1146
+ ' (does not persist and may produce broken installs)',
1147
+ ];
1148
+ const manualInstallHint = [
1149
+ 'If you installed the dependencies manually, pass "--skip-install" to avoid re-installing them:',
1150
+ ' nx migrate --run-migrations --skip-install',
1151
+ ];
1152
+ if (phase === 'pre-migration') {
1153
+ output_1.output.error({
1154
+ title: 'You need to resolve the peer dependency conflicts before the migration can continue',
1155
+ bodyLines: [
1156
+ ...peerDepsResolutionSteps,
1157
+ '',
1158
+ 'Once the conflicts are resolved, re-run the migrations:',
1159
+ ' nx migrate --run-migrations',
1160
+ '',
1161
+ ...manualInstallHint,
1162
+ ],
1163
+ });
1164
+ }
1165
+ else {
1166
+ output_1.output.error({
1167
+ title: 'Some migrations have been applied, but installing the updated dependencies failed',
1168
+ bodyLines: [
1169
+ ...peerDepsResolutionSteps,
1170
+ '',
1171
+ 'Once the conflicts are resolved, run "npm install" to install the updated dependencies.',
1172
+ 'If the migration was interrupted before completing, re-run the remaining migrations:',
1173
+ ' nx migrate --run-migrations',
1174
+ '',
1175
+ ...manualInstallHint,
1176
+ ],
1177
+ });
1178
+ }
1179
+ }
1180
+ async function executeMigrations(root, migrations, isVerbose, shouldCreateCommits, commitPrefix, shouldSkipInstall = false) {
1181
+ const changedDepInstaller = new ChangedDepInstaller(root, shouldSkipInstall);
1089
1182
  const migrationsWithNoChanges = [];
1090
1183
  const sortedMigrations = migrations.sort((a, b) => {
1091
1184
  // special case for the split configuration migration to run first
@@ -1114,30 +1207,53 @@ async function executeMigrations(root, migrations, isVerbose, shouldCreateCommit
1114
1207
  logger_1.logger.info(`---------------------------------------------------------`);
1115
1208
  }
1116
1209
  catch (e) {
1117
- output_1.output.error({
1118
- title: `Failed to run ${m.name} from ${m.package}. This workspace is NOT up to date!`,
1119
- });
1210
+ if (!(e instanceof NpmPeerDepsInstallError)) {
1211
+ output_1.output.error({
1212
+ title: `Failed to run ${m.name} from ${m.package}. This workspace is NOT up to date!`,
1213
+ });
1214
+ }
1120
1215
  throw e;
1121
1216
  }
1122
1217
  }
1123
1218
  if (!shouldCreateCommits) {
1124
- changedDepInstaller.installDepsIfChanged();
1219
+ await changedDepInstaller.installDepsIfChanged();
1220
+ }
1221
+ if (changedDepInstaller.skippedInstall) {
1222
+ logSkippedPostMigrationInstall(root);
1125
1223
  }
1126
1224
  return { migrationsWithNoChanges, nextSteps: allNextSteps };
1127
1225
  }
1128
1226
  class ChangedDepInstaller {
1129
- constructor(root) {
1227
+ constructor(root, shouldSkipInstall = false) {
1130
1228
  this.root = root;
1229
+ this.shouldSkipInstall = shouldSkipInstall;
1230
+ this._skippedInstall = false;
1131
1231
  this.initialDeps = getStringifiedPackageJsonDeps(root);
1132
1232
  }
1133
- installDepsIfChanged() {
1233
+ get skippedInstall() {
1234
+ return this._skippedInstall;
1235
+ }
1236
+ async installDepsIfChanged() {
1134
1237
  const currentDeps = getStringifiedPackageJsonDeps(this.root);
1135
1238
  if (this.initialDeps !== currentDeps) {
1136
- runInstall(this.root);
1239
+ if (this.shouldSkipInstall) {
1240
+ this._skippedInstall = true;
1241
+ }
1242
+ else {
1243
+ await runInstall(this.root, 'post-migration');
1244
+ }
1137
1245
  }
1138
1246
  this.initialDeps = currentDeps;
1139
1247
  }
1140
1248
  }
1249
+ function logSkippedPostMigrationInstall(root) {
1250
+ const packageManager = (0, package_manager_1.detectPackageManager)(root);
1251
+ const installCommand = (0, package_manager_1.getPackageManagerCommand)(packageManager, root).install;
1252
+ output_1.output.warn({
1253
+ title: 'Migrations updated your dependencies, but the install was skipped',
1254
+ bodyLines: [`Run "${installCommand}" to install the updated dependencies.`],
1255
+ });
1256
+ }
1141
1257
  async function runNxOrAngularMigration(root, migration, isVerbose, shouldCreateCommits, commitPrefix, installDepsIfChanged, handleInstallDeps = false) {
1142
1258
  if (!installDepsIfChanged) {
1143
1259
  const changedDepInstaller = new ChangedDepInstaller(root);
@@ -1173,7 +1289,7 @@ async function runNxOrAngularMigration(root, migration, isVerbose, shouldCreateC
1173
1289
  logger_1.logger.info('');
1174
1290
  }
1175
1291
  if (shouldCreateCommits) {
1176
- installDepsIfChanged();
1292
+ await installDepsIfChanged();
1177
1293
  const commitMessage = `${commitPrefix}${migration.name}`;
1178
1294
  try {
1179
1295
  const committedSha = (0, git_utils_1.commitChanges)(commitMessage, root);
@@ -1190,13 +1306,13 @@ async function runNxOrAngularMigration(root, migration, isVerbose, shouldCreateC
1190
1306
  // if we are running this function alone, we need to install deps internally
1191
1307
  }
1192
1308
  else if (handleInstallDeps) {
1193
- installDepsIfChanged();
1309
+ await installDepsIfChanged();
1194
1310
  }
1195
1311
  return { changes, nextSteps };
1196
1312
  }
1197
- async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits = false, commitPrefix) {
1198
- if (!process.env.NX_MIGRATE_SKIP_INSTALL) {
1199
- runInstall();
1313
+ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits = false, commitPrefix, shouldSkipInstall = false) {
1314
+ if (!shouldSkipInstall && !process.env.NX_MIGRATE_SKIP_INSTALL) {
1315
+ await runInstall();
1200
1316
  }
1201
1317
  if (!__dirname.startsWith(workspace_root_1.workspaceRoot)) {
1202
1318
  // we are running from a temp installation with nx latest, switch to running
@@ -1229,7 +1345,7 @@ async function runMigrations(root, opts, args, isVerbose, shouldCreateCommits =
1229
1345
  (shouldCreateCommits ? ', with each applied in a dedicated commit' : ''),
1230
1346
  });
1231
1347
  const migrations = (0, fileutils_1.readJsonFile)((0, path_1.join)(root, opts.runMigrations)).migrations;
1232
- const { migrationsWithNoChanges, nextSteps } = await executeMigrations(root, migrations, isVerbose, shouldCreateCommits, commitPrefix);
1348
+ const { migrationsWithNoChanges, nextSteps } = await executeMigrations(root, migrations, isVerbose, shouldCreateCommits, commitPrefix, shouldSkipInstall);
1233
1349
  if (migrationsWithNoChanges.length < migrations.length) {
1234
1350
  output_1.output.success({
1235
1351
  title: `Successfully finished running migrations from '${opts.runMigrations}'. This workspace is up to date!`,
@@ -1283,7 +1399,18 @@ async function migrate(root, args, rawArgs) {
1283
1399
  await generateMigrationsJsonAndUpdatePackageJson(root, opts);
1284
1400
  }
1285
1401
  else {
1286
- return runMigrations(root, opts, rawArgs, args['verbose'], args['createCommits'], args['commitPrefix']);
1402
+ try {
1403
+ return await runMigrations(root, opts, rawArgs, args['verbose'], args['createCommits'], args['commitPrefix'], args['skipInstall']);
1404
+ }
1405
+ catch (e) {
1406
+ // The remediation guidance is already logged by `runInstall`; swallow
1407
+ // the error here so `handleErrors` doesn't print a noisy stack after
1408
+ // the friendly output.
1409
+ if (e instanceof NpmPeerDepsInstallError) {
1410
+ return 1;
1411
+ }
1412
+ throw e;
1413
+ }
1287
1414
  }
1288
1415
  });
1289
1416
  }
@@ -15,6 +15,7 @@ const semver_1 = require("semver");
15
15
  const utils_1 = require("../../../tasks-runner/utils");
16
16
  const output_1 = require("../../../utils/output");
17
17
  const git_1 = require("./git");
18
+ const find_matching_projects_1 = require("../../../utils/find-matching-projects");
18
19
  exports.noDiffInChangelogMessage = pc.yellow(`NOTE: There was no diff detected for the changelog entry. Maybe you intended to pass alternative git references via --from and --to?`);
19
20
  function isPrerelease(version) {
20
21
  // prerelease returns an array of matching prerelease "components", or null if the version is not a prerelease
@@ -329,23 +330,32 @@ async function getCommitsRelevantToProjects(projectGraph, commits, projects, nxR
329
330
  // Try to get the graph associated with the commit shortHash
330
331
  // if not available, calculate it and store it in the cache
331
332
  let affectedGraph = await releaseGraph.resolveAffectedFilesPerCommitInProjectGraph(commit, projectGraph);
333
+ // Resolve commit scopes using Nx matcher
334
+ const scopePatterns = commit.scope
335
+ ? commit.scope.split(',').map((s) => s.trim())
336
+ : [];
337
+ let scopedProjects = null;
338
+ if (scopePatterns.length > 0) {
339
+ const matches = (0, find_matching_projects_1.findMatchingProjects)(scopePatterns, projectGraph.nodes);
340
+ // detect ambiguity
341
+ for (const pattern of scopePatterns) {
342
+ const perPatternMatches = (0, find_matching_projects_1.findMatchingProjects)([pattern], projectGraph.nodes);
343
+ if (perPatternMatches.length > 1) {
344
+ throw new Error(`Ambiguous scope "${pattern}" in commit "${commit.message}". ` +
345
+ `Matches: ${perPatternMatches.join(', ')}`);
346
+ }
347
+ }
348
+ scopedProjects = new Set(matches);
349
+ }
332
350
  for (const projectName of Object.keys(affectedGraph.nodes)) {
333
351
  if (projectSet.has(projectName)) {
334
352
  if (!relevantCommits.has(projectName)) {
335
353
  relevantCommits.set(projectName, []);
336
354
  }
337
- if (commit.scope === projectName ||
338
- commit.scope.split(',').includes(projectName) ||
339
- !commit.scope) {
340
- relevantCommits
341
- .get(projectName)
342
- ?.push({ commit, isProjectScopedCommit: true });
343
- }
344
- else {
345
- relevantCommits
346
- .get(projectName)
347
- ?.push({ commit, isProjectScopedCommit: false });
348
- }
355
+ const isProjectScopedCommit = scopedProjects === null || scopedProjects.has(projectName);
356
+ relevantCommits
357
+ .get(projectName)
358
+ ?.push({ commit, isProjectScopedCommit });
349
359
  }
350
360
  }
351
361
  }