@mnci/cli 1.5.0 → 1.7.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/README.md CHANGED
@@ -29,10 +29,12 @@ first-party (or established community) Nx equivalent:
29
29
  ```sh
30
30
  mnci new my-repo # create a monorepo (prompts scope + registry)
31
31
  mnci new my-repo --yes --registry npm --scope @my
32
+ mnci new my-repo --yes --registry npm --scope @my --nx-cloud # opt in to Nx Cloud
32
33
 
33
34
  cd my-repo
34
35
  mnci add react-app web # @nx/react (Vite + Jest)
35
36
  mnci add node-app svc # @nx/node (plain Node app, esbuild)
37
+ mnci add node-app api --framework express # ...or fastify | koa | nest
36
38
  mnci add node-function-app api # @nx/node + an Azure Functions v4 overlay
37
39
  mnci add npm-lib sdk # @nx/js publishable lib -> packages/
38
40
  mnci add internal-lib utils # @nx/js private lib -> libs/
@@ -42,6 +44,7 @@ mnci add python-app svc # app -> apps/ (wheel, zipped into the drop)
42
44
  mnci add python-function-app fn # Azure Functions (Python v2) -> apps/
43
45
  mnci add python-lib shared # publishable -> python-packages/ (twine upload)
44
46
  mnci add python-internal-lib core # private shared lib -> libs/
47
+ mnci add python-vendor shared --lib core # wire core's module into shared's built wheel
45
48
 
46
49
  mnci upgrade # re-apply the latest overlay (see below)
47
50
  mnci upgrade --agent windows-latest # ...with an explicit override
@@ -58,6 +61,7 @@ each a single cross-platform command:
58
61
  | `npm run affected` | `nx affected -t lint,test,build` (vs `main`) |
59
62
  | `npm run graph` | `nx graph` |
60
63
  | `npm run release:preview` | `nx release --dry-run` |
64
+ | `npm run python:install` | fixed Python toolchain (ruff/pytest/build/twine) + editable-install every Python project — the same two guards CI runs, for local dev |
61
65
  | `prepare` | `husky` (commit-msg lint hook) |
62
66
 
63
67
  ## What `new` actually does
@@ -339,6 +343,30 @@ only for `github`/`both` — an `azure`-only workspace gets no
339
343
  `.github/dependabot.yml`, matching every other GitHub-only file this CLI
340
344
  writes.
341
345
 
346
+ ### Nx Cloud (`--nx-cloud`, opt-in)
347
+
348
+ `mnci new` never connects to Nx Cloud unless asked — `--nx-cloud` (or
349
+ answering "yes" to the interactive prompt) opts in; the flagless/`--yes`
350
+ default stays fully disconnected, exactly as before this existed. When
351
+ opted in, `mnci` passes `create-nx-workspace` a **named** `--nxCloud`
352
+ provider value derived from the chosen `--ci` (`azure`→`azure`,
353
+ `github`/`both`→`github`) — never the bare `--nxCloud=yes`. Verified
354
+ empirically: bare `yes` prompts "Will you be using GitHub as your git
355
+ hosting provider?" even with `--no-interactive` set, and exits without
356
+ creating the workspace at all when stdin isn't a TTY — a real
357
+ `create-nx-workspace` inconsistency, not something `mnci` can configure
358
+ around. The named-provider value sidesteps it and completes non-interactively
359
+ every time. The only visible effect of *which* named value is chosen is a
360
+ throwaway CI workflow file `create-nx-workspace` writes as a side effect of
361
+ Cloud setup — this CLI's own overlay unconditionally overwrites whatever
362
+ lands at that path immediately after, so the pipeline you actually get is
363
+ always the same one described above, Nx Cloud or not.
364
+
365
+ Connecting still requires finishing setup in a browser: `create-nx-workspace`
366
+ prints a `https://cloud.nx.app/connect/…` URL to complete linking the
367
+ workspace to an Nx Cloud account (remote caching, CI insights, `nx
368
+ fix-ci`) — `mnci` does not automate that step.
369
+
342
370
  ## Dependency & risk notes
343
371
 
