@mnci/cli 1.9.0 → 1.9.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
@@ -52,6 +52,11 @@ mnci add go-function-app fn # serverless handler -> apps/
52
52
  mnci add go-lib core # publishable (by git tag) -> packages/
53
53
  mnci add go-internal-lib util # private shared package -> libs/
54
54
 
55
+ # Flutter (@mnci/nx-flutter — one root pubspec.yaml pub workspace, analyze + test)
56
+ mnci add flutter-app hello # Flutter web app -> apps/ (bundle, zipped into the drop)
57
+ mnci add flutter-lib shared # publishable (by git tag) -> packages/
58
+ mnci add flutter-internal-lib core # private shared package -> libs/
59
+
55
60
  mnci upgrade # re-apply the latest overlay (see below)
56
61
  mnci upgrade --agent windows-latest # ...with an explicit override
57
62
  ```
@@ -162,15 +167,24 @@ no programmatic API yet; the two aliases are what make it work.)
162
167
 
163
168
  ## Layout convention = release scoping
164
169
 
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 |
171
-
172
- No custom tags, no stamp file — the directory (and, for npm, the `private` flag)
173
- are the whole model. Publishable Python packages get their own
170
+ | Directory | Contents | Released? |
171
+ | ------------------ | -------------------------------------------------------------- | -------------------------------------- |
172
+ | `apps/` | React / Node / Python / Go / Flutter apps (plain or Functions) | Never (packed into the drop) |
173
+ | `packages/` | Publishable npm libraries, plus Go and Dart packages | Yes — `nx release`, per-package tags |
174
+ | `python-packages/` | Publishable Python packages (hatchling wheels) | Yes — `twine upload` (Azure Artifacts) |
175
+ | `libs/` | Internal libraries (TS, Python, Go or Dart), never published | Never |
176
+
177
+ The directory is very nearly the whole model — one exception, and it is a bug
178
+ fix rather than a nicety. `go-lib` also lives in `packages/`, but a Go package
179
+ has **no per-project manifest** (mnci puts every Go project in one root
180
+ `go.mod`), so Nx's default `versionActions` looks for a `package.json` that is
181
+ not there and aborts _while building the release graph_ — killing `nx release`
182
+ for the whole workspace, not just the Go project. It is therefore excluded with
183
+ `!tag:type:go-lib`, which is also the semantically correct call: one module
184
+ means its packages have no independent versions to bump. A publishable **Dart**
185
+ package in `packages/` needs no such exclusion — `pubspec.yaml` has a real
186
+ `version:` field, and `@mnci/nx-flutter` stamps a `versionActions` override that
187
+ reads it. Publishable Python packages get their own
174
188
  `python-packages/` dir so the npm `nx release` (`packages/*`) is never entangled
175
189
  with Python publishing.
176
190
 
@@ -299,7 +313,7 @@ lint,test,build`. Pushes to `main` then:
299
313
  separate explicit `git push origin --tags` step on this provider. `--ci=azure`
300
314
  and `--ci=both` keep today's behaviour (no GitHub Release, explicit tag
301
315
  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
316
+ _only_ configured provider, since that's the one case a `GITHUB_TOKEN` is
303
317
  guaranteed to exist.
304
318
 
305
319
  **npm auth** is the base64 `PAT`, read the same way on both providers but from
@@ -379,17 +393,31 @@ fix-ci`) — `mnci` does not automate that step.
379
393
  Being upfront about what mnci leans on, so it's a conscious trade-off rather
380
394
  than a surprise:
381
395
 
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.
396
+ - **Two Nx plugins this project builds and maintains** carry the most weight,
397
+ both for the same reason — the ecosystem has no maintained, Nx-23-compatible
398
+ option:
399
+ - **`@mnci/nx-python-pip`** (`packages/nx-python-pip`): no maintained plugin
400
+ supports pip. The obvious candidate, `@nxlv/python`, requires `uv`, which
401
+ the company standardizing on this tool does not use.
402
+ - **`@mnci/nx-flutter`** (`packages/nx-flutter`): `@nxrocks/nx-flutter`
403
+ cannot even load on Nx 23 — it imports
404
+ `@nx/workspace/src/utilities/fileutils`, removed in 23 — and there is no
405
+ alternative. Its exposure is smaller than the Python plugin's, because
406
+ scaffolding is delegated to the official `flutter create` rather than
407
+ hand-maintained templates; what this project owns is the pub-workspace
408
+ wiring, the targets and the release integration.
409
+
410
+ Both trade third-party risk for a different, real one: **this project owns
411
+ two extra packages' maintenance surface** (generators, executors, their own
412
+ release cycles). Unlike official `@nx/*` plugins, if either needs fixes, this
413
+ project owns them directly. That is the cost of the gaps in the ecosystem.
414
+
415
+ - **`@nx-go/nx-go` is a third-party plugin on a declared-incompatible range.**
416
+ It declares `@nx/devkit ">= 20 < 23"` while this workspace runs Nx 23. That
417
+ range is a plain dependency rather than a peer, so npm nests its own devkit
418
+ copy and everything works — validated empirically against a real Nx 23.1.0
419
+ workspace (generators, build, test, lint). It is still a version trap worth
420
+ re-checking on Nx upgrades.
393
421
  - **The TS7 dual-compiler aliases pin a very new, fast-moving dependency.**
394
422
  TypeScript 7's native compiler is recent; `TS_COMPILER_DEPENDENCIES` pins
395
423
  `npm:typescript@^7.0.2` / `npm:@typescript/typescript6@^6.0.2` specifically
@@ -401,6 +429,15 @@ than a surprise:
401
429
  ## Known gaps (accepted for the experiment)
402
430
 
403
431
  - No `doctor`/`resurrect`/`spell` — out of scope until the model is proven.
432
+ - **Flutter apps build for web only.** Android would require the Android SDK and
433
+ NDK on every build agent; iOS is impossible on a Linux agent regardless. Add
434
+ other platforms per-app with `flutter create --platforms=...` — the generated
435
+ `build` target only knows about web.
436
+ - **Go has no e2e coverage.** The `go-*` kinds have real unit tests and real CI
437
+ wiring, but the e2e suite never drives them, because it needs Go on the
438
+ machine. Flutter took the opposite approach — its e2e section runs when the
439
+ SDK is present and reports **SKIPPED** (loudly) when it is not — so the same
440
+ pattern could be applied to Go, but has not been.
404
441
  - Azure Functions Core Tools is only needed for **local** `func start` — never
405
442
  for `mnci add node-function-app`/`python-function-app` generation, since
406
443
  neither shells out to the `func` CLI.
@@ -681,3 +718,69 @@ build` links statically, so the binary in the drop already contains
681
718
  then added to `PATH` for later steps. All three skip cleanly when the
682
719
  workspace has no root `go.mod`, and the linter install also skips when the
683
720
  agent already provides it.
721
+
722
+ ## Flutter (`@mnci/nx-flutter` — one root `pubspec.yaml` pub workspace)
723
+
724
+ Requires the **Flutter SDK** (3.27+, for Dart 3.6+ pub workspaces) on the
725
+ machine; `mnci add flutter-*` fails fast with an install link when `flutter` is
726
+ not on the `PATH`. Unlike Python and Go, the SDK is **not** present on hosted
727
+ build agents, so the generated pipeline installs it itself (see below).
728
+
729
+ | Kind | Location | Build / deploy |
730
+ | ---------------------- | ----------------- | ----------------------------------------------------------------------------------------------------- |
731
+ | `flutter-app` | `apps/<name>` | `flutter build web` bundle into `dist/apps/<name>/`, zipped into `dist/drop/flutter-app-<name>.zip` |
732
+ | `flutter-lib` | `packages/<name>` | publishable **by git tag** — see below; analyze + test targets only |
733
+ | `flutter-internal-lib` | `libs/<name>` | private shared code, analyze + test only — a Dart package is compiled into whatever app depends on it |
734
+
735
+ - **Dependencies are central, via a Dart pub workspace.** One root
736
+ `pubspec.yaml` lists every project under `workspace:`, and each project
737
+ carries `resolution: workspace`. A single `flutter pub get` at the root then
738
+ resolves the whole graph into **one** `pubspec.lock` and **one**
739
+ `.dart_tool/package_config.json` — pub actively deletes any per-package
740
+ copies. This is the Flutter half of the same root-manifest model as the root
741
+ `package.json` for TS, `requirements-dev.txt` for Python and `go.mod` for Go.
742
+ - **An internal library is consumed with a plain version constraint — no
743
+ `path:`.** `dependencies: { core: ^0.0.1 }` resolves to the local package
744
+ because it is a workspace member. That is also why Flutter needs **no
745
+ vendoring step**: contrast `mnci add python-vendor`, which exists only
746
+ because pip cannot bundle an unpublished sibling into a wheel. Flutter is in
747
+ the Go camp here — nothing to weave in at build time.
748
+ - **Lint configuration is central too.** The workspace root owns one
749
+ `analysis_options.yaml` (including `package:flutter_lints/flutter.yaml`), and
750
+ each project's own file is a one-line relative `include:` of it, so a rule
751
+ change lands in one place.
752
+ - **`flutter analyze --fatal-infos`, pinned explicitly.** `flutter analyze`
753
+ already defaults `--fatal-infos` on — verified against 3.44.8, where
754
+ `--no-fatal-infos` turns a failing lint run green. It is passed anyway
755
+ because that default is the only thing making this a real gate: nearly every
756
+ `flutter_lints` rule reports at _info_ severity. Worth knowing that plain
757
+ `dart analyze` defaults the opposite way (it fails on errors and warnings but
758
+ not infos), so swapping the command without carrying the flag across would
759
+ silently stop enforcing anything.
760
+ - **Publishing a `flutter-lib` is a git tag, not a registry upload.** Azure
761
+ Artifacts has no pub/Dart feed type, so a private pub registry is not
762
+ available on this stack, and these packages are deliberately not pushed to
763
+ pub.dev. `nx release` versions and tags them; no `nx-release-publish` target
764
+ is written.
765
+ - **The publishable lib carries a `versionActions` override, and it is
766
+ load-bearing.** Nx's default reads a `package.json`, which a Dart package
767
+ does not have. Without the override `nx release` aborts while building the
768
+ release graph — taking down the release of **every** project in the
769
+ workspace, not just the Dart one. The plugin's `library` generator stamps
770
+ `@mnci/nx-flutter/release/version-actions` on, which reads and writes
771
+ `pubspec.yaml`'s `version:`.
772
+ - **Apps build for web only.** Web needs nothing beyond the Flutter SDK,
773
+ whereas an Android build would drag the whole Android SDK and NDK onto every
774
+ build agent. Other platforms can be added per-app later with
775
+ `flutter create --platforms=...`.
776
+ - **CI** installs the SDK by shallow `git clone` at a pinned tag — Flutter's own
777
+ documented install method, and the only one uniform across agents (the
778
+ release archives differ by platform). It is cloned **outside** the workspace,
779
+ under the agent's home directory: the SDK ships dozens of its own
780
+ `pubspec.yaml` files, which inside the tree would pollute pub's resolution and
781
+ give Nx thousands of extra files to glob. The SDK version is **pinned**
782
+ (unlike `golangci-lint`'s `@latest`) because it determines the Dart version,
783
+ and pub workspaces need Dart 3.6+. Three guarded steps precede the build —
784
+ install, add to `PATH`, and one root `flutter pub get` — and all three skip
785
+ cleanly when the workspace has no root `pubspec.yaml`; the install also skips
786
+ when an SDK is already available.
package/dist/cli.js CHANGED
@@ -164,10 +164,6 @@ function assertValidProjectName(name, label) {
164
164
  }
165
165
  }
166
166
 
167
- // src/commands/add/go.ts
168
- var import_node_fs2 = require("fs");
169
- var import_node_path3 = require("path");
170
-
171
167
  // src/commands/add/shared.ts
172
168
  var import_node_path2 = require("path");
173
169
  function hasPlugin(workspaceRoot, packageName) {
@@ -203,7 +199,48 @@ function addNxTargets(manifestPath, newTargets) {
203
199
  );
204
200
  }
205
201
 
202
+ // src/commands/add/flutter.ts
203
+ function ensureFlutter(workspaceRoot) {
204
+ if (runShell("flutter", ["--version"], workspaceRoot) !== 0) {
205
+ throw new Error(
206
+ "Flutter not found. Install the Flutter SDK first (3.27+, for Dart 3.6+ pub workspaces): https://docs.flutter.dev/get-started/install"
207
+ );
208
+ }
209
+ }
210
+ function nxFlutterPluginSpec() {
211
+ return process.env.MNCI_NX_FLUTTER_SPEC ?? "@mnci/nx-flutter";
212
+ }
213
+ function ensureNxFlutterPlugin(workspaceRoot) {
214
+ if (hasPlugin(workspaceRoot, "@mnci/nx-flutter")) {
215
+ return;
216
+ }
217
+ const spec = nxFlutterPluginSpec();
218
+ logger.step(`Installing the Flutter toolchain plugin (${spec})`);
219
+ if (runShell("npm", ["install", "--save-dev", spec, "--no-audit", "--no-fund"], workspaceRoot) !== 0) {
220
+ throw new Error("npm install of @mnci/nx-flutter failed");
221
+ }
222
+ }
223
+ function prepareFlutter(workspaceRoot) {
224
+ ensureFlutter(workspaceRoot);
225
+ ensureNxFlutterPlugin(workspaceRoot);
226
+ }
227
+ function addFlutterApp(workspaceRoot, name) {
228
+ prepareFlutter(workspaceRoot);
229
+ ensureAdmZip(workspaceRoot);
230
+ runNx(["g", "@mnci/nx-flutter:application", name, "--no-interactive"], workspaceRoot);
231
+ }
232
+ function addFlutterLib(workspaceRoot, name) {
233
+ prepareFlutter(workspaceRoot);
234
+ runNx(["g", "@mnci/nx-flutter:library", name, "--no-interactive"], workspaceRoot);
235
+ }
236
+ function addFlutterInternalLib(workspaceRoot, name) {
237
+ prepareFlutter(workspaceRoot);
238
+ runNx(["g", "@mnci/nx-flutter:internal-library", name, "--no-interactive"], workspaceRoot);
239
+ }
240
+
206
241
  // src/commands/add/go.ts
242
+ var import_node_fs2 = require("fs");
243
+ var import_node_path3 = require("path");
207
244
  function ensureGo(workspaceRoot) {
208
245
  if (runShell("go", ["version"], workspaceRoot) !== 0) {
209
246
  throw new Error("Go not found. Install Go 1.21+ first: https://go.dev/dl/");
@@ -539,6 +576,17 @@ export default [
539
576
  ignoredFiles: [
540
577
  '{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}',
541
578
  '{projectRoot}/rollup.config.{js,ts,mjs,mts,cjs,cts}',
579
+ // Same reasoning as the two config files above, extended to the test
580
+ // toolchain: rollup bundles from the entry point only, so neither the
581
+ // Vitest config nor the spec files reach the published package \u2014 their
582
+ // imports must not drive the published manifest.
583
+ //
584
+ // Without this, a vitest-stack workspace fails \`npm run lint\` on a
585
+ // freshly generated npm-lib: Nx's own spec imports \`vitest\`, and
586
+ // @nx/dependency-checks then demands it be declared as a runtime
587
+ // dependency of a package that never uses it at runtime.
588
+ '{projectRoot}/vitest.config.{js,ts,mjs,mts,cjs,cts}',
589
+ '{projectRoot}/**/*.spec.{js,ts,jsx,tsx}',
542
590
  ],
