@mnci/cli 4.41.0 → 4.43.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.`
@@ -8476,11 +8485,146 @@ function runDoctor(workspaceRoot, options = {}) {
8476
8485
  process.exitCode = 1;
8477
8486
  }
8478
8487
 
8488
+ // src/repository-adoption/inspect-repository.use-case.ts
8489
+ var import_node_fs31 = require("fs");
8490
+ var import_node_path36 = require("path");
8491
+ var LOCKFILES = [
8492
+ ["package-lock.json", "npm"],
8493
+ ["pnpm-lock.yaml", "pnpm"],
8494
+ ["yarn.lock", "yarn"],
8495
+ ["bun.lock", "bun"],
8496
+ ["bun.lockb", "bun"]
8497
+ ];
8498
+ var PERSONAL_FILES = ["CLAUDE.md", "AGENTS.md"];
8499
+ var RETIRED_FILE_PATTERN = /^(?:\.prettierrc.*|\.prettierignore|\.oxfmtrc\.json|oxlint\.config\.ts)$/;
8500
+ var RETIRED_NAMES = ["prettier", "oxfmt", "oxlint", "verdaccio"];
8501
+ function retiredNamesIn(manifest) {
8502
+ const declared = Object.keys({ ...manifest.dependencies, ...manifest.devDependencies });
8503
+ const scripts = Object.values(manifest.scripts ?? {}).join(" ");
8504
+ return RETIRED_NAMES.filter((name) => declared.includes(name) || new RegExp(String.raw`\b${name}\b`).test(scripts));
8505
+ }
8506
+ function inspectRepository(repositoryRoot) {
8507
+ const inside = runCapture("git", ["rev-parse", "--is-inside-work-tree"], repositoryRoot);
8508
+ const isGitRepo = inside.status === 0 && inside.stdout.trim() === "true";
8509
+ const dirty = isGitRepo && runCapture("git", ["status", "--porcelain"], repositoryRoot).stdout.trim() !== "";
8510
+ const tags = isGitRepo ? runCapture("git", ["tag", "--list"], repositoryRoot).stdout.split(/\r?\n/).filter(Boolean) : [];
8511
+ const manifestPath = (0, import_node_path36.join)(repositoryRoot, "package.json");
8512
+ const manifest = fileExists(manifestPath) ? readJson(manifestPath) : {};
8513
+ const nxJsonPath = (0, import_node_path36.join)(repositoryRoot, "nx.json");
8514
+ const nxJson = fileExists(nxJsonPath) ? readJson(nxJsonPath) : {};
8515
+ const ci = [];
8516
+ const pipelines = [];
8517
+ if ((0, import_node_fs31.existsSync)((0, import_node_path36.join)(repositoryRoot, "azure-pipelines.yml"))) {
8518
+ ci.push("azure");
8519
+ pipelines.push((0, import_node_path36.join)(repositoryRoot, "azure-pipelines.yml"));
8520
+ }
8521
+ const workflows = (0, import_node_path36.join)(repositoryRoot, ".github", "workflows");
8522
+ if ((0, import_node_fs31.existsSync)(workflows)) {
8523
+ const files = (0, import_node_fs31.readdirSync)(workflows).filter((file) => /\.ya?ml$/.test(file));
8524
+ if (files.length > 0) {
8525
+ ci.push("github");
8526
+ pipelines.push(...files.map((file) => (0, import_node_path36.join)(workflows, file)));
8527
+ }
8528
+ }
8529
+ const rootFiles = (0, import_node_fs31.readdirSync)(repositoryRoot);
8530
+ return {
8531
+ isGitRepo,
8532
+ dirty,
8533
+ packageManager: LOCKFILES.find(([file]) => rootFiles.includes(file))?.[1],
8534
+ nxVersion: manifest.devDependencies?.nx ?? manifest.dependencies?.nx,
8535
+ alreadyMnci: nxJson.mnci !== void 0,
8536
+ projects: locateProjects(repositoryRoot).map(({ name, dir, ecosystem }) => ({ name, dir, ecosystem })),
8537
+ tagCount: tags.length,
8538
+ strandedTags: isGitRepo ? locateStrandedReleaseTags(repositoryRoot, nxJson.release?.releaseTag?.pattern) : [],
8539
+ ci,
8540
+ pipelineUsesMnci: pipelines.some((file) => /\bmnci ci\b/.test((0, import_node_fs31.readFileSync)(file, "utf8"))),
8541
+ retiredTooling: [.../* @__PURE__ */ new Set([...rootFiles.filter((file) => RETIRED_FILE_PATTERN.test(file) || file === ".verdaccio"), ...retiredNamesIn(manifest)])],
8542
+ personalFiles: PERSONAL_FILES.filter((file) => rootFiles.includes(file))
8543
+ };
8544
+ }
8545
+
8546
+ // src/repository-adoption/judge-adoption.policy.ts
8547
+ function judgeAdoption(facts) {
8548
+ const findings = [];
8549
+ const blocker = (detail, step) => {
8550
+ findings.push({ severity: "blocker", detail, step });
8551
+ };
8552
+ const warning = (detail, step) => {
8553
+ findings.push({ severity: "warning", detail, step });
8554
+ };
8555
+ if (!facts.isGitRepo) {
8556
+ blocker("this directory is not a git repository", "run `git init` (or clone the repository) so every step can be its own commit");
8557
+ } else if (facts.dirty) {
8558
+ blocker("the working tree has uncommitted changes", "commit or stash them, so adoption starts from a state you can return to");
8559
+ }
8560
+ if (facts.packageManager !== void 0 && facts.packageManager !== "npm") {
8561
+ blocker(`the repository uses ${facts.packageManager}; mnci workspaces are npm workspaces`, "convert to npm first (out of scope for adopt for now)");
8562
+ }
8563
+ if (facts.projects.length === 0) {
8564
+ blocker("no project manifest was found under the repository", "run mnci adopt from the repository root, or `mnci new` for an empty one");
8565
+ }
8566
+ if (facts.alreadyMnci) {
8567
+ warning("nx.json already carries an mnci block, so this is an mnci workspace", "use `mnci upgrade`; adopt is for repositories mnci has not touched");
8568
+ }
8569
+ if (facts.nxVersion === void 0) {
8570
+ warning("Nx is not installed", "the toolchain step installs the Nx family at the version mnci supports (#380)");
8571
+ }
8572
+ if (facts.strandedTags.length > 0) {
8573
+ warning(
8574
+ `${facts.strandedTags.length} project(s) have release tags only under an old name (${facts.strandedTags.map((entry) => entry.project).join(", ")})`,
8575
+ "the tags step creates the baseline tags before the first release (#379)"
8576
+ );
8577
+ }
8578
+ if (facts.retiredTooling.length > 0) {
8579
+ warning(`retired tooling is present: ${facts.retiredTooling.join(", ")}`, "the toolchain step removes it (#380)");
8580
+ }
8581
+ if (facts.ci.length > 0 && !facts.pipelineUsesMnci) {
8582
+ warning(`an existing ${facts.ci.join(" and ")} pipeline does not call mnci`, "the pipeline step moves its custom steps into slots (#381)");
8583
+ }
8584
+ if (facts.personalFiles.length > 0) {
8585
+ warning(`${facts.personalFiles.join(", ")} exist and are yours`, "adoption leaves them alone; only a block marked by Nx is ever removed");
8586
+ }
8587
+ const ordered = [...findings.filter((finding) => finding.severity === "blocker"), ...findings.filter((finding) => finding.severity === "warning")];
8588
+ return { ready: ordered.every((finding) => finding.severity !== "blocker"), facts, findings: ordered };
8589
+ }
8590
+
8591
+ // src/repository-adoption/run-adopt.use-case.ts
8592
+ function reportAdoption(repositoryRoot) {
8593
+ return judgeAdoption(inspectRepository(repositoryRoot));
8594
+ }
8595
+ function runAdopt(repositoryRoot, options = {}) {
8596
+ const report = reportAdoption(repositoryRoot);
8597
+ if (!report.ready) {
8598
+ process.exitCode = 1;
8599
+ }
8600
+ if (options.json === true) {
8601
+ printJson(report);
8602
+ return;
8603
+ }
8604
+ const { facts } = report;
8605
+ logger.info(`${facts.projects.length} project(s), ${facts.tagCount} tag(s), ${facts.packageManager ?? "no lockfile"}, nx ${facts.nxVersion ?? "not installed"}`);
8606
+ for (const finding of report.findings) {
8607
+ const line = `${finding.severity === "blocker" ? "BLOCKER" : "warning"}: ${finding.detail}
8608
+ next: ${finding.step}`;
8609
+ if (finding.severity === "blocker") {
8610
+ logger.error(line);
8611
+ } else {
8612
+ logger.warn(line);
8613
+ }
8614
+ }
8615
+ if (report.ready) {
8616
+ logger.success("Nothing blocks adoption. This was a read-only report; nothing was changed.");
8617
+ } else {
8618
+ logger.error("Adoption is blocked. Clear the blockers above and run the report again.");
8619
+ }
8620
+ }
8621
+
8479
8622
  // src/command-catalog/command-groups.config.ts
8480
8623
  var COMMAND_GROUPS = {
8481
8624
  new: "workspace",
8482
8625
  upgrade: "workspace",
8483
8626
  doctor: "workspace",
8627
+ adopt: "inspect",
8484
8628
  add: "projects",
8485
8629
  install: "dependencies",
8486
8630
  sync: "dependencies",
@@ -8546,11 +8690,11 @@ function runCommands(program, options) {
8546
8690
  }
8547
8691
 
8548
8692
  // src/workspace-info/list-projects.use-case.ts
8549
- var import_node_path35 = require("path");
8693
+ var import_node_path37 = require("path");
8550
8694
  var TYPE_TAG_PREFIX = "type:";
8551
8695
  function listProjects(workspaceRoot) {
8552
8696
  return locateProjects(workspaceRoot).map((location) => {
8553
- const projectJsonPath = (0, import_node_path35.join)(workspaceRoot, location.dir, "project.json");
8697
+ const projectJsonPath = (0, import_node_path37.join)(workspaceRoot, location.dir, "project.json");
8554
8698
  const project = fileExists(projectJsonPath) ? readJson(projectJsonPath) : {};
8555
8699
  const typeTag = (project.tags ?? []).find((tag) => tag.startsWith(TYPE_TAG_PREFIX));
8556
8700
  return {
@@ -8563,7 +8707,7 @@ function listProjects(workspaceRoot) {
8563
8707
  }).toSorted((a, b) => a.dir.localeCompare(b.dir));
8564
8708
  }
8565
8709
  function runProjects(workspaceRoot, options) {
8566
- if (!fileExists((0, import_node_path35.join)(workspaceRoot, "nx.json"))) {
8710
+ if (!fileExists((0, import_node_path37.join)(workspaceRoot, "nx.json"))) {
8567
8711
  throw new Error("No nx.json found here. Run `projects` from the workspace root.");
8568
8712
  }
8569
8713
  const projects = listProjects(workspaceRoot);
@@ -8577,11 +8721,11 @@ function runProjects(workspaceRoot, options) {
8577
8721
  }
8578
8722
 
8579
8723
  // src/workspace-info/read-workspace-info.use-case.ts
8580
- var import_node_path37 = require("path");
8724
+ var import_node_path39 = require("path");
8581
8725
 
8582
8726
  // src/cli-version/check-for-update.use-case.ts
8583
- var import_node_fs30 = require("fs");
8584
- var import_node_path36 = require("path");
8727
+ var import_node_fs32 = require("fs");
8728
+ var import_node_path38 = require("path");
8585
8729
  var import_cross_spawn3 = __toESM(require("cross-spawn"));
8586
8730
  var PACKAGE_NAME2 = "@mnci/cli";
8587
8731
  var REGISTRY_TIMEOUT_MS = 2e3;
@@ -8640,8 +8784,8 @@ function releaseTriple(version) {
8640
8784
  }
8641
8785
  function readCliVersion(packageDirectory) {
8642
8786
  try {
8643
- const manifestPath = (0, import_node_path36.join)(packageDirectory, "..", "package.json");
8644
- const manifest = JSON.parse((0, import_node_fs30.readFileSync)(manifestPath, "utf8"));
8787
+ const manifestPath = (0, import_node_path38.join)(packageDirectory, "..", "package.json");
8788
+ const manifest = JSON.parse((0, import_node_fs32.readFileSync)(manifestPath, "utf8"));
8645
8789
  return manifest.version ?? "0.0.0";
8646
8790
  } catch {
8647
8791
  return "0.0.0";
@@ -8651,7 +8795,7 @@ function readCliVersion(packageDirectory) {
8651
8795
  // src/workspace-info/read-workspace-info.use-case.ts
8652
8796
  function readWorkspaceInfo(workspaceRoot, cliVersion) {
8653
8797
  const latest = fetchLatestVersion() ?? null;
8654
- const isWorkspace = fileExists((0, import_node_path37.join)(workspaceRoot, "nx.json"));
8798
+ const isWorkspace = fileExists((0, import_node_path39.join)(workspaceRoot, "nx.json"));
8655
8799
  return {
8656
8800
  cli: {
8657
8801
  version: cliVersion,
@@ -8672,8 +8816,8 @@ function runInfo(workspaceRoot, cliVersion, options) {
8672
8816
  }
8673
8817
 
8674
8818
  // src/workspace-creation/adopt-directory.use-case.ts
8675
- var import_node_fs31 = require("fs");
8676
- var import_node_path38 = require("path");
8819
+ var import_node_fs33 = require("fs");
8820
+ var import_node_path40 = require("path");
8677
8821
  var NEVER_COPIED = /* @__PURE__ */ new Set([".git", "node_modules"]);
8678
8822
  var AGENT_AND_CI_ENTRIES = [
8679
8823
  "AGENTS.md",
@@ -8704,13 +8848,13 @@ var PREFLIGHT_BLOCKERS = [
8704
8848
  "packages"
8705
8849
  ];
8706
8850
  function assertAdoptableDirectory(target) {
8707
- if (!(0, import_node_fs31.existsSync)(target)) {
8851
+ if (!(0, import_node_fs33.existsSync)(target)) {
8708
8852
  throw new Error(`--into '${target}' does not exist. Create or clone it first.`);
8709
8853
  }
8710
- if (!(0, import_node_fs31.statSync)(target).isDirectory()) {
8854
+ if (!(0, import_node_fs33.statSync)(target).isDirectory()) {
8711
8855
  throw new Error(`--into '${target}' is not a directory.`);
8712
8856
  }
8713
- const blockers = PREFLIGHT_BLOCKERS.filter((entry) => (0, import_node_fs31.existsSync)((0, import_node_path38.join)(target, entry)));
8857
+ const blockers = PREFLIGHT_BLOCKERS.filter((entry) => (0, import_node_fs33.existsSync)((0, import_node_path40.join)(target, entry)));
8714
8858
  if (blockers.length > 0) {
8715
8859
  throw new Error(
8716
8860
  `--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 +8862,15 @@ function assertAdoptableDirectory(target) {
8718
8862
  }
