@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/README.md +173 -1
- package/dist/cli.js +1138 -60
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
1402
|
-
|
|
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
|
-
|
|
1450
|
-
manifest.files.
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
2495
|
-
var
|
|
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
|
|
3197
|
+
function toPosix2(path) {
|
|
2498
3198
|
return path.replaceAll("\\", "/");
|
|
2499
3199
|
}
|
|
2500
3200
|
function checkEslintConfigs(workspaceRoot) {
|
|
2501
|
-
const rootConfigs = (0,
|
|
2502
|
-
const projectConfigs = (0,
|
|
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) =>
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
2598
|
-
...(0,
|
|
2599
|
-
].map((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,
|
|
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,
|
|
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,
|
|
2648
|
-
...(0,
|
|
2649
|
-
].map((manifest) =>
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
2730
|
-
var
|
|
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,
|
|
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,
|
|
2801
|
-
(0,
|
|
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
|
|
2832
|
-
var
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
2947
|
-
var
|
|
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(
|
|
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(
|
|
4026
|
+
reportIfOutdated(currentVersion2);
|
|
2959
4027
|
} catch {
|
|
2960
4028
|
}
|
|
2961
4029
|
});
|
|
2962
4030
|
} catch {
|
|
2963
4031
|
}
|
|
2964
4032
|
}
|
|
2965
|
-
function reportIfOutdated(
|
|
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,
|
|
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: ${
|
|
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,
|
|
3004
|
-
const manifest = JSON.parse((0,
|
|
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)"
|