344
372
  Being upfront about what mnci leans on, so it's a conscious trade-off rather
@@ -394,18 +422,34 @@ than a surprise:
394
422
  Python project workspace-wide (see "Workspace-wide install" above),
395
423
  straight into whatever `<python>` resolves to on the agent (`python3` on
396
424
  POSIX, `python` on Windows — see above); locally, create your own venv
397
- (`python3 -m venv` / `python -m venv` on Windows) and reproduce the same
398
- two installs by hand (`pip install -r requirements-dev.txt`, then
399
- `pip install -e <dir>` for each Python project).
425
+ (`python3 -m venv` / `python -m venv` on Windows) and run
426
+ `npm run python:install` to reproduce the same two installs — the root
427
+ script chains the identical two guards CI runs (see the scripts table
428
+ above).
400
429
 
401
430
  ## How Node apps work (plain `@nx/node:application`, no Azure Functions plugin)
402
431
 
403
432
  `node-app` and `node-function-app` are both the **official**
404
- `@nx/node:application` generator (`--bundler=esbuild --framework=none`) — no
405
- third-party Azure Functions plugin, and no post-generation build-output
406
- rewiring. `node-function-app` is exactly that generator plus a hand-written
407
- Azure Functions v4 file overlay, the same split `python-app`/
408
- `python-function-app` already use:
433
+ `@nx/node:application` generator (`--bundler=esbuild`) — no third-party Azure
434
+ Functions plugin, and no post-generation build-output rewiring.
435
+ `node-function-app` is exactly that generator plus a hand-written Azure
436
+ Functions v4 file overlay, the same split `python-app`/`python-function-app`
437
+ already use:
438
+
439
+ - **`node-app` framework choice** (`--framework`, default `none`): plain flag
440
+ plumbing to the generator's own `express`/`fastify`/`koa`/`nest`/`none`
441
+ choices — `mnci` adds no framework-specific logic of its own. Verified
442
+ empirically that all four scaffold, build and test cleanly on Nx 23.1.0.
443
+ One quirk worth knowing: `--framework=nest` silently overrides
444
+ `--bundler=esbuild` — NestJS needs its own webpack build (decorator/DI
445
+ metadata emission esbuild's transform-only approach can't produce), so a
446
+ `nest` app's `dist/main.js` is a single webpack bundle instead of the
447
+ esbuild non-bundled mirrored-tree + shim the other frameworks (and `none`)
448
+ produce. The `package` target needs no framework branch either way — both
449
+ shapes' runnable entry is `dist/main.js`, so zipping the whole `dist` folder
450
+ works unchanged. `node-function-app` never accepts `--framework`: the Azure
451
+ Functions v4 programming model (`app.http(...)` registration) runs its own
452
+ request lifecycle, so a full HTTP server framework doesn't apply there.
409
453
 
410
454
  - `build` = the generator's own `@nx/esbuild:esbuild` target, **non-bundled**
411
455
  (`bundle: false`): it transpiles each file individually and mirrors the