543
591
  },
544
592
  ],
@@ -873,7 +921,10 @@ var PROJECT_KINDS = [
873
921
  "go-app",
874
922
  "go-function-app",
875
923
  "go-lib",
876
- "go-internal-lib"
924
+ "go-internal-lib",
925
+ "flutter-app",
926
+ "flutter-lib",
927
+ "flutter-internal-lib"
877
928
  ];
878
929
  async function runAdd(kind, name, options) {
879
930
  const workspaceRoot = process.cwd();
@@ -953,6 +1004,18 @@ async function runAdd(kind, name, options) {
953
1004
  addGoInternalLib(workspaceRoot, resolvedName);
954
1005
  break;
955
1006
  }
1007
+ case "flutter-app": {
1008
+ addFlutterApp(workspaceRoot, resolvedName);
1009
+ break;
1010
+ }
1011
+ case "flutter-lib": {
1012
+ addFlutterLib(workspaceRoot, resolvedName);
1013
+ break;
1014
+ }
1015
+ case "flutter-internal-lib": {
1016
+ addFlutterInternalLib(workspaceRoot, resolvedName);
1017
+ break;
1018
+ }
956
1019
  case "python-vendor": {
957
1020
  await addPythonVendor(workspaceRoot, resolvedName, options, kindProvided);
958
1021
  syncProjectReferences(workspaceRoot);
@@ -1038,7 +1101,7 @@ function releaseConfig(ci) {
1038
1101
  const githubReleases = ci === "github";
1039
1102
  return {
1040
1103
  projectsRelationship: "independent",
1041
- projects: ["packages/*", "python-packages/*"],
1104
+ projects: ["packages/*", "python-packages/*", "!tag:type:go-lib"],
1042
1105
  releaseTag: { pattern: "{projectName}@{version}" },
1043
1106
  git: { commit: false, tag: true, push: githubReleases },
1044
1107
  version: {
@@ -1193,6 +1256,13 @@ var GOLANGCI_LINT_INSTALL_GUARD = `node -e "const fs=require('node:fs'),cp=requi
1193
1256
  var GO_TOOL_PATH_PRELUDE = `const fs=require('node:fs'),cp=require('node:child_process');if(!fs.existsSync('go.mod')){console.log('No Go projects - skipping.');process.exit(0)}const r=cp.spawnSync('go',['env','GOPATH'],{encoding:'utf8'});if(r.status!==0){console.log('Could not resolve GOPATH - skipping.');process.exit(0)}const bin=require('node:path').join(r.stdout.trim(),'bin');`;
1194
1257
  var GO_TOOL_PATH_AZURE = `node -e "${GO_TOOL_PATH_PRELUDE}console.log('##vso[task.prependpath]'+bin)"`;
1195
1258
  var GO_TOOL_PATH_GITHUB = `node -e "${GO_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'`})"`;
1259
+ var FLUTTER_SDK_VERSION = "3.44.8";
1260
+ var FLUTTER_SDK_DIRECTORY_EXPRESSION = `require('node:path').join(require('node:os').homedir(),'.mnci-flutter-${FLUTTER_SDK_VERSION}')`;
1261
+ var FLUTTER_SDK_INSTALL_GUARD = `node -e "const fs=require('node:fs'),cp=require('node:child_process');if(!fs.existsSync('pubspec.yaml')){console.log('No Flutter projects - skipping.');process.exit(0)}if(cp.spawnSync('flutter',['--version'],{stdio:'ignore',shell:process.platform==='win32'}).status===0){console.log('Flutter SDK already on PATH - skipping.');process.exit(0)}const sdk=${FLUTTER_SDK_DIRECTORY_EXPRESSION};if(fs.existsSync(sdk)){console.log('Flutter SDK already installed at '+sdk+' - skipping.');process.exit(0)}process.exit(cp.spawnSync('git',['clone','--depth','1','--branch','${FLUTTER_SDK_VERSION}','https://github.com/flutter/flutter.git',sdk],{stdio:'inherit'}).status ?? 1)"`;
1262
+ var FLUTTER_TOOL_PATH_PRELUDE = `const fs=require('node:fs'),path=require('node:path');if(!fs.existsSync('pubspec.yaml')){console.log('No Flutter projects - skipping.');process.exit(0)}const sdk=${FLUTTER_SDK_DIRECTORY_EXPRESSION};if(!fs.existsSync(sdk)){console.log('Flutter SDK was not installed by mnci (already on PATH) - skipping.');process.exit(0)}const bin=path.join(sdk,'bin');`;
1263
+ var FLUTTER_TOOL_PATH_AZURE = `node -e "${FLUTTER_TOOL_PATH_PRELUDE}console.log('##vso[task.prependpath]'+bin)"`;
1264
+ 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'`})"`;
1265
+ 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)"`;
1196
1266
  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";
1197
1267
  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)"`;
1198
1268
  function releaseGuard(pythonPublishEnv) {
@@ -1308,6 +1378,23 @@ steps:
1308
1378
  - script: ${GO_TOOL_PATH_AZURE}
1309
1379
  displayName: Add Go tool bin to PATH
1310
1380
 
1381
+ # Flutter, if the workspace has any. Unlike Python and Go, hosted agents do
1382
+ # NOT ship the Flutter SDK, so this installs it (shallow git clone at a
1383
+ # pinned tag) and puts it on PATH. All three guards skip cleanly on a
1384
+ # workspace with no root pubspec.yaml, and the install also skips when an
1385
+ # SDK is already on PATH.
1386
+ - script: ${FLUTTER_SDK_INSTALL_GUARD}
1387
+ displayName: Install the Flutter SDK (${FLUTTER_SDK_VERSION})
1388
+
1389
+ - script: ${FLUTTER_TOOL_PATH_AZURE}
1390
+ displayName: Add the Flutter SDK to PATH
1391
+
1392
+ # One command resolves EVERY Dart dependency, internal and external, for the
1393
+ # whole workspace: the projects are pub workspace members, so this writes a
1394
+ # single root pubspec.lock they all share.
1395
+ - script: ${FLUTTER_PUB_GET_GUARD}
1396
+ displayName: Resolve Dart dependencies (one pub get for the whole workspace)
1397
+
1311
1398
  # Fails fast, with an unambiguous message, when a stale TypeScript project
1312
1399
  # reference (or another sync generator's drift) was never synced+committed
1313
1400
  # locally \u2014 sync.applyChanges (nx.json) only auto-applies interactively, so
@@ -1462,6 +1549,23 @@ jobs:
1462
1549
  - run: ${GO_TOOL_PATH_GITHUB}
1463
1550
  name: Add Go tool bin to PATH
1464
1551
 
1552
+ # Flutter, if the workspace has any. Unlike Python and Go, hosted runners
1553
+ # do NOT ship the Flutter SDK, so this installs it (shallow git clone at
1554
+ # a pinned tag) and puts it on PATH. All three guards skip cleanly on a
1555
+ # workspace with no root pubspec.yaml, and the install also skips when an
1556
+ # SDK is already on PATH.
1557
+ - run: ${FLUTTER_SDK_INSTALL_GUARD}
1558
+ name: Install the Flutter SDK (${FLUTTER_SDK_VERSION})
1559
+
1560
+ - run: ${FLUTTER_TOOL_PATH_GITHUB}
1561
+ name: Add the Flutter SDK to PATH
1562
+
1563
+ # One command resolves EVERY Dart dependency, internal and external, for
1564
+ # the whole workspace: the projects are pub workspace members, so this
1565
+ # writes a single root pubspec.lock they all share.
1566
+ - run: ${FLUTTER_PUB_GET_GUARD}
1567
+ name: Resolve Dart dependencies (one pub get for the whole workspace)
1568
+
1465
1569
  # Fails fast, with an unambiguous message, when a stale TypeScript project
1466
1570
  # reference (or another sync generator's drift) was never synced+committed
1467
1571
  # locally \u2014 sync.applyChanges (nx.json) only auto-applies interactively, so
@@ -1547,6 +1651,18 @@ updates:
1547
1651
  - "/libs/*"
1548
1652
  schedule:
1549
1653
  interval: weekly
1654
+
1655
+ # Same glob reasoning as pip above. Dart projects are pub workspace members,
1656
+ # so each one declares its own dependencies even though they all resolve
1657
+ # through the single root pubspec.lock \u2014 hence the per-project directories
1658
+ # rather than just "/".
1659
+ - package-ecosystem: pub
1660
+ directories:
1661
+ - "/apps/*"
1662
+ - "/packages/*"
1663
+ - "/libs/*"
1664
+ schedule:
1665
+ interval: weekly
1550
1666
  `;
1551
1667
  function applyOverlay(workspaceRoot, options) {
1552
1668
  const nxJsonPath = (0, import_node_path9.join)(workspaceRoot, "nx.json");
@@ -1689,7 +1805,13 @@ async function runNew(name, options) {
1689
1805
  " mnci add react-app web # or: node-app, node-function-app, npm-lib, internal-lib,"
1690
1806
  );
1691
1807
  logger.info(
1692
- " # python-app, python-function-app, python-lib, python-internal-lib"
1808
+ " # python-app, python-function-app, python-lib, python-internal-lib,"
1809
+ );
1810
+ logger.info(
1811
+ " # go-app, go-function-app, go-lib, go-internal-lib,"
1812
+ );
1813
+ logger.info(
1814
+ " # flutter-app, flutter-lib, flutter-internal-lib"
1693
1815
  );
1694
1816
  logger.info(' git add -A && git commit -m "feat: initial workspace"');
1695
1817
  }