@mnci/cli 4.40.9 → 4.42.0

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/cli.js CHANGED
@@ -7896,15 +7896,60 @@ function runKinds(options) {
7896
7896
 
7897
7897
  // src/workspace-diagnostics/check-invariants.use-case.ts
7898
7898
  var import_node_child_process = require("child_process");
7899
+ var import_node_fs30 = require("fs");
7900
+ var import_node_path35 = require("path");
7901
+
7902
+ // src/workspace-diagnostics/locate-stranded-release-tags.use-case.ts
7899
7903
  var import_node_fs29 = require("fs");
7900
7904
  var import_node_path34 = require("path");
7905
+
7906
+ // src/workspace-diagnostics/stranded-release-tags.algorithm.ts
7907
+ function compareVersions2(left, right) {
7908
+ const a = left.split(".").map(Number);
7909
+ const b = right.split(".").map(Number);
7910
+ return a[0] - b[0] || a[1] - b[1] || a[2] - b[2] || 0;
7911
+ }
7912
+ function versionsOf(tags, name) {
7913
+ return tags.filter((tag) => tag.startsWith(`${name}@`)).map((tag) => tag.slice(name.length + 1)).filter((version) => /^\d+\.\d+\.\d+$/.test(version));
7914
+ }
7915
+ function findStrandedReleaseTags(projects, tags) {
7916
+ const stranded = [];
7917
+ for (const project of projects) {
7918
+ const unscoped = /^@[^/]+\/(.+)$/.exec(project)?.[1];
7919
+ if (unscoped === void 0) {
7920
+ continue;
7921
+ }
7922
+ const current = versionsOf(tags, project).sort(compareVersions2).at(-1);
7923
+ const newest = versionsOf(tags, unscoped).sort(compareVersions2).at(-1);
7924
+ if (newest !== void 0 && (current === void 0 || compareVersions2(newest, current) > 0)) {
7925
+ stranded.push({ project, oldTag: `${unscoped}@${newest}`, newTag: `${project}@${newest}` });
7926
+ }
7927
+ }
7928
+ return stranded;
7929
+ }
7930
+
7931
+ // src/workspace-diagnostics/locate-stranded-release-tags.use-case.ts
7932
+ var MNCI_TAG_PATTERN = "{projectName}@{version}";
7933
+ function locateStrandedReleaseTags(workspaceRoot, tagPattern) {
7934
+ if (tagPattern !== void 0 && tagPattern !== MNCI_TAG_PATTERN) {
7935
+ return [];
7936
+ }
7937
+ const listed = runCapture("git", ["tag", "--list"], workspaceRoot);
7938
+ if (listed.status !== 0) {
7939
+ return [];
7940
+ }
7941
+ const projects = (0, import_node_fs29.globSync)("{apps,libs,packages}/*/package.json", { cwd: workspaceRoot }).map((path) => readJson((0, import_node_path34.join)(workspaceRoot, path))).filter((manifest) => manifest.private !== true && manifest.name !== void 0).map((manifest) => manifest.name);
7942
+ return findStrandedReleaseTags(projects, listed.stdout.split(/\r?\n/).filter(Boolean));
7943
+ }
7944
+
7945
+ // src/workspace-diagnostics/check-invariants.use-case.ts
7901
7946
  var SUPPORTED_ESLINT_MAJOR = ESLINT_VERSION.replace(/^\D*/, "").split(".", 1)[0];
7902
7947
  function toPosix2(path) {
7903
7948
  return path.replaceAll("\\", "/");
7904
7949
  }
7905
7950
  function checkEslintConfigs(workspaceRoot) {
7906
- const rootConfigs = (0, import_node_fs29.globSync)("eslint.config.{js,mjs,cjs,ts,mts,cts}", { cwd: workspaceRoot });
7907
- const projectConfigs = (0, import_node_fs29.globSync)("{apps,libs,packages}/*/eslint.config.{js,mjs,cjs,ts,mts,cts}", {
7951
+ const rootConfigs = (0, import_node_fs30.globSync)("eslint.config.{js,mjs,cjs,ts,mts,cts}", { cwd: workspaceRoot });
7952
+ const projectConfigs = (0, import_node_fs30.globSync)("{apps,libs,packages}/*/eslint.config.{js,mjs,cjs,ts,mts,cts}", {
7908
7953
  cwd: workspaceRoot
7909
7954
  }).map((config) => toPosix2(config));
7910
7955
  return [
@@ -7924,10 +7969,10 @@ function checkEslintConfigs(workspaceRoot) {
7924
7969
  ];
7925
7970
  }
7926
7971
  function checkEslintEntryPointReachesTheRules(workspaceRoot) {
7927
- const rules = (0, import_node_path34.join)(workspaceRoot, ESLINT_MNCI_FILENAME);
7928
- if (!(0, import_node_fs29.existsSync)(rules)) return [];
7929
- const entryPoint = (0, import_node_path34.join)(workspaceRoot, ESLINT_USER_FILENAME);
7930
- const source = (0, import_node_fs29.existsSync)(entryPoint) ? (0, import_node_fs29.readFileSync)(entryPoint, "utf8") : "";
7972
+ const rules = (0, import_node_path35.join)(workspaceRoot, ESLINT_MNCI_FILENAME);
7973
+ if (!(0, import_node_fs30.existsSync)(rules)) return [];
7974
+ const entryPoint = (0, import_node_path35.join)(workspaceRoot, ESLINT_USER_FILENAME);
7975
+ const source = (0, import_node_fs30.existsSync)(entryPoint) ? (0, import_node_fs30.readFileSync)(entryPoint, "utf8") : "";
7931
7976
  return [
7932
7977
  {
7933
7978
  check: `${ESLINT_USER_FILENAME} imports the mnci rules`,
@@ -7940,7 +7985,7 @@ function checkEslintEntryPointReachesTheRules(workspaceRoot) {
7940
7985
  function declaredDevDependencies(workspaceRoot) {
7941
7986
  try {
7942
7987
  const manifest = readJson(
7943
- (0, import_node_path34.join)(workspaceRoot, "package.json")
7988
+ (0, import_node_path35.join)(workspaceRoot, "package.json")
7944
7989
  );
7945
7990
  return manifest.devDependencies ?? {};
7946
7991
  } catch {
@@ -7948,7 +7993,7 @@ function declaredDevDependencies(workspaceRoot) {
7948
7993
  }
7949
7994
  }
7950
7995
  function checkNoRetiredFormatter(workspaceRoot) {
7951
- const files = RETIRED_FORMATTER_FILES.filter((file) => fileExists((0, import_node_path34.join)(workspaceRoot, file)));
7996
+ const files = RETIRED_FORMATTER_FILES.filter((file) => fileExists((0, import_node_path35.join)(workspaceRoot, file)));
7952
7997
  const declared = ["prettier", "eslint-config-prettier", "oxlint", "oxfmt", "@mnci/oxlint-config"].filter((name) => declaredDevDependencies(workspaceRoot)[name] !== void 0);
7953
7998
  const stale = [...files, ...declared];
7954
7999
  if (stale.length === 0) {
@@ -7974,7 +8019,7 @@ function checkEslintPlugin(nxJson) {
7974
8019
  };
7975
8020
  }
7976
8021
  function checkResolvedEslint(workspaceRoot) {
7977
- const manifestPath = (0, import_node_path34.join)(workspaceRoot, "node_modules/eslint/package.json");
8022
+ const manifestPath = (0, import_node_path35.join)(workspaceRoot, "node_modules/eslint/package.json");
7978
8023
  if (!fileExists(manifestPath)) {
7979
8024
  return void 0;
7980
8025
  }
@@ -7992,11 +8037,11 @@ function checkResolvedEslint(workspaceRoot) {
7992
8037
  }
7993
8038
  }
7994
8039
  function checkNpmrc(workspaceRoot, registry, scope) {
7995
- const npmrcPath = (0, import_node_path34.join)(workspaceRoot, ".npmrc");
8040
+ const npmrcPath = (0, import_node_path35.join)(workspaceRoot, ".npmrc");
7996
8041
  if (!registry || !fileExists(npmrcPath)) {
7997
8042
  return void 0;
7998
8043
  }
7999
- const npmrc = (0, import_node_fs29.readFileSync)(npmrcPath, "utf8");
8044
+ const npmrc = (0, import_node_fs30.readFileSync)(npmrcPath, "utf8");
8000
8045
  if (registry.kind === "npm") {
8001
8046
  return {
8002
8047
  check: ".npmrc authenticates the public registry",
@@ -8013,11 +8058,11 @@ function checkNpmrc(workspaceRoot, registry, scope) {
8013
8058
  };
8014
8059
  }
8015
8060
  function checkNpmrcCredentialResolves(workspaceRoot, environment = process.env) {
8016
- const npmrcPath = (0, import_node_path34.join)(workspaceRoot, ".npmrc");
8061
+ const npmrcPath = (0, import_node_path35.join)(workspaceRoot, ".npmrc");
8017
8062
  if (!fileExists(npmrcPath)) {
8018
8063
  return void 0;
8019
8064
  }
8020
- const npmrc = (0, import_node_fs29.readFileSync)(npmrcPath, "utf8");
8065
+ const npmrc = (0, import_node_fs30.readFileSync)(npmrcPath, "utf8");
8021
8066
  const credentials = npmrc.split("\n").filter((line) => !line.trimStart().startsWith(";") && !line.trimStart().startsWith("#")).filter((line) => /(?:_authToken|_password|_auth)\s*=/u.test(line));
8022
8067
  const referenced = [
8023
8068
  ...new Set(
@@ -8041,13 +8086,13 @@ function checkNpmrcCredentialResolves(workspaceRoot, environment = process.env)
8041
8086
  };
8042
8087
  }
8043
8088
  function checkNpmAuthMatchesPipeline(workspaceRoot, registry) {
8044
- const npmrcPath = (0, import_node_path34.join)(workspaceRoot, ".npmrc");
8045
- const pipelinePath = (0, import_node_path34.join)(workspaceRoot, "azure-pipelines.yml");
8089
+ const npmrcPath = (0, import_node_path35.join)(workspaceRoot, ".npmrc");
8090
+ const pipelinePath = (0, import_node_path35.join)(workspaceRoot, "azure-pipelines.yml");
8046
8091
  if (registry?.kind !== "azure-artifacts" || !fileExists(npmrcPath) || !fileExists(pipelinePath)) {
8047
8092
  return void 0;
8048
8093
  }
8049
- const holdsCredentials = (0, import_node_fs29.readFileSync)(npmrcPath, "utf8").split("\n").filter((line) => !line.trimStart().startsWith(";") && !line.trimStart().startsWith("#")).some((line) => /(?:_authToken|_password|_auth)\s*=/u.test(line));
8050
- const runsTask = (0, import_node_fs29.readFileSync)(pipelinePath, "utf8").includes("npmAuthenticate@0");
8094
+ const holdsCredentials = (0, import_node_fs30.readFileSync)(npmrcPath, "utf8").split("\n").filter((line) => !line.trimStart().startsWith(";") && !line.trimStart().startsWith("#")).some((line) => /(?:_authToken|_password|_auth)\s*=/u.test(line));
8095
+ const runsTask = (0, import_node_fs30.readFileSync)(pipelinePath, "utf8").includes("npmAuthenticate@0");
8051
8096
  if (!holdsCredentials && !runsTask) {
8052
8097
  return {
8053
8098
  check: "npm auth: .npmrc and azure-pipelines.yml agree",
@@ -8092,13 +8137,13 @@ function npmjsCredentialShape(credentials, environment) {
8092
8137
  }
8093
8138
  function checkVersionActions(workspaceRoot) {
8094
8139
  const candidates = [
8095
- ...(0, import_node_fs29.globSync)("packages/*/pubspec.yaml", { cwd: workspaceRoot }),
8096
- ...(0, import_node_fs29.globSync)("packages/*/*.csproj", { cwd: workspaceRoot }),
8097
- ...(0, import_node_fs29.globSync)("python-packages/*/pyproject.toml", { cwd: workspaceRoot })
8140
+ ...(0, import_node_fs30.globSync)("packages/*/pubspec.yaml", { cwd: workspaceRoot }),
8141
+ ...(0, import_node_fs30.globSync)("packages/*/*.csproj", { cwd: workspaceRoot }),
8142
+ ...(0, import_node_fs30.globSync)("python-packages/*/pyproject.toml", { cwd: workspaceRoot })
8098
8143
  ].map((manifest) => toPosix2(manifest));
8099
8144
  return candidates.flatMap((manifest) => {
8100
8145
  const projectRoot = manifest.slice(0, manifest.lastIndexOf("/"));
8101
- const projectJsonPath = (0, import_node_path34.join)(workspaceRoot, projectRoot, "project.json");
8146
+ const projectJsonPath = (0, import_node_path35.join)(workspaceRoot, projectRoot, "project.json");
8102
8147
  let hasOverride = false;
8103
8148
  try {
8104
8149
  const projectJson = readJson(projectJsonPath);
@@ -8127,7 +8172,7 @@ function declaredTargets(workspaceRoot, projectRoot) {
8127
8172
  ]) {
8128
8173
  try {
8129
8174
  const targets = extract(
8130
- readJson((0, import_node_path34.join)(workspaceRoot, projectRoot, file))
8175
+ readJson((0, import_node_path35.join)(workspaceRoot, projectRoot, file))
8131
8176
  );
8132
8177
  if (targets && typeof targets === "object") {
8133
8178
  sources.push({
@@ -8143,8 +8188,8 @@ function declaredTargets(workspaceRoot, projectRoot) {
8143
8188
  function checkTargetFilesExist(workspaceRoot) {
8144
8189
  const projectRoots = new Set(
8145
8190
  [
8146
- ...(0, import_node_fs29.globSync)("{apps,libs,packages}/*/project.json", { cwd: workspaceRoot }),
8147
- ...(0, import_node_fs29.globSync)("{apps,libs,packages}/*/package.json", { cwd: workspaceRoot })
8191
+ ...(0, import_node_fs30.globSync)("{apps,libs,packages}/*/project.json", { cwd: workspaceRoot }),
8192
+ ...(0, import_node_fs30.globSync)("{apps,libs,packages}/*/package.json", { cwd: workspaceRoot })
8148
8193
  ].map((manifest) => toPosix2(manifest).split("/").slice(0, 2).join("/"))
8149
8194
  );
8150
8195
  return [...projectRoots].flatMap(
@@ -8159,7 +8204,7 @@ function checkOneTargetsFiles(workspaceRoot, source, targetName, target) {
8159
8204
  return TARGET_FILE_OPTIONS.flatMap((option) => {
8160
8205
  const value = target?.options?.[option];
8161
8206
  const checkable = typeof value === "string" && !value.includes("{");
8162
- if (!checkable || fileExists((0, import_node_path34.join)(workspaceRoot, value))) {
8207
+ if (!checkable || fileExists((0, import_node_path35.join)(workspaceRoot, value))) {
8163
8208
  return [];
8164
8209
  }
8165
8210
  return [
@@ -8198,15 +8243,15 @@ function checkGoPluginRegistered(workspaceRoot) {
8198
8243
  };
8199
8244
  }
8200
8245
  function checkGoWorkInSync(workspaceRoot) {
8201
- const goWorkPath = (0, import_node_path34.join)(workspaceRoot, "go.work");
8246
+ const goWorkPath = (0, import_node_path35.join)(workspaceRoot, "go.work");
8202
8247
  if (!fileExists(goWorkPath)) {
8203
8248
  return void 0;
8204
8249
  }
8205
- const contents = (0, import_node_fs29.existsSync)(goWorkPath) ? (0, import_node_fs29.readFileSync)(goWorkPath, "utf8") : "";
8250
+ const contents = (0, import_node_fs30.existsSync)(goWorkPath) ? (0, import_node_fs30.readFileSync)(goWorkPath, "utf8") : "";
8206
8251
  const blockEntries = Array.from(contents.matchAll(/(?:^|\n)\s*use\s+\(([^)]*)\)/g), (match) => match[1].split("\n")).flat();
8207
8252
  const singleEntries = Array.from(contents.matchAll(/(?:^|\n)\s*use\s+(\.\S+)/g), (match) => match[1]);
8208
8253
  const uses = [...blockEntries, ...singleEntries].map((entry) => entry.trim()).filter(Boolean);
8209
- const stale = uses.filter((entry) => !(0, import_node_fs29.existsSync)((0, import_node_path34.join)(workspaceRoot, entry, "go.mod")));
8254
+ const stale = uses.filter((entry) => !(0, import_node_fs30.existsSync)((0, import_node_path35.join)(workspaceRoot, entry, "go.mod")));
8210
8255
  return {
8211
8256
  check: "every go.work `use` entry points to a module that still exists",
8212
8257
  ok: stale.length === 0,
@@ -8222,9 +8267,9 @@ function checkNativeApps(workspaceRoot) {
8222
8267
  { file: ".github/workflows/ci.yml", marker: "native (${{ matrix.os }})" },
8223
8268
  { file: "azure-pipelines.yml", marker: "- job: native" }
8224
8269
  ];
8225
- const findings = pipelines.filter(({ file }) => fileExists((0, import_node_path34.join)(workspaceRoot, file))).map(({ file, marker }) => ({
8270
+ const findings = pipelines.filter(({ file }) => fileExists((0, import_node_path35.join)(workspaceRoot, file))).map(({ file, marker }) => ({
8226
8271
  check: `${file} builds the native apps on every OS`,
8227
- ok: (0, import_node_fs29.readFileSync)((0, import_node_path34.join)(workspaceRoot, file), "utf8").includes(marker),
8272
+ ok: (0, import_node_fs30.readFileSync)((0, import_node_path35.join)(workspaceRoot, file), "utf8").includes(marker),
8228
8273
  detail: `${file} has no native job: CI would verify a cgo app on the one agent that cannot build it`,
8229
8274
  remedy: "run `mnci upgrade`"
8230
8275
  }));
@@ -8237,7 +8282,7 @@ function checkNativeApps(workspaceRoot) {
8237
8282
  return findings;
8238
8283
  }
8239
8284
  function checkNoRootRuntimeDependencies(workspaceRoot) {
8240
- const manifestPath = (0, import_node_path34.join)(workspaceRoot, "package.json");
8285
+ const manifestPath = (0, import_node_path35.join)(workspaceRoot, "package.json");
8241
8286
  if (!fileExists(manifestPath)) {
8242
8287
  return void 0;
8243
8288
  }
@@ -8257,14 +8302,14 @@ function checkNoRootRuntimeDependencies(workspaceRoot) {
8257
8302
  };
8258
8303
  }
8259
8304
  function checkRollupSourceMaps(workspaceRoot) {
8260
- const configs = (0, import_node_fs29.globSync)(["packages/*/rollup.config.cjs", "libs/*/rollup.config.cjs"], {
8305
+ const configs = (0, import_node_fs30.globSync)(["packages/*/rollup.config.cjs", "libs/*/rollup.config.cjs"], {
8261
8306
  cwd: workspaceRoot
8262
8307
  }).map((config) => toPosix2(config));
8263
8308
  return configs.flatMap((relativePath) => {
8264
- const configPath = (0, import_node_path34.join)(workspaceRoot, relativePath);
8309
+ const configPath = (0, import_node_path35.join)(workspaceRoot, relativePath);
8265
8310
  let config;
8266
8311
  try {
8267
- config = (0, import_node_fs29.readFileSync)(configPath, "utf8");
8312
+ config = (0, import_node_fs30.readFileSync)(configPath, "utf8");
8268
8313
  } catch {
8269
8314
  return [];
8270
8315
  }
@@ -8279,11 +8324,11 @@ function checkRollupSourceMaps(workspaceRoot) {
8279
8324
  });
8280
8325
  }
8281
8326
  function checkDeclarationSpecifiers(workspaceRoot) {
8282
- const configs = (0, import_node_fs29.globSync)(["packages/*/rollup.config.cjs", "libs/*/rollup.config.cjs"], {
8327
+ const configs = (0, import_node_fs30.globSync)(["packages/*/rollup.config.cjs", "libs/*/rollup.config.cjs"], {
8283
8328
  cwd: workspaceRoot
8284
8329
  }).map((config) => toPosix2(config));
8285
8330
  return configs.flatMap((relativePath) => {
8286
- const configPath = (0, import_node_path34.join)(workspaceRoot, relativePath);
8331
+ const configPath = (0, import_node_path35.join)(workspaceRoot, relativePath);
8287
8332
  if (!fileExists(configPath)) {
8288
8333
  return [];
8289
8334
  }
@@ -8298,7 +8343,7 @@ function checkDeclarationSpecifiers(workspaceRoot) {
8298
8343
  // `plugins` array (what @nx/react:library writes). For any other shape it would no-op, and
8299
8344
  // naming a command that silently does nothing is the failure mode the source-map check
8300
8345
  // above goes out of its way to avoid, so the remedy depends on the shape.
8301
- remedy: canAddDeclarationSpecifierPlugin((0, import_node_fs29.readFileSync)(configPath, "utf8")) ? "run `mnci upgrade`, which adds the plugin to the plugins array of withNx's second argument" : "add the plugin to withNx's second argument by hand (copy it from a freshly `mnci add npm-lib`'d project) \u2014 mnci does not recognise the shape of this config, so `mnci upgrade` cannot add it"
8346
+ remedy: canAddDeclarationSpecifierPlugin((0, import_node_fs30.readFileSync)(configPath, "utf8")) ? "run `mnci upgrade`, which adds the plugin to the plugins array of withNx's second argument" : "add the plugin to withNx's second argument by hand (copy it from a freshly `mnci add npm-lib`'d project) \u2014 mnci does not recognise the shape of this config, so `mnci upgrade` cannot add it"
8302
8347
  }
8303
8348
  ];
8304
8349
  }
@@ -8314,8 +8359,8 @@ function checkDeclarationSpecifiers(workspaceRoot) {
8314
8359
  }
8315
8360
  function checkPipelinePhases(workspaceRoot) {
8316
8361
  const files = ["azure-pipelines.yml", ".github/workflows/ci.yml"];
8317
- return files.filter((file) => fileExists((0, import_node_path34.join)(workspaceRoot, file))).flatMap((file) => {
8318
- const { verifyActive, switchedOff } = inspectPipeline((0, import_node_fs29.readFileSync)((0, import_node_path34.join)(workspaceRoot, file), "utf8"));
8362
+ return files.filter((file) => fileExists((0, import_node_path35.join)(workspaceRoot, file))).flatMap((file) => {
8363
+ const { verifyActive, switchedOff } = inspectPipeline((0, import_node_fs30.readFileSync)((0, import_node_path35.join)(workspaceRoot, file), "utf8"));
8319
8364
  const verify = {
8320
8365
  check: `${file} runs the enforced verify phase`,
8321
8366
  ok: verifyActive,
@@ -8356,7 +8401,7 @@ function checkDependencyRangesNotPreserved(nxJson) {
8356
8401
  };
8357
8402
  }
8358
8403
  function checkLockfileHasNoLocalRegistry(workspaceRoot) {
8359
- if (!fileExists((0, import_node_path34.join)(workspaceRoot, "package-lock.json"))) {
8404
+ if (!fileExists((0, import_node_path35.join)(workspaceRoot, "package-lock.json"))) {
8360
8405
  return void 0;
8361
8406
  }
8362
8407
  return {
@@ -8366,8 +8411,17 @@ function checkLockfileHasNoLocalRegistry(workspaceRoot) {
8366
8411
  remedy: "run `mnci upgrade`, which removes it, then commit package-lock.json"
8367
8412
  };
8368
8413
  }
8414
+ function checkReleaseTagsResolve(workspaceRoot, nxJson) {
8415
+ const stranded = locateStrandedReleaseTags(workspaceRoot, nxJson.release?.releaseTag?.pattern);
8416
+ return {
8417
+ check: "release tags resolve under each project's current name",
8418
+ ok: stranded.length === 0,
8419
+ detail: `${stranded.map((entry) => `${entry.project} has only ${entry.oldTag}`).join("; ")} - nx release finds no tag under the new name and would release from the disk version, a downgrade`,
8420
+ remedy: `on the commit of each old tag run: ${stranded.map((entry) => `git tag ${entry.newTag} ${entry.oldTag}^{commit}`).join(" ; ")} - then push the tags`
8421
+ };
8422
+ }
8369
8423
  function collectFindings(workspaceRoot) {
8370
- const nxJsonPath = (0, import_node_path34.join)(workspaceRoot, "nx.json");
8424
+ const nxJsonPath = (0, import_node_path35.join)(workspaceRoot, "nx.json");
8371
8425
  if (!fileExists(nxJsonPath)) {
8372
8426
  throw new Error(
8373
8427
  `No nx.json found in ${workspaceRoot} \u2014 run 'mnci doctor' from the workspace root.`
@@ -8394,7 +8448,8 @@ function collectFindings(workspaceRoot) {
8394
8448
  ...checkPipelinePhases(workspaceRoot),
8395
8449
  ...checkNativeBuildConfig(workspaceRoot),
8396
8450
  checkDependencyRangesNotPreserved(nxJson),
8397
- checkLockfileHasNoLocalRegistry(workspaceRoot)
8451
+ checkLockfileHasNoLocalRegistry(workspaceRoot),
8452
+ checkReleaseTagsResolve(workspaceRoot, nxJson)
8398
8453
  ].filter((finding) => finding !== void 0);
8399
8454
  }
8400
8455
  function runDoctor(workspaceRoot, options = {}) {
@@ -8500,11 +8555,11 @@ function runCommands(program, options) {
8500
8555
  }
8501
8556
 
8502
8557
  // src/workspace-info/list-projects.use-case.ts
8503
- var import_node_path35 = require("path");
8558
+ var import_node_path36 = require("path");
8504
8559
  var TYPE_TAG_PREFIX = "type:";
8505
8560
  function listProjects(workspaceRoot) {
8506
8561
  return locateProjects(workspaceRoot).map((location) => {
8507
- const projectJsonPath = (0, import_node_path35.join)(workspaceRoot, location.dir, "project.json");
8562
+ const projectJsonPath = (0, import_node_path36.join)(workspaceRoot, location.dir, "project.json");
8508
8563
  const project = fileExists(projectJsonPath) ? readJson(projectJsonPath) : {};
8509
8564
  const typeTag = (project.tags ?? []).find((tag) => tag.startsWith(TYPE_TAG_PREFIX));
8510
8565
  return {
@@ -8517,7 +8572,7 @@ function listProjects(workspaceRoot) {
8517
8572
  }).toSorted((a, b) => a.dir.localeCompare(b.dir));
8518
8573
  }
8519
8574
  function runProjects(workspaceRoot, options) {
8520
- if (!fileExists((0, import_node_path35.join)(workspaceRoot, "nx.json"))) {
8575
+ if (!fileExists((0, import_node_path36.join)(workspaceRoot, "nx.json"))) {
8521
8576
  throw new Error("No nx.json found here. Run `projects` from the workspace root.");
8522
8577
  }
8523
8578
  const projects = listProjects(workspaceRoot);
@@ -8531,11 +8586,11 @@ function runProjects(workspaceRoot, options) {
8531
8586
  }
8532
8587
 
8533
8588
  // src/workspace-info/read-workspace-info.use-case.ts
8534
- var import_node_path37 = require("path");
8589
+ var import_node_path38 = require("path");
8535
8590
 
8536
8591
  // src/cli-version/check-for-update.use-case.ts
8537
- var import_node_fs30 = require("fs");
8538
- var import_node_path36 = require("path");
8592
+ var import_node_fs31 = require("fs");
8593
+ var import_node_path37 = require("path");
8539
8594
  var import_cross_spawn3 = __toESM(require("cross-spawn"));
8540
8595
  var PACKAGE_NAME2 = "@mnci/cli";
8541
8596
  var REGISTRY_TIMEOUT_MS = 2e3;
@@ -8594,8 +8649,8 @@ function releaseTriple(version) {
8594
8649
  }
8595
8650
  function readCliVersion(packageDirectory) {
8596
8651
  try {
8597
- const manifestPath = (0, import_node_path36.join)(packageDirectory, "..", "package.json");
8598
- const manifest = JSON.parse((0, import_node_fs30.readFileSync)(manifestPath, "utf8"));
8652
+ const manifestPath = (0, import_node_path37.join)(packageDirectory, "..", "package.json");
8653
+ const manifest = JSON.parse((0, import_node_fs31.readFileSync)(manifestPath, "utf8"));
8599
8654
  return manifest.version ?? "0.0.0";
8600
8655
  } catch {
8601
8656
  return "0.0.0";
@@ -8605,7 +8660,7 @@ function readCliVersion(packageDirectory) {
8605
8660
  // src/workspace-info/read-workspace-info.use-case.ts
8606
8661
  function readWorkspaceInfo(workspaceRoot, cliVersion) {
8607
8662
  const latest = fetchLatestVersion() ?? null;
8608
- const isWorkspace = fileExists((0, import_node_path37.join)(workspaceRoot, "nx.json"));
8663
+ const isWorkspace = fileExists((0, import_node_path38.join)(workspaceRoot, "nx.json"));
8609
8664
  return {
8610
8665
  cli: {
8611
8666
  version: cliVersion,
@@ -8626,8 +8681,8 @@ function runInfo(workspaceRoot, cliVersion, options) {
8626
8681
  }
8627
8682
 
8628
8683
  // src/workspace-creation/adopt-directory.use-case.ts
8629
- var import_node_fs31 = require("fs");
8630
- var import_node_path38 = require("path");
8684
+ var import_node_fs32 = require("fs");
8685
+ var import_node_path39 = require("path");
8631
8686
  var NEVER_COPIED = /* @__PURE__ */ new Set([".git", "node_modules"]);
8632
8687
  var AGENT_AND_CI_ENTRIES = [
8633
8688
  "AGENTS.md",
@@ -8658,13 +8713,13 @@ var PREFLIGHT_BLOCKERS = [
8658
8713
  "packages"
8659
8714
  ];
8660
8715
  function assertAdoptableDirectory(target) {
8661
- if (!(0, import_node_fs31.existsSync)(target)) {
8716
+ if (!(0, import_node_fs32.existsSync)(target)) {
8662
8717
  throw new Error(`--into '${target}' does not exist. Create or clone it first.`);
8663
8718
  }
8664
- if (!(0, import_node_fs31.statSync)(target).isDirectory()) {
8719
+ if (!(0, import_node_fs32.statSync)(target).isDirectory()) {
8665
8720
  throw new Error(`--into '${target}' is not a directory.`);
8666
8721
  }
8667
- const blockers = PREFLIGHT_BLOCKERS.filter((entry) => (0, import_node_fs31.existsSync)((0, import_node_path38.join)(target, entry)));
8722
+ const blockers = PREFLIGHT_BLOCKERS.filter((entry) => (0, import_node_fs32.existsSync)((0, import_node_path39.join)(target, entry)));
8668
8723
  if (blockers.length > 0) {
8669
8724
  throw new Error(
8670
8725
  `--into '${target}' already holds ${blockers.join(", ")}, so it is a project already. Use \`mnci upgrade\` to re-apply the overlay to an existing workspace, or point --into at an empty checkout.`
@@ -8672,15 +8727,15 @@ function assertAdoptableDirectory(target) {
8672
8727
  }
8673
8728
  }
8674
8729
  function mergeGitignore(generatedFile, targetFile) {
8675
- const existing = (0, import_node_fs31.readFileSync)(targetFile, "utf8");
8730
+ const existing = (0, import_node_fs32.readFileSync)(targetFile, "utf8");
8676
8731
  const present = new Set(existing.split("\n").map((line) => line.trim()));
8677
- const additions = (0, import_node_fs31.readFileSync)(generatedFile, "utf8").split("\n").map((line) => line.trimEnd()).filter((line) => {
8732
+ const additions = (0, import_node_fs32.readFileSync)(generatedFile, "utf8").split("\n").map((line) => line.trimEnd()).filter((line) => {
8678
8733
  const trimmed = line.trim();
8679
8734
  return trimmed !== "" && !trimmed.startsWith("#") && !present.has(trimmed);
8680
8735
  });
8681
8736
  if (additions.length === 0) return false;
8682
8737
  const separator = existing.endsWith("\n") ? "" : "\n";
8683
- (0, import_node_fs31.writeFileSync)(
8738
+ (0, import_node_fs32.writeFileSync)(
8684
8739
  targetFile,
8685
8740
  `${existing}${separator}
8686
8741
  # Added by mnci (Nx workspace)
@@ -8690,9 +8745,9 @@ ${additions.join("\n")}
8690
8745
  return true;
8691
8746
  }
8692
8747
  function adoptGeneratedWorkspace(generatedRoot, target) {
8693
- const entries = (0, import_node_fs31.readdirSync)(generatedRoot).filter((entry) => !NEVER_COPIED.has(entry));
8748
+ const entries = (0, import_node_fs32.readdirSync)(generatedRoot).filter((entry) => !NEVER_COPIED.has(entry));
8694
8749
  const collisions = entries.filter(
8695
- (entry) => (0, import_node_fs31.existsSync)((0, import_node_path38.join)(target, entry)) && !EXISTING_WINS.has(entry) && entry !== ".gitignore"
8750
+ (entry) => (0, import_node_fs32.existsSync)((0, import_node_path39.join)(target, entry)) && !EXISTING_WINS.has(entry) && entry !== ".gitignore"
8696
8751
  );
8697
8752
  if (collisions.length > 0) {
8698
8753
  throw new Error(
@@ -8701,28 +8756,28 @@ function adoptGeneratedWorkspace(generatedRoot, target) {
8701
8756
  }
8702
8757
  const result = { copied: [], kept: [], mergedGitignore: false };
8703
8758
  for (const entry of entries) {
8704
- const from = (0, import_node_path38.join)(generatedRoot, entry);
8705
- const to = (0, import_node_path38.join)(target, entry);
8706
- if (entry === ".gitignore" && (0, import_node_fs31.existsSync)(to)) {
8759
+ const from = (0, import_node_path39.join)(generatedRoot, entry);
8760
+ const to = (0, import_node_path39.join)(target, entry);
8761
+ if (entry === ".gitignore" && (0, import_node_fs32.existsSync)(to)) {
8707
8762
  result.mergedGitignore = mergeGitignore(from, to);
8708
8763
  result.kept.push(entry);
8709
8764
  continue;
8710
8765
  }
8711
- if ((0, import_node_fs31.existsSync)(to)) {
8766
+ if ((0, import_node_fs32.existsSync)(to)) {
8712
8767
  result.kept.push(entry);
8713
8768
  continue;
8714
8769
  }
8715
- (0, import_node_fs31.cpSync)(from, to, { recursive: true });
8770
+ (0, import_node_fs32.cpSync)(from, to, { recursive: true });
8716
8771
  result.copied.push(entry);
8717
8772
  }
8718
- (0, import_node_fs31.rmSync)(generatedRoot, { recursive: true, force: true });
8773
+ (0, import_node_fs32.rmSync)(generatedRoot, { recursive: true, force: true });
8719
8774
  return result;
8720
8775
  }
8721
8776
 
8722
8777
  // src/workspace-creation/create-workspace.use-case.ts
8723
- var import_node_fs32 = require("fs");
8778
+ var import_node_fs33 = require("fs");
8724
8779
  var import_node_os2 = require("os");
8725
- var import_node_path39 = require("path");
8780
+ var import_node_path40 = require("path");
8726
8781
  function nxCloudProviderValue(ci) {
8727
8782
  return ci === "azure" ? "azure" : "github";
8728
8783
  }
@@ -8765,9 +8820,9 @@ async function resolveRegistry(options) {
8765
8820
  return await promptRegistry();
8766
8821
  }
8767
8822
  async function runNew(name, options) {
8768
- const adoptTarget = options.into === void 0 ? void 0 : (0, import_node_path39.resolve)(process.cwd(), options.into);
8823
+ const adoptTarget = options.into === void 0 ? void 0 : (0, import_node_path40.resolve)(process.cwd(), options.into);
8769
8824
  if (adoptTarget !== void 0) assertAdoptableDirectory(adoptTarget);
8770
- const workspaceName = name ?? (adoptTarget === void 0 ? await promptText("Workspace name") : (0, import_node_path39.basename)(adoptTarget));
8825
+ const workspaceName = name ?? (adoptTarget === void 0 ? await promptText("Workspace name") : (0, import_node_path40.basename)(adoptTarget));
8771
8826
  assertValidProjectName(workspaceName, "Workspace name");
8772
8827
  const scope = options.scope ?? (options.yes ? `@${workspaceName}` : await promptText("npm scope for publishable packages", `@${workspaceName}`));
8773
8828
  const registry = await resolveRegistry(options);
@@ -8780,8 +8835,8 @@ async function runNew(name, options) {
8780
8835
  const npmAuth = resolveNpmAuth(options.npmAuth, registry, ci);
8781
8836
  const stack = await resolveStack(options);
8782
8837
  const nxCloud = options.nxCloud ?? (options.yes ? false : await promptNxCloud());
8783
- const stagingParent = adoptTarget === void 0 ? process.cwd() : (0, import_node_fs32.mkdtempSync)((0, import_node_path39.join)((0, import_node_os2.tmpdir)(), "mnci-new-"));
8784
- let workspaceRoot = (0, import_node_path39.join)(stagingParent, workspaceName);
8838
+ const stagingParent = adoptTarget === void 0 ? process.cwd() : (0, import_node_fs33.mkdtempSync)((0, import_node_path40.join)((0, import_node_os2.tmpdir)(), "mnci-new-"));
8839
+ let workspaceRoot = (0, import_node_path40.join)(stagingParent, workspaceName);
8785
8840
  try {
8786
8841
  logger.step(`Creating Nx workspace '${workspaceName}' (preset: ts)`);
8787
8842
  runNpx(
@@ -8804,7 +8859,7 @@ async function runNew(name, options) {
8804
8859
  workspaceRoot = adoptTarget;
8805
8860
  }
8806
8861
  } finally {
8807
- if (adoptTarget !== void 0) (0, import_node_fs32.rmSync)(stagingParent, { recursive: true, force: true });
8862
+ if (adoptTarget !== void 0) (0, import_node_fs33.rmSync)(stagingParent, { recursive: true, force: true });
8808
8863
  }
8809
8864
  logger.step(
8810
8865
  "Applying MoNecromanCI overlay (VS Code workspace, release config, .npmrc, commitlint, pipeline, stack)"
@@ -8814,8 +8869,8 @@ async function runNew(name, options) {
8814
8869
  { workspaceName, scope, registry, agent, variableGroup, ci, stack, ...npmAuth && { npmAuth } },
8815
8870
  logger.detail
8816
8871
  );
8817
- (0, import_node_fs32.rmSync)((0, import_node_path39.join)(workspaceRoot, "node_modules"), { recursive: true, force: true });
8818
- (0, import_node_fs32.rmSync)((0, import_node_path39.join)(workspaceRoot, "package-lock.json"), { force: true });
8872
+ (0, import_node_fs33.rmSync)((0, import_node_path40.join)(workspaceRoot, "node_modules"), { recursive: true, force: true });
8873
+ (0, import_node_fs33.rmSync)((0, import_node_path40.join)(workspaceRoot, "package-lock.json"), { force: true });
8819
8874
  logger.step("Installing the toolchain (commitlint + husky + linting/formatting)");
8820
8875
  const installStatus = runShell(
8821
8876
  "npm",
@@ -8853,12 +8908,12 @@ function projectKindHintLines(kinds = PROJECT_KINDS) {
8853
8908
  }
8854
8909
 
8855
8910
  // src/workspace-creation/interactive-wizard.use-case.ts
8856
- var import_node_path41 = require("path");
8911
+ var import_node_path42 = require("path");
8857
8912
  var import_prompts4 = require("@inquirer/prompts");
8858
8913
 
8859
8914
  // src/workspace-upgrade/upgrade-workspace.use-case.ts
8860
- var import_node_fs33 = require("fs");
8861
- var import_node_path40 = require("path");
8915
+ var import_node_fs34 = require("fs");
8916
+ var import_node_path41 = require("path");
8862
8917
  function resolveRegistry2(options, persisted) {
8863
8918
  if (options.registry === "azure-artifacts" || options.organization && options.artifactsFeed) {
8864
8919
  const persistedAzure = persisted?.kind === "azure-artifacts" ? persisted : void 0;
@@ -8887,7 +8942,7 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
8887
8942
  return persisted.workspaceName;
8888
8943
  }
8889
8944
  try {
8890
- const existing = (0, import_node_fs33.readdirSync)(workspaceRoot).find(
8945
+ const existing = (0, import_node_fs34.readdirSync)(workspaceRoot).find(
8891
8946
  (file) => file.endsWith(".code-workspace") && file !== "undefined.code-workspace"
8892
8947
  );
8893
8948
  if (existing) {
@@ -8895,11 +8950,11 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
8895
8950
  }
8896
8951
  } catch {
8897
8952
  }
8898
- return (0, import_node_path40.basename)(workspaceRoot);
8953
+ return (0, import_node_path41.basename)(workspaceRoot);
8899
8954
  }
8900
8955
  function detectNpmAuth(workspaceRoot) {
8901
8956
  try {
8902
- return (0, import_node_fs33.readFileSync)((0, import_node_path40.join)(workspaceRoot, "azure-pipelines.yml"), "utf8").includes("npmAuthenticate@0") ? "build-identity" : void 0;
8957
+ return (0, import_node_fs34.readFileSync)((0, import_node_path41.join)(workspaceRoot, "azure-pipelines.yml"), "utf8").includes("npmAuthenticate@0") ? "build-identity" : void 0;
8903
8958
  } catch {
8904
8959
  return void 0;
8905
8960
  }
@@ -8946,7 +9001,7 @@ function resolveOverlayOptions(workspaceRoot, options, persisted) {
8946
9001
  };
8947
9002
  }
8948
9003
  function runUpgrade(workspaceRoot, options) {
8949
- if (!fileExists((0, import_node_path40.join)(workspaceRoot, "nx.json"))) {
9004
+ if (!fileExists((0, import_node_path41.join)(workspaceRoot, "nx.json"))) {
8950
9005
  throw new Error(
8951
9006
  `No nx.json found in ${workspaceRoot} \u2014 this does not look like an Nx workspace. Run 'mnci upgrade' from the workspace root.`
8952
9007
  );
@@ -9020,13 +9075,18 @@ function runUpgrade(workspaceRoot, options) {
9020
9075
  "Formatting the workspace (eslint --fix) \u2014 the slowest step, minutes on a large workspace"
9021
9076
  );
9022
9077
  runFormatter(workspaceRoot);
9078
+ const nxJson = readJson((0, import_node_path41.join)(workspaceRoot, "nx.json"));
9079
+ const stranded = locateStrandedReleaseTags(workspaceRoot, nxJson.release?.releaseTag?.pattern);
9080
+ for (const entry of stranded) {
9081
+ logger.warn(`${entry.project} has release tags only under ${entry.oldTag}: nx release would restart it from its disk version. Run \`git tag ${entry.newTag} ${entry.oldTag}^{commit}\` before the next release (\`mnci doctor\` lists them all).`);
9082
+ }
9023
9083
  logger.success("Done. Review the changes with `git diff` before committing.");
9024
9084
  logger.info("Run `npm install` and commit package-lock.json too: the overlay may have added or moved a dependency, and CI installs with `npm ci`, which refuses a stale lockfile.");
9025
9085
  }
9026
9086
 
9027
9087
  // src/workspace-creation/interactive-wizard.use-case.ts
9028
9088
  async function runInteractive() {
9029
- const inWorkspace = fileExists((0, import_node_path41.join)(process.cwd(), "nx.json"));
9089
+ const inWorkspace = fileExists((0, import_node_path42.join)(process.cwd(), "nx.json"));
9030
9090
  const choiceNew = { name: "Create a new monorepo", value: "new" };
9031
9091
  const choiceAdd = { name: "Add a project to this workspace", value: "add" };
9032
9092
  const choiceUpgrade = {