@mnci/cli 1.7.0 → 1.9.0

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
@@ -15,14 +15,14 @@ config package, a custom CI engine, a dependency-injection step for published
15
15
  packages, a doctor/drift-sync system to keep it all consistent — already has a
16
16
  first-party (or established community) Nx equivalent:
17
17
 
18
- | Hand-rolled elsewhere | This CLI uses instead |
19
- | ------------------------------------------------ | ----------------------------------------------------------- |
20
- | Template engine + per-project config files | `create-nx-workspace --preset=ts` + `nx g` plugin generators |
21
- | A shared toolchain package for configs | The configs the Nx generators emit (one root ESLint/tsconfig) |
22
- | A custom multi-step CI engine | `nx affected -t lint,test,build` + `nx release` (~60-line pipeline) |
23
- | A dist-package dependency injector | `nx release` updates dependent versions natively |
24
- | Hand-written Azure Function templates | `@nx/node:application` (plain Node app) + a thin Azure Functions v4 overlay |
25
- | doctor/drift sync of tool-owned files | Nothing to drift: this CLI owns 5 small files, Nx owns the rest |
18
+ | Hand-rolled elsewhere | This CLI uses instead |
19
+ | ------------------------------------------ | --------------------------------------------------------------------------- |
20
+ | Template engine + per-project config files | `create-nx-workspace --preset=ts` + `nx g` plugin generators |
21
+ | A shared toolchain package for configs | The configs the Nx generators emit (one root ESLint/tsconfig) |
22
+ | A custom multi-step CI engine | `nx affected -t lint,test,build` + `nx release` (~60-line pipeline) |
23
+ | A dist-package dependency injector | `nx release` updates dependent versions natively |
24
+ | Hand-written Azure Function templates | `@nx/node:application` (plain Node app) + a thin Azure Functions v4 overlay |
25
+ | doctor/drift sync of tool-owned files | Nothing to drift: this CLI owns 5 small files, Nx owns the rest |
26
26
 
27
27
  ## Commands (deliberately just three)
28
28
 
@@ -46,6 +46,12 @@ mnci add python-lib shared # publishable -> python-packages/ (twine uplo
46
46
  mnci add python-internal-lib core # private shared lib -> libs/
47
47
  mnci add python-vendor shared --lib core # wire core's module into shared's built wheel
48
48
 
49
+ # Go (@nx-go/nx-go — one root go.mod, golangci-lint + go test)
50
+ mnci add go-app api # executable -> apps/ (binary, zipped into the drop)
51
+ mnci add go-function-app fn # serverless handler -> apps/
52
+ mnci add go-lib core # publishable (by git tag) -> packages/
53
+ mnci add go-internal-lib util # private shared package -> libs/
54
+
49
55
  mnci upgrade # re-apply the latest overlay (see below)
50
56
  mnci upgrade --agent windows-latest # ...with an explicit override
