@mnci/cli 4.31.0 → 4.32.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
@@ -44,6 +44,12 @@ var import_node_path = require("path");
44
44
  // src/nx-workspace/nx.client.ts
45
45
  var import_cross_spawn = __toESM(require("cross-spawn"));
46
46
 
47
+ // src/terminal/json-output.client.ts
48
+ function printJson(value) {
49
+ process.stdout.write(`${JSON.stringify(value, null, 2)}
50
+ `);
51
+ }
52
+
47
53
  // src/terminal/logger.client.ts
48
54
  var logger = {
49
55
  info(message) {
@@ -7055,6 +7061,45 @@ function readWorkspaceStack(workspaceRoot) {
7055
7061
  };
7056
7062
  }
7057
7063
 
7064
+ // src/project-scaffolding/project-kind-catalog.config.ts
7065
+ var PROJECT_KIND_CATALOG = [
7066
+ { kind: "react-app", language: "typescript", label: "React app", description: "A Vite React app, built per environment", flags: [], requiredFlags: [] },
7067
+ { kind: "react-lib", language: "typescript", label: "React library", description: "A publishable React component library", flags: ["scope", "empty"], requiredFlags: [] },
7068
+ { kind: "react-internal-lib", language: "typescript", label: "React internal library", description: "A private React library for apps in this workspace", flags: ["empty"], requiredFlags: [] },
7069
+ { kind: "node-app", language: "typescript", label: "Node app", description: "A Node service, with Express, Fastify, Koa or Nest if you choose one", flags: ["framework"], requiredFlags: [] },
7070
+ { kind: "node-function-app", language: "typescript", label: "Node function app", description: "An Azure Functions (v4) app in TypeScript", flags: ["empty"], requiredFlags: [] },
7071
+ { kind: "npm-lib", language: "typescript", label: "npm library", description: "A library published to npm, bundled with Rollup", flags: ["scope", "empty"], requiredFlags: [] },
7072
+ { kind: "internal-lib", language: "typescript", label: "Internal library", description: "A private TypeScript library for apps in this workspace", flags: ["empty"], requiredFlags: [] },
7073
+ { kind: "vscode-extension", language: "typescript", label: "VS Code extension", description: "A bundled Marketplace extension, optionally shipping a Go sidecar", flags: ["publisher", "sidecar"], requiredFlags: [] },
7074
+ { kind: "python-app", language: "python", label: "Python app", description: "A pip-native Python application (Ruff, pytest)", flags: [], requiredFlags: [] },
7075
+ { kind: "python-function-app", language: "python", label: "Python function app", description: "An Azure Functions (v2 model) app in Python", flags: [], requiredFlags: [] },
7076
+ { kind: "python-lib", language: "python", label: "Python library", description: "A Python package published with twine", flags: [], requiredFlags: [] },
7077
+ { kind: "python-internal-lib", language: "python", label: "Python internal library", description: "A private Python library, vendored into the apps that use it", flags: [], requiredFlags: [] },
7078
+ { kind: "python-vendor", language: "python", label: "Vendor a Python library", description: "Copy an internal Python library into a consumer so it can be packaged", flags: ["lib"], requiredFlags: ["lib"] },
7079
+ { kind: "go-app", language: "go", label: "Go app", description: "A Go executable with one static binary per platform", flags: ["release", "cgo", "web"], requiredFlags: [] },
7080
+ { kind: "go-function-app", language: "go", label: "Go function app", description: "A Go Azure Functions custom handler", flags: [], requiredFlags: [] },
7081
+ { kind: "go-lib", language: "go", label: "Go library", description: "A Go module other repositories can go get", flags: [], requiredFlags: [] },
7082
+ { kind: "go-internal-lib", language: "go", label: "Go internal library", description: "A private Go module for apps in this workspace", flags: [], requiredFlags: [] },
7083
+ { kind: "flutter-app", language: "flutter", label: "Flutter app", description: "A Flutter app built for the web", flags: [], requiredFlags: [] },
7084
+ { kind: "flutter-lib", language: "flutter", label: "Flutter library", description: "A Dart package released by git tag", flags: [], requiredFlags: [] },
7085
+ { kind: "flutter-internal-lib", language: "flutter", label: "Flutter internal library", description: "A private Dart package for apps in this workspace", flags: [], requiredFlags: [] },
7086
+ { kind: "csharp-app", language: "csharp", label: "C# app", description: "A .NET console, web API or worker app", flags: [], requiredFlags: [] },
7087
+ { kind: "csharp-function-app", language: "csharp", label: "C# function app", description: "An Azure Functions isolated-worker app", flags: [], requiredFlags: [] },
7088
+ { kind: "csharp-lib", language: "csharp", label: "C# library", description: "A NuGet package", flags: ["scope"], requiredFlags: [] },
7089
+ { kind: "csharp-internal-lib", language: "csharp", label: "C# internal library", description: "A private .NET library for apps in this workspace", flags: [], requiredFlags: [] }
7090
+ ];
7091
+
7092
+ // src/project-scaffolding/list-project-kinds.use-case.ts
7093
+ function runKinds(options) {
7094
+ if (options.json === true) {
7095
+ printJson(PROJECT_KIND_CATALOG);
7096
+ return;
7097
+ }
7098
+ for (const entry of PROJECT_KIND_CATALOG) {
7099
+ logger.info(`${entry.kind.padEnd(22)} ${entry.description}`);
7100
+ }
7101
+ }
7102
+
7058
7103
  // src/workspace-diagnostics/check-invariants.use-case.ts
7059
7104
  var import_node_child_process = require("child_process");
7060
7105
  var import_node_fs19 = require("fs");
@@ -7334,10 +7379,12 @@ function checkOneTargetsFiles(workspaceRoot, source, targetName, target) {
7334
7379
  });
