@mnci/cli 4.30.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) {
@@ -2563,6 +2569,19 @@ function csharpConsoleProgram(identity) {
2563
2569
  Console.WriteLine(GreetUseCase.Greet("world").Message);
2564
2570
  `;
2565
2571
  }
2572
+ function csharpExampleTest(identity) {
2573
+ return `using ${identity};
2574
+
2575
+ public class GreetUseCaseTests
2576
+ {
2577
+ [Fact]
2578
+ public void GreetsAName()
2579
+ {
2580
+ Assert.Equal("Hello, world!", GreetUseCase.Greet("world").Message);
2581
+ }
2582
+ }
2583
+ `;
2584
+ }
2566
2585
 
2567
2586
  // src/project-scaffolding/post-generation.use-case.ts
2568
2587
  var import_node_fs5 = require("fs");
@@ -2970,6 +2989,21 @@ function writeCsharpExample(absoluteRoot, identity, kind) {
2970
2989
  writeFileEnsured((0, import_node_path6.join)(absoluteRoot, "Program.cs"), csharpConsoleProgram(identity));
2971
2990
  }
2972
2991
  }
2992
+ function addCsharpTestProject(workspaceRoot, projectRoot, identity) {
2993
+ const name = (0, import_node_path6.basename)(projectRoot);
2994
+ const testRoot = `tests/${name}`;
2995
+ scaffoldDotnetProject(workspaceRoot, testRoot, `${identity}.Tests`, "xunit");
2996
+ (0, import_node_fs6.rmSync)((0, import_node_path6.join)(workspaceRoot, testRoot, "UnitTest1.cs"), { force: true });
2997
+ writeFileEnsured((0, import_node_path6.join)(workspaceRoot, testRoot, "GreetUseCaseTests.cs"), csharpExampleTest(identity));
2998
+ writeFileEnsured(
2999
+ (0, import_node_path6.join)(workspaceRoot, testRoot, "project.json"),
3000
+ toJson({ name: `${name}-tests`, tags: ["type:csharp-test"] })
3001
+ );
3002
+ const reference = (0, import_node_path6.join)(workspaceRoot, projectRoot, `${identity}.csproj`);
3003
+ if (runShell("dotnet", ["add", (0, import_node_path6.join)(workspaceRoot, testRoot), "reference", reference], workspaceRoot) !== 0) {
3004
+ throw new Error(`dotnet add reference failed for ${testRoot}`);
3005
+ }
3006
+ }
2973
3007
  function csharpAppPackageTarget(tag, projectRoot, name) {
2974
3008
  const outDir = `dist/apps/${name}`;
2975
3009
  const zip = `dist/drop/${tag}-${name}.zip`;
@@ -2994,6 +3028,7 @@ function addCsharpApp(workspaceRoot, name, template = "console") {
2994
3028
  const projectRoot = `apps/${name}`;
2995
3029
  scaffoldDotnetProject(workspaceRoot, projectRoot, pascalCase(name), template);
2996
3030
  writeCsharpExample((0, import_node_path6.join)(workspaceRoot, projectRoot), pascalCase(name), template);
3031
+ addCsharpTestProject(workspaceRoot, projectRoot, pascalCase(name));
2997
3032
  addProjectJsonTargets((0, import_node_path6.join)(workspaceRoot, projectRoot, "project.json"), {
2998
3033
  package: csharpAppPackageTarget("csharp-app", projectRoot, name),
2999
3034
  start: csharpAppStartTarget(projectRoot)
@@ -3118,6 +3153,7 @@ async function addCsharpLib(workspaceRoot, name, options, kindProvided) {
3118
3153
  const identity = `${pascalScope(scope)}.${pascalCase(name)}`;
3119
3154
  scaffoldDotnetProject(workspaceRoot, projectRoot, identity, "classlib");
3120
3155
  writeCsharpExample((0, import_node_path6.join)(workspaceRoot, projectRoot), identity, "classlib");
3156
+ addCsharpTestProject(workspaceRoot, projectRoot, identity);
3121
3157
  addInitialVersion((0, import_node_path6.join)(workspaceRoot, projectRoot, `${identity}.csproj`));
3122
3158
  writeCsharpVersionActions(workspaceRoot);
3123
3159
  addProjectJsonReleaseVersionActions((0, import_node_path6.join)(workspaceRoot, projectRoot, "project.json"));
@@ -3135,6 +3171,7 @@ function addCsharpInternalLib(workspaceRoot, name) {
3135
3171
  const identity = pascalCase(name);
3136
3172
  scaffoldDotnetProject(workspaceRoot, projectRoot, identity, "classlib");
3137
3173
  writeCsharpExample((0, import_node_path6.join)(workspaceRoot, projectRoot), identity, "classlib");
3174
+ addCsharpTestProject(workspaceRoot, projectRoot, identity);
3138
3175
  registerProjectCommands(workspaceRoot, name, { build: false });
3139
3176
  logger.step(
3140
3177
  `Reference it from a consumer with: dotnet add <consumer>.csproj reference ${projectRoot}/${identity}.csproj`
@@ -3214,6 +3251,7 @@ function addCsharpFunctionApp(workspaceRoot, name) {
3214
3251
  if (runShell("dotnet", ["restore", projectRoot], workspaceRoot) !== 0) {
3215
3252
  throw new Error(`dotnet restore failed for ${projectRoot}`);
3216
3253
  }
3254
+ addCsharpTestProject(workspaceRoot, projectRoot, identity);
3217
3255
  addProjectJsonTargets((0, import_node_path6.join)(absoluteRoot, "project.json"), {
3218
3256
  package: csharpAppPackageTarget("csharp-function-app", projectRoot, name),
3219
3257
  start: csharpFunctionAppStartTarget(projectRoot)
@@ -7023,6 +7061,45 @@ function readWorkspaceStack(workspaceRoot) {
7023
7061
  };
7024
7062
  }
7025
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
+
7026
7103
  // src/workspace-diagnostics/check-invariants.use-case.ts
7027
7104
  var import_node_child_process = require("child_process");
7028
7105
  var import_node_fs19 = require("fs");
@@ -7302,10 +7379,12 @@ function checkOneTargetsFiles(workspaceRoot, source, targetName, target) {
7302
7379
  });
7303
7380
  }
7304
7381
  function checkSync(workspaceRoot) {
7382
+ const result = runCapture("npx", ["nx", "sync:check"], workspaceRoot);
7305
7383
  return {
7306
7384
  check: "TypeScript project references synced",
7307
- ok: runShell("npx", ["nx", "sync:check"], workspaceRoot) === 0,
7308
- 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()}` : ""}`,
7309
7388
  remedy: "run `npx nx sync` and commit the result"
7310
7389
  };
7311
7390
  }
@@ -7470,9 +7549,16 @@ function collectFindings(workspaceRoot) {
7470
7549
  ...checkNativeApps(workspaceRoot)
7471
7550
  ].filter((finding) => finding !== void 0);
7472
7551
  }
7473
- function runDoctor(workspaceRoot) {
7552
+ function runDoctor(workspaceRoot, options = {}) {
7474
7553
  const findings = collectFindings(workspaceRoot);
7475
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
+ }
7476
7562
  for (const finding of findings) {
7477
7563
  if (finding.ok) {
7478
7564
  logger.success(finding.check);
@@ -7491,9 +7577,204 @@ function runDoctor(workspaceRoot) {
7491
7577
  process.exitCode = 1;
7492
7578
  }
7493
7579
 
7494
- // src/workspace-creation/adopt-directory.use-case.ts
7495
- 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
7496
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");
7497
7778
  var NEVER_COPIED = /* @__PURE__ */ new Set([".git", "node_modules"]);
7498
7779
  var AGENT_AND_CI_ENTRIES = [
7499
7780
  "AGENTS.md",
@@ -7524,13 +7805,13 @@ var PREFLIGHT_BLOCKERS = [
7524
7805
  "packages"
7525
7806
  ];
7526
7807
  function assertAdoptableDirectory(target) {
7527
- if (!(0, import_node_fs20.existsSync)(target)) {
7808
+ if (!(0, import_node_fs21.existsSync)(target)) {
7528
7809
  throw new Error(`--into '${target}' does not exist. Create or clone it first.`);
7529
7810
  }
7530
- if (!(0, import_node_fs20.statSync)(target).isDirectory()) {
7811
+ if (!(0, import_node_fs21.statSync)(target).isDirectory()) {
7531
7812
  throw new Error(`--into '${target}' is not a directory.`);
7532
7813
  }
7533
- 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)));
7534
7815
  if (blockers.length > 0) {
7535
7816
  throw new Error(
7536
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.`
@@ -7538,15 +7819,15 @@ function assertAdoptableDirectory(target) {
7538
7819
  }
7539
7820
  }
