@mnci/cli 4.14.0 → 4.15.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
@@ -103,6 +103,7 @@ mnci add python-vendor shared --lib core # wire core's module into shared's bui
103
103
  # Go (@nx-go/nx-go — one root go.mod, golangci-lint + go test)
104
104
  mnci add go-app api # executable -> apps/ (binary, zipped into the drop)
105
105
  mnci add go-app cli --release # ...released: tag + per-platform zips on the GitHub Release
106
+ mnci add go-app tray --cgo # needs a C toolchain: built on a runner of each OS
106
107
  mnci add go-function-app fn # serverless handler -> apps/
107
108
  mnci add go-lib core # publishable (by git tag) -> packages/
108
109
  mnci add go-internal-lib util # private shared package -> libs/
@@ -1439,6 +1440,51 @@ pipeline installs `golangci-lint` itself (see below).
1439
1440
  `nx-release-publish` target is written: there is nothing to push. The only
1440
1441
  real difference between `go-lib` and `go-internal-lib` is intent, recorded
1441
1442
  in the `type:go-lib` tag and the `packages/` location.
1443
+ - **Pure-Go apps are cross-compiled; an app that needs a C toolchain is not
1444
+ (`mnci add go-app <name> --cgo`).** `build-all` builds six static binaries from
1445
+ one machine with `CGO_ENABLED=0`, which is right until the app needs cgo: a
1446
+ system-tray icon (Cocoa on macOS, GTK on Linux), a native GUI toolkit, a cgo
1447
+ database driver. Those can only be built where the C toolchain and the target
1448
+ OS's libraries are, so a `--cgo` app is tagged `build:cgo` and gets
1449
+ `build-native` and `package-native` (this machine only, `CGO_ENABLED=1`, the
1450
+ same `VERSION` stamp and `-trimpath`, the zip named as `package-all` names a
1451
+ platform's) **instead of** `package`, `build-all` and `package-all`. It keeps
1452
+ `build`, `test`, `lint` and `start` for local work.
1453
+ - **A `native` job, only when such an app exists.** The generated pipeline
1454
+ gains a job with one leg each on `windows-latest`, `macos-latest` and
1455
+ `ubuntu-latest` (GitHub Actions: a matrix; Azure Pipelines: a matrix of
1456
+ `vmImage`s, with the original job moved under `jobs:`). Each leg lints,
1457
+ tests, builds and packages the native apps and publishes its zips as an
1458
+ artifact. The single-agent verify excludes them (`--exclude=tag:build:cgo`),
1459
+ because it cannot build them. A workspace with no native app keeps a
1460
+ pipeline byte-identical to the one it had: the job is decided when the file is
1461
+ written, since neither provider can skip a whole job on a file's existence.
1462
+ - **Run `mnci upgrade` after adding one.** `add` does not rewrite the
1463
+ pipeline files, so until then CI would verify the app on the one agent that
1464
+ cannot build it. `mnci add` says so, and `mnci doctor` fails while a
1465
+ pipeline lacks the job, and while no C compiler is installed on the machine
1466
+ (it reads `CC`, then `gcc`, `clang`, `cc`).
1467
+ - **Linux prerequisites are yours to finish.** The leg installs a C compiler
1468
+ and `pkg-config`, which is all mnci can know. The `-dev` packages your app
1469
+ links (`libgtk-3-dev` and `libayatana-appindicator3-dev` for a tray icon) go
1470
+ on that one marked line in the pipeline. The macOS runner ships Xcode's tools,
1471
+ and the Windows leg relies on the hosted image's MinGW-w64 `gcc`: the nightly
1472
+ e2e builds a cgo app on `windows-latest`, and says so loudly if it finds no
1473
+ compiler there rather than passing.
1474
+ - **Architectures: one per OS, the runner's own.** `windows-latest` and
1475
+ `ubuntu-latest` are amd64 and `macos-latest` is arm64, so a native app ships
1476
+ `windows-amd64`, `linux-amd64` and `darwin-arm64`. `darwin-amd64`,
1477
+ `linux-arm64` and `windows-arm64` are not built: they need an Intel macOS
1478
+ runner, an arm Linux runner or a cross-compiler. The legs are fixed in the
1479
+ generated pipeline today, so adding one means editing a file `mnci upgrade`
1480
+ rewrites (russoedu/MoNecromanCi#269 is about giving that a safe place).
1481
+ - **Releasing one.** With `--release` as well, each leg, on a push to main and
1482
+ after the `ci` job has tagged, runs
1483
+ `node tools/go-app-release.cjs assets --native`, which builds
1484
+ `package-native` with `VERSION` set to the tag's version and uploads that
1485
+ OS's zip to the same GitHub Release, so one release collects a zip from every
1486
+ runner. Azure Pipelines has no GitHub Release to attach to and stops at the
1487
+ artifact.
1442
1488
  - **Releasing a Go app is an opt-in: `mnci add go-app <name> --release`.** The
1443
1489
  app is tagged `release:go`, which `release.projects` selects by tag (as it
1444
1490
  does for a VS Code extension, so `mnci upgrade` keeps it). Without the flag
@@ -1472,8 +1518,8 @@ pipeline installs `golangci-lint` itself (see below).
1472
1518
  commits that touch an app's dependencies, not only its own folder (measured:
1473
1519
  a `fix:` touching only an imported `go-internal-lib` produced a patch
1474
1520
  release of the app), which is right for a binary that links the library in.
1475
- - Not covered yet: apps that need a native toolchain (cgo) and so one runner
1476
- per OS (russoedu/MoNecromanCi#263). The asset step builds all six
1521
+ - An app that needs a C toolchain is released from several runners instead:
1522
+ see _Pure-Go apps are cross-compiled_ above. This step builds all six
1477
1523
  platforms on one runner, which is what `CGO_ENABLED=0` allows.
1478
1524
  - **No publish-time dependency injection**, unlike Python's vendoring: `go
1479
1525
  build` links statically, so the binary in the drop already contains
package/dist/cli.js CHANGED
@@ -435,6 +435,17 @@ ${feedShortKey}:email=npm-requires-this-and-never-uses-it
435
435
  }
436
436
  var VSCODE_EXTENSION_TAG = "type:vscode-extension";
437
437
  var GO_RELEASE_TAG = "release:go";
438
+ var GO_CGO_TAG = "build:cgo";
439
+ function hasNativeGoApp(workspaceRoot) {
440
+ return (0, import_node_fs2.globSync)("apps/*/project.json", { cwd: workspaceRoot }).some((projectJson) => {
441
+ try {
442
+ const { tags } = JSON.parse((0, import_node_fs2.readFileSync)((0, import_node_path2.join)(workspaceRoot, projectJson), "utf8"));
443
+ return (tags ?? []).includes(GO_CGO_TAG);
444
+ } catch {
445
+ return false;
446
+ }
447
+ });
448
+ }
438
449
  function releaseConfig(ci) {
439
450
  const githubReleases = ci === "github";
440
451
  return {
@@ -1035,7 +1046,126 @@ var FLUTTER_TOOL_PATH_GITHUB = `node -e "${FLUTTER_TOOL_PATH_PRELUDE}if(!process
1035
1046
  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)"`;
1036
1047
  var NPM_AUDIT_STEP = `node -e "const cp=require('node:child_process');const r=cp.spawnSync('npm',['audit','--json'],{encoding:'utf8',shell:process.platform==='win32',maxBuffer:33554432});let d;try{d=JSON.parse(r.stdout)}catch{console.log('npm audit produced no JSON (exit '+r.status+') - not blocking on a broken audit.');process.exit(0)}const all=Object.values(d.vulnerabilities||{});const BLOCK=['critical','high','moderate'];const major=v=>v.fixAvailable&&typeof v.fixAvailable==='object'&&v.fixAvailable.isSemVerMajor;const act=all.filter(v=>v.fixAvailable&&BLOCK.includes(v.severity)&&!major(v));const rest=all.filter(v=>!act.includes(v));for(const v of rest)console.log(' note ['+v.severity+'] '+v.name+(!v.fixAvailable?' - NO fix available upstream, nothing to do here':major(v)?' - only a semver-major change would remove it (often a parent downgrade), not applied automatically':' - fix available, below the blocking threshold'));if(act.length===0){console.log('npm audit - '+all.length+' advisory(ies), none actionable at moderate or above.');process.exit(0)}for(const v of act)console.log(' BLOCKING ['+v.severity+'] '+v.name+' - fix available');console.log('Each has a published fix. Add a targeted overrides entry in package.json rather than npm audit fix --force.');process.exit(1)"`;
1037
1048
  var VERIFY_TARGETS = "lint,typecheck,test,build";
1038
- 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)"`;
1049
+ function affectedOrAllGuard(exclude = "") {
1050
+ const extra = exclude === "" ? "" : `+' ${exclude}'`;
1051
+ return `node -e "const cp=require('node:child_process');const T='${VERIFY_TARGETS}';const all=()=>process.exit(cp.spawnSync('npx nx run-many -t '+T${extra},{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${extra},{stdio:'inherit',shell:true}).status ?? 1)"`;
1052
+ }
1053
+ var AFFECTED_OR_ALL_GUARD = affectedOrAllGuard();
1054
+ var AFFECTED_OR_ALL_GUARD_WITHOUT_NATIVE = affectedOrAllGuard(`--exclude=tag:${GO_CGO_TAG}`);
1055
+ var NATIVE_BUILD_COMMAND = `npx nx run-many -t lint,test,build-native,package-native --projects=tag:${GO_CGO_TAG}`;
1056
+ var NATIVE_LINUX_PREREQUISITES = "sudo apt-get update && sudo apt-get install -y gcc pkg-config";
1057
+ function githubNativeJob(npmAuthName, npmAuthValue, onMain) {
1058
+ return `
1059
+ # Apps that need a C toolchain (mnci add go-app --cgo) cannot be cross-compiled
1060
+ # from the job above, so each is linted, tested, built and packaged here, on a
1061
+ # runner of every OS it ships for, and is left out of that job's verify step.
1062
+ native:
1063
+ name: native (\${{ matrix.os }})
1064
+ needs: ci
1065
+ runs-on: \${{ matrix.os }}
1066
+ strategy:
1067
+ fail-fast: false
1068
+ matrix:
1069
+ os: [windows-latest, macos-latest, ubuntu-latest]
1070
+ steps:
1071
+ - uses: actions/checkout@${ACTION_VERSIONS["actions/checkout"]}
1072
+ with:
1073
+ fetch-depth: 0
1074
+
1075
+ # The release tags name the version a native build is stamped with.
1076
+ - run: git fetch --all --prune --tags
1077
+ name: Fetch branches and release tags
1078
+
1079
+ - uses: actions/setup-node@${ACTION_VERSIONS["actions/setup-node"]}
1080
+ with:
1081
+ node-version: ${NODE_VERSION}
1082
+ cache: npm
1083
+
1084
+ - run: npm install -g npm@${NPM_VERSION}
1085
+ name: Pin npm to the major mnci verifies against
1086
+
1087
+ - run: npm ci
1088
+ name: Install dependencies
1089
+ env:
1090
+ ${npmAuthName}: ${npmAuthValue}
1091
+
1092
+ - run: ${GO_MODULE_DOWNLOAD_GUARD}
1093
+ name: Download Go module dependencies
1094
+
1095
+ - run: ${GOLANGCI_LINT_INSTALL_GUARD}
1096
+ name: Install golangci-lint
1097
+
1098
+ - run: ${GO_TOOL_PATH_GITHUB}
1099
+ name: Add Go tool bin to PATH
1100
+
1101
+ # A C compiler and pkg-config, which is all mnci can know a cgo app needs.
1102
+ # Add the -dev packages your app links to this line, for example
1103
+ # libgtk-3-dev and libayatana-appindicator3-dev for a system-tray icon.
1104
+ # macOS and Windows runners ship their toolchains.
1105
+ - run: ${NATIVE_LINUX_PREREQUISITES}
1106
+ name: Install native prerequisites (Linux)
1107
+ if: \${{ runner.os == 'Linux' }}
1108
+
1109
+ - run: ${NATIVE_BUILD_COMMAND}
1110
+ name: Lint, test, build and package the native apps
1111
+
1112
+ - uses: actions/upload-artifact@${ACTION_VERSIONS["actions/upload-artifact"]}
1113
+ with:
1114
+ name: native-\${{ matrix.os }}
1115
+ path: dist/drop
1116
+ if-no-files-found: ignore
1117
+
1118
+ # A releasable native app (--cgo with --release) gets this OS's zip attached to
1119
+ # the GitHub Release the ci job just created, stamped with the tag's version.
1120
+ - run: node tools/go-app-release.cjs assets --native
1121
+ name: Attach this OS's zip to the GitHub Release (releasable native apps)
1122
+ if: \${{ ${onMain} && hashFiles('tools/go-app-release.cjs') != '' }}
1123
+ env:
1124
+ GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
1125
+ `;
1126
+ }
1127
+ function indentYaml(text, spaces) {
1128
+ const pad = " ".repeat(spaces);
1129
+ return text.split("\n").map((line) => line === "" ? line : pad + line).join("\n");
1130
+ }
1131
+ function withAzureNativeJob(document, nativeSteps) {
1132
+ const poolAt = document.indexOf("\npool:\n");
1133
+ const variablesAt = document.indexOf("\nvariables:\n");
1134
+ const stepsAt = document.indexOf("\nsteps:\n");
1135
+ if (poolAt === -1 || variablesAt < poolAt || stepsAt < variablesAt) {
1136
+ throw new Error("The Azure pipeline no longer has its pool, variables and steps blocks in that order.");
1137
+ }
1138
+ const head = document.slice(0, poolAt + 1);
1139
+ const pool2 = document.slice(poolAt + "\npool:\n".length, variablesAt).replace(/\n+$/, "");
1140
+ const variables = document.slice(variablesAt + 1, stepsAt).replace(/\n+$/, "");
1141
+ const steps = document.slice(stepsAt + "\nsteps:\n".length).replace(/\n+$/, "");
1142
+ const matrix = Object.entries({ windows: "windows-latest", macos: "macos-latest", linux: "ubuntu-latest" }).map(([leg, image]) => ` ${leg}:
1143
+ legName: ${leg}
1144
+ vmImage: ${image}`).join("\n");
1145
+ return `${head}${variables}
1146
+
1147
+ jobs:
1148
+ - job: ci
1149
+ pool:
1150
+ ${indentYaml(pool2, 4)}
1151
+ steps:
1152
+ ${indentYaml(steps, 4)}
1153
+
1154
+ # Apps that need a C toolchain (mnci add go-app --cgo) cannot be cross-compiled
1155
+ # from the job above, so each is linted, tested, built and packaged here, on an
1156
+ # agent of every OS it ships for, and is left out of that job's verify step.
1157
+ - job: native
1158
+ displayName: Native apps (cgo)
1159
+ dependsOn: ci
1160
+ strategy:
1161
+ matrix:
1162
+ ${matrix}
1163
+ pool:
1164
+ vmImage: $(vmImage)
1165
+ steps:
1166
+ ${indentYaml(nativeSteps, 4)}
1167
+ `;
1168
+ }
1039
1169
  var PACK_APPS_GUARD = `node -e "const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const hasProjectJson=fs.globSync('apps/*/project.json').length>0;const hasInlineNx=fs.globSync('apps/*/package.json').some((f)=>{try{return Boolean(JSON.parse(fs.readFileSync(f,'utf8')).nx)}catch{return false}});const hasCsproj=fs.globSync('apps/*/*.csproj').length>0;if(!hasProjectJson&&!hasInlineNx&&!hasCsproj){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)"`;
