@mnci/cli 4.0.8 → 4.1.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
@@ -38,7 +38,7 @@ module.exports = __toCommonJS(cli_exports);
38
38
  var import_commander = require("commander");
39
39
 
40
40
  // src/commands/add.ts
41
- var import_node_path11 = require("path");
41
+ var import_node_path13 = require("path");
42
42
  var import_prompts5 = require("@inquirer/prompts");
43
43
 
44
44
  // src/nx.ts
@@ -78,6 +78,52 @@ function runShell(command, arguments_, cwd) {
78
78
  const result = import_cross_spawn.default.sync(command, arguments_, { stdio: "inherit", cwd });
79
79
  return result.status ?? 1;
80
80
  }
81
+ var CAPTURE_TIMEOUT_MS = 6e4;
82
+ function runCapture(command, arguments_, cwd) {
83
+ const result = import_cross_spawn.default.sync(command, arguments_, {
84
+ cwd,
85
+ encoding: "utf8",
86
+ timeout: CAPTURE_TIMEOUT_MS,
87
+ stdio: ["ignore", "pipe", "ignore"]
88
+ });
89
+ if (result.error) {
90
+ return { status: 1, stdout: "" };
91
+ }
92
+ return { status: result.status ?? 1, stdout: result.stdout ?? "" };
93
+ }
94
+ async function runCaptureAsync(command, arguments_, cwd) {
95
+ return await new Promise((resolve) => {
96
+ const child = (0, import_cross_spawn.default)(command, arguments_, {
97
+ cwd,
98
+ timeout: CAPTURE_TIMEOUT_MS,
99
+ stdio: ["ignore", "pipe", "ignore"]
100
+ });
101
+ let stdout = "";
102
+ child.stdout?.setEncoding("utf8");
103
+ child.stdout?.on("data", (chunk) => {
104
+ stdout += chunk;
105
+ });
106
+ child.on("error", () => {
107
+ resolve({ status: 1, stdout: "" });
108
+ });
109
+ child.on("close", (code) => {
110
+ resolve({ status: code ?? 1, stdout });
111
+ });
112
+ });
113
+ }
114
+ async function pool(items, limit, job) {
115
+ const results = Array.from({ length: items.length });
116
+ let next = 0;
117
+ const worker = async () => {
118
+ while (next < items.length) {
119
+ const index = next;
120
+ next += 1;
121
+ results[index] = await job(items[index]);
122
+ }
123
+ };
124
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, async () => await worker()));
125
+ return results;
126
+ }
81
127
  function runNx(arguments_, cwd) {
82
128
  const status = runShell("npx", ["nx", ...arguments_], cwd);
83
129
  if (status !== 0) {
@@ -1343,6 +1389,7 @@ function markPublic(manifestPath) {
1343
1389
  var WRONG_TYPES_PATH = "./dist/index.esm.d.ts";
1344
1390
  var ACTUAL_TYPES_PATH = "./dist/src/index.d.ts";
1345
1391
  var DECLARATION_MAP_EXCLUSION = "!**/*.d.ts.map";
1392
+ var SOURCE_MAP_EXCLUSION = "!**/*.js.map";
1346
1393
  var ROLLUP_CONFIG_PLACEHOLDER = [
1347
1394
  " {",
1348
1395
  " // Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options",
@@ -1350,6 +1397,68 @@ var ROLLUP_CONFIG_PLACEHOLDER = [
1350
1397
  " // output: { sourcemap: true },",
1351
1398
  " }"
1352
1399
  ].join("\n");
1400
+ var GENERATED_COMPILER = " compiler: 'swc',";
1401
+ var SOURCE_MAP_CAPABLE_COMPILER = [
1402
+ " // Swapped from swc by MoNecromanCI. @nx/rollup runs swc without",
1403
+ " // sourceMaps, so it returns no map and the bundle's map comes out empty -",
1404
+ " // valid-looking, and useless for debugging. See ROADMAP.",
1405
+ " compiler: 'babel',"
1406
+ ].join("\n");
1407
+ var ROLLUP_ARG_ONE_BOUNDARY = [" },", " {", ""].join("\n");
1408
+ var ROLLUP_ARG_ONE_WITH_SOURCE_MAPS = [
1409
+ " // Added by MoNecromanCI: without this rollup emits no .js.map at all, so",
1410
+ " // a breakpoint in a .ts file can never bind. Not published - see `files`.",
1411
+ " sourceMap: true",
1412
+ " },",
1413
+ " {",
1414
+ ""
1415
+ ].join("\n");
1416
+ function hasRollupSourceMaps(config) {
1417
+ return config.includes("sourceMap: true");
1418
+ }
1419
+ function withRollupSourceMaps(config) {
1420
+ if (hasRollupSourceMaps(config) || !config.includes(ROLLUP_ARG_ONE_BOUNDARY)) {
1421
+ return config;
1422
+ }
1423
+ const withCompiler = config.replace(GENERATED_COMPILER, () => SOURCE_MAP_CAPABLE_COMPILER);
1424
+ const withFlag = withCompiler.replace(
1425
+ ROLLUP_ARG_ONE_BOUNDARY,
1426
+ () => ROLLUP_ARG_ONE_WITH_SOURCE_MAPS
1427
+ );
1428
+ return withFlag.includes("sourcemapPathTransform") ? withFlag : withFlag.replace(
1429
+ ROLLUP_ARG_ONE_WITH_SOURCE_MAPS,
1430
+ () => `${ROLLUP_ARG_ONE_WITH_SOURCE_MAPS}${SOURCEMAP_PATH_TRANSFORM}`
1431
+ );
1432
+ }
1433
+ function repairRollupSourceMaps(workspaceRoot) {
1434
+ const changed = [];
1435
+ const configs = (0, import_node_fs3.globSync)(["packages/*/rollup.config.cjs", "libs/*/rollup.config.cjs"], {
1436
+ cwd: workspaceRoot
1437
+ });
1438
+ for (const relativePath of configs) {
1439
+ const configPath = (0, import_node_path3.join)(workspaceRoot, relativePath);
1440
+ const before = (0, import_node_fs3.readFileSync)(configPath, "utf8");
1441
+ const after = withRollupSourceMaps(before);
1442
+ if (after !== before) {
1443
+ writeFileEnsured(configPath, after);
1444
+ changed.push(relativePath.replaceAll("\\", "/"));
1445
+ }
1446
+ }
1447
+ return changed;
1448
+ }
1449
+ var SOURCEMAP_PATH_TRANSFORM = [
1450
+ " // Added by MoNecromanCI. rollup hands sourcemapPathTransform an OS-NATIVE",
1451
+ " // path with one parent segment too many, so `sources` resolve to nothing",
1452
+ " // and no breakpoint can bind. Separators are normalised too: a sources",
1453
+ " // entry is URL-style, so a backslash is wrong on every platform.",
1454
+ " output: {",
1455
+ " sourcemapPathTransform: relativeSourcePath =>",
1456
+ " relativeSourcePath",
1457
+ " .replaceAll(String.fromCodePoint(92), '/')",
1458
+ " .replace(/^([.][.][/])+/, '../')",
1459
+ " },",
1460
+ ""
1461
+ ].join("\n");
1353
1462
  var ROLLUP_CONFIG_WITH_DTS_FIX = [
1354
1463
  " {",
1355
1464
  " // Added by MoNecromanCI. @nx/rollup's dts-bundle plugin writes dist/index.d.ts",
@@ -1398,10 +1507,8 @@ function repairDeclarationSpecifiers(projectRoot) {
1398
1507
  if (!config.includes(ROLLUP_CONFIG_PLACEHOLDER)) {
1399
1508
  return;
1400
1509
  }
1401
- writeFileEnsured(
1402
- configPath,
1403
- config.split(ROLLUP_CONFIG_PLACEHOLDER).join(ROLLUP_CONFIG_WITH_DTS_FIX)
1404
- );
1510
+ const withDtsFix = config.split(ROLLUP_CONFIG_PLACEHOLDER).join(ROLLUP_CONFIG_WITH_DTS_FIX);
1511
+ writeFileEnsured(configPath, withRollupSourceMaps(withDtsFix));
1405
1512
  }
1406
1513
  function writeProjectReadme(projectRoot, projectName, testRunner) {
1407
1514
  const runner = testRunner === "vitest" ? "[Vitest](https://vitest.dev)" : "[Jest](https://jestjs.io)";
@@ -1446,8 +1553,10 @@ function repairPublishableManifest(manifestPath) {
1446
1553
  if (typeof dot === "object" && dot.types === WRONG_TYPES_PATH) {
1447
1554
  dot.types = ACTUAL_TYPES_PATH;
1448
1555
  }
1449
- if (manifest.files && !manifest.files.includes(DECLARATION_MAP_EXCLUSION)) {
1450
- manifest.files.push(DECLARATION_MAP_EXCLUSION);
1556
+ for (const exclusion of [DECLARATION_MAP_EXCLUSION, SOURCE_MAP_EXCLUSION]) {
1557
+ if (manifest.files && !manifest.files.includes(exclusion)) {
1558
+ manifest.files.push(exclusion);
1559
+ }
1451
1560
  }
1452
1561
  writeFileEnsured(manifestPath, toJson(manifest));
1453
1562
  }
@@ -2333,6 +2442,605 @@ function addReactInternalLib(workspaceRoot, name, stack) {
2333
2442
  registerProjectCommands(workspaceRoot, name, { build: true });
2334
2443
  }
2335
2444
 
2445
+ // src/commands/sync.ts
2446
+ var import_node_path12 = require("path");
2447
+
2448
+ // src/deps/inventory.ts
2449
+ var import_node_fs7 = require("fs");
2450
+ var import_node_path11 = require("path");
2451
+ var ECOSYSTEMS = ["npm", "pip", "pub", "go"];
2452
+ var ROOT_LABEL = "(root)";
2453
+ var MANIFEST_GLOBS = {
2454
+ // `libs/*` carries private internal libs; `packages/*` the publishable ones.
2455
+ npm: ["apps/*/package.json", "libs/*/package.json", "packages/*/package.json"],
2456
+ // Matches PYTHON_PROJECT_DIRS in commands/add/python.ts.
2457
+ pip: ["apps/*/pyproject.toml", "python-packages/*/pyproject.toml", "libs/*/pyproject.toml"],
2458
+ pub: ["apps/*/pubspec.yaml", "packages/*/pubspec.yaml", "libs/*/pubspec.yaml"],
2459
+ // Single root module by design — there are no per-project Go manifests.
2460
+ go: []
2461
+ };
2462
+ var PEER_SECTION = "peerDep";
2463
+ var NPM_SECTIONS = {
2464
+ dependencies: "dep",
2465
+ devDependencies: "devDep",
2466
+ peerDependencies: "peerDep",
2467
+ optionalDependencies: "optionalDep"
2468
+ };
2469
+ function toPosix(path) {
2470
+ return path.replaceAll("\\", "/");
2471
+ }
2472
+ function projectOf(manifestRelativePath) {
2473
+ const posix = toPosix(manifestRelativePath);
2474
+ const lastSlash = posix.lastIndexOf("/");
2475
+ return lastSlash === -1 ? ROOT_LABEL : posix.slice(0, lastSlash);
2476
+ }
2477
+ function hasEcosystem(workspaceRoot, ecosystem) {
2478
+ if (ecosystem === "go") {
2479
+ return fileExists((0, import_node_path11.join)(workspaceRoot, "go.mod"));
2480
+ }
2481
+ const rootMarker = {
2482
+ npm: "package.json",
2483
+ pip: "requirements-dev.txt",
2484
+ pub: "pubspec.yaml"
2485
+ };
2486
+ return fileExists((0, import_node_path11.join)(workspaceRoot, rootMarker[ecosystem])) || (0, import_node_fs7.globSync)(MANIFEST_GLOBS[ecosystem], { cwd: workspaceRoot }).length > 0;
2487
+ }
2488
+ function collectNpm(workspaceRoot) {
2489
+ const sites = [];
2490
+ const relativePaths = ["package.json", ...(0, import_node_fs7.globSync)(MANIFEST_GLOBS.npm, { cwd: workspaceRoot })];
2491
+ for (const relativePath of relativePaths) {
2492
+ const manifestPath = (0, import_node_path11.join)(workspaceRoot, relativePath);
2493
+ try {
2494
+ const manifest = readJson(manifestPath);
2495
+ sites.push(...npmManifestSites(manifest, relativePath, manifestPath));
2496
+ } catch {
2497
+ }
2498
+ }
2499
+ return sites;
2500
+ }
2501
+ function npmManifestSites(manifest, relativePath, manifestPath) {
2502
+ const sites = [];
2503
+ const project = projectOf(relativePath);
2504
+ for (const [block, section] of Object.entries(NPM_SECTIONS)) {
2505
+ const declared = manifest[block] ?? {};
2506
+ for (const [name, spec] of Object.entries(declared)) {
2507
+ sites.push({
2508
+ name,
2509
+ ecosystem: "npm",
2510
+ project,
2511
+ manifestPath,
2512
+ section,
2513
+ spec,
2514
+ // Two independent reasons to refuse a rewrite. A `file:`/`git+`/
2515
+ // `https:` target, a `workspace:` protocol or npm's `npm:pkg@range`
2516
+ // alias is a shape this cannot safely edit; a peer range is a shape it
2517
+ // could edit and must not (see PEER_SECTION).
2518
+ rewritable: section !== PEER_SECTION && /^[\^~>=<]*\d/.test(spec.trim())
2519
+ });
2520
+ }
2521
+ }
2522
+ return sites;
2523
+ }
2524
+ function parseRequirement(requirement) {
2525
+ const comment = requirement.indexOf("#");
2526
+ const text = (comment === -1 ? requirement : requirement.slice(0, comment)).trim();
2527
+ if (!text || text.startsWith("-")) {
2528
+ return void 0;
2529
+ }
2530
+ const name = /^[a-z0-9][\w.-]*/i.exec(text)?.[0];
2531
+ if (!name) {
2532
+ return void 0;
2533
+ }
2534
+ const spec = text.slice(name.length).trim();
2535
+ return {
2536
+ name,
2537
+ spec,
2538
+ // Extras, environment markers and compound ranges are reportable only.
2539
+ rewritable: !spec.startsWith("[") && !spec.includes(";") && !spec.includes(",")
2540
+ };
2541
+ }
2542
+ function collectPip(workspaceRoot) {
2543
+ const sites = [];
2544
+ const push = (relativePath, section, requirement) => {
2545
+ const parsed = parseRequirement(requirement);
2546
+ if (!parsed) {
2547
+ return;
2548
+ }
2549
+ sites.push({
2550
+ name: parsed.name,
2551
+ ecosystem: "pip",
2552
+ project: projectOf(relativePath),
2553
+ manifestPath: (0, import_node_path11.join)(workspaceRoot, relativePath),
2554
+ section,
2555
+ spec: parsed.spec,
2556
+ rewritable: parsed.rewritable && parsed.spec !== ""
2557
+ });
2558
+ };
2559
+ const pyprojectPaths = (0, import_node_fs7.globSync)(MANIFEST_GLOBS.pip, { cwd: workspaceRoot });
2560
+ for (const relativePath of pyprojectPaths) {
2561
+ let content;
2562
+ try {
2563
+ content = (0, import_node_fs7.readFileSync)((0, import_node_path11.join)(workspaceRoot, relativePath), "utf8");
2564
+ } catch {
2565
+ continue;
2566
+ }
2567
+ const declared = pyprojectDependencies(content);
2568
+ for (const entry of declared) {
2569
+ push(relativePath, "dep", entry);
2570
+ }
2571
+ }
2572
+ const requirementsPaths = [
2573
+ "requirements-dev.txt",
2574
+ ...(0, import_node_fs7.globSync)("apps/*/requirements.txt", { cwd: workspaceRoot })
2575
+ ];
2576
+ for (const relativePath of requirementsPaths) {
2577
+ if (!fileExists((0, import_node_path11.join)(workspaceRoot, relativePath))) {
2578
+ continue;
2579
+ }
2580
+ const section = relativePath === "requirements-dev.txt" ? "devDep" : "dep";
2581
+ const lines = (0, import_node_fs7.readFileSync)((0, import_node_path11.join)(workspaceRoot, relativePath), "utf8").split(/\r?\n/);
2582
+ for (const line of lines) {
2583
+ push(relativePath, section, line);
2584
+ }
2585
+ }
2586
+ return sites;
2587
+ }
2588
+ function pyprojectDependencies(content) {
2589
+ const entries = [];
2590
+ let inProjectTable = false;
2591
+ let inArray = false;
2592
+ for (const line of content.split(/\r?\n/)) {
2593
+ const text = line.trim();
2594
+ if (text.startsWith("[")) {
2595
+ inProjectTable = text === "[project]";
2596
+ inArray = false;
2597
+ continue;
2598
+ }
2599
+ if (!inProjectTable) {
2600
+ continue;
2601
+ }
2602
+ if (!inArray && /^dependencies\s*=\s*\[/.test(text)) {
2603
+ inArray = true;
2604
+ }
2605
+ if (!inArray) {
2606
+ continue;
2607
+ }
2608
+ for (const quoted of text.matchAll(/"([^"]*)"|'([^']*)'/g)) {
2609
+ entries.push(quoted[1] ?? quoted[2]);
2610
+ }
2611
+ if (text.includes("]")) {
2612
+ inArray = false;
2613
+ }
2614
+ }
2615
+ return entries;
2616
+ }
2617
+ function collectPub(workspaceRoot) {
2618
+ const sites = [];
2619
+ const pubspecPaths = (0, import_node_fs7.globSync)(MANIFEST_GLOBS.pub, { cwd: workspaceRoot });
2620
+ for (const relativePath of pubspecPaths) {
2621
+ let content;
2622
+ try {
2623
+ content = (0, import_node_fs7.readFileSync)((0, import_node_path11.join)(workspaceRoot, relativePath), "utf8");
2624
+ } catch {
2625
+ continue;
2626
+ }
2627
+ for (const block of ["dependencies", "dev_dependencies"]) {
2628
+ for (const entry of pubspecBlockEntries(content, block)) {
2629
+ sites.push({
2630
+ name: entry.name,
2631
+ ecosystem: "pub",
2632
+ project: projectOf(relativePath),
2633
+ manifestPath: (0, import_node_path11.join)(workspaceRoot, relativePath),
2634
+ section: block === "dependencies" ? "dep" : "devDep",
2635
+ spec: entry.spec,
2636
+ rewritable: entry.spec !== ""
2637
+ });
2638
+ }
2639
+ }
2640
+ }
2641
+ return sites;
2642
+ }
2643
+ function pubspecBlockEntries(content, block) {
2644
+ const lines = content.split(/\r?\n/);
2645
+ const start = lines.findIndex((line) => line.trimEnd() === `${block}:`);
2646
+ if (start === -1) {
2647
+ return [];
2648
+ }
2649
+ const entries = [];
2650
+ let indent;
2651
+ const body = lines.slice(start + 1);
2652
+ for (const line of body) {
2653
+ if (line.trim() === "" || line.trimStart().startsWith("#")) {
2654
+ continue;
2655
+ }
2656
+ const lineIndent = line.length - line.trimStart().length;
2657
+ if (lineIndent === 0) {
2658
+ break;
2659
+ }
2660
+ indent ??= lineIndent;
2661
+ if (lineIndent > indent) {
2662
+ continue;
2663
+ }
2664
+ if (lineIndent < indent) {
2665
+ break;
2666
+ }
2667
+ const colon = line.indexOf(":");
2668
+ if (colon === -1) {
2669
+ continue;
2670
+ }
2671
+ const key = line.slice(0, colon).trim();
2672
+ if (/^[a-z_][\w-]*$/i.test(key)) {
2673
+ entries.push({ name: key, spec: stripQuotes(line.slice(colon + 1).trim()) });
2674
+ }
2675
+ }
2676
+ return entries;
2677
+ }
2678
+ function stripQuotes(value) {
2679
+ return /^(['"]).*\1$/.test(value) ? value.slice(1, -1) : value;
2680
+ }
2681
+ function collectGo(workspaceRoot) {
2682
+ const manifestPath = (0, import_node_path11.join)(workspaceRoot, "go.mod");
2683
+ if (!fileExists(manifestPath)) {
2684
+ return [];
2685
+ }
2686
+ let content;
2687
+ try {
2688
+ content = (0, import_node_fs7.readFileSync)(manifestPath, "utf8");
2689
+ } catch {
2690
+ return [];
2691
+ }
2692
+ const sites = Array.from(content.matchAll(
2693
+ /^\s*(?:require\s+)?([\w.~-]+(?:\/[\w.~-]+)+)\s+(v\S+)(\s*\/\/\s*indirect)?/gm
2694
+ ), (match) => ({
2695
+ name: match[1],
2696
+ ecosystem: "go",
2697
+ project: ROOT_LABEL,
2698
+ manifestPath,
2699
+ section: match[3] ? "indirect" : "module",
2700
+ spec: match[2],
2701
+ rewritable: false
2702
+ }));
2703
+ return sites;
2704
+ }
2705
+ function collectInventory(workspaceRoot, ecosystems = ECOSYSTEMS) {
2706
+ const readers = {
2707
+ npm: collectNpm,
2708
+ pip: collectPip,
2709
+ pub: collectPub,
2710
+ go: collectGo
2711
+ };
2712
+ const inventory = /* @__PURE__ */ new Map();
2713
+ for (const ecosystem of ecosystems) {
2714
+ if (!hasEcosystem(workspaceRoot, ecosystem)) {
2715
+ continue;
2716
+ }
2717
+ const sites = readers[ecosystem](workspaceRoot);
2718
+ for (const site of sites) {
2719
+ const existing = inventory.get(site.name);
2720
+ if (existing) {
2721
+ existing.push(site);
2722
+ } else {
2723
+ inventory.set(site.name, [site]);
2724
+ }
2725
+ }
2726
+ }
2727
+ return inventory;
2728
+ }
2729
+ function rewriteSpec(site, spec) {
2730
+ if (!site.rewritable || spec === site.spec) {
2731
+ return false;
2732
+ }
2733
+ const { name } = site;
2734
+ if (site.ecosystem === "npm") {
2735
+ const manifest = readJson(site.manifestPath);
2736
+ const block = Object.entries(NPM_SECTIONS).find(([, label]) => label === site.section)?.[0];
2737
+ if (!block || manifest[block]?.[name] === void 0) {
2738
+ return false;
2739
+ }
2740
+ manifest[block][name] = spec;
2741
+ writeFileEnsured(site.manifestPath, toJson(manifest));
2742
+ return true;
2743
+ }
2744
+ const before = (0, import_node_fs7.readFileSync)(site.manifestPath, "utf8");
2745
+ const after = site.ecosystem === "pip" ? replacePipSpec(before, name, spec) : replacePubSpec(before, name, spec);
2746
+ if (after === before) {
2747
+ return false;
2748
+ }
2749
+ writeFileEnsured(site.manifestPath, after);
2750
+ return true;
2751
+ }
2752
+ function replacePipSpec(content, name, spec) {
2753
+ const escaped = escapeForRegex(name);
2754
+ return content.replaceAll(
2755
+ new RegExp(String.raw`(^|["'\s])(${escaped})\s*(?:[<>=!~^][^"'\n,]*)?(?=["'\n]|$)`, "gm"),
2756
+ (match, lead, matched) => `${lead}${matched}${spec}`
2757
+ );
2758
+ }
2759
+ function replacePubSpec(content, name, spec) {
2760
+ return content.replaceAll(
2761
+ new RegExp(String.raw`^(\s+${escapeForRegex(name)}:[^\S\n]*)\S[^\n]*$`, "gm"),
2762
+ (match, lead) => `${lead}${spec}`
2763
+ );
2764
+ }
2765
+ function escapeForRegex(value) {
2766
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
2767
+ }
2768
+ function isWorkspaceProject(workspaceRoot, name) {
2769
+ const short = name.includes("/") ? name.split("/").at(-1) : name;
2770
+ return ["apps", "libs", "packages"].some(
2771
+ (directory) => fileExists((0, import_node_path11.join)(workspaceRoot, directory, short ?? name, "package.json"))
2772
+ );
2773
+ }
2774
+ function isAliasedInstall(workspaceRoot, name) {
2775
+ try {
2776
+ const installed = readJson(
2777
+ (0, import_node_path11.join)(workspaceRoot, "node_modules", name, "package.json")
2778
+ );
2779
+ return installed.name !== void 0 && installed.name !== name;
2780
+ } catch {
2781
+ return false;
2782
+ }
2783
+ }
2784
+ function resolvedVersion(workspaceRoot, ecosystem, name) {
2785
+ if (ecosystem === "npm") {
2786
+ try {
2787
+ return readJson(
2788
+ (0, import_node_path11.join)(workspaceRoot, "node_modules", name, "package.json")
2789
+ ).version;
2790
+ } catch {
2791
+ return void 0;
2792
+ }
2793
+ }
2794
+ if (ecosystem === "pub") {
2795
+ const lockPath = (0, import_node_path11.join)(workspaceRoot, "pubspec.lock");
2796
+ if (!fileExists(lockPath)) {
2797
+ return void 0;
2798
+ }
2799
+ try {
2800
+ const match = new RegExp(
2801
+ String.raw`^\s{2}${escapeForRegex(name)}:[^]*?^\s+version:\s*["']?([^"'\s]+)`,
2802
+ "m"
2803
+ ).exec((0, import_node_fs7.readFileSync)(lockPath, "utf8"));
2804
+ return match?.[1];
2805
+ } catch {
2806
+ return void 0;
2807
+ }
2808
+ }
2809
+ if (ecosystem === "pip") {
2810
+ const python = process.platform === "win32" ? "python" : "python3";
2811
+ const result = runCapture(python, ["-m", "pip", "show", name], workspaceRoot);
2812
+ if (result.status !== 0) {
2813
+ return void 0;
2814
+ }
2815
+ return /^Version:\s*(\S+)/m.exec(result.stdout)?.[1];
2816
+ }
2817
+ return void 0;
2818
+ }
2819
+
2820
+ // src/deps/semver.ts
2821
+ var UPDATE_KINDS = ["patch", "minor", "major", "non-semver"];
2822
+ var UPDATE_KIND_LABELS = {
2823
+ patch: { title: "Patch Update", blurb: "Backwards-compatible bug fixes." },
2824
+ minor: { title: "Minor Update", blurb: "New backwards-compatible features." },
2825
+ major: { title: "Major Update", blurb: "Potentially breaking API changes. Use caution." },
2826
+ "non-semver": { title: "Non-Semver", blurb: "Versions less than 1.0.0, caution." }
2827
+ };
2828
+ var RANGE_OPERATORS = [">=", "<=", "^", "~", ">", "<", "="];
2829
+ function parseVersion(raw) {
2830
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([\w.-]+))?/.exec(raw.trim());
2831
+ if (!match) {
2832
+ return void 0;
2833
+ }
2834
+ return {
2835
+ major: Number(match[1]),
2836
+ minor: Number(match[2]),
2837
+ patch: Number(match[3]),
2838
+ prerelease: match[4]
2839
+ };
2840
+ }
2841
+ function rangeOperator(spec) {
2842
+ const trimmed = spec.trim();
2843
+ return RANGE_OPERATORS.find((operator) => trimmed.startsWith(operator)) ?? "";
2844
+ }
2845
+ function specVersion(spec) {
2846
+ const bare2 = spec.trim().slice(rangeOperator(spec).length).trim();
2847
+ return /^\d+\.\d+(?:\.\d+)?(?:-[\w.-]+)?$/.test(bare2) ? bare2 : void 0;
2848
+ }
2849
+ function compareVersions(left, right) {
2850
+ const a = parseVersion(left);
2851
+ const b = parseVersion(right);
2852
+ if (!a || !b) {
2853
+ return 0;
2854
+ }
2855
+ if (a.major !== b.major) {
2856
+ return a.major - b.major;
2857
+ }
2858
+ if (a.minor !== b.minor) {
2859
+ return a.minor - b.minor;
2860
+ }
2861
+ if (a.patch !== b.patch) {
2862
+ return a.patch - b.patch;
2863
+ }
2864
+ if (a.prerelease === b.prerelease) {
2865
+ return 0;
2866
+ }
2867
+ if (!a.prerelease) {
2868
+ return 1;
2869
+ }
2870
+ if (!b.prerelease) {
2871
+ return -1;
2872
+ }
2873
+ return a.prerelease < b.prerelease ? -1 : 1;
2874
+ }
2875
+ function isNewer(candidate, current) {
2876
+ return compareVersions(candidate, current) > 0;
2877
+ }
2878
+ function classify(current, latest) {
2879
+ const from = parseVersion(current);
2880
+ const to = parseVersion(latest);
2881
+ if (!from || !to || from.major === 0) {
2882
+ return "non-semver";
2883
+ }
2884
+ if (from.major !== to.major) {
2885
+ return "major";
2886
+ }
2887
+ return from.minor === to.minor ? "patch" : "minor";
2888
+ }
2889
+ function highestSpec(specs) {
2890
+ let best;
2891
+ let bestVersion;
2892
+ for (const spec of specs) {
2893
+ const version = specVersion(spec);
2894
+ if (!version) {
2895
+ continue;
2896
+ }
2897
+ if (!bestVersion || isNewer(version, bestVersion)) {
2898
+ best = spec;
2899
+ bestVersion = version;
2900
+ }
2901
+ }
2902
+ return best;
2903
+ }
2904
+
2905
+ // src/commands/sync.ts
2906
+ function resolveEcosystems(requested) {
2907
+ if (!requested) {
2908
+ return ECOSYSTEMS;
2909
+ }
2910
+ const match = ECOSYSTEMS.find((ecosystem) => ecosystem === requested);
2911
+ if (!match) {
2912
+ throw new Error(
2913
+ `Unknown ecosystem '${requested}'. Expected one of: ${ECOSYSTEMS.join(", ")}.`
2914
+ );
2915
+ }
2916
+ return [match];
2917
+ }
2918
+ function collectDrift(workspaceRoot, ecosystems = ECOSYSTEMS) {
2919
+ const reconcilable = ecosystems.filter((ecosystem) => ecosystem !== "go");
2920
+ const inventory = collectInventory(workspaceRoot, reconcilable);
2921
+ const drift = [];
2922
+ for (const [name, allSites] of inventory) {
2923
+ const sites = allSites.filter((site) => site.section !== PEER_SECTION);
2924
+ if (sites.length < 2 || isWorkspaceLocal(workspaceRoot, sites, name)) {
2925
+ continue;
2926
+ }
2927
+ const distinct = new Set(sites.map((site) => site.spec));
2928
+ if (distinct.size < 2) {
2929
+ continue;
2930
+ }
2931
+ const installed = resolvedVersion(workspaceRoot, sites[0].ecosystem, name);
2932
+ const target = chooseTarget(sites, installed);
2933
+ if (!target) {
2934
+ continue;
2935
+ }
2936
+ const disagreeing = sites.filter((site) => site.spec !== target);
2937
+ drift.push({
2938
+ name,
2939
+ ecosystem: sites[0].ecosystem,
2940
+ target,
2941
+ reason: installed ? "resolved" : "highest",
2942
+ fixable: disagreeing.filter((site) => site.rewritable),
2943
+ blocked: disagreeing.filter((site) => !site.rewritable)
2944
+ });
2945
+ }
2946
+ return drift.toSorted((left, right) => left.name.localeCompare(right.name));
2947
+ }
2948
+ function isWorkspaceLocal(workspaceRoot, sites, name) {
2949
+ return sites[0].ecosystem === "npm" && isWorkspaceProject(workspaceRoot, name);
2950
+ }
2951
+ function chooseTarget(sites, installed) {
2952
+ if (!installed) {
2953
+ return highestSpec(sites.map((site) => site.spec));
2954
+ }
2955
+ const counts = /* @__PURE__ */ new Map();
2956
+ for (const site of sites) {
2957
+ if (!specVersion(site.spec)) {
2958
+ continue;
2959
+ }
2960
+ const operator2 = rangeOperator(site.spec);
2961
+ counts.set(operator2, (counts.get(operator2) ?? 0) + 1);
2962
+ }
2963
+ if (counts.size === 0) {
2964
+ return void 0;
2965
+ }
2966
+ const [operator] = [...counts].toSorted(
2967
+ (left, right) => right[1] - left[1] || (left[0] === "^" ? -1 : 1)
2968
+ )[0];
2969
+ return `${operator}${installed}`;
2970
+ }
2971
+ function applyDrift(drift) {
2972
+ const changed = /* @__PURE__ */ new Set();
2973
+ for (const finding of drift) {
2974
+ for (const site of finding.fixable) {
2975
+ if (rewriteSpec(site, finding.target)) {
2976
+ changed.add(site.manifestPath);
2977
+ }
2978
+ }
2979
+ }
2980
+ return [...changed];
2981
+ }
2982
+ function syncProjectReferences(workspaceRoot) {
2983
+ logger.step("Syncing TypeScript project references (nx sync)");
2984
+ if (runShell("npx", ["nx", "sync"], workspaceRoot) !== 0) {
2985
+ logger.warn(
2986
+ "nx sync did not complete \u2014 run `npx nx sync` yourself so cross-project imports resolve in your editor."
2987
+ );
2988
+ }
2989
+ }
2990
+ function describe(finding) {
2991
+ const sites = [...finding.fixable, ...finding.blocked].map((site) => `${site.project === ROOT_LABEL ? ROOT_LABEL : site.project} ${site.spec}`).join(", ");
2992
+ return `${finding.name} \u2192 ${finding.target} (${finding.reason}); disagreeing: ${sites}`;
2993
+ }
2994
+ function runSync(workspaceRoot, options) {
2995
+ if (!fileExists((0, import_node_path12.join)(workspaceRoot, "nx.json"))) {
2996
+ throw new Error(
2997
+ `No nx.json found in ${workspaceRoot} \u2014 run 'mnci sync' from the workspace root.`
2998
+ );
2999
+ }
3000
+ const ecosystems = resolveEcosystems(options.ecosystem);
3001
+ for (const ecosystem of ecosystems) {
3002
+ if (!hasEcosystem(workspaceRoot, ecosystem)) {
3003
+ logger.info(`\u2298 SKIPPED ${ecosystem} \u2014 no ${ecosystem} project in this workspace.`);
3004
+ }
3005
+ }
3006
+ if (ecosystems.includes("go") && hasEcosystem(workspaceRoot, "go")) {
3007
+ logger.success("go \u2014 nothing to sync; one root go.mod means one version of every module.");
3008
+ }
3009
+ const drift = collectDrift(workspaceRoot, ecosystems);
3010
+ if (drift.length === 0) {
3011
+ logger.success("Every project agrees on every shared dependency version.");
3012
+ } else {
3013
+ for (const finding of drift) {
3014
+ logger.info(` ${describe(finding)}`);
3015
+ }
3016
+ }
3017
+ if (options.check) {
3018
+ if (drift.length > 0) {
3019
+ logger.error(
3020
+ `${drift.length} package(s) declared at more than one version. Run 'mnci sync' to converge them.`
3021
+ );
3022
+ process.exitCode = 1;
3023
+ }
3024
+ return;
3025
+ }
3026
+ const changed = applyDrift(drift);
3027
+ for (const path of changed) {
3028
+ logger.detail(`updated ${path}`);
3029
+ }
3030
+ const blocked = drift.flatMap((finding) => finding.blocked);
3031
+ for (const site of blocked) {
3032
+ logger.warn(
3033
+ `${site.name} in ${site.project} is declared as '${site.spec}', which is not a plain version range \u2014 left unchanged.`
3034
+ );
3035
+ }
3036
+ syncProjectReferences(workspaceRoot);
3037
+ if (changed.length > 0) {
3038
+ logger.step("Formatting the changed manifests (eslint --fix)");
3039
+ runFormatter(workspaceRoot);
3040
+ }
3041
+ logger.success("Done. Review the changes with `git diff` before committing.");
3042
+ }
3043
+
2336
3044
  // src/commands/add.ts
2337
3045
  var PROJECT_KINDS = [
2338
3046
  "react-app",
@@ -2357,7 +3065,7 @@ var PROJECT_KINDS = [
2357
3065
  ];
2358
3066
  async function runAdd(kind, name, options) {
2359
3067
  const workspaceRoot = process.cwd();
2360
- if (!fileExists((0, import_node_path11.join)(workspaceRoot, "nx.json"))) {
3068
+ if (!fileExists((0, import_node_path13.join)(workspaceRoot, "nx.json"))) {
2361
3069
  throw new Error("No nx.json found here. Run `add` from the workspace root.");
2362
3070
  }
2363
3071
  const stack = readWorkspaceStack(workspaceRoot);
@@ -2406,7 +3114,7 @@ async function runAdd(kind, name, options) {
2406
3114
  ],
2407
3115
  workspaceRoot
2408
3116
  );
2409
- markPrivate((0, import_node_path11.join)(workspaceRoot, "libs", resolvedName, "package.json"));
3117
+ markPrivate((0, import_node_path13.join)(workspaceRoot, "libs", resolvedName, "package.json"));
2410
3118
  removeGeneratedEslintConfig(workspaceRoot, `libs/${resolvedName}`);
2411
3119
  registerProjectCommands(workspaceRoot, resolvedName, { build: true });
2412
3120
  break;
@@ -2472,17 +3180,9 @@ async function runAdd(kind, name, options) {
2472
3180
  runFormatter(workspaceRoot);
2473
3181
  logger.success(`Added ${resolvedKind} '${resolvedName}'.`);
2474
3182
  }
2475
- function syncProjectReferences(workspaceRoot) {
2476
- logger.step("Syncing TypeScript project references (nx sync)");
2477
- if (runShell("npx", ["nx", "sync"], workspaceRoot) !== 0) {
2478
- logger.warn(
2479
- "nx sync did not complete \u2014 run `npx nx sync` yourself so cross-project imports resolve in your editor."
2480
- );
2481
- }
2482
- }
2483
3183
  function readWorkspaceStack(workspaceRoot) {
2484
3184
  const nxJson = readJson(
2485
- (0, import_node_path11.join)(workspaceRoot, "nx.json")
3185
+ (0, import_node_path13.join)(workspaceRoot, "nx.json")
2486
3186
  );
2487
3187
  const stack = nxJson.mnci?.stack;
2488
3188
  return {
@@ -2491,17 +3191,17 @@ function readWorkspaceStack(workspaceRoot) {
2491
3191
  }
2492
3192
 
2493
3193
  // src/commands/doctor.ts
2494
- var import_node_fs7 = require("fs");
2495
- var import_node_path12 = require("path");
3194
+ var import_node_fs8 = require("fs");
3195
+ var import_node_path14 = require("path");
2496
3196
  var SUPPORTED_ESLINT_MAJOR = ESLINT_VERSION.replace(/^\D*/, "").split(".", 1)[0];
2497
- function toPosix(path) {
3197
+ function toPosix2(path) {
2498
3198
  return path.replaceAll("\\", "/");
2499
3199
  }
2500
3200
  function checkEslintConfigs(workspaceRoot) {
2501
- const rootConfigs = (0, import_node_fs7.globSync)("eslint.config.{js,mjs,cjs,ts,mts,cts}", { cwd: workspaceRoot });
2502
- const projectConfigs = (0, import_node_fs7.globSync)("{apps,libs,packages}/*/eslint.config.{js,mjs,cjs,ts,mts,cts}", {
3201
+ const rootConfigs = (0, import_node_fs8.globSync)("eslint.config.{js,mjs,cjs,ts,mts,cts}", { cwd: workspaceRoot });
3202
+ const projectConfigs = (0, import_node_fs8.globSync)("{apps,libs,packages}/*/eslint.config.{js,mjs,cjs,ts,mts,cts}", {
2503
3203
  cwd: workspaceRoot
2504
- }).map((config) => toPosix(config));
3204
+ }).map((config) => toPosix2(config));
2505
3205
  return [
2506
3206
  {
2507
3207
  check: "root ESLint config",
@@ -2520,7 +3220,7 @@ function checkEslintConfigs(workspaceRoot) {
2520
3220
  function declaredDevDependencies(workspaceRoot) {
2521
3221
  try {
2522
3222
  const manifest = readJson(
2523
- (0, import_node_path12.join)(workspaceRoot, "package.json")
3223
+ (0, import_node_path14.join)(workspaceRoot, "package.json")
2524
3224
  );
2525
3225
  return manifest.devDependencies ?? {};
2526
3226
  } catch {
@@ -2528,7 +3228,7 @@ function declaredDevDependencies(workspaceRoot) {
2528
3228
  }
2529
3229
  }
2530
3230
  function checkNoRetiredFormatter(workspaceRoot) {
2531
- const files = RETIRED_FORMATTER_FILES.filter((file) => fileExists((0, import_node_path12.join)(workspaceRoot, file)));
3231
+ const files = RETIRED_FORMATTER_FILES.filter((file) => fileExists((0, import_node_path14.join)(workspaceRoot, file)));
2532
3232
  const declared = ["prettier", "eslint-config-prettier", "oxlint", "oxfmt", "@mnci/oxlint-config"].filter((name) => declaredDevDependencies(workspaceRoot)[name] !== void 0);
2533
3233
  const stale = [...files, ...declared];
2534
3234
  if (stale.length === 0) {
@@ -2554,7 +3254,7 @@ function checkEslintPlugin(nxJson) {
2554
3254
  };
2555
3255
  }
2556
3256
  function checkResolvedEslint(workspaceRoot) {
2557
- const manifestPath = (0, import_node_path12.join)(workspaceRoot, "node_modules/eslint/package.json");
3257
+ const manifestPath = (0, import_node_path14.join)(workspaceRoot, "node_modules/eslint/package.json");
2558
3258
  if (!fileExists(manifestPath)) {
2559
3259
  return void 0;
2560
3260
  }
@@ -2572,11 +3272,11 @@ function checkResolvedEslint(workspaceRoot) {
2572
3272
  }
2573
3273
  }
2574
3274
  function checkNpmrc(workspaceRoot, registry, scope) {
2575
- const npmrcPath = (0, import_node_path12.join)(workspaceRoot, ".npmrc");
3275
+ const npmrcPath = (0, import_node_path14.join)(workspaceRoot, ".npmrc");
2576
3276
  if (!registry || !fileExists(npmrcPath)) {
2577
3277
  return void 0;
2578
3278
  }
2579
- const npmrc = (0, import_node_fs7.readFileSync)(npmrcPath, "utf8");
3279
+ const npmrc = (0, import_node_fs8.readFileSync)(npmrcPath, "utf8");
2580
3280
  if (registry.kind === "npm") {
2581
3281
  return {
2582
3282
  check: ".npmrc authenticates the public registry",
@@ -2594,12 +3294,12 @@ function checkNpmrc(workspaceRoot, registry, scope) {
2594
3294
  }
2595
3295
  function checkVersionActions(workspaceRoot) {
2596
3296
  const candidates = [
2597
- ...(0, import_node_fs7.globSync)("packages/*/pubspec.yaml", { cwd: workspaceRoot }),
2598
- ...(0, import_node_fs7.globSync)("python-packages/*/pyproject.toml", { cwd: workspaceRoot })
2599
- ].map((manifest) => toPosix(manifest));
3297
+ ...(0, import_node_fs8.globSync)("packages/*/pubspec.yaml", { cwd: workspaceRoot }),
3298
+ ...(0, import_node_fs8.globSync)("python-packages/*/pyproject.toml", { cwd: workspaceRoot })
3299
+ ].map((manifest) => toPosix2(manifest));
2600
3300
  return candidates.flatMap((manifest) => {
2601
3301
  const projectRoot = manifest.slice(0, manifest.lastIndexOf("/"));
2602
- const projectJsonPath = (0, import_node_path12.join)(workspaceRoot, projectRoot, "project.json");
3302
+ const projectJsonPath = (0, import_node_path14.join)(workspaceRoot, projectRoot, "project.json");
2603
3303
  let hasOverride = false;
2604
3304
  try {
2605
3305
  const projectJson = readJson(projectJsonPath);
@@ -2628,7 +3328,7 @@ function declaredTargets(workspaceRoot, projectRoot) {
2628
3328
  ]) {
2629
3329
  try {
2630
3330
  const targets = extract(
2631
- readJson((0, import_node_path12.join)(workspaceRoot, projectRoot, file))
3331
+ readJson((0, import_node_path14.join)(workspaceRoot, projectRoot, file))
2632
3332
  );
2633
3333
  if (targets && typeof targets === "object") {
2634
3334
  sources.push({
@@ -2644,9 +3344,9 @@ function declaredTargets(workspaceRoot, projectRoot) {
2644
3344
  function checkTargetFilesExist(workspaceRoot) {
2645
3345
  const projectRoots = new Set(
2646
3346
  [
2647
- ...(0, import_node_fs7.globSync)("{apps,libs,packages}/*/project.json", { cwd: workspaceRoot }),
2648
- ...(0, import_node_fs7.globSync)("{apps,libs,packages}/*/package.json", { cwd: workspaceRoot })
2649
- ].map((manifest) => toPosix(manifest).split("/").slice(0, 2).join("/"))
3347
+ ...(0, import_node_fs8.globSync)("{apps,libs,packages}/*/project.json", { cwd: workspaceRoot }),
3348
+ ...(0, import_node_fs8.globSync)("{apps,libs,packages}/*/package.json", { cwd: workspaceRoot })
3349
+ ].map((manifest) => toPosix2(manifest).split("/").slice(0, 2).join("/"))
2650
3350
  );
2651
3351
  return [...projectRoots].flatMap(
2652
3352
  (projectRoot) => declaredTargets(workspaceRoot, projectRoot).flatMap(
@@ -2660,7 +3360,7 @@ function checkOneTargetsFiles(workspaceRoot, source, targetName, target) {
2660
3360
  return TARGET_FILE_OPTIONS.flatMap((option) => {
2661
3361
  const value = target?.options?.[option];
2662
3362
  const checkable = typeof value === "string" && !value.includes("{");
2663
- if (!checkable || fileExists((0, import_node_path12.join)(workspaceRoot, value))) {
3363
+ if (!checkable || fileExists((0, import_node_path14.join)(workspaceRoot, value))) {
2664
3364
  return [];
2665
3365
  }
2666
3366
  return [
@@ -2681,8 +3381,49 @@ function checkSync(workspaceRoot) {
2681
3381
  remedy: "run `npx nx sync` and commit the result"
2682
3382
  };
2683
3383
  }
3384
+ function checkNoRootRuntimeDependencies(workspaceRoot) {
3385
+ const manifestPath = (0, import_node_path14.join)(workspaceRoot, "package.json");
3386
+ if (!fileExists(manifestPath)) {
3387
+ return void 0;
3388
+ }
3389
+ let declared;
3390
+ try {
3391
+ declared = Object.keys(
3392
+ readJson(manifestPath).dependencies ?? {}
3393
+ );
3394
+ } catch {
3395
+ return void 0;
3396
+ }
3397
+ return {
3398
+ check: "no runtime dependencies in the root manifest",
3399
+ ok: declared.length === 0,
3400
+ detail: `the root package.json declares ${declared.join(", ")} \u2014 the root is private and never published, and @nx/rollup externalises only what a project's own manifest declares, so a package importing one of these ships an inlined private copy instead`,
3401
+ remedy: `move ${declared.join(", ")} into the dependencies of each package that imports it (devDependencies at the root are fine \u2014 that is what the root is for)`
3402
+ };
3403
+ }
3404
+ function checkRollupSourceMaps(workspaceRoot) {
3405
+ const configs = (0, import_node_fs8.globSync)(["packages/*/rollup.config.cjs", "libs/*/rollup.config.cjs"], {
3406
+ cwd: workspaceRoot
3407
+ }).map((config) => toPosix2(config));
3408
+ return configs.flatMap((relativePath) => {
3409
+ let config;
3410
+ try {
3411
+ config = (0, import_node_fs8.readFileSync)((0, import_node_path14.join)(workspaceRoot, relativePath), "utf8");
3412
+ } catch {
3413
+ return [];
3414
+ }
3415
+ return [
3416
+ {
3417
+ check: `source maps enabled in ${relativePath}`,
3418
+ ok: hasRollupSourceMaps(config),
3419
+ detail: "rollup emits no .js.map without it, so a breakpoint in a .ts file can never bind",
3420
+ remedy: "run `mnci upgrade`, which adds it to every rollup config"
3421
+ }
3422
+ ];
3423
+ });
3424
+ }
2684
3425
  function collectFindings(workspaceRoot) {
2685
- const nxJsonPath = (0, import_node_path12.join)(workspaceRoot, "nx.json");
3426
+ const nxJsonPath = (0, import_node_path14.join)(workspaceRoot, "nx.json");
2686
3427
  if (!fileExists(nxJsonPath)) {
2687
3428
  throw new Error(
2688
3429
  `No nx.json found in ${workspaceRoot} \u2014 run 'mnci doctor' from the workspace root.`
@@ -2695,6 +3436,8 @@ function collectFindings(workspaceRoot) {
2695
3436
  checkEslintPlugin(nxJson),
2696
3437
  checkResolvedEslint(workspaceRoot),
2697
3438
  checkNpmrc(workspaceRoot, nxJson.mnci?.registry, nxJson.mnci?.scope),
3439
+ checkNoRootRuntimeDependencies(workspaceRoot),
3440
+ ...checkRollupSourceMaps(workspaceRoot),
2698
3441
  ...checkVersionActions(workspaceRoot),
2699
3442
  ...checkTargetFilesExist(workspaceRoot),
2700
3443
  checkSync(workspaceRoot)
@@ -2722,12 +3465,12 @@ function runDoctor(workspaceRoot) {
2722
3465
  }
2723
3466
 
2724
3467
  // src/commands/interactive.ts
2725
- var import_node_path15 = require("path");
3468
+ var import_node_path17 = require("path");
2726
3469
  var import_prompts8 = require("@inquirer/prompts");
2727
3470
 
2728
3471
  // src/commands/new.ts
2729
- var import_node_fs8 = require("fs");
2730
- var import_node_path13 = require("path");
3472
+ var import_node_fs9 = require("fs");
3473
+ var import_node_path15 = require("path");
2731
3474
  function nxCloudProviderValue(ci) {
2732
3475
  return ci === "azure" ? "azure" : "github";
2733
3476
  }
@@ -2788,7 +3531,7 @@ async function runNew(name, options) {
2788
3531
  ],
2789
3532
  process.cwd()
2790
3533
  );
2791
- const workspaceRoot = (0, import_node_path13.join)(process.cwd(), workspaceName);
3534
+ const workspaceRoot = (0, import_node_path15.join)(process.cwd(), workspaceName);
2792
3535
  logger.step(
2793
3536
  "Applying MoNecromanCI overlay (VS Code workspace, release config, .npmrc, commitlint, pipeline, stack)"
2794
3537
  );
@@ -2797,8 +3540,8 @@ async function runNew(name, options) {
2797
3540
  { workspaceName, scope, registry, agent, variableGroup, ci, stack },
2798
3541
  logger.detail
2799
3542
  );
2800
- (0, import_node_fs8.rmSync)((0, import_node_path13.join)(workspaceRoot, "node_modules"), { recursive: true, force: true });
2801
- (0, import_node_fs8.rmSync)((0, import_node_path13.join)(workspaceRoot, "package-lock.json"), { force: true });
3543
+ (0, import_node_fs9.rmSync)((0, import_node_path15.join)(workspaceRoot, "node_modules"), { recursive: true, force: true });
3544
+ (0, import_node_fs9.rmSync)((0, import_node_path15.join)(workspaceRoot, "package-lock.json"), { force: true });
2802
3545
  logger.step("Installing the toolchain (commitlint + husky + linting/formatting)");
2803
3546
  const installStatus = runShell(
2804
3547
  "npm",
@@ -2828,8 +3571,8 @@ async function runNew(name, options) {
2828
3571
  }
2829
3572
 
2830
3573
  // src/commands/upgrade.ts
2831
- var import_node_fs9 = require("fs");
2832
- var import_node_path14 = require("path");
3574
+ var import_node_fs10 = require("fs");
3575
+ var import_node_path16 = require("path");
2833
3576
  function resolveRegistry2(options, persisted) {
2834
3577
  if (options.registry === "azure-artifacts" || options.organization && options.artifactsFeed) {
2835
3578
  const persistedAzure = persisted?.kind === "azure-artifacts" ? persisted : void 0;
@@ -2858,7 +3601,7 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
2858
3601
  return persisted.workspaceName;
2859
3602
  }
2860
3603
  try {
2861
- const existing = (0, import_node_fs9.readdirSync)(workspaceRoot).find(
3604
+ const existing = (0, import_node_fs10.readdirSync)(workspaceRoot).find(
2862
3605
  (file) => file.endsWith(".code-workspace") && file !== "undefined.code-workspace"
2863
3606
  );
2864
3607
  if (existing) {
@@ -2866,7 +3609,7 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
2866
3609
  }
2867
3610
  } catch {
2868
3611
  }
2869
- return (0, import_node_path14.basename)(workspaceRoot);
3612
+ return (0, import_node_path16.basename)(workspaceRoot);
2870
3613
  }
2871
3614
  function resolveOverlayOptions(workspaceRoot, options, persisted) {
2872
3615
  const scope = options.scope ?? persisted.scope;
@@ -2904,7 +3647,7 @@ function resolveOverlayOptions(workspaceRoot, options, persisted) {
2904
3647
  };
2905
3648
  }
2906
3649
  function runUpgrade(workspaceRoot, options) {
2907
- if (!fileExists((0, import_node_path14.join)(workspaceRoot, "nx.json"))) {
3650
+ if (!fileExists((0, import_node_path16.join)(workspaceRoot, "nx.json"))) {
2908
3651
  throw new Error(
2909
3652
  `No nx.json found in ${workspaceRoot} \u2014 this does not look like an Nx workspace. Run 'mnci upgrade' from the workspace root.`
2910
3653
  );
@@ -2913,6 +3656,13 @@ function runUpgrade(workspaceRoot, options) {
2913
3656
  const resolved = resolveOverlayOptions(workspaceRoot, options, persisted);
2914
3657
  logger.step("Re-applying the MoNecromanCI overlay");
2915
3658
  applyOverlay(workspaceRoot, resolved, logger.detail);
3659
+ const repaired = repairRollupSourceMaps(workspaceRoot);
3660
+ if (repaired.length > 0) {
3661
+ logger.step("Enabling source maps in rollup configs (breakpoints in .ts files)");
3662
+ for (const path of repaired) {
3663
+ logger.detail(`updated ${path}`);
3664
+ }
3665
+ }
2916
3666
  logger.step(
2917
3667
  "Formatting the workspace (eslint --fix) \u2014 the slowest step, minutes on a large workspace"
2918
3668
  );
@@ -2922,7 +3672,7 @@ function runUpgrade(workspaceRoot, options) {
2922
3672
 
2923
3673
  // src/commands/interactive.ts
2924
3674
  async function runInteractive() {
2925
- const inWorkspace = fileExists((0, import_node_path15.join)(process.cwd(), "nx.json"));
3675
+ const inWorkspace = fileExists((0, import_node_path17.join)(process.cwd(), "nx.json"));
2926
3676
  const choiceNew = { name: "Create a new monorepo", value: "new" };
2927
3677
  const choiceAdd = { name: "Add a project to this workspace", value: "add" };
2928
3678
  const choiceUpgrade = {
@@ -2942,27 +3692,345 @@ async function runInteractive() {
2942
3692
  }
2943
3693
  }
2944
3694
 
3695
+ // src/commands/up.ts
3696
+ var import_node_path18 = require("path");
3697
+ var import_prompts9 = require("@inquirer/prompts");
3698
+
3699
+ // src/deps/registry.ts
3700
+ var LOOKUP_CONCURRENCY = 8;
3701
+ async function latestNpmVersions(names, cwd) {
3702
+ const results = await pool(names, LOOKUP_CONCURRENCY, async (name) => {
3703
+ const result = await runCaptureAsync("npm", ["view", name, "version", "--json"], cwd);
3704
+ if (result.status !== 0) {
3705
+ return;
3706
+ }
3707
+ try {
3708
+ const parsed = JSON.parse(result.stdout);
3709
+ const version = Array.isArray(parsed) ? parsed.at(-1) : parsed;
3710
+ return typeof version === "string" ? [name, version] : void 0;
3711
+ } catch {
3712
+ return;
3713
+ }
3714
+ });
3715
+ return new Map(results.filter((entry) => entry !== void 0));
3716
+ }
3717
+ async function latestPipVersions(names, cwd) {
3718
+ const python = process.platform === "win32" ? "python" : "python3";
3719
+ const results = await pool(names, LOOKUP_CONCURRENCY, async (name) => {
3720
+ const result = await runCaptureAsync(python, ["-m", "pip", "index", "versions", name], cwd);
3721
+ if (result.status !== 0) {
3722
+ return;
3723
+ }
3724
+ const match = /\(([^)]+)\)/.exec(result.stdout);
3725
+ return match ? [name, match[1]] : void 0;
3726
+ });
3727
+ return new Map(results.filter((entry) => entry !== void 0));
3728
+ }
3729
+ async function latestGoVersions(cwd) {
3730
+ const result = await runCaptureAsync("go", ["list", "-m", "-u", "-json", "all"], cwd);
3731
+ if (result.status !== 0) {
3732
+ return /* @__PURE__ */ new Map();
3733
+ }
3734
+ const latest = /* @__PURE__ */ new Map();
3735
+ for (const chunk of result.stdout.split(/^\}\s*$/m)) {
3736
+ const text = `${chunk.trim()}${chunk.trim() ? "}" : ""}`;
3737
+ if (!text.startsWith("{")) {
3738
+ continue;
3739
+ }
3740
+ try {
3741
+ const module_ = JSON.parse(text);
3742
+ if (module_.Path && module_.Update?.Version) {
3743
+ latest.set(module_.Path, module_.Update.Version);
3744
+ }
3745
+ } catch {
3746
+ }
3747
+ }
3748
+ return latest;
3749
+ }
3750
+ async function latestPubVersions(cwd) {
3751
+ const result = await runCaptureAsync("flutter", ["pub", "outdated", "--json"], cwd);
3752
+ if (result.status !== 0) {
3753
+ return /* @__PURE__ */ new Map();
3754
+ }
3755
+ try {
3756
+ const report = JSON.parse(result.stdout);
3757
+ const latest = /* @__PURE__ */ new Map();
3758
+ const packages = report.packages ?? [];
3759
+ for (const entry of packages) {
3760
+ if (entry.package && entry.latest?.version) {
3761
+ latest.set(entry.package, entry.latest.version);
3762
+ }
3763
+ }
3764
+ return latest;
3765
+ } catch {
3766
+ return /* @__PURE__ */ new Map();
3767
+ }
3768
+ }
3769
+ async function latestVersions(ecosystem, names, cwd) {
3770
+ switch (ecosystem) {
3771
+ case "npm": {
3772
+ return await latestNpmVersions(names, cwd);
3773
+ }
3774
+ case "pip": {
3775
+ return await latestPipVersions(names, cwd);
3776
+ }
3777
+ case "go": {
3778
+ return await latestGoVersions(cwd);
3779
+ }
3780
+ default: {
3781
+ return await latestPubVersions(cwd);
3782
+ }
3783
+ }
3784
+ }
3785
+
3786
+ // src/commands/up.ts
3787
+ function currentVersion(workspaceRoot, sites) {
3788
+ const installed = resolvedVersion(workspaceRoot, sites[0].ecosystem, sites[0].name);
3789
+ if (installed) {
3790
+ return installed;
3791
+ }
3792
+ if (sites[0].ecosystem === "go") {
3793
+ return bare(sites[0].spec);
3794
+ }
3795
+ const highest = highestSpec(sites.map((site) => site.spec));
3796
+ return highest ? specVersion(highest) : void 0;
3797
+ }
3798
+ async function collectOutdated(workspaceRoot, ecosystems) {
3799
+ const present = ecosystems.filter((ecosystem) => hasEcosystem(workspaceRoot, ecosystem));
3800
+ const inventory = collectInventory(workspaceRoot, present);
3801
+ const outdated = [];
3802
+ for (const ecosystem of present) {
3803
+ const forEcosystem = [...inventory].filter(
3804
+ ([name, sites]) => offerable(workspaceRoot, ecosystem, name, sites)
3805
+ );
3806
+ const latest = await latestVersions(
3807
+ ecosystem,
3808
+ forEcosystem.map(([name]) => name),
3809
+ workspaceRoot
3810
+ );
3811
+ outdated.push(...upgradable(workspaceRoot, ecosystem, forEcosystem, latest));
3812
+ }
3813
+ return outdated.toSorted(
3814
+ (left, right) => UPDATE_KINDS.indexOf(left.kind) - UPDATE_KINDS.indexOf(right.kind) || left.name.localeCompare(right.name)
3815
+ );
3816
+ }
3817
+ function offerable(workspaceRoot, ecosystem, name, sites) {
3818
+ if (sites[0].ecosystem !== ecosystem || sites[0].section === "indirect") {
3819
+ return false;
3820
+ }
3821
+ if (sites.every((site) => site.section === PEER_SECTION)) {
3822
+ return false;
3823
+ }
3824
+ if (ecosystem !== "npm") {
3825
+ return true;
3826
+ }
3827
+ return !isWorkspaceProject(workspaceRoot, name) && !isAliasedInstall(workspaceRoot, name);
3828
+ }
3829
+ function bare(version) {
3830
+ return version.replace(/^v/, "");
3831
+ }
3832
+ function upgradable(workspaceRoot, ecosystem, declared, latest) {
3833
+ const outdated = [];
3834
+ for (const [name, sites] of declared) {
3835
+ const newest = latest.get(name);
3836
+ const current = currentVersion(workspaceRoot, sites);
3837
+ if (!newest || !current || !isNewer(bare(newest), bare(current))) {
3838
+ continue;
3839
+ }
3840
+ outdated.push({
3841
+ name,
3842
+ ecosystem,
3843
+ current,
3844
+ latest: newest,
3845
+ kind: classify(bare(current), bare(newest)),
3846
+ sites
3847
+ });
3848
+ }
3849
+ return outdated;
3850
+ }
3851
+ function row(entry, widths) {
3852
+ const sections = [...new Set(entry.sites.map((site) => site.section))].join("/");
3853
+ const projects = [...new Set(entry.sites.map((site) => site.project))].join(", ");
3854
+ return [
3855
+ entry.name.padEnd(widths.name),
3856
+ sections.padEnd(widths.section),
3857
+ `${entry.current.padStart(widths.current)} \u203A ${entry.latest}`,
3858
+ projects
3859
+ ].join(" ");
3860
+ }
3861
+ function columnWidths(entries) {
3862
+ return {
3863
+ name: widthOf(entries.map((entry) => entry.name)),
3864
+ section: widthOf(
3865
+ entries.map((entry) => [...new Set(entry.sites.map((site) => site.section))].join("/"))
3866
+ ),
3867
+ current: widthOf(entries.map((entry) => entry.current))
3868
+ };
3869
+ }
3870
+ function widthOf(values) {
3871
+ return Math.max(0, ...values.map((value) => value.length));
3872
+ }
3873
+ function reportOutdated(outdated) {
3874
+ const widths = columnWidths(outdated);
3875
+ for (const kind of UPDATE_KINDS) {
3876
+ const section = outdated.filter((entry) => entry.kind === kind);
3877
+ if (section.length === 0) {
3878
+ continue;
3879
+ }
3880
+ const { title, blurb } = UPDATE_KIND_LABELS[kind];
3881
+ logger.info("");
3882
+ logger.info(`${title} ${blurb}`);
3883
+ for (const entry of section) {
3884
+ logger.info(` ${row(entry, widths)}`);
3885
+ }
3886
+ }
3887
+ }
3888
+ async function promptForUpdates(outdated) {
3889
+ const widths = columnWidths(outdated);
3890
+ const choices = [];
3891
+ for (const kind of UPDATE_KINDS) {
3892
+ const section = outdated.filter((entry) => entry.kind === kind);
3893
+ if (section.length === 0) {
3894
+ continue;
3895
+ }
3896
+ const { title, blurb } = UPDATE_KIND_LABELS[kind];
3897
+ choices.push(new import_prompts9.Separator(`
3898
+ ${title} ${blurb}`));
3899
+ for (const entry of section) {
3900
+ choices.push({ name: row(entry, widths), value: entry });
3901
+ }
3902
+ }
3903
+ return await (0, import_prompts9.checkbox)({
3904
+ message: "Choose which packages to update.",
3905
+ choices,
3906
+ pageSize: 20
3907
+ });
3908
+ }
3909
+ function applyUpdate(entry) {
3910
+ const changed = [];
3911
+ for (const site of entry.sites) {
3912
+ if (!site.rewritable) {
3913
+ continue;
3914
+ }
3915
+ const spec = `${rangeOperator(site.spec)}${entry.latest}`;
3916
+ if (rewriteSpec(site, spec)) {
3917
+ changed.push(site.manifestPath);
3918
+ }
3919
+ }
3920
+ return changed;
3921
+ }
3922
+ function reinstall(workspaceRoot, ecosystems) {
3923
+ const commands = [
3924
+ ["npm", "npm", ["install"]],
3925
+ ["pip", "npm", ["run", "python:install"]],
3926
+ ["pub", "flutter", ["pub", "get"]],
3927
+ ["go", "go", ["mod", "tidy"]]
3928
+ ];
3929
+ for (const [ecosystem, command, arguments_] of commands) {
3930
+ if (!ecosystems.has(ecosystem)) {
3931
+ continue;
3932
+ }
3933
+ if (ecosystem === "pip" && !hasPythonInstallScript(workspaceRoot)) {
3934
+ logger.warn(
3935
+ "This workspace has no 'python:install' script \u2014 run 'mnci upgrade' to add it, then reinstall by hand."
3936
+ );
3937
+ continue;
3938
+ }
3939
+ logger.step(`Reinstalling ${ecosystem} dependencies (${command} ${arguments_.join(" ")})`);
3940
+ if (runShell(command, arguments_, workspaceRoot) !== 0) {
3941
+ logger.warn(
3942
+ `${command} ${arguments_.join(" ")} failed \u2014 the manifests were updated, so re-run it once the cause is fixed.`
3943
+ );
3944
+ }
3945
+ }
3946
+ }
3947
+ function hasPythonInstallScript(workspaceRoot) {
3948
+ try {
3949
+ const manifest = readJson(
3950
+ (0, import_node_path18.join)(workspaceRoot, "package.json")
3951
+ );
3952
+ return manifest.scripts?.["python:install"] !== void 0;
3953
+ } catch {
3954
+ return false;
3955
+ }
3956
+ }
3957
+ function applyGoUpdates(workspaceRoot, entries) {
3958
+ for (const entry of entries) {
3959
+ logger.step(`go get ${entry.name}@${entry.latest}`);
3960
+ if (runShell("go", ["get", `${entry.name}@${entry.latest}`], workspaceRoot) !== 0) {
3961
+ logger.warn(`go get ${entry.name}@${entry.latest} failed \u2014 left at ${entry.current}.`);
3962
+ }
3963
+ }
3964
+ }
3965
+ async function runUp(workspaceRoot, options) {
3966
+ if (!fileExists((0, import_node_path18.join)(workspaceRoot, "nx.json"))) {
3967
+ throw new Error(`No nx.json found in ${workspaceRoot} \u2014 run 'mnci up' from the workspace root.`);
3968
+ }
3969
+ const ecosystems = resolveEcosystems(options.ecosystem);
3970
+ for (const ecosystem of ecosystems) {
3971
+ if (!hasEcosystem(workspaceRoot, ecosystem)) {
3972
+ logger.info(`\u2298 SKIPPED ${ecosystem} \u2014 no ${ecosystem} project in this workspace.`);
3973
+ }
3974
+ }
3975
+ logger.step("Querying registries for the latest published versions");
3976
+ const outdated = await collectOutdated(workspaceRoot, ecosystems);
3977
+ if (outdated.length === 0) {
3978
+ logger.success("Every dependency is on its latest published version.");
3979
+ return;
3980
+ }
3981
+ const readOnly = options.check ?? !process.stdout.isTTY;
3982
+ if (readOnly) {
3983
+ reportOutdated(outdated);
3984
+ logger.info("");
3985
+ logger.info(`${outdated.length} package(s) have a newer release. Run 'mnci up' to update them.`);
3986
+ return;
3987
+ }
3988
+ const selected = options.yes ? [...outdated] : await promptForUpdates(outdated);
3989
+ if (selected.length === 0) {
3990
+ logger.info("Nothing selected; no manifest was changed.");
3991
+ return;
3992
+ }
3993
+ const goSelections = selected.filter((entry) => entry.ecosystem === "go");
3994
+ const changed = selected.filter((entry) => entry.ecosystem !== "go").flatMap((entry) => applyUpdate(entry));
3995
+ const changedManifests = new Set(changed);
3996
+ for (const path of changedManifests) {
3997
+ logger.detail(`updated ${path}`);
3998
+ }
3999
+ applyGoUpdates(workspaceRoot, goSelections);
4000
+ if (options.install !== false) {
4001
+ reinstall(workspaceRoot, new Set(selected.map((entry) => entry.ecosystem)));
4002
+ }
4003
+ if (changed.length > 0) {
4004
+ syncProjectReferences(workspaceRoot);
4005
+ logger.step("Formatting the changed manifests (eslint --fix)");
4006
+ runFormatter(workspaceRoot);
4007
+ }
4008
+ logger.success(
4009
+ `Updated ${selected.length} package(s). Review the changes with \`git diff\` before committing.`
4010
+ );
4011
+ }
4012
+
2945
4013
  // src/util/versionChecker.ts
2946
- var import_node_fs10 = require("fs");
2947
- var import_node_path16 = require("path");
4014
+ var import_node_fs11 = require("fs");
4015
+ var import_node_path19 = require("path");
2948
4016
  var import_cross_spawn2 = __toESM(require("cross-spawn"));
2949
4017
  var PACKAGE_NAME = "@mnci/cli";
2950
4018
  var REGISTRY_TIMEOUT_MS = 2e3;
2951
- function checkForUpdate(currentVersion) {
4019
+ function checkForUpdate(currentVersion2) {
2952
4020
  try {
2953
4021
  if (!process.stdout.isTTY) {
2954
4022
  return;
2955
4023
  }
2956
4024
  setImmediate(() => {
2957
4025
  try {
2958
- reportIfOutdated(currentVersion);
4026
+ reportIfOutdated(currentVersion2);
2959
4027
  } catch {
2960
4028
  }
2961
4029
  });
2962
4030
  } catch {
2963
4031
  }
2964
4032
  }
2965
- function reportIfOutdated(currentVersion) {
4033
+ function reportIfOutdated(currentVersion2) {
2966
4034
  const result = import_cross_spawn2.default.sync("npm", ["view", PACKAGE_NAME, "version"], {
2967
4035
  encoding: "utf8",
2968
4036
  timeout: REGISTRY_TIMEOUT_MS,
@@ -2972,12 +4040,12 @@ function reportIfOutdated(currentVersion) {
2972
4040
  return;
2973
4041
  }
2974
4042
  const latestVersion = (result.stdout ?? "").trim();
2975
- if (!latestVersion || !isNewerVersion(latestVersion, currentVersion)) {
4043
+ if (!latestVersion || !isNewerVersion(latestVersion, currentVersion2)) {
2976
4044
  return;
2977
4045
  }
2978
4046
  logger.info("");
2979
4047
  logger.info(`A new version of ${PACKAGE_NAME} is available: ${latestVersion}`);
2980
- logger.info(` Current version: ${currentVersion}`);
4048
+ logger.info(` Current version: ${currentVersion2}`);
2981
4049
  logger.info(` Update with: npm install -g ${PACKAGE_NAME}@latest`);
2982
4050
  logger.info("");
2983
4051
  }
@@ -3000,8 +4068,8 @@ function releaseTriple(version) {
3000
4068
  }
3001
4069
  function readCliVersion(packageDirectory) {
3002
4070
  try {
3003
- const manifestPath = (0, import_node_path16.join)(packageDirectory, "..", "package.json");
3004
- const manifest = JSON.parse((0, import_node_fs10.readFileSync)(manifestPath, "utf8"));
4071
+ const manifestPath = (0, import_node_path19.join)(packageDirectory, "..", "package.json");
4072
+ const manifest = JSON.parse((0, import_node_fs11.readFileSync)(manifestPath, "utf8"));
3005
4073
  return manifest.version ?? "0.0.0";
3006
4074
  } catch {
3007
4075
  return "0.0.0";
@@ -3031,6 +4099,16 @@ function buildProgram(cliVersion) {
3031
4099
  ).action(() => {
3032
4100
  runDoctor(process.cwd());
3033
4101
  });
4102
+ program.command("sync").description(
4103
+ "Make every project agree: converge external dependency ranges declared at more than one version, then run 'nx sync' for TypeScript project references. Go is a no-op \u2014 one root go.mod means one version of every module"
4104
+ ).option("--check", "report drift and exit non-zero without writing anything").option("--ecosystem <name>", "restrict to one ecosystem: npm | pip | pub | go").action((options) => {
4105
+ runSync(process.cwd(), options);
4106
+ });
4107
+ program.command("up").description(
4108
+ "Show every dependency with a newer published release \u2014 grouped patch/minor/major/non-semver, with the projects declaring each one \u2014 and interactively update the ones you pick"
4109
+ ).option("--check", "report only; never prompt and never write (the default when piped)").option("-y, --yes", "select every available update without prompting").option("--ecosystem <name>", "restrict to one ecosystem: npm | pip | pub | go").option("--no-install", "update the manifests but skip the reinstall step").action(async (options) => {
4110
+ await runUp(process.cwd(), options);
4111
+ });
3034
4112
  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(
3035
4113
  "--framework <framework>",
3036
4114
  "node-app only: express | fastify | koa | nest | none (default: none)"