@@ -518,18 +562,21 @@ identically to a Linux/macOS one.
518
562
  <name>` just like the TS apps.
519
563
  - **Internal-lib vendoring** replaces `@nxlv/python`'s `bundleLocalDependencies`:
520
564
  plain pip has no bundled-local-dependency feature, so a project that imports
521
- a workspace-internal Python library needs a hand-added `vendor` entry (under
565
+ a workspace-internal Python library needs a `vendor` entry (under
522
566
  `[tool.mnci-python-pip]`) in its own `pyproject.toml` (the pip-world
523
- counterpart of hand-wiring a `dependencies = [...]` entry — neither mnci
524
- nor the plugin wires cross-project Python dependencies automatically). The
525
- plugin's `build` executor reads that entry, resolves the named project's
526
- root via the **Nx project graph**, copies its module into a staged copy of
527
- the consuming project, and builds from there — so the resulting wheel
528
- contains the vendored module as a real top-level package. Verified
529
- empirically that this does **not** reproduce the old `@nxlv/python` bug
530
- where combining a vendored internal lib and a real external dependency on
531
- the same project silently dropped the external one from the wheel's
532
- metadata — both survive correctly.
567
+ counterpart of a `dependencies = [...]` entry — neither mnci nor the plugin
568
+ wires cross-project Python dependencies automatically). `mnci add
569
+ python-vendor <consumer> --lib <name>` automates writing that entry —
570
+ idempotent (safe to run twice), and works on any consumer with a
571
+ `pyproject.toml` (app, publishable lib, or another internal lib), not just
572
+ apps. The plugin's `build` executor reads the entry, resolves the named
573
+ project's root via the **Nx project graph**, copies its module into a
574
+ staged copy of the consuming project, and builds from there — so the
575
+ resulting wheel contains the vendored module as a real top-level package.
576
+ Verified empirically that this does **not** reproduce the old
577
+ `@nxlv/python` bug where combining a vendored internal lib and a real
578
+ external dependency on the same project silently dropped the external one
579
+ from the wheel's metadata — both survive correctly.
533
580
  - **Workspace-wide install** (mnci's own CI step, not the plugin's) — pip has
534
581
  no npm-workspaces-style hoisting, so mnci writes one: a guarded step
535
582
  editable-installs every Python project (`apps/*`, `python-packages/*`,
package/dist/cli.js CHANGED
@@ -35,13 +35,13 @@ __export(cli_exports, {
35
35
  main: () => main
36
36
  });
37
37
  module.exports = __toCommonJS(cli_exports);
38
- var import_node_fs3 = require("fs");
38
+ var import_node_fs4 = require("fs");
39
39
  var import_node_path12 = require("path");
40
40
  var import_commander = require("commander");
41
41
 
42
42
  // src/commands/add.ts
43
43
  var import_node_path7 = require("path");
44
- var import_prompts3 = require("@inquirer/prompts");
44
+ var import_prompts4 = require("@inquirer/prompts");
45
45
 
46
46
  // src/nx.ts
47
47
  var import_cross_spawn = __toESM(require("cross-spawn"));
@@ -100,6 +100,12 @@ async function promptCi() {
100
100
  ]
101
101
  });
102
102
  }
103
+ async function promptNxCloud() {
104
+ return await (0, import_prompts.confirm)({
105
+ message: "Connect this workspace to Nx Cloud (remote caching + CI insights)?",
106
+ default: false
107
+ });
108
+ }
103
109
  async function promptStack() {
104
110
  const linter = await (0, import_prompts.select)({
105
111
  message: "Linter",
@@ -204,7 +210,7 @@ function ensurePlugin(workspaceRoot, packageName) {
204
210
  logger.step(`Installing Nx plugin ${packageName}`);
205
211
  runNx(["add", packageName], workspaceRoot);
206
212
  }
207
- function runNodeApp(workspaceRoot, name, stack) {
213
+ function runNodeApp(workspaceRoot, name, stack, framework) {
208
214
  ensurePlugin(workspaceRoot, "@nx/node");
209
215
  runNx([
210
216
  "g",
@@ -214,7 +220,7 @@ function runNodeApp(workspaceRoot, name, stack) {
214
220
  `--unitTestRunner=${stack.testRunner}`,
215
221
  `--linter=${stack.linter}`,
216
222
  "--e2eTestRunner=none",
217
- "--framework=none",
223
+ `--framework=${framework}`,
218
224
  "--no-interactive"
219
225
  ], workspaceRoot);
220
226
  }
@@ -229,8 +235,8 @@ function nodeAppPackageTarget(name) {
229
235
  options: { command }
230
236
  };
231
237
  }
232
- function addNodeApp(workspaceRoot, name, stack) {
233
- runNodeApp(workspaceRoot, name, stack);
238
+ function addNodeApp(workspaceRoot, name, stack, framework = "none") {
239
+ runNodeApp(workspaceRoot, name, stack, framework);
234
240
  ensureAdmZip(workspaceRoot);
235
241
  addNxTargets((0, import_node_path3.join)(workspaceRoot, "apps", name, "package.json"), { package: nodeAppPackageTarget(name) });
236
242
  }
@@ -306,7 +312,7 @@ function nodeFunctionAppPackageTarget(name) {
306
312
  };
307
313
  }
308
314
  function addNodeFunctionApp(workspaceRoot, name, stack) {
309
- runNodeApp(workspaceRoot, name, stack);
315
+ runNodeApp(workspaceRoot, name, stack, "none");
310
316
  ensureAzureFunctionsPackage(workspaceRoot);
311
317
  const nodeFunctionAppRoot = (0, import_node_path3.join)(workspaceRoot, "apps", name);
312
318
  writeFileEnsured((0, import_node_path3.join)(nodeFunctionAppRoot, "src/main.ts"), NODE_FUNCTION_APP_MAIN);
@@ -385,6 +391,7 @@ function markPublic(manifestPath) {
385
391
  }
386
392
 
387
393
  // src/commands/add/python.ts
394
+ var import_node_fs2 = require("fs");
388
395
  var import_node_path5 = require("path");
389
396
  function ensurePython(workspaceRoot) {
390
397
  const hasPython = runShell("python3", ["--version"], workspaceRoot) === 0 || runShell("python", ["--version"], workspaceRoot) === 0;
@@ -476,9 +483,57 @@ function addPythonInternalLib(workspaceRoot, name) {
476
483
  ensureRequirementsDev(workspaceRoot);
477
484
  runNx(["g", "@mnci/nx-python-pip:internal-library", name, `--directory=libs/${name}`, "--no-interactive"], workspaceRoot);
478
485
  }
486
+ var PYTHON_PROJECT_DIRS = ["apps", "python-packages", "libs"];
487
+ function findPythonProjectDirectory(workspaceRoot, name) {
488
+ return PYTHON_PROJECT_DIRS.find((directory) => fileExists((0, import_node_path5.join)(workspaceRoot, directory, name, "pyproject.toml")));
489
+ }
490
+ function parseVendorEntries(pyprojectToml) {
491
+ const match = /^\s*vendor\s*=\s*\[([^\]]*)\]/m.exec(pyprojectToml);
492
+ if (!match) {
493
+ return [];
494
+ }
495
+ return match[1].split(",").map((entry) => entry.trim().replaceAll(/^["']|["']$/g, "")).filter(Boolean);
496
+ }
497
+ function addVendorEntry(pyprojectToml, lib) {
498
+ const existing = parseVendorEntries(pyprojectToml);
499
+ if (existing.length > 0) {
500
+ const merged = [...existing, lib].map((name) => `"${name}"`).join(", ");
501
+ return pyprojectToml.replace(/^(\s*vendor\s*=\s*)\[([^\]]*)\]/m, (_match, prefix) => `${prefix}[${merged}]`);
502
+ }
503
+ const table = `[tool.mnci-python-pip]
504
+ vendor = ["${lib}"]
505
+
506
+ `;
507
+ return pyprojectToml.includes("[tool.pytest.ini_options]") ? pyprojectToml.replace("[tool.pytest.ini_options]", () => `${table}[tool.pytest.ini_options]`) : `${pyprojectToml}
508
+ ${table}`;
509
+ }
510
+ async function addPythonVendor(workspaceRoot, consumer, options, kindProvided) {
511
+ const lib = options.lib ?? (kindProvided ? void 0 : await promptText("Internal Python library to vendor (an existing libs/<name>)"));
512
+ if (!lib) {
513
+ throw new Error("`add python-vendor` needs the library to vendor: pass --lib <name> (an existing libs/<name> internal library).");
514
+ }
515
+ const consumerDirectory = findPythonProjectDirectory(workspaceRoot, consumer);
516
+ if (!consumerDirectory) {
517
+ throw new Error(`No Python project named '${consumer}' with a pyproject.toml found (checked apps/, python-packages/, libs/). A Python function app has no pyproject.toml \u2014 there is nothing to vendor into.`);
518
+ }
519
+ if (!fileExists((0, import_node_path5.join)(workspaceRoot, "libs", lib, "pyproject.toml"))) {
520
+ throw new Error(`No internal Python library named '${lib}' found at libs/${lib}/pyproject.toml. Only an internal library (added via 'mnci add python-internal-lib') can be vendored.`);
521
+ }
522
+ if (consumerDirectory === "libs" && consumer === lib) {
523
+ throw new Error(`'${consumer}' cannot vendor itself.`);
524
+ }
525
+ const pyprojectPath = (0, import_node_path5.join)(workspaceRoot, consumerDirectory, consumer, "pyproject.toml");
526
+ const pyprojectToml = (0, import_node_fs2.readFileSync)(pyprojectPath, "utf8");
527
+ if (parseVendorEntries(pyprojectToml).includes(lib)) {
528
+ logger.info(`'${consumer}' already vendors '${lib}' \u2014 nothing to do.`);
529
+ return;
530
+ }
531
+ writeFileEnsured(pyprojectPath, addVendorEntry(pyprojectToml, lib));
532
+ logger.success(`'${consumer}' now vendors '${lib}' \u2014 its wheel will include '${lib}''s module on the next build.`);
533
+ }
479
534
 
480
535
  // src/commands/add/reactApp.ts
481
- var import_node_fs2 = require("fs");
536
+ var import_node_fs3 = require("fs");
482
537
  var import_node_path6 = require("path");
483
538
  function ensurePlugin2(workspaceRoot, packageName) {
484
539
  if (hasPlugin(workspaceRoot, packageName)) {
@@ -521,7 +576,7 @@ function allowEnvFiles(workspaceRoot) {
521
576
  return;
522
577
  }
523
578
  const marker = "# MoNecromanCI: keep the committed per-environment Vite config (public VITE_ values)";
524
- const current = (0, import_node_fs2.readFileSync)(gitignorePath, "utf8");
579
+ const current = (0, import_node_fs3.readFileSync)(gitignorePath, "utf8");
525
580
  if (current.includes(marker)) {
526
581
  return;
527
582
  }
@@ -563,7 +618,8 @@ var PROJECT_KINDS = [
563
618
  "python-app",
564
619
  "python-function-app",
565
620
  "python-lib",
566
- "python-internal-lib"
621
+ "python-internal-lib",
622
+ "python-vendor"
567
623
  ];
568
624
  async function runAdd(kind, name, options) {
569
625
  const workspaceRoot = process.cwd();
@@ -572,7 +628,7 @@ async function runAdd(kind, name, options) {
572
628
  }
573
629
  const stack = readWorkspaceStack(workspaceRoot);
574
630
  const kindProvided = kind !== void 0;
575
- const resolvedKind = kind ?? await (0, import_prompts3.select)({
631
+ const resolvedKind = kind ?? await (0, import_prompts4.select)({
576
632
  message: "What kind of project?",
577
633
  choices: PROJECT_KINDS.map((value) => ({ name: value, value }))
578
634
  });
@@ -584,7 +640,7 @@ async function runAdd(kind, name, options) {
584
640
  break;
585
641
  }
586
642
  case "node-app": {
587
- addNodeApp(workspaceRoot, resolvedName, stack);
643
+ addNodeApp(workspaceRoot, resolvedName, stack, options.framework);
588
644
  break;
589
645
  }
590
646
  case "node-function-app": {
@@ -624,6 +680,11 @@ async function runAdd(kind, name, options) {
624
680
  addPythonInternalLib(workspaceRoot, resolvedName);
625
681
  break;
626
682
  }
683
+ case "python-vendor": {
684
+ await addPythonVendor(workspaceRoot, resolvedName, options, kindProvided);
685
+ syncProjectReferences(workspaceRoot);
686
+ return;
687
+ }
627
688
  default: {
628
689
  const exhaustive = resolvedKind;
629
690
  throw new Error(`Unknown project kind '${exhaustive}'. Expected one of: ${PROJECT_KINDS.join(", ")}.`);
@@ -653,7 +714,7 @@ function markPrivate(manifestPath) {
653
714
 
654
715
  // src/commands/interactive.ts
655
716
  var import_node_path10 = require("path");
656
- var import_prompts6 = require("@inquirer/prompts");
717
+ var import_prompts7 = require("@inquirer/prompts");
657
718
 
658
719
  // src/commands/new.ts
659
720
  var import_node_path9 = require("path");
@@ -730,15 +791,13 @@ var ROOT_SCRIPTS = {
730
791
  prepare: "husky"
731
792
  };
732
793
  function rootScripts(stack) {
733
- if (stack.linter === "oxlint") {
734
- return {
735
- ...ROOT_SCRIPTS,
736
- lint: "oxlint",
737
- format: "oxfmt -c oxfmt.config.mts .",
738
- "format:check": "oxfmt -c oxfmt.config.mts --check ."
739
- };
740
- }
741
- return { ...ROOT_SCRIPTS };
794
+ const scripts = stack.linter === "oxlint" ? {
795
+ ...ROOT_SCRIPTS,
796
+ lint: "oxlint",
797
+ format: "oxfmt -c oxfmt.config.mts .",
798
+ "format:check": "oxfmt -c oxfmt.config.mts --check ."
799
+ } : { ...ROOT_SCRIPTS };
800
+ return { ...scripts, "python:install": `${PYTHON_INSTALL_GUARD} && ${PYTHON_WORKSPACE_INSTALL_GUARD}` };
742
801
  }
743
802
  function generatorDefaults(stack) {
744
803
  const shared = { linter: stack.linter === "oxlint" ? "none" : "eslint", unitTestRunner: stack.testRunner };
@@ -1142,6 +1201,9 @@ function applyOverlay(workspaceRoot, options) {
1142
1201
  }
1143
1202
 
1144
1203
  // src/commands/new.ts
1204
+ function nxCloudProviderValue(ci) {
1205
+ return ci === "azure" ? "azure" : "github";
1206
+ }
1145
1207
  async function resolveStack(options) {
1146
1208
  const fromFlags = { linter: options.linter, testRunner: options.testRunner };
1147
1209
  const complete = Boolean(fromFlags.linter && fromFlags.testRunner);
@@ -1190,6 +1252,7 @@ async function runNew(name, options) {
1190
1252
  const agent = options.agent ?? (options.yes ? "ubuntu-latest" : await promptText("CI build agent/runner (vmImage, GitHub Actions runner label, or self-hosted pool name)", "ubuntu-latest"));
1191
1253
  const variableGroup = ci === "github" ? options.variableGroup ?? "Build" : options.variableGroup ?? (options.yes ? "Build" : await promptText("Azure DevOps variable group holding the npm PAT", "Build"));
1192
1254
  const stack = await resolveStack(options);
1255
+ const nxCloud = options.nxCloud ?? (options.yes ? false : await promptNxCloud());
1193
1256
  logger.step(`Creating Nx workspace '${workspaceName}' (preset: ts)`);
1194
1257
  runNpx([
1195
1258
  "--yes",
@@ -1197,7 +1260,7 @@ async function runNew(name, options) {
1197
1260
  workspaceName,
1198
1261
  "--preset=ts",
1199
1262
  "--pm=npm",
1200
- "--nxCloud=skip",
1263
+ nxCloud ? `--nxCloud=${nxCloudProviderValue(ci)}` : "--nxCloud=skip",
1201
1264
  "--no-interactive"
1202
1265
  ], process.cwd());
1203
1266
  const workspaceRoot = (0, import_node_path9.join)(process.cwd(), workspaceName);
@@ -1222,7 +1285,7 @@ async function runInteractive() {
1222
1285
  const inWorkspace = fileExists((0, import_node_path10.join)(process.cwd(), "nx.json"));
1223
1286
  const newChoice = { name: "Create a new monorepo", value: "new" };
1224
1287
  const addChoice = { name: "Add a project to this workspace", value: "add" };
1225
- const action = await (0, import_prompts6.select)({
1288
+ const action = await (0, import_prompts7.select)({
1226
1289
  message: "What would you like to do?",
1227
1290
  choices: inWorkspace ? [addChoice, newChoice] : [newChoice, addChoice]
1228
1291
  });
@@ -1290,7 +1353,7 @@ function runUpgrade(workspaceRoot, options) {
1290
1353
  // src/cli.ts
1291
1354
  function readVersion() {
1292
1355
  try {
1293
- const package_ = JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path12.join)(__dirname, "..", "package.json"), "utf8"));
1356
+ const package_ = JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path12.join)(__dirname, "..", "package.json"), "utf8"));
1294
1357
  return package_.version ?? "0.0.0";
1295
1358
  } catch {
1296
1359
  return "0.0.0";
@@ -1299,13 +1362,13 @@ function readVersion() {
1299
1362
  function buildProgram() {
1300
1363
  const program = new import_commander.Command();
1301
1364
  program.name("mnci").description("MoNecromanCI \u2014 a thin CLI over official Nx plugins").version(readVersion(), "-v, --version", "display the version");
1302
- program.command("new").argument("[name]", "workspace name").description("Create a new monorepo (Nx TS preset + release/pipeline overlay)").option("-y, --yes", "accept defaults for anything not passed as a flag").option("--scope <scope>", "npm scope for publishable packages (e.g. @demo)").option("--registry <kind>", "azure-artifacts | npm").option("--organization <name>", "Azure DevOps organization").option("--project <name>", "Azure DevOps project").option("--artifacts-feed <name>", "Azure Artifacts feed").option("--agent <pool>", "CI build agent: a vmImage (e.g. ubuntu-latest) or self-hosted pool name").option("--variable-group <name>", "Azure DevOps variable group holding the npm PAT").option("--ci <provider>", "CI provider: azure | github | both").option("--linter <linter>", "linter: eslint | oxlint").option("--test-runner <runner>", "unit-test runner: jest | vitest").action(async (name, options) => {
1365
+ program.command("new").argument("[name]", "workspace name").description("Create a new monorepo (Nx TS preset + release/pipeline overlay)").option("-y, --yes", "accept defaults for anything not passed as a flag").option("--scope <scope>", "npm scope for publishable packages (e.g. @demo)").option("--registry <kind>", "azure-artifacts | npm").option("--organization <name>", "Azure DevOps organization").option("--project <name>", "Azure DevOps project").option("--artifacts-feed <name>", "Azure Artifacts feed").option("--agent <pool>", "CI build agent: a vmImage (e.g. ubuntu-latest) or self-hosted pool name").option("--variable-group <name>", "Azure DevOps variable group holding the npm PAT").option("--ci <provider>", "CI provider: azure | github | both").option("--linter <linter>", "linter: eslint | oxlint").option("--test-runner <runner>", "unit-test runner: jest | vitest").option("--nx-cloud", "connect the workspace to Nx Cloud (remote caching + CI insights)").action(async (name, options) => {
1303
1366
  await runNew(name, options);
1304
1367
  });
1305
1368
  program.command("upgrade").description("Re-apply the latest MoNecromanCI overlay to this workspace (release config, pipeline, npmrc, commitlint, husky hook, curated scripts)").option("--scope <scope>", "npm scope for publishable packages (overrides the persisted value)").option("--registry <kind>", "azure-artifacts | npm (overrides the persisted value)").option("--organization <name>", "Azure DevOps organization").option("--project <name>", "Azure DevOps project").option("--artifacts-feed <name>", "Azure Artifacts feed").option("--agent <pool>", "CI build agent (overrides the persisted value)").option("--variable-group <name>", "Azure DevOps variable group holding the npm PAT").option("--ci <provider>", "CI provider: azure | github | both (overrides the persisted value)").option("--linter <linter>", "linter: eslint | oxlint (overrides the persisted value)").option("--test-runner <runner>", "unit-test runner: jest | vitest (overrides the persisted value)").action((options) => {
1306
1369
  runUpgrade(process.cwd(), options);
1307
1370
  });
1308
- 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>)").action(async (kind, name, options) => {
1371
+ 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("--framework <framework>", "node-app only: express | fastify | koa | nest | none (default: none)").option("--lib <name>", "python-vendor only: the internal Python library (libs/<name>) to vendor into <name>").action(async (kind, name, options) => {
1309
1372
  await runAdd(kind, name, options);
1310
1373
  });
1311
1374
  program.action(async () => {