@mnci/cli 1.14.0 → 1.15.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 +52 -5
- package/dist/cli.js +212 -46
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ first-party (or established community) Nx equivalent:
|
|
|
24
24
|
| Hand-written Azure Function templates | `@nx/node:application` (plain Node app) + a thin Azure Functions v4 overlay |
|
|
25
25
|
| doctor/drift sync of tool-owned files | Nothing to drift: this CLI owns 5 small files, Nx owns the rest |
|
|
26
26
|
|
|
27
|
-
## Commands (deliberately just
|
|
27
|
+
## Commands (deliberately just four)
|
|
28
28
|
|
|
29
29
|
```sh
|
|
30
30
|
mnci new my-repo # create a monorepo (prompts scope + registry)
|
|
@@ -61,8 +61,30 @@ mnci add flutter-internal-lib core # private shared package -> libs/
|
|
|
61
61
|
|
|
62
62
|
mnci upgrade # re-apply the latest overlay (see below)
|
|
63
63
|
mnci upgrade --agent windows-latest # ...with an explicit override
|
|
64
|
+
|
|
65
|
+
mnci doctor # check this workspace's invariants (read-only)
|
|
64
66
|
```
|
|
65
67
|
|
|
68
|
+
## `mnci doctor`: checking the invariants actually hold
|
|
69
|
+
|
|
70
|
+
Read-only — it never edits the workspace. Every failing finding names the command
|
|
71
|
+
that fixes it (usually `mnci upgrade`), and it exits non-zero when anything failed,
|
|
72
|
+
so it works as a CI step as well as a local command.
|
|
73
|
+
|
|
74
|
+
Every check corresponds to an invariant that has **actually** been violated, in
|
|
75
|
+
this repo or in a workspace it generated. None are hypothetical; a check nobody has
|
|
76
|
+
ever needed is noise that trains people to ignore the output.
|
|
77
|
+
|
|
78
|
+
| Check | The failure it catches |
|
|
79
|
+
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
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.json`, so the whole formatting opinion is silently discarded while both files look fine |
|
|
82
|
+
| `@nx/eslint/plugin` registered in `nx.json` | Without it `npm run lint` exits 0 while linting nothing |
|
|
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
|
+
| `.npmrc` matches the recorded registry | The two registry kinds get different files; an Azure workspace also needs its scope routed |
|
|
85
|
+
| `versionActions` on publishable Dart/Python packages | Its absence aborts `nx release` for the **whole** workspace, not just that project |
|
|
86
|
+
| `nx sync:check` | A stale TypeScript project reference that was never committed |
|
|
87
|
+
|
|
66
88
|
Everything else is plain Nx, surfaced as a small curated set of root scripts —
|
|
67
89
|
each a single cross-platform command:
|
|
68
90
|
|
|
@@ -343,8 +365,12 @@ default `azure`): `azure` writes `azure-pipelines.yml`, `github` writes
|
|
|
343
365
|
GitHub-hosted repo, or `both` while migrating between the two. Whichever
|
|
344
366
|
provider(s), the pipeline does the **exact same thing**: both files are built
|
|
345
367
|
from the same shared guard scripts (`overlay.ts`'s `PYTHON_INSTALL_GUARD`,
|
|
346
|
-
`PACK_APPS_GUARD`, `releaseGuard`), so they can never
|
|
347
|
-
actually runs — only the provider's own syntax differs.
|
|
368
|
+
`PACK_APPS_GUARD`, `releaseGuard`, `AFFECTED_OR_ALL_GUARD`), so they can never
|
|
369
|
+
drift on what CI actually runs — only the provider's own syntax differs. That
|
|
370
|
+
matters most for the last of those: the two providers detect a pull request
|
|
371
|
+
through different environment variables, so the guard reads both, and a
|
|
372
|
+
provider-specific copy would change _what CI verifies_ rather than merely how it
|
|
373
|
+
is spelled.
|
|
348
374
|
|
|
349
375
|
The pipeline contains **no bash and no PowerShell**: every step is a built-in
|
|
350
376
|
task/action or a single-line `git`/`npm`/`npx`/`node` command that `cmd.exe`
|
|
@@ -372,8 +398,29 @@ vulnerable transitive package) is exactly what this monorepo's own
|
|
|
372
398
|
attempts automatically.
|
|
373
399
|
|
|
374
400
|
Then `nx sync:check` (fails fast and clearly if the workspace wasn't
|
|
375
|
-
synced+committed locally — see above), then
|
|
376
|
-
lint,test,build`.
|
|
401
|
+
synced+committed locally — see above), then `npm run format:check`, then **one
|
|
402
|
+
verify step** running `lint,typecheck,test,build`. `typecheck` is in that list
|
|
403
|
+
because a bundler-built project strips types without reading them, so `build`
|
|
404
|
+
passing proves nothing about type correctness.
|
|
405
|
+
|
|
406
|
+
That step verifies the **affected** projects on a pull request and **every**
|
|
407
|
+
project on anything else — including a push to `main`, so a release is always
|
|
408
|
+
verified in full. There is deliberately no separate `npm run lint` step: that is
|
|
409
|
+
`nx run-many -t lint`, a strict subset of the list above, and on an
|
|
410
|
+
affected-scoped PR it would re-lint every project and throw the benefit away.
|
|
411
|
+
`format:check` does stay workspace-wide, because `prettier --check .` is one
|
|
412
|
+
invocation over the whole tree rather than a per-project target, so there is
|
|
413
|
+
nothing to narrow.
|
|
414
|
+
|
|
415
|
+
Every fallback in that step verifies **everything**: no PR target branch, an
|
|
416
|
+
unresolvable merge-base (shallow clone, absent remote branch), any non-PR run.
|
|
417
|
+
That direction is deliberate — resolving the base too wide costs a few minutes,
|
|
418
|
+
while resolving it too narrow means CI runs almost nothing, reports green and has
|
|
419
|
+
verified nothing. The base is a `git merge-base`, not either provider's "base SHA"
|
|
420
|
+
field and not the GitHub-only `nrwl/nx-set-shas`, so one mechanism serves both
|
|
421
|
+
providers and is correct in each by construction.
|
|
422
|
+
|
|
423
|
+
Pushes to `main` then:
|
|
377
424
|
|
|
378
425
|
- **Pack all apps** — each app's `package` target zips its build output into
|
|
379
426
|
`dist/drop/<type>-<name>.zip` (e.g. `node-function-app-api.zip`,
|
package/dist/cli.js
CHANGED
|
@@ -1234,11 +1234,8 @@ function readWorkspaceStack(workspaceRoot) {
|
|
|
1234
1234
|
};
|
|
1235
1235
|
}
|
|
1236
1236
|
|
|
1237
|
-
// src/commands/
|
|
1238
|
-
var
|
|
1239
|
-
var import_prompts8 = require("@inquirer/prompts");
|
|
1240
|
-
|
|
1241
|
-
// src/commands/new.ts
|
|
1237
|
+
// src/commands/doctor.ts
|
|
1238
|
+
var import_node_fs7 = require("fs");
|
|
1242
1239
|
var import_node_path12 = require("path");
|
|
1243
1240
|
|
|
1244
1241
|
// src/overlay.ts
|
|
@@ -1509,6 +1506,8 @@ var FLUTTER_TOOL_PATH_AZURE = `node -e "${FLUTTER_TOOL_PATH_PRELUDE}console.log(
|
|
|
1509
1506
|
var FLUTTER_TOOL_PATH_GITHUB = `node -e "${FLUTTER_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'`})"`;
|
|
1510
1507
|
var FLUTTER_PUB_GET_GUARD = `node -e "const fs=require('node:fs');if(!fs.existsSync('pubspec.yaml')){console.log('No Flutter projects - skipping.');process.exit(0)}process.exit(require('node:child_process').spawnSync('flutter',['pub','get'],{stdio:'inherit',shell:process.platform==='win32'}).status ?? 1)"`;
|
|
1511
1508
|
var NPM_AUDIT_STEP = "npm audit --audit-level=high || echo npm audit found vulnerabilities, see log above - non-blocking, run npm audit locally to inspect";
|
|
1509
|
+
var VERIFY_TARGETS = "lint,typecheck,test,build";
|
|
1510
|
+
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)"`;
|
|
1512
1511
|
var PACK_APPS_GUARD = `node -e "const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});if(fs.globSync('apps/*/project.json').length===0){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)"`;
|
|
1513
1512
|
function releaseGuard(pythonPublishEnv) {
|
|
1514
1513
|
return `node -e "const fs=require('node:fs'),cp=require('node:child_process');const hasNpm=fs.globSync('packages/*/package.json').length>0;const hasPython=fs.globSync('python-packages/*/pyproject.toml').length>0;if(!hasNpm&&!hasPython){console.log('Nothing to release - skipping.');process.exit(0)}const env={...process.env};${pythonPublishEnv}process.exit(cp.spawnSync('npx nx release --yes',{stdio:'inherit',shell:true,env}).status ?? 1)"`;
|
|
@@ -1677,27 +1676,28 @@ steps:
|
|
|
1677
1676
|
- script: npx nx sync:check
|
|
1678
1677
|
displayName: Verify the workspace is synced (run 'npx nx sync' locally and commit if this fails)
|
|
1679
1678
|
|
|
1680
|
-
# One verify for every run (PR and main). Nx cache makes unchanged test/build
|
|
1681
|
-
# projects instant.
|
|
1682
|
-
- script: npm run lint
|
|
1683
|
-
displayName: Lint
|
|
1684
|
-
|
|
1685
1679
|
# Prettier owns ALL formatting in this workspace, so it needs a gate of its
|
|
1686
1680
|
# own: ESLint is configured for correctness only and deliberately reports
|
|
1687
1681
|
# nothing about formatting. Without this step the entire formatting opinion is
|
|
1688
1682
|
# advisory, and a workspace drifts out of compliance with no signal anywhere.
|
|
1683
|
+
#
|
|
1684
|
+
# Stays workspace-wide even on a PR, unlike the verify step below: it is one
|
|
1685
|
+
# Prettier invocation over the whole tree, not a per-project Nx target, so
|
|
1686
|
+
# there is nothing to scope down and formatting is never partially checked.
|
|
1689
1687
|
- script: npm run format:check
|
|
1690
1688
|
displayName: Check formatting (run 'npm run format' locally to fix)
|
|
1691
|
-
|
|
1692
|
-
#
|
|
1693
|
-
#
|
|
1689
|
+
|
|
1690
|
+
# The one verify step, and deliberately the only one: affected projects on a
|
|
1691
|
+
# pull request, EVERY project on anything else (a push to main included, so a
|
|
1692
|
+
# release is always verified in full). Every fallback \u2014 no target branch, an
|
|
1693
|
+
# unresolvable merge-base \u2014 takes the full path, because a run that verifies
|
|
1694
|
+
# too little still reports green.
|
|
1694
1695
|
#
|
|
1695
|
-
#
|
|
1696
|
-
#
|
|
1697
|
-
#
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
displayName: Lint, typecheck, test and build everything
|
|
1696
|
+
# 'npm run lint' is 'nx run-many -t lint', a strict subset of the targets
|
|
1697
|
+
# below, so adding it back as its own step would only duplicate work \u2014 and on
|
|
1698
|
+
# a pull request it would re-lint every project, discarding the point of this.
|
|
1699
|
+
- script: ${AFFECTED_OR_ALL_GUARD}
|
|
1700
|
+
displayName: Verify (affected on a PR, every project on main)
|
|
1701
1701
|
|
|
1702
1702
|
# Pack every app into dist/drop/<type>-<name>.zip via each app's 'package'
|
|
1703
1703
|
# target. Portable guard: skip cleanly when the workspace has no apps yet.
|
|
@@ -1876,29 +1876,31 @@ jobs:
|
|
|
1876
1876
|
- run: npx nx sync:check
|
|
1877
1877
|
name: Verify the workspace is synced (run 'npx nx sync' locally and commit if this fails)
|
|
1878
1878
|
|
|
1879
|
-
# One verify for every run (PR and main). Nx cache makes unchanged
|
|
1880
|
-
# test/build projects instant.
|
|
1881
|
-
- run: npm run lint
|
|
1882
|
-
name: Lint
|
|
1883
|
-
|
|
1884
1879
|
# Prettier owns ALL formatting in this workspace, so it needs a gate of
|
|
1885
1880
|
# its own: ESLint is configured for correctness only and deliberately
|
|
1886
1881
|
# reports nothing about formatting. Without this step the entire
|
|
1887
1882
|
# formatting opinion is advisory, and a workspace drifts out of
|
|
1888
1883
|
# compliance with no signal anywhere.
|
|
1884
|
+
#
|
|
1885
|
+
# Stays workspace-wide even on a PR, unlike the verify step below: it is
|
|
1886
|
+
# one Prettier invocation over the whole tree, not a per-project Nx
|
|
1887
|
+
# target, so there is nothing to scope down and formatting is never
|
|
1888
|
+
# partially checked.
|
|
1889
1889
|
- run: npm run format:check
|
|
1890
1890
|
name: Check formatting (run 'npm run format' locally to fix)
|
|
1891
|
-
|
|
1892
|
-
#
|
|
1893
|
-
#
|
|
1891
|
+
|
|
1892
|
+
# The one verify step, and deliberately the only one: affected projects on
|
|
1893
|
+
# a pull request, EVERY project on anything else (a push to main included,
|
|
1894
|
+
# so a release is always verified in full). Every fallback \u2014 no target
|
|
1895
|
+
# branch, an unresolvable merge-base \u2014 takes the full path, because a run
|
|
1896
|
+
# that verifies too little still reports green.
|
|
1894
1897
|
#
|
|
1895
|
-
#
|
|
1896
|
-
#
|
|
1897
|
-
#
|
|
1898
|
-
#
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
name: Lint, typecheck, test and build everything
|
|
1898
|
+
# 'npm run lint' is 'nx run-many -t lint', a strict subset of the targets
|
|
1899
|
+
# below, so adding it back as its own step would only duplicate work \u2014 and
|
|
1900
|
+
# on a pull request it would re-lint every project, discarding the point
|
|
1901
|
+
# of this.
|
|
1902
|
+
- run: ${AFFECTED_OR_ALL_GUARD}
|
|
1903
|
+
name: Verify (affected on a PR, every project on main)
|
|
1902
1904
|
|
|
1903
1905
|
# Pack every app into dist/drop/<type>-<name>.zip via each app's 'package'
|
|
1904
1906
|
# target. Portable guard: skip cleanly when the workspace has no apps yet.
|
|
@@ -2053,7 +2055,166 @@ function applyOverlay(workspaceRoot, options) {
|
|
|
2053
2055
|
}
|
|
2054
2056
|
}
|
|
2055
2057
|
|
|
2058
|
+
// src/commands/doctor.ts
|
|
2059
|
+
var SUPPORTED_ESLINT_MAJOR = ESLINT_VERSION.replace(/^\D*/, "").split(".")[0];
|
|
2060
|
+
function checkEslintConfigs(workspaceRoot) {
|
|
2061
|
+
const rootConfigs = (0, import_node_fs7.globSync)("eslint.config.{js,mjs,cjs,ts,mts,cts}", { cwd: workspaceRoot });
|
|
2062
|
+
const projectConfigs = (0, import_node_fs7.globSync)("{apps,libs,packages}/*/eslint.config.{js,mjs,cjs,ts,mts,cts}", {
|
|
2063
|
+
cwd: workspaceRoot
|
|
2064
|
+
});
|
|
2065
|
+
return [
|
|
2066
|
+
{
|
|
2067
|
+
check: "root ESLint config",
|
|
2068
|
+
ok: rootConfigs.length === 1,
|
|
2069
|
+
detail: rootConfigs.length === 0 ? "no eslint.config.* at the workspace root" : `${rootConfigs.length} root configs: ${rootConfigs.join(", ")}`,
|
|
2070
|
+
remedy: "run `mnci upgrade` to rewrite the root config"
|
|
2071
|
+
},
|
|
2072
|
+
{
|
|
2073
|
+
check: "no per-project ESLint configs",
|
|
2074
|
+
ok: projectConfigs.length === 0,
|
|
2075
|
+
detail: `found ${projectConfigs.length}: ${projectConfigs.join(", ")}`,
|
|
2076
|
+
remedy: "run `mnci upgrade`, which sweeps {apps,libs,packages}/*/eslint.config.*"
|
|
2077
|
+
}
|
|
2078
|
+
];
|
|
2079
|
+
}
|
|
2080
|
+
function checkPrettierConfig(workspaceRoot) {
|
|
2081
|
+
const strayExists = fileExists((0, import_node_path12.join)(workspaceRoot, ".prettierrc"));
|
|
2082
|
+
return {
|
|
2083
|
+
check: "Prettier config is mnci\u2019s",
|
|
2084
|
+
ok: !strayExists,
|
|
2085
|
+
detail: ".prettierrc exists and outranks .prettierrc.json, so mnci\u2019s config is ignored",
|
|
2086
|
+
remedy: "delete .prettierrc (or run `mnci upgrade`, which deletes it)"
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
function checkEslintPlugin(nxJson) {
|
|
2090
|
+
const plugins = nxJson.plugins ?? [];
|
|
2091
|
+
const registered = plugins.some(
|
|
2092
|
+
(entry) => typeof entry === "string" ? entry === "@nx/eslint/plugin" : entry.plugin === "@nx/eslint/plugin"
|
|
2093
|
+
);
|
|
2094
|
+
return {
|
|
2095
|
+
check: "@nx/eslint/plugin registered",
|
|
2096
|
+
ok: registered,
|
|
2097
|
+
detail: "not in nx.json plugins \u2014 every project silently loses its lint target",
|
|
2098
|
+
remedy: "run `mnci upgrade`"
|
|
2099
|
+
};
|
|
2100
|
+
}
|
|
2101
|
+
function checkResolvedEslint(workspaceRoot) {
|
|
2102
|
+
const manifestPath = (0, import_node_path12.join)(workspaceRoot, "node_modules/eslint/package.json");
|
|
2103
|
+
if (!fileExists(manifestPath)) {
|
|
2104
|
+
return void 0;
|
|
2105
|
+
}
|
|
2106
|
+
try {
|
|
2107
|
+
const { version } = readJson(manifestPath);
|
|
2108
|
+
const major = version.split(".")[0];
|
|
2109
|
+
return {
|
|
2110
|
+
check: `resolved eslint is ${SUPPORTED_ESLINT_MAJOR}.x`,
|
|
2111
|
+
ok: major === SUPPORTED_ESLINT_MAJOR,
|
|
2112
|
+
detail: `node_modules/eslint is ${version}, but this stack supports ${SUPPORTED_ESLINT_MAJOR}.x (eslint-plugin-react has no release beyond it)`,
|
|
2113
|
+
remedy: `pin eslint to ${ESLINT_VERSION} in every package manifest, then reinstall`
|
|
2114
|
+
};
|
|
2115
|
+
} catch {
|
|
2116
|
+
return void 0;
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
function checkNpmrc(workspaceRoot, registry, scope) {
|
|
2120
|
+
const npmrcPath = (0, import_node_path12.join)(workspaceRoot, ".npmrc");
|
|
2121
|
+
if (!registry || !fileExists(npmrcPath)) {
|
|
2122
|
+
return void 0;
|
|
2123
|
+
}
|
|
2124
|
+
const npmrc = (0, import_node_fs7.readFileSync)(npmrcPath, "utf8");
|
|
2125
|
+
if (registry.kind === "npm") {
|
|
2126
|
+
return {
|
|
2127
|
+
check: ".npmrc authenticates the public registry",
|
|
2128
|
+
ok: npmrc.includes("//registry.npmjs.org/:_authToken="),
|
|
2129
|
+
detail: "no npmjs.org token line \u2014 `npm publish` cannot authenticate",
|
|
2130
|
+
remedy: "run `mnci upgrade`"
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2133
|
+
return {
|
|
2134
|
+
check: ".npmrc routes the scope to the feed",
|
|
2135
|
+
ok: scope !== void 0 && npmrc.includes(`${scope}:registry=`),
|
|
2136
|
+
detail: `no '${scope ?? "@scope"}:registry=' line \u2014 a scoped package could publish to npmjs.org instead of the feed`,
|
|
2137
|
+
remedy: "run `mnci upgrade`"
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
function checkVersionActions(workspaceRoot) {
|
|
2141
|
+
const candidates = [
|
|
2142
|
+
...(0, import_node_fs7.globSync)("packages/*/pubspec.yaml", { cwd: workspaceRoot }),
|
|
2143
|
+
...(0, import_node_fs7.globSync)("python-packages/*/pyproject.toml", { cwd: workspaceRoot })
|
|
2144
|
+
];
|
|
2145
|
+
return candidates.flatMap((manifest) => {
|
|
2146
|
+
const projectRoot = manifest.slice(0, manifest.lastIndexOf("/"));
|
|
2147
|
+
const projectJsonPath = (0, import_node_path12.join)(workspaceRoot, projectRoot, "project.json");
|
|
2148
|
+
let hasOverride = false;
|
|
2149
|
+
try {
|
|
2150
|
+
const projectJson = readJson(projectJsonPath);
|
|
2151
|
+
hasOverride = Boolean(projectJson.release?.version?.versionActions);
|
|
2152
|
+
} catch {
|
|
2153
|
+
hasOverride = false;
|
|
2154
|
+
}
|
|
2155
|
+
return [
|
|
2156
|
+
{
|
|
2157
|
+
check: `${projectRoot} keeps its versionActions override`,
|
|
2158
|
+
ok: hasOverride,
|
|
2159
|
+
detail: "missing \u2014 nx release aborts for the ENTIRE workspace, not just this project",
|
|
2160
|
+
remedy: `add release.version.versionActions to ${projectRoot}/project.json`
|
|
2161
|
+
}
|
|
2162
|
+
];
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
function checkSync(workspaceRoot) {
|
|
2166
|
+
return {
|
|
2167
|
+
check: "TypeScript project references synced",
|
|
2168
|
+
ok: runShell("npx", ["nx", "sync:check"], workspaceRoot) === 0,
|
|
2169
|
+
detail: "nx sync:check failed \u2014 a stale project reference was never committed",
|
|
2170
|
+
remedy: "run `npx nx sync` and commit the result"
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
function collectFindings(workspaceRoot) {
|
|
2174
|
+
const nxJsonPath = (0, import_node_path12.join)(workspaceRoot, "nx.json");
|
|
2175
|
+
if (!fileExists(nxJsonPath)) {
|
|
2176
|
+
throw new Error(
|
|
2177
|
+
`No nx.json found in ${workspaceRoot} \u2014 run 'mnci doctor' from the workspace root.`
|
|
2178
|
+
);
|
|
2179
|
+
}
|
|
2180
|
+
const nxJson = readJson(nxJsonPath);
|
|
2181
|
+
return [
|
|
2182
|
+
...checkEslintConfigs(workspaceRoot),
|
|
2183
|
+
checkPrettierConfig(workspaceRoot),
|
|
2184
|
+
checkEslintPlugin(nxJson),
|
|
2185
|
+
checkResolvedEslint(workspaceRoot),
|
|
2186
|
+
checkNpmrc(workspaceRoot, nxJson.mnci?.registry, nxJson.mnci?.scope),
|
|
2187
|
+
...checkVersionActions(workspaceRoot),
|
|
2188
|
+
checkSync(workspaceRoot)
|
|
2189
|
+
].filter((finding) => finding !== void 0);
|
|
2190
|
+
}
|
|
2191
|
+
function runDoctor(workspaceRoot) {
|
|
2192
|
+
const findings = collectFindings(workspaceRoot);
|
|
2193
|
+
const failed = findings.filter((finding) => !finding.ok);
|
|
2194
|
+
for (const finding of findings) {
|
|
2195
|
+
if (finding.ok) {
|
|
2196
|
+
logger.success(finding.check);
|
|
2197
|
+
} else {
|
|
2198
|
+
logger.error(`${finding.check} \u2014 ${finding.detail ?? "failed"}`);
|
|
2199
|
+
if (finding.remedy) {
|
|
2200
|
+
logger.info(` fix: ${finding.remedy}`);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
if (failed.length === 0) {
|
|
2205
|
+
logger.success(`All ${findings.length} checks passed.`);
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
logger.error(`${failed.length} of ${findings.length} checks failed.`);
|
|
2209
|
+
process.exitCode = 1;
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
// src/commands/interactive.ts
|
|
2213
|
+
var import_node_path15 = require("path");
|
|
2214
|
+
var import_prompts8 = require("@inquirer/prompts");
|
|
2215
|
+
|
|
2056
2216
|
// src/commands/new.ts
|
|
2217
|
+
var import_node_path13 = require("path");
|
|
2057
2218
|
function nxCloudProviderValue(ci) {
|
|
2058
2219
|
return ci === "azure" ? "azure" : "github";
|
|
2059
2220
|
}
|
|
@@ -2118,7 +2279,7 @@ async function runNew(name, options) {
|
|
|
2118
2279
|
],
|
|
2119
2280
|
process.cwd()
|
|
2120
2281
|
);
|
|
2121
|
-
const workspaceRoot = (0,
|
|
2282
|
+
const workspaceRoot = (0, import_node_path13.join)(process.cwd(), workspaceName);
|
|
2122
2283
|
logger.step(
|
|
2123
2284
|
"Applying MoNecromanCI overlay (VS Code workspace, release config, .npmrc, commitlint, pipeline, stack)"
|
|
2124
2285
|
);
|
|
@@ -2152,8 +2313,8 @@ async function runNew(name, options) {
|
|
|
2152
2313
|
}
|
|
2153
2314
|
|
|
2154
2315
|
// src/commands/upgrade.ts
|
|
2155
|
-
var
|
|
2156
|
-
var
|
|
2316
|
+
var import_node_fs8 = require("fs");
|
|
2317
|
+
var import_node_path14 = require("path");
|
|
2157
2318
|
function resolveRegistry2(options, persisted) {
|
|
2158
2319
|
if (options.registry === "azure-artifacts" || options.organization && options.artifactsFeed) {
|
|
2159
2320
|
const persistedAzure = persisted?.kind === "azure-artifacts" ? persisted : void 0;
|
|
@@ -2182,7 +2343,7 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
|
|
|
2182
2343
|
return persisted.workspaceName;
|
|
2183
2344
|
}
|
|
2184
2345
|
try {
|
|
2185
|
-
const existing = (0,
|
|
2346
|
+
const existing = (0, import_node_fs8.readdirSync)(workspaceRoot).find(
|
|
2186
2347
|
(file) => file.endsWith(".code-workspace") && file !== "undefined.code-workspace"
|
|
2187
2348
|
);
|
|
2188
2349
|
if (existing) {
|
|
@@ -2190,7 +2351,7 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
|
|
|
2190
2351
|
}
|
|
2191
2352
|
} catch {
|
|
2192
2353
|
}
|
|
2193
|
-
return (0,
|
|
2354
|
+
return (0, import_node_path14.basename)(workspaceRoot);
|
|
2194
2355
|
}
|
|
2195
2356
|
function resolveOverlayOptions(workspaceRoot, options, persisted) {
|
|
2196
2357
|
const scope = options.scope ?? persisted.scope;
|
|
@@ -2228,7 +2389,7 @@ function resolveOverlayOptions(workspaceRoot, options, persisted) {
|
|
|
2228
2389
|
};
|
|
2229
2390
|
}
|
|
2230
2391
|
function runUpgrade(workspaceRoot, options) {
|
|
2231
|
-
if (!fileExists((0,
|
|
2392
|
+
if (!fileExists((0, import_node_path14.join)(workspaceRoot, "nx.json"))) {
|
|
2232
2393
|
throw new Error(
|
|
2233
2394
|
`No nx.json found in ${workspaceRoot} \u2014 this does not look like an Nx workspace. Run 'mnci upgrade' from the workspace root.`
|
|
2234
2395
|
);
|
|
@@ -2245,7 +2406,7 @@ function runUpgrade(workspaceRoot, options) {
|
|
|
2245
2406
|
|
|
2246
2407
|
// src/commands/interactive.ts
|
|
2247
2408
|
async function runInteractive() {
|
|
2248
|
-
const inWorkspace = fileExists((0,
|
|
2409
|
+
const inWorkspace = fileExists((0, import_node_path15.join)(process.cwd(), "nx.json"));
|
|
2249
2410
|
const newChoice = { name: "Create a new monorepo", value: "new" };
|
|
2250
2411
|
const addChoice = { name: "Add a project to this workspace", value: "add" };
|
|
2251
2412
|
const upgradeChoice = {
|
|
@@ -2266,8 +2427,8 @@ async function runInteractive() {
|
|
|
2266
2427
|
}
|
|
2267
2428
|
|
|
2268
2429
|
// src/util/versionChecker.ts
|
|
2269
|
-
var
|
|
2270
|
-
var
|
|
2430
|
+
var import_node_fs9 = require("fs");
|
|
2431
|
+
var import_node_path16 = require("path");
|
|
2271
2432
|
var import_cross_spawn2 = __toESM(require("cross-spawn"));
|
|
2272
2433
|
var PACKAGE_NAME = "@mnci/cli";
|
|
2273
2434
|
var REGISTRY_TIMEOUT_MS = 2e3;
|
|
@@ -2323,8 +2484,8 @@ function releaseTriple(version) {
|
|
|
2323
2484
|
}
|
|
2324
2485
|
function readCliVersion(packageDirectory) {
|
|
2325
2486
|
try {
|
|
2326
|
-
const manifestPath = (0,
|
|
2327
|
-
const manifest = JSON.parse((0,
|
|
2487
|
+
const manifestPath = (0, import_node_path16.join)(packageDirectory, "..", "package.json");
|
|
2488
|
+
const manifest = JSON.parse((0, import_node_fs9.readFileSync)(manifestPath, "utf8"));
|
|
2328
2489
|
return manifest.version ?? "0.0.0";
|
|
2329
2490
|
} catch {
|
|
2330
2491
|
return "0.0.0";
|
|
@@ -2349,6 +2510,11 @@ function buildProgram(cliVersion) {
|
|
|
2349
2510
|
).action((options) => {
|
|
2350
2511
|
runUpgrade(process.cwd(), options);
|
|
2351
2512
|
});
|
|
2513
|
+
program.command("doctor").description(
|
|
2514
|
+
"Check this workspace against the invariants mnci maintains (one ESLint config, no stray .prettierrc, the eslint plugin registered, the resolved eslint major, .npmrc vs the recorded registry, versionActions overrides, nx sync) \u2014 read-only; exits non-zero if anything failed"
|
|
2515
|
+
).action(() => {
|
|
2516
|
+
runDoctor(process.cwd());
|
|
2517
|
+
});
|
|
2352
2518
|
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(
|
|
2353
2519
|
"--framework <framework>",
|
|
2354
2520
|
"node-app only: express | fastify | koa | nest | none (default: none)"
|
|
@@ -2382,7 +2548,7 @@ async function main() {
|
|
|
2382
2548
|
}
|
|
2383
2549
|
}
|
|
2384
2550
|
if (require.main === module) {
|
|
2385
|
-
main();
|
|
2551
|
+
void main();
|
|
2386
2552
|
}
|
|
2387
2553
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2388
2554
|
0 && (module.exports = {
|