@mnci/cli 4.35.0 → 4.36.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 +1 -1
- package/dist/cli.js +126 -385
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -717,9 +717,9 @@ function devcontainerJson(workspaceName) {
|
|
|
717
717
|
// precisely the drift this file exists to remove.
|
|
718
718
|
`npm install -g npm@${NPM_VERSION}`,
|
|
719
719
|
"npm ci",
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
720
|
+
// The same command the pipeline runs: the Python, Go and Flutter toolchains, each
|
|
721
|
+
// skipped when the workspace has no project in that language.
|
|
722
|
+
"npx mnci ci setup"
|
|
723
723
|
].join(" && "),
|
|
724
724
|
// The same recommendations the `.code-workspace` file carries, so opening
|
|
725
725
|
// the folder in a container suggests the identical toolset.
|
|
@@ -1158,13 +1158,7 @@ function nugetConfigContent(registry, scope) {
|
|
|
1158
1158
|
}
|
|
1159
1159
|
var PYTHON_INSTALL_GUARD = `node -e "if(!require('node:fs').existsSync('requirements-dev.txt')){console.log('No Python projects - skipping.');process.exit(0)}const py=process.platform==='win32'?'python':'python3';process.exit(require('node:child_process').spawnSync(py+' -m pip install -r requirements-dev.txt',{stdio:'inherit',shell:true}).status ?? 1)"`;
|
|
1160
1160
|
var PYTHON_WORKSPACE_INSTALL_GUARD = `node -e "const fs=require('node:fs'),path=require('node:path');const editableDirs=[...fs.globSync('apps/*/pyproject.toml'),...fs.globSync('python-packages/*/pyproject.toml'),...fs.globSync('libs/*/pyproject.toml')].map((p)=>path.dirname(p));const requirementsFiles=fs.globSync('apps/*/requirements.txt');if(editableDirs.length===0&&requirementsFiles.length===0){console.log('No Python projects - skipping.');process.exit(0)}const args=['-m','pip','install','--quiet',...editableDirs.flatMap((d)=>['-e',d]),...requirementsFiles.flatMap((f)=>['-r',f])];const py=process.platform==='win32'?'python':'python3';process.exit(require('node:child_process').spawnSync(py,args,{stdio:'inherit'}).status ?? 1)"`;
|
|
1161
|
-
var PIP_AUDIT_GUARD = `node -e "if(!require('node:fs').existsSync('requirements-dev.txt')){console.log('No Python projects - skipping.');process.exit(0)}const py=process.platform==='win32'?'python':'python3';require('node:child_process').spawnSync(py,['-m','pip_audit'],{stdio:'inherit'});process.exit(0)"`;
|
|
1162
|
-
var GO_MODULE_DOWNLOAD_GUARD = `node -e "if(!require('node:fs').existsSync('go.mod')){console.log('No Go projects - skipping.');process.exit(0)}process.exit(require('node:child_process').spawnSync('go',['mod','download'],{stdio:'inherit'}).status ?? 1)"`;
|
|
1163
1161
|
var GOLANGCI_LINT_VERSION = "2.14.0";
|
|
1164
|
-
var GOLANGCI_LINT_INSTALL_GUARD = `node -e "const fs=require('node:fs'),cp=require('node:child_process'),path=require('node:path'),os=require('node:os'),crypto=require('node:crypto');if(!fs.existsSync('go.mod')){console.log('No Go projects - skipping.');process.exit(0)}if(cp.spawnSync('golangci-lint',['--version'],{stdio:'ignore'}).status===0){console.log('golangci-lint already installed - skipping.');process.exit(0)}const v='${GOLANGCI_LINT_VERSION}';const fallback=reason=>{console.log('Prebuilt golangci-lint '+v+' unavailable ('+reason+') - building it with go install instead.');process.exit(cp.spawnSync('go',['install','github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v'+v],{stdio:'inherit'}).status ?? 1)};const plat={linux:'linux',darwin:'darwin',win32:'windows'}[process.platform],arch={x64:'amd64',arm64:'arm64'}[process.arch];if(!plat||!arch)fallback('no prebuilt release for '+process.platform+'/'+process.arch);const gopath=cp.spawnSync('go',['env','GOPATH'],{encoding:'utf8'});if(gopath.status!==0)fallback('go env GOPATH failed');const name='golangci-lint-'+v+'-'+plat+'-'+arch,file=name+(plat==='windows'?'.zip':'.tar.gz'),base='https://github.com/golangci/golangci-lint/releases/download/v'+v+'/';const get=async u=>{const r=await fetch(u,{signal:AbortSignal.timeout(60000)});if(!r.ok)throw new Error('HTTP '+r.status+' for '+u);return Buffer.from(await r.arrayBuffer())};(async()=>{` + String.raw`const sums=(await get(base+'golangci-lint-'+v+'-checksums.txt')).toString('utf8').split('\n').map(l=>l.trim().split(' ').filter(Boolean));` + `const entry=sums.find(p=>p[1]===file);if(!entry)throw new Error(file+' is not in the release checksums');const archive=await get(base+file);if(crypto.createHash('sha256').update(archive).digest('hex')!==entry[0])throw new Error('checksum mismatch for '+file);const tmp=fs.mkdtempSync(path.join(os.tmpdir(),'golangci-lint-')),archivePath=path.join(tmp,file);fs.writeFileSync(archivePath,archive);const tar=plat==='windows'?path.join(process.env.SystemRoot||'','System32','tar.exe'):'tar';const x=cp.spawnSync(tar,['-xf',archivePath,'-C',tmp],{stdio:'inherit'});if(x.status!==0)throw new Error('could not extract '+file);const exe='golangci-lint'+(plat==='windows'?'.exe':''),bin=path.join(gopath.stdout.trim(),'bin');fs.mkdirSync(bin,{recursive:true});fs.copyFileSync(path.join(tmp,name,exe),path.join(bin,exe));fs.chmodSync(path.join(bin,exe),0o755);fs.rmSync(tmp,{recursive:true,force:true});console.log('golangci-lint '+v+' installed from its checksum-verified prebuilt release into '+bin)})().catch(error=>fallback(error.message))"`;
|
|
1165
|
-
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');";
|
|
1166
|
-
var GO_TOOL_PATH_AZURE = `node -e "${GO_TOOL_PATH_PRELUDE}console.log('##vso[task.prependpath]'+bin)"`;
|
|
1167
|
-
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'`})"`;
|
|
1168
1162
|
var FLUTTER_SDK_VERSION = "3.44.8";
|
|
1169
1163
|
var NODE_VERSION = "24";
|
|
1170
1164
|
var ACTION_VERSIONS = {
|
|
@@ -1177,24 +1171,10 @@ var ACTION_VERSIONS = {
|
|
|
1177
1171
|
var NPM_VERSION = "11";
|
|
1178
1172
|
var DOTNET_SDK_VERSION = "10.0.x";
|
|
1179
1173
|
var DOTNET_DETECT_AZURE = `node -e "const fs=require('node:fs');const has=[...fs.globSync('apps/*/*.csproj'),...fs.globSync('packages/*/*.csproj'),...fs.globSync('libs/*/*.csproj')].length>0;console.log('##vso[task.setvariable variable=hasDotnetProjects]'+has)"`;
|
|
1180
|
-
var FLUTTER_SDK_DIRECTORY_EXPRESSION = `require('node:path').join(require('node:os').homedir(),'.mnci-flutter-${FLUTTER_SDK_VERSION}')`;
|
|
1181
|
-
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)"`;
|
|
1182
|
-
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');`;
|
|
1183
|
-
var FLUTTER_TOOL_PATH_AZURE = `node -e "${FLUTTER_TOOL_PATH_PRELUDE}console.log('##vso[task.prependpath]'+bin)"`;
|
|
1184
|
-
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'`})"`;
|
|
1185
|
-
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)"`;
|
|
1186
|
-
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)"`;
|
|
1187
1174
|
var VERIFY_TARGETS = "lint,typecheck,test,build";
|
|
1188
|
-
function affectedOrAllGuard(exclude = "") {
|
|
1189
|
-
const extra = exclude === "" ? "" : `+' ${exclude}'`;
|
|
1190
|
-
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)"`;
|
|
1191
|
-
}
|
|
1192
|
-
var AFFECTED_OR_ALL_GUARD = affectedOrAllGuard();
|
|
1193
|
-
var AFFECTED_OR_ALL_GUARD_WITHOUT_NATIVE = affectedOrAllGuard(`--exclude=tag:${GO_CGO_TAG}`);
|
|
1194
1175
|
var NATIVE_TARGETS = "lint,test,build-native,package-native";
|
|
1195
|
-
var NATIVE_BUILD_COMMAND = `npx nx run-many -t ${NATIVE_TARGETS} --projects=tag:${GO_CGO_TAG}`;
|
|
1196
1176
|
var NATIVE_LINUX_PREREQUISITES = "sudo apt-get update && sudo apt-get install -y gcc pkg-config";
|
|
1197
|
-
function githubNativeJob(npmAuthName, npmAuthValue
|
|
1177
|
+
function githubNativeJob(npmAuthName, npmAuthValue) {
|
|
1198
1178
|
return `
|
|
1199
1179
|
# Apps that need a C toolchain (mnci add go-app --cgo) cannot be cross-compiled
|
|
1200
1180
|
# from the job above, so each is linted, tested, built and packaged here, on a
|
|
@@ -1229,14 +1209,10 @@ function githubNativeJob(npmAuthName, npmAuthValue, onMain) {
|
|
|
1229
1209
|
env:
|
|
1230
1210
|
${npmAuthName}: ${npmAuthValue}
|
|
1231
1211
|
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
name: Install golangci-lint
|
|
1237
|
-
|
|
1238
|
-
- run: ${GO_TOOL_PATH_GITHUB}
|
|
1239
|
-
name: Add Go tool bin to PATH
|
|
1212
|
+
# The Go toolchain: the modules, and a checksum-verified golangci-lint put
|
|
1213
|
+
# on PATH. The other toolchains are skipped cleanly when absent.
|
|
1214
|
+
- run: npx mnci ci setup
|
|
1215
|
+
name: Set up the Go toolchain
|
|
1240
1216
|
|
|
1241
1217
|
# A C compiler and pkg-config, which is all mnci can know a cgo app needs.
|
|
1242
1218
|
# Add the -dev packages your app links to this line, for example
|
|
@@ -1246,22 +1222,20 @@ function githubNativeJob(npmAuthName, npmAuthValue, onMain) {
|
|
|
1246
1222
|
name: Install native prerequisites (Linux)
|
|
1247
1223
|
if: \${{ runner.os == 'Linux' }}
|
|
1248
1224
|
|
|
1249
|
-
|
|
1225
|
+
# Lints, tests, builds and packages the native apps on this OS. On a push to
|
|
1226
|
+
# main, a releasable native app (--cgo with --release) also gets this OS's zip
|
|
1227
|
+
# attached to the GitHub Release the ci job just created, stamped with the
|
|
1228
|
+
# tag's version.
|
|
1229
|
+
- run: npx mnci ci native
|
|
1250
1230
|
name: Lint, test, build and package the native apps
|
|
1231
|
+
env:
|
|
1232
|
+
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
1251
1233
|
|
|
1252
1234
|
- uses: actions/upload-artifact@${ACTION_VERSIONS["actions/upload-artifact"]}
|
|
1253
1235
|
with:
|
|
1254
1236
|
name: native-\${{ matrix.os }}
|
|
1255
1237
|
path: dist/drop
|
|
1256
1238
|
if-no-files-found: ignore
|
|
1257
|
-
|
|
1258
|
-
# A releasable native app (--cgo with --release) gets this OS's zip attached to
|
|
1259
|
-
# the GitHub Release the ci job just created, stamped with the tag's version.
|
|
1260
|
-
- run: node tools/go-app-release.cjs assets --native
|
|
1261
|
-
name: Attach this OS's zip to the GitHub Release (releasable native apps)
|
|
1262
|
-
if: \${{ ${onMain} && hashFiles('tools/go-app-release.cjs') != '' }}
|
|
1263
|
-
env:
|
|
1264
|
-
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
1265
1239
|
`;
|
|
1266
1240
|
}
|
|
1267
1241
|
function indentYaml(text, spaces) {
|
|
@@ -1306,40 +1280,12 @@ ${matrix}
|
|
|
1306
1280
|
${indentYaml(nativeSteps, 4)}
|
|
1307
1281
|
`;
|
|
1308
1282
|
}
|
|
1309
|
-
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)"`;
|
|
1310
|
-
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())"`;
|
|
1311
|
-
function npmAuthPreflight(registryKind) {
|
|
1312
|
-
return registryKind === "npm" ? NPM_AUTH_PREFLIGHT : "";
|
|
1313
|
-
}
|
|
1314
|
-
var PYPI_RELEASE_PREFLIGHT = String.raw`node -e "const fs=require('node:fs');const files=fs.globSync('python-packages/*/pyproject.toml');if(files.length===0){console.log('No Python packages to release - skipping.');process.exit(0)}if(!process.env.PYPI_TOKEN){console.error('PYPI_TOKEN is empty or unset, so the publish would fail AFTER nx release has already tagged. Add it as a repository secret named exactly PYPI_TOKEN - under Actions, not the Dependabot or Codespaces tab, and not an Environment secret.');process.exit(1)}if(!process.env.PYPI_TOKEN.startsWith('pypi-')){console.error('PYPI_TOKEN does not look like a PyPI API token - every one of them begins with pypi-. A password or a truncated paste is rejected only at upload time, which is AFTER nx release has tagged.');process.exit(1)}const names=files.map(f=>{const t=fs.readFileSync(f,'utf8');const b=t.slice(t.indexOf('[project]'));const l=b.split('\n').find(x=>x.trim().startsWith('name'));return l?l.split('=')[1].replace(/[^a-zA-Z0-9._-]/g,''):''}).filter(Boolean);(async()=>{const fresh=[];for(const n of names){try{const r=await fetch('https://pypi.org/pypi/'+n.toLowerCase().replace(/[-_.]+/g,'-')+'/json',{signal:AbortSignal.timeout(10000)});if(r.status===404)fresh.push(n)}catch{console.log('Could not reach PyPI to check '+n+' - continuing.')}}if(fresh.length===0){console.log('Every Python package already exists on PyPI - this release creates none.');return}console.log('NOTE - this release may CREATE '+fresh.length+' new PyPI project(s) - '+fresh.join(', ')+'. Project creation is rate limited per account, and a 429 there arrives AFTER nx release has tagged - leaving those versions tagged with nothing published, and skipped forever, because the next run resolves the current version from the newest tag. If that happens, delete the tags for the versions that did not publish before releasing again. PyPI offers no way to check the limit, or the token, in advance.')})()"`;
|
|
1315
|
-
function pypiReleasePreflight(registryKind) {
|
|
1316
|
-
return registryKind === "npm" ? PYPI_RELEASE_PREFLIGHT : "";
|
|
1317
|
-
}
|
|
1318
|
-
var SHALLOW_CLONE_GUARD = `node -e "const r=require('node:child_process').spawnSync('git',['rev-parse','--is-shallow-repository'],{encoding:'utf8'});if(r.status!==0){console.error('Could not determine whether this checkout has full history (git rev-parse --is-shallow-repository failed) - refusing to release. '+(r.stderr||'').trim());process.exit(1)}if(r.stdout.trim()==='true'){console.error('This checkout is a shallow clone. nx release resolves the current version of each package from its git tag, and silently falls back to the permanently-stale on-disk version when a tag cannot be found - which can propose, and publish, a version DOWNGRADE. Fetch full history before releasing - set fetchDepth (Azure) or fetch-depth (GitHub) to 0.');process.exit(1)}"`;
|
|
1319
|
-
function releaseGuard(pythonPublishEnv, nugetPublishEnv) {
|
|
1320
|
-
return String.raw`node -e "const fs=require('node:fs'),cp=require('node:child_process');const npmCount=fs.globSync('packages/*/package.json').length;const csharpCount=fs.globSync('packages/*/*.csproj').length;const pythonCount=fs.globSync('python-packages/*/pyproject.toml').length;const dartCount=fs.globSync('packages/*/pubspec.yaml').length;const vscodeCount=fs.globSync('apps/*/package.json').filter(p=>{try{return(JSON.parse(fs.readFileSync(p,'utf8')).nx?.tags||[]).includes('${VSCODE_EXTENSION_TAG}')}catch{return false}}).length;const goCount=fs.globSync('apps/*/project.json').filter(p=>{try{return(JSON.parse(fs.readFileSync(p,'utf8')).tags||[]).includes('${GO_RELEASE_TAG}')}catch{return false}}).length;const hasNpm=npmCount>0;const hasPython=pythonCount>0;const hasCsharp=csharpCount>0;const hasDart=dartCount>0;const hasVscode=vscodeCount>0;const hasGo=goCount>0;if(!hasNpm&&!hasPython&&!hasCsharp&&!hasDart&&!hasVscode&&!hasGo){console.log('Nothing to release - skipping.');process.exit(0)}const specifier=process.env.RELEASE_SPECIFIER||'';let releaseCmd='npx nx release --yes';if(specifier){if(!/^(major|minor|patch|\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?)$/.test(specifier)){console.error('RELEASE_SPECIFIER value \''+specifier+'\' is invalid - use major, minor, patch, or an exact version like 1.2.3.');process.exit(1)}const releaseProjectCount=npmCount+csharpCount+pythonCount+dartCount+vscodeCount+goCount;if(/^(major|minor|patch)$/.test(specifier)&&releaseProjectCount>1){console.error('RELEASE_SPECIFIER is a keyword (\''+specifier+'\') but this workspace has '+releaseProjectCount+' releasable packages - a keyword under-bumps interdependent packages, because nx computes the dependency-bump pass from a stale cached version. Set RELEASE_SPECIFIER to an exact version instead, or clear it.');process.exit(1)}releaseCmd='npx nx release '+specifier+' --yes'}const env={...process.env};${pythonPublishEnv}${nugetPublishEnv}process.exit(cp.spawnSync(releaseCmd,{stdio:'inherit',shell:true,env}).status ?? 1)"`;
|
|
1321
|
-
}
|
|
1322
1283
|
var PYPI_TOKEN_VARIABLE = "PYPI_TOKEN";
|
|
1323
1284
|
var AZURE_PAT_VARIABLE = "PAT";
|
|
1324
|
-
function azurePatPreflight(countExpression, subject) {
|
|
1325
|
-
return `if(!process.env.${AZURE_PAT_VARIABLE}){console.error('This workspace has '+${countExpression}+' ${subject} package(s) to publish to Azure Artifacts but ${AZURE_PAT_VARIABLE} is empty. Add your base64-encoded Azure DevOps PAT as a secret named ${AZURE_PAT_VARIABLE}, or remove the ${subject} packages from release.projects.');process.exit(1)}`;
|
|
1326
|
-
}
|
|
1327
1285
|
function pypiTokenEnvLine(registryKind, variableReference, indent) {
|
|
1328
1286
|
return registryKind === "npm" ? `
|
|
1329
1287
|
${indent}${PYPI_TOKEN_VARIABLE}: ${variableReference(PYPI_TOKEN_VARIABLE)}` : "";
|
|
1330
1288
|
}
|
|
1331
|
-
function pythonPublishEnvFragment(pythonPublishUrl2, registryKind) {
|
|
1332
|
-
if (pythonPublishUrl2 !== void 0) {
|
|
1333
|
-
return `if(hasPython){${azurePatPreflight("pythonCount", "Python")}env.TWINE_REPOSITORY_URL='${pythonPublishUrl2}';env.TWINE_USERNAME='AzureArtifacts';env.TWINE_PASSWORD=Buffer.from(process.env.${AZURE_PAT_VARIABLE},'base64').toString()}`;
|
|
1334
|
-
}
|
|
1335
|
-
if (registryKind !== "npm") {
|
|
1336
|
-
return "";
|
|
1337
|
-
}
|
|
1338
|
-
return `if(hasPython){if(!process.env.${PYPI_TOKEN_VARIABLE}){console.error('This workspace has '+pythonCount+' Python package(s) to publish but ${PYPI_TOKEN_VARIABLE} is empty. Add a PyPI API token as a secret named ${PYPI_TOKEN_VARIABLE}, or remove the Python packages from release.projects.');process.exit(1)}env.TWINE_USERNAME='__token__';env.TWINE_PASSWORD=process.env.${PYPI_TOKEN_VARIABLE};env.TWINE_NON_INTERACTIVE='1'}`;
|
|
1339
|
-
}
|
|
1340
|
-
function nugetPublishEnvFragment(nugetFeedUrl2) {
|
|
1341
|
-
return nugetFeedUrl2 ? `if(hasCsharp){${azurePatPreflight("csharpCount", "C#")}env.NUGET_PAT=Buffer.from(process.env.${AZURE_PAT_VARIABLE},'base64').toString()}` : "";
|
|
1342
|
-
}
|
|
1343
1289
|
function npmAuthEnvVariable(registryKind, variableReference) {
|
|
1344
1290
|
return registryKind === "npm" ? ["NODE_AUTH_TOKEN", variableReference("NPM_TOKEN")] : [AZURE_PAT_VARIABLE, variableReference(AZURE_PAT_VARIABLE)];
|
|
1345
1291
|
}
|
|
@@ -1483,66 +1429,25 @@ steps:
|
|
|
1483
1429
|
${npmAuthenticateStep} - script: npm ci
|
|
1484
1430
|
displayName: Install dependencies${npmCiEnv}
|
|
1485
1431
|
|
|
1432
|
+
# Everything from here to the release is a command of the mnci CLI, a
|
|
1433
|
+
# devDependency of this workspace, so the version is the lockfile's and a
|
|
1434
|
+
# developer reproduces CI by running the same line. The guards that used to be
|
|
1435
|
+
# inline here are tested TypeScript in the CLI's ci-pipeline slice. Node, the
|
|
1436
|
+
# checkout and 'npm ci' stay above: Node must exist before 'npx' can run.
|
|
1437
|
+
|
|
1438
|
+
# The language toolchains the workspace needs: Python (ruff, pytest, build,
|
|
1439
|
+
# twine, and every project installed into one shared environment), Go (the
|
|
1440
|
+
# modules and a checksum-verified golangci-lint, put on PATH) and Flutter (the
|
|
1441
|
+
# pinned SDK, put on PATH, and one pub get). Each is skipped cleanly when the
|
|
1442
|
+
# workspace has no project in that language.
|
|
1443
|
+
- script: npx mnci ci setup
|
|
1444
|
+
displayName: Set up the language toolchains (Python, Go, Flutter)
|
|
1445
|
+
|
|
1486
1446
|
# Fails ONLY on an advisory that has a published fix, at moderate or above;
|
|
1487
|
-
# anything upstream has not fixed is printed and passes.
|
|
1488
|
-
#
|
|
1489
|
-
- script:
|
|
1490
|
-
displayName:
|
|
1491
|
-
|
|
1492
|
-
# Installs the fixed Python toolchain (ruff, pytest, build, twine, pip-audit)
|
|
1493
|
-
# \u2014 written by 'mnci add' to requirements-dev.txt on the first Python
|
|
1494
|
-
# project. Plain pip, no uv/Poetry: portable guard skips cleanly on a
|
|
1495
|
-
# workspace with none.
|
|
1496
|
-
- script: ${PYTHON_INSTALL_GUARD}
|
|
1497
|
-
displayName: Install Python dependencies (ruff, pytest, build, twine, pip-audit)
|
|
1498
|
-
|
|
1499
|
-
# Editable-installs every Python project into one shared environment, the
|
|
1500
|
-
# pip-world counterpart of 'npm install' hoisting every workspace package
|
|
1501
|
-
# into one root node_modules \u2014 so a project that imports an internal lib
|
|
1502
|
-
# (normally vendored only at build time) can resolve that import at
|
|
1503
|
-
# lint/test time too. Portable guard skips cleanly on a workspace with none.
|
|
1504
|
-
- script: ${PYTHON_WORKSPACE_INSTALL_GUARD}
|
|
1505
|
-
displayName: Install Python project dependencies (editable, workspace-wide)
|
|
1506
|
-
|
|
1507
|
-
# Non-blocking, same reasoning as the npm audit step above. Runs after the
|
|
1508
|
-
# workspace-wide install so it scans the workspace's actual dependency set,
|
|
1509
|
-
# not just the fixed toolchain.
|
|
1510
|
-
- script: ${PIP_AUDIT_GUARD}
|
|
1511
|
-
displayName: pip-audit (non-blocking)
|
|
1512
|
-
|
|
1513
|
-
# Go, if the workspace has any: hosted agents ship the toolchain, so only
|
|
1514
|
-
# the module cache and the linter need seeding. Both guards skip cleanly on
|
|
1515
|
-
# a workspace with no root go.mod.
|
|
1516
|
-
- script: ${GO_MODULE_DOWNLOAD_GUARD}
|
|
1517
|
-
displayName: Download Go module dependencies
|
|
1518
|
-
|
|
1519
|
-
# golangci-lint is what the generated Go lint target actually runs (the
|
|
1520
|
-
# plugin's own default is 'go fmt', which only reformats). The pinned,
|
|
1521
|
-
# checksum-verified prebuilt release lands in GOPATH/bin (go install at the
|
|
1522
|
-
# same version is the fallback), which is not on PATH by default on a hosted
|
|
1523
|
-
# agent \u2014 so prepend it for every later step in the job.
|
|
1524
|
-
- script: ${GOLANGCI_LINT_INSTALL_GUARD}
|
|
1525
|
-
displayName: Install golangci-lint
|
|
1526
|
-
|
|
1527
|
-
- script: ${GO_TOOL_PATH_AZURE}
|
|
1528
|
-
displayName: Add Go tool bin to PATH
|
|
1529
|
-
|
|
1530
|
-
# Flutter, if the workspace has any. Unlike Python and Go, hosted agents do
|
|
1531
|
-
# NOT ship the Flutter SDK, so this installs it (shallow git clone at a
|
|
1532
|
-
# pinned tag) and puts it on PATH. All three guards skip cleanly on a
|
|
1533
|
-
# workspace with no root pubspec.yaml, and the install also skips when an
|
|
1534
|
-
# SDK is already on PATH.
|
|
1535
|
-
- script: ${FLUTTER_SDK_INSTALL_GUARD}
|
|
1536
|
-
displayName: Install the Flutter SDK (${FLUTTER_SDK_VERSION})
|
|
1537
|
-
|
|
1538
|
-
- script: ${FLUTTER_TOOL_PATH_AZURE}
|
|
1539
|
-
displayName: Add the Flutter SDK to PATH
|
|
1540
|
-
|
|
1541
|
-
# One command resolves EVERY Dart dependency, internal and external, for the
|
|
1542
|
-
# whole workspace: the projects are pub workspace members, so this writes a
|
|
1543
|
-
# single root pubspec.lock they all share.
|
|
1544
|
-
- script: ${FLUTTER_PUB_GET_GUARD}
|
|
1545
|
-
displayName: Resolve Dart dependencies (one pub get for the whole workspace)
|
|
1447
|
+
# anything upstream has not fixed is printed and passes. pip-audit only
|
|
1448
|
+
# reports: its output has no "a fix exists" field to draw that line with.
|
|
1449
|
+
- script: npx mnci ci audit
|
|
1450
|
+
displayName: Audit dependencies (fails on an actionable npm advisory)
|
|
1546
1451
|
|
|
1547
1452
|
# .NET, if the workspace has any. Azure's 'condition:' expression language
|
|
1548
1453
|
# has no glob function, so this script step detects C# projects first and
|
|
@@ -1558,29 +1463,18 @@ ${npmAuthenticateStep} - script: npm ci
|
|
|
1558
1463
|
inputs:
|
|
1559
1464
|
version: ${DOTNET_SDK_VERSION}
|
|
1560
1465
|
|
|
1561
|
-
# Fails fast
|
|
1562
|
-
#
|
|
1563
|
-
#
|
|
1564
|
-
#
|
|
1565
|
-
#
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
# The one verify step, and deliberately the only one: affected projects on a
|
|
1570
|
-
# pull request, EVERY project on anything else (a push to main included, so a
|
|
1571
|
-
# release is always verified in full). Every fallback \u2014 no target branch, an
|
|
1572
|
-
# unresolvable merge-base \u2014 takes the full path, because a run that verifies
|
|
1573
|
-
# too little still reports green.
|
|
1574
|
-
#
|
|
1575
|
-
# 'npm run lint' is 'nx run-many -t lint', a strict subset of the targets
|
|
1576
|
-
# below, so adding it back as its own step would only duplicate work \u2014 and on
|
|
1577
|
-
# a pull request it would re-lint every project, discarding the point of this.
|
|
1578
|
-
- script: ${nativeApps ? AFFECTED_OR_ALL_GUARD_WITHOUT_NATIVE : AFFECTED_OR_ALL_GUARD}
|
|
1579
|
-
displayName: Verify (affected on a PR, every project on main)
|
|
1466
|
+
# Fails fast on a stale TypeScript project reference, then the one verify
|
|
1467
|
+
# step, and deliberately the only one: affected projects on a pull request,
|
|
1468
|
+
# EVERY project on anything else (a push to main included, so a release is
|
|
1469
|
+
# always verified in full). Every fallback takes the full path, because a run
|
|
1470
|
+
# that verifies too little still reports green. Apps that need a C toolchain
|
|
1471
|
+
# are left to the native job.
|
|
1472
|
+
- script: npx mnci ci verify
|
|
1473
|
+
displayName: Verify (sync check, then affected on a PR, every project on main)
|
|
1580
1474
|
|
|
1581
1475
|
# Pack every app into dist/drop/<type>-<name>.zip via each app's 'package'
|
|
1582
|
-
# target
|
|
1583
|
-
- script:
|
|
1476
|
+
# target; skipped cleanly when the workspace has no apps yet.
|
|
1477
|
+
- script: npx mnci ci pack
|
|
1584
1478
|
displayName: Pack all apps (one zip per app -> dist/drop)
|
|
1585
1479
|
condition: ${onMain}
|
|
1586
1480
|
|
|
@@ -1598,56 +1492,23 @@ ${npmAuthenticateStep} - script: npm ci
|
|
|
1598
1492
|
displayName: Tag the run per app (type-name)
|
|
1599
1493
|
condition: ${onMain}
|
|
1600
1494
|
|
|
1601
|
-
#
|
|
1602
|
-
#
|
|
1603
|
-
#
|
|
1604
|
-
#
|
|
1605
|
-
#
|
|
1606
|
-
#
|
|
1607
|
-
#
|
|
1608
|
-
|
|
1609
|
-
displayName: Verify this is a full checkout (nx release needs the real tag history)
|
|
1610
|
-
condition: ${onMain}
|
|
1611
|
-
${npmAuthPreflight(registryKind) ? `
|
|
1612
|
-
# Prove npm will accept us BEFORE anything irreversible happens. nx release
|
|
1613
|
-
# tags first and publishes last, so a rejected token leaves a version tagged
|
|
1614
|
-
# with nothing on the registry - and that number is then skipped forever,
|
|
1615
|
-
# because the next run resolves the current version from the newest tag.
|
|
1616
|
-
- script: ${npmAuthPreflight(registryKind)}
|
|
1617
|
-
displayName: Preflight \u2014 npm must accept the token before anything is tagged
|
|
1618
|
-
condition: ${onMain}
|
|
1619
|
-
env:
|
|
1620
|
-
NODE_AUTH_TOKEN: $(NPM_TOKEN)
|
|
1621
|
-
` : ""}${pypiReleasePreflight(registryKind) ? `
|
|
1622
|
-
# PyPI cannot be asked whether a token is good - the only endpoint that
|
|
1623
|
-
# authenticates is the upload itself - so this checks the token's SHAPE and
|
|
1624
|
-
# names the projects this release would have to CREATE. Project creation is
|
|
1625
|
-
# rate limited per account, and a 429 arrives AFTER nx release has tagged.
|
|
1626
|
-
- script: ${pypiReleasePreflight(registryKind)}
|
|
1627
|
-
displayName: Preflight \u2014 name the PyPI projects this release would create
|
|
1628
|
-
condition: ${onMain}
|
|
1629
|
-
env:
|
|
1630
|
-
PYPI_TOKEN: $(PYPI_TOKEN)
|
|
1631
|
-
` : ""}
|
|
1632
|
-
# Version + tag + publish, in one release, for npm (packages/*), Python
|
|
1633
|
-
# (python-packages/*) AND C# (packages/*/*.csproj) \u2014 conventional commits,
|
|
1634
|
-
# tag-only push. Portable guard: nx release errors on an empty scope, so
|
|
1635
|
-
# skip cleanly when there is nothing to release. When there are Python or
|
|
1636
|
-
# C# packages and an Azure feed, twine/NuGet publish credentials are
|
|
1637
|
-
# exported (raw PAT, decoded from the base64 variable).
|
|
1495
|
+
# The release, in the order that keeps a failure safe: refuse a shallow
|
|
1496
|
+
# clone, prove the npm token and name the PyPI projects it would create (all
|
|
1497
|
+
# BEFORE anything is tagged), then version + tag + publish in one 'nx release'
|
|
1498
|
+
# for npm, Python, C# and VS Code, then push the tags explicitly (nx release's
|
|
1499
|
+
# own push never runs without a remote GitHub/GitLab Release configured, which
|
|
1500
|
+
# this pipeline never does). Which registry is in use is read from the 'mnci'
|
|
1501
|
+
# block of nx.json, not baked into this file. The secrets are mapped here, in
|
|
1502
|
+
# the one place a command cannot reach.
|
|
1638
1503
|
#
|
|
1639
|
-
# RELEASE_SPECIFIER (the pipeline variable above) overrides the bump nx
|
|
1640
|
-
#
|
|
1641
|
-
# more than one releasable package
|
|
1642
|
-
# is unreliable
|
|
1643
|
-
#
|
|
1644
|
-
#
|
|
1645
|
-
#
|
|
1646
|
-
|
|
1647
|
-
# it whenever more than one package is releasable. The guard below fails
|
|
1648
|
-
# the run rather than under-bumping silently: clear the variable back to
|
|
1649
|
-
# '' once the override is no longer needed.
|
|
1650
|
-
- script: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl2, registryKind), nugetPublishEnvFragment(nugetFeedUrl2))}
|
|
1504
|
+
# RELEASE_SPECIFIER (the pipeline variable above) overrides the bump nx would
|
|
1505
|
+
# compute from conventional commits for THIS run: 'major', 'minor', 'patch', or
|
|
1506
|
+
# an exact version ('1.2.3'). With more than one releasable package a bare
|
|
1507
|
+
# keyword is unreliable (nx versions interdependent packages in two passes, and
|
|
1508
|
+
# the second computes from a version cached before the first), so the command
|
|
1509
|
+
# fails the run rather than under-bumping silently: prefer an exact version,
|
|
1510
|
+
# and clear the variable back to '' once the override is no longer needed.
|
|
1511
|
+
- script: npx mnci ci release
|
|
1651
1512
|
displayName: Release \u2014 version, tag and publish (npm + Python + C# + VS Code)
|
|
1652
1513
|
condition: ${onMain}
|
|
1653
1514
|
env:
|
|
@@ -1656,15 +1517,6 @@ ${npmAuthPreflight(registryKind) ? `
|
|
|
1656
1517
|
# A VS Code extension publishes with it; tools/vscode-extension.cjs skips the
|
|
1657
1518
|
# Marketplace when it is unset, including when Azure leaves it as '$(VSCE_PAT)'.
|
|
1658
1519
|
VSCE_PAT: $(VSCE_PAT)
|
|
1659
|
-
|
|
1660
|
-
# nx release's own git push (release.git.push) is deliberately left off: it
|
|
1661
|
-
# only runs when a remote GitHub/GitLab Release is configured, which this
|
|
1662
|
-
# pipeline never does, so it would never push the tag the step above just
|
|
1663
|
-
# created. Pushed explicitly, unconditionally (a no-op when nothing released)
|
|
1664
|
-
# once tagging is guaranteed to have already happened.
|
|
1665
|
-
- script: git push origin --tags
|
|
1666
|
-
displayName: Push release tags (nx release's own push never runs without a remote Release configured)
|
|
1667
|
-
condition: ${onMain}
|
|
1668
1520
|
`;
|
|
1669
1521
|
if (!nativeApps) {
|
|
1670
1522
|
return document;
|
|
@@ -1687,14 +1539,10 @@ ${npmAuthPreflight(registryKind) ? `
|
|
|
1687
1539
|
${npmAuthenticateStep} - script: npm ci
|
|
1688
1540
|
displayName: Install dependencies${npmCiEnv}
|
|
1689
1541
|
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
displayName: Install golangci-lint
|
|
1695
|
-
|
|
1696
|
-
- script: ${GO_TOOL_PATH_AZURE}
|
|
1697
|
-
displayName: Add Go tool bin to PATH
|
|
1542
|
+
# The Go toolchain: the modules, and a checksum-verified golangci-lint put on
|
|
1543
|
+
# PATH. The other toolchains are skipped cleanly when absent.
|
|
1544
|
+
- script: npx mnci ci setup
|
|
1545
|
+
displayName: Set up the Go toolchain
|
|
1698
1546
|
|
|
1699
1547
|
# A C compiler and pkg-config, which is all mnci can know a cgo app needs. Add the
|
|
1700
1548
|
# -dev packages your app links to this line, for example libgtk-3-dev and
|
|
@@ -1704,7 +1552,7 @@ ${npmAuthenticateStep} - script: npm ci
|
|
|
1704
1552
|
displayName: Install native prerequisites (Linux)
|
|
1705
1553
|
condition: eq(variables['Agent.OS'], 'Linux')
|
|
1706
1554
|
|
|
1707
|
-
- script:
|
|
1555
|
+
- script: npx mnci ci native
|
|
1708
1556
|
displayName: Lint, test, build and package the native apps
|
|
1709
1557
|
|
|
1710
1558
|
- task: PublishBuildArtifacts@1
|
|
@@ -1795,66 +1643,25 @@ jobs:
|
|
|
1795
1643
|
env:
|
|
1796
1644
|
${npmAuthName}: ${npmAuthValue}
|
|
1797
1645
|
|
|
1798
|
-
#
|
|
1799
|
-
#
|
|
1800
|
-
#
|
|
1801
|
-
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
#
|
|
1805
|
-
#
|
|
1806
|
-
#
|
|
1807
|
-
#
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
#
|
|
1813
|
-
#
|
|
1814
|
-
#
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
name: Install Python project dependencies (editable, workspace-wide)
|
|
1818
|
-
|
|
1819
|
-
# Non-blocking, same reasoning as the npm audit step above. Runs after
|
|
1820
|
-
# the workspace-wide install so it scans the workspace's actual
|
|
1821
|
-
# dependency set, not just the fixed toolchain.
|
|
1822
|
-
- run: ${PIP_AUDIT_GUARD}
|
|
1823
|
-
name: pip-audit (non-blocking)
|
|
1824
|
-
|
|
1825
|
-
# Go, if the workspace has any: hosted runners ship the toolchain, so
|
|
1826
|
-
# only the module cache and the linter need seeding. Both guards skip
|
|
1827
|
-
# cleanly on a workspace with no root go.mod.
|
|
1828
|
-
- run: ${GO_MODULE_DOWNLOAD_GUARD}
|
|
1829
|
-
name: Download Go module dependencies
|
|
1830
|
-
|
|
1831
|
-
# golangci-lint is what the generated Go lint target actually runs (the
|
|
1832
|
-
# plugin's own default is 'go fmt', which only reformats). The pinned,
|
|
1833
|
-
# checksum-verified prebuilt release lands in GOPATH/bin (go install at the
|
|
1834
|
-
# same version is the fallback), which is not on PATH by default on a
|
|
1835
|
-
# hosted runner \u2014 so publish it for every later step in the job.
|
|
1836
|
-
- run: ${GOLANGCI_LINT_INSTALL_GUARD}
|
|
1837
|
-
name: Install golangci-lint
|
|
1838
|
-
|
|
1839
|
-
- run: ${GO_TOOL_PATH_GITHUB}
|
|
1840
|
-
name: Add Go tool bin to PATH
|
|
1841
|
-
|
|
1842
|
-
# Flutter, if the workspace has any. Unlike Python and Go, hosted runners
|
|
1843
|
-
# do NOT ship the Flutter SDK, so this installs it (shallow git clone at
|
|
1844
|
-
# a pinned tag) and puts it on PATH. All three guards skip cleanly on a
|
|
1845
|
-
# workspace with no root pubspec.yaml, and the install also skips when an
|
|
1846
|
-
# SDK is already on PATH.
|
|
1847
|
-
- run: ${FLUTTER_SDK_INSTALL_GUARD}
|
|
1848
|
-
name: Install the Flutter SDK (${FLUTTER_SDK_VERSION})
|
|
1849
|
-
|
|
1850
|
-
- run: ${FLUTTER_TOOL_PATH_GITHUB}
|
|
1851
|
-
name: Add the Flutter SDK to PATH
|
|
1852
|
-
|
|
1853
|
-
# One command resolves EVERY Dart dependency, internal and external, for
|
|
1854
|
-
# the whole workspace: the projects are pub workspace members, so this
|
|
1855
|
-
# writes a single root pubspec.lock they all share.
|
|
1856
|
-
- run: ${FLUTTER_PUB_GET_GUARD}
|
|
1857
|
-
name: Resolve Dart dependencies (one pub get for the whole workspace)
|
|
1646
|
+
# Everything from here to the release is a command of the mnci CLI, a
|
|
1647
|
+
# devDependency of this workspace, so the version is the lockfile's and a
|
|
1648
|
+
# developer reproduces CI by running the same line. The guards that used to be
|
|
1649
|
+
# inline here are tested TypeScript in the CLI's ci-pipeline slice. Node,
|
|
1650
|
+
# the checkout and 'npm ci' stay above: Node must exist before 'npx' can run.
|
|
1651
|
+
|
|
1652
|
+
# The language toolchains the workspace needs: Python (ruff, pytest, build,
|
|
1653
|
+
# twine, and every project installed into one shared environment), Go (the
|
|
1654
|
+
# modules and a checksum-verified golangci-lint, put on PATH) and Flutter (the
|
|
1655
|
+
# pinned SDK, put on PATH, and one pub get). Each is skipped cleanly when the
|
|
1656
|
+
# workspace has no project in that language.
|
|
1657
|
+
- run: npx mnci ci setup
|
|
1658
|
+
name: Set up the language toolchains (Python, Go, Flutter)
|
|
1659
|
+
|
|
1660
|
+
# Fails ONLY on an advisory that has a published fix, at moderate or above;
|
|
1661
|
+
# anything upstream has not fixed is printed and passes. pip-audit only
|
|
1662
|
+
# reports: its output has no "a fix exists" field to draw that line with.
|
|
1663
|
+
- run: npx mnci ci audit
|
|
1664
|
+
name: Audit dependencies (fails on an actionable npm advisory)
|
|
1858
1665
|
|
|
1859
1666
|
# .NET, if the workspace has any. actions/setup-dotnet is the
|
|
1860
1667
|
# maintained, cached installer GitHub itself ships, so this uses it
|
|
@@ -1866,30 +1673,18 @@ jobs:
|
|
|
1866
1673
|
with:
|
|
1867
1674
|
dotnet-version: ${DOTNET_SDK_VERSION}
|
|
1868
1675
|
|
|
1869
|
-
# Fails fast
|
|
1870
|
-
#
|
|
1871
|
-
#
|
|
1872
|
-
#
|
|
1873
|
-
#
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
# The one verify step, and deliberately the only one: affected projects on
|
|
1878
|
-
# a pull request, EVERY project on anything else (a push to main included,
|
|
1879
|
-
# so a release is always verified in full). Every fallback \u2014 no target
|
|
1880
|
-
# branch, an unresolvable merge-base \u2014 takes the full path, because a run
|
|
1881
|
-
# that verifies too little still reports green.
|
|
1882
|
-
#
|
|
1883
|
-
# 'npm run lint' is 'nx run-many -t lint', a strict subset of the targets
|
|
1884
|
-
# below, so adding it back as its own step would only duplicate work \u2014 and
|
|
1885
|
-
# on a pull request it would re-lint every project, discarding the point
|
|
1886
|
-
# of this.
|
|
1887
|
-
- run: ${nativeApps ? AFFECTED_OR_ALL_GUARD_WITHOUT_NATIVE : AFFECTED_OR_ALL_GUARD}
|
|
1888
|
-
name: Verify (affected on a PR, every project on main)
|
|
1676
|
+
# Fails fast on a stale TypeScript project reference, then the one verify
|
|
1677
|
+
# step, and deliberately the only one: affected projects on a pull request,
|
|
1678
|
+
# EVERY project on anything else (a push to main included, so a release is
|
|
1679
|
+
# always verified in full). Every fallback takes the full path, because a run
|
|
1680
|
+
# that verifies too little still reports green. Apps that need a C toolchain
|
|
1681
|
+
# are left to the native job.
|
|
1682
|
+
- run: npx mnci ci verify
|
|
1683
|
+
name: Verify (sync check, then affected on a PR, every project on main)
|
|
1889
1684
|
|
|
1890
1685
|
# Pack every app into dist/drop/<type>-<name>.zip via each app's 'package'
|
|
1891
|
-
# target
|
|
1892
|
-
- run:
|
|
1686
|
+
# target; skipped cleanly when the workspace has no apps yet.
|
|
1687
|
+
- run: npx mnci ci pack
|
|
1893
1688
|
name: Pack all apps (one zip per app -> dist/drop)
|
|
1894
1689
|
if: \${{ ${onMain} }}
|
|
1895
1690
|
|
|
@@ -1900,65 +1695,6 @@ jobs:
|
|
|
1900
1695
|
path: dist/drop
|
|
1901
1696
|
if-no-files-found: ignore
|
|
1902
1697
|
|
|
1903
|
-
# nx release resolves each package's current version from its git tag,
|
|
1904
|
-
# and falls back to the (permanently stale, since git.commit is false)
|
|
1905
|
-
# on-disk version when a tag cannot be resolved \u2014 which happens
|
|
1906
|
-
# whenever this checkout lacks full history. This is currently
|
|
1907
|
-
# guaranteed by fetch-depth 0 above plus the explicit fetch below, but
|
|
1908
|
-
# neither is expressed as a dependency anywhere else, so this makes it
|
|
1909
|
-
# a hard, loud precondition instead of a silently wrong version
|
|
1910
|
-
# discovered after release.
|
|
1911
|
-
- run: ${SHALLOW_CLONE_GUARD}
|
|
1912
|
-
name: Verify this is a full checkout (nx release needs the real tag history)
|
|
1913
|
-
if: \${{ ${onMain} }}
|
|
1914
|
-
${npmAuthPreflight(registryKind) ? `
|
|
1915
|
-
# Prove npm will accept us BEFORE anything irreversible happens. nx
|
|
1916
|
-
# release tags first and publishes last, so a rejected token leaves a
|
|
1917
|
-
# version tagged with nothing on the registry - and that number is then
|
|
1918
|
-
# skipped forever, because the next run resolves the current version
|
|
1919
|
-
# from the newest tag.
|
|
1920
|
-
- run: ${npmAuthPreflight(registryKind)}
|
|
1921
|
-
name: Preflight \u2014 npm must accept the token before anything is tagged
|
|
1922
|
-
if: \${{ ${onMain} }}
|
|
1923
|
-
env:
|
|
1924
|
-
NODE_AUTH_TOKEN: \${{ secrets.NPM_TOKEN }}
|
|
1925
|
-
` : ""}${pypiReleasePreflight(registryKind) ? `
|
|
1926
|
-
# PyPI cannot be asked whether a token is good - the only endpoint that
|
|
1927
|
-
# authenticates is the upload itself - so this checks the token's SHAPE
|
|
1928
|
-
# and names the projects this release would have to CREATE. Project
|
|
1929
|
-
# creation is rate limited per account, and a 429 arrives AFTER nx
|
|
1930
|
-
# release has tagged.
|
|
1931
|
-
- run: ${pypiReleasePreflight(registryKind)}
|
|
1932
|
-
name: Preflight \u2014 name the PyPI projects this release would create
|
|
1933
|
-
if: \${{ ${onMain} }}
|
|
1934
|
-
env:
|
|
1935
|
-
PYPI_TOKEN: \${{ secrets.PYPI_TOKEN }}
|
|
1936
|
-
` : ""}
|
|
1937
|
-
# Version + tag + publish, in one release, for npm (packages/*), Python
|
|
1938
|
-
# (python-packages/*) AND C# (packages/*/*.csproj) \u2014 conventional
|
|
1939
|
-
# commits, tag-only push. Portable guard: nx release errors on an empty
|
|
1940
|
-
# scope, so skip cleanly when there is nothing to release. When there
|
|
1941
|
-
# are Python or C# packages and an Azure feed, twine/NuGet publish
|
|
1942
|
-
# credentials are exported (raw PAT, decoded from the base64 secret).
|
|
1943
|
-
#
|
|
1944
|
-
# RELEASE_SPECIFIER (repository variable, Settings -> Secrets and
|
|
1945
|
-
# variables -> Actions -> Variables) overrides the bump nx would compute
|
|
1946
|
-
# from conventional commits for THIS run: 'major', 'minor', 'patch', or
|
|
1947
|
-
# an exact version ('1.2.3'). In a workspace with more than one
|
|
1948
|
-
# releasable package, a bare keyword is unreliable \u2014 nx versions
|
|
1949
|
-
# interdependent packages in two passes (a dependency-bump pass, then
|
|
1950
|
-
# the specifier pass), and the second pass computes from a version
|
|
1951
|
-
# cached before the first pass ran, silently landing back on the same
|
|
1952
|
-
# patch bump either way. An exact version does not have this problem,
|
|
1953
|
-
# since both passes apply it verbatim \u2014 prefer it whenever more than one
|
|
1954
|
-
# package is releasable. The guard below fails the run rather than
|
|
1955
|
-
# under-bumping silently: unset or clear the variable once the override
|
|
1956
|
-
# is no longer needed.${githubReleases ? `
|
|
1957
|
-
# This provider also creates a per-project GitHub Release (changelog
|
|
1958
|
-
# generated from conventional commits since that project's last tag),
|
|
1959
|
-
# which is why 'nx release' pushes the tag itself here \u2014 see
|
|
1960
|
-
# releaseConfig's remarks for why that is safe on this Nx version and
|
|
1961
|
-
# why every other provider combination keeps the explicit push step below.` : ""}
|
|
1962
1698
|
# Signs in to Microsoft Entra ID as the Marketplace publishing identity (an
|
|
1963
1699
|
# app registration with a federated credential for this repository's main
|
|
1964
1700
|
# branch), so the release step publishes VS Code extensions with
|
|
@@ -1974,7 +1710,23 @@ ${npmAuthPreflight(registryKind) ? `
|
|
|
1974
1710
|
tenant-id: \${{ vars.AZURE_TENANT_ID }}
|
|
1975
1711
|
allow-no-subscriptions: true
|
|
1976
1712
|
|
|
1977
|
-
|
|
1713
|
+
# The release, in the order that keeps a failure safe: refuse a shallow
|
|
1714
|
+
# clone, prove the npm token and name the PyPI projects it would create (all
|
|
1715
|
+
# BEFORE anything is tagged), then version + tag + publish in one 'nx release'
|
|
1716
|
+
# for npm, Python, C# and VS Code, then attach a releasable Go app's zips and
|
|
1717
|
+
# push the tags. Which registry, and whether GitHub Releases are on, are read
|
|
1718
|
+
# from the 'mnci' block of nx.json, not baked into this file. The secrets are
|
|
1719
|
+
# mapped here, in the one place a command cannot reach.
|
|
1720
|
+
#
|
|
1721
|
+
# RELEASE_SPECIFIER (repository variable, Settings -> Secrets and
|
|
1722
|
+
# variables -> Actions -> Variables) overrides the bump nx would compute
|
|
1723
|
+
# from conventional commits for THIS run: 'major', 'minor', 'patch', or an
|
|
1724
|
+
# exact version ('1.2.3'). With more than one releasable package a bare
|
|
1725
|
+
# keyword is unreliable (nx versions interdependent packages in two passes,
|
|
1726
|
+
# and the second computes from a version cached before the first), so the
|
|
1727
|
+
# command fails the run rather than under-bumping silently: prefer an exact
|
|
1728
|
+
# version, and clear the variable once the override is no longer needed.
|
|
1729
|
+
- run: npx mnci ci release
|
|
1978
1730
|
name: Release \u2014 version, tag${githubReleases ? ", publish and GitHub Release" : " and publish"} (npm + Python + C# + VS Code)
|
|
1979
1731
|
if: \${{ ${onMain} }}
|
|
1980
1732
|
env:
|
|
@@ -1985,28 +1737,9 @@ ${npmAuthPreflight(registryKind) ? `
|
|
|
1985
1737
|
# tools/vscode-extension.cjs skips the Marketplace with neither.
|
|
1986
1738
|
VSCE_AUTH: \${{ vars.AZURE_CLIENT_ID != '' && 'entra' || '' }}
|
|
1987
1739
|
VSCE_PAT: \${{ secrets.VSCE_PAT }}${githubReleases ? `
|
|
1988
|
-
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
# tag, and its per-platform zips are attached to the GitHub Release the step
|
|
1992
|
-
# above just created. Built here, after tagging, with VERSION set to the tag's
|
|
1993
|
-
# version so the binary reports it. A run that released no Go app uploads
|
|
1994
|
-
# nothing, and a workspace without one skips the step.
|
|
1995
|
-
- run: node tools/go-app-release.cjs assets
|
|
1996
|
-
name: Attach the per-platform zips to the GitHub Release (releasable Go apps)
|
|
1997
|
-
if: \${{ ${onMain} && hashFiles('tools/go-app-release.cjs') != '' }}
|
|
1998
|
-
env:
|
|
1999
|
-
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
2000
|
-
` : `
|
|
2001
|
-
# nx release's own git push (release.git.push) is deliberately left off: it
|
|
2002
|
-
# only runs when a remote GitHub/GitLab Release is configured, which this
|
|
2003
|
-
# workflow never does, so it would never push the tag the step above just
|
|
2004
|
-
# created. Pushed explicitly, unconditionally (a no-op when nothing released)
|
|
2005
|
-
# once tagging is guaranteed to have already happened.
|
|
2006
|
-
- run: git push origin --tags
|
|
2007
|
-
name: Push release tags (nx release's own push never runs without a remote Release configured)
|
|
2008
|
-
if: \${{ ${onMain} }}
|
|
2009
|
-
`}${nativeApps ? githubNativeJob(npmAuthName, npmAuthValue, onMain) : ""}`;
|
|
1740
|
+
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
1741
|
+
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}` : ""}
|
|
1742
|
+
${nativeApps ? githubNativeJob(npmAuthName, npmAuthValue) : ""}`;
|
|
2010
1743
|
}
|
|
2011
1744
|
var PIP_PROJECT_GLOBS = ["apps", "python-packages", "libs"];
|
|
2012
1745
|
var PUB_PROJECT_GLOBS = ["apps", "packages", "libs"];
|
|
@@ -2373,7 +2106,7 @@ function runNative(workspaceRoot, dependencies = {}) {
|
|
|
2373
2106
|
if (built !== 0) {
|
|
2374
2107
|
return built;
|
|
2375
2108
|
}
|
|
2376
|
-
if (!isMainPush(environment) || !(0, import_node_fs5.existsSync)((0, import_node_path4.join)(workspaceRoot, "tools", "go-app-release.cjs"))) {
|
|
2109
|
+
if (host !== "github" || !isMainPush(environment) || !(0, import_node_fs5.existsSync)((0, import_node_path4.join)(workspaceRoot, "tools", "go-app-release.cjs"))) {
|
|
2377
2110
|
return 0;
|
|
2378
2111
|
}
|
|
2379
2112
|
return inGroup("Attach this OS's zip to the GitHub Release", "node", ["tools/go-app-release.cjs", "assets", "--native"]);
|
|
@@ -2557,14 +2290,22 @@ function runReleaseCommand(workspaceRoot, registry, counts, environment, process
|
|
|
2557
2290
|
}
|
|
2558
2291
|
return processes.run("npx", ["nx", "release", ...extra, "--yes"]);
|
|
2559
2292
|
}
|
|
2560
|
-
function
|
|
2561
|
-
|
|
2293
|
+
function nxPushesTags(workspaceRoot) {
|
|
2294
|
+
try {
|
|
2295
|
+
const nx = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path6.join)(workspaceRoot, "nx.json"), "utf8"));
|
|
2296
|
+
return nx.release?.git?.push === true;
|
|
2297
|
+
} catch {
|
|
2298
|
+
return false;
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
function afterRelease(workspaceRoot, processes, githubReleases) {
|
|
2302
|
+
if (githubReleases && (0, import_node_fs7.existsSync)((0, import_node_path6.join)(workspaceRoot, "tools", "go-app-release.cjs"))) {
|
|
2562
2303
|
const assets = processes.run("node", ["tools/go-app-release.cjs", "assets"]);
|
|
2563
2304
|
if (assets !== 0) {
|
|
2564
2305
|
return assets;
|
|
2565
2306
|
}
|
|
2566
2307
|
}
|
|
2567
|
-
return processes.run("git", ["push", "origin", "--tags"]);
|
|
2308
|
+
return githubReleases ? 0 : processes.run("git", ["push", "origin", "--tags"]);
|
|
2568
2309
|
}
|
|
2569
2310
|
async function runRelease(workspaceRoot, dependencies = {}) {
|
|
2570
2311
|
const environment = dependencies.environment ?? process.env;
|
|
@@ -2605,7 +2346,7 @@ async function runRelease(workspaceRoot, dependencies = {}) {
|
|
|
2605
2346
|
if (released !== 0) {
|
|
2606
2347
|
return close(released);
|
|
2607
2348
|
}
|
|
2608
|
-
return close(afterRelease(workspaceRoot, processes));
|
|
2349
|
+
return close(afterRelease(workspaceRoot, processes, nxPushesTags(workspaceRoot)));
|
|
2609
2350
|
}
|
|
2610
2351
|
|
|
2611
2352
|
// src/ci-pipeline/setup.use-case.ts
|