@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 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 four)
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 —