@mnci/cli 4.41.0 → 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,6 +7896,10 @@ 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");
7901
7905
 
@@ -7924,14 +7928,28 @@ function findStrandedReleaseTags(projects, tags) {
7924
7928
  return stranded;
7925
7929
  }
7926
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
+
7927
7945
  // src/workspace-diagnostics/check-invariants.use-case.ts
7928
7946
  var SUPPORTED_ESLINT_MAJOR = ESLINT_VERSION.replace(/^\D*/, "").split(".", 1)[0];
7929
7947
  function toPosix2(path) {
7930
7948
  return path.replaceAll("\\", "/");
7931
7949
  }
7932
7950
  function checkEslintConfigs(workspaceRoot) {
7933
- const rootConfigs = (0, import_node_fs29.globSync)("eslint.config.{js,mjs,cjs,ts,mts,cts}", { cwd: workspaceRoot });
7934
- 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}", {
7935
7953
  cwd: workspaceRoot
7936
7954
  }).map((config) => toPosix2(config));
7937
7955
  return [
@@ -7951,10 +7969,10 @@ function checkEslintConfigs(workspaceRoot) {
7951
7969
  ];
7952
7970
  }
7953
7971
  function checkEslintEntryPointReachesTheRules(workspaceRoot) {
7954
- const rules = (0, import_node_path34.join)(workspaceRoot, ESLINT_MNCI_FILENAME);
7955
- if (!(0, import_node_fs29.existsSync)(rules)) return [];
7956
- const entryPoint = (0, import_node_path34.join)(workspaceRoot, ESLINT_USER_FILENAME);
7957
- 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") : "";
7958
7976
  return [
7959
7977
  {
7960
7978
  check: `${ESLINT_USER_FILENAME} imports the mnci rules`,
@@ -7967,7 +7985,7 @@ function checkEslintEntryPointReachesTheRules(workspaceRoot) {
7967
7985
  function declaredDevDependencies(workspaceRoot) {
7968
7986
  try {
7969
7987
  const manifest = readJson(
7970
- (0, import_node_path34.join)(workspaceRoot, "package.json")
7988
+ (0, import_node_path35.join)(workspaceRoot, "package.json")
7971
7989
  );
7972
7990
  return manifest.devDependencies ?? {};
7973
7991
  } catch {
@@ -7975,7 +7993,7 @@ function declaredDevDependencies(workspaceRoot) {
7975
7993
  }
7976
7994
  }
7977
7995
  function checkNoRetiredFormatter(workspaceRoot) {
7978
- 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)));
7979
7997
  const declared = ["prettier", "eslint-config-prettier", "oxlint", "oxfmt", "@mnci/oxlint-config"].filter((name) => declaredDevDependencies(workspaceRoot)[name] !== void 0);
7980
7998
  const stale = [...files, ...declared];
7981
7999
  if (stale.length === 0) {
@@ -8001,7 +8019,7 @@ function checkEslintPlugin(nxJson) {
8001
8019
  };
8002
8020
  }
8003
8021
  function checkResolvedEslint(workspaceRoot) {
8004
- 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");
8005
8023
  if (!fileExists(manifestPath)) {
8006
8024
  return void 0;
8007
8025
  }
@@ -8019,11 +8037,11 @@ function checkResolvedEslint(workspaceRoot) {
8019
8037
  }
8020
8038
  }
8021
8039
  function checkNpmrc(workspaceRoot, registry, scope) {
8022
- const npmrcPath = (0, import_node_path34.join)(workspaceRoot, ".npmrc");
8040
+ const npmrcPath = (0, import_node_path35.join)(workspaceRoot, ".npmrc");
8023
8041
  if (!registry || !fileExists(npmrcPath)) {
8024
8042
  return void 0;
8025
8043
  }
8026
- const npmrc = (0, import_node_fs29.readFileSync)(npmrcPath, "utf8");
8044
+ const npmrc = (0, import_node_fs30.readFileSync)(npmrcPath, "utf8");
8027
8045
  if (registry.kind === "npm") {
8028
8046
  return {
8029
8047
  check: ".npmrc authenticates the public registry",
@@ -8040,11 +8058,11 @@ function checkNpmrc(workspaceRoot, registry, scope) {
8040
8058
  };
8041
8059
  }
8042
8060
  function checkNpmrcCredentialResolves(workspaceRoot, environment = process.env) {
8043
- const npmrcPath = (0, import_node_path34.join)(workspaceRoot, ".npmrc");
8061
+ const npmrcPath = (0, import_node_path35.join)(workspaceRoot, ".npmrc");
8044
8062
  if (!fileExists(npmrcPath)) {
8045
8063
  return void 0;
8046
8064
  }
8047
- const npmrc = (0, import_node_fs29.readFileSync)(npmrcPath, "utf8");
8065
+ const npmrc = (0, import_node_fs30.readFileSync)(npmrcPath, "utf8");
8048
8066
  const credentials = npmrc.split("\n").filter((line) => !line.trimStart().startsWith(";") && !line.trimStart().startsWith("#")).filter((line) => /(?:_authToken|_password|_auth)\s*=/u.test(line));
8049
8067
  const referenced = [
8050
8068
  ...new Set(
@@ -8068,13 +8086,13 @@ function checkNpmrcCredentialResolves(workspaceRoot, environment = process.env)
8068
8086
  };
8069
8087
  }
8070
8088
  function checkNpmAuthMatchesPipeline(workspaceRoot, registry) {
8071
- const npmrcPath = (0, import_node_path34.join)(workspaceRoot, ".npmrc");
8072
- 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");
8073
8091
  if (registry?.kind !== "azure-artifacts" || !fileExists(npmrcPath) || !fileExists(pipelinePath)) {
8074
8092
  return void 0;
8075
8093
  }
8076
- 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));
8077
- 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");
8078
8096
  if (!holdsCredentials && !runsTask) {
8079
8097
  return {
8080
8098
  check: "npm auth: .npmrc and azure-pipelines.yml agree",
@@ -8119,13 +8137,13 @@ function npmjsCredentialShape(credentials, environment) {
8119
8137
  }
8120
8138
  function checkVersionActions(workspaceRoot) {
8121
8139
  const candidates = [
8122
- ...(0, import_node_fs29.globSync)("packages/*/pubspec.yaml", { cwd: workspaceRoot }),
8123
- ...(0, import_node_fs29.globSync)("packages/*/*.csproj", { cwd: workspaceRoot }),
8124
- ...(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 })
8125
8143
  ].map((manifest) => toPosix2(manifest));
8126
8144
  return candidates.flatMap((manifest) => {
8127
8145
  const projectRoot = manifest.slice(0, manifest.lastIndexOf("/"));
8128
- const projectJsonPath = (0, import_node_path34.join)(workspaceRoot, projectRoot, "project.json");
8146
+ const projectJsonPath = (0, import_node_path35.join)(workspaceRoot, projectRoot, "project.json");
8129
8147
  let hasOverride = false;
8130
8148
  try {
8131
8149
  const projectJson = readJson(projectJsonPath);
@@ -8154,7 +8172,7 @@ function declaredTargets(workspaceRoot, projectRoot) {
8154
8172
  ]) {
8155
8173
  try {
8156
8174
  const targets = extract(
8157
- readJson((0, import_node_path34.join)(workspaceRoot, projectRoot, file))
8175
+ readJson((0, import_node_path35.join)(workspaceRoot, projectRoot, file))
8158
8176
  );
8159
8177
  if (targets && typeof targets === "object") {
8160
8178
  sources.push({
@@ -8170,8 +8188,8 @@ function declaredTargets(workspaceRoot, projectRoot) {
8170
8188
  function checkTargetFilesExist(workspaceRoot) {
8171
8189
  const projectRoots = new Set(
8172
8190
  [
8173
- ...(0, import_node_fs29.globSync)("{apps,libs,packages}/*/project.json", { cwd: workspaceRoot }),
8174
- ...(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 })
8175
8193
  ].map((manifest) => toPosix2(manifest).split("/").slice(0, 2).join("/"))
8176
8194
  );
8177
8195
  return [...projectRoots].flatMap(
@@ -8186,7 +8204,7 @@ function checkOneTargetsFiles(workspaceRoot, source, targetName, target) {
8186
8204
  return TARGET_FILE_OPTIONS.flatMap((option) => {
8187
8205
  const value = target?.options?.[option];
8188
8206
  const checkable = typeof value === "string" && !value.includes("{");
8189
- if (!checkable || fileExists((0, import_node_path34.join)(workspaceRoot, value))) {
8207
+ if (!checkable || fileExists((0, import_node_path35.join)(workspaceRoot, value))) {
8190
8208
  return [];
8191
8209
  }
8192
8210
  return [
@@ -8225,15 +8243,15 @@ function checkGoPluginRegistered(workspaceRoot) {
8225
8243
  };
8226
8244
  }
8227
8245
  function checkGoWorkInSync(workspaceRoot) {
8228
- const goWorkPath = (0, import_node_path34.join)(workspaceRoot, "go.work");
8246
+ const goWorkPath = (0, import_node_path35.join)(workspaceRoot, "go.work");
8229
8247
  if (!fileExists(goWorkPath)) {
8230
8248
  return void 0;
8231
8249
  }
8232
- 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") : "";
8233
8251
  const blockEntries = Array.from(contents.matchAll(/(?:^|\n)\s*use\s+\(([^)]*)\)/g), (match) => match[1].split("\n")).flat();
8234
8252
  const singleEntries = Array.from(contents.matchAll(/(?:^|\n)\s*use\s+(\.\S+)/g), (match) => match[1]);
8235
8253
  const uses = [...blockEntries, ...singleEntries].map((entry) => entry.trim()).filter(Boolean);
8236
- 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")));
8237
8255
  return {
8238
8256
  check: "every go.work `use` entry points to a module that still exists",
8239
8257
  ok: stale.length === 0,
@@ -8249,9 +8267,9 @@ function checkNativeApps(workspaceRoot) {
8249
8267
  { file: ".github/workflows/ci.yml", marker: "native (${{ matrix.os }})" },
8250
8268
  { file: "azure-pipelines.yml", marker: "- job: native" }
8251
8269
  ];
8252
- 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 }) => ({
8253
8271
  check: `${file} builds the native apps on every OS`,
8254
- 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),
8255
8273
  detail: `${file} has no native job: CI would verify a cgo app on the one agent that cannot build it`,
8256
8274
  remedy: "run `mnci upgrade`"
8257
8275
  }));
@@ -8264,7 +8282,7 @@ function checkNativeApps(workspaceRoot) {
8264
8282
  return findings;
8265
8283
  }
8266
8284
  function checkNoRootRuntimeDependencies(workspaceRoot) {
8267
- const manifestPath = (0, import_node_path34.join)(workspaceRoot, "package.json");
8285
+ const manifestPath = (0, import_node_path35.join)(workspaceRoot, "package.json");
8268
8286
  if (!fileExists(manifestPath)) {
8269
8287
  return void 0;
8270
8288
  }
@@ -8284,14 +8302,14 @@ function checkNoRootRuntimeDependencies(workspaceRoot) {
8284
8302
  };
8285
8303
  }
8286
8304
  function checkRollupSourceMaps(workspaceRoot) {
8287
- 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"], {
8288
8306
  cwd: workspaceRoot
8289
8307
  }).map((config) => toPosix2(config));
8290
8308
  return configs.flatMap((relativePath) => {
8291
- const configPath = (0, import_node_path34.join)(workspaceRoot, relativePath);
8309
+ const configPath = (0, import_node_path35.join)(workspaceRoot, relativePath);
8292
8310
  let config;
8293
8311
  try {
8294
- config = (0, import_node_fs29.readFileSync)(configPath, "utf8");
8312
+ config = (0, import_node_fs30.readFileSync)(configPath, "utf8");
8295
8313
  } catch {
8296
8314
  return [];
8297
8315
  }
@@ -8306,11 +8324,11 @@ function checkRollupSourceMaps(workspaceRoot) {
8306
8324
  });
8307
8325
  }
8308
8326
  function checkDeclarationSpecifiers(workspaceRoot) {
8309
- 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"], {
8310
8328
  cwd: workspaceRoot
8311
8329
  }).map((config) => toPosix2(config));
8312
8330
  return configs.flatMap((relativePath) => {
8313
- const configPath = (0, import_node_path34.join)(workspaceRoot, relativePath);
8331
+ const configPath = (0, import_node_path35.join)(workspaceRoot, relativePath);
8314
8332
  if (!fileExists(configPath)) {
8315
8333
  return [];
8316
8334
  }
@@ -8325,7 +8343,7 @@ function checkDeclarationSpecifiers(workspaceRoot) {
8325
8343
  // `plugins` array (what @nx/react:library writes). For any other shape it would no-op, and
8326
8344
  // naming a command that silently does nothing is the failure mode the source-map check
8327
8345
  // above goes out of its way to avoid, so the remedy depends on the shape.
8328
- 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"
8329
8347
  }
8330
8348
  ];
8331
8349
  }
@@ -8341,8 +8359,8 @@ function checkDeclarationSpecifiers(workspaceRoot) {
8341
8359
  }
8342
8360
  function checkPipelinePhases(workspaceRoot) {
8343
8361
  const files = ["azure-pipelines.yml", ".github/workflows/ci.yml"];
8344
- return files.filter((file) => fileExists((0, import_node_path34.join)(workspaceRoot, file))).flatMap((file) => {
8345
- 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"));
8346
8364
  const verify = {
8347
8365
  check: `${file} runs the enforced verify phase`,
8348
8366
  ok: verifyActive,
@@ -8383,7 +8401,7 @@ function checkDependencyRangesNotPreserved(nxJson) {
8383
8401
  };
8384
8402
  }
8385
8403
  function checkLockfileHasNoLocalRegistry(workspaceRoot) {
8386
- if (!fileExists((0, import_node_path34.join)(workspaceRoot, "package-lock.json"))) {
8404
+ if (!fileExists((0, import_node_path35.join)(workspaceRoot, "package-lock.json"))) {
8387
8405
  return void 0;
8388
8406
  }
8389
8407
  return {
@@ -8394,16 +8412,7 @@ function checkLockfileHasNoLocalRegistry(workspaceRoot) {
8394
8412
  };
8395
8413
  }
8396
8414
  function checkReleaseTagsResolve(workspaceRoot, nxJson) {
8397
- const pattern = nxJson.release?.releaseTag?.pattern;
8398
- if (pattern !== void 0 && pattern !== "{projectName}@{version}") {
8399
- return void 0;
8400
- }
8401
- const listed = runCapture("git", ["tag", "--list"], workspaceRoot);
8402
- if (listed.status !== 0) {
8403
- return void 0;
8404
- }
8405
- 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);
8406
- const stranded = findStrandedReleaseTags(projects, listed.stdout.split(/\r?\n/).filter(Boolean));
8415
+ const stranded = locateStrandedReleaseTags(workspaceRoot, nxJson.release?.releaseTag?.pattern);
8407
8416
  return {
8408
8417
  check: "release tags resolve under each project's current name",
8409
8418
  ok: stranded.length === 0,
@@ -8412,7 +8421,7 @@ function checkReleaseTagsResolve(workspaceRoot, nxJson) {
8412
8421
  };
8413
8422
  }
8414
8423
  function collectFindings(workspaceRoot) {
8415
- const nxJsonPath = (0, import_node_path34.join)(workspaceRoot, "nx.json");
8424
+ const nxJsonPath = (0, import_node_path35.join)(workspaceRoot, "nx.json");
8416
8425
  if (!fileExists(nxJsonPath)) {
8417
8426
  throw new Error(
8418
8427
  `No nx.json found in ${workspaceRoot} \u2014 run 'mnci doctor' from the workspace root.`
@@ -8546,11 +8555,11 @@ function runCommands(program, options) {
8546
8555
  }
8547
8556
 
8548
8557
  // src/workspace-info/list-projects.use-case.ts
8549
- var import_node_path35 = require("path");
8558
+ var import_node_path36 = require("path");
8550
8559
  var TYPE_TAG_PREFIX = "type:";
8551
8560
  function listProjects(workspaceRoot) {
8552
8561
  return locateProjects(workspaceRoot).map((location) => {
8553
- 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");
8554
8563
  const project = fileExists(projectJsonPath) ? readJson(projectJsonPath) : {};
8555
8564
  const typeTag = (project.tags ?? []).find((tag) => tag.startsWith(TYPE_TAG_PREFIX));
8556
8565
  return {
@@ -8563,7 +8572,7 @@ function listProjects(workspaceRoot) {
8563
8572
  }).toSorted((a, b) => a.dir.localeCompare(b.dir));
8564
8573
  }
8565
8574
  function runProjects(workspaceRoot, options) {
8566
- if (!fileExists((0, import_node_path35.join)(workspaceRoot, "nx.json"))) {
8575
+ if (!fileExists((0, import_node_path36.join)(workspaceRoot, "nx.json"))) {
8567
8576
  throw new Error("No nx.json found here. Run `projects` from the workspace root.");
8568
8577
  }
8569
8578
  const projects = listProjects(workspaceRoot);
@@ -8577,11 +8586,11 @@ function runProjects(workspaceRoot, options) {
8577
8586
  }
8578
8587
 
8579
8588
  // src/workspace-info/read-workspace-info.use-case.ts
8580
- var import_node_path37 = require("path");
8589
+ var import_node_path38 = require("path");
8581
8590
 
8582
8591
  // src/cli-version/check-for-update.use-case.ts
8583
- var import_node_fs30 = require("fs");
8584
- var import_node_path36 = require("path");
8592
+ var import_node_fs31 = require("fs");
8593
+ var import_node_path37 = require("path");
8585
8594
  var import_cross_spawn3 = __toESM(require("cross-spawn"));
8586
8595
  var PACKAGE_NAME2 = "@mnci/cli";
8587
8596
  var REGISTRY_TIMEOUT_MS = 2e3;
@@ -8640,8 +8649,8 @@ function releaseTriple(version) {
8640
8649
  }
8641
8650
  function readCliVersion(packageDirectory) {
8642
8651
  try {
8643
- const manifestPath = (0, import_node_path36.join)(packageDirectory, "..", "package.json");
8644
- 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"));
8645
8654
  return manifest.version ?? "0.0.0";
8646
8655
  } catch {
8647
8656
  return "0.0.0";
@@ -8651,7 +8660,7 @@ function readCliVersion(packageDirectory) {
8651
8660
  // src/workspace-info/read-workspace-info.use-case.ts
8652
8661
  function readWorkspaceInfo(workspaceRoot, cliVersion) {
8653
8662
  const latest = fetchLatestVersion() ?? null;
8654
- const isWorkspace = fileExists((0, import_node_path37.join)(workspaceRoot, "nx.json"));
8663
+ const isWorkspace = fileExists((0, import_node_path38.join)(workspaceRoot, "nx.json"));
8655
8664
  return {
8656
8665
  cli: {
8657
8666
  version: cliVersion,
@@ -8672,8 +8681,8 @@ function runInfo(workspaceRoot, cliVersion, options) {
8672
8681
  }
8673
8682
 
8674
8683
  // src/workspace-creation/adopt-directory.use-case.ts
8675
- var import_node_fs31 = require("fs");
8676
- var import_node_path38 = require("path");
8684
+ var import_node_fs32 = require("fs");
8685
+ var import_node_path39 = require("path");
8677
8686
  var NEVER_COPIED = /* @__PURE__ */ new Set([".git", "node_modules"]);
8678
8687
  var AGENT_AND_CI_ENTRIES = [
8679
8688
  "AGENTS.md",
@@ -8704,13 +8713,13 @@ var PREFLIGHT_BLOCKERS = [
8704
8713
  "packages"
8705
8714
  ];
8706
8715
  function assertAdoptableDirectory(target) {
8707
- if (!(0, import_node_fs31.existsSync)(target)) {
8716
+ if (!(0, import_node_fs32.existsSync)(target)) {
8708
8717
  throw new Error(`--into '${target}' does not exist. Create or clone it first.`);
8709
8718
  }
8710
- if (!(0, import_node_fs31.statSync)(target).isDirectory()) {
8719
+ if (!(0, import_node_fs32.statSync)(target).isDirectory()) {
8711
8720
  throw new Error(`--into '${target}' is not a directory.`);
8712
8721
  }
8713
- 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)));
8714
8723
  if (blockers.length > 0) {
8715
8724
  throw new Error(
8716
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.`
@@ -8718,15 +8727,15 @@ function assertAdoptableDirectory(target) {
8718
8727
  }
8719
8728
  }
8720
8729
  function mergeGitignore(generatedFile, targetFile) {
8721
- const existing = (0, import_node_fs31.readFileSync)(targetFile, "utf8");
8730
+ const existing = (0, import_node_fs32.readFileSync)(targetFile, "utf8");
8722
8731
  const present = new Set(existing.split("\n").map((line) => line.trim()));
8723
- 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) => {
8724
8733
  const trimmed = line.trim();
8725
8734
  return trimmed !== "" && !trimmed.startsWith("#") && !present.has(trimmed);
8726
8735
  });
8727
8736
  if (additions.length === 0) return false;
8728
8737
  const separator = existing.endsWith("\n") ? "" : "\n";
8729
- (0, import_node_fs31.writeFileSync)(
8738
+ (0, import_node_fs32.writeFileSync)(
8730
8739
  targetFile,
8731
8740
  `${existing}${separator}
8732
8741
  # Added by mnci (Nx workspace)
@@ -8736,9 +8745,9 @@ ${additions.join("\n")}
8736
8745
  return true;
8737
8746
  }
8738
8747
  function adoptGeneratedWorkspace(generatedRoot, target) {
8739
- 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));
8740
8749
  const collisions = entries.filter(
8741
- (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"
8742
8751
  );
8743
8752
  if (collisions.length > 0) {
8744
8753
  throw new Error(
@@ -8747,28 +8756,28 @@ function adoptGeneratedWorkspace(generatedRoot, target) {
8747
8756
  }
8748
8757
  const result = { copied: [], kept: [], mergedGitignore: false };
8749
8758
  for (const entry of entries) {
8750
- const from = (0, import_node_path38.join)(generatedRoot, entry);
8751
- const to = (0, import_node_path38.join)(target, entry);
8752
- 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)) {
8753
8762
  result.mergedGitignore = mergeGitignore(from, to);
8754
8763
  result.kept.push(entry);
8755
8764
  continue;
8756
8765
  }
8757
- if ((0, import_node_fs31.existsSync)(to)) {
8766
+ if ((0, import_node_fs32.existsSync)(to)) {
8758
8767
  result.kept.push(entry);
8759
8768
  continue;
8760
8769
  }
8761
- (0, import_node_fs31.cpSync)(from, to, { recursive: true });
8770
+ (0, import_node_fs32.cpSync)(from, to, { recursive: true });
8762
8771
  result.copied.push(entry);
8763
8772
  }
8764
- (0, import_node_fs31.rmSync)(generatedRoot, { recursive: true, force: true });
8773
+ (0, import_node_fs32.rmSync)(generatedRoot, { recursive: true, force: true });
8765
8774
  return result;
8766
8775
  }
8767
8776
 
8768
8777
  // src/workspace-creation/create-workspace.use-case.ts
8769
- var import_node_fs32 = require("fs");
8778
+ var import_node_fs33 = require("fs");
8770
8779
  var import_node_os2 = require("os");
8771
- var import_node_path39 = require("path");
8780
+ var import_node_path40 = require("path");
8772
8781
  function nxCloudProviderValue(ci) {
8773
8782
  return ci === "azure" ? "azure" : "github";
8774
8783
  }
@@ -8811,9 +8820,9 @@ async function resolveRegistry(options) {
8811
8820
  return await promptRegistry();
8812
8821
  }
8813
8822
  async function runNew(name, options) {
8814
- 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);
8815
8824
  if (adoptTarget !== void 0) assertAdoptableDirectory(adoptTarget);
8816
- 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));
8817
8826
  assertValidProjectName(workspaceName, "Workspace name");
8818
8827
  const scope = options.scope ?? (options.yes ? `@${workspaceName}` : await promptText("npm scope for publishable packages", `@${workspaceName}`));
8819
8828
  const registry = await resolveRegistry(options);
@@ -8826,8 +8835,8 @@ async function runNew(name, options) {
8826
8835
  const npmAuth = resolveNpmAuth(options.npmAuth, registry, ci);
8827
8836
  const stack = await resolveStack(options);
8828
8837
  const nxCloud = options.nxCloud ?? (options.yes ? false : await promptNxCloud());
8829
- const stagingParent = adoptTarget === void 0 ? process.cwd() : (0, import_node_fs32.mkdtempSync)((0, import_node_path39.join)((0, import_node_os2.tmpdir)(), "mnci-new-"));
8830
- 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);
8831
8840
  try {
8832
8841
  logger.step(`Creating Nx workspace '${workspaceName}' (preset: ts)`);
8833
8842
  runNpx(
@@ -8850,7 +8859,7 @@ async function runNew(name, options) {
8850
8859
  workspaceRoot = adoptTarget;
8851
8860
  }
8852
8861
  } finally {
8853
- 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 });
8854
8863
  }
8855
8864
  logger.step(
8856
8865
  "Applying MoNecromanCI overlay (VS Code workspace, release config, .npmrc, commitlint, pipeline, stack)"
@@ -8860,8 +8869,8 @@ async function runNew(name, options) {
8860
8869
  { workspaceName, scope, registry, agent, variableGroup, ci, stack, ...npmAuth && { npmAuth } },
8861
8870
  logger.detail
8862
8871
  );
8863
- (0, import_node_fs32.rmSync)((0, import_node_path39.join)(workspaceRoot, "node_modules"), { recursive: true, force: true });
8864
- (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 });
8865
8874
  logger.step("Installing the toolchain (commitlint + husky + linting/formatting)");
8866
8875
  const installStatus = runShell(
8867
8876
  "npm",
@@ -8899,12 +8908,12 @@ function projectKindHintLines(kinds = PROJECT_KINDS) {
8899
8908
  }
8900
8909
 
8901
8910
  // src/workspace-creation/interactive-wizard.use-case.ts
8902
- var import_node_path41 = require("path");
8911
+ var import_node_path42 = require("path");
8903
8912
  var import_prompts4 = require("@inquirer/prompts");
8904
8913
 
8905
8914
  // src/workspace-upgrade/upgrade-workspace.use-case.ts
8906
- var import_node_fs33 = require("fs");
8907
- var import_node_path40 = require("path");
8915
+ var import_node_fs34 = require("fs");
8916
+ var import_node_path41 = require("path");
8908
8917
  function resolveRegistry2(options, persisted) {
8909
8918
  if (options.registry === "azure-artifacts" || options.organization && options.artifactsFeed) {
8910
8919
  const persistedAzure = persisted?.kind === "azure-artifacts" ? persisted : void 0;
@@ -8933,7 +8942,7 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
8933
8942
  return persisted.workspaceName;
8934
8943
  }
8935
8944
  try {
8936
- const existing = (0, import_node_fs33.readdirSync)(workspaceRoot).find(
8945
+ const existing = (0, import_node_fs34.readdirSync)(workspaceRoot).find(
8937
8946
  (file) => file.endsWith(".code-workspace") && file !== "undefined.code-workspace"
8938
8947
  );
8939
8948
  if (existing) {
@@ -8941,11 +8950,11 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
8941
8950
  }
8942
8951
  } catch {
8943
8952
  }
8944
- return (0, import_node_path40.basename)(workspaceRoot);
8953
+ return (0, import_node_path41.basename)(workspaceRoot);
8945
8954
  }
8946
8955
  function detectNpmAuth(workspaceRoot) {
8947
8956
  try {
8948
- 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;
8949
8958
  } catch {
8950
8959
  return void 0;
8951
8960
  }
@@ -8992,7 +9001,7 @@ function resolveOverlayOptions(workspaceRoot, options, persisted) {
8992
9001
  };
8993
9002
  }
8994
9003
  function runUpgrade(workspaceRoot, options) {
8995
- if (!fileExists((0, import_node_path40.join)(workspaceRoot, "nx.json"))) {
9004
+ if (!fileExists((0, import_node_path41.join)(workspaceRoot, "nx.json"))) {
8996
9005
  throw new Error(
8997
9006
  `No nx.json found in ${workspaceRoot} \u2014 this does not look like an Nx workspace. Run 'mnci upgrade' from the workspace root.`
8998
9007
  );
@@ -9066,13 +9075,18 @@ function runUpgrade(workspaceRoot, options) {
9066
9075
  "Formatting the workspace (eslint --fix) \u2014 the slowest step, minutes on a large workspace"
9067
9076
  );
9068
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
+ }
9069
9083
  logger.success("Done. Review the changes with `git diff` before committing.");
9070
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.");
9071
9085
  }
9072
9086
 
9073
9087
  // src/workspace-creation/interactive-wizard.use-case.ts
9074
9088
  async function runInteractive() {
9075
- 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"));
9076
9090
  const choiceNew = { name: "Create a new monorepo", value: "new" };
9077
9091
  const choiceAdd = { name: "Add a project to this workspace", value: "add" };
9078
9092
  const choiceUpgrade = {