@mnci/cli 1.16.0 → 1.17.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 +28 -10
- package/dist/cli.js +111 -20
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -78,7 +78,7 @@ ever needed is noise that trains people to ignore the output.
|
|
|
78
78
|
| Check | The failure it catches |
|
|
79
79
|
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
80
80
|
| Exactly one root ESLint config, and no per-project ones | The config fragmenting — every `@nx/*` generator writes one, so each project ends up linting against whichever config sits nearest |
|
|
81
|
-
| No stray `.prettierrc` | It outranks `.prettierrc.
|
|
81
|
+
| No stray `.prettierrc` | It outranks `.prettierrc.mjs`, so the whole formatting opinion is silently discarded while both files look fine |
|
|
82
82
|
| `@nx/eslint/plugin` registered in `nx.json` | Without it `npm run lint` exits 0 while linting nothing |
|
|
83
83
|
| The **resolved** `eslint` major | A declared range and an installed version are different things — manifests once said `^10` while the pin said 9 |
|
|
84
84
|
| `.npmrc` matches the recorded registry | The two registry kinds get different files; an Azure workspace also needs its scope routed |
|
|
@@ -152,9 +152,11 @@ that would just fail felt worse than being upfront that it doesn't exist yet.
|
|
|
152
152
|
changing one marked only the root pseudo-project — which has no
|
|
153
153
|
lint/typecheck/test/build target — and the affected-scoped verify step ran
|
|
154
154
|
nothing at all while reporting green.
|
|
155
|
-
3. Writes `eslint.config.mjs` (
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
3. Writes `eslint.config.mjs` (one import from `@mnci/eslint-config` — the whole
|
|
156
|
+
linting opinion, in one root config — plus a commented inventory naming every
|
|
157
|
+
config block and how to override it), `.prettierrc.mjs` (which re-exports
|
|
158
|
+
`@mnci/eslint-config/prettier`, so lint and format ship as one package and
|
|
159
|
+
upgrade together) + `.prettierignore`, `.npmrc` (publish auth — see **Publish auth** below),
|
|
158
160
|
`commitlint.config.mjs`, a husky `commit-msg` hook, the chosen CI provider's
|
|
159
161
|
pipeline file(s)
|
|
160
162
|
(`azure-pipelines.yml` and/or `.github/workflows/ci.yml`, `--ci`, default
|
|
@@ -177,8 +179,8 @@ same options `new` would have and calls the exact same `applyOverlay` `new`
|
|
|
177
179
|
itself calls — the one function that does every bit of `mnci`-owned file
|
|
178
180
|
writing (`nx.json`'s `release`/`sync`/`generators`/`namedInputs.sharedGlobals`/
|
|
179
181
|
`mnci` blocks, `.npmrc`,
|
|
180
|
-
`eslint.config.mjs`, `.prettierrc.
|
|
181
|
-
`.husky/commit-msg`, the CI pipeline file(s), the
|
|
182
|
+
`eslint.config.mjs`, `.prettierrc.mjs`, `commitlint.config.mjs`,
|
|
183
|
+
`.husky/commit-msg`, the CI pipeline file(s), `.devcontainer/devcontainer.json`, the
|
|
182
184
|
`<workspace-name>.code-workspace` file, and the curated root `package.json`
|
|
183
185
|
scripts). Nothing else in the workspace — app/lib source, `project.json` targets
|
|
184
186
|
from `mnci add` — is ever touched, and it finishes by running Prettier over the
|
|
@@ -194,9 +196,10 @@ project's build/qa/start entry on upgrade.
|
|
|
194
196
|
has always done — one more reason to run `git diff` first, as the command's own
|
|
195
197
|
output tells you to:
|
|
196
198
|
|
|
197
|
-
- `create-nx-workspace`'s `.prettierrc` and `.vscode/`,
|
|
198
|
-
`.prettierrc.json
|
|
199
|
-
`.
|
|
199
|
+
- `create-nx-workspace`'s `.prettierrc` and `.vscode/`, plus mnci's own former
|
|
200
|
+
`.prettierrc.json`. The two Prettier files are deleted for the same reason:
|
|
201
|
+
both outrank `.prettierrc.mjs`, which Prettier would otherwise never reach.
|
|
202
|
+
`.vscode/` is superseded by the `.code-workspace` file.
|
|
200
203
|
- **every per-project `eslint.config.*` under `apps/`, `libs/` and
|
|
201
204
|
`packages/`.** This is the migration path for a workspace generated before
|
|
202
205
|
mnci owned linting: without it an upgrade would install the root config while
|
|
@@ -251,11 +254,26 @@ switch linting off workspace-wide.)
|
|
|
251
254
|
|
|
252
255
|
ESLint handles code quality only; **Prettier owns all formatting**, configured
|
|
253
256
|
for JavaScript Standard Style (no semicolons, single quotes, 2-space indents, no
|
|
254
|
-
trailing commas).
|
|
257
|
+
trailing commas). Both opinions come from the same package: `.prettierrc.mjs`
|
|
258
|
+
re-exports `@mnci/eslint-config/prettier`, so a formatting fix arrives through
|
|
259
|
+
`npm update` exactly as a rule fix does, and the two cannot drift into
|
|
260
|
+
contradicting each other. `mnci` runs Prettier itself at the end of `new` and every
|
|
255
261
|
`add`, so a generated workspace passes its own `format:check` immediately — Nx's
|
|
256
262
|
generators emit semicolons and double quotes, and without that pass the first
|
|
257
263
|
commit buries every real change under generator noise.
|
|
258
264
|
|
|
265
|
+
**Finding and overriding a rule.** One root config importing a package is easy to
|
|
266
|
+
own and hard to read: nothing in three lines says which of ~20 plugins reported
|
|
267
|
+
the thing you disagree with. Every block in `@mnci/eslint-config` therefore
|
|
268
|
+
carries a `name` — `mnci/base`, `mnci/react`, `mnci/type-aware`,
|
|
269
|
+
`mnci/prettier-compat` — and the generated `eslint.config.mjs` ships the full list
|
|
270
|
+
as a comment, next to the recipe for overriding one. `npx eslint --inspect-config`
|
|
271
|
+
lists them as ESLint actually resolves them, `npx eslint --print-config <file>`
|
|
272
|
+
shows the merged result for a single file, and an override is a named block
|
|
273
|
+
appended after the spread, since later blocks win. Don't edit the package inside
|
|
274
|
+
`node_modules`: it is a dependency, and `npm update` will replace it.
|
|
275
|
+
`packages/eslint-config/README.md` has the block-by-block table.
|
|
276
|
+
|
|
259
277
|
One caveat worth knowing: `space-before-function-paren`, Standard's signature
|
|
260
278
|
rule, is **not** enforced. Prettier rewrites `function f (a)` to `function f(a)`
|
|
261
279
|
on every run and has closed the corresponding option permanently, so enabling it
|
package/dist/cli.js
CHANGED
|
@@ -1350,21 +1350,55 @@ var COMMITLINT_CONFIG = `export default { extends: ['@commitlint/config-conventi
|
|
|
1350
1350
|
`;
|
|
1351
1351
|
var COMMIT_MSG_HOOK = `npx --no -- commitlint --edit "$1"
|
|
1352
1352
|
`;
|
|
1353
|
-
var PRETTIER_CONFIG =
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1353
|
+
var PRETTIER_CONFIG = `// The formatting half of @mnci/eslint-config.
|
|
1354
|
+
//
|
|
1355
|
+
// Prettier's options live in the same package as the ESLint rules on purpose:
|
|
1356
|
+
// linting and formatting are one decision. \`eslint-config-prettier\` is composed
|
|
1357
|
+
// last inside that package so every formatting rule defers to these settings, and
|
|
1358
|
+
// splitting the two across packages means a version pair that can drift until
|
|
1359
|
+
// \`npm run lint\` and \`npm run format:check\` disagree with each other.
|
|
1360
|
+
//
|
|
1361
|
+
// Consuming it as a shareable config also means a formatting fix reaches this
|
|
1362
|
+
// workspace through \`npm update\`, the same way a rule change does, instead of
|
|
1363
|
+
// waiting for \`mnci upgrade\` to rewrite a file.
|
|
1364
|
+
//
|
|
1365
|
+
// TO OVERRIDE an option, spread it rather than re-exporting:
|
|
1366
|
+
//
|
|
1367
|
+
// import mnci from '@mnci/eslint-config/prettier'
|
|
1368
|
+
// export default { ...mnci, printWidth: 120 }
|
|
1369
|
+
//
|
|
1370
|
+
// TO SEE the resolved options for a file: npx prettier --find-config-path <file>
|
|
1371
|
+
export { default } from '@mnci/eslint-config/prettier'
|
|
1362
1372
|
`;
|
|
1363
1373
|
var ESLINT_CONFIG_VERSION = "^0.1.0";
|
|
1364
1374
|
function eslintConfigSpec() {
|
|
1365
1375
|
return process.env.MNCI_ESLINT_CONFIG_SPEC ?? ESLINT_CONFIG_VERSION;
|
|
1366
1376
|
}
|
|
1367
1377
|
var ESLINT_VERSION = "^10.8.0";
|
|
1378
|
+
function devcontainerJson(workspaceName) {
|
|
1379
|
+
return `${toJson({
|
|
1380
|
+
name: workspaceName,
|
|
1381
|
+
image: `mcr.microsoft.com/devcontainers/typescript-node:${NODE_VERSION}-bookworm`,
|
|
1382
|
+
features: {
|
|
1383
|
+
"ghcr.io/devcontainers/features/python:1": { version: "3.12" },
|
|
1384
|
+
"ghcr.io/devcontainers/features/go:1": { version: "latest" }
|
|
1385
|
+
},
|
|
1386
|
+
// `npm ci` first: every guard after it runs through the workspace's own
|
|
1387
|
+
// scripts and Nx, which do not exist until the install completes.
|
|
1388
|
+
postCreateCommand: [
|
|
1389
|
+
"npm ci",
|
|
1390
|
+
"npm run python:install",
|
|
1391
|
+
GOLANGCI_LINT_INSTALL_GUARD,
|
|
1392
|
+
FLUTTER_SDK_INSTALL_GUARD
|
|
1393
|
+
].join(" && "),
|
|
1394
|
+
// The same recommendations the `.code-workspace` file carries, so opening
|
|
1395
|
+
// the folder in a container suggests the identical toolset.
|
|
1396
|
+
customizations: {
|
|
1397
|
+
vscode: { extensions: [...VSCODE_RECOMMENDED_EXTENSIONS] }
|
|
1398
|
+
}
|
|
1399
|
+
})}
|
|
1400
|
+
`;
|
|
1401
|
+
}
|
|
1368
1402
|
var ROOT_LINT_TARGET = {
|
|
1369
1403
|
executor: "nx:run-commands",
|
|
1370
1404
|
cache: true,
|
|
@@ -1391,8 +1425,61 @@ function eslintToolchainDependencies(nxVersion) {
|
|
|
1391
1425
|
"@mnci/eslint-config": eslintConfigSpec()
|
|
1392
1426
|
};
|
|
1393
1427
|
}
|
|
1428
|
+
var ESLINT_BLOCK_INVENTORY = `// WHAT IS IN HERE. Each line is one config block, by the \`name\` it carries.
|
|
1429
|
+
//
|
|
1430
|
+
// mnci/ignores paths never linted (dist, coverage, .venv, \u2026)
|
|
1431
|
+
// mnci/base JS/TS correctness \u2014 @eslint/js, eslint-plugin-unicorn,
|
|
1432
|
+
// -promise, -n, -unused-imports
|
|
1433
|
+
// typescript-eslint/* typescript-eslint's own recommended blocks
|
|
1434
|
+
// mnci/typescript* TS rules on top of them, no type information needed
|
|
1435
|
+
// mnci/type-aware* the rules that DO read types (no-floating-promises and
|
|
1436
|
+
// friends), scoped to {apps,libs,packages}/*/src
|
|
1437
|
+
// mnci/import-graph import cycles \u2014 eslint-plugin-import-x
|
|
1438
|
+
// mnci/react JSX/TSX \u2014 @eslint-react/eslint-plugin,
|
|
1439
|
+
// eslint-plugin-react-hooks, -react-refresh, -jsx-a11y
|
|
1440
|
+
// mnci/regexp* regex correctness \u2014 eslint-plugin-regexp
|
|
1441
|
+
// mnci/json mnci/jsonc mnci/json5
|
|
1442
|
+
// eslint-plugin-jsonc \u2014 comments are allowed in .jsonc
|
|
1443
|
+
// and tsconfig.json, forbidden in plain .json
|
|
1444
|
+
// mnci/yaml* eslint-plugin-yml \u2014 your CI pipeline files
|
|
1445
|
+
// mnci/toml/base* eslint-plugin-toml, PARSER ONLY: a malformed
|
|
1446
|
+
// pyproject.toml is a syntax error, nothing is styled
|
|
1447
|
+
// mnci/markdown @eslint/markdown
|
|
1448
|
+
// mnci/css @eslint/css
|
|
1449
|
+
// mnci/html @html-eslint/eslint-plugin
|
|
1450
|
+
// mnci/tests *.spec/*.test relaxations \u2014 eslint-plugin-jest
|
|
1451
|
+
// (Vitest's globals too; the two stacks share them)
|
|
1452
|
+
// mnci/nx-dependency-checks @nx/eslint-plugin, on publishable packages' manifests
|
|
1453
|
+
// mnci/prettier-compat eslint-config-prettier \u2014 switches off every rule
|
|
1454
|
+
// Prettier owns. Composed LAST, on purpose.
|
|
1455
|
+
// mnci/stylistic the 3 Standard rules Prettier does not touch
|
|
1456
|
+
//
|
|
1457
|
+
// To list them as ESLint actually resolves them: npx eslint --inspect-config
|
|
1458
|
+
`;
|
|
1394
1459
|
var ESLINT_CONFIG = `import mnci from '@mnci/eslint-config'
|
|
1395
1460
|
|
|
1461
|
+
${ESLINT_BLOCK_INVENTORY}//
|
|
1462
|
+
// TO OVERRIDE a rule, append a block AFTER the spread \u2014 later blocks win, so one
|
|
1463
|
+
// of your own beats anything above it. Give it a name, so the inspector shows
|
|
1464
|
+
// where the change came from:
|
|
1465
|
+
//
|
|
1466
|
+
// export default [
|
|
1467
|
+
// ...mnci({ workspaceRoot: import.meta.dirname }),
|
|
1468
|
+
// {
|
|
1469
|
+
// name: 'local/legacy-app-allows-any',
|
|
1470
|
+
// files: ['apps/legacy/**/*.ts'],
|
|
1471
|
+
// rules: { '@typescript-eslint/no-explicit-any': 'off' }
|
|
1472
|
+
// }
|
|
1473
|
+
// ]
|
|
1474
|
+
//
|
|
1475
|
+
// Do NOT edit @mnci/eslint-config inside node_modules, and do not fork it: it is
|
|
1476
|
+
// a dependency, so \`npm update\` brings rule fixes in the way it brings any
|
|
1477
|
+
// other. An override here survives that; an edit to the package does not.
|
|
1478
|
+
//
|
|
1479
|
+
// One override cannot work, and it is the one people reach for first:
|
|
1480
|
+
// \`space-before-function-paren\`. Prettier rewrites \`f (a)\` to \`f(a)\` on every
|
|
1481
|
+
// run, so switching that rule on makes \`npm run lint\` and \`npm run format:check\`
|
|
1482
|
+
// impossible to satisfy at the same time. Formatting lives in .prettierrc.mjs.
|
|
1396
1483
|
export default mnci({ workspaceRoot: import.meta.dirname })
|
|
1397
1484
|
`;
|
|
1398
1485
|
var PRETTIER_IGNORE = `node_modules
|
|
@@ -1414,6 +1501,12 @@ __pycache__
|
|
|
1414
1501
|
.ruff_cache
|
|
1415
1502
|
.dart_tool
|
|
1416
1503
|
`;
|
|
1504
|
+
var VSCODE_RECOMMENDED_EXTENSIONS = [
|
|
1505
|
+
"dbaeumer.vscode-eslint",
|
|
1506
|
+
"esbenp.prettier-vscode",
|
|
1507
|
+
"nrwl.angular-console",
|
|
1508
|
+
"firsttris.vscode-jest-runner"
|
|
1509
|
+
];
|
|
1417
1510
|
function vscodeWorkspace(workspaceName, existingTasks) {
|
|
1418
1511
|
return JSON.stringify(
|
|
1419
1512
|
{
|
|
@@ -1444,14 +1537,7 @@ function vscodeWorkspace(workspaceName, existingTasks) {
|
|
|
1444
1537
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
1445
1538
|
}
|
|
1446
1539
|
},
|
|
1447
|
-
extensions: {
|
|
1448
|
-
recommendations: [
|
|
1449
|
-
"dbaeumer.vscode-eslint",
|
|
1450
|
-
"esbenp.prettier-vscode",
|
|
1451
|
-
"nrwl.angular-console",
|
|
1452
|
-
"firsttris.vscode-jest-runner"
|
|
1453
|
-
]
|
|
1454
|
-
},
|
|
1540
|
+
extensions: { recommendations: [...VSCODE_RECOMMENDED_EXTENSIONS] },
|
|
1455
1541
|
tasks: {
|
|
1456
1542
|
version: existingTasks?.version ?? "2.0.0",
|
|
1457
1543
|
tasks: existingTasks?.tasks ?? []
|
|
@@ -1531,6 +1617,7 @@ var GO_TOOL_PATH_PRELUDE = `const fs=require('node:fs'),cp=require('node:child_p
|
|
|
1531
1617
|
var GO_TOOL_PATH_AZURE = `node -e "${GO_TOOL_PATH_PRELUDE}console.log('##vso[task.prependpath]'+bin)"`;
|
|
1532
1618
|
var GO_TOOL_PATH_GITHUB = `node -e "${GO_TOOL_PATH_PRELUDE}if(!process.env.GITHUB_PATH){console.log('Not running in GitHub Actions - skipping.');process.exit(0)}fs.appendFileSync(process.env.GITHUB_PATH,bin+${String.raw`'\n'`})"`;
|
|
1533
1619
|
var FLUTTER_SDK_VERSION = "3.44.8";
|
|
1620
|
+
var NODE_VERSION = "24";
|
|
1534
1621
|
var FLUTTER_SDK_DIRECTORY_EXPRESSION = `require('node:path').join(require('node:os').homedir(),'.mnci-flutter-${FLUTTER_SDK_VERSION}')`;
|
|
1535
1622
|
var FLUTTER_SDK_INSTALL_GUARD = `node -e "const fs=require('node:fs'),cp=require('node:child_process');if(!fs.existsSync('pubspec.yaml')){console.log('No Flutter projects - skipping.');process.exit(0)}if(cp.spawnSync('flutter',['--version'],{stdio:'ignore',shell:process.platform==='win32'}).status===0){console.log('Flutter SDK already on PATH - skipping.');process.exit(0)}const sdk=${FLUTTER_SDK_DIRECTORY_EXPRESSION};if(fs.existsSync(sdk)){console.log('Flutter SDK already installed at '+sdk+' - skipping.');process.exit(0)}process.exit(cp.spawnSync('git',['clone','--depth','1','--branch','${FLUTTER_SDK_VERSION}','https://github.com/flutter/flutter.git',sdk],{stdio:'inherit'}).status ?? 1)"`;
|
|
1536
1623
|
var FLUTTER_TOOL_PATH_PRELUDE = `const fs=require('node:fs'),path=require('node:path');if(!fs.existsSync('pubspec.yaml')){console.log('No Flutter projects - skipping.');process.exit(0)}const sdk=${FLUTTER_SDK_DIRECTORY_EXPRESSION};if(!fs.existsSync(sdk)){console.log('Flutter SDK was not installed by mnci (already on PATH) - skipping.');process.exit(0)}const bin=path.join(sdk,'bin');`;
|
|
@@ -1828,7 +1915,7 @@ jobs:
|
|
|
1828
1915
|
|
|
1829
1916
|
- uses: actions/setup-node@v4
|
|
1830
1917
|
with:
|
|
1831
|
-
node-version:
|
|
1918
|
+
node-version: ${NODE_VERSION}
|
|
1832
1919
|
# Caches ~/.npm keyed on package-lock.json, so \`npm ci\` restores from
|
|
1833
1920
|
# the local cache instead of re-downloading every tarball on every run.
|
|
1834
1921
|
# Nothing to invalidate by hand: the action keys on the lockfile, so a
|
|
@@ -2012,7 +2099,7 @@ updates:
|
|
|
2012
2099
|
schedule:
|
|
2013
2100
|
interval: weekly
|
|
2014
2101
|
`;
|
|
2015
|
-
var NX_SCAFFOLDING_TO_REMOVE = [".prettierrc", ".vscode"];
|
|
2102
|
+
var NX_SCAFFOLDING_TO_REMOVE = [".prettierrc", ".prettierrc.json", ".vscode"];
|
|
2016
2103
|
function removeNxScaffolding(workspaceRoot) {
|
|
2017
2104
|
for (const entry of NX_SCAFFOLDING_TO_REMOVE) {
|
|
2018
2105
|
(0, import_node_fs6.rmSync)((0, import_node_path11.join)(workspaceRoot, entry), { recursive: true, force: true });
|
|
@@ -2082,7 +2169,11 @@ function applyOverlay(workspaceRoot, options) {
|
|
|
2082
2169
|
writeFileEnsured(hookPath, COMMIT_MSG_HOOK);
|
|
2083
2170
|
markExecutable(hookPath);
|
|
2084
2171
|
writeFileEnsured((0, import_node_path11.join)(workspaceRoot, "eslint.config.mjs"), ESLINT_CONFIG);
|
|
2085
|
-
writeFileEnsured((0, import_node_path11.join)(workspaceRoot, ".prettierrc.
|
|
2172
|
+
writeFileEnsured((0, import_node_path11.join)(workspaceRoot, ".prettierrc.mjs"), PRETTIER_CONFIG);
|
|
2173
|
+
writeFileEnsured(
|
|
2174
|
+
(0, import_node_path11.join)(workspaceRoot, ".devcontainer/devcontainer.json"),
|
|
2175
|
+
devcontainerJson(options.workspaceName)
|
|
2176
|
+
);
|
|
2086
2177
|
writeFileEnsured((0, import_node_path11.join)(workspaceRoot, ".prettierignore"), PRETTIER_IGNORE);
|
|
2087
2178
|
removeNxScaffolding(workspaceRoot);
|
|
2088
2179
|
const codeWorkspacePath = (0, import_node_path11.join)(workspaceRoot, `${options.workspaceName}.code-workspace`);
|