@mnci/cli 4.0.8 → 4.1.1
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 +1184 -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
|
}
|
|
@@ -1560,6 +1669,50 @@ function registerProjectCommands(workspaceRoot, name, commands) {
|
|
|
1560
1669
|
})
|
|
1561
1670
|
);
|
|
1562
1671
|
}
|
|
1672
|
+
var PROJECT_MANIFEST_ROOTS = ["apps", "packages", "libs"];
|
|
1673
|
+
function rootRuntimeDependencies(workspaceRoot) {
|
|
1674
|
+
try {
|
|
1675
|
+
return readJson((0, import_node_path3.join)(workspaceRoot, "package.json")).dependencies ?? {};
|
|
1676
|
+
} catch {
|
|
1677
|
+
return {};
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
function relocateRootRuntimeDependencies(workspaceRoot, projectName, before) {
|
|
1681
|
+
const after = rootRuntimeDependencies(workspaceRoot);
|
|
1682
|
+
const added = Object.keys(after).filter((name) => !Object.hasOwn(before, name));
|
|
1683
|
+
if (added.length === 0) {
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
const manifestPath = PROJECT_MANIFEST_ROOTS.map(
|
|
1687
|
+
(root) => (0, import_node_path3.join)(workspaceRoot, root, projectName, "package.json")
|
|
1688
|
+
).find((candidate) => fileExists(candidate));
|
|
1689
|
+
if (manifestPath === void 0) {
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
const manifest = readJson(manifestPath);
|
|
1693
|
+
const owned = manifest.dependencies ?? {};
|
|
1694
|
+
const moved = Object.fromEntries(added.map((name) => [name, after[name]]));
|
|
1695
|
+
writeFileEnsured(manifestPath, toJson({ ...manifest, dependencies: { ...moved, ...owned } }));
|
|
1696
|
+
const remaining = Object.fromEntries(
|
|
1697
|
+
Object.entries(after).filter(([name]) => !added.includes(name))
|
|
1698
|
+
);
|
|
1699
|
+
const rootManifest = readJson((0, import_node_path3.join)(workspaceRoot, "package.json"));
|
|
1700
|
+
const { dependencies: _dropped, ...rest } = rootManifest;
|
|
1701
|
+
writeFileEnsured(
|
|
1702
|
+
(0, import_node_path3.join)(workspaceRoot, "package.json"),
|
|
1703
|
+
toJson(Object.keys(remaining).length > 0 ? { ...rest, dependencies: remaining } : rest)
|
|
1704
|
+
);
|
|
1705
|
+
logger.step(`Moved ${added.join(", ")} into ${projectName}'s own manifest`);
|
|
1706
|
+
if (runShell(
|
|
1707
|
+
"npm",
|
|
1708
|
+
["install", "--package-lock-only", "--no-audit", "--no-fund"],
|
|
1709
|
+
workspaceRoot
|
|
1710
|
+
) !== 0) {
|
|
1711
|
+
logger.warn(
|
|
1712
|
+
`Could not refresh package-lock.json after moving ${added.join(", ")}. Run 'npm install'.`
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1563
1716
|
|
|
1564
1717
|
// src/commands/add/flutter.ts
|
|
1565
1718
|
function addProjectJsonTargets(projectJsonPath, newTargets) {
|
|
@@ -2333,6 +2486,605 @@ function addReactInternalLib(workspaceRoot, name, stack) {
|
|
|
2333
2486
|
registerProjectCommands(workspaceRoot, name, { build: true });
|
|
2334
2487
|
}
|
|
2335
2488
|
|
|
2489
|
+
// src/commands/sync.ts
|
|
2490
|
+
var import_node_path12 = require("path");
|
|
2491
|
+
|
|
2492
|
+
// src/deps/inventory.ts
|
|
2493
|
+
var import_node_fs7 = require("fs");
|
|
2494
|
+
var import_node_path11 = require("path");
|
|
2495
|
+
var ECOSYSTEMS = ["npm", "pip", "pub", "go"];
|
|
2496
|
+
var ROOT_LABEL = "(root)";
|
|
2497
|
+
var MANIFEST_GLOBS = {
|
|
2498
|
+
// `libs/*` carries private internal libs; `packages/*` the publishable ones.
|
|
2499
|
+
npm: ["apps/*/package.json", "libs/*/package.json", "packages/*/package.json"],
|
|
2500
|
+
// Matches PYTHON_PROJECT_DIRS in commands/add/python.ts.
|
|
2501
|
+
pip: ["apps/*/pyproject.toml", "python-packages/*/pyproject.toml", "libs/*/pyproject.toml"],
|
|
2502
|
+
pub: ["apps/*/pubspec.yaml", "packages/*/pubspec.yaml", "libs/*/pubspec.yaml"],
|
|
2503
|
+
// Single root module by design — there are no per-project Go manifests.
|
|
2504
|
+
go: []
|
|
2505
|
+
};
|
|
2506
|
+
var PEER_SECTION = "peerDep";
|
|
2507
|
+
var NPM_SECTIONS = {
|
|
2508
|
+
dependencies: "dep",
|
|
2509
|
+
devDependencies: "devDep",
|
|
2510
|
+
peerDependencies: "peerDep",
|
|
2511
|
+
optionalDependencies: "optionalDep"
|
|
2512
|
+
};
|
|
2513
|
+
function toPosix(path) {
|
|
2514
|
+
return path.replaceAll("\\", "/");
|
|
2515
|
+
}
|
|
2516
|
+
function projectOf(manifestRelativePath) {
|
|
2517
|
+
const posix = toPosix(manifestRelativePath);
|
|
2518
|
+
const lastSlash = posix.lastIndexOf("/");
|
|
2519
|
+
return lastSlash === -1 ? ROOT_LABEL : posix.slice(0, lastSlash);
|
|
2520
|
+
}
|
|
2521
|
+
function hasEcosystem(workspaceRoot, ecosystem) {
|
|
2522
|
+
if (ecosystem === "go") {
|
|
2523
|
+
return fileExists((0, import_node_path11.join)(workspaceRoot, "go.mod"));
|
|
2524
|
+
}
|
|
2525
|
+
const rootMarker = {
|
|
2526
|
+
npm: "package.json",
|
|
2527
|
+
pip: "requirements-dev.txt",
|
|
2528
|
+
pub: "pubspec.yaml"
|
|
2529
|
+
};
|
|
2530
|
+
return fileExists((0, import_node_path11.join)(workspaceRoot, rootMarker[ecosystem])) || (0, import_node_fs7.globSync)(MANIFEST_GLOBS[ecosystem], { cwd: workspaceRoot }).length > 0;
|
|
2531
|
+
}
|
|
2532
|
+
function collectNpm(workspaceRoot) {
|
|
2533
|
+
const sites = [];
|
|
2534
|
+
const relativePaths = ["package.json", ...(0, import_node_fs7.globSync)(MANIFEST_GLOBS.npm, { cwd: workspaceRoot })];
|
|
2535
|
+
for (const relativePath of relativePaths) {
|
|
2536
|
+
const manifestPath = (0, import_node_path11.join)(workspaceRoot, relativePath);
|
|
2537
|
+
try {
|
|
2538
|
+
const manifest = readJson(manifestPath);
|
|
2539
|
+
sites.push(...npmManifestSites(manifest, relativePath, manifestPath));
|
|
2540
|
+
} catch {
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
return sites;
|
|
2544
|
+
}
|
|
2545
|
+
function npmManifestSites(manifest, relativePath, manifestPath) {
|
|
2546
|
+
const sites = [];
|
|
2547
|
+
const project = projectOf(relativePath);
|
|
2548
|
+
for (const [block, section] of Object.entries(NPM_SECTIONS)) {
|
|
2549
|
+
const declared = manifest[block] ?? {};
|
|
2550
|
+
for (const [name, spec] of Object.entries(declared)) {
|
|
2551
|
+
sites.push({
|
|
2552
|
+
name,
|
|
2553
|
+
ecosystem: "npm",
|
|
2554
|
+
project,
|
|
2555
|
+
manifestPath,
|
|
2556
|
+
section,
|
|
2557
|
+
spec,
|
|
2558
|
+
// Two independent reasons to refuse a rewrite. A `file:`/`git+`/
|
|
2559
|
+
// `https:` target, a `workspace:` protocol or npm's `npm:pkg@range`
|
|
2560
|
+
// alias is a shape this cannot safely edit; a peer range is a shape it
|
|
2561
|
+
// could edit and must not (see PEER_SECTION).
|
|
2562
|
+
rewritable: section !== PEER_SECTION && /^[\^~>=<]*\d/.test(spec.trim())
|
|
2563
|
+
});
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
return sites;
|
|
2567
|
+
}
|
|
2568
|
+
function parseRequirement(requirement) {
|
|
2569
|
+
const comment = requirement.indexOf("#");
|
|
2570
|
+
const text = (comment === -1 ? requirement : requirement.slice(0, comment)).trim();
|
|
2571
|
+
if (!text || text.startsWith("-")) {
|
|
2572
|
+
return void 0;
|
|
2573
|
+
}
|
|
2574
|
+
const name = /^[a-z0-9][\w.-]*/i.exec(text)?.[0];
|
|
2575
|
+
if (!name) {
|
|
2576
|
+
return void 0;
|
|
2577
|
+
}
|
|
2578
|
+
const spec = text.slice(name.length).trim();
|
|
2579
|
+
return {
|
|
2580
|
+
name,
|
|
2581
|
+
spec,
|
|
2582
|
+
// Extras, environment markers and compound ranges are reportable only.
|
|
2583
|
+
rewritable: !spec.startsWith("[") && !spec.includes(";") && !spec.includes(",")
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
function collectPip(workspaceRoot) {
|
|
2587
|
+
const sites = [];
|
|
2588
|
+
const push = (relativePath, section, requirement) => {
|
|
2589
|
+
const parsed = parseRequirement(requirement);
|
|
2590
|
+
if (!parsed) {
|
|
2591
|
+
return;
|
|
2592
|
+
}
|
|
2593
|
+
sites.push({
|
|
2594
|
+
name: parsed.name,
|
|
2595
|
+
ecosystem: "pip",
|
|
2596
|
+
project: projectOf(relativePath),
|
|
2597
|
+
manifestPath: (0, import_node_path11.join)(workspaceRoot, relativePath),
|
|
2598
|
+
section,
|
|
2599
|
+
spec: parsed.spec,
|
|
2600
|
+
rewritable: parsed.rewritable && parsed.spec !== ""
|
|
2601
|
+
});
|
|
2602
|
+
};
|
|
2603
|
+
const pyprojectPaths = (0, import_node_fs7.globSync)(MANIFEST_GLOBS.pip, { cwd: workspaceRoot });
|
|
2604
|
+
for (const relativePath of pyprojectPaths) {
|
|
2605
|
+
let content;
|
|
2606
|
+
try {
|
|
2607
|
+
content = (0, import_node_fs7.readFileSync)((0, import_node_path11.join)(workspaceRoot, relativePath), "utf8");
|
|
2608
|
+
} catch {
|
|
2609
|
+
continue;
|
|
2610
|
+
}
|
|
2611
|
+
const declared = pyprojectDependencies(content);
|
|
2612
|
+
for (const entry of declared) {
|
|
2613
|
+
push(relativePath, "dep", entry);
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
const requirementsPaths = [
|
|
2617
|
+
"requirements-dev.txt",
|
|
2618
|
+
...(0, import_node_fs7.globSync)("apps/*/requirements.txt", { cwd: workspaceRoot })
|
|
2619
|
+
];
|
|
2620
|
+
for (const relativePath of requirementsPaths) {
|
|
2621
|
+
if (!fileExists((0, import_node_path11.join)(workspaceRoot, relativePath))) {
|
|
2622
|
+
continue;
|
|
2623
|
+
}
|
|
2624
|
+
const section = relativePath === "requirements-dev.txt" ? "devDep" : "dep";
|
|
2625
|
+
const lines = (0, import_node_fs7.readFileSync)((0, import_node_path11.join)(workspaceRoot, relativePath), "utf8").split(/\r?\n/);
|
|
2626
|
+
for (const line of lines) {
|
|
2627
|
+
push(relativePath, section, line);
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
return sites;
|
|
2631
|
+
}
|
|
2632
|
+
function pyprojectDependencies(content) {
|
|
2633
|
+
const entries = [];
|
|
2634
|
+
let inProjectTable = false;
|
|
2635
|
+
let inArray = false;
|
|
2636
|
+
for (const line of content.split(/\r?\n/)) {
|
|
2637
|
+
const text = line.trim();
|
|
2638
|
+
if (text.startsWith("[")) {
|
|
2639
|
+
inProjectTable = text === "[project]";
|
|
2640
|
+
inArray = false;
|
|
2641
|
+
continue;
|
|
2642
|
+
}
|
|
2643
|
+
if (!inProjectTable) {
|
|
2644
|
+
continue;
|
|
2645
|
+
}
|
|
2646
|
+
if (!inArray && /^dependencies\s*=\s*\[/.test(text)) {
|
|
2647
|
+
inArray = true;
|
|
2648
|
+
}
|
|
2649
|
+
if (!inArray) {
|
|
2650
|
+
continue;
|
|
2651
|
+
}
|
|
2652
|
+
for (const quoted of text.matchAll(/"([^"]*)"|'([^']*)'/g)) {
|
|
2653
|
+
entries.push(quoted[1] ?? quoted[2]);
|
|
2654
|
+
}
|
|
2655
|
+
if (text.includes("]")) {
|
|
2656
|
+
inArray = false;
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
return entries;
|
|
2660
|
+
}
|
|
2661
|
+
function collectPub(workspaceRoot) {
|
|
2662
|
+
const sites = [];
|
|
2663
|
+
const pubspecPaths = (0, import_node_fs7.globSync)(MANIFEST_GLOBS.pub, { cwd: workspaceRoot });
|
|
2664
|
+
for (const relativePath of pubspecPaths) {
|
|
2665
|
+
let content;
|
|
2666
|
+
try {
|
|
2667
|
+
content = (0, import_node_fs7.readFileSync)((0, import_node_path11.join)(workspaceRoot, relativePath), "utf8");
|
|
2668
|
+
} catch {
|
|
2669
|
+
continue;
|
|
2670
|
+
}
|
|
2671
|
+
for (const block of ["dependencies", "dev_dependencies"]) {
|
|
2672
|
+
for (const entry of pubspecBlockEntries(content, block)) {
|
|
2673
|
+
sites.push({
|
|
2674
|
+
name: entry.name,
|
|
2675
|
+
ecosystem: "pub",
|
|
2676
|
+
project: projectOf(relativePath),
|
|
2677
|
+
manifestPath: (0, import_node_path11.join)(workspaceRoot, relativePath),
|
|
2678
|
+
section: block === "dependencies" ? "dep" : "devDep",
|
|
2679
|
+
spec: entry.spec,
|
|
2680
|
+
rewritable: entry.spec !== ""
|
|
2681
|
+
});
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
return sites;
|
|
2686
|
+
}
|
|
2687
|
+
function pubspecBlockEntries(content, block) {
|
|
2688
|
+
const lines = content.split(/\r?\n/);
|
|
2689
|
+
const start = lines.findIndex((line) => line.trimEnd() === `${block}:`);
|
|
2690
|
+
if (start === -1) {
|
|
2691
|
+
return [];
|
|
2692
|
+
}
|
|
2693
|
+
const entries = [];
|
|
2694
|
+
let indent;
|
|
2695
|
+
const body = lines.slice(start + 1);
|
|
2696
|
+
for (const line of body) {
|
|
2697
|
+
if (line.trim() === "" || line.trimStart().startsWith("#")) {
|
|
2698
|
+
continue;
|
|
2699
|
+
}
|
|
2700
|
+
const lineIndent = line.length - line.trimStart().length;
|
|
2701
|
+
if (lineIndent === 0) {
|
|
2702
|
+
break;
|
|
2703
|
+
}
|
|
2704
|
+
indent ??= lineIndent;
|
|
2705
|
+
if (lineIndent > indent) {
|
|
2706
|
+
continue;
|
|
2707
|
+
}
|
|
2708
|
+
if (lineIndent < indent) {
|
|
2709
|
+
break;
|
|
2710
|
+
}
|
|
2711
|
+
const colon = line.indexOf(":");
|
|
2712
|
+
if (colon === -1) {
|
|
2713
|
+
continue;
|
|
2714
|
+
}
|
|
2715
|
+
const key = line.slice(0, colon).trim();
|
|
2716
|
+
if (/^[a-z_][\w-]*$/i.test(key)) {
|
|
2717
|
+
entries.push({ name: key, spec: stripQuotes(line.slice(colon + 1).trim()) });
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
return entries;
|
|
2721
|
+
}
|
|
2722
|
+
function stripQuotes(value) {
|
|
2723
|
+
return /^(['"]).*\1$/.test(value) ? value.slice(1, -1) : value;
|
|
2724
|
+
}
|
|
2725
|
+
function collectGo(workspaceRoot) {
|
|
2726
|
+
const manifestPath = (0, import_node_path11.join)(workspaceRoot, "go.mod");
|
|
2727
|
+
if (!fileExists(manifestPath)) {
|
|
2728
|
+
return [];
|
|
2729
|
+
}
|
|
2730
|
+
let content;
|
|
2731
|
+
try {
|
|
2732
|
+
content = (0, import_node_fs7.readFileSync)(manifestPath, "utf8");
|
|
2733
|
+
} catch {
|
|
2734
|
+
return [];
|
|
2735
|
+
}
|
|
2736
|
+
const sites = Array.from(content.matchAll(
|
|
2737
|
+
/^\s*(?:require\s+)?([\w.~-]+(?:\/[\w.~-]+)+)\s+(v\S+)(\s*\/\/\s*indirect)?/gm
|
|
2738
|
+
), (match) => ({
|
|
2739
|
+
name: match[1],
|
|
2740
|
+
ecosystem: "go",
|
|
2741
|
+
project: ROOT_LABEL,
|
|
2742
|
+
manifestPath,
|
|
2743
|
+
section: match[3] ? "indirect" : "module",
|
|
2744
|
+
spec: match[2],
|
|
2745
|
+
rewritable: false
|
|
2746
|
+
}));
|
|
2747
|
+
return sites;
|
|
2748
|
+
}
|
|
2749
|
+
function collectInventory(workspaceRoot, ecosystems = ECOSYSTEMS) {
|
|
2750
|
+
const readers = {
|
|
2751
|
+
npm: collectNpm,
|
|
2752
|
+
pip: collectPip,
|
|
2753
|
+
pub: collectPub,
|
|
2754
|
+
go: collectGo
|
|
2755
|
+
};
|
|
2756
|
+
const inventory = /* @__PURE__ */ new Map();
|
|
2757
|
+
for (const ecosystem of ecosystems) {
|
|
2758
|
+
if (!hasEcosystem(workspaceRoot, ecosystem)) {
|
|
2759
|
+
continue;
|
|
2760
|
+
}
|
|
2761
|
+
const sites = readers[ecosystem](workspaceRoot);
|
|
2762
|
+
for (const site of sites) {
|
|
2763
|
+
const existing = inventory.get(site.name);
|
|
2764
|
+
if (existing) {
|
|
2765
|
+
existing.push(site);
|
|
2766
|
+
} else {
|
|
2767
|
+
inventory.set(site.name, [site]);
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
return inventory;
|
|
2772
|
+
}
|
|
2773
|
+
function rewriteSpec(site, spec) {
|
|
2774
|
+
if (!site.rewritable || spec === site.spec) {
|
|
2775
|
+
return false;
|
|
2776
|
+
}
|
|
2777
|
+
const { name } = site;
|
|
2778
|
+
if (site.ecosystem === "npm") {
|
|
2779
|
+
const manifest = readJson(site.manifestPath);
|
|
2780
|
+
const block = Object.entries(NPM_SECTIONS).find(([, label]) => label === site.section)?.[0];
|
|
2781
|
+
if (!block || manifest[block]?.[name] === void 0) {
|
|
2782
|
+
return false;
|
|
2783
|
+
}
|
|
2784
|
+
manifest[block][name] = spec;
|
|
2785
|
+
writeFileEnsured(site.manifestPath, toJson(manifest));
|
|
2786
|
+
return true;
|
|
2787
|
+
}
|
|
2788
|
+
const before = (0, import_node_fs7.readFileSync)(site.manifestPath, "utf8");
|
|
2789
|
+
const after = site.ecosystem === "pip" ? replacePipSpec(before, name, spec) : replacePubSpec(before, name, spec);
|
|
2790
|
+
if (after === before) {
|
|
2791
|
+
return false;
|
|
2792
|
+
}
|
|
2793
|
+
writeFileEnsured(site.manifestPath, after);
|
|
2794
|
+
return true;
|
|
2795
|
+
}
|
|
2796
|
+
function replacePipSpec(content, name, spec) {
|
|
2797
|
+
const escaped = escapeForRegex(name);
|
|
2798
|
+
return content.replaceAll(
|
|
2799
|
+
new RegExp(String.raw`(^|["'\s])(${escaped})\s*(?:[<>=!~^][^"'\n,]*)?(?=["'\n]|$)`, "gm"),
|
|
2800
|
+
(match, lead, matched) => `${lead}${matched}${spec}`
|
|
2801
|
+
);
|
|
2802
|
+
}
|
|
2803
|
+
function replacePubSpec(content, name, spec) {
|
|
2804
|
+
return content.replaceAll(
|
|
2805
|
+
new RegExp(String.raw`^(\s+${escapeForRegex(name)}:[^\S\n]*)\S[^\n]*$`, "gm"),
|
|
2806
|
+
(match, lead) => `${lead}${spec}`
|
|
2807
|
+
);
|
|
2808
|
+
}
|
|
2809
|
+
function escapeForRegex(value) {
|
|
2810
|
+
return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
2811
|
+
}
|
|
2812
|
+
function isWorkspaceProject(workspaceRoot, name) {
|
|
2813
|
+
const short = name.includes("/") ? name.split("/").at(-1) : name;
|
|
2814
|
+
return ["apps", "libs", "packages"].some(
|
|
2815
|
+
(directory) => fileExists((0, import_node_path11.join)(workspaceRoot, directory, short ?? name, "package.json"))
|
|
2816
|
+
);
|
|
2817
|
+
}
|
|
2818
|
+
function isAliasedInstall(workspaceRoot, name) {
|
|
2819
|
+
try {
|
|
2820
|
+
const installed = readJson(
|
|
2821
|
+
(0, import_node_path11.join)(workspaceRoot, "node_modules", name, "package.json")
|
|
2822
|
+
);
|
|
2823
|
+
return installed.name !== void 0 && installed.name !== name;
|
|
2824
|
+
} catch {
|
|
2825
|
+
return false;
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
function resolvedVersion(workspaceRoot, ecosystem, name) {
|
|
2829
|
+
if (ecosystem === "npm") {
|
|
2830
|
+
try {
|
|
2831
|
+
return readJson(
|
|
2832
|
+
(0, import_node_path11.join)(workspaceRoot, "node_modules", name, "package.json")
|
|
2833
|
+
).version;
|
|
2834
|
+
} catch {
|
|
2835
|
+
return void 0;
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
if (ecosystem === "pub") {
|
|
2839
|
+
const lockPath = (0, import_node_path11.join)(workspaceRoot, "pubspec.lock");
|
|
2840
|
+
if (!fileExists(lockPath)) {
|
|
2841
|
+
return void 0;
|
|
2842
|
+
}
|
|
2843
|
+
try {
|
|
2844
|
+
const match = new RegExp(
|
|
2845
|
+
String.raw`^\s{2}${escapeForRegex(name)}:[^]*?^\s+version:\s*["']?([^"'\s]+)`,
|
|
2846
|
+
"m"
|
|
2847
|
+
).exec((0, import_node_fs7.readFileSync)(lockPath, "utf8"));
|
|
2848
|
+
return match?.[1];
|
|
2849
|
+
} catch {
|
|
2850
|
+
return void 0;
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
if (ecosystem === "pip") {
|
|
2854
|
+
const python = process.platform === "win32" ? "python" : "python3";
|
|
2855
|
+
const result = runCapture(python, ["-m", "pip", "show", name], workspaceRoot);
|
|
2856
|
+
if (result.status !== 0) {
|
|
2857
|
+
return void 0;
|
|
2858
|
+
}
|
|
2859
|
+
return /^Version:\s*(\S+)/m.exec(result.stdout)?.[1];
|
|
2860
|
+
}
|
|
2861
|
+
return void 0;
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
// src/deps/semver.ts
|
|
2865
|
+
var UPDATE_KINDS = ["patch", "minor", "major", "non-semver"];
|
|
2866
|
+
var UPDATE_KIND_LABELS = {
|
|
2867
|
+
patch: { title: "Patch Update", blurb: "Backwards-compatible bug fixes." },
|
|
2868
|
+
minor: { title: "Minor Update", blurb: "New backwards-compatible features." },
|
|
2869
|
+
major: { title: "Major Update", blurb: "Potentially breaking API changes. Use caution." },
|
|
2870
|
+
"non-semver": { title: "Non-Semver", blurb: "Versions less than 1.0.0, caution." }
|
|
2871
|
+
};
|
|
2872
|
+
var RANGE_OPERATORS = [">=", "<=", "^", "~", ">", "<", "="];
|
|
2873
|
+
function parseVersion(raw) {
|
|
2874
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([\w.-]+))?/.exec(raw.trim());
|
|
2875
|
+
if (!match) {
|
|
2876
|
+
return void 0;
|
|
2877
|
+
}
|
|
2878
|
+
return {
|
|
2879
|
+
major: Number(match[1]),
|
|
2880
|
+
minor: Number(match[2]),
|
|
2881
|
+
patch: Number(match[3]),
|
|
2882
|
+
prerelease: match[4]
|
|
2883
|
+
};
|
|
2884
|
+
}
|
|
2885
|
+
function rangeOperator(spec) {
|
|
2886
|
+
const trimmed = spec.trim();
|
|
2887
|
+
return RANGE_OPERATORS.find((operator) => trimmed.startsWith(operator)) ?? "";
|
|
2888
|
+
}
|
|
2889
|
+
function specVersion(spec) {
|
|
2890
|
+
const bare2 = spec.trim().slice(rangeOperator(spec).length).trim();
|
|
2891
|
+
return /^\d+\.\d+(?:\.\d+)?(?:-[\w.-]+)?$/.test(bare2) ? bare2 : void 0;
|
|
2892
|
+
}
|
|
2893
|
+
function compareVersions(left, right) {
|
|
2894
|
+
const a = parseVersion(left);
|
|
2895
|
+
const b = parseVersion(right);
|
|
2896
|
+
if (!a || !b) {
|
|
2897
|
+
return 0;
|
|
2898
|
+
}
|
|
2899
|
+
if (a.major !== b.major) {
|
|
2900
|
+
return a.major - b.major;
|
|
2901
|
+
}
|
|
2902
|
+
if (a.minor !== b.minor) {
|
|
2903
|
+
return a.minor - b.minor;
|
|
2904
|
+
}
|
|
2905
|
+
if (a.patch !== b.patch) {
|
|
2906
|
+
return a.patch - b.patch;
|
|
2907
|
+
}
|
|
2908
|
+
if (a.prerelease === b.prerelease) {
|
|
2909
|
+
return 0;
|
|
2910
|
+
}
|
|
2911
|
+
if (!a.prerelease) {
|
|
2912
|
+
return 1;
|
|
2913
|
+
}
|
|
2914
|
+
if (!b.prerelease) {
|
|
2915
|
+
return -1;
|
|
2916
|
+
}
|
|
2917
|
+
return a.prerelease < b.prerelease ? -1 : 1;
|
|
2918
|
+
}
|
|
2919
|
+
function isNewer(candidate, current) {
|
|
2920
|
+
return compareVersions(candidate, current) > 0;
|
|
2921
|
+
}
|
|
2922
|
+
function classify(current, latest) {
|
|
2923
|
+
const from = parseVersion(current);
|
|
2924
|
+
const to = parseVersion(latest);
|
|
2925
|
+
if (!from || !to || from.major === 0) {
|
|
2926
|
+
return "non-semver";
|
|
2927
|
+
}
|
|
2928
|
+
if (from.major !== to.major) {
|
|
2929
|
+
return "major";
|
|
2930
|
+
}
|
|
2931
|
+
return from.minor === to.minor ? "patch" : "minor";
|
|
2932
|
+
}
|
|
2933
|
+
function highestSpec(specs) {
|
|
2934
|
+
let best;
|
|
2935
|
+
let bestVersion;
|
|
2936
|
+
for (const spec of specs) {
|
|
2937
|
+
const version = specVersion(spec);
|
|
2938
|
+
if (!version) {
|
|
2939
|
+
continue;
|
|
2940
|
+
}
|
|
2941
|
+
if (!bestVersion || isNewer(version, bestVersion)) {
|
|
2942
|
+
best = spec;
|
|
2943
|
+
bestVersion = version;
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
return best;
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2949
|
+
// src/commands/sync.ts
|
|
2950
|
+
function resolveEcosystems(requested) {
|
|
2951
|
+
if (!requested) {
|
|
2952
|
+
return ECOSYSTEMS;
|
|
2953
|
+
}
|
|
2954
|
+
const match = ECOSYSTEMS.find((ecosystem) => ecosystem === requested);
|
|
2955
|
+
if (!match) {
|
|
2956
|
+
throw new Error(
|
|
2957
|
+
`Unknown ecosystem '${requested}'. Expected one of: ${ECOSYSTEMS.join(", ")}.`
|
|
2958
|
+
);
|
|
2959
|
+
}
|
|
2960
|
+
return [match];
|
|
2961
|
+
}
|
|
2962
|
+
function collectDrift(workspaceRoot, ecosystems = ECOSYSTEMS) {
|
|
2963
|
+
const reconcilable = ecosystems.filter((ecosystem) => ecosystem !== "go");
|
|
2964
|
+
const inventory = collectInventory(workspaceRoot, reconcilable);
|
|
2965
|
+
const drift = [];
|
|
2966
|
+
for (const [name, allSites] of inventory) {
|
|
2967
|
+
const sites = allSites.filter((site) => site.section !== PEER_SECTION);
|
|
2968
|
+
if (sites.length < 2 || isWorkspaceLocal(workspaceRoot, sites, name)) {
|
|
2969
|
+
continue;
|
|
2970
|
+
}
|
|
2971
|
+
const distinct = new Set(sites.map((site) => site.spec));
|
|
2972
|
+
if (distinct.size < 2) {
|
|
2973
|
+
continue;
|
|
2974
|
+
}
|
|
2975
|
+
const installed = resolvedVersion(workspaceRoot, sites[0].ecosystem, name);
|
|
2976
|
+
const target = chooseTarget(sites, installed);
|
|
2977
|
+
if (!target) {
|
|
2978
|
+
continue;
|
|
2979
|
+
}
|
|
2980
|
+
const disagreeing = sites.filter((site) => site.spec !== target);
|
|
2981
|
+
drift.push({
|
|
2982
|
+
name,
|
|
2983
|
+
ecosystem: sites[0].ecosystem,
|
|
2984
|
+
target,
|
|
2985
|
+
reason: installed ? "resolved" : "highest",
|
|
2986
|
+
fixable: disagreeing.filter((site) => site.rewritable),
|
|
2987
|
+
blocked: disagreeing.filter((site) => !site.rewritable)
|
|
2988
|
+
});
|
|
2989
|
+
}
|
|
2990
|
+
return drift.toSorted((left, right) => left.name.localeCompare(right.name));
|
|
2991
|
+
}
|
|
2992
|
+
function isWorkspaceLocal(workspaceRoot, sites, name) {
|
|
2993
|
+
return sites[0].ecosystem === "npm" && isWorkspaceProject(workspaceRoot, name);
|
|
2994
|
+
}
|
|
2995
|
+
function chooseTarget(sites, installed) {
|
|
2996
|
+
if (!installed) {
|
|
2997
|
+
return highestSpec(sites.map((site) => site.spec));
|
|
2998
|
+
}
|
|
2999
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3000
|
+
for (const site of sites) {
|
|
3001
|
+
if (!specVersion(site.spec)) {
|
|
3002
|
+
continue;
|
|
3003
|
+
}
|
|
3004
|
+
const operator2 = rangeOperator(site.spec);
|
|
3005
|
+
counts.set(operator2, (counts.get(operator2) ?? 0) + 1);
|
|
3006
|
+
}
|
|
3007
|
+
if (counts.size === 0) {
|
|
3008
|
+
return void 0;
|
|
3009
|
+
}
|
|
3010
|
+
const [operator] = [...counts].toSorted(
|
|
3011
|
+
(left, right) => right[1] - left[1] || (left[0] === "^" ? -1 : 1)
|
|
3012
|
+
)[0];
|
|
3013
|
+
return `${operator}${installed}`;
|
|
3014
|
+
}
|
|
3015
|
+
function applyDrift(drift) {
|
|
3016
|
+
const changed = /* @__PURE__ */ new Set();
|
|
3017
|
+
for (const finding of drift) {
|
|
3018
|
+
for (const site of finding.fixable) {
|
|
3019
|
+
if (rewriteSpec(site, finding.target)) {
|
|
3020
|
+
changed.add(site.manifestPath);
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
return [...changed];
|
|
3025
|
+
}
|
|
3026
|
+
function syncProjectReferences(workspaceRoot) {
|
|
3027
|
+
logger.step("Syncing TypeScript project references (nx sync)");
|
|
3028
|
+
if (runShell("npx", ["nx", "sync"], workspaceRoot) !== 0) {
|
|
3029
|
+
logger.warn(
|
|
3030
|
+
"nx sync did not complete \u2014 run `npx nx sync` yourself so cross-project imports resolve in your editor."
|
|
3031
|
+
);
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
function describe(finding) {
|
|
3035
|
+
const sites = [...finding.fixable, ...finding.blocked].map((site) => `${site.project === ROOT_LABEL ? ROOT_LABEL : site.project} ${site.spec}`).join(", ");
|
|
3036
|
+
return `${finding.name} \u2192 ${finding.target} (${finding.reason}); disagreeing: ${sites}`;
|
|
3037
|
+
}
|
|
3038
|
+
function runSync(workspaceRoot, options) {
|
|
3039
|
+
if (!fileExists((0, import_node_path12.join)(workspaceRoot, "nx.json"))) {
|
|
3040
|
+
throw new Error(
|
|
3041
|
+
`No nx.json found in ${workspaceRoot} \u2014 run 'mnci sync' from the workspace root.`
|
|
3042
|
+
);
|
|
3043
|
+
}
|
|
3044
|
+
const ecosystems = resolveEcosystems(options.ecosystem);
|
|
3045
|
+
for (const ecosystem of ecosystems) {
|
|
3046
|
+
if (!hasEcosystem(workspaceRoot, ecosystem)) {
|
|
3047
|
+
logger.info(`\u2298 SKIPPED ${ecosystem} \u2014 no ${ecosystem} project in this workspace.`);
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
if (ecosystems.includes("go") && hasEcosystem(workspaceRoot, "go")) {
|
|
3051
|
+
logger.success("go \u2014 nothing to sync; one root go.mod means one version of every module.");
|
|
3052
|
+
}
|
|
3053
|
+
const drift = collectDrift(workspaceRoot, ecosystems);
|
|
3054
|
+
if (drift.length === 0) {
|
|
3055
|
+
logger.success("Every project agrees on every shared dependency version.");
|
|
3056
|
+
} else {
|
|
3057
|
+
for (const finding of drift) {
|
|
3058
|
+
logger.info(` ${describe(finding)}`);
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
if (options.check) {
|
|
3062
|
+
if (drift.length > 0) {
|
|
3063
|
+
logger.error(
|
|
3064
|
+
`${drift.length} package(s) declared at more than one version. Run 'mnci sync' to converge them.`
|
|
3065
|
+
);
|
|
3066
|
+
process.exitCode = 1;
|
|
3067
|
+
}
|
|
3068
|
+
return;
|
|
3069
|
+
}
|
|
3070
|
+
const changed = applyDrift(drift);
|
|
3071
|
+
for (const path of changed) {
|
|
3072
|
+
logger.detail(`updated ${path}`);
|
|
3073
|
+
}
|
|
3074
|
+
const blocked = drift.flatMap((finding) => finding.blocked);
|
|
3075
|
+
for (const site of blocked) {
|
|
3076
|
+
logger.warn(
|
|
3077
|
+
`${site.name} in ${site.project} is declared as '${site.spec}', which is not a plain version range \u2014 left unchanged.`
|
|
3078
|
+
);
|
|
3079
|
+
}
|
|
3080
|
+
syncProjectReferences(workspaceRoot);
|
|
3081
|
+
if (changed.length > 0) {
|
|
3082
|
+
logger.step("Formatting the changed manifests (eslint --fix)");
|
|
3083
|
+
runFormatter(workspaceRoot);
|
|
3084
|
+
}
|
|
3085
|
+
logger.success("Done. Review the changes with `git diff` before committing.");
|
|
3086
|
+
}
|
|
3087
|
+
|
|
2336
3088
|
// src/commands/add.ts
|
|
2337
3089
|
var PROJECT_KINDS = [
|
|
2338
3090
|
"react-app",
|
|
@@ -2357,7 +3109,7 @@ var PROJECT_KINDS = [
|
|
|
2357
3109
|
];
|
|
2358
3110
|
async function runAdd(kind, name, options) {
|
|
2359
3111
|
const workspaceRoot = process.cwd();
|
|
2360
|
-
if (!fileExists((0,
|
|
3112
|
+
if (!fileExists((0, import_node_path13.join)(workspaceRoot, "nx.json"))) {
|
|
2361
3113
|
throw new Error("No nx.json found here. Run `add` from the workspace root.");
|
|
2362
3114
|
}
|
|
2363
3115
|
const stack = readWorkspaceStack(workspaceRoot);
|
|
@@ -2368,6 +3120,7 @@ async function runAdd(kind, name, options) {
|
|
|
2368
3120
|
});
|
|
2369
3121
|
const resolvedName = name ?? await promptText("Project name");
|
|
2370
3122
|
assertValidProjectName(resolvedName, "Project name");
|
|
3123
|
+
const rootDependenciesBefore = rootRuntimeDependencies(workspaceRoot);
|
|
2371
3124
|
switch (resolvedKind) {
|
|
2372
3125
|
case "react-app": {
|
|
2373
3126
|
addReactApp(workspaceRoot, resolvedName, stack);
|
|
@@ -2406,7 +3159,7 @@ async function runAdd(kind, name, options) {
|
|
|
2406
3159
|
],
|
|
2407
3160
|
workspaceRoot
|
|
2408
3161
|
);
|
|
2409
|
-
markPrivate((0,
|
|
3162
|
+
markPrivate((0, import_node_path13.join)(workspaceRoot, "libs", resolvedName, "package.json"));
|
|
2410
3163
|
removeGeneratedEslintConfig(workspaceRoot, `libs/${resolvedName}`);
|
|
2411
3164
|
registerProjectCommands(workspaceRoot, resolvedName, { build: true });
|
|
2412
3165
|
break;
|
|
@@ -2468,21 +3221,14 @@ async function runAdd(kind, name, options) {
|
|
|
2468
3221
|
);
|
|
2469
3222
|
}
|
|
2470
3223
|
}
|
|
3224
|
+
relocateRootRuntimeDependencies(workspaceRoot, resolvedName, rootDependenciesBefore);
|
|
2471
3225
|
syncProjectReferences(workspaceRoot);
|
|
2472
3226
|
runFormatter(workspaceRoot);
|
|
2473
3227
|
logger.success(`Added ${resolvedKind} '${resolvedName}'.`);
|
|
2474
3228
|
}
|
|
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
3229
|
function readWorkspaceStack(workspaceRoot) {
|
|
2484
3230
|
const nxJson = readJson(
|
|
2485
|
-
(0,
|
|
3231
|
+
(0, import_node_path13.join)(workspaceRoot, "nx.json")
|
|
2486
3232
|
);
|
|
2487
3233
|
const stack = nxJson.mnci?.stack;
|
|
2488
3234
|
return {
|
|
@@ -2491,17 +3237,17 @@ function readWorkspaceStack(workspaceRoot) {
|
|
|
2491
3237
|
}
|
|
2492
3238
|
|
|
2493
3239
|
// src/commands/doctor.ts
|
|
2494
|
-
var
|
|
2495
|
-
var
|
|
3240
|
+
var import_node_fs8 = require("fs");
|
|
3241
|
+
var import_node_path14 = require("path");
|
|
2496
3242
|
var SUPPORTED_ESLINT_MAJOR = ESLINT_VERSION.replace(/^\D*/, "").split(".", 1)[0];
|
|
2497
|
-
function
|
|
3243
|
+
function toPosix2(path) {
|
|
2498
3244
|
return path.replaceAll("\\", "/");
|
|
2499
3245
|
}
|
|
2500
3246
|
function checkEslintConfigs(workspaceRoot) {
|
|
2501
|
-
const rootConfigs = (0,
|
|
2502
|
-
const projectConfigs = (0,
|
|
3247
|
+
const rootConfigs = (0, import_node_fs8.globSync)("eslint.config.{js,mjs,cjs,ts,mts,cts}", { cwd: workspaceRoot });
|
|
3248
|
+
const projectConfigs = (0, import_node_fs8.globSync)("{apps,libs,packages}/*/eslint.config.{js,mjs,cjs,ts,mts,cts}", {
|
|
2503
3249
|
cwd: workspaceRoot
|
|
2504
|
-
}).map((config) =>
|
|
3250
|
+
}).map((config) => toPosix2(config));
|
|
2505
3251
|
return [
|
|
2506
3252
|
{
|
|
2507
3253
|
check: "root ESLint config",
|
|
@@ -2520,7 +3266,7 @@ function checkEslintConfigs(workspaceRoot) {
|
|
|
2520
3266
|
function declaredDevDependencies(workspaceRoot) {
|
|
2521
3267
|
try {
|
|
2522
3268
|
const manifest = readJson(
|
|
2523
|
-
(0,
|
|
3269
|
+
(0, import_node_path14.join)(workspaceRoot, "package.json")
|
|
2524
3270
|
);
|
|
2525
3271
|
return manifest.devDependencies ?? {};
|
|
2526
3272
|
} catch {
|
|
@@ -2528,7 +3274,7 @@ function declaredDevDependencies(workspaceRoot) {
|
|
|
2528
3274
|
}
|
|
2529
3275
|
}
|
|
2530
3276
|
function checkNoRetiredFormatter(workspaceRoot) {
|
|
2531
|
-
const files = RETIRED_FORMATTER_FILES.filter((file) => fileExists((0,
|
|
3277
|
+
const files = RETIRED_FORMATTER_FILES.filter((file) => fileExists((0, import_node_path14.join)(workspaceRoot, file)));
|
|
2532
3278
|
const declared = ["prettier", "eslint-config-prettier", "oxlint", "oxfmt", "@mnci/oxlint-config"].filter((name) => declaredDevDependencies(workspaceRoot)[name] !== void 0);
|
|
2533
3279
|
const stale = [...files, ...declared];
|
|
2534
3280
|
if (stale.length === 0) {
|
|
@@ -2554,7 +3300,7 @@ function checkEslintPlugin(nxJson) {
|
|
|
2554
3300
|
};
|
|
2555
3301
|
}
|
|
2556
3302
|
function checkResolvedEslint(workspaceRoot) {
|
|
2557
|
-
const manifestPath = (0,
|
|
3303
|
+
const manifestPath = (0, import_node_path14.join)(workspaceRoot, "node_modules/eslint/package.json");
|
|
2558
3304
|
if (!fileExists(manifestPath)) {
|
|
2559
3305
|
return void 0;
|
|
2560
3306
|
}
|
|
@@ -2572,11 +3318,11 @@ function checkResolvedEslint(workspaceRoot) {
|
|
|
2572
3318
|
}
|
|
2573
3319
|
}
|
|
2574
3320
|
function checkNpmrc(workspaceRoot, registry, scope) {
|
|
2575
|
-
const npmrcPath = (0,
|
|
3321
|
+
const npmrcPath = (0, import_node_path14.join)(workspaceRoot, ".npmrc");
|
|
2576
3322
|
if (!registry || !fileExists(npmrcPath)) {
|
|
2577
3323
|
return void 0;
|
|
2578
3324
|
}
|
|
2579
|
-
const npmrc = (0,
|
|
3325
|
+
const npmrc = (0, import_node_fs8.readFileSync)(npmrcPath, "utf8");
|
|
2580
3326
|
if (registry.kind === "npm") {
|
|
2581
3327
|
return {
|
|
2582
3328
|
check: ".npmrc authenticates the public registry",
|
|
@@ -2594,12 +3340,12 @@ function checkNpmrc(workspaceRoot, registry, scope) {
|
|
|
2594
3340
|
}
|
|
2595
3341
|
function checkVersionActions(workspaceRoot) {
|
|
2596
3342
|
const candidates = [
|
|
2597
|
-
...(0,
|
|
2598
|
-
...(0,
|
|
2599
|
-
].map((manifest) =>
|
|
3343
|
+
...(0, import_node_fs8.globSync)("packages/*/pubspec.yaml", { cwd: workspaceRoot }),
|
|
3344
|
+
...(0, import_node_fs8.globSync)("python-packages/*/pyproject.toml", { cwd: workspaceRoot })
|
|
3345
|
+
].map((manifest) => toPosix2(manifest));
|
|
2600
3346
|
return candidates.flatMap((manifest) => {
|
|
2601
3347
|
const projectRoot = manifest.slice(0, manifest.lastIndexOf("/"));
|
|
2602
|
-
const projectJsonPath = (0,
|
|
3348
|
+
const projectJsonPath = (0, import_node_path14.join)(workspaceRoot, projectRoot, "project.json");
|
|
2603
3349
|
let hasOverride = false;
|
|
2604
3350
|
try {
|
|
2605
3351
|
const projectJson = readJson(projectJsonPath);
|
|
@@ -2628,7 +3374,7 @@ function declaredTargets(workspaceRoot, projectRoot) {
|
|
|
2628
3374
|
]) {
|
|
2629
3375
|
try {
|
|
2630
3376
|
const targets = extract(
|
|
2631
|
-
readJson((0,
|
|
3377
|
+
readJson((0, import_node_path14.join)(workspaceRoot, projectRoot, file))
|
|
2632
3378
|
);
|
|
2633
3379
|
if (targets && typeof targets === "object") {
|
|
2634
3380
|
sources.push({
|
|
@@ -2644,9 +3390,9 @@ function declaredTargets(workspaceRoot, projectRoot) {
|
|
|
2644
3390
|
function checkTargetFilesExist(workspaceRoot) {
|
|
2645
3391
|
const projectRoots = new Set(
|
|
2646
3392
|
[
|
|
2647
|
-
...(0,
|
|
2648
|
-
...(0,
|
|
2649
|
-
].map((manifest) =>
|
|
3393
|
+
...(0, import_node_fs8.globSync)("{apps,libs,packages}/*/project.json", { cwd: workspaceRoot }),
|
|
3394
|
+
...(0, import_node_fs8.globSync)("{apps,libs,packages}/*/package.json", { cwd: workspaceRoot })
|
|
3395
|
+
].map((manifest) => toPosix2(manifest).split("/").slice(0, 2).join("/"))
|
|
2650
3396
|
);
|
|
2651
3397
|
return [...projectRoots].flatMap(
|
|
2652
3398
|
(projectRoot) => declaredTargets(workspaceRoot, projectRoot).flatMap(
|
|
@@ -2660,7 +3406,7 @@ function checkOneTargetsFiles(workspaceRoot, source, targetName, target) {
|
|
|
2660
3406
|
return TARGET_FILE_OPTIONS.flatMap((option) => {
|
|
2661
3407
|
const value = target?.options?.[option];
|
|
2662
3408
|
const checkable = typeof value === "string" && !value.includes("{");
|
|
2663
|
-
if (!checkable || fileExists((0,
|
|
3409
|
+
if (!checkable || fileExists((0, import_node_path14.join)(workspaceRoot, value))) {
|
|
2664
3410
|
return [];
|
|
2665
3411
|
}
|
|
2666
3412
|
return [
|
|
@@ -2681,8 +3427,49 @@ function checkSync(workspaceRoot) {
|
|
|
2681
3427
|
remedy: "run `npx nx sync` and commit the result"
|
|
2682
3428
|
};
|
|
2683
3429
|
}
|
|
3430
|
+
function checkNoRootRuntimeDependencies(workspaceRoot) {
|
|
3431
|
+
const manifestPath = (0, import_node_path14.join)(workspaceRoot, "package.json");
|
|
3432
|
+
if (!fileExists(manifestPath)) {
|
|
3433
|
+
return void 0;
|
|
3434
|
+
}
|
|
3435
|
+
let declared;
|
|
3436
|
+
try {
|
|
3437
|
+
declared = Object.keys(
|
|
3438
|
+
readJson(manifestPath).dependencies ?? {}
|
|
3439
|
+
);
|
|
3440
|
+
} catch {
|
|
3441
|
+
return void 0;
|
|
3442
|
+
}
|
|
3443
|
+
return {
|
|
3444
|
+
check: "no runtime dependencies in the root manifest",
|
|
3445
|
+
ok: declared.length === 0,
|
|
3446
|
+
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`,
|
|
3447
|
+
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)`
|
|
3448
|
+
};
|
|
3449
|
+
}
|
|
3450
|
+
function checkRollupSourceMaps(workspaceRoot) {
|
|
3451
|
+
const configs = (0, import_node_fs8.globSync)(["packages/*/rollup.config.cjs", "libs/*/rollup.config.cjs"], {
|
|
3452
|
+
cwd: workspaceRoot
|
|
3453
|
+
}).map((config) => toPosix2(config));
|
|
3454
|
+
return configs.flatMap((relativePath) => {
|
|
3455
|
+
let config;
|
|
3456
|
+
try {
|
|
3457
|
+
config = (0, import_node_fs8.readFileSync)((0, import_node_path14.join)(workspaceRoot, relativePath), "utf8");
|
|
3458
|
+
} catch {
|
|
3459
|
+
return [];
|
|
3460
|
+
}
|
|
3461
|
+
return [
|
|
3462
|
+
{
|
|
3463
|
+
check: `source maps enabled in ${relativePath}`,
|
|
3464
|
+
ok: hasRollupSourceMaps(config),
|
|
3465
|
+
detail: "rollup emits no .js.map without it, so a breakpoint in a .ts file can never bind",
|
|
3466
|
+
remedy: "run `mnci upgrade`, which adds it to every rollup config"
|
|
3467
|
+
}
|
|
3468
|
+
];
|
|
3469
|
+
});
|
|
3470
|
+
}
|
|
2684
3471
|
function collectFindings(workspaceRoot) {
|
|
2685
|
-
const nxJsonPath = (0,
|
|
3472
|
+
const nxJsonPath = (0, import_node_path14.join)(workspaceRoot, "nx.json");
|
|
2686
3473
|
if (!fileExists(nxJsonPath)) {
|
|
2687
3474
|
throw new Error(
|
|
2688
3475
|
`No nx.json found in ${workspaceRoot} \u2014 run 'mnci doctor' from the workspace root.`
|
|
@@ -2695,6 +3482,8 @@ function collectFindings(workspaceRoot) {
|
|
|
2695
3482
|
checkEslintPlugin(nxJson),
|
|
2696
3483
|
checkResolvedEslint(workspaceRoot),
|
|
2697
3484
|
checkNpmrc(workspaceRoot, nxJson.mnci?.registry, nxJson.mnci?.scope),
|
|
3485
|
+
checkNoRootRuntimeDependencies(workspaceRoot),
|
|
3486
|
+
...checkRollupSourceMaps(workspaceRoot),
|
|
2698
3487
|
...checkVersionActions(workspaceRoot),
|
|
2699
3488
|
...checkTargetFilesExist(workspaceRoot),
|
|
2700
3489
|
checkSync(workspaceRoot)
|
|
@@ -2722,12 +3511,12 @@ function runDoctor(workspaceRoot) {
|
|
|
2722
3511
|
}
|
|
2723
3512
|
|
|
2724
3513
|
// src/commands/interactive.ts
|
|
2725
|
-
var
|
|
3514
|
+
var import_node_path17 = require("path");
|
|
2726
3515
|
var import_prompts8 = require("@inquirer/prompts");
|
|
2727
3516
|
|
|
2728
3517
|
// src/commands/new.ts
|
|
2729
|
-
var
|
|
2730
|
-
var
|
|
3518
|
+
var import_node_fs9 = require("fs");
|
|
3519
|
+
var import_node_path15 = require("path");
|
|
2731
3520
|
function nxCloudProviderValue(ci) {
|
|
2732
3521
|
return ci === "azure" ? "azure" : "github";
|
|
2733
3522
|
}
|
|
@@ -2788,7 +3577,7 @@ async function runNew(name, options) {
|
|
|
2788
3577
|
],
|
|
2789
3578
|
process.cwd()
|
|
2790
3579
|
);
|
|
2791
|
-
const workspaceRoot = (0,
|
|
3580
|
+
const workspaceRoot = (0, import_node_path15.join)(process.cwd(), workspaceName);
|
|
2792
3581
|
logger.step(
|
|
2793
3582
|
"Applying MoNecromanCI overlay (VS Code workspace, release config, .npmrc, commitlint, pipeline, stack)"
|
|
2794
3583
|
);
|
|
@@ -2797,8 +3586,8 @@ async function runNew(name, options) {
|
|
|
2797
3586
|
{ workspaceName, scope, registry, agent, variableGroup, ci, stack },
|
|
2798
3587
|
logger.detail
|
|
2799
3588
|
);
|
|
2800
|
-
(0,
|
|
2801
|
-
(0,
|
|
3589
|
+
(0, import_node_fs9.rmSync)((0, import_node_path15.join)(workspaceRoot, "node_modules"), { recursive: true, force: true });
|
|
3590
|
+
(0, import_node_fs9.rmSync)((0, import_node_path15.join)(workspaceRoot, "package-lock.json"), { force: true });
|
|
2802
3591
|
logger.step("Installing the toolchain (commitlint + husky + linting/formatting)");
|
|
2803
3592
|
const installStatus = runShell(
|
|
2804
3593
|
"npm",
|
|
@@ -2828,8 +3617,8 @@ async function runNew(name, options) {
|
|
|
2828
3617
|
}
|
|
2829
3618
|
|
|
2830
3619
|
// src/commands/upgrade.ts
|
|
2831
|
-
var
|
|
2832
|
-
var
|
|
3620
|
+
var import_node_fs10 = require("fs");
|
|
3621
|
+
var import_node_path16 = require("path");
|
|
2833
3622
|
function resolveRegistry2(options, persisted) {
|
|
2834
3623
|
if (options.registry === "azure-artifacts" || options.organization && options.artifactsFeed) {
|
|
2835
3624
|
const persistedAzure = persisted?.kind === "azure-artifacts" ? persisted : void 0;
|
|
@@ -2858,7 +3647,7 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
|
|
|
2858
3647
|
return persisted.workspaceName;
|
|
2859
3648
|
}
|
|
2860
3649
|
try {
|
|
2861
|
-
const existing = (0,
|
|
3650
|
+
const existing = (0, import_node_fs10.readdirSync)(workspaceRoot).find(
|
|
2862
3651
|
(file) => file.endsWith(".code-workspace") && file !== "undefined.code-workspace"
|
|
2863
3652
|
);
|
|
2864
3653
|
if (existing) {
|
|
@@ -2866,7 +3655,7 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
|
|
|
2866
3655
|
}
|
|
2867
3656
|
} catch {
|
|
2868
3657
|
}
|
|
2869
|
-
return (0,
|
|
3658
|
+
return (0, import_node_path16.basename)(workspaceRoot);
|
|
2870
3659
|
}
|
|
2871
3660
|
function resolveOverlayOptions(workspaceRoot, options, persisted) {
|
|
2872
3661
|
const scope = options.scope ?? persisted.scope;
|
|
@@ -2904,7 +3693,7 @@ function resolveOverlayOptions(workspaceRoot, options, persisted) {
|
|
|
2904
3693
|
};
|
|
2905
3694
|
}
|
|
2906
3695
|
function runUpgrade(workspaceRoot, options) {
|
|
2907
|
-
if (!fileExists((0,
|
|
3696
|
+
if (!fileExists((0, import_node_path16.join)(workspaceRoot, "nx.json"))) {
|
|
2908
3697
|
throw new Error(
|
|
2909
3698
|
`No nx.json found in ${workspaceRoot} \u2014 this does not look like an Nx workspace. Run 'mnci upgrade' from the workspace root.`
|
|
2910
3699
|
);
|
|
@@ -2913,6 +3702,13 @@ function runUpgrade(workspaceRoot, options) {
|
|
|
2913
3702
|
const resolved = resolveOverlayOptions(workspaceRoot, options, persisted);
|
|
2914
3703
|
logger.step("Re-applying the MoNecromanCI overlay");
|
|
2915
3704
|
applyOverlay(workspaceRoot, resolved, logger.detail);
|
|
3705
|
+
const repaired = repairRollupSourceMaps(workspaceRoot);
|
|
3706
|
+
if (repaired.length > 0) {
|
|
3707
|
+
logger.step("Enabling source maps in rollup configs (breakpoints in .ts files)");
|
|
3708
|
+
for (const path of repaired) {
|
|
3709
|
+
logger.detail(`updated ${path}`);
|
|
3710
|
+
}
|
|
3711
|
+
}
|
|
2916
3712
|
logger.step(
|
|
2917
3713
|
"Formatting the workspace (eslint --fix) \u2014 the slowest step, minutes on a large workspace"
|
|
2918
3714
|
);
|
|
@@ -2922,7 +3718,7 @@ function runUpgrade(workspaceRoot, options) {
|
|
|
2922
3718
|
|
|
2923
3719
|
// src/commands/interactive.ts
|
|
2924
3720
|
async function runInteractive() {
|
|
2925
|
-
const inWorkspace = fileExists((0,
|
|
3721
|
+
const inWorkspace = fileExists((0, import_node_path17.join)(process.cwd(), "nx.json"));
|
|
2926
3722
|
const choiceNew = { name: "Create a new monorepo", value: "new" };
|
|
2927
3723
|
const choiceAdd = { name: "Add a project to this workspace", value: "add" };
|
|
2928
3724
|
const choiceUpgrade = {
|
|
@@ -2942,27 +3738,345 @@ async function runInteractive() {
|
|
|
2942
3738
|
}
|
|
2943
3739
|
}
|
|
2944
3740
|
|
|
3741
|
+
// src/commands/up.ts
|
|
3742
|
+
var import_node_path18 = require("path");
|
|
3743
|
+
var import_prompts9 = require("@inquirer/prompts");
|
|
3744
|
+
|
|
3745
|
+
// src/deps/registry.ts
|
|
3746
|
+
var LOOKUP_CONCURRENCY = 8;
|
|
3747
|
+
async function latestNpmVersions(names, cwd) {
|
|
3748
|
+
const results = await pool(names, LOOKUP_CONCURRENCY, async (name) => {
|
|
3749
|
+
const result = await runCaptureAsync("npm", ["view", name, "version", "--json"], cwd);
|
|
3750
|
+
if (result.status !== 0) {
|
|
3751
|
+
return;
|
|
3752
|
+
}
|
|
3753
|
+
try {
|
|
3754
|
+
const parsed = JSON.parse(result.stdout);
|
|
3755
|
+
const version = Array.isArray(parsed) ? parsed.at(-1) : parsed;
|
|
3756
|
+
return typeof version === "string" ? [name, version] : void 0;
|
|
3757
|
+
} catch {
|
|
3758
|
+
return;
|
|
3759
|
+
}
|
|
3760
|
+
});
|
|
3761
|
+
return new Map(results.filter((entry) => entry !== void 0));
|
|
3762
|
+
}
|
|
3763
|
+
async function latestPipVersions(names, cwd) {
|
|
3764
|
+
const python = process.platform === "win32" ? "python" : "python3";
|
|
3765
|
+
const results = await pool(names, LOOKUP_CONCURRENCY, async (name) => {
|
|
3766
|
+
const result = await runCaptureAsync(python, ["-m", "pip", "index", "versions", name], cwd);
|
|
3767
|
+
if (result.status !== 0) {
|
|
3768
|
+
return;
|
|
3769
|
+
}
|
|
3770
|
+
const match = /\(([^)]+)\)/.exec(result.stdout);
|
|
3771
|
+
return match ? [name, match[1]] : void 0;
|
|
3772
|
+
});
|
|
3773
|
+
return new Map(results.filter((entry) => entry !== void 0));
|
|
3774
|
+
}
|
|
3775
|
+
async function latestGoVersions(cwd) {
|
|
3776
|
+
const result = await runCaptureAsync("go", ["list", "-m", "-u", "-json", "all"], cwd);
|
|
3777
|
+
if (result.status !== 0) {
|
|
3778
|
+
return /* @__PURE__ */ new Map();
|
|
3779
|
+
}
|
|
3780
|
+
const latest = /* @__PURE__ */ new Map();
|
|
3781
|
+
for (const chunk of result.stdout.split(/^\}\s*$/m)) {
|
|
3782
|
+
const text = `${chunk.trim()}${chunk.trim() ? "}" : ""}`;
|
|
3783
|
+
if (!text.startsWith("{")) {
|
|
3784
|
+
continue;
|
|
3785
|
+
}
|
|
3786
|
+
try {
|
|
3787
|
+
const module_ = JSON.parse(text);
|
|
3788
|
+
if (module_.Path && module_.Update?.Version) {
|
|
3789
|
+
latest.set(module_.Path, module_.Update.Version);
|
|
3790
|
+
}
|
|
3791
|
+
} catch {
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3794
|
+
return latest;
|
|
3795
|
+
}
|
|
3796
|
+
async function latestPubVersions(cwd) {
|
|
3797
|
+
const result = await runCaptureAsync("flutter", ["pub", "outdated", "--json"], cwd);
|
|
3798
|
+
if (result.status !== 0) {
|
|
3799
|
+
return /* @__PURE__ */ new Map();
|
|
3800
|
+
}
|
|
3801
|
+
try {
|
|
3802
|
+
const report = JSON.parse(result.stdout);
|
|
3803
|
+
const latest = /* @__PURE__ */ new Map();
|
|
3804
|
+
const packages = report.packages ?? [];
|
|
3805
|
+
for (const entry of packages) {
|
|
3806
|
+
if (entry.package && entry.latest?.version) {
|
|
3807
|
+
latest.set(entry.package, entry.latest.version);
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
return latest;
|
|
3811
|
+
} catch {
|
|
3812
|
+
return /* @__PURE__ */ new Map();
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
async function latestVersions(ecosystem, names, cwd) {
|
|
3816
|
+
switch (ecosystem) {
|
|
3817
|
+
case "npm": {
|
|
3818
|
+
return await latestNpmVersions(names, cwd);
|
|
3819
|
+
}
|
|
3820
|
+
case "pip": {
|
|
3821
|
+
return await latestPipVersions(names, cwd);
|
|
3822
|
+
}
|
|
3823
|
+
case "go": {
|
|
3824
|
+
return await latestGoVersions(cwd);
|
|
3825
|
+
}
|
|
3826
|
+
default: {
|
|
3827
|
+
return await latestPubVersions(cwd);
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
|
|
3832
|
+
// src/commands/up.ts
|
|
3833
|
+
function currentVersion(workspaceRoot, sites) {
|
|
3834
|
+
const installed = resolvedVersion(workspaceRoot, sites[0].ecosystem, sites[0].name);
|
|
3835
|
+
if (installed) {
|
|
3836
|
+
return installed;
|
|
3837
|
+
}
|
|
3838
|
+
if (sites[0].ecosystem === "go") {
|
|
3839
|
+
return bare(sites[0].spec);
|
|
3840
|
+
}
|
|
3841
|
+
const highest = highestSpec(sites.map((site) => site.spec));
|
|
3842
|
+
return highest ? specVersion(highest) : void 0;
|
|
3843
|
+
}
|
|
3844
|
+
async function collectOutdated(workspaceRoot, ecosystems) {
|
|
3845
|
+
const present = ecosystems.filter((ecosystem) => hasEcosystem(workspaceRoot, ecosystem));
|
|
3846
|
+
const inventory = collectInventory(workspaceRoot, present);
|
|
3847
|
+
const outdated = [];
|
|
3848
|
+
for (const ecosystem of present) {
|
|
3849
|
+
const forEcosystem = [...inventory].filter(
|
|
3850
|
+
([name, sites]) => offerable(workspaceRoot, ecosystem, name, sites)
|
|
3851
|
+
);
|
|
3852
|
+
const latest = await latestVersions(
|
|
3853
|
+
ecosystem,
|
|
3854
|
+
forEcosystem.map(([name]) => name),
|
|
3855
|
+
workspaceRoot
|
|
3856
|
+
);
|
|
3857
|
+
outdated.push(...upgradable(workspaceRoot, ecosystem, forEcosystem, latest));
|
|
3858
|
+
}
|
|
3859
|
+
return outdated.toSorted(
|
|
3860
|
+
(left, right) => UPDATE_KINDS.indexOf(left.kind) - UPDATE_KINDS.indexOf(right.kind) || left.name.localeCompare(right.name)
|
|
3861
|
+
);
|
|
3862
|
+
}
|
|
3863
|
+
function offerable(workspaceRoot, ecosystem, name, sites) {
|
|
3864
|
+
if (sites[0].ecosystem !== ecosystem || sites[0].section === "indirect") {
|
|
3865
|
+
return false;
|
|
3866
|
+
}
|
|
3867
|
+
if (sites.every((site) => site.section === PEER_SECTION)) {
|
|
3868
|
+
return false;
|
|
3869
|
+
}
|
|
3870
|
+
if (ecosystem !== "npm") {
|
|
3871
|
+
return true;
|
|
3872
|
+
}
|
|
3873
|
+
return !isWorkspaceProject(workspaceRoot, name) && !isAliasedInstall(workspaceRoot, name);
|
|
3874
|
+
}
|
|
3875
|
+
function bare(version) {
|
|
3876
|
+
return version.replace(/^v/, "");
|
|
3877
|
+
}
|
|
3878
|
+
function upgradable(workspaceRoot, ecosystem, declared, latest) {
|
|
3879
|
+
const outdated = [];
|
|
3880
|
+
for (const [name, sites] of declared) {
|
|
3881
|
+
const newest = latest.get(name);
|
|
3882
|
+
const current = currentVersion(workspaceRoot, sites);
|
|
3883
|
+
if (!newest || !current || !isNewer(bare(newest), bare(current))) {
|
|
3884
|
+
continue;
|
|
3885
|
+
}
|
|
3886
|
+
outdated.push({
|
|
3887
|
+
name,
|
|
3888
|
+
ecosystem,
|
|
3889
|
+
current,
|
|
3890
|
+
latest: newest,
|
|
3891
|
+
kind: classify(bare(current), bare(newest)),
|
|
3892
|
+
sites
|
|
3893
|
+
});
|
|
3894
|
+
}
|
|
3895
|
+
return outdated;
|
|
3896
|
+
}
|
|
3897
|
+
function row(entry, widths) {
|
|
3898
|
+
const sections = [...new Set(entry.sites.map((site) => site.section))].join("/");
|
|
3899
|
+
const projects = [...new Set(entry.sites.map((site) => site.project))].join(", ");
|
|
3900
|
+
return [
|
|
3901
|
+
entry.name.padEnd(widths.name),
|
|
3902
|
+
sections.padEnd(widths.section),
|
|
3903
|
+
`${entry.current.padStart(widths.current)} \u203A ${entry.latest}`,
|
|
3904
|
+
projects
|
|
3905
|
+
].join(" ");
|
|
3906
|
+
}
|
|
3907
|
+
function columnWidths(entries) {
|
|
3908
|
+
return {
|
|
3909
|
+
name: widthOf(entries.map((entry) => entry.name)),
|
|
3910
|
+
section: widthOf(
|
|
3911
|
+
entries.map((entry) => [...new Set(entry.sites.map((site) => site.section))].join("/"))
|
|
3912
|
+
),
|
|
3913
|
+
current: widthOf(entries.map((entry) => entry.current))
|
|
3914
|
+
};
|
|
3915
|
+
}
|
|
3916
|
+
function widthOf(values) {
|
|
3917
|
+
return Math.max(0, ...values.map((value) => value.length));
|
|
3918
|
+
}
|
|
3919
|
+
function reportOutdated(outdated) {
|
|
3920
|
+
const widths = columnWidths(outdated);
|
|
3921
|
+
for (const kind of UPDATE_KINDS) {
|
|
3922
|
+
const section = outdated.filter((entry) => entry.kind === kind);
|
|
3923
|
+
if (section.length === 0) {
|
|
3924
|
+
continue;
|
|
3925
|
+
}
|
|
3926
|
+
const { title, blurb } = UPDATE_KIND_LABELS[kind];
|
|
3927
|
+
logger.info("");
|
|
3928
|
+
logger.info(`${title} ${blurb}`);
|
|
3929
|
+
for (const entry of section) {
|
|
3930
|
+
logger.info(` ${row(entry, widths)}`);
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
async function promptForUpdates(outdated) {
|
|
3935
|
+
const widths = columnWidths(outdated);
|
|
3936
|
+
const choices = [];
|
|
3937
|
+
for (const kind of UPDATE_KINDS) {
|
|
3938
|
+
const section = outdated.filter((entry) => entry.kind === kind);
|
|
3939
|
+
if (section.length === 0) {
|
|
3940
|
+
continue;
|
|
3941
|
+
}
|
|
3942
|
+
const { title, blurb } = UPDATE_KIND_LABELS[kind];
|
|
3943
|
+
choices.push(new import_prompts9.Separator(`
|
|
3944
|
+
${title} ${blurb}`));
|
|
3945
|
+
for (const entry of section) {
|
|
3946
|
+
choices.push({ name: row(entry, widths), value: entry });
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
return await (0, import_prompts9.checkbox)({
|
|
3950
|
+
message: "Choose which packages to update.",
|
|
3951
|
+
choices,
|
|
3952
|
+
pageSize: 20
|
|
3953
|
+
});
|
|
3954
|
+
}
|
|
3955
|
+
function applyUpdate(entry) {
|
|
3956
|
+
const changed = [];
|
|
3957
|
+
for (const site of entry.sites) {
|
|
3958
|
+
if (!site.rewritable) {
|
|
3959
|
+
continue;
|
|
3960
|
+
}
|
|
3961
|
+
const spec = `${rangeOperator(site.spec)}${entry.latest}`;
|
|
3962
|
+
if (rewriteSpec(site, spec)) {
|
|
3963
|
+
changed.push(site.manifestPath);
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
return changed;
|
|
3967
|
+
}
|
|
3968
|
+
function reinstall(workspaceRoot, ecosystems) {
|
|
3969
|
+
const commands = [
|
|
3970
|
+
["npm", "npm", ["install"]],
|
|
3971
|
+
["pip", "npm", ["run", "python:install"]],
|
|
3972
|
+
["pub", "flutter", ["pub", "get"]],
|
|
3973
|
+
["go", "go", ["mod", "tidy"]]
|
|
3974
|
+
];
|
|
3975
|
+
for (const [ecosystem, command, arguments_] of commands) {
|
|
3976
|
+
if (!ecosystems.has(ecosystem)) {
|
|
3977
|
+
continue;
|
|
3978
|
+
}
|
|
3979
|
+
if (ecosystem === "pip" && !hasPythonInstallScript(workspaceRoot)) {
|
|
3980
|
+
logger.warn(
|
|
3981
|
+
"This workspace has no 'python:install' script \u2014 run 'mnci upgrade' to add it, then reinstall by hand."
|
|
3982
|
+
);
|
|
3983
|
+
continue;
|
|
3984
|
+
}
|
|
3985
|
+
logger.step(`Reinstalling ${ecosystem} dependencies (${command} ${arguments_.join(" ")})`);
|
|
3986
|
+
if (runShell(command, arguments_, workspaceRoot) !== 0) {
|
|
3987
|
+
logger.warn(
|
|
3988
|
+
`${command} ${arguments_.join(" ")} failed \u2014 the manifests were updated, so re-run it once the cause is fixed.`
|
|
3989
|
+
);
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
function hasPythonInstallScript(workspaceRoot) {
|
|
3994
|
+
try {
|
|
3995
|
+
const manifest = readJson(
|
|
3996
|
+
(0, import_node_path18.join)(workspaceRoot, "package.json")
|
|
3997
|
+
);
|
|
3998
|
+
return manifest.scripts?.["python:install"] !== void 0;
|
|
3999
|
+
} catch {
|
|
4000
|
+
return false;
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
function applyGoUpdates(workspaceRoot, entries) {
|
|
4004
|
+
for (const entry of entries) {
|
|
4005
|
+
logger.step(`go get ${entry.name}@${entry.latest}`);
|
|
4006
|
+
if (runShell("go", ["get", `${entry.name}@${entry.latest}`], workspaceRoot) !== 0) {
|
|
4007
|
+
logger.warn(`go get ${entry.name}@${entry.latest} failed \u2014 left at ${entry.current}.`);
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
async function runUp(workspaceRoot, options) {
|
|
4012
|
+
if (!fileExists((0, import_node_path18.join)(workspaceRoot, "nx.json"))) {
|
|
4013
|
+
throw new Error(`No nx.json found in ${workspaceRoot} \u2014 run 'mnci up' from the workspace root.`);
|
|
4014
|
+
}
|
|
4015
|
+
const ecosystems = resolveEcosystems(options.ecosystem);
|
|
4016
|
+
for (const ecosystem of ecosystems) {
|
|
4017
|
+
if (!hasEcosystem(workspaceRoot, ecosystem)) {
|
|
4018
|
+
logger.info(`\u2298 SKIPPED ${ecosystem} \u2014 no ${ecosystem} project in this workspace.`);
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
logger.step("Querying registries for the latest published versions");
|
|
4022
|
+
const outdated = await collectOutdated(workspaceRoot, ecosystems);
|
|
4023
|
+
if (outdated.length === 0) {
|
|
4024
|
+
logger.success("Every dependency is on its latest published version.");
|
|
4025
|
+
return;
|
|
4026
|
+
}
|
|
4027
|
+
const readOnly = options.check ?? !process.stdout.isTTY;
|
|
4028
|
+
if (readOnly) {
|
|
4029
|
+
reportOutdated(outdated);
|
|
4030
|
+
logger.info("");
|
|
4031
|
+
logger.info(`${outdated.length} package(s) have a newer release. Run 'mnci up' to update them.`);
|
|
4032
|
+
return;
|
|
4033
|
+
}
|
|
4034
|
+
const selected = options.yes ? [...outdated] : await promptForUpdates(outdated);
|
|
4035
|
+
if (selected.length === 0) {
|
|
4036
|
+
logger.info("Nothing selected; no manifest was changed.");
|
|
4037
|
+
return;
|
|
4038
|
+
}
|
|
4039
|
+
const goSelections = selected.filter((entry) => entry.ecosystem === "go");
|
|
4040
|
+
const changed = selected.filter((entry) => entry.ecosystem !== "go").flatMap((entry) => applyUpdate(entry));
|
|
4041
|
+
const changedManifests = new Set(changed);
|
|
4042
|
+
for (const path of changedManifests) {
|
|
4043
|
+
logger.detail(`updated ${path}`);
|
|
4044
|
+
}
|
|
4045
|
+
applyGoUpdates(workspaceRoot, goSelections);
|
|
4046
|
+
if (options.install !== false) {
|
|
4047
|
+
reinstall(workspaceRoot, new Set(selected.map((entry) => entry.ecosystem)));
|
|
4048
|
+
}
|
|
4049
|
+
if (changed.length > 0) {
|
|
4050
|
+
syncProjectReferences(workspaceRoot);
|
|
4051
|
+
logger.step("Formatting the changed manifests (eslint --fix)");
|
|
4052
|
+
runFormatter(workspaceRoot);
|
|
4053
|
+
}
|
|
4054
|
+
logger.success(
|
|
4055
|
+
`Updated ${selected.length} package(s). Review the changes with \`git diff\` before committing.`
|
|
4056
|
+
);
|
|
4057
|
+
}
|
|
4058
|
+
|
|
2945
4059
|
// src/util/versionChecker.ts
|
|
2946
|
-
var
|
|
2947
|
-
var
|
|
4060
|
+
var import_node_fs11 = require("fs");
|
|
4061
|
+
var import_node_path19 = require("path");
|
|
2948
4062
|
var import_cross_spawn2 = __toESM(require("cross-spawn"));
|
|
2949
4063
|
var PACKAGE_NAME = "@mnci/cli";
|
|
2950
4064
|
var REGISTRY_TIMEOUT_MS = 2e3;
|
|
2951
|
-
function checkForUpdate(
|
|
4065
|
+
function checkForUpdate(currentVersion2) {
|
|
2952
4066
|
try {
|
|
2953
4067
|
if (!process.stdout.isTTY) {
|
|
2954
4068
|
return;
|
|
2955
4069
|
}
|
|
2956
4070
|
setImmediate(() => {
|
|
2957
4071
|
try {
|
|
2958
|
-
reportIfOutdated(
|
|
4072
|
+
reportIfOutdated(currentVersion2);
|
|
2959
4073
|
} catch {
|
|
2960
4074
|
}
|
|
2961
4075
|
});
|
|
2962
4076
|
} catch {
|
|
2963
4077
|
}
|
|
2964
4078
|
}
|
|
2965
|
-
function reportIfOutdated(
|
|
4079
|
+
function reportIfOutdated(currentVersion2) {
|
|
2966
4080
|
const result = import_cross_spawn2.default.sync("npm", ["view", PACKAGE_NAME, "version"], {
|
|
2967
4081
|
encoding: "utf8",
|
|
2968
4082
|
timeout: REGISTRY_TIMEOUT_MS,
|
|
@@ -2972,12 +4086,12 @@ function reportIfOutdated(currentVersion) {
|
|
|
2972
4086
|
return;
|
|
2973
4087
|
}
|
|
2974
4088
|
const latestVersion = (result.stdout ?? "").trim();
|
|
2975
|
-
if (!latestVersion || !isNewerVersion(latestVersion,
|
|
4089
|
+
if (!latestVersion || !isNewerVersion(latestVersion, currentVersion2)) {
|
|
2976
4090
|
return;
|
|
2977
4091
|
}
|
|
2978
4092
|
logger.info("");
|
|
2979
4093
|
logger.info(`A new version of ${PACKAGE_NAME} is available: ${latestVersion}`);
|
|
2980
|
-
logger.info(` Current version: ${
|
|
4094
|
+
logger.info(` Current version: ${currentVersion2}`);
|
|
2981
4095
|
logger.info(` Update with: npm install -g ${PACKAGE_NAME}@latest`);
|
|
2982
4096
|
logger.info("");
|
|
2983
4097
|
}
|
|
@@ -3000,8 +4114,8 @@ function releaseTriple(version) {
|
|
|
3000
4114
|
}
|
|
3001
4115
|
function readCliVersion(packageDirectory) {
|
|
3002
4116
|
try {
|
|
3003
|
-
const manifestPath = (0,
|
|
3004
|
-
const manifest = JSON.parse((0,
|
|
4117
|
+
const manifestPath = (0, import_node_path19.join)(packageDirectory, "..", "package.json");
|
|
4118
|
+
const manifest = JSON.parse((0, import_node_fs11.readFileSync)(manifestPath, "utf8"));
|
|
3005
4119
|
return manifest.version ?? "0.0.0";
|
|
3006
4120
|
} catch {
|
|
3007
4121
|
return "0.0.0";
|
|
@@ -3031,6 +4145,16 @@ function buildProgram(cliVersion) {
|
|
|
3031
4145
|
).action(() => {
|
|
3032
4146
|
runDoctor(process.cwd());
|
|
3033
4147
|
});
|
|
4148
|
+
program.command("sync").description(
|
|
4149
|
+
"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"
|
|
4150
|
+
).option("--check", "report drift and exit non-zero without writing anything").option("--ecosystem <name>", "restrict to one ecosystem: npm | pip | pub | go").action((options) => {
|
|
4151
|
+
runSync(process.cwd(), options);
|
|
4152
|
+
});
|
|
4153
|
+
program.command("up").description(
|
|
4154
|
+
"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"
|
|
4155
|
+
).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) => {
|
|
4156
|
+
await runUp(process.cwd(), options);
|
|
4157
|
+
});
|
|
3034
4158
|
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
4159
|
"--framework <framework>",
|
|
3036
4160
|
"node-app only: express | fastify | koa | nest | none (default: none)"
|