51
57
  ```
@@ -53,16 +59,16 @@ mnci upgrade --agent windows-latest # ...with an explicit override
53
59
  Everything else is plain Nx, surfaced as a small curated set of root scripts —
54
60
  each a single cross-platform command:
55
61
 
56
- | Script | Runs |
57
- | --------------------- | ------------------------------- |
58
- | `npm run build` | `nx run-many -t build` |
59
- | `npm run lint` | `nx run-many -t lint` |
60
- | `npm run test` | `nx run-many -t test` |
61
- | `npm run affected` | `nx affected -t lint,test,build` (vs `main`) |
62
- | `npm run graph` | `nx graph` |
63
- | `npm run release:preview` | `nx release --dry-run` |
64
- | `npm run python:install` | fixed Python toolchain (ruff/pytest/build/twine) + editable-install every Python project — the same two guards CI runs, for local dev |
65
- | `prepare` | `husky` (commit-msg lint hook) |
62
+ | Script | Runs |
63
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
64
+ | `npm run build` | `nx run-many -t build` |
65
+ | `npm run lint` | `nx run-many -t lint` |
66
+ | `npm run test` | `nx run-many -t test` |
67
+ | `npm run affected` | `nx affected -t lint,test,build` (vs `main`) |
68
+ | `npm run graph` | `nx graph` |
69
+ | `npm run release:preview` | `nx release --dry-run` |
70
+ | `npm run python:install` | fixed Python toolchain (ruff/pytest/build/twine) + editable-install every Python project — the same two guards CI runs, for local dev |
71
+ | `prepare` | `husky` (commit-msg lint hook) |
66
72
 
67
73
  ## What `new` actually does
68
74
 
@@ -78,14 +84,17 @@ each a single cross-platform command:
78
84
  `commit-msg` hook, the chosen CI provider's pipeline file(s)
79
85
  (`azure-pipelines.yml` and/or `.github/workflows/ci.yml`, `--ci`, default
80
86
  `azure`; `github`/`both` also gets `.github/dependabot.yml` — weekly
81
- dependency-update PRs), and the curated root scripts.
87
+ dependency-update PRs), a `<workspace-name>.code-workspace` file (VS Code
88
+ workspace configuration with folder structure, ESLint/Prettier settings, and
89
+ recommended extensions — open it in VS Code via `File > Open Workspace from
90
+ File`), and the curated root scripts.
82
91
  4. Installs the chosen **stack** (see below), `husky` + `@commitlint/*` for
83
92
  real, so versions resolve at generation time.
84
93
 
85
94
  ## `mnci upgrade`: re-applying the overlay to an existing workspace
86
95
 
87
96
  Every fix to `overlay.ts` — a release-config correction, a CI guard rewritten,
88
- a new Windows code path — only ever reached *future* `mnci new` calls until
97
+ a new Windows code path — only ever reached _future_ `mnci new` calls until
89
98
  this existed; nothing let an already-generated workspace pick one up.
90
99
  `mnci upgrade`, run from the workspace root, closes that gap: it resolves the
91
100
  same options `new` would have and calls the exact same `applyOverlay` `new`
@@ -120,16 +129,23 @@ will overwrite hand customizations to any of the files it owns (e.g. an extra
120
129
  CI job appended by hand to the pipeline file) — `git diff` is exactly how
121
130
  you'd notice and re-apply those on top.
122
131
 
123
- ## Stack: two choices asked up front
132
+ ## Stack: one choice asked up front
133
+
134
+ `mnci new` (run bare, or with flags) asks one question — the test runner. It is
135
+ stored where every later `mnci add` honours it, so the whole workspace stays one
136
+ stack:
124
137
 
125
- `mnci new` (run bare, or with flags) asks two questions — the linter and the
126
- test runner. Each is stored where every later `mnci add` honours it, so the
127
- whole workspace stays one stack:
138
+ | Question | Options | Default | Stored as / honoured via |
139
+ | --------------- | ------------------ | ------- | ---------------------------------------------------------------------------------------- |
140
+ | `--test-runner` | `jest` \| `vitest` | `jest` | `nx.json` generator `unitTestRunner` default; the hand-built function app follows it too |
128
141
 
129
- | Question | Options | Default | Stored as / honoured via |
130
- | -------------- | ------------------ | ------- | ------------------------ |
131
- | `--linter` | `eslint` \| `oxlint` | `eslint` | `nx.json` generator `linter` default (`none` for oxlint) + a typed `oxlint.config.mts` + `oxfmt.config.mts` (both from the [oxc-standard](https://github.com/JohnDeved/ox-standard) preset) + the `oxlint` / `oxfmt` root scripts |
132
- | `--test-runner`| `jest` \| `vitest` | `jest` | `nx.json` generator `unitTestRunner` default; the hand-built function app follows it too |
142
+ **Linting and formatting are unified across the workspace**: every project uses
143
+ **ESLint + Prettier** with the JavaScript Standard Style configuration (no
144
+ semicolons, single quotes, 2-space indents). ESLint is a per-project Nx target
145
+ handling code quality (correctness only), while Prettier handles all formatting.
146
+ Both are automatically configured; `npm run lint` checks code quality, `npm run
147
+ format` (write) and `npm run format:check` (CI-safe) handle formatting. The CI
148
+ runs lint/test/build only — formatting is left as a local/pre-commit step.
133
149
 
134
150
  TypeScript is not a question — every workspace runs the **dual compiler** from
135
151
  [Nx's TS 7 guide](https://nx.dev/docs/technologies/typescript/guides/typescript-7):
@@ -141,35 +157,17 @@ analysing config through the TS 6 API — no target rewiring, frozen per repo by
141
157
  the lockfile. (A plain `typescript@7` install would break Nx, since TS 7 ships
142
158
  no programmatic API yet; the two aliases are what make it work.)
143
159
 
144
- - **Linter**: ESLint is a per-project Nx target; oxlint is a single
145
- workspace-wide binary. Either way `npm run lint` (and the CI) is
146
- linter-agnostic, so nothing downstream branches. Under oxlint a publishable
147
- package's private-lib import needs no dependency-check override (that rule is
148
- ESLint-only), so none is written.
149
- - The oxlint stack installs **[oxc-standard](https://github.com/JohnDeved/ox-standard)**
150
- (which brings `oxlint` + `oxfmt`) and generates two `.mts` configs that use
151
- its **JavaScript Standard Style** preset: `oxlint.config.mts` *extends*
152
- `oxc-standard`'s rule set (unicorn + React + react-perf + TypeScript + oxc),
153
- and `oxfmt.config.mts` mirrors its formatting (no semicolons, single quotes,
154
- 2-space, `es5` trailing commas, avoid arrow parens). That gives the full
155
- Standard experience — **linting *and* formatting** — since oxlint (a linter)
156
- does not enforce layout.
157
- - Formatting is `npm run format` (write) and `npm run format:check` (CI-safe,
158
- no writes). Nx generators emit semicolon/double-quote code, so run
159
- `npm run format` once after scaffolding to normalise a new workspace to
160
- Standard Style. (CI stays lint/test/build only — formatting is left as a
161
- local/pre-commit step so the pipeline needs no linter-specific branch.)
162
160
  - **Test runner**: passed straight to the `@nx/*` generators; the function app
163
161
  gets a matching `jest.config.mjs` (+ ts-jest) or `vitest.config.ts`.
164
162
 
165
163
  ## Layout convention = release scoping
166
164
 
167
- | Directory | Contents | Released? |
168
- | ------------------ | ------------------------------------------------- | ---------------------------- |
169
- | `apps/` | React / Node / Python apps (plain or Azure Functions) | Never (packed into the drop) |
170
- | `packages/` | Publishable npm libraries (rollup-bundled) | Yes — `nx release`, per-package tags |
171
- | `python-packages/` | Publishable Python packages (hatchling wheels) | Yes — `twine upload` (Azure Artifacts) |
172
- | `libs/` | Internal libraries (TS or Python), never published | Never |
165
+ | Directory | Contents | Released? |
166
+ | ------------------ | ----------------------------------------------------- | -------------------------------------- |
167
+ | `apps/` | React / Node / Python apps (plain or Azure Functions) | Never (packed into the drop) |
168
+ | `packages/` | Publishable npm libraries (rollup-bundled) | Yes — `nx release`, per-package tags |
169
+ | `python-packages/` | Publishable Python packages (hatchling wheels) | Yes — `twine upload` (Azure Artifacts) |
170
+ | `libs/` | Internal libraries (TS or Python), never published | Never |
173
171
 
174
172
  No custom tags, no stamp file — the directory (and, for npm, the `private` flag)
175
173
  are the whole model. Publishable Python packages get their own
@@ -223,13 +221,13 @@ file to add a new cross-project import** later (nothing about that is an
223
221
  `mnci add`, so that step can't catch it). For that case every generated
224
222
  workspace sets `sync.applyChanges: true` in `nx.json`: `--preset=ts` already
225
223
  registers the `@nx/js:typescript-sync` generator on the `build`/`typecheck`
226
- targets, so instead of just *prompting* ("Would you like to sync the
224
+ targets, so instead of just _prompting_ ("Would you like to sync the
227
225
  identified changes?") on your next `nx build`/`typecheck`/`affected`, Nx fixes
228
226
  the references **automatically** — no prompt, no manual `npx nx sync`. A
229
227
  brand-new package may still need one VSCode window reload to be picked up by
230
228
  the TypeScript server.
231
229
 
232
- `applyChanges` only affects *interactive* runs, by design: CI always runs sync
230
+ `applyChanges` only affects _interactive_ runs, by design: CI always runs sync
233
231
  generators in dry-run mode and fails instead of silently patching an ephemeral
234
232
  checkout that never gets committed. That's what the pipeline's `nx sync:check`
235
233
  step (below) surfaces early — if it fails, run `npx nx sync` locally and
@@ -261,7 +259,7 @@ Every run (PR and main) installs dependencies, then runs `npm audit`
261
259
  (also non-blocking) — visibility, not enforcement: verified empirically that
262
260
  a real `npm audit` on this monorepo's own tree flagged nothing but
263
261
  already-latest upstream packages (`nx`, `verdaccio`) bundling their own not-
264
- yet-patched transitive dependencies, nothing an edit to *this* workspace's
262
+ yet-patched transitive dependencies, nothing an edit to _this_ workspace's
265
263
  manifest could fix. A hard-failing audit step would turn CI red for a
266
264
  problem with no user-actionable fix, for as long as upstream took to patch
267
265
  it — so both steps always exit 0 regardless of findings, surfacing results
@@ -279,7 +277,7 @@ lint,test,build`. Pushes to `main` then:
279
277
  `dist/drop/<type>-<name>.zip` (e.g. `node-function-app-api.zip`,
280
278
  `react-app-web.zip`); the whole `dist/drop` is published as the **`drop`**
281
279
  artifact.
282
- - **Tag the run per app** *(Azure only)* — one build tag per zip, **exactly**
280
+ - **Tag the run per app** _(Azure only)_ — one build tag per zip, **exactly**
283
281
  `<type>-<name>` (derived from the zip filenames, so the tag can never drift
284
282
  from the artifact). A classic Azure release/CD pipeline keys its trigger off
285
283
  these; GitHub Actions has no equivalent mechanism, so the `drop` artifact
@@ -293,7 +291,16 @@ lint,test,build`. Pushes to `main` then:
293
291
  the raw token twine needs for the Python publish. Skipped cleanly when there
294
292
  is nothing to release. A guarded step installs the fixed Python toolchain
295
293
  (`ruff`/`pytest`/`build`/`twine`/`pip-audit`) before any Python target runs,
296
- skipped cleanly on a workspace with no Python projects.
294
+ skipped cleanly on a workspace with no Python projects. On a `--ci=github`
295
+ workspace this same step also creates a **GitHub Release per project**, with
296
+ a changelog Nx generates from conventional commits — `nx release` pushes the
297
+ tag itself here (needs `GITHUB_TOKEN`, which GitHub Actions provides for
298
+ free under the workflow's own `contents: write` permission), so there's no
299
+ separate explicit `git push origin --tags` step on this provider. `--ci=azure`
300
+ and `--ci=both` keep today's behaviour (no GitHub Release, explicit tag
301
+ push) — GitHub Release creation only turns on when GitHub Actions is the
302
+ *only* configured provider, since that's the one case a `GITHUB_TOKEN` is
303
+ guaranteed to exist.
297
304
 
298
305
  **npm auth** is the base64 `PAT`, read the same way on both providers but from
299
306
  a different place: on Azure Pipelines, a **variable group**
@@ -306,7 +313,7 @@ the root `.npmrc`'s `_password` block — the PAT value never lands in a file.
306
313
  No `npmAuthenticate@0` task (it would overwrite the hand-set password).
307
314
 
308
315
  On Azure, two one-time grants are required (project admin): **Contribute** on
309
- the repo for the *Project Collection Build Service* account (tag push), and
316
+ the repo for the _Project Collection Build Service_ account (tag push), and
310
317
  **publish** rights on the feed for the PAT's owner. On GitHub, the workflow's
311
318
  `permissions: contents: write` is what lets its own checkout token push the
312
319
  release tag — no separate grant, but the job still needs that permission
@@ -321,7 +328,7 @@ to feed" instructions give you. npm's `.npmrc` `_password` field expects
321
328
  exactly that pre-encoded form, so it's used as-is. `twine`/pypi basic auth, by
322
329
  contrast, wants the **raw** token — so the shared `releaseGuard` fragment
323
330
  (`overlay.ts`, used by both `azurePipelinesYaml` and `githubActionsYaml`)
324
- explicitly *decodes* the same `PAT`
331
+ explicitly _decodes_ the same `PAT`
325
332
  (`Buffer.from(process.env.PAT, 'base64').toString()`) before handing it to
326
333
  `TWINE_PASSWORD`. Both are correct for their protocol today, but it's an easy
327
334
  trap to get backwards: if you ever wire a third registry protocol, check
@@ -356,7 +363,7 @@ hosting provider?" even with `--no-interactive` set, and exits without
356
363
  creating the workspace at all when stdin isn't a TTY — a real
357
364
  `create-nx-workspace` inconsistency, not something `mnci` can configure
358
365
  around. The named-provider value sidesteps it and completes non-interactively
359
- every time. The only visible effect of *which* named value is chosen is a
366
+ every time. The only visible effect of _which_ named value is chosen is a
360
367
  throwaway CI workflow file `create-nx-workspace` writes as a side effect of
361
368
  Cloud setup — this CLI's own overlay unconditionally overwrites whatever
362
369
  lands at that path immediately after, so the pipeline you actually get is
@@ -372,22 +379,17 @@ fix-ci`) — `mnci` does not automate that step.
372
379
  Being upfront about what mnci leans on, so it's a conscious trade-off rather
373
380
  than a surprise:
374
381
 
375
- - **One unofficial, small-team third-party Nx plugin carries real weight**:
376
- [`oxc-standard`](https://github.com/JohnDeved/ox-standard) (the oxlint/oxfmt
377
- StandardJS preset) — not `@nx`-scoped/officially maintained. Neither Azure
378
- Function generation nor any Python kind pulls in a *third-party* Nx plugin:
379
- Node apps use the **official** `@nx/node:application` plus a small
380
- hand-written Azure Functions v4 file overlay (see "How Node apps work"
381
- below), and Python uses **`@mnci/nx-python-pip`** — a real Nx plugin this
382
- project built and maintains itself (`libs/nx-python-pip` in this same
383
- monorepo), after `@nxlv/python` (the previous Python plugin) turned out to
384
- require `uv`, which the company standardizing on this tool does not use,
385
- and no maintained alternative supports pip. That trades third-party risk
386
- for a different, real one: **this project now owns a second package's
387
- maintenance surface** (generators, executors, its own release cycle) —
388
- worth being explicit about, since it did not exist before this migration.
389
- If `oxc-standard` stalls or breaks compatibility with a future Nx major,
390
- that surface needs a real maintenance response, not just a version bump.
382
+ - **One Nx plugin this project builds and maintains** carries the most weight:
383
+ **`@mnci/nx-python-pip`** — a real Nx plugin built into this monorepo itself
384
+ (`packages/nx-python-pip`), created because no maintained, Nx-23-compatible
385
+ Python plugin supports pip (the previous Python plugin, `@nxlv/python`,
386
+ requires `uv`, which the company standardizing on this tool does not use, and
387
+ no maintained alternative supports pip). This trades third-party risk for a
388
+ different, real one: **this project now owns a second package's maintenance
389
+ surface** (generators, executors, its own release cycle) — worth being
390
+ explicit about, since it did not exist before. Unlike official `@nx/*`
391
+ plugins, if this one needs fixes or updates, this project owns those directly.
392
+ That's the cost of having no maintained pip-native Nx plugin in the ecosystem.
391
393
  - **The TS7 dual-compiler aliases pin a very new, fast-moving dependency.**
392
394
  TypeScript 7's native compiler is recent; `TS_COMPILER_DEPENDENCIES` pins
393
395
  `npm:typescript@^7.0.2` / `npm:@typescript/typescript6@^6.0.2` specifically
@@ -402,13 +404,18 @@ than a surprise:
402
404
  - Azure Functions Core Tools is only needed for **local** `func start` — never
403
405
  for `mnci add node-function-app`/`python-function-app` generation, since
404
406
  neither shells out to the `func` CLI.
405
- - Function-app *deployment* (e.g. `AzureFunctionApp@2`) is not wired into the
407
+ - Function-app _deployment_ (e.g. `AzureFunctionApp@2`) is not wired into the
406
408
  pipeline; the `node-function-app-<name>.zip`/`python-function-app-<name>.zip`
407
409
  inside the published `drop` artifact is the deploy input. Deploying it means
408
410
  Azure's Oryx build installing real dependencies (`npm install`/`pip install`)
409
411
  from the zipped manifest — no `node_modules`/venv is bundled.
410
- - Changelog files are off (unpushable under the tag-only model); the git tag
411
- history is the changelog for now.
412
+ - Changelog **files** are off everywhere (unpushable under the tag-only
413
+ model — `git.commit` stays `false`, so a written `CHANGELOG.md` would just
414
+ be discarded at the end of every CI run). On a `--ci=github` workspace (and
415
+ only that one — see below) each release still gets a real changelog: Nx
416
+ generates it from conventional commits and posts it straight to a GitHub
417
+ Release, with no file ever touched. `--ci=azure` and `--ci=both` fall back
418
+ to the git tag history as the changelog, same as before.
412
419
  - **No lock file for Python** — plain pip has none, matching the company
413
420
  standard this migration was for. A published wheel's `Requires-Dist` mirrors
414
421
  whatever specifier the `pyproject.toml` declares (e.g. `tomli>=2.0.0`)
@@ -548,25 +555,25 @@ Windows (the standard python.org Windows installer registers no
548
555
  hard-coded name, so a `windows-latest` (or self-hosted Windows) agent works
549
556
  identically to a Linux/macOS one.
550
557
 
551
- | Kind | Location | Build / deploy |
552
- | ---- | -------- | -------------- |
553
- | `python-app` | `apps/<name>` | `python -m build` wheel (the plugin's `build` executor), zipped by mnci into `dist/drop/python-app-<name>.zip` |
554
- | `python-function-app` | `apps/<name>` | Azure Functions **v2** (`function_app.py` + `host.json` + `requirements.txt`); no `pyproject.toml`/wheel — the **source** is zipped by mnci into `dist/drop/python-function-app-<name>.zip` (no `func` CLI needed to generate) |
555
- | `python-lib` | `python-packages/<name>` | publishable wheel; the plugin's `publish` executor (`twine upload --skip-existing`) |
556
- | `python-internal-lib` | `libs/<name>` | private shared code, lint + test only — no build/package target of its own |
558
+ | Kind | Location | Build / deploy |
559
+ | --------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
560
+ | `python-app` | `apps/<name>` | `python -m build` wheel (the plugin's `build` executor), zipped by mnci into `dist/drop/python-app-<name>.zip` |
561
+ | `python-function-app` | `apps/<name>` | Azure Functions **v2** (`function_app.py` + `host.json` + `requirements.txt`); no `pyproject.toml`/wheel — the **source** is zipped by mnci into `dist/drop/python-function-app-<name>.zip` (no `func` CLI needed to generate) |
562
+ | `python-lib` | `python-packages/<name>` | publishable wheel; the plugin's `publish` executor (`twine upload --skip-existing`) |
563
+ | `python-internal-lib` | `libs/<name>` | private shared code, lint + test only — no build/package target of its own |
557
564
 
558
565
  - **Apps** get a `package` target — mnci's own CI packaging convention, not
559
566
  a generic plugin concern — merged into the plugin-written `project.json`
560
567
  after generation, fitting the existing CI unchanged: the pipeline's
561
568
  `apps/*` pack step tags them `python-app-<name>` / `python-function-app-
562
- <name>` just like the TS apps.
569
+ <name>` just like the TS apps.
563
570
  - **Internal-lib vendoring** replaces `@nxlv/python`'s `bundleLocalDependencies`:
564
571
  plain pip has no bundled-local-dependency feature, so a project that imports
565
572
  a workspace-internal Python library needs a `vendor` entry (under
566
573
  `[tool.mnci-python-pip]`) in its own `pyproject.toml` (the pip-world
567
574
  counterpart of a `dependencies = [...]` entry — neither mnci nor the plugin
568
575
  wires cross-project Python dependencies automatically). `mnci add
569
- python-vendor <consumer> --lib <name>` automates writing that entry —
576
+ python-vendor <consumer> --lib <name>` automates writing that entry —
570
577
  idempotent (safe to run twice), and works on any consumer with a
571
578
  `pyproject.toml` (app, publishable lib, or another internal lib), not just
572
579
  apps. The plugin's `build` executor reads the entry, resolves the named
@@ -600,7 +607,7 @@ identically to a Linux/macOS one.
600
607
  `release.version.versionActions` override pointing at
601
608
  `@mnci/nx-python-pip/release/version-actions` (a `VersionActions`
602
609
  implementation — six methods, verified empirically against a real `nx
603
- release version --dry-run`), which wins over the workspace's default
610
+ release version --dry-run`), which wins over the workspace's default
604
611
  (npm's) `versionActions` for that one project. **Publishing** reuses the
605
612
  registry: an Azure Artifacts feed is **multi-protocol**, so the same
606
613
  org/project/feed serves Python — the release step exports `TWINE_*` (URL +
@@ -609,8 +616,68 @@ identically to a Linux/macOS one.
609
616
  Python package is still versioned + tagged, but publishing it needs
610
617
  user-provided `TWINE_*` — e.g. a PyPI token.)
611
618
  - **CI** also runs `nx run-many -t lint,test,build`, so Python's ruff `lint`
612
- target runs alongside the JS build even on the oxlint stack (whose
613
- `npm run lint` only covers JS). One guarded pipeline step installs
619
+ target runs alongside the JS ESLint build. One guarded pipeline step installs
614
620
  `requirements-dev.txt` first (the fixed toolchain), then a second installs
615
621
  every Python project workspace-wide (the workspace-wide install above) —
616
622
  both skipped cleanly when the workspace has no Python projects.
623
+
624
+ ## Go (`@nx-go/nx-go` — one root `go.mod`, golangci-lint + `go test`)
625
+
626
+ Requires **Go 1.21+** on the machine and on the build agent; `mnci add go-*`
627
+ fails fast with an install link when `go` is not on the `PATH`. The generated
628
+ pipeline installs `golangci-lint` itself (see below).
629
+
630
+ | Kind | Location | Build / deploy |
631
+ | ----------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
632
+ | `go-app` | `apps/<name>` | `go build` binary into `dist/apps/<name>/`, zipped by mnci into `dist/drop/go-app-<name>.zip` |
633
+ | `go-function-app` | `apps/<name>` | same build; zipped into `dist/drop/go-function-app-<name>.zip`. The handler body is yours to write — AWS Lambda, Google Cloud Functions and Azure each want a different signature, and mnci does not pick one |
634
+ | `go-lib` | `packages/<name>` | publishable **by git tag** — see below; lint + test targets only |
635
+ | `go-internal-lib` | `libs/<name>` | private shared code, lint + test only — a non-`main` package produces no binary |
636
+
637
+ - **One root `go.mod`**, matching how TS uses one root `package.json` and
638
+ Python one root `requirements-dev.txt`. `add/go.ts` bootstraps it on the
639
+ first Go `add` by running the plugin's `init` then `convert-to-one-mod`
640
+ generators, in that order — `convert-to-one-mod` refuses once `go.work`
641
+ lists any module, so it has to happen before the first Go project exists.
642
+ Every Go project then shares that module and imports its siblings as
643
+ `<module>/libs/<name>`, with no per-project manifests and no `replace`
644
+ directives.
645
+ - **The `go.work` multi-module layout was rejected deliberately.** Besides
646
+ splitting dependencies across per-project manifests, it is brittle: a
647
+ single stale `use` entry — a project directory removed by hand — makes
648
+ `go list -m -json` fail, and that breaks the **entire** Nx project graph,
649
+ not just the Go projects. Verified empirically.
650
+ - **Targets are written explicitly** rather than inferred. `@nx-go/nx-go`
651
+ supplies `build`/`test`/`lint` by inference, but keys that inference on a
652
+ per-project `go.mod` — which single-module mode does not have, so nothing
653
+ is inferred. mnci writes them into `project.json` instead, as it already
654
+ does for most kinds.
655
+ - **Lint is `golangci-lint`, pinned deliberately.** The plugin's `lint`
656
+ executor defaults to `go fmt`, which only reformats — a green lint step
657
+ with that default would mean nothing. The generated target passes
658
+ `linter: golangci-lint`, and `mnci add` warns (without failing) when the
659
+ binary is missing locally, since CI installs its own.
660
+ - **Publishing a `go-lib` is a git tag, not a registry upload.** The whole
661
+ repository is one module, so consumers depend on a library by import path
662
+ at a repo-level version tag — `go get <module>/packages/<name>@v1.2.3`.
663
+ That tag is the one `nx release` already creates, so no
664
+ `nx-release-publish` target is written: there is nothing to push. The only
665
+ real difference between `go-lib` and `go-internal-lib` is intent, recorded
666
+ in the `type:go-lib` tag and the `packages/` location.
667
+ - **No publish-time dependency injection**, unlike Python's vendoring: `go
668
+ build` links statically, so the binary in the drop already contains
669
+ everything it needs.
670
+ - **Build output is a directory**, `dist/apps/<name>/`, with the binary
671
+ inside it. The executor's own default writes a bare file at
672
+ `dist/apps/<name>`, which cannot be declared as an Nx `outputs` entry —
673
+ Nx scans each declared output to cache it, and scanning a file fails with
674
+ `ENOTDIR`. Building one level deeper keeps the root-`dist` convention and
675
+ makes the target cacheable.
676
+ - **CI** runs Go through the same `nx run-many -t lint,test,build` as
677
+ everything else. Two guarded steps precede it: `go mod download` (so a
678
+ network failure reads as a dependency failure rather than a confusing
679
+ build error), and a `golangci-lint` install via `go install` — no package
680
+ manager, no sudo, same command on every agent OS — whose `GOPATH/bin` is
681
+ then added to `PATH` for later steps. All three skip cleanly when the
682
+ workspace has no root `go.mod`, and the linter install also skips when the
683
+ agent already provides it.