@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 +48 -2
- package/dist/cli.js +286 -34
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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
|
-
-
|
|
1476
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 + ':
|
|
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 + ':
|
|
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
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
"
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
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
|
-
|
|
5458
|
-
|
|
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);
|