7540
7821
  function mergeGitignore(generatedFile, targetFile) {
7541
- const existing = (0, import_node_fs20.readFileSync)(targetFile, "utf8");
7822
+ const existing = (0, import_node_fs21.readFileSync)(targetFile, "utf8");
7542
7823
  const present = new Set(existing.split("\n").map((line) => line.trim()));
7543
- 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) => {
7544
7825
  const trimmed = line.trim();
7545
7826
  return trimmed !== "" && !trimmed.startsWith("#") && !present.has(trimmed);
7546
7827
  });
7547
7828
  if (additions.length === 0) return false;
7548
7829
  const separator = existing.endsWith("\n") ? "" : "\n";
7549
- (0, import_node_fs20.writeFileSync)(
7830
+ (0, import_node_fs21.writeFileSync)(
7550
7831
  targetFile,
7551
7832
  `${existing}${separator}
7552
7833
  # Added by mnci (Nx workspace)
@@ -7556,9 +7837,9 @@ ${additions.join("\n")}
7556
7837
  return true;
7557
7838
  }
7558
7839
  function adoptGeneratedWorkspace(generatedRoot, target) {
7559
- 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));
7560
7841
  const collisions = entries.filter(
7561
- (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"
7562
7843
  );
7563
7844
  if (collisions.length > 0) {
7564
7845
  throw new Error(
@@ -7567,28 +7848,28 @@ function adoptGeneratedWorkspace(generatedRoot, target) {
7567
7848
  }
7568
7849
  const result = { copied: [], kept: [], mergedGitignore: false };
7569
7850
  for (const entry of entries) {
7570
- const from = (0, import_node_path27.join)(generatedRoot, entry);
7571
- const to = (0, import_node_path27.join)(target, entry);
7572
- 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)) {
7573
7854
  result.mergedGitignore = mergeGitignore(from, to);
7574
7855
  result.kept.push(entry);
7575
7856
  continue;
7576
7857
  }
7577
- if ((0, import_node_fs20.existsSync)(to)) {
7858
+ if ((0, import_node_fs21.existsSync)(to)) {
7578
7859
  result.kept.push(entry);
7579
7860
  continue;
7580
7861
  }
7581
- (0, import_node_fs20.cpSync)(from, to, { recursive: true });
7862
+ (0, import_node_fs21.cpSync)(from, to, { recursive: true });
7582
7863
  result.copied.push(entry);
7583
7864
  }
7584
- (0, import_node_fs20.rmSync)(generatedRoot, { recursive: true, force: true });
7865
+ (0, import_node_fs21.rmSync)(generatedRoot, { recursive: true, force: true });
7585
7866
  return result;
7586
7867
  }
7587
7868
 
7588
7869
  // src/workspace-creation/create-workspace.use-case.ts
7589
- var import_node_fs21 = require("fs");
7870
+ var import_node_fs22 = require("fs");
7590
7871
  var import_node_os = require("os");
7591
- var import_node_path28 = require("path");
7872
+ var import_node_path31 = require("path");
7592
7873
  function nxCloudProviderValue(ci) {
7593
7874
  return ci === "azure" ? "azure" : "github";
7594
7875
  }
@@ -7631,9 +7912,9 @@ async function resolveRegistry(options) {
7631
7912
  return await promptRegistry();
7632
7913
  }
7633
7914
  async function runNew(name, options) {
7634
- 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);
7635
7916
  if (adoptTarget !== void 0) assertAdoptableDirectory(adoptTarget);
7636
- 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));
7637
7918
  assertValidProjectName(workspaceName, "Workspace name");
7638
7919
  const scope = options.scope ?? (options.yes ? `@${workspaceName}` : await promptText("npm scope for publishable packages", `@${workspaceName}`));
7639
7920
  const registry = await resolveRegistry(options);
@@ -7646,8 +7927,8 @@ async function runNew(name, options) {
7646
7927
  const npmAuth = resolveNpmAuth(options.npmAuth, registry, ci);
7647
7928
  const stack = await resolveStack(options);
7648
7929
  const nxCloud = options.nxCloud ?? (options.yes ? false : await promptNxCloud());
7649
- const stagingParent = adoptTarget === void 0 ? process.cwd() : (0, import_node_fs21.mkdtempSync)((0, import_node_path28.join)((0, import_node_os.tmpdir)(), "mnci-new-"));
7650
- 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);
7651
7932
  try {
7652
7933
  logger.step(`Creating Nx workspace '${workspaceName}' (preset: ts)`);
7653
7934
  runNpx(
@@ -7670,7 +7951,7 @@ async function runNew(name, options) {
7670
7951
  workspaceRoot = adoptTarget;
7671
7952
  }
7672
7953
  } finally {
7673
- 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 });
7674
7955
  }
7675
7956
  logger.step(
7676
7957
  "Applying MoNecromanCI overlay (VS Code workspace, release config, .npmrc, commitlint, pipeline, stack)"
@@ -7680,8 +7961,8 @@ async function runNew(name, options) {
7680
7961
  { workspaceName, scope, registry, agent, variableGroup, ci, stack, ...npmAuth && { npmAuth } },
7681
7962
  logger.detail
7682
7963
  );
7683
- (0, import_node_fs21.rmSync)((0, import_node_path28.join)(workspaceRoot, "node_modules"), { recursive: true, force: true });
7684
- (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 });
7685
7966
  logger.step("Installing the toolchain (commitlint + husky + linting/formatting)");
7686
7967
  const installStatus = runShell(
7687
7968
  "npm",
@@ -7719,12 +8000,12 @@ function projectKindHintLines(kinds = PROJECT_KINDS) {
7719
8000
  }
7720
8001
 
7721
8002
  // src/workspace-creation/interactive-wizard.use-case.ts
7722
- var import_node_path30 = require("path");
8003
+ var import_node_path33 = require("path");
7723
8004
  var import_prompts4 = require("@inquirer/prompts");
7724
8005
 
7725
8006
  // src/workspace-upgrade/upgrade-workspace.use-case.ts
7726
- var import_node_fs22 = require("fs");
7727
- var import_node_path29 = require("path");
8007
+ var import_node_fs23 = require("fs");
8008
+ var import_node_path32 = require("path");
7728
8009
  function resolveRegistry2(options, persisted) {
7729
8010
  if (options.registry === "azure-artifacts" || options.organization && options.artifactsFeed) {
7730
8011
  const persistedAzure = persisted?.kind === "azure-artifacts" ? persisted : void 0;
@@ -7753,7 +8034,7 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
7753
8034
  return persisted.workspaceName;
7754
8035
  }
7755
8036
  try {
7756
- const existing = (0, import_node_fs22.readdirSync)(workspaceRoot).find(
8037
+ const existing = (0, import_node_fs23.readdirSync)(workspaceRoot).find(
7757
8038
  (file) => file.endsWith(".code-workspace") && file !== "undefined.code-workspace"
7758
8039
  );
7759
8040
  if (existing) {
@@ -7761,11 +8042,11 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
7761
8042
  }
7762
8043
  } catch {
7763
8044
  }
7764
- return (0, import_node_path29.basename)(workspaceRoot);
8045
+ return (0, import_node_path32.basename)(workspaceRoot);
7765
8046
  }
7766
8047
  function detectNpmAuth(workspaceRoot) {
7767
8048
  try {
7768
- 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;
7769
8050
  } catch {
7770
8051
  return void 0;
7771
8052
  }
@@ -7812,7 +8093,7 @@ function resolveOverlayOptions(workspaceRoot, options, persisted) {
7812
8093
  };
7813
8094
  }
7814
8095
  function runUpgrade(workspaceRoot, options) {
7815
- if (!fileExists((0, import_node_path29.join)(workspaceRoot, "nx.json"))) {
8096
+ if (!fileExists((0, import_node_path32.join)(workspaceRoot, "nx.json"))) {
7816
8097
  throw new Error(
7817
8098
  `No nx.json found in ${workspaceRoot} \u2014 this does not look like an Nx workspace. Run 'mnci upgrade' from the workspace root.`
7818
8099
  );
@@ -7877,7 +8158,7 @@ function runUpgrade(workspaceRoot, options) {
7877
8158
 
7878
8159
  // src/workspace-creation/interactive-wizard.use-case.ts
7879
8160
  async function runInteractive() {
7880
- 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"));
7881
8162
  const choiceNew = { name: "Create a new monorepo", value: "new" };
7882
8163
  const choiceAdd = { name: "Add a project to this workspace", value: "add" };
7883
8164
  const choiceUpgrade = {
@@ -7897,72 +8178,6 @@ async function runInteractive() {
7897
8178
  }
7898
8179
  }
7899
8180
 
7900
- // src/cli-version/check-for-update.use-case.ts
7901
- var import_node_fs23 = require("fs");
7902
- var import_node_path31 = require("path");
7903
- var import_cross_spawn2 = __toESM(require("cross-spawn"));
7904
- var PACKAGE_NAME = "@mnci/cli";
7905
- var REGISTRY_TIMEOUT_MS = 2e3;
7906
- function checkForUpdate(currentVersion2) {
7907
- try {
7908
- if (!process.stdout.isTTY) {
7909
- return;
7910
- }
7911
- setImmediate(() => {
7912
- try {
7913
- reportIfOutdated(currentVersion2);
7914
- } catch {
7915
- }
7916
- });
7917
- } catch {
7918
- }
7919
- }
7920
- function reportIfOutdated(currentVersion2) {
7921
- const result = import_cross_spawn2.default.sync("npm", ["view", PACKAGE_NAME, "version"], {
7922
- encoding: "utf8",
7923
- timeout: REGISTRY_TIMEOUT_MS,
7924
- stdio: ["ignore", "pipe", "ignore"]
7925
- });
7926
- if (result.error || result.status !== 0) {
7927
- return;
7928
- }
7929
- const latestVersion = (result.stdout ?? "").trim();
7930
- if (!latestVersion || !isNewerVersion(latestVersion, currentVersion2)) {
7931
- return;
7932
- }
7933
- logger.info("");
7934
- logger.info(`A new version of ${PACKAGE_NAME} is available: ${latestVersion}`);
7935
- logger.info(` Current version: ${currentVersion2}`);
7936
- logger.info(` Update with: npm install -g ${PACKAGE_NAME}@latest`);
7937
- logger.info("");
7938
- }
7939
- function isNewerVersion(candidate, current) {
7940
- const candidateParts = releaseTriple(candidate);
7941
- const currentParts = releaseTriple(current);
7942
- if (!candidateParts || !currentParts) {
7943
- return false;
7944
- }
7945
- for (const [index, candidatePart] of candidateParts.entries()) {
7946
- if (candidatePart !== currentParts[index]) {
7947
- return candidatePart > currentParts[index];
7948
- }
7949
- }
7950
- return false;
7951
- }
7952
- function releaseTriple(version) {
7953
- const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
7954
- return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : void 0;
7955
- }
7956
- function readCliVersion(packageDirectory) {
7957
- try {
7958
- const manifestPath = (0, import_node_path31.join)(packageDirectory, "..", "package.json");
7959
- const manifest = JSON.parse((0, import_node_fs23.readFileSync)(manifestPath, "utf8"));
7960
- return manifest.version ?? "0.0.0";
7961
- } catch {
7962
- return "0.0.0";
7963
- }
7964
- }
7965
-
7966
8181
  // src/main.ts
7967
8182
  function buildProgram(cliVersion) {
7968
8183
  const program = new import_commander.Command();
@@ -7992,8 +8207,8 @@ function buildProgram(cliVersion) {
7992
8207
  });
7993
8208
  program.command("doctor").description(
7994
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"
7995
- ).action(() => {
7996
- runDoctor(process.cwd());
8210
+ ).option("--json", "print the findings as one JSON document, each with its remedy").action((options) => {
8211
+ runDoctor(process.cwd(), options);
7997
8212
  });
7998
8213
  program.command("ci").addArgument(new import_commander.Argument("<phase>", "which phase of the pipeline to run").choices(CI_PHASES)).description(
7999
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"
@@ -8012,12 +8227,16 @@ function buildProgram(cliVersion) {
8012
8227
  });
8013
8228
  program.command("install").alias("i").argument("[packages...]", "packages to add (a bare name or a pinned spec); omit to install only").description(
8014
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>'"
8015
- ).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) => {
8016
8236
  runInstall(process.cwd(), packages, options);
8017
8237
  });
8018
- 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(
8019
- "--framework <framework>",
8020
- "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"])
8021
8240
  ).option(
8022
8241
  "--empty",
8023
8242
  "npm-lib, internal-lib, react-lib, react-internal-lib, node-function-app: scaffold the slice skeleton only, with no sample code or spec"
@@ -8044,6 +8263,18 @@ function buildProgram(cliVersion) {
8044
8263
  await runAdd(kind, name, options);
8045
8264
  }
8046
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
+ });
8047
8278
  program.action(async () => {
8048
8279
  await runInteractive();
8049
8280
  });