@mnci/cli 4.6.0 → 4.6.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 +57 -7
- package/dist/cli.js +83 -21
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -429,15 +429,20 @@ that would just fail felt worse than being upfront that it doesn't exist yet.
|
|
|
429
429
|
`{projectName}@{version}` tags, **tag-only git** (`commit: false`) — nothing
|
|
430
430
|
is ever pushed to `main`; future runs resolve versions from tag names. Also
|
|
431
431
|
fills in `namedInputs.sharedGlobals` with the root config files
|
|
432
|
-
(`eslint.config.mjs`, `
|
|
432
|
+
(`eslint.config.mjs`, `eslint.config.mnci.mjs`, `tsconfig.base.json`,
|
|
433
|
+
`package.json`), without which
|
|
433
434
|
`nx affected` on a pull request is blind to them: they live in no project, so
|
|
434
435
|
changing one marked only the root pseudo-project — which has no
|
|
435
436
|
lint/typecheck/test/build target — and the affected-scoped verify step ran
|
|
436
437
|
nothing at all while reporting green.
|
|
437
|
-
3. Writes
|
|
438
|
-
|
|
439
|
-
config
|
|
440
|
-
|
|
438
|
+
3. Writes **two** ESLint files, and only one of them is mnci's:
|
|
439
|
+
`eslint.config.mnci.mjs` holds the whole linting opinion (one import from
|
|
440
|
+
`@mnci/eslint-config`, plus a commented inventory naming every config block)
|
|
441
|
+
and is rewritten on every upgrade; `eslint.config.mjs` is the file ESLint
|
|
442
|
+
actually loads, imports that one, holds **your** blocks, and is written once
|
|
443
|
+
and then never touched again. See **Your half of the ESLint config** below.
|
|
444
|
+
There is no formatter config, because ESLint is the formatter. Also writes
|
|
445
|
+
`.npmrc` (publish auth — see **Publish auth** below),
|
|
441
446
|
`commitlint.config.mjs`, a husky `commit-msg` hook, the chosen CI provider's
|
|
442
447
|
pipeline file(s)
|
|
443
448
|
(`azure-pipelines.yml` and/or `.github/workflows/ci.yml`, `--ci`, default
|
|
@@ -460,7 +465,8 @@ same options `new` would have and calls the exact same `applyOverlay` `new`
|
|
|
460
465
|
itself calls — the one function that does every bit of `mnci`-owned file
|
|
461
466
|
writing (`nx.json`'s `release`/`sync`/`generators`/`namedInputs.sharedGlobals`/
|
|
462
467
|
`mnci` blocks, `.npmrc`,
|
|
463
|
-
`eslint.config.mjs
|
|
468
|
+
`eslint.config.mnci.mjs` (**not** `eslint.config.mjs` — see below),
|
|
469
|
+
`commitlint.config.mjs`,
|
|
464
470
|
`.husky/commit-msg`, the CI pipeline file(s), `.devcontainer/devcontainer.json`, the
|
|
465
471
|
`<workspace-name>.code-workspace` file, and the curated root `package.json`
|
|
466
472
|
scripts). Nothing else in the workspace — app/lib source, `project.json` targets
|
|
@@ -532,8 +538,52 @@ stack:
|
|
|
532
538
|
| --------------- | ------------------ | ------- | ---------------------------------------------------------------------------------------- |
|
|
533
539
|
| `--test-runner` | `jest` \| `vitest` | `jest` | `nx.json` generator `unitTestRunner` default; the hand-built function app follows it too |
|
|
534
540
|
|
|
541
|
+
### Your half of the ESLint config
|
|
542
|
+
|
|
543
|
+
There are two root ESLint files, and the split exists because the old
|
|
544
|
+
single-file layout lost work. `eslint.config.mjs` used to be mnci-owned and
|
|
545
|
+
rewritten wholesale on every `mnci upgrade` — so a block appended to it, in the
|
|
546
|
+
way a comment mnci itself wrote three lines above described, was deleted
|
|
547
|
+
without a word. Worse, `upgrade` then tells you to run `npm run format`, so the
|
|
548
|
+
first thing that happens after your overrides vanish is every file in the
|
|
549
|
+
repository being rewritten against the rules you thought you had changed.
|
|
550
|
+
|
|
551
|
+
So:
|
|
552
|
+
|
|
553
|
+
| File | Owner | On `mnci upgrade` |
|
|
554
|
+
| --- | --- | --- |
|
|
555
|
+
| `eslint.config.mnci.mjs` | mnci | rewritten every time |
|
|
556
|
+
| `eslint.config.mjs` | you | written once, then never touched |
|
|
557
|
+
|
|
558
|
+
`eslint.config.mjs` is what ESLint loads, because `eslint.config.mjs` is
|
|
559
|
+
ESLint's own default filename — the file the tool looks for has to be the one
|
|
560
|
+
you own, or the tool's default is the one mnci overwrites.
|
|
561
|
+
|
|
562
|
+
```js
|
|
563
|
+
import mnci from './eslint.config.mnci.mjs'
|
|
564
|
+
|
|
565
|
+
export default [
|
|
566
|
+
...mnci(),
|
|
567
|
+
{ name: 'local/legacy-app-allows-any', files: ['apps/legacy/**/*.ts'], rules: { … } },
|
|
568
|
+
]
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
The owned file exports a **function**, not a resolved array, so options still
|
|
572
|
+
reach `@mnci/eslint-config` from the file you own:
|
|
573
|
+
`...mnci({ verticalSlices: ['packages/*/src/**/*.ts'] })`. An array would have
|
|
574
|
+
had nowhere to receive them — mnci's own repository passes `verticalSlices`,
|
|
575
|
+
which is how that was caught.
|
|
576
|
+
|
|
577
|
+
**Upgrading an existing workspace.** If your `eslint.config.mjs` is still the
|
|
578
|
+
old single-file one and you never edited it, `mnci upgrade` moves it onto the
|
|
579
|
+
split for you: it replaces the file only when it provably holds nothing but
|
|
580
|
+
mnci's own output. If you did edit it, it is left exactly as it is and
|
|
581
|
+
`mnci doctor` reports that it no longer imports the rules, with the line to
|
|
582
|
+
add. mnci does not rewrite that file any more — which is the point, and also
|
|
583
|
+
why it cannot do this part for you.
|
|
584
|
+
|
|
535
585
|
**Linting and formatting are unified across the workspace, from exactly one
|
|
536
|
-
config
|
|
586
|
+
pair of config files.** The rules are three lines importing
|
|
537
587
|
[`@mnci/eslint-config`](../eslint-config/README.md); every `@nx/*` generator
|
|
538
588
|
drops a config into the project it creates, and `mnci add` deletes it. Projects
|
|
539
589
|
still get their `lint` target: `@nx/eslint/plugin` infers it by mapping config
|
package/dist/cli.js
CHANGED
|
@@ -419,6 +419,7 @@ function withEslintPlugin(nxJson) {
|
|
|
419
419
|
}
|
|
420
420
|
var SHARED_GLOBAL_INPUTS = [
|
|
421
421
|
"{workspaceRoot}/eslint.config.mjs",
|
|
422
|
+
"{workspaceRoot}/eslint.config.mnci.mjs",
|
|
422
423
|
"{workspaceRoot}/tsconfig.base.json",
|
|
423
424
|
"{workspaceRoot}/package.json"
|
|
424
425
|
];
|
|
@@ -599,26 +600,15 @@ var ESLINT_BLOCK_INVENTORY = `// WHAT IS IN HERE. Each line is one config block,
|
|
|
599
600
|
//
|
|
600
601
|
// To list them as ESLint actually resolves them: npx eslint --inspect-config
|
|
601
602
|
`;
|
|
602
|
-
var
|
|
603
|
+
var ESLINT_MNCI_FILENAME = "eslint.config.mnci.mjs";
|
|
604
|
+
var ESLINT_USER_FILENAME = "eslint.config.mjs";
|
|
605
|
+
var ESLINT_MNCI_CONFIG = `// GENERATED BY mnci. Every \`mnci upgrade\` overwrites this file.
|
|
606
|
+
//
|
|
607
|
+
// Do not edit it: put your own blocks in eslint.config.mjs next to it, which
|
|
608
|
+
// imports this one and which mnci never touches once it exists.
|
|
609
|
+
import mnci from '@mnci/eslint-config'
|
|
603
610
|
|
|
604
611
|
${ESLINT_BLOCK_INVENTORY}//
|
|
605
|
-
// TO OVERRIDE a rule, append a block AFTER the spread \u2014 later blocks win, so one
|
|
606
|
-
// of your own beats anything above it. Give it a name, so the inspector shows
|
|
607
|
-
// where the change came from:
|
|
608
|
-
//
|
|
609
|
-
// export default [
|
|
610
|
-
// ...mnci({ workspaceRoot: import.meta.dirname }),
|
|
611
|
-
// {
|
|
612
|
-
// name: 'local/legacy-app-allows-any',
|
|
613
|
-
// files: ['apps/legacy/**/*.ts'],
|
|
614
|
-
// rules: { '@typescript-eslint/no-explicit-any': 'off' }
|
|
615
|
-
// }
|
|
616
|
-
// ]
|
|
617
|
-
//
|
|
618
|
-
// Do NOT edit @mnci/eslint-config inside node_modules, and do not fork it: it is
|
|
619
|
-
// a dependency, so \`npm update\` brings rule fixes in the way it brings any
|
|
620
|
-
// other. An override here survives that; an edit to the package does not.
|
|
621
|
-
//
|
|
622
612
|
// FORMATTING IS LINTING HERE. There is no Prettier, no oxfmt and no
|
|
623
613
|
// \`format:check\` \u2014 \`npm run lint\` reports indentation, quotes and spacing as
|
|
624
614
|
// ordinary errors, and \`npm run format\` is \`eslint . --fix\`. So do not add a
|
|
@@ -630,8 +620,64 @@ ${ESLINT_BLOCK_INVENTORY}//
|
|
|
630
620
|
// enough on its own \u2014 neither needs a config file, and with none present they
|
|
631
621
|
// format against their own defaults (semicolons, double quotes), which is the
|
|
632
622
|
// inverse of Standard.
|
|
633
|
-
|
|
623
|
+
//
|
|
624
|
+
// Exported as a FUNCTION, not as the resolved array. @mnci/eslint-config takes
|
|
625
|
+
// options \u2014 \`verticalSlices\` is the one a workspace is most likely to want \u2014
|
|
626
|
+
// and an already-resolved array has nowhere to receive them. Calling it from
|
|
627
|
+
// eslint.config.mjs is what keeps every option reachable from the file you own.
|
|
628
|
+
//
|
|
629
|
+
// \`workspaceRoot\` is resolved HERE because both files sit at the root, and it
|
|
630
|
+
// is what enables the @nx/dependency-checks block.
|
|
631
|
+
//
|
|
632
|
+
// A named function declaration, exported inline. Both shapes matter and both
|
|
633
|
+
// are enforced by this very config: \`unicorn/no-anonymous-default-export\`
|
|
634
|
+
// rejects an anonymous arrow, and \`unicorn/default-export-style\` rejects
|
|
635
|
+
// declaring one above the export and referring to it. Either mistake fails a
|
|
636
|
+
// generated workspace's own lint on its first run.
|
|
637
|
+
export default function mnciConfig (options = {}) {
|
|
638
|
+
return mnci({ workspaceRoot: import.meta.dirname, ...options })
|
|
639
|
+
}
|
|
640
|
+
`;
|
|
641
|
+
var ESLINT_USER_CONFIG = `// This file is YOURS. mnci writes it once and never touches it again, so
|
|
642
|
+
// anything you add here survives \`mnci upgrade\`.
|
|
643
|
+
//
|
|
644
|
+
// The rules live in ./eslint.config.mnci.mjs, which mnci DOES rewrite on every
|
|
645
|
+
// upgrade \u2014 so put your changes here, not there.
|
|
646
|
+
import mnci from './${ESLINT_MNCI_FILENAME}'
|
|
647
|
+
|
|
648
|
+
// TO CONFIGURE the shared rules, pass options to mnci() below \u2014 e.g.
|
|
649
|
+
// \`...mnci({ verticalSlices: ['packages/*/src/**/*.ts'] })\`.
|
|
650
|
+
//
|
|
651
|
+
// TO OVERRIDE a rule, append a block AFTER the spread \u2014 later blocks win, so
|
|
652
|
+
// one of your own beats anything above it. Give it a name, so
|
|
653
|
+
// \`npx eslint --inspect-config\` shows where the change came from:
|
|
654
|
+
//
|
|
655
|
+
// {
|
|
656
|
+
// name: 'local/legacy-app-allows-any',
|
|
657
|
+
// files: ['apps/legacy/**/*.ts'],
|
|
658
|
+
// rules: { '@typescript-eslint/no-explicit-any': 'off' }
|
|
659
|
+
// }
|
|
660
|
+
//
|
|
661
|
+
// Do NOT edit @mnci/eslint-config inside node_modules, and do not fork it: it
|
|
662
|
+
// is a dependency, so \`npm update\` brings rule fixes in the way it brings any
|
|
663
|
+
// other. An override here survives that; an edit to the package does not.
|
|
664
|
+
export default [
|
|
665
|
+
...mnci(),
|
|
666
|
+
]
|
|
634
667
|
`;
|
|
668
|
+
function isUnmodifiedMnciEslintConfig(content) {
|
|
669
|
+
const code = content.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !line.startsWith("//"));
|
|
670
|
+
return code.length === 2 && code[0] === "import mnci from '@mnci/eslint-config'" && code[1] === "export default mnci({ workspaceRoot: import.meta.dirname })";
|
|
671
|
+
}
|
|
672
|
+
function writeEslintEntryPoint(workspaceRoot, onProgress) {
|
|
673
|
+
const path = (0, import_node_path2.join)(workspaceRoot, ESLINT_USER_FILENAME);
|
|
674
|
+
if ((0, import_node_fs2.existsSync)(path) && !isUnmodifiedMnciEslintConfig((0, import_node_fs2.readFileSync)(path, "utf8"))) {
|
|
675
|
+
onProgress(`${ESLINT_USER_FILENAME} \u2014 kept as it is, it is yours`);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
onProgress(`${ESLINT_USER_FILENAME} \u2014 the entry point, yours to edit from now on`);
|
|
679
|
+
writeFileEnsured(path, ESLINT_USER_CONFIG);
|
|
680
|
+
}
|
|
635
681
|
var VSCODE_RECOMMENDED_EXTENSIONS = [
|
|
636
682
|
"dbaeumer.vscode-eslint",
|
|
637
683
|
"nrwl.angular-console",
|
|
@@ -1620,8 +1666,9 @@ function applyOverlay(workspaceRoot, options, onProgress = () => {
|
|
|
1620
1666
|
const hookPath = (0, import_node_path2.join)(workspaceRoot, ".husky/commit-msg");
|
|
1621
1667
|
writeFileEnsured(hookPath, COMMIT_MSG_HOOK);
|
|
1622
1668
|
markExecutable(hookPath);
|
|
1623
|
-
onProgress(
|
|
1624
|
-
writeFileEnsured((0, import_node_path2.join)(workspaceRoot,
|
|
1669
|
+
onProgress(`${ESLINT_MNCI_FILENAME} \u2014 the shared lint AND formatting opinion`);
|
|
1670
|
+
writeFileEnsured((0, import_node_path2.join)(workspaceRoot, ESLINT_MNCI_FILENAME), ESLINT_MNCI_CONFIG);
|
|
1671
|
+
writeEslintEntryPoint(workspaceRoot, onProgress);
|
|
1625
1672
|
ensureEslintCacheIgnored(workspaceRoot);
|
|
1626
1673
|
for (const retired of RETIRED_FORMATTER_FILES) {
|
|
1627
1674
|
removeIfPresent((0, import_node_path2.join)(workspaceRoot, retired));
|
|
@@ -4485,6 +4532,21 @@ function checkEslintConfigs(workspaceRoot) {
|
|
|
4485
4532
|
ok: projectConfigs.length === 0,
|
|
4486
4533
|
detail: `found ${projectConfigs.length}: ${projectConfigs.join(", ")}`,
|
|
4487
4534
|
remedy: "run `mnci upgrade`, which sweeps {apps,libs,packages}/*/eslint.config.*"
|
|
4535
|
+
},
|
|
4536
|
+
...checkEslintEntryPointReachesTheRules(workspaceRoot)
|
|
4537
|
+
];
|
|
4538
|
+
}
|
|
4539
|
+
function checkEslintEntryPointReachesTheRules(workspaceRoot) {
|
|
4540
|
+
const rules = (0, import_node_path19.join)(workspaceRoot, ESLINT_MNCI_FILENAME);
|
|
4541
|
+
if (!(0, import_node_fs12.existsSync)(rules)) return [];
|
|
4542
|
+
const entryPoint = (0, import_node_path19.join)(workspaceRoot, ESLINT_USER_FILENAME);
|
|
4543
|
+
const source = (0, import_node_fs12.existsSync)(entryPoint) ? (0, import_node_fs12.readFileSync)(entryPoint, "utf8") : "";
|
|
4544
|
+
return [
|
|
4545
|
+
{
|
|
4546
|
+
check: `${ESLINT_USER_FILENAME} imports the mnci rules`,
|
|
4547
|
+
ok: source.includes(ESLINT_MNCI_FILENAME),
|
|
4548
|
+
detail: source === "" ? `${ESLINT_USER_FILENAME} is missing, so nothing loads the rules` : `${ESLINT_USER_FILENAME} never mentions ${ESLINT_MNCI_FILENAME}`,
|
|
4549
|
+
remedy: `make its first import \`import mnci from './${ESLINT_MNCI_FILENAME}'\` and spread \`...mnci()\` into the exported array, keeping your own blocks after it \u2014 mnci does not rewrite this file, so it cannot do this for you`
|
|
4488
4550
|
}
|
|
4489
4551
|
];
|
|
4490
4552
|
}
|