8719
8863
  }
8720
8864
  function mergeGitignore(generatedFile, targetFile) {
8721
- const existing = (0, import_node_fs31.readFileSync)(targetFile, "utf8");
8865
+ const existing = (0, import_node_fs33.readFileSync)(targetFile, "utf8");
8722
8866
  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) => {
8867
+ const additions = (0, import_node_fs33.readFileSync)(generatedFile, "utf8").split("\n").map((line) => line.trimEnd()).filter((line) => {
8724
8868
  const trimmed = line.trim();
8725
8869
  return trimmed !== "" && !trimmed.startsWith("#") && !present.has(trimmed);
8726
8870
  });
8727
8871
  if (additions.length === 0) return false;
8728
8872
  const separator = existing.endsWith("\n") ? "" : "\n";
8729
- (0, import_node_fs31.writeFileSync)(
8873
+ (0, import_node_fs33.writeFileSync)(
8730
8874
  targetFile,
8731
8875
  `${existing}${separator}
8732
8876
  # Added by mnci (Nx workspace)
@@ -8736,9 +8880,9 @@ ${additions.join("\n")}
8736
8880
  return true;
8737
8881
  }
8738
8882
  function adoptGeneratedWorkspace(generatedRoot, target) {
8739
- const entries = (0, import_node_fs31.readdirSync)(generatedRoot).filter((entry) => !NEVER_COPIED.has(entry));
8883
+ const entries = (0, import_node_fs33.readdirSync)(generatedRoot).filter((entry) => !NEVER_COPIED.has(entry));
8740
8884
  const collisions = entries.filter(
8741
- (entry) => (0, import_node_fs31.existsSync)((0, import_node_path38.join)(target, entry)) && !EXISTING_WINS.has(entry) && entry !== ".gitignore"
8885
+ (entry) => (0, import_node_fs33.existsSync)((0, import_node_path40.join)(target, entry)) && !EXISTING_WINS.has(entry) && entry !== ".gitignore"
8742
8886
  );
8743
8887
  if (collisions.length > 0) {
8744
8888
  throw new Error(
@@ -8747,28 +8891,28 @@ function adoptGeneratedWorkspace(generatedRoot, target) {
8747
8891
  }
8748
8892
  const result = { copied: [], kept: [], mergedGitignore: false };
8749
8893
  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)) {
8894
+ const from = (0, import_node_path40.join)(generatedRoot, entry);
8895
+ const to = (0, import_node_path40.join)(target, entry);
8896
+ if (entry === ".gitignore" && (0, import_node_fs33.existsSync)(to)) {
8753
8897
  result.mergedGitignore = mergeGitignore(from, to);
8754
8898
  result.kept.push(entry);
8755
8899
  continue;
8756
8900
  }
8757
- if ((0, import_node_fs31.existsSync)(to)) {
8901
+ if ((0, import_node_fs33.existsSync)(to)) {
8758
8902
  result.kept.push(entry);
8759
8903
  continue;
8760
8904
  }
8761
- (0, import_node_fs31.cpSync)(from, to, { recursive: true });
8905
+ (0, import_node_fs33.cpSync)(from, to, { recursive: true });
8762
8906
  result.copied.push(entry);
8763
8907
  }
8764
- (0, import_node_fs31.rmSync)(generatedRoot, { recursive: true, force: true });
8908
+ (0, import_node_fs33.rmSync)(generatedRoot, { recursive: true, force: true });
8765
8909
  return result;
8766
8910
  }
8767
8911
 
8768
8912
  // src/workspace-creation/create-workspace.use-case.ts
8769
- var import_node_fs32 = require("fs");
8913
+ var import_node_fs34 = require("fs");
8770
8914
  var import_node_os2 = require("os");
8771
- var import_node_path39 = require("path");
8915
+ var import_node_path41 = require("path");
8772
8916
  function nxCloudProviderValue(ci) {
8773
8917
  return ci === "azure" ? "azure" : "github";
8774
8918
  }
@@ -8811,9 +8955,9 @@ async function resolveRegistry(options) {
8811
8955
  return await promptRegistry();
8812
8956
  }
8813
8957
  async function runNew(name, options) {
8814
- const adoptTarget = options.into === void 0 ? void 0 : (0, import_node_path39.resolve)(process.cwd(), options.into);
8958
+ const adoptTarget = options.into === void 0 ? void 0 : (0, import_node_path41.resolve)(process.cwd(), options.into);
8815
8959
  if (adoptTarget !== void 0) assertAdoptableDirectory(adoptTarget);
8816
- const workspaceName = name ?? (adoptTarget === void 0 ? await promptText("Workspace name") : (0, import_node_path39.basename)(adoptTarget));
8960
+ const workspaceName = name ?? (adoptTarget === void 0 ? await promptText("Workspace name") : (0, import_node_path41.basename)(adoptTarget));
8817
8961
  assertValidProjectName(workspaceName, "Workspace name");
8818
8962
  const scope = options.scope ?? (options.yes ? `@${workspaceName}` : await promptText("npm scope for publishable packages", `@${workspaceName}`));
8819
8963
  const registry = await resolveRegistry(options);
@@ -8826,8 +8970,8 @@ async function runNew(name, options) {
8826
8970
  const npmAuth = resolveNpmAuth(options.npmAuth, registry, ci);
8827
8971
  const stack = await resolveStack(options);
8828
8972
  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);
8973
+ const stagingParent = adoptTarget === void 0 ? process.cwd() : (0, import_node_fs34.mkdtempSync)((0, import_node_path41.join)((0, import_node_os2.tmpdir)(), "mnci-new-"));
8974
+ let workspaceRoot = (0, import_node_path41.join)(stagingParent, workspaceName);
8831
8975
  try {
8832
8976
  logger.step(`Creating Nx workspace '${workspaceName}' (preset: ts)`);
8833
8977
  runNpx(
@@ -8850,7 +8994,7 @@ async function runNew(name, options) {
8850
8994
  workspaceRoot = adoptTarget;
8851
8995
  }
8852
8996
  } finally {
8853
- if (adoptTarget !== void 0) (0, import_node_fs32.rmSync)(stagingParent, { recursive: true, force: true });
8997
+ if (adoptTarget !== void 0) (0, import_node_fs34.rmSync)(stagingParent, { recursive: true, force: true });
8854
8998
  }
8855
8999
  logger.step(
8856
9000
  "Applying MoNecromanCI overlay (VS Code workspace, release config, .npmrc, commitlint, pipeline, stack)"
@@ -8860,8 +9004,8 @@ async function runNew(name, options) {
8860
9004
  { workspaceName, scope, registry, agent, variableGroup, ci, stack, ...npmAuth && { npmAuth } },
8861
9005
  logger.detail
8862
9006
  );
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 });
9007
+ (0, import_node_fs34.rmSync)((0, import_node_path41.join)(workspaceRoot, "node_modules"), { recursive: true, force: true });
9008
+ (0, import_node_fs34.rmSync)((0, import_node_path41.join)(workspaceRoot, "package-lock.json"), { force: true });
8865
9009
  logger.step("Installing the toolchain (commitlint + husky + linting/formatting)");
8866
9010
  const installStatus = runShell(
8867
9011
  "npm",
@@ -8899,12 +9043,12 @@ function projectKindHintLines(kinds = PROJECT_KINDS) {
8899
9043
  }
8900
9044
 
8901
9045
  // src/workspace-creation/interactive-wizard.use-case.ts
8902
- var import_node_path41 = require("path");
9046
+ var import_node_path43 = require("path");
8903
9047
  var import_prompts4 = require("@inquirer/prompts");
8904
9048
 
8905
9049
  // src/workspace-upgrade/upgrade-workspace.use-case.ts
8906
- var import_node_fs33 = require("fs");
8907
- var import_node_path40 = require("path");
9050
+ var import_node_fs35 = require("fs");
9051
+ var import_node_path42 = require("path");
8908
9052
  function resolveRegistry2(options, persisted) {
8909
9053
  if (options.registry === "azure-artifacts" || options.organization && options.artifactsFeed) {
8910
9054
  const persistedAzure = persisted?.kind === "azure-artifacts" ? persisted : void 0;
@@ -8933,7 +9077,7 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
8933
9077
  return persisted.workspaceName;
8934
9078
  }
8935
9079
  try {
8936
- const existing = (0, import_node_fs33.readdirSync)(workspaceRoot).find(
9080
+ const existing = (0, import_node_fs35.readdirSync)(workspaceRoot).find(
8937
9081
  (file) => file.endsWith(".code-workspace") && file !== "undefined.code-workspace"
8938
9082
  );
8939
9083
  if (existing) {
@@ -8941,11 +9085,11 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
8941
9085
  }
8942
9086
  } catch {
8943
9087
  }
8944
- return (0, import_node_path40.basename)(workspaceRoot);
9088
+ return (0, import_node_path42.basename)(workspaceRoot);
8945
9089
  }
8946
9090
  function detectNpmAuth(workspaceRoot) {
8947
9091
  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;
9092
+ return (0, import_node_fs35.readFileSync)((0, import_node_path42.join)(workspaceRoot, "azure-pipelines.yml"), "utf8").includes("npmAuthenticate@0") ? "build-identity" : void 0;
8949
9093
  } catch {
8950
9094
  return void 0;
8951
9095
  }
@@ -8992,7 +9136,7 @@ function resolveOverlayOptions(workspaceRoot, options, persisted) {
8992
9136
  };
8993
9137
  }
8994
9138
  function runUpgrade(workspaceRoot, options) {
8995
- if (!fileExists((0, import_node_path40.join)(workspaceRoot, "nx.json"))) {
9139
+ if (!fileExists((0, import_node_path42.join)(workspaceRoot, "nx.json"))) {
8996
9140
  throw new Error(
8997
9141
  `No nx.json found in ${workspaceRoot} \u2014 this does not look like an Nx workspace. Run 'mnci upgrade' from the workspace root.`
8998
9142
  );
@@ -9066,13 +9210,18 @@ function runUpgrade(workspaceRoot, options) {
9066
9210
  "Formatting the workspace (eslint --fix) \u2014 the slowest step, minutes on a large workspace"
9067
9211
  );
9068
9212
  runFormatter(workspaceRoot);
9213
+ const nxJson = readJson((0, import_node_path42.join)(workspaceRoot, "nx.json"));
9214
+ const stranded = locateStrandedReleaseTags(workspaceRoot, nxJson.release?.releaseTag?.pattern);
9215
+ for (const entry of stranded) {
9216
+ 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).`);
9217
+ }
9069
9218
  logger.success("Done. Review the changes with `git diff` before committing.");
9070
9219
  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
9220
  }
