@mnci/cli 1.19.0 → 1.19.2
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 +23 -1
- package/dist/cli.js +68 -33
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -280,7 +280,8 @@ config (`@mnci/eslint-config`'s `nonJs()`) for the file types oxlint cannot read
|
|
|
280
280
|
| JS/TS lint | ESLint, 452 rules | oxlint, 206 native + 225 bridged |
|
|
281
281
|
| YAML/TOML/MD/CSS/HTML/JSON lint | ESLint | **ESLint** (unchanged) |
|
|
282
282
|
| `@nx/dependency-checks` | yes | **yes** (unchanged) |
|
|
283
|
-
| Formatter | Prettier | oxfmt (~
|
|
283
|
+
| Formatter | Prettier | oxfmt (~6x faster on this repo) |
|
|
284
|
+
| Formats `.toml` | no — Prettier has no TOML parser | **yes** |
|
|
284
285
|
| Config files | `eslint.config.mjs`, `.prettierrc.mjs` | `oxlint.config.ts`, `.oxfmtrc.json`, `eslint.config.mjs` |
|
|
285
286
|
| VS Code extension | ESLint + Prettier | Oxc (covers both) + ESLint |
|
|
286
287
|
|
|
@@ -290,6 +291,27 @@ all reported by nothing. Switching modes with `mnci upgrade --linter=<other>`
|
|
|
290
291
|
rewrites the config files **and deletes the mode you left**, so a workspace never
|
|
291
292
|
carries two formatter configs.
|
|
292
293
|
|
|
294
|
+
**Exactly one formatter is declared, and that is a fix rather than tidiness.**
|
|
295
|
+
`prettier` under `eslint`, `oxfmt` under `oxlint`, and switching modes drops the
|
|
296
|
+
other from `devDependencies`. prettier stays in `node_modules` either way, since
|
|
297
|
+
`@mnci/eslint-config` depends on it — so what matters is the _declaration_:
|
|
298
|
+
`esbenp.prettier-vscode` resolves the formatter from the **project's**
|
|
299
|
+
dependencies, so an oxlint workspace that still declared prettier is one where a
|
|
300
|
+
globally installed Prettier extension reformats on save against the opinion oxfmt
|
|
301
|
+
is not applying, while `npm run format:check` (oxfmt) reports the result as
|
|
302
|
+
unformatted. Both configs valid, both binaries resolvable, neither one wrong on
|
|
303
|
+
its own — the `.prettierrc` precedence bug in another costume. `mnci doctor`
|
|
304
|
+
checks it.
|
|
305
|
+
|
|
306
|
+
**oxfmt formats `.toml`; Prettier cannot.** `prettier` on a `.toml` exits with
|
|
307
|
+
`No parser could be inferred for file`, so an oxlint workspace gets a formatted
|
|
308
|
+
`pyproject.toml` and the default cannot — the one gap
|
|
309
|
+
`@mnci/eslint-config`'s parser-only TOML block has always documented as
|
|
310
|
+
unenforceable. It is the only file type where the two formatters differ in what
|
|
311
|
+
they can parse, which is why `[toml]` is pinned for format-on-save under `oxlint`
|
|
312
|
+
only: pinning it under `eslint` would route the file to a formatter that errors
|
|
313
|
+
on it.
|
|
314
|
+
|
|
293
315
|
**Finding and overriding a rule.** One root config importing a package is easy to
|
|
294
316
|
own and hard to read: nothing in three lines says which of ~20 plugins reported
|
|
295
317
|
the thing you disagree with. Every block in `@mnci/eslint-config` therefore
|
package/dist/cli.js
CHANGED
|
@@ -80,11 +80,12 @@ function runNpx(arguments_, cwd) {
|
|
|
80
80
|
throw new Error(`npx ${arguments_.join(" ")} failed with exit code ${status}`);
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
function
|
|
84
|
-
const
|
|
83
|
+
function runFormatter(cwd, linter, target = ".") {
|
|
84
|
+
const [command, arguments_] = linter === "oxlint" ? ["oxfmt", ["--write", target]] : ["prettier", ["--write", "--log-level", "warn", target]];
|
|
85
|
+
const status = runShell("npx", [command, ...arguments_], cwd);
|
|
85
86
|
if (status !== 0) {
|
|
86
87
|
logger.warn(
|
|
87
|
-
|
|
88
|
+
`${command} could not format '${target}' (exit code ${status}). The project was generated; run 'npm run format' to normalise it.`
|
|
88
89
|
);
|
|
89
90
|
}
|
|
90
91
|
}
|
|
@@ -1215,7 +1216,7 @@ async function runAdd(kind, name, options) {
|
|
|
1215
1216
|
case "python-vendor": {
|
|
1216
1217
|
await addPythonVendor(workspaceRoot, resolvedName, options, kindProvided);
|
|
1217
1218
|
syncProjectReferences(workspaceRoot);
|
|
1218
|
-
|
|
1219
|
+
runFormatter(workspaceRoot, stack.linter);
|
|
1219
1220
|
return;
|
|
1220
1221
|
}
|
|
1221
1222
|
default: {
|
|
@@ -1226,7 +1227,7 @@ async function runAdd(kind, name, options) {
|
|
|
1226
1227
|
}
|
|
1227
1228
|
}
|
|
1228
1229
|
syncProjectReferences(workspaceRoot);
|
|
1229
|
-
|
|
1230
|
+
runFormatter(workspaceRoot, stack.linter);
|
|
1230
1231
|
logger.success(`Added ${resolvedKind} '${resolvedName}'.`);
|
|
1231
1232
|
}
|
|
1232
1233
|
function syncProjectReferences(workspaceRoot) {
|
|
@@ -1243,7 +1244,14 @@ function readWorkspaceStack(workspaceRoot) {
|
|
|
1243
1244
|
);
|
|
1244
1245
|
const stack = nxJson.mnci?.stack;
|
|
1245
1246
|
return {
|
|
1246
|
-
testRunner: stack?.testRunner === "vitest" ? "vitest" : "jest"
|
|
1247
|
+
testRunner: stack?.testRunner === "vitest" ? "vitest" : "jest",
|
|
1248
|
+
// Defaults to `eslint` when absent, the same call `mnci upgrade` makes and
|
|
1249
|
+
// for the same reason: every workspace generated before the choice existed
|
|
1250
|
+
// has no persisted value and is an ESLint workspace. Reading it at all is
|
|
1251
|
+
// the fix — this function returned `testRunner` alone, so `add` finished by
|
|
1252
|
+
// formatting an oxlint workspace with Prettier, against Prettier's defaults,
|
|
1253
|
+
// since the overlay had deleted the config that would have corrected it.
|
|
1254
|
+
linter: stack?.linter === "oxlint" ? "oxlint" : "eslint"
|
|
1247
1255
|
};
|
|
1248
1256
|
}
|
|
1249
1257
|
|
|
@@ -1435,16 +1443,17 @@ function eslintToolchainDependencies(nxVersion) {
|
|
|
1435
1443
|
eslint: ESLINT_VERSION,
|
|
1436
1444
|
"@nx/eslint": nxVersion,
|
|
1437
1445
|
"@nx/eslint-plugin": nxVersion,
|
|
1438
|
-
"@mnci/eslint-config": eslintConfigSpec()
|
|
1439
|
-
// Declared rather than left to hoisting. `@mnci/eslint-config` depends on
|
|
1440
|
-
// prettier, so `npx prettier` works either way — but the VS Code extension
|
|
1441
|
-
// resolves prettier from the PROJECT's dependencies and silently falls back
|
|
1442
|
-
// to its own bundled copy when it finds none. A workspace should declare the
|
|
1443
|
-
// formatter it formats with instead of relying on npm flattening someone
|
|
1444
|
-
// else's transitive dependency to the root.
|
|
1445
|
-
prettier: PRETTIER_VERSION
|
|
1446
|
+
"@mnci/eslint-config": eslintConfigSpec()
|
|
1446
1447
|
};
|
|
1447
1448
|
}
|
|
1449
|
+
var LINTER_ONLY_DEPENDENCIES = {
|
|
1450
|
+
eslint: ["prettier"],
|
|
1451
|
+
oxlint: ["@mnci/oxlint-config", "oxlint", "oxfmt"]
|
|
1452
|
+
};
|
|
1453
|
+
function withoutStaleLinterDependencies(devDeps, linter) {
|
|
1454
|
+
const stale = new Set(LINTER_ONLY_DEPENDENCIES[linter === "oxlint" ? "eslint" : "oxlint"]);
|
|
1455
|
+
return Object.fromEntries(Object.entries(devDeps).filter(([name]) => !stale.has(name)));
|
|
1456
|
+
}
|
|
1448
1457
|
function rootLintTarget(linter) {
|
|
1449
1458
|
if (linter !== "oxlint") return ROOT_LINT_TARGET;
|
|
1450
1459
|
return {
|
|
@@ -1610,8 +1619,13 @@ var FORMATTED_LANGUAGES = [
|
|
|
1610
1619
|
"jsonc",
|
|
1611
1620
|
"yaml",
|
|
1612
1621
|
"markdown",
|
|
1613
|
-
"css"
|
|
1622
|
+
"css",
|
|
1623
|
+
"html"
|
|
1614
1624
|
];
|
|
1625
|
+
var OXFMT_ONLY_LANGUAGES = ["toml"];
|
|
1626
|
+
function formattedLanguages(linter) {
|
|
1627
|
+
return [...FORMATTED_LANGUAGES, ...linter === "oxlint" ? OXFMT_ONLY_LANGUAGES : []];
|
|
1628
|
+
}
|
|
1615
1629
|
function vscodeExtensions(linter) {
|
|
1616
1630
|
return [...LINTER_EXTENSIONS[linter], ...SHARED_VSCODE_EXTENSIONS];
|
|
1617
1631
|
}
|
|
@@ -1649,7 +1663,7 @@ function vscodeSettings(linter) {
|
|
|
1649
1663
|
// while `.json`/`.jsonc`/`.yaml` were — exactly the three that had explicit
|
|
1650
1664
|
// entries here and nothing else did.
|
|
1651
1665
|
...Object.fromEntries(
|
|
1652
|
-
|
|
1666
|
+
formattedLanguages(linter).map((language) => [
|
|
1653
1667
|
`[${language}]`,
|
|
1654
1668
|
{ "editor.defaultFormatter": formatter }
|
|
1655
1669
|
])
|
|
@@ -2260,16 +2274,20 @@ function applyOverlay(workspaceRoot, options) {
|
|
|
2260
2274
|
...rootScripts(options.stack.linter)
|
|
2261
2275
|
};
|
|
2262
2276
|
const existingDevDeps = manifest.devDependencies;
|
|
2263
|
-
const devDeps =
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2277
|
+
const devDeps = withoutStaleLinterDependencies(
|
|
2278
|
+
{
|
|
2279
|
+
...existingDevDeps,
|
|
2280
|
+
...TS_COMPILER_DEPENDENCIES,
|
|
2281
|
+
// The preset pins `nx` itself; the ESLint plugins must match it exactly.
|
|
2282
|
+
...eslintToolchainDependencies(existingDevDeps?.nx ?? "latest"),
|
|
2283
|
+
// Additive for the linter, exclusive for the formatter: the hybrid keeps
|
|
2284
|
+
// the whole ESLint toolchain for the file types oxlint cannot parse, but
|
|
2285
|
+
// exactly one of prettier/oxfmt formats the workspace. Declaring both is
|
|
2286
|
+
// how an editor ends up applying the one CI does not check.
|
|
2287
|
+
...options.stack.linter === "oxlint" ? oxlintToolchainDependencies() : { prettier: PRETTIER_VERSION }
|
|
2288
|
+
},
|
|
2289
|
+
options.stack.linter
|
|
2290
|
+
);
|
|
2273
2291
|
const overrides = {
|
|
2274
2292
|
...manifest.overrides,
|
|
2275
2293
|
...ESLINT_PEER_OVERRIDES
|
|
@@ -2386,6 +2404,23 @@ function checkLinterModeIsConsistent(workspaceRoot) {
|
|
|
2386
2404
|
remedy: "run `mnci upgrade`, which removes the mode the workspace did not choose"
|
|
2387
2405
|
};
|
|
2388
2406
|
}
|
|
2407
|
+
function declaredDevDependencies(workspaceRoot) {
|
|
2408
|
+
const manifestPath = (0, import_node_path12.join)(workspaceRoot, "package.json");
|
|
2409
|
+
if (!fileExists(manifestPath)) return {};
|
|
2410
|
+
const manifest = readJson(manifestPath);
|
|
2411
|
+
return manifest.devDependencies ?? {};
|
|
2412
|
+
}
|
|
2413
|
+
function checkOneFormatterDeclared(workspaceRoot) {
|
|
2414
|
+
const linter = readMnciConfig(workspaceRoot).stack?.linter ?? "eslint";
|
|
2415
|
+
const stale = linter === "oxlint" ? "prettier" : "oxfmt";
|
|
2416
|
+
const declared = declaredDevDependencies(workspaceRoot)[stale] !== void 0;
|
|
2417
|
+
return {
|
|
2418
|
+
check: `only ${linter === "oxlint" ? "oxfmt" : "prettier"} is declared as the formatter`,
|
|
2419
|
+
ok: !declared,
|
|
2420
|
+
detail: `${stale} is declared but nothing runs it \u2014 the editor may still resolve and apply it, disagreeing with \`npm run format:check\``,
|
|
2421
|
+
remedy: "run `mnci upgrade`, which drops the formatter of the mode you left"
|
|
2422
|
+
};
|
|
2423
|
+
}
|
|
2389
2424
|
function checkOxlintToolchainDeclared(workspaceRoot) {
|
|
2390
2425
|
const linter = readMnciConfig(workspaceRoot).stack?.linter ?? "eslint";
|
|
2391
2426
|
if (linter !== "oxlint") {
|
|
@@ -2396,10 +2431,7 @@ function checkOxlintToolchainDeclared(workspaceRoot) {
|
|
|
2396
2431
|
remedy: ""
|
|
2397
2432
|
};
|
|
2398
2433
|
}
|
|
2399
|
-
const
|
|
2400
|
-
(0, import_node_path12.join)(workspaceRoot, "package.json")
|
|
2401
|
-
);
|
|
2402
|
-
const devDeps = manifest?.devDependencies ?? {};
|
|
2434
|
+
const devDeps = declaredDevDependencies(workspaceRoot);
|
|
2403
2435
|
const missing = ["oxlint", "oxfmt", "@mnci/oxlint-config"].filter(
|
|
2404
2436
|
(dependency) => devDeps[dependency] === void 0
|
|
2405
2437
|
);
|
|
@@ -2505,6 +2537,7 @@ function collectFindings(workspaceRoot) {
|
|
|
2505
2537
|
...checkEslintConfigs(workspaceRoot),
|
|
2506
2538
|
checkPrettierConfig(workspaceRoot),
|
|
2507
2539
|
checkLinterModeIsConsistent(workspaceRoot),
|
|
2540
|
+
checkOneFormatterDeclared(workspaceRoot),
|
|
2508
2541
|
checkOxlintToolchainDeclared(workspaceRoot),
|
|
2509
2542
|
checkEslintPlugin(nxJson),
|
|
2510
2543
|
checkResolvedEslint(workspaceRoot),
|
|
@@ -2621,8 +2654,10 @@ async function runNew(name, options) {
|
|
|
2621
2654
|
throw new Error(`npm install of the toolchain failed with exit code ${installStatus}`);
|
|
2622
2655
|
}
|
|
2623
2656
|
runShell("npx", ["husky"], workspaceRoot);
|
|
2624
|
-
logger.step(
|
|
2625
|
-
|
|
2657
|
+
logger.step(
|
|
2658
|
+
`Formatting the workspace (${stack.linter === "oxlint" ? "oxfmt" : "Prettier"}, JavaScript Standard Style)`
|
|
2659
|
+
);
|
|
2660
|
+
runFormatter(workspaceRoot, stack.linter);
|
|
2626
2661
|
logger.success("Done. Next steps:");
|
|
2627
2662
|
logger.info(` cd ${workspaceName}`);
|
|
2628
2663
|
logger.info(" mnci add react-app web # or: react-lib, react-internal-lib, node-app,");
|
|
@@ -2728,7 +2763,7 @@ function runUpgrade(workspaceRoot, options) {
|
|
|
2728
2763
|
"Re-applying the MoNecromanCI overlay (release config, .npmrc, commitlint, pipeline, stack)"
|
|
2729
2764
|
);
|
|
2730
2765
|
applyOverlay(workspaceRoot, resolved);
|
|
2731
|
-
|
|
2766
|
+
runFormatter(workspaceRoot, resolved.stack.linter);
|
|
2732
2767
|
logger.success("Done. Review the changes with `git diff` before committing.");
|
|
2733
2768
|
}
|
|
2734
2769
|
|