1040
1170
  var NPM_AUTH_PREFLIGHT = `node -e "const fs=require('node:fs'),cp=require('node:child_process');if(fs.globSync('packages/*/package.json').length===0){console.log('No npm packages to release - skipping.');process.exit(0)}if(!process.env.NODE_AUTH_TOKEN){console.error('NPM_TOKEN is empty or unset, so the publish would fail AFTER nx release has already tagged. Add it as a repository secret named exactly NPM_TOKEN - under Actions, not the Dependabot or Codespaces tab, and not an Environment secret. Use an npm Automation token - a Publish token is refused by 2FA in CI.');process.exit(1)}const r=cp.spawnSync('npm',['whoami','--registry=https://registry.npmjs.org/'],{encoding:'utf8',shell:process.platform==='win32'});if(r.status!==0){console.error('NPM_TOKEN is set but the registry rejected it - '+((r.stderr||'')+(r.stdout||'')).trim()+'. Check that it has not expired and that it grants publish rights on this scope.');process.exit(1)}console.log('npm auth OK as '+r.stdout.trim())"`;
1041
1171
  function npmAuthPreflight(registryKind) {
@@ -1076,7 +1206,7 @@ function npmAuthEnvVariable(registryKind, variableReference) {
1076
1206
  function poolBlock(agent) {
1077
1207
  return /^(?:ubuntu|windows|macos)-/i.test(agent) ? ` vmImage: ${agent}` : ` name: ${agent}`;
1078
1208
  }
1079
- function azurePipelinesYaml(agent, variableGroup, pythonPublishUrl2, registryKind = "azure-artifacts", nugetFeedUrl2, npmAuth = "pat") {
1209
+ function azurePipelinesYaml(agent, variableGroup, pythonPublishUrl2, registryKind = "azure-artifacts", nugetFeedUrl2, npmAuth = "pat", nativeApps = false) {
1080
1210
  const onMain = "and(succeeded(), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI'), eq(variables['Build.SourceBranchName'], 'main'))";
1081
1211
  const [npmAuthName, npmAuthValue] = npmAuthEnvVariable(registryKind, (name) => `$(${name})`);
1082
1212
  const buildIdentity = npmAuth === "build-identity" && registryKind === "azure-artifacts";
@@ -1094,7 +1224,7 @@ function azurePipelinesYaml(agent, variableGroup, pythonPublishUrl2, registryKin
1094
1224
  const npmCiEnv = buildIdentity ? "" : `
1095
1225
  env:
1096
1226
  ${npmAuthName}: ${npmAuthValue}`;
1097
- return `name: monorepo-ci-$(Date:yyyyMMdd)$(Rev:.r)
1227
+ const document = `name: monorepo-ci-$(Date:yyyyMMdd)$(Rev:.r)
1098
1228
 
1099
1229
  # Generated by MoNecromanCI. Deliberately thin: Nx builds, 'nx release'
1100
1230
  # versions from conventional commits and pushes ONLY a tag to main, and each
@@ -1305,7 +1435,7 @@ ${npmAuthenticateStep} - script: npm ci
1305
1435
  # 'npm run lint' is 'nx run-many -t lint', a strict subset of the targets
1306
1436
  # below, so adding it back as its own step would only duplicate work \u2014 and on
1307
1437
  # a pull request it would re-lint every project, discarding the point of this.
1308
- - script: ${AFFECTED_OR_ALL_GUARD}
1438
+ - script: ${nativeApps ? AFFECTED_OR_ALL_GUARD_WITHOUT_NATIVE : AFFECTED_OR_ALL_GUARD}
1309
1439
  displayName: Verify (affected on a PR, every project on main)
1310
1440
 
1311
1441
  # Pack every app into dist/drop/<type>-<name>.zip via each app's 'package'
@@ -1396,8 +1526,56 @@ ${npmAuthPreflight(registryKind) ? `
1396
1526
  displayName: Push release tags (nx release's own push never runs without a remote Release configured)
1397
1527
  condition: ${onMain}
1398
1528
  `;
1529
+ if (!nativeApps) {
1530
+ return document;
1531
+ }
1532
+ const nativeSteps = ` - checkout: self
1533
+ fetchDepth: 0
1534
+
1535
+ - task: UseNode@1
1536
+ inputs:
1537
+ version: 24.x
1538
+
1539
+ - task: Cache@2
1540
+ displayName: Cache npm packages
1541
+ inputs:
1542
+ key: 'npm | "$(Agent.OS)" | package-lock.json'
1543
+ restoreKeys: |
1544
+ npm | "$(Agent.OS)"
1545
+ path: $(npm_config_cache)
1546
+
1547
+ ${npmAuthenticateStep} - script: npm ci
1548
+ displayName: Install dependencies${npmCiEnv}
1549
+
1550
+ - script: ${GO_MODULE_DOWNLOAD_GUARD}
1551
+ displayName: Download Go module dependencies
1552
+
1553
+ - script: ${GOLANGCI_LINT_INSTALL_GUARD}
1554
+ displayName: Install golangci-lint
1555
+
1556
+ - script: ${GO_TOOL_PATH_AZURE}
1557
+ displayName: Add Go tool bin to PATH
1558
+
1559
+ # A C compiler and pkg-config, which is all mnci can know a cgo app needs. Add the
1560
+ # -dev packages your app links to this line, for example libgtk-3-dev and
1561
+ # libayatana-appindicator3-dev for a system-tray icon. macOS and Windows agents
1562
+ # ship their toolchains.
1563
+ - script: ${NATIVE_LINUX_PREREQUISITES}
1564
+ displayName: Install native prerequisites (Linux)
1565
+ condition: eq(variables['Agent.OS'], 'Linux')
1566
+
1567
+ - script: ${NATIVE_BUILD_COMMAND}
1568
+ displayName: Lint, test, build and package the native apps
1569
+
1570
+ - task: PublishBuildArtifacts@1
1571
+ displayName: Publish this OS's native zips
1572
+ inputs:
1573
+ PathtoPublish: $(Build.SourcesDirectory)/dist/drop
1574
+ ArtifactName: native-$(legName)
1575
+ `;
1576
+ return withAzureNativeJob(document, nativeSteps);
1399
1577
  }
1400
- function githubActionsYaml(agent, pythonPublishUrl2, registryKind = "azure-artifacts", ci = "github", nugetFeedUrl2) {
1578
+ function githubActionsYaml(agent, pythonPublishUrl2, registryKind = "azure-artifacts", ci = "github", nugetFeedUrl2, nativeApps = false) {
1401
1579
  const onMain = "github.event_name == 'push' && github.ref_name == 'main'";
1402
1580
  const [npmAuthName, npmAuthValue] = npmAuthEnvVariable(
1403
1581
  registryKind,
@@ -1566,7 +1744,7 @@ jobs:
1566
1744
  # below, so adding it back as its own step would only duplicate work \u2014 and
1567
1745
  # on a pull request it would re-lint every project, discarding the point
1568
1746
  # of this.
1569
- - run: ${AFFECTED_OR_ALL_GUARD}
1747
+ - run: ${nativeApps ? AFFECTED_OR_ALL_GUARD_WITHOUT_NATIVE : AFFECTED_OR_ALL_GUARD}
1570
1748
  name: Verify (affected on a PR, every project on main)
1571
1749
 
1572
1750
  # Pack every app into dist/drop/<type>-<name>.zip via each app's 'package'
@@ -1688,7 +1866,7 @@ ${githubReleases ? `
1688
1866
  - run: git push origin --tags
1689
1867
  name: Push release tags (nx release's own push never runs without a remote Release configured)
1690
1868
  if: \${{ ${onMain} }}
1691
- `}`;
1869
+ `}${nativeApps ? githubNativeJob(npmAuthName, npmAuthValue, onMain) : ""}`;
1692
1870
  }
1693
1871
  var PIP_PROJECT_GLOBS = ["apps", "python-packages", "libs"];
1694
1872
  var PUB_PROJECT_GLOBS = ["apps", "packages", "libs"];
@@ -2005,6 +2183,7 @@ function applyOverlay(workspaceRoot, options, onProgress = () => {
2005
2183
  }
2006
2184
  const publishUrl = pythonPublishUrl(options.registry);
2007
2185
  const nugetUrl = nugetFeedUrl(options.registry);
2186
+ const nativeApps = hasNativeGoApp(workspaceRoot);
2008
2187
  if (options.ci === "azure" || options.ci === "both") {
2009
2188
  onProgress("azure-pipelines.yml \u2014 build, verify, pack and release");
2010
2189
  writeFileEnsured(
@@ -2015,7 +2194,8 @@ function applyOverlay(workspaceRoot, options, onProgress = () => {
2015
2194
  publishUrl,
2016
2195
  options.registry.kind,
2017
2196
  nugetUrl,
2018
- options.npmAuth
2197
+ options.npmAuth,
2198
+ nativeApps
2019
2199
  )
2020
2200
  );
2021
2201
  }
@@ -2023,7 +2203,7 @@ function applyOverlay(workspaceRoot, options, onProgress = () => {
2023
2203
  onProgress(".github/workflows/ci.yml and dependabot.yml");
2024
2204
  writeFileEnsured(
2025
2205
  (0, import_node_path2.join)(workspaceRoot, ".github/workflows/ci.yml"),
2026
- githubActionsYaml(options.agent, publishUrl, options.registry.kind, options.ci, nugetUrl)
2206
+ githubActionsYaml(options.agent, publishUrl, options.registry.kind, options.ci, nugetUrl, nativeApps)
2027
2207
  );
2028
2208
  writeFileEnsured((0, import_node_path2.join)(workspaceRoot, ".github/dependabot.yml"), dependabotConfig(workspaceRoot));
2029
2209
  }
@@ -2631,10 +2811,11 @@ var GO_RELEASE_SCRIPT = String.raw`#!/usr/bin/env node
2631
2811
  // A releasable Go app (tag release:go) is versioned from its git tag, with no
2632
2812
  // manifest, and its per-platform zips are attached to its GitHub Release:
2633
2813
  // release.version.versionActions this file, loaded by 'nx release'
2634
- // node tools/go-app-release.cjs assets
2814
+ // node tools/go-app-release.cjs assets the apps built for all six platforms
2815
+ // node tools/go-app-release.cjs assets --native the apps that need a C toolchain, this OS only
2635
2816
  'use strict'
2636
2817
  const { spawnSync } = require('node:child_process')
2637
- const { existsSync, readdirSync } = require('node:fs')
2818
+ const { existsSync, readdirSync, readFileSync } = require('node:fs')
2638
2819
  const { dirname, join } = require('node:path')
2639
2820
  const { VersionActions } = require('nx/release')
2640
2821
 
@@ -2691,17 +2872,28 @@ function nx (args, env) {
2691
2872
  return run(process.execPath, [join(dirname(manifest), entry), ...args], env)
2692
2873
  }
2693
2874
 
2694
- function releasableApps () {
2695
- return JSON.parse(nx(['show', 'projects', '--projects', 'tag:release:go', '--json'])).filter(Boolean)
2875
+ function releasableApps (native) {
2876
+ const names = existsSync('apps') ? readdirSync('apps') : []
2877
+
2878
+ return names.filter(name => {
2879
+ try {
2880
+ const tags = JSON.parse(readFileSync(join('apps', name, 'project.json'), 'utf8')).tags || []
2881
+
2882
+ return tags.includes('release:go') && tags.includes('build:cgo') === native
2883
+ } catch {
2884
+ return false
2885
+ }
2886
+ })
2696
2887
  }
2697
2888
 
2698
- function attachAssets () {
2699
- const apps = releasableApps()
2889
+ function attachAssets (native) {
2890
+ const apps = releasableApps(native)
2700
2891
  if (apps.length === 0) {
2701
- console.log('No releasable Go app - nothing to attach.')
2892
+ console.log('No releasable ' + (native ? 'native ' : '') + 'Go app - nothing to attach.')
2702
2893
 
2703
2894
  return
2704
2895
  }
2896
+ const target = native ? 'package-native' : 'package-all'
2705
2897
  const tags = run('git', ['tag', '--points-at', 'HEAD']).split(/\r?\n/).filter(Boolean)
2706
2898
  let attached = 0
2707
2899
  for (const app of apps) {
@@ -2711,11 +2903,11 @@ function attachAssets () {
2711
2903
  continue
2712
2904
  }
2713
2905
  const version = tag.slice(app.length + 1)
2714
- console.log(app + ': building the six platforms as ' + version)
2715
- nx(['run', app + ':package-all'], { VERSION: version })
2906
+ console.log(app + ': ' + (native ? 'building for this OS' : 'building the six platforms') + ' as ' + version)
2907
+ nx(['run', app + ':' + target], { VERSION: version })
2716
2908
  const prefix = 'go-app-' + app + '-'
2717
2909
  const zips = existsSync('dist/drop') ? readdirSync('dist/drop').filter(each => each.startsWith(prefix) && each.endsWith('.zip')) : []
2718
- if (zips.length === 0) fail(app + ': package-all produced no ' + prefix + '*.zip in dist/drop.')
2910
+ if (zips.length === 0) fail(app + ': ' + target + ' produced no ' + prefix + '*.zip in dist/drop.')
2719
2911
  const upload = spawnSync('gh', ['release', 'upload', tag, ...zips.map(each => join('dist/drop', each)), '--clobber'], { stdio: 'inherit', shell: process.platform === 'win32' })
2720
2912
  if (upload.status !== 0) fail(app + ': could not attach the zips to the ' + tag + ' release (exit ' + upload.status + ').')
2721
2913
  console.log(app + ': attached ' + zips.length + ' zips to ' + tag)
@@ -2725,8 +2917,8 @@ function attachAssets () {
2725
2917
  }
2726
2918
 
2727
2919
  if (require.main === module) {
2728
- if (process.argv[2] !== 'assets') fail('Usage: node tools/go-app-release.cjs assets')
2729
- attachAssets()
2920
+ if (process.argv[2] !== 'assets' || (process.argv[3] !== undefined && process.argv[3] !== '--native')) fail('Usage: node tools/go-app-release.cjs assets [--native]')
2921
+ attachAssets(process.argv[3] === '--native')
2730
2922
  }
2731
2923
  `;
2732
2924
  var GO_RELEASE_PUBLISH_TARGET = {
@@ -2875,6 +3067,25 @@ function goPackageAllTarget(tag, name) {
2875
3067
  options: { command }
2876
3068
  };
2877
3069
  }
3070
+ var GO_HOST_PLATFORM = "const host=k=>spawnSync('go',['env',k],{encoding:'utf8'}).stdout.trim();const os=host('GOOS'),arch=host('GOARCH');";
3071
+ function goNativeBuildTarget(name) {
3072
+ const command = `node -e "const{spawnSync}=require('node:child_process');${GO_HOST_PLATFORM}const v=process.env.VERSION||'dev';const out='../../dist/platforms/${name}/'+os+'-'+arch+'/${name}'+(os==='windows'?'.exe':'');const r=spawnSync('go',['build','-trimpath','-ldflags','-s -w -X main.version='+v,'-o',out,'.'],{cwd:'apps/${name}',stdio:'inherit',env:{...process.env,CGO_ENABLED:'1'}});process.exit(r.status??1)"`;
3073
+ return {
3074
+ executor: "nx:run-commands",
3075
+ inputs: ["default", "^default", "{workspaceRoot}/go.mod", "{workspaceRoot}/go.sum", { env: "VERSION" }],
3076
+ outputs: [`{workspaceRoot}/dist/platforms/${name}`],
3077
+ options: { command }
3078
+ };
3079
+ }
3080
+ function goNativePackageTarget(name) {
3081
+ const command = `node -e "const fs=require('node:fs');const{spawnSync}=require('node:child_process');const A=require('adm-zip');${GO_HOST_PLATFORM}const d=os+'-'+arch;fs.mkdirSync('dist/drop',{recursive:true});const z=new A();z.addLocalFolder('dist/platforms/${name}/'+d);z.writeZip('dist/drop/go-app-${name}-'+d+'.zip')"`;
3082
+ return {
3083
+ executor: "nx:run-commands",
3084
+ dependsOn: ["build-native"],
3085
+ outputs: [`{workspaceRoot}/dist/drop/go-app-${name}-*.zip`],
3086
+ options: { command }
3087
+ };
3088
+ }
2878
3089
  function addGoPlatformTargets(workspaceRoot) {
2879
3090
  const changed = [];
2880
3091
  const apps = (0, import_node_path7.join)(workspaceRoot, "apps");
@@ -2888,7 +3099,7 @@ function addGoPlatformTargets(workspaceRoot) {
2888
3099
  }
2889
3100
  const project = JSON.parse((0, import_node_fs6.readFileSync)(projectJsonPath, "utf8"));
2890
3101
  const tag = (project.tags ?? []).find((each) => each === "type:go-app" || each === "type:go-function-app")?.slice("type:".length);
2891
- if (tag === void 0) {
3102
+ if (tag === void 0 || (project.tags ?? []).includes(GO_CGO_TAG)) {
2892
3103
  continue;
2893
3104
  }
2894
3105
  const missing = {};
@@ -2978,6 +3189,7 @@ function prepareGo(workspaceRoot) {
2978
3189
  ensureGoModule(workspaceRoot);
2979
3190
  }
2980
3191
  function addGoApp(workspaceRoot, name, options = {}) {
3192
+ const cgo = options.cgo === true;
2981
3193
  prepareGo(workspaceRoot);
2982
3194
  ensureAdmZip(workspaceRoot);
2983
3195
  runNx(
@@ -2986,24 +3198,31 @@ function addGoApp(workspaceRoot, name, options = {}) {
2986
3198
  "@nx-go/nx-go:application",
2987
3199
  `apps/${name}`,
2988
3200
  `--name=${name}`,
2989
- "--tags=type:go-app",
3201
+ cgo ? `--tags=type:go-app,${GO_CGO_TAG}` : "--tags=type:go-app",
2990
3202
  "--no-interactive"
2991
3203
  ],
2992
3204
  workspaceRoot
2993
3205
  );
2994
3206
  addProjectJsonTargets((0, import_node_path7.join)(workspaceRoot, "apps", name, "project.json"), {
2995
- "build": goBuildTarget(name),
2996
- "test": goTestTarget(),
2997
- "lint": goLintTarget(),
2998
- "package": goPackageTarget("go-app", name),
2999
- "build-all": goBuildAllTarget(name),
3000
- "package-all": goPackageAllTarget("go-app", name),
3001
- "start": goStartTarget(name)
3207
+ build: goBuildTarget(name),
3208
+ test: goTestTarget(),
3209
+ lint: goLintTarget(),
3210
+ ...cgo ? { "build-native": goNativeBuildTarget(name), "package-native": goNativePackageTarget(name) } : {
3211
+ "package": goPackageTarget("go-app", name),
3212
+ "build-all": goBuildAllTarget(name),
3213
+ "package-all": goPackageAllTarget("go-app", name)
3214
+ },
3215
+ start: goStartTarget(name)
3002
3216
  });
3003
3217
  if (options.release === true) {
3004
3218
  makeGoAppReleasable(workspaceRoot, name);
3005
3219
  }
3006
3220
  registerProjectCommands(workspaceRoot, name, { build: true, start: `nx run ${name}:start` });
3221
+ if (cgo) {
3222
+ logger.warn(
3223
+ `${name} needs a C toolchain, so CI builds it on a runner of each OS. Run \`mnci upgrade\` to add the native job to your pipeline, and add the -dev packages it links to the Linux prerequisites step.`
3224
+ );
3225
+ }
3007
3226
  }
3008
3227
  function addGoFunctionApp(workspaceRoot, name) {
3009
3228
  prepareGo(workspaceRoot);
@@ -5454,8 +5673,10 @@ async function runAdd(kind, name, options) {
5454
5673
  message: "What kind of project?",
5455
5674
  choices: PROJECT_KINDS.map((value) => ({ name: value, value }))
5456
5675
  });
5457
- if (resolvedKind !== "go-app" && options.release === true) {
5458
- throw new Error(`--release applies to go-app only, not ${resolvedKind}.`);
5676
+ for (const [flag, requested] of [["--release", options.release], ["--cgo", options.cgo]]) {
5677
+ if (resolvedKind !== "go-app" && requested === true) {
5678
+ throw new Error(`${flag} applies to go-app only, not ${resolvedKind}.`);
5679
+ }
5459
5680
  }
5460
5681
  const resolvedName = name ?? await promptText("Project name");
5461
5682
  assertValidProjectName(resolvedName, "Project name");
@@ -5522,7 +5743,7 @@ async function runAdd(kind, name, options) {
5522
5743
  break;
5523
5744
  }
5524
5745
  case "go-app": {
5525
- addGoApp(workspaceRoot, resolvedName, { release: options.release });
5746
+ addGoApp(workspaceRoot, resolvedName, { release: options.release, cgo: options.cgo });
5526
5747
  break;
5527
5748
  }
5528
5749
  case "go-function-app": {
@@ -5601,6 +5822,7 @@ function readWorkspaceStack(workspaceRoot) {
5601
5822
  }
5602
5823
 
5603
5824
  // src/workspace-diagnostics/check-invariants.use-case.ts
5825
+ var import_node_child_process = require("child_process");
5604
5826
  var import_node_fs14 = require("fs");
5605
5827
  var import_node_path21 = require("path");
5606
5828
  var SUPPORTED_ESLINT_MAJOR = ESLINT_VERSION.replace(/^\D*/, "").split(".", 1)[0];
@@ -5885,6 +6107,32 @@ function checkSync(workspaceRoot) {
5885
6107
  remedy: "run `npx nx sync` and commit the result"
5886
6108
  };
5887
6109
  }
6110
+ function hasCCompiler() {
6111
+ const candidates = [process.env.CC?.split(" ", 1)[0], "gcc", "clang", "cc"];
6112
+ return candidates.some((command) => command !== void 0 && command !== "" && (0, import_node_child_process.spawnSync)(command, ["--version"], { stdio: "ignore" }).status === 0);
6113
+ }
6114
+ function checkNativeApps(workspaceRoot) {
6115
+ if (!hasNativeGoApp(workspaceRoot)) {
6116
+ return [];
6117
+ }
6118
+ const pipelines = [
6119
+ { file: ".github/workflows/ci.yml", marker: "native (${{ matrix.os }})" },
6120
+ { file: "azure-pipelines.yml", marker: "- job: native" }
6121
+ ];
6122
+ const findings = pipelines.filter(({ file }) => fileExists((0, import_node_path21.join)(workspaceRoot, file))).map(({ file, marker }) => ({
6123
+ check: `${file} builds the native apps on every OS`,
6124
+ ok: (0, import_node_fs14.readFileSync)((0, import_node_path21.join)(workspaceRoot, file), "utf8").includes(marker),
6125
+ detail: `${file} has no native job: CI would verify a cgo app on the one agent that cannot build it`,
6126
+ remedy: "run `mnci upgrade`"
6127
+ }));
6128
+ findings.push({
6129
+ check: "a C compiler is installed (cgo needs one)",
6130
+ ok: hasCCompiler(),
6131
+ detail: "none of CC, gcc, clang or cc answered --version, so nx run <app>:build-native cannot build here",
6132
+ remedy: "install one: Xcode command line tools on macOS, build-essential on Linux, MinGW-w64 (gcc) on Windows"
6133
+ });
6134
+ return findings;
6135
+ }
5888
6136
  function checkNoRootRuntimeDependencies(workspaceRoot) {
5889
6137
  const manifestPath = (0, import_node_path21.join)(workspaceRoot, "package.json");
5890
6138
  if (!fileExists(manifestPath)) {
@@ -5986,7 +6234,8 @@ function collectFindings(workspaceRoot) {
5986
6234
  ...checkDeclarationSpecifiers(workspaceRoot),
5987
6235
  ...checkVersionActions(workspaceRoot),
5988
6236
  ...checkTargetFilesExist(workspaceRoot),
5989
- checkSync(workspaceRoot)
6237
+ checkSync(workspaceRoot),
6238
+ ...checkNativeApps(workspaceRoot)
5990
6239
  ].filter((finding) => finding !== void 0);
5991
6240
  }
5992
6241
  function runDoctor(workspaceRoot) {
@@ -6521,6 +6770,9 @@ function buildProgram(cliVersion) {
6521
6770
  ).option(
6522
6771
  "--release",
6523
6772
  "go-app only: release the app. Versioned from its git tag, its six-platform zips attached to its GitHub Release"
6773
+ ).option(
6774
+ "--cgo",
6775
+ "go-app only: the app needs a C toolchain (a tray icon, a native GUI, a cgo driver), so CI builds it on a runner of each OS instead of cross-compiling"
6524
6776
  ).action(
6525
6777
  async (kind, name, options) => {
6526
6778
  await runAdd(kind, name, options);