9072
9221
 
9073
9222
  // src/workspace-creation/interactive-wizard.use-case.ts
9074
9223
  async function runInteractive() {
9075
- const inWorkspace = fileExists((0, import_node_path41.join)(process.cwd(), "nx.json"));
9224
+ const inWorkspace = fileExists((0, import_node_path43.join)(process.cwd(), "nx.json"));
9076
9225
  const choiceNew = { name: "Create a new monorepo", value: "new" };
9077
9226
  const choiceAdd = { name: "Add a project to this workspace", value: "add" };
9078
9227
  const choiceUpgrade = {
@@ -9124,6 +9273,9 @@ function buildProgram(cliVersion) {
9124
9273
  ).option("--json", "print the findings as one JSON document, each with its remedy").action((options) => {
9125
9274
  runDoctor(process.cwd(), options);
9126
9275
  });
9276
+ program.command("adopt").description("Read an existing repository and report what bringing it under mnci would involve: blockers, warnings and the step that clears each. Read-only; exits non-zero when something blocks").option("--json", "print the report as one JSON document").action((options) => {
9277
+ runAdopt(process.cwd(), options);
9278
+ });
9127
9279
  program.command("ci").addArgument(new import_commander.Argument("<phase>", "which phase of the pipeline to run").choices(CI_PHASES)).description(
9128
9280
  "Run a phase of the CI pipeline here, the same way CI runs it: `verify` is the sync check and then every project (or, in a pull request, the affected ones) through lint, typecheck, test and build. `setup` installs the Python, Go and Flutter toolchains the workspace needs, `audit` fails on a known advisory that has a fix, `pack` builds the per-app artifacts, `release` versions, tags and publishes, and `native` builds the apps that need a C toolchain on this OS. Exits non-zero when it fails"
9129
9281
  ).action(async (phase) => {