@mnci/cli 4.5.0 → 4.6.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 +57 -7
- package/dist/cli.js +128 -25
- 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
|
@@ -313,8 +313,24 @@ function npmrcContent(registry, scope) {
|
|
|
313
313
|
if (registry.kind === "npm") {
|
|
314
314
|
return `; Publish authentication for the public npm registry.
|
|
315
315
|
;
|
|
316
|
-
; NODE_AUTH_TOKEN is exported by the generated CI's release step
|
|
317
|
-
;
|
|
316
|
+
; NODE_AUTH_TOKEN is exported by the generated CI's release step. Nothing needs
|
|
317
|
+
; it for day-to-day work here - installing, building and testing never
|
|
318
|
+
; authenticate.
|
|
319
|
+
;
|
|
320
|
+
; It is NOT harmless the moment you DO want to authenticate from this directory,
|
|
321
|
+
; which an earlier version of this comment claimed. A project .npmrc wins over
|
|
322
|
+
; your user one, so with NODE_AUTH_TOKEN unset this line hands npm an empty
|
|
323
|
+
; token and overrides 'npm login': inside this repo 'npm whoami' fails with a
|
|
324
|
+
; 401 and any write is refused. The refusal is easy to misread, because for a
|
|
325
|
+
; scoped package the registry answers a write you are not allowed to make with
|
|
326
|
+
;
|
|
327
|
+
; npm error 404 Not Found - PUT https://registry.npmjs.org/@scope%2fthing
|
|
328
|
+
;
|
|
329
|
+
; and everything above it is npm listing what it intended to send, so a long
|
|
330
|
+
; healthy-looking run still changes nothing.
|
|
331
|
+
;
|
|
332
|
+
; To publish or deprecate by hand, run it from outside this directory, or export
|
|
333
|
+
; a real token first.
|
|
318
334
|
//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}
|
|
319
335
|
|
|
320
336
|
; There is deliberately NO '${scope}:registry' line here. npmjs.org is already
|
|
@@ -403,6 +419,7 @@ function withEslintPlugin(nxJson) {
|
|
|
403
419
|
}
|
|
404
420
|
var SHARED_GLOBAL_INPUTS = [
|
|
405
421
|
"{workspaceRoot}/eslint.config.mjs",
|
|
422
|
+
"{workspaceRoot}/eslint.config.mnci.mjs",
|
|
406
423
|
"{workspaceRoot}/tsconfig.base.json",
|
|
407
424
|
"{workspaceRoot}/package.json"
|
|
408
425
|
];
|
|
@@ -583,26 +600,15 @@ var ESLINT_BLOCK_INVENTORY = `// WHAT IS IN HERE. Each line is one config block,
|
|
|
583
600
|
//
|
|
584
601
|
// To list them as ESLint actually resolves them: npx eslint --inspect-config
|
|
585
602
|
`;
|
|
586
|
-
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'
|
|
587
610
|
|
|
588
611
|
${ESLINT_BLOCK_INVENTORY}//
|
|
589
|
-
// TO OVERRIDE a rule, append a block AFTER the spread \u2014 later blocks win, so one
|
|
590
|
-
// of your own beats anything above it. Give it a name, so the inspector shows
|
|
591
|
-
// where the change came from:
|
|
592
|
-
//
|
|
593
|
-
// export default [
|
|
594
|
-
// ...mnci({ workspaceRoot: import.meta.dirname }),
|
|
595
|
-
// {
|
|
596
|
-
// name: 'local/legacy-app-allows-any',
|
|
597
|
-
// files: ['apps/legacy/**/*.ts'],
|
|
598
|
-
// rules: { '@typescript-eslint/no-explicit-any': 'off' }
|
|
599
|
-
// }
|
|
600
|
-
// ]
|
|
601
|
-
//
|
|
602
|
-
// Do NOT edit @mnci/eslint-config inside node_modules, and do not fork it: it is
|
|
603
|
-
// a dependency, so \`npm update\` brings rule fixes in the way it brings any
|
|
604
|
-
// other. An override here survives that; an edit to the package does not.
|
|
605
|
-
//
|
|
606
612
|
// FORMATTING IS LINTING HERE. There is no Prettier, no oxfmt and no
|
|
607
613
|
// \`format:check\` \u2014 \`npm run lint\` reports indentation, quotes and spacing as
|
|
608
614
|
// ordinary errors, and \`npm run format\` is \`eslint . --fix\`. So do not add a
|
|
@@ -614,8 +620,64 @@ ${ESLINT_BLOCK_INVENTORY}//
|
|
|
614
620
|
// enough on its own \u2014 neither needs a config file, and with none present they
|
|
615
621
|
// format against their own defaults (semicolons, double quotes), which is the
|
|
616
622
|
// inverse of Standard.
|
|
617
|
-
|
|
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
|
+
}
|
|
618
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
|
+
]
|
|
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
|
+
}
|
|
619
681
|
var VSCODE_RECOMMENDED_EXTENSIONS = [
|
|
620
682
|
"dbaeumer.vscode-eslint",
|
|
621
683
|
"nrwl.angular-console",
|
|
@@ -873,6 +935,10 @@ var NPM_AUDIT_STEP = `node -e "const cp=require('node:child_process');const r=cp
|
|
|
873
935
|
var VERIFY_TARGETS = "lint,typecheck,test,build";
|
|
874
936
|
var AFFECTED_OR_ALL_GUARD = `node -e "const cp=require('node:child_process');const T='${VERIFY_TARGETS}';const all=()=>process.exit(cp.spawnSync('npx nx run-many -t '+T,{stdio:'inherit',shell:true}).status ?? 1);const ref=process.env.GITHUB_BASE_REF||process.env.SYSTEM_PULLREQUEST_TARGETBRANCH||'';if(!ref){console.log('Not a pull request - verifying EVERY project.');all()}const target=ref.replace('refs/heads/','');const mergeBase=r=>{const o=cp.spawnSync('git',['merge-base',r,'HEAD'],{encoding:'utf8'});return o.status===0?o.stdout.trim():''};let base=mergeBase('origin/'+target);if(!base){console.log('No origin/'+target+' ref - fetching it to resolve a merge-base.');cp.spawnSync('git',['fetch','--no-tags','origin',target],{stdio:'inherit'});base=mergeBase('FETCH_HEAD')}if(!base){console.log('Could not resolve a merge-base with '+target+' - verifying EVERY project.');all()}console.log('Pull request against '+target+' - verifying projects affected since '+base);process.exit(cp.spawnSync('npx nx affected -t '+T+' --base='+base,{stdio:'inherit',shell:true}).status ?? 1)"`;
|
|
875
937
|
var PACK_APPS_GUARD = `node -e "const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const hasProjectJson=fs.globSync('apps/*/project.json').length>0;const hasInlineNx=fs.globSync('apps/*/package.json').some((f)=>{try{return Boolean(JSON.parse(fs.readFileSync(f,'utf8')).nx)}catch{return false}});const hasCsproj=fs.globSync('apps/*/*.csproj').length>0;if(!hasProjectJson&&!hasInlineNx&&!hasCsproj){console.log('No apps to pack - skipping.');process.exit(0)}process.exit(require('node:child_process').spawnSync('npx nx run-many -t package',{stdio:'inherit',shell:true}).status ?? 1)"`;
|
|
938
|
+
var NPM_AUTH_PREFLIGHT = `node -e "const fs=require('node:fs'),cp=require('node:child_process');if(fs.globSync('packages/*/package.json').length===0){console.log('No npm packages to release - skipping.');process.exit(0)}if(!process.env.NODE_AUTH_TOKEN){console.error('NPM_TOKEN is empty or unset, so the publish would fail AFTER nx release has already tagged. Add it as a repository secret named exactly NPM_TOKEN - under Actions, not the Dependabot or Codespaces tab, and not an Environment secret. Use an npm Automation token - a Publish token is refused by 2FA in CI.');process.exit(1)}const r=cp.spawnSync('npm',['whoami','--registry=https://registry.npmjs.org/'],{encoding:'utf8',shell:process.platform==='win32'});if(r.status!==0){console.error('NPM_TOKEN is set but the registry rejected it - '+((r.stderr||'')+(r.stdout||'')).trim()+'. Check that it has not expired and that it grants publish rights on this scope.');process.exit(1)}console.log('npm auth OK as '+r.stdout.trim())"`;
|
|
939
|
+
function npmAuthPreflight(registryKind) {
|
|
940
|
+
return registryKind === "npm" ? NPM_AUTH_PREFLIGHT : "";
|
|
941
|
+
}
|
|
876
942
|
var SHALLOW_CLONE_GUARD = `node -e "const r=require('node:child_process').spawnSync('git',['rev-parse','--is-shallow-repository'],{encoding:'utf8'});if(r.status!==0){console.error('Could not determine whether this checkout has full history (git rev-parse --is-shallow-repository failed) - refusing to release. '+(r.stderr||'').trim());process.exit(1)}if(r.stdout.trim()==='true'){console.error('This checkout is a shallow clone. nx release resolves the current version of each package from its git tag, and silently falls back to the permanently-stale on-disk version when a tag cannot be found - which can propose, and publish, a version DOWNGRADE. Fetch full history before releasing - set fetchDepth (Azure) or fetch-depth (GitHub) to 0.');process.exit(1)}"`;
|
|
877
943
|
function releaseGuard(pythonPublishEnv, nugetPublishEnv) {
|
|
878
944
|
return String.raw`node -e "const fs=require('node:fs'),cp=require('node:child_process');const npmCount=fs.globSync('packages/*/package.json').length;const csharpCount=fs.globSync('packages/*/*.csproj').length;const pythonCount=fs.globSync('python-packages/*/pyproject.toml').length;const dartCount=fs.globSync('packages/*/pubspec.yaml').length;const hasNpm=npmCount>0;const hasPython=pythonCount>0;const hasCsharp=csharpCount>0;const hasDart=dartCount>0;if(!hasNpm&&!hasPython&&!hasCsharp&&!hasDart){console.log('Nothing to release - skipping.');process.exit(0)}const specifier=process.env.RELEASE_SPECIFIER||'';let releaseCmd='npx nx release --yes';if(specifier){if(!/^(major|minor|patch|\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?)$/.test(specifier)){console.error('RELEASE_SPECIFIER value \''+specifier+'\' is invalid - use major, minor, patch, or an exact version like 1.2.3.');process.exit(1)}const releaseProjectCount=npmCount+csharpCount+pythonCount+dartCount;if(/^(major|minor|patch)$/.test(specifier)&&releaseProjectCount>1){console.error('RELEASE_SPECIFIER is a keyword (\''+specifier+'\') but this workspace has '+releaseProjectCount+' releasable packages - a keyword under-bumps interdependent packages, because nx computes the dependency-bump pass from a stale cached version. Set RELEASE_SPECIFIER to an exact version instead, or clear it.');process.exit(1)}releaseCmd='npx nx release '+specifier+' --yes'}const env={...process.env};${pythonPublishEnv}${nugetPublishEnv}process.exit(cp.spawnSync(releaseCmd,{stdio:'inherit',shell:true,env}).status ?? 1)"`;
|
|
@@ -1148,7 +1214,17 @@ steps:
|
|
|
1148
1214
|
- script: ${SHALLOW_CLONE_GUARD}
|
|
1149
1215
|
displayName: Verify this is a full checkout (nx release needs the real tag history)
|
|
1150
1216
|
condition: ${onMain}
|
|
1151
|
-
|
|
1217
|
+
${npmAuthPreflight(registryKind) ? `
|
|
1218
|
+
# Prove npm will accept us BEFORE anything irreversible happens. nx release
|
|
1219
|
+
# tags first and publishes last, so a rejected token leaves a version tagged
|
|
1220
|
+
# with nothing on the registry - and that number is then skipped forever,
|
|
1221
|
+
# because the next run resolves the current version from the newest tag.
|
|
1222
|
+
- script: ${npmAuthPreflight(registryKind)}
|
|
1223
|
+
displayName: Preflight \u2014 npm must accept the token before anything is tagged
|
|
1224
|
+
condition: ${onMain}
|
|
1225
|
+
env:
|
|
1226
|
+
NODE_AUTH_TOKEN: $(NPM_TOKEN)
|
|
1227
|
+
` : ""}
|
|
1152
1228
|
# Version + tag + publish, in one release, for npm (packages/*), Python
|
|
1153
1229
|
# (python-packages/*) AND C# (packages/*/*.csproj) \u2014 conventional commits,
|
|
1154
1230
|
# tag-only push. Portable guard: nx release errors on an empty scope, so
|
|
@@ -1374,7 +1450,18 @@ jobs:
|
|
|
1374
1450
|
- run: ${SHALLOW_CLONE_GUARD}
|
|
1375
1451
|
name: Verify this is a full checkout (nx release needs the real tag history)
|
|
1376
1452
|
if: \${{ ${onMain} }}
|
|
1377
|
-
|
|
1453
|
+
${npmAuthPreflight(registryKind) ? `
|
|
1454
|
+
# Prove npm will accept us BEFORE anything irreversible happens. nx
|
|
1455
|
+
# release tags first and publishes last, so a rejected token leaves a
|
|
1456
|
+
# version tagged with nothing on the registry - and that number is then
|
|
1457
|
+
# skipped forever, because the next run resolves the current version
|
|
1458
|
+
# from the newest tag.
|
|
1459
|
+
- run: ${npmAuthPreflight(registryKind)}
|
|
1460
|
+
name: Preflight \u2014 npm must accept the token before anything is tagged
|
|
1461
|
+
if: \${{ ${onMain} }}
|
|
1462
|
+
env:
|
|
1463
|
+
NODE_AUTH_TOKEN: \${{ secrets.NPM_TOKEN }}
|
|
1464
|
+
` : ""}
|
|
1378
1465
|
# Version + tag + publish, in one release, for npm (packages/*), Python
|
|
1379
1466
|
# (python-packages/*) AND C# (packages/*/*.csproj) \u2014 conventional
|
|
1380
1467
|
# commits, tag-only push. Portable guard: nx release errors on an empty
|
|
@@ -1579,8 +1666,9 @@ function applyOverlay(workspaceRoot, options, onProgress = () => {
|
|
|
1579
1666
|
const hookPath = (0, import_node_path2.join)(workspaceRoot, ".husky/commit-msg");
|
|
1580
1667
|
writeFileEnsured(hookPath, COMMIT_MSG_HOOK);
|
|
1581
1668
|
markExecutable(hookPath);
|
|
1582
|
-
onProgress(
|
|
1583
|
-
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);
|
|
1584
1672
|
ensureEslintCacheIgnored(workspaceRoot);
|
|
1585
1673
|
for (const retired of RETIRED_FORMATTER_FILES) {
|
|
1586
1674
|
removeIfPresent((0, import_node_path2.join)(workspaceRoot, retired));
|
|
@@ -4444,6 +4532,21 @@ function checkEslintConfigs(workspaceRoot) {
|
|
|
4444
4532
|
ok: projectConfigs.length === 0,
|
|
4445
4533
|
detail: `found ${projectConfigs.length}: ${projectConfigs.join(", ")}`,
|
|
4446
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`
|
|
4447
4550
|
}
|
|
4448
4551
|
];
|
|
4449
4552
|
}
|