@mnci/cli 4.0.8 → 4.1.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 +173 -1
- package/dist/cli.js +1184 -60
- 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 six)
|
|
28
28
|
|
|
29
29
|
```sh
|
|
30
30
|
mnci new my-repo # create a monorepo (prompts scope + registry)
|
|
@@ -63,8 +63,178 @@ mnci upgrade # re-apply the latest overlay (see below)
|
|
|
63
63
|
mnci upgrade --agent windows-latest # ...with an explicit override
|
|
64
64
|
|
|
65
65
|
mnci doctor # check this workspace's invariants (read-only)
|
|
66
|
+
|
|
67
|
+
mnci sync # converge dependency ranges + nx sync (TS project refs)
|
|
68
|
+
mnci sync --check # ...report and exit non-zero, writing nothing
|
|
69
|
+
|
|
70
|
+
mnci up # what has a newer release, grouped; pick what to update
|
|
71
|
+
mnci up --check # ...report only (the default when output is piped)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Where a dependency belongs: root vs project
|
|
75
|
+
|
|
76
|
+
One rule, and it is the same in every language mnci supports:
|
|
77
|
+
|
|
78
|
+
> **Shared development and tool packages live at the root. Runtime dependencies
|
|
79
|
+
> belong to the package that imports them.**
|
|
80
|
+
|
|
81
|
+
| | Runtime deps declared in | What the root file holds |
|
|
82
|
+
| -------- | -------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
|
83
|
+
| npm | each project's `package.json` | `package.json` — scripts, devDependencies, `overrides` (root-only by npm's rules) |
|
|
84
|
+
| pip | each project's `pyproject.toml` (a function app: its `requirements.txt`) | `requirements-dev.txt` — the shared toolchain, nothing else |
|
|
85
|
+
| pub | each member's `pubspec.yaml` | `pubspec.yaml` — the member list and an SDK floor, **no** dependency blocks |
|
|
86
|
+
| go | *(nothing per-project)* | `go.mod` — the whole module's requirements |
|
|
87
|
+
|
|
88
|
+
**Go is the stated exception.** Its single-root-module layout means there are no
|
|
89
|
+
per-project manifests to own anything, so every Go dependency is a root
|
|
90
|
+
dependency by construction. That is deliberate — the multi-module `go.work`
|
|
91
|
+
alternative was rejected because one stale `use` entry makes `go list -m -json`
|
|
92
|
+
fail, which breaks the entire Nx project graph, not just the Go projects.
|
|
93
|
+
|
|
94
|
+
### Why hoisting a runtime dependency to the root is a bug, not a tidy-up
|
|
95
|
+
|
|
96
|
+
It looks like centralisation and it is not. Two independent reasons:
|
|
97
|
+
|
|
98
|
+
1. **The root manifest is `private` and never published.** A runtime dependency
|
|
99
|
+
declared there reaches no consumer of any package; an installed `@scope/lib`
|
|
100
|
+
simply fails to resolve it.
|
|
101
|
+
2. **`@nx/rollup` externalises exactly what a project's OWN manifest declares.**
|
|
102
|
+
Pull a dependency out of `packages/thing/package.json` and rollup stops
|
|
103
|
+
treating it as external — it **inlines a private copy into the bundle**.
|
|
104
|
+
Measured on a real generated workspace: moving `axios` out of one package's
|
|
105
|
+
manifest took its published bundle from 14.5 KB to 832 KB, silently.
|
|
106
|
+
|
|
107
|
+
Two things catch this, from opposite directions. `@nx/dependency-checks` (in the
|
|
108
|
+
root ESLint config, so it runs as part of `lint`) fails the project whose import
|
|
109
|
+
is now undeclared, and `mnci doctor` fails the root that took it.
|
|
110
|
+
|
|
111
|
+
## Debugging: breakpoints in the TypeScript, not the built JavaScript
|
|
112
|
+
|
|
113
|
+
A publishable library is bundled by `@nx/rollup`, so what runs is `dist/*.js`.
|
|
114
|
+
A breakpoint in the `.ts` binds only if the build emitted a source map that
|
|
115
|
+
points back at real files. Three separate things had to be fixed for that to be
|
|
116
|
+
true, and a generated workspace now gets all three:
|
|
117
|
+
|
|
118
|
+
| | The default | What mnci writes |
|
|
119
|
+
| --- | --- | --- |
|
|
120
|
+
| `sourceMap` | unset, so **no `.js.map` at all** | `true`, in `withNx`'s first argument |
|
|
121
|
+
| `compiler` | `'swc'`, hardcoded by `@nx/js:lib` | `'babel'` — see below |
|
|
122
|
+
| `sources` paths | OS-native, one parent segment too many | repaired by `sourcemapPathTransform` |
|
|
123
|
+
|
|
124
|
+
Each one alone leaves breakpoints grey, and none of them reports an error.
|
|
125
|
+
|
|
126
|
+
**`sourceMap` has to go in the first argument.** The obvious spot is
|
|
127
|
+
`output: { sourcemap: true }` in the second — the generator's own placeholder
|
|
128
|
+
comment even suggests it — and it silently does nothing: `withNx` spreads your
|
|
129
|
+
`output` and *then* assigns `sourcemap: options.sourceMap`, so its own undefined
|
|
130
|
+
value always wins.
|
|
131
|
+
|
|
132
|
+
**The compiler swap is not a preference.** `@nx/rollup`'s swc plugin calls
|
|
133
|
+
swc's `transform()` without `sourceMaps`, so swc returns no map, the rollup
|
|
134
|
+
chain breaks, and the map comes out valid-looking and **empty** — `sources: []`.
|
|
135
|
+
Measured on a real package: swc gave 0 sources, babel gave 9. Revert the swap
|
|
136
|
+
once Nx passes `sourceMaps` through; ROADMAP 7d has the one-line upstream fix.
|
|
137
|
+
|
|
138
|
+
**The paths are wrong twice over.** rollup hands `sourcemapPathTransform` a
|
|
139
|
+
path like `..\..\src\index.ts` for a map in `dist/` — one parent segment too
|
|
140
|
+
many, so it resolves above the project to a file that does not exist, and
|
|
141
|
+
back-slashed, which is invalid in a sourcemap `sources` entry on every platform
|
|
142
|
+
(a `sources` entry is URL-style — the same bug class as the declaration stub).
|
|
143
|
+
Both are repaired, by collapsing the parent-segment run rather than stripping a
|
|
144
|
+
fixed prefix, so it cannot go stale at another nesting depth.
|
|
145
|
+
|
|
146
|
+
**Maps are built always and published never.** `!**/*.js.map` joins `files`, so
|
|
147
|
+
`npm run <lib>:build` is debuggable while the tarball stays lean — the same
|
|
148
|
+
trade already made for `.d.ts.map`. There is deliberately no dev-build flag: a
|
|
149
|
+
build you have to remember to run differently is one you will not have run at
|
|
150
|
+
the moment you need it.
|
|
151
|
+
|
|
152
|
+
`mnci doctor` reports any rollup config missing this, and `mnci upgrade` sweeps
|
|
153
|
+
`packages/*` and `libs/*` to add it — a rollup config is written once at `add`
|
|
154
|
+
time, so a workspace generated earlier would never fix itself otherwise. The
|
|
155
|
+
sweep is idempotent.
|
|
156
|
+
|
|
157
|
+
## `mnci sync`: making every project agree
|
|
158
|
+
|
|
159
|
+
`nx sync` runs the workspace's **sync generators**, and the only one a generated
|
|
160
|
+
workspace registers is `@nx/js:typescript-sync` — it reconciles TypeScript project
|
|
161
|
+
references and has no opinion whatsoever about dependency versions. And npm has no
|
|
162
|
+
`catalog:`, pnpm's one-version-per-workspace mechanism, so keeping two projects on
|
|
163
|
+
the same range is a convention nothing enforces.
|
|
164
|
+
|
|
165
|
+
`mnci sync` is both halves:
|
|
166
|
+
|
|
167
|
+
1. Every external package declared at more than one version converges on one spec.
|
|
168
|
+
2. `nx sync` then reconciles the TypeScript project references.
|
|
169
|
+
|
|
170
|
+
The winning spec is the one matching what is actually **resolved** —
|
|
171
|
+
`node_modules` for npm, `pubspec.lock` for pub, the interpreter for pip. That is
|
|
172
|
+
the same source `@nx/dependency-checks` pins a drifted range to when it
|
|
173
|
+
auto-fixes, so the command and the lint rule converge on one answer instead of
|
|
174
|
+
overwriting each other. With nothing installed, the highest declared range wins
|
|
175
|
+
instead, and the report says which rule was applied.
|
|
176
|
+
|
|
177
|
+
It keeps the range operator the majority of sites already use, so a workspace
|
|
178
|
+
that pins exactly stays pinned.
|
|
179
|
+
|
|
180
|
+
**Three things it deliberately never touches:**
|
|
181
|
+
|
|
182
|
+
- **Peer ranges.** `>=21.0.0` on `@nx/devkit` is a *compatibility declaration*,
|
|
183
|
+
not a version choice — narrowing it to the 23.x you happen to resolve drops two
|
|
184
|
+
majors of consumers. The first run of this command against mnci's own repo
|
|
185
|
+
reported six findings, five of which were exactly that mistake.
|
|
186
|
+
- **The workspace's own projects.** An internal `@scope/lib` is symlinked and
|
|
187
|
+
versioned by `nx release`; its loose range is what lets both the link and the
|
|
188
|
+
tag satisfy it.
|
|
189
|
+
- **A spec whose shape it cannot safely edit** — a `git:`/`path:`/URL target, a
|
|
190
|
+
`workspace:` protocol, an `npm:pkg@range` alias, a pub `git:` map. Those are
|
|
191
|
+
reported as a warning and left alone.
|
|
192
|
+
|
|
193
|
+
**Go reports "nothing to sync" rather than a silent pass**, because one root
|
|
194
|
+
`go.mod` means one version of every module — there is nothing that *could*
|
|
195
|
+
disagree.
|
|
196
|
+
|
|
197
|
+
`--check` reports and exits non-zero without writing anything, so it works as a CI
|
|
198
|
+
step. `--ecosystem npm|pip|pub|go` narrows the run.
|
|
199
|
+
|
|
200
|
+
## `mnci up`: what has a newer release, and who is using it
|
|
201
|
+
|
|
202
|
+
Modelled on `npm-check -u` — the same four sections in the same order, the same
|
|
203
|
+
interactive multiselect — with one addition that `npm-check` cannot give you in a
|
|
204
|
+
monorepo: **every project declaring the package**.
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
Minor Update New backwards-compatible features.
|
|
208
|
+
@nx/devkit devDep/peerDep 23.1.1 › 23.2.0 (root), packages/nx-python-pip, packages/nx-flutter
|
|
209
|
+
@typescript-eslint/parser dep/devDep 8.68.0 › 8.69.0 packages/eslint-config, packages/az-durable
|
|
66
210
|
```
|
|
67
211
|
|
|
212
|
+
That column is the point. It is what tells you an upgrade touches three projects
|
|
213
|
+
before you pick it, and picking one rewrites **every** declaration of it — which
|
|
214
|
+
is what stops `mnci up` from creating the drift `mnci sync` then has to repair.
|
|
215
|
+
|
|
216
|
+
Latest versions come from each ecosystem's own tooling, never a hand-rolled HTTP
|
|
217
|
+
call: `npm view` (so a scoped Azure Artifacts feed and its `.npmrc` credentials
|
|
218
|
+
just work), `pip index versions`, one `go list -m -u -json all`, one
|
|
219
|
+
`flutter pub outdated --json`. An ecosystem whose toolchain is absent is reported
|
|
220
|
+
as a loud `SKIPPED`, never quietly dropped.
|
|
221
|
+
|
|
222
|
+
The same three exclusions as `mnci sync` apply, plus two more that only matter
|
|
223
|
+
here: an **indirect Go module** (`go mod tidy` owns those) and an **aliased
|
|
224
|
+
install**, where the manifest key names a different package than the one on disk.
|
|
225
|
+
The alias case is not hypothetical — mnci's own root manifest pins the dual
|
|
226
|
+
TypeScript compiler as `typescript: npm:@typescript/typescript6@^6.0.2`, and the
|
|
227
|
+
first run of this command offered "typescript 6.0.2 › 7.0.2", which is real
|
|
228
|
+
TypeScript's version, about a package the workspace does not have.
|
|
229
|
+
|
|
230
|
+
A selected Go module is upgraded with `go get <module>@<version>`, never by
|
|
231
|
+
editing `go.mod` — that file is the toolchain's to write.
|
|
232
|
+
|
|
233
|
+
Flags: `--check` (report only; also the automatic behaviour when stdout is not a
|
|
234
|
+
TTY, so a piped or CI run reports instead of hanging on a prompt), `-y/--yes`
|
|
235
|
+
(take everything), `--ecosystem`, and `--no-install` (edit the manifests but skip
|
|
236
|
+
the reinstall).
|
|
237
|
+
|
|
68
238
|
## `mnci doctor`: checking the invariants actually hold
|
|
69
239
|
|
|
70
240
|
Read-only — it never edits the workspace. Every failing finding names the command
|
|
@@ -83,6 +253,8 @@ ever needed is noise that trains people to ignore the output.
|
|
|
83
253
|
| `.npmrc` matches the recorded registry | The two registry kinds get different files; an Azure workspace also needs its scope routed |
|
|
84
254
|
| `versionActions` on publishable Dart/Python packages | Its absence aborts `nx release` for the **whole** workspace, not just that project |
|
|
85
255
|
| `nx sync:check` | A stale TypeScript project reference that was never committed |
|
|
256
|
+
| No runtime dependency in the root manifest | A dependency hoisted to the root, which reaches no consumer (the root is private) and makes `@nx/rollup` inline a private copy into the package that imports it — 14.5 KB to 832 KB on a real measurement. See "Where a dependency belongs" above |
|
|
257
|
+
| Source maps enabled in every rollup config | A build that emits no `.js.map`, so every breakpoint in a `.ts` file stays grey and unbound with nothing reporting why. A rollup config is written once at `add` time, so an older workspace never fixes itself — `mnci upgrade` sweeps them |
|
|
86
258
|
| No retired formatter is still configured | A leftover `.prettierrc*`, `.oxfmtrc.json` or `oxlint.config.ts`, or a `prettier`/`oxlint`/`oxfmt` devDependency, runs from no command line — which is what makes it dangerous, since an editor extension still resolves it and reformats on save, undoing Standard after every gate has passed |
|
|
87
259
|
|
|
88
260
|
Everything else is plain Nx, surfaced as a small curated set of root scripts —
|