7335
7380
  }
7336
7381
  function checkSync(workspaceRoot) {
7382
+ const result = runCapture("npx", ["nx", "sync:check"], workspaceRoot);
7337
7383
  return {
7338
7384
  check: "TypeScript project references synced",
7339
- ok: runShell("npx", ["nx", "sync:check"], workspaceRoot) === 0,
7340
- detail: "nx sync:check failed \u2014 a stale project reference was never committed",
7385
+ ok: result.status === 0,
7386
+ detail: `nx sync:check failed \u2014 a stale project reference was never committed${result.stdout.trim() ? `
7387
+ ${result.stdout.trim()}` : ""}`,
7341
7388
  remedy: "run `npx nx sync` and commit the result"
7342
7389
  };
7343
7390
  }
@@ -7502,9 +7549,16 @@ function collectFindings(workspaceRoot) {
7502
7549
  ...checkNativeApps(workspaceRoot)
7503
7550
  ].filter((finding) => finding !== void 0);
7504
7551
  }
7505
- function runDoctor(workspaceRoot) {
7552
+ function runDoctor(workspaceRoot, options = {}) {
7506
7553
  const findings = collectFindings(workspaceRoot);
7507
7554
  const failed = findings.filter((finding) => !finding.ok);
7555
+ if (options.json === true) {
7556
+ printJson({ findings, passed: findings.length - failed.length, failed: failed.length });
7557
+ if (failed.length > 0) {
7558
+ process.exitCode = 1;
7559
+ }
7560
+ return;
7561
+ }
7508
7562
  for (const finding of findings) {
7509
7563
  if (finding.ok) {
7510
7564
  logger.success(finding.check);
@@ -7523,9 +7577,204 @@ function runDoctor(workspaceRoot) {
7523
7577
  process.exitCode = 1;
7524
7578
  }
7525
7579
 
7526
- // src/workspace-creation/adopt-directory.use-case.ts
7527
- var import_node_fs20 = require("fs");
7580
+ // src/command-catalog/command-groups.config.ts
7581
+ var COMMAND_GROUPS = {
7582
+ new: "workspace",
7583
+ upgrade: "workspace",
7584
+ doctor: "workspace",
7585
+ add: "projects",
7586
+ install: "dependencies",
7587
+ sync: "dependencies",
7588
+ up: "dependencies",
7589
+ ci: "pipeline",
7590
+ commands: "inspect",
7591
+ kinds: "inspect",
7592
+ projects: "inspect",
7593
+ info: "inspect"
7594
+ };
7595
+
7596
+ // src/command-catalog/describe-commands.algorithm.ts
7597
+ function describeOption(option) {
7598
+ const isScalar = ["string", "number", "boolean"].includes(typeof option.defaultValue);
7599
+ return {
7600
+ name: option.name(),
7601
+ flags: option.flags,
7602
+ short: option.short,
7603
+ description: option.description,
7604
+ takesValue: option.required ? "required" : option.optional ? "optional" : "none",
7605
+ variadic: option.variadic,
7606
+ // A repeatable flag is declared with a list as its default, which its collector appends to.
7607
+ repeatable: Array.isArray(option.defaultValue),
7608
+ negated: option.negate,
7609
+ choices: option.argChoices,
7610
+ defaultValue: isScalar ? option.defaultValue : void 0
7611
+ };
7612
+ }
7613
+ function describeCommands(program) {
7614
+ return program.commands.filter((command) => command.name() !== "help").map((command) => {
7615
+ const group = COMMAND_GROUPS[command.name()];
7616
+ if (group === void 0) {
7617
+ throw new Error(`Command '${command.name()}' has no group in COMMAND_GROUPS (command-catalog/command-groups.config.ts).`);
7618
+ }
7619
+ const args = command.registeredArguments.map((argument) => ({
7620
+ name: argument.name(),
7621
+ description: argument.description,
7622
+ required: argument.required,
7623
+ variadic: argument.variadic,
7624
+ choices: argument.argChoices
7625
+ }));
7626
+ return {
7627
+ name: command.name(),
7628
+ aliases: command.aliases(),
7629
+ group,
7630
+ description: command.description(),
7631
+ arguments: args,
7632
+ options: command.options.map((option) => describeOption(option))
7633
+ };
7634
+ });
7635
+ }
7636
+
7637
+ // src/command-catalog/list-commands.use-case.ts
7638
+ function runCommands(program, options) {
7639
+ const commands = describeCommands(program);
7640
+ if (options.json === true) {
7641
+ printJson(commands);
7642
+ return;
7643
+ }
7644
+ for (const command of commands) {
7645
+ logger.info(`${command.name.padEnd(10)} ${command.description}`);
7646
+ }
7647
+ }
7648
+
7649
+ // src/workspace-info/list-projects.use-case.ts
7528
7650
  var import_node_path27 = require("path");
7651
+ var TYPE_TAG_PREFIX = "type:";
7652
+ function listProjects(workspaceRoot) {
7653
+ return locateProjects(workspaceRoot).map((location) => {
7654
+ const projectJsonPath = (0, import_node_path27.join)(workspaceRoot, location.dir, "project.json");
7655
+ const project = fileExists(projectJsonPath) ? readJson(projectJsonPath) : {};
7656
+ const typeTag = (project.tags ?? []).find((tag) => tag.startsWith(TYPE_TAG_PREFIX));
7657
+ return {
7658
+ name: location.name,
7659
+ dir: location.dir,
7660
+ ecosystem: location.ecosystem,
7661
+ kind: typeTag?.slice(TYPE_TAG_PREFIX.length),
7662
+ targets: Object.keys(project.targets ?? {})
7663
+ };
7664
+ }).toSorted((a, b) => a.dir.localeCompare(b.dir));
7665
+ }
7666
+ function runProjects(workspaceRoot, options) {
7667
+ if (!fileExists((0, import_node_path27.join)(workspaceRoot, "nx.json"))) {
7668
+ throw new Error("No nx.json found here. Run `projects` from the workspace root.");
7669
+ }
7670
+ const projects = listProjects(workspaceRoot);
7671
+ if (options.json === true) {
7672
+ printJson(projects);
7673
+ return;
7674
+ }
7675
+ for (const project of projects) {
7676
+ logger.info(`${project.dir.padEnd(32)} ${project.ecosystem}${project.kind ? ` ${project.kind}` : ""}`);
7677
+ }
7678
+ }
7679
+
7680
+ // src/workspace-info/read-workspace-info.use-case.ts
7681
+ var import_node_path29 = require("path");
7682
+
7683
+ // src/cli-version/check-for-update.use-case.ts
7684
+ var import_node_fs20 = require("fs");
7685
+ var import_node_path28 = require("path");
7686
+ var import_cross_spawn2 = __toESM(require("cross-spawn"));
7687
+ var PACKAGE_NAME = "@mnci/cli";
7688
+ var REGISTRY_TIMEOUT_MS = 2e3;
7689
+ function checkForUpdate(currentVersion2) {
7690
+ try {
7691
+ if (!process.stdout.isTTY) {
7692
+ return;
7693
+ }
7694
+ setImmediate(() => {
7695
+ try {
7696
+ reportIfOutdated(currentVersion2);
7697
+ } catch {
7698
+ }
7699
+ });
7700
+ } catch {
7701
+ }
7702
+ }
7703
+ function fetchLatestVersion() {
7704
+ const result = import_cross_spawn2.default.sync("npm", ["view", PACKAGE_NAME, "version"], {
7705
+ encoding: "utf8",
7706
+ timeout: REGISTRY_TIMEOUT_MS,
7707
+ stdio: ["ignore", "pipe", "ignore"]
7708
+ });
7709
+ if (result.error || result.status !== 0) {
7710
+ return void 0;
7711
+ }
7712
+ return (result.stdout ?? "").trim() || void 0;
7713
+ }
7714
+ function reportIfOutdated(currentVersion2) {
7715
+ const latestVersion = fetchLatestVersion();
7716
+ if (!latestVersion || !isNewerVersion(latestVersion, currentVersion2)) {
7717
+ return;
7718
+ }
7719
+ logger.info("");
7720
+ logger.info(`A new version of ${PACKAGE_NAME} is available: ${latestVersion}`);
7721
+ logger.info(` Current version: ${currentVersion2}`);
7722
+ logger.info(` Update with: npm install -g ${PACKAGE_NAME}@latest`);
7723
+ logger.info("");
7724
+ }
7725
+ function isNewerVersion(candidate, current) {
7726
+ const candidateParts = releaseTriple(candidate);
7727
+ const currentParts = releaseTriple(current);
7728
+ if (!candidateParts || !currentParts) {
7729
+ return false;
7730
+ }
7731
+ for (const [index, candidatePart] of candidateParts.entries()) {
7732
+ if (candidatePart !== currentParts[index]) {
7733
+ return candidatePart > currentParts[index];
7734
+ }
7735
+ }
7736
+ return false;
7737
+ }
7738
+ function releaseTriple(version) {
7739
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
7740
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : void 0;
7741
+ }
7742
+ function readCliVersion(packageDirectory) {
7743
+ try {
7744
+ const manifestPath = (0, import_node_path28.join)(packageDirectory, "..", "package.json");
7745
+ const manifest = JSON.parse((0, import_node_fs20.readFileSync)(manifestPath, "utf8"));
7746
+ return manifest.version ?? "0.0.0";
7747
+ } catch {
7748
+ return "0.0.0";
7749
+ }
7750
+ }
7751
+
7752
+ // src/workspace-info/read-workspace-info.use-case.ts
7753
+ function readWorkspaceInfo(workspaceRoot, cliVersion) {
7754
+ const latest = fetchLatestVersion() ?? null;
7755
+ const isWorkspace = fileExists((0, import_node_path29.join)(workspaceRoot, "nx.json"));
7756
+ return {
7757
+ cli: {
7758
+ version: cliVersion,
7759
+ latest,
7760
+ updateAvailable: latest !== null && isNewerVersion(latest, cliVersion)
7761
+ },
7762
+ workspace: isWorkspace ? { root: workspaceRoot, config: { ...readMnciConfig(workspaceRoot) } } : null
7763
+ };
7764
+ }
7765
+ function runInfo(workspaceRoot, cliVersion, options) {
7766
+ const info = readWorkspaceInfo(workspaceRoot, cliVersion);
7767
+ if (options.json === true) {
7768
+ printJson(info);
7769
+ return;
7770
+ }
7771
+ logger.info(`mnci ${info.cli.version}${info.cli.updateAvailable ? ` (${info.cli.latest} is available)` : ""}`);
7772
+ logger.info(info.workspace ? `workspace: ${info.workspace.root}` : "not inside a workspace");
7773
+ }
7774
+
7775
+ // src/workspace-creation/adopt-directory.use-case.ts
7776
+ var import_node_fs21 = require("fs");
7777
+ var import_node_path30 = require("path");
7529
7778
  var NEVER_COPIED = /* @__PURE__ */ new Set([".git", "node_modules"]);
7530
7779
  var AGENT_AND_CI_ENTRIES = [
7531
7780
  "AGENTS.md",
@@ -7556,13 +7805,13 @@ var PREFLIGHT_BLOCKERS = [
7556
7805
  "packages"
7557
7806
  ];
7558
7807
  function assertAdoptableDirectory(target) {
7559
- if (!(0, import_node_fs20.existsSync)(target)) {
7808
+ if (!(0, import_node_fs21.existsSync)(target)) {
7560
7809
  throw new Error(`--into '${target}' does not exist. Create or clone it first.`);
7561
7810
  }
7562
- if (!(0, import_node_fs20.statSync)(target).isDirectory()) {
7811
+ if (!(0, import_node_fs21.statSync)(target).isDirectory()) {
7563
7812
  throw new Error(`--into '${target}' is not a directory.`);
7564
7813
  }
7565
- const blockers = PREFLIGHT_BLOCKERS.filter((entry) => (0, import_node_fs20.existsSync)((0, import_node_path27.join)(target, entry)));
7814
+ const blockers = PREFLIGHT_BLOCKERS.filter((entry) => (0, import_node_fs21.existsSync)((0, import_node_path30.join)(target, entry)));
7566
7815
  if (blockers.length > 0) {
7567
7816
  throw new Error(
7568
7817
  `--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.`
@@ -7570,15 +7819,15 @@ function assertAdoptableDirectory(target) {
7570
7819
  }
7571
7820
  }
7572
7821
  function mergeGitignore(generatedFile, targetFile) {
7573
- const existing = (0, import_node_fs20.readFileSync)(targetFile, "utf8");
7822
+ const existing = (0, import_node_fs21.readFileSync)(targetFile, "utf8");
7574
7823
  const present = new Set(existing.split("\n").map((line) => line.trim()));
7575
- const additions = (0, import_node_fs20.readFileSync)(generatedFile, "utf8").split("\n").map((line) => line.trimEnd()).filter((line) => {
7824
+ const additions = (0, import_node_fs21.readFileSync)(generatedFile, "utf8").split("\n").map((line) => line.trimEnd()).filter((line) => {
7576
7825
  const trimmed = line.trim();
7577
7826
  return trimmed !== "" && !trimmed.startsWith("#") && !present.has(trimmed);
7578
7827
  });
7579
7828
  if (additions.length === 0) return false;
7580
7829
  const separator = existing.endsWith("\n") ? "" : "\n";
7581
- (0, import_node_fs20.writeFileSync)(
7830
+ (0, import_node_fs21.writeFileSync)(
7582
7831
  targetFile,
7583
7832
  `${existing}${separator}
7584
7833
  # Added by mnci (Nx workspace)
@@ -7588,9 +7837,9 @@ ${additions.join("\n")}
7588
7837
  return true;
7589
7838
  }
7590
7839
  function adoptGeneratedWorkspace(generatedRoot, target) {
7591
- const entries = (0, import_node_fs20.readdirSync)(generatedRoot).filter((entry) => !NEVER_COPIED.has(entry));
7840
+ const entries = (0, import_node_fs21.readdirSync)(generatedRoot).filter((entry) => !NEVER_COPIED.has(entry));
7592
7841
  const collisions = entries.filter(
7593
- (entry) => (0, import_node_fs20.existsSync)((0, import_node_path27.join)(target, entry)) && !EXISTING_WINS.has(entry) && entry !== ".gitignore"
7842
+ (entry) => (0, import_node_fs21.existsSync)((0, import_node_path30.join)(target, entry)) && !EXISTING_WINS.has(entry) && entry !== ".gitignore"
7594
7843
  );
7595
7844
  if (collisions.length > 0) {
7596
7845
  throw new Error(
@@ -7599,28 +7848,28 @@ function adoptGeneratedWorkspace(generatedRoot, target) {
7599
7848
  }
7600
7849
  const result = { copied: [], kept: [], mergedGitignore: false };
7601
7850
  for (const entry of entries) {
7602
- const from = (0, import_node_path27.join)(generatedRoot, entry);
7603
- const to = (0, import_node_path27.join)(target, entry);
7604
- if (entry === ".gitignore" && (0, import_node_fs20.existsSync)(to)) {
7851
+ const from = (0, import_node_path30.join)(generatedRoot, entry);
7852
+ const to = (0, import_node_path30.join)(target, entry);
7853
+ if (entry === ".gitignore" && (0, import_node_fs21.existsSync)(to)) {
7605
7854
  result.mergedGitignore = mergeGitignore(from, to);
7606
7855
  result.kept.push(entry);
7607
7856
  continue;
7608
7857
  }
7609
- if ((0, import_node_fs20.existsSync)(to)) {
7858
+ if ((0, import_node_fs21.existsSync)(to)) {
7610
7859
  result.kept.push(entry);
7611
7860
  continue;
7612
7861
  }
7613
- (0, import_node_fs20.cpSync)(from, to, { recursive: true });
7862
+ (0, import_node_fs21.cpSync)(from, to, { recursive: true });
7614
7863
  result.copied.push(entry);
7615
7864
  }
7616
- (0, import_node_fs20.rmSync)(generatedRoot, { recursive: true, force: true });
7865
+ (0, import_node_fs21.rmSync)(generatedRoot, { recursive: true, force: true });
7617
7866
  return result;
7618
7867
  }
7619
7868
 
7620
7869
  // src/workspace-creation/create-workspace.use-case.ts
7621
- var import_node_fs21 = require("fs");
7870
+ var import_node_fs22 = require("fs");
7622
7871
  var import_node_os = require("os");
7623
- var import_node_path28 = require("path");
7872
+ var import_node_path31 = require("path");
7624
7873
  function nxCloudProviderValue(ci) {
7625
7874
  return ci === "azure" ? "azure" : "github";
7626
7875
  }
@@ -7663,9 +7912,9 @@ async function resolveRegistry(options) {
7663
7912
  return await promptRegistry();
7664
7913
  }
7665
7914
  async function runNew(name, options) {
7666
- const adoptTarget = options.into === void 0 ? void 0 : (0, import_node_path28.resolve)(process.cwd(), options.into);
7915
+ const adoptTarget = options.into === void 0 ? void 0 : (0, import_node_path31.resolve)(process.cwd(), options.into);
7667
7916
  if (adoptTarget !== void 0) assertAdoptableDirectory(adoptTarget);
7668
- const workspaceName = name ?? (adoptTarget === void 0 ? await promptText("Workspace name") : (0, import_node_path28.basename)(adoptTarget));
7917
+ const workspaceName = name ?? (adoptTarget === void 0 ? await promptText("Workspace name") : (0, import_node_path31.basename)(adoptTarget));
7669
7918
  assertValidProjectName(workspaceName, "Workspace name");
7670
7919
  const scope = options.scope ?? (options.yes ? `@${workspaceName}` : await promptText("npm scope for publishable packages", `@${workspaceName}`));
7671
7920
  const registry = await resolveRegistry(options);
@@ -7678,8 +7927,8 @@ async function runNew(name, options) {
7678
7927
  const npmAuth = resolveNpmAuth(options.npmAuth, registry, ci);
7679
7928
  const stack = await resolveStack(options);
7680
7929
  const nxCloud = options.nxCloud ?? (options.yes ? false : await promptNxCloud());
7681
- const stagingParent = adoptTarget === void 0 ? process.cwd() : (0, import_node_fs21.mkdtempSync)((0, import_node_path28.join)((0, import_node_os.tmpdir)(), "mnci-new-"));
7682
- let workspaceRoot = (0, import_node_path28.join)(stagingParent, workspaceName);
7930
+ const stagingParent = adoptTarget === void 0 ? process.cwd() : (0, import_node_fs22.mkdtempSync)((0, import_node_path31.join)((0, import_node_os.tmpdir)(), "mnci-new-"));
7931
+ let workspaceRoot = (0, import_node_path31.join)(stagingParent, workspaceName);
7683
7932
  try {
7684
7933
  logger.step(`Creating Nx workspace '${workspaceName}' (preset: ts)`);
7685
7934
  runNpx(
@@ -7702,7 +7951,7 @@ async function runNew(name, options) {
7702
7951
  workspaceRoot = adoptTarget;
7703
7952
  }
7704
7953
  } finally {
7705
- if (adoptTarget !== void 0) (0, import_node_fs21.rmSync)(stagingParent, { recursive: true, force: true });
7954
+ if (adoptTarget !== void 0) (0, import_node_fs22.rmSync)(stagingParent, { recursive: true, force: true });
7706
7955
  }
7707
7956
  logger.step(
7708
7957
  "Applying MoNecromanCI overlay (VS Code workspace, release config, .npmrc, commitlint, pipeline, stack)"
@@ -7712,8 +7961,8 @@ async function runNew(name, options) {
7712
7961
  { workspaceName, scope, registry, agent, variableGroup, ci, stack, ...npmAuth && { npmAuth } },
7713
7962
  logger.detail
7714
7963
  );
7715
- (0, import_node_fs21.rmSync)((0, import_node_path28.join)(workspaceRoot, "node_modules"), { recursive: true, force: true });
7716
- (0, import_node_fs21.rmSync)((0, import_node_path28.join)(workspaceRoot, "package-lock.json"), { force: true });
7964
+ (0, import_node_fs22.rmSync)((0, import_node_path31.join)(workspaceRoot, "node_modules"), { recursive: true, force: true });
7965
+ (0, import_node_fs22.rmSync)((0, import_node_path31.join)(workspaceRoot, "package-lock.json"), { force: true });
7717
7966
  logger.step("Installing the toolchain (commitlint + husky + linting/formatting)");
7718
7967
  const installStatus = runShell(
7719
7968
  "npm",
@@ -7751,12 +8000,12 @@ function projectKindHintLines(kinds = PROJECT_KINDS) {
7751
8000
  }
7752
8001
 
7753
8002
  // src/workspace-creation/interactive-wizard.use-case.ts
7754
- var import_node_path30 = require("path");
8003
+ var import_node_path33 = require("path");
7755
8004
  var import_prompts4 = require("@inquirer/prompts");
7756
8005
 
7757
8006
  // src/workspace-upgrade/upgrade-workspace.use-case.ts
7758
- var import_node_fs22 = require("fs");
7759
- var import_node_path29 = require("path");
8007
+ var import_node_fs23 = require("fs");
8008
+ var import_node_path32 = require("path");
7760
8009
  function resolveRegistry2(options, persisted) {
7761
8010
  if (options.registry === "azure-artifacts" || options.organization && options.artifactsFeed) {
7762
8011
  const persistedAzure = persisted?.kind === "azure-artifacts" ? persisted : void 0;
@@ -7785,7 +8034,7 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
7785
8034
  return persisted.workspaceName;
7786
8035
  }
7787
8036
  try {
7788
- const existing = (0, import_node_fs22.readdirSync)(workspaceRoot).find(
8037
+ const existing = (0, import_node_fs23.readdirSync)(workspaceRoot).find(
7789
8038
  (file) => file.endsWith(".code-workspace") && file !== "undefined.code-workspace"
7790
8039
  );
7791
8040
  if (existing) {
@@ -7793,11 +8042,11 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
7793
8042
  }
7794
8043
  } catch {
7795
8044
  }
7796
- return (0, import_node_path29.basename)(workspaceRoot);
8045
+ return (0, import_node_path32.basename)(workspaceRoot);
7797
8046
  }
7798
8047
  function detectNpmAuth(workspaceRoot) {
7799
8048
  try {
7800
- return (0, import_node_fs22.readFileSync)((0, import_node_path29.join)(workspaceRoot, "azure-pipelines.yml"), "utf8").includes("npmAuthenticate@0") ? "build-identity" : void 0;
8049
+ return (0, import_node_fs23.readFileSync)((0, import_node_path32.join)(workspaceRoot, "azure-pipelines.yml"), "utf8").includes("npmAuthenticate@0") ? "build-identity" : void 0;
7801
8050
  } catch {
7802
8051
  return void 0;
7803
8052
  }
@@ -7844,7 +8093,7 @@ function resolveOverlayOptions(workspaceRoot, options, persisted) {
7844
8093
  };
7845
8094
  }
7846
8095
  function runUpgrade(workspaceRoot, options) {
7847
- if (!fileExists((0, import_node_path29.join)(workspaceRoot, "nx.json"))) {
8096
+ if (!fileExists((0, import_node_path32.join)(workspaceRoot, "nx.json"))) {
7848
8097
  throw new Error(
7849
8098
  `No nx.json found in ${workspaceRoot} \u2014 this does not look like an Nx workspace. Run 'mnci upgrade' from the workspace root.`
7850
8099
  );
@@ -7909,7 +8158,7 @@ function runUpgrade(workspaceRoot, options) {
7909
8158
 
7910
8159
  // src/workspace-creation/interactive-wizard.use-case.ts
7911
8160
  async function runInteractive() {
7912
- const inWorkspace = fileExists((0, import_node_path30.join)(process.cwd(), "nx.json"));
8161
+ const inWorkspace = fileExists((0, import_node_path33.join)(process.cwd(), "nx.json"));
7913
8162
  const choiceNew = { name: "Create a new monorepo", value: "new" };
7914
8163
  const choiceAdd = { name: "Add a project to this workspace", value: "add" };
7915
8164
  const choiceUpgrade = {
@@ -7929,72 +8178,6 @@ async function runInteractive() {
7929
8178
  }
7930
8179
  }
7931
8180
 
7932
- // src/cli-version/check-for-update.use-case.ts
7933
- var import_node_fs23 = require("fs");
7934
- var import_node_path31 = require("path");
7935
- var import_cross_spawn2 = __toESM(require("cross-spawn"));
7936
- var PACKAGE_NAME = "@mnci/cli";
7937
- var REGISTRY_TIMEOUT_MS = 2e3;
7938
- function checkForUpdate(currentVersion2) {
7939
- try {
7940
- if (!process.stdout.isTTY) {
7941
- return;
7942
- }
7943
- setImmediate(() => {
7944
- try {
7945
- reportIfOutdated(currentVersion2);
7946
- } catch {
7947
- }
7948
- });
7949
- } catch {
7950
- }
7951
- }
7952
- function reportIfOutdated(currentVersion2) {
7953
- const result = import_cross_spawn2.default.sync("npm", ["view", PACKAGE_NAME, "version"], {
7954
- encoding: "utf8",
7955
- timeout: REGISTRY_TIMEOUT_MS,
7956
- stdio: ["ignore", "pipe", "ignore"]
7957
- });
7958
- if (result.error || result.status !== 0) {
7959
- return;
7960
- }
7961
- const latestVersion = (result.stdout ?? "").trim();
7962
- if (!latestVersion || !isNewerVersion(latestVersion, currentVersion2)) {
7963
- return;
7964
- }
7965
- logger.info("");
7966
- logger.info(`A new version of ${PACKAGE_NAME} is available: ${latestVersion}`);
7967
- logger.info(` Current version: ${currentVersion2}`);
7968
- logger.info(` Update with: npm install -g ${PACKAGE_NAME}@latest`);
7969
- logger.info("");
7970
- }
7971
- function isNewerVersion(candidate, current) {
7972
- const candidateParts = releaseTriple(candidate);
7973
- const currentParts = releaseTriple(current);
7974
- if (!candidateParts || !currentParts) {
7975
- return false;
7976
- }
7977
- for (const [index, candidatePart] of candidateParts.entries()) {
7978
- if (candidatePart !== currentParts[index]) {
7979
- return candidatePart > currentParts[index];
7980
- }
7981
- }
7982
- return false;
7983
- }
7984
- function releaseTriple(version) {
7985
- const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
7986
- return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : void 0;
7987
- }
7988
- function readCliVersion(packageDirectory) {
7989
- try {
7990
- const manifestPath = (0, import_node_path31.join)(packageDirectory, "..", "package.json");
7991
- const manifest = JSON.parse((0, import_node_fs23.readFileSync)(manifestPath, "utf8"));
7992
- return manifest.version ?? "0.0.0";
7993
- } catch {
7994
- return "0.0.0";
7995
- }
7996
- }
7997
-
7998
8181
  // src/main.ts
7999
8182
  function buildProgram(cliVersion) {
8000
8183
  const program = new import_commander.Command();
@@ -8024,8 +8207,8 @@ function buildProgram(cliVersion) {
8024
8207
  });
8025
8208
  program.command("doctor").description(
8026
8209
  "Check this workspace against the invariants mnci maintains (one ESLint config, no stray .prettierrc, the eslint plugin registered, the resolved eslint major, .npmrc vs the recorded registry, versionActions overrides, nx sync) \u2014 read-only; exits non-zero if anything failed"
8027
- ).action(() => {
8028
- runDoctor(process.cwd());
8210
+ ).option("--json", "print the findings as one JSON document, each with its remedy").action((options) => {
8211
+ runDoctor(process.cwd(), options);
8029
8212
  });
8030
8213
  program.command("ci").addArgument(new import_commander.Argument("<phase>", "which phase of the pipeline to run").choices(CI_PHASES)).description(
8031
8214
  "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. Exits non-zero when it fails"
@@ -8044,12 +8227,16 @@ function buildProgram(cliVersion) {
8044
8227
  });
8045
8228
  program.command("install").alias("i").argument("[packages...]", "packages to add (a bare name or a pinned spec); omit to install only").description(
8046
8229
  "Add a dependency to a specific project using its own toolchain (npm/pip/pub/nuget/go), or \u2014 with no package \u2014 install everything. Each project owns its dependencies, so a package needs a target: 'mnci i -w <project> <package>'"
8047
- ).option("-w, --workspace <project...>", "target project, by directory (apps/foo) or basename (foo); repeatable").option("-D, --save-dev", "add as a development dependency, where the ecosystem distinguishes one").action((packages, options) => {
8230
+ ).option(
8231
+ "-w, --workspace <project>",
8232
+ "target project, by directory (apps/foo) or basename (foo); give the flag again for each project",
8233
+ (project, projects) => [...projects, project],
8234
+ []
8235
+ ).option("-D, --save-dev", "add as a development dependency, where the ecosystem distinguishes one").action((packages, options) => {
8048
8236
  runInstall(process.cwd(), packages, options);
8049
8237
  });
8050
- program.command("add").addArgument(new import_commander.Argument("[kind]", "project kind").choices(PROJECT_KINDS)).argument("[name]", "project name").description("Add a project by delegating to the matching Nx plugin generator").option("--scope <scope>", "npm scope for a publishable lib (defaults to @<workspace name>)").option(
8051
- "--framework <framework>",
8052
- "node-app only: express | fastify | koa | nest | none (default: none)"
8238
+ program.command("add").addArgument(new import_commander.Argument("[kind]", "project kind").choices(PROJECT_KINDS)).argument("[name]", "project name").description("Add a project by delegating to the matching Nx plugin generator").option("--scope <scope>", "npm scope for a publishable lib (defaults to @<workspace name>)").addOption(
8239
+ new import_commander.Option("--framework <framework>", "node-app only: the web framework (default: none)").choices(["express", "fastify", "koa", "nest", "none"])
8053
8240
  ).option(
8054
8241
  "--empty",
8055
8242
  "npm-lib, internal-lib, react-lib, react-internal-lib, node-function-app: scaffold the slice skeleton only, with no sample code or spec"
@@ -8076,6 +8263,18 @@ function buildProgram(cliVersion) {
8076
8263
  await runAdd(kind, name, options);
8077
8264
  }
8078
8265
  );
8266
+ program.command("commands").description("List every command with what it does and how to call it (--json is what an editor builds its menu from)").option("--json", "print the catalog as one JSON document").action((options) => {
8267
+ runCommands(program, options);
8268
+ });
8269
+ program.command("kinds").description("List every project kind that `add` accepts, with its language, a description and the flags that apply").option("--json", "print the kinds as one JSON document").action((options) => {
8270
+ runKinds(options);
8271
+ });
8272
+ program.command("projects").description("List this workspace's projects: directory, ecosystem, the kind mnci recorded and its explicit targets").option("--json", "print the projects as one JSON document").action((options) => {
8273
+ runProjects(process.cwd(), options);
8274
+ });
8275
+ program.command("info").description("Show the installed mnci version, the newest published one, and this workspace's recorded settings").option("--json", "print the report as one JSON document").action((options) => {
8276
+ runInfo(process.cwd(), cliVersion, options);
8277
+ });
8079
8278
  program.action(async () => {
8080
8279
  await runInteractive();
8081
8280
  });