@mnci/cli 4.35.0 → 4.37.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 +310 -389
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -429,6 +429,131 @@ function fileExists(path) {
|
|
|
429
429
|
return (0, import_node_fs3.existsSync)(path);
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
+
// src/pipeline-customization/pipeline-markers.algorithm.ts
|
|
433
|
+
var CHOOSABLE_PHASES = ["pack", "release"];
|
|
434
|
+
var SLOT_BEGIN = /^(\s*)# mnci:slot (\S+)/;
|
|
435
|
+
var SLOT_END = /^\s*# mnci:slot-end \S+/;
|
|
436
|
+
var PHASE_BEGIN = /^(\s*)# mnci:phase (\S+)/;
|
|
437
|
+
var PHASE_END = /^\s*# mnci:phase-end (\S+)/;
|
|
438
|
+
var SWITCHED_OFF_HINT = "# mnci: switched off. Uncomment the lines below to run it again (mnci doctor lists it).";
|
|
439
|
+
function slotMarkers(name, indent) {
|
|
440
|
+
return [
|
|
441
|
+
`${indent}# mnci:slot ${name} (your own steps go between these two lines; mnci upgrade keeps them)`,
|
|
442
|
+
`${indent}# mnci:slot-end ${name}`
|
|
443
|
+
].join("\n");
|
|
444
|
+
}
|
|
445
|
+
function phaseStart(name, indent) {
|
|
446
|
+
return `${indent}# mnci:phase ${name} (delete or comment this block to switch it off; mnci upgrade keeps it off)`;
|
|
447
|
+
}
|
|
448
|
+
function phaseEnd(name, indent) {
|
|
449
|
+
return `${indent}# mnci:phase-end ${name}`;
|
|
450
|
+
}
|
|
451
|
+
function extractSlots(text) {
|
|
452
|
+
const slots = /* @__PURE__ */ new Map();
|
|
453
|
+
const lines = text.split("\n");
|
|
454
|
+
for (let index = 0; index < lines.length; index++) {
|
|
455
|
+
const begin = SLOT_BEGIN.exec(lines[index]);
|
|
456
|
+
if (!begin) {
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const [, indent, name] = begin;
|
|
460
|
+
const content = [];
|
|
461
|
+
for (index++; index < lines.length && !SLOT_END.test(lines[index]); index++) {
|
|
462
|
+
content.push(lines[index].startsWith(indent) ? lines[index].slice(indent.length) : lines[index].trimStart());
|
|
463
|
+
}
|
|
464
|
+
slots.set(name, content);
|
|
465
|
+
index--;
|
|
466
|
+
}
|
|
467
|
+
return slots;
|
|
468
|
+
}
|
|
469
|
+
function injectSlots(generated, slots) {
|
|
470
|
+
const lines = generated.split("\n");
|
|
471
|
+
const result = [];
|
|
472
|
+
for (let index = 0; index < lines.length; index++) {
|
|
473
|
+
result.push(lines[index]);
|
|
474
|
+
const begin = SLOT_BEGIN.exec(lines[index]);
|
|
475
|
+
if (!begin) {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
const [, indent, name] = begin;
|
|
479
|
+
const content = slots.get(name) ?? [];
|
|
480
|
+
for (const line of content) {
|
|
481
|
+
result.push(line === "" ? "" : `${indent}${line}`);
|
|
482
|
+
}
|
|
483
|
+
while (index + 1 < lines.length && !SLOT_END.test(lines[index + 1])) {
|
|
484
|
+
index++;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return result.join("\n");
|
|
488
|
+
}
|
|
489
|
+
function switchedOffPhases(text) {
|
|
490
|
+
const lines = text.split("\n");
|
|
491
|
+
if (lines.every((line) => !(PHASE_BEGIN.test(line) || SLOT_BEGIN.test(line)))) {
|
|
492
|
+
return [];
|
|
493
|
+
}
|
|
494
|
+
return CHOOSABLE_PHASES.filter((phase) => {
|
|
495
|
+
const begin = lines.findIndex((line) => PHASE_BEGIN.exec(line)?.[2] === phase);
|
|
496
|
+
if (begin === -1) {
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
for (let index = begin + 1; index < lines.length && PHASE_END.exec(lines[index])?.[1] !== phase; index++) {
|
|
500
|
+
const line = lines[index].trim();
|
|
501
|
+
if (line !== "" && !line.startsWith("#")) {
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return true;
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
function commentOutPhases(generated, phases) {
|
|
509
|
+
if (phases.length === 0) {
|
|
510
|
+
return generated;
|
|
511
|
+
}
|
|
512
|
+
const lines = generated.split("\n");
|
|
513
|
+
const result = [];
|
|
514
|
+
for (let index = 0; index < lines.length; index++) {
|
|
515
|
+
result.push(lines[index]);
|
|
516
|
+
const begin = PHASE_BEGIN.exec(lines[index]);
|
|
517
|
+
if (!begin || !phases.includes(begin[2])) {
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
const [, indent, name] = begin;
|
|
521
|
+
result.push(`${indent}${SWITCHED_OFF_HINT}`);
|
|
522
|
+
for (index++; index < lines.length && PHASE_END.exec(lines[index])?.[1] !== name; index++) {
|
|
523
|
+
const line = lines[index];
|
|
524
|
+
result.push(line.trim() === "" || line.trimStart().startsWith("#") ? line : `${indent}# ${line.slice(indent.length)}`);
|
|
525
|
+
}
|
|
526
|
+
index--;
|
|
527
|
+
}
|
|
528
|
+
return result.join("\n");
|
|
529
|
+
}
|
|
530
|
+
function inspectPipeline(text) {
|
|
531
|
+
return {
|
|
532
|
+
verifyActive: text.split("\n").some((line) => !line.trimStart().startsWith("#") && /\bci verify\b/.test(line)),
|
|
533
|
+
switchedOff: switchedOffPhases(text)
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// src/pipeline-customization/merge-pipeline.use-case.ts
|
|
538
|
+
function mergePipeline(existing, generated) {
|
|
539
|
+
if (existing === void 0) {
|
|
540
|
+
return { text: generated, notes: [] };
|
|
541
|
+
}
|
|
542
|
+
const normalized = existing.replaceAll("\r\n", "\n");
|
|
543
|
+
if (!/^\s*# mnci:(?:slot|phase) /m.test(normalized)) {
|
|
544
|
+
return {
|
|
545
|
+
text: generated,
|
|
546
|
+
notes: ['This pipeline was written before mnci kept user slots, so it was regenerated whole. Edits outside the generated steps are in git history: re-apply them inside the new "# mnci:slot" blocks.']
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
const off = switchedOffPhases(normalized);
|
|
550
|
+
const text = commentOutPhases(injectSlots(generated, extractSlots(normalized)), off);
|
|
551
|
+
return {
|
|
552
|
+
text,
|
|
553
|
+
notes: off.map((phase) => `The ${phase} phase is switched off in this pipeline and stays off.`)
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
|
|
432
557
|
// src/workspace-overlay/overlay.use-case.ts
|
|
433
558
|
var DEFAULT_STACK = { testRunner: "jest" };
|
|
434
559
|
var TS_COMPILER_DEPENDENCIES = {
|
|
@@ -717,9 +842,9 @@ function devcontainerJson(workspaceName) {
|
|
|
717
842
|
// precisely the drift this file exists to remove.
|
|
718
843
|
`npm install -g npm@${NPM_VERSION}`,
|
|
719
844
|
"npm ci",
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
845
|
+
// The same command the pipeline runs: the Python, Go and Flutter toolchains, each
|
|
846
|
+
// skipped when the workspace has no project in that language.
|
|
847
|
+
"npx mnci ci setup"
|
|
723
848
|
].join(" && "),
|
|
724
849
|
// The same recommendations the `.code-workspace` file carries, so opening
|
|
725
850
|
// the folder in a container suggests the identical toolset.
|
|
@@ -918,6 +1043,13 @@ function isUnmodifiedMnciEslintConfig(content) {
|
|
|
918
1043
|
const code = content.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !line.startsWith("//"));
|
|
919
1044
|
return code.length === 2 && code[0] === "import mnci from '@mnci/eslint-config'" && code[1] === "export default mnci({ workspaceRoot: import.meta.dirname })";
|
|
920
1045
|
}
|
|
1046
|
+
function writePipeline(path, onProgress, generated) {
|
|
1047
|
+
const { text, notes } = mergePipeline((0, import_node_fs4.existsSync)(path) ? (0, import_node_fs4.readFileSync)(path, "utf8") : void 0, generated);
|
|
1048
|
+
for (const note of notes) {
|
|
1049
|
+
onProgress(` ${note}`);
|
|
1050
|
+
}
|
|
1051
|
+
writeFileEnsured(path, text);
|
|
1052
|
+
}
|
|
921
1053
|
function writeEslintEntryPoint(workspaceRoot, onProgress) {
|
|
922
1054
|
const path = (0, import_node_path3.join)(workspaceRoot, ESLINT_USER_FILENAME);
|
|
923
1055
|
if ((0, import_node_fs4.existsSync)(path) && !isUnmodifiedMnciEslintConfig((0, import_node_fs4.readFileSync)(path, "utf8"))) {
|
|
@@ -1158,13 +1290,7 @@ function nugetConfigContent(registry, scope) {
|
|
|
1158
1290
|
}
|
|
1159
1291
|
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
1292
|
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
1293
|
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
1294
|
var FLUTTER_SDK_VERSION = "3.44.8";
|
|
1169
1295
|
var NODE_VERSION = "24";
|
|
1170
1296
|
var ACTION_VERSIONS = {
|
|
@@ -1177,24 +1303,10 @@ var ACTION_VERSIONS = {
|
|
|
1177
1303
|
var NPM_VERSION = "11";
|
|
1178
1304
|
var DOTNET_SDK_VERSION = "10.0.x";
|
|
1179
1305
|
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
1306
|
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
1307
|
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
1308
|
var NATIVE_LINUX_PREREQUISITES = "sudo apt-get update && sudo apt-get install -y gcc pkg-config";
|
|
1197
|
-
function githubNativeJob(npmAuthName, npmAuthValue
|
|
1309
|
+
function githubNativeJob(npmAuthName, npmAuthValue) {
|
|
1198
1310
|
return `
|
|
1199
1311
|
# Apps that need a C toolchain (mnci add go-app --cgo) cannot be cross-compiled
|
|
1200
1312
|
# from the job above, so each is linted, tested, built and packaged here, on a
|
|
@@ -1229,14 +1341,10 @@ function githubNativeJob(npmAuthName, npmAuthValue, onMain) {
|
|
|
1229
1341
|
env:
|
|
1230
1342
|
${npmAuthName}: ${npmAuthValue}
|
|
1231
1343
|
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
name: Install golangci-lint
|
|
1237
|
-
|
|
1238
|
-
- run: ${GO_TOOL_PATH_GITHUB}
|
|
1239
|
-
name: Add Go tool bin to PATH
|
|
1344
|
+
# The Go toolchain: the modules, and a checksum-verified golangci-lint put
|
|
1345
|
+
# on PATH. The other toolchains are skipped cleanly when absent.
|
|
1346
|
+
- run: npx mnci ci setup
|
|
1347
|
+
name: Set up the Go toolchain
|
|
1240
1348
|
|
|
1241
1349
|
# A C compiler and pkg-config, which is all mnci can know a cgo app needs.
|
|
1242
1350
|
# Add the -dev packages your app links to this line, for example
|
|
@@ -1246,22 +1354,20 @@ function githubNativeJob(npmAuthName, npmAuthValue, onMain) {
|
|
|
1246
1354
|
name: Install native prerequisites (Linux)
|
|
1247
1355
|
if: \${{ runner.os == 'Linux' }}
|
|
1248
1356
|
|
|
1249
|
-
|
|
1357
|
+
# Lints, tests, builds and packages the native apps on this OS. On a push to
|
|
1358
|
+
# main, a releasable native app (--cgo with --release) also gets this OS's zip
|
|
1359
|
+
# attached to the GitHub Release the ci job just created, stamped with the
|
|
1360
|
+
# tag's version.
|
|
1361
|
+
- run: npx mnci ci native
|
|
1250
1362
|
name: Lint, test, build and package the native apps
|
|
1363
|
+
env:
|
|
1364
|
+
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
1251
1365
|
|
|
1252
1366
|
- uses: actions/upload-artifact@${ACTION_VERSIONS["actions/upload-artifact"]}
|
|
1253
1367
|
with:
|
|
1254
1368
|
name: native-\${{ matrix.os }}
|
|
1255
1369
|
path: dist/drop
|
|
1256
1370
|
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
1371
|
`;
|
|
1266
1372
|
}
|
|
1267
1373
|
function indentYaml(text, spaces) {
|
|
@@ -1306,40 +1412,12 @@ ${matrix}
|
|
|
1306
1412
|
${indentYaml(nativeSteps, 4)}
|
|
1307
1413
|
`;
|
|
1308
1414
|
}
|
|
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
1415
|
var PYPI_TOKEN_VARIABLE = "PYPI_TOKEN";
|
|
1323
1416
|
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
1417
|
function pypiTokenEnvLine(registryKind, variableReference, indent) {
|
|
1328
1418
|
return registryKind === "npm" ? `
|
|
1329
1419
|
${indent}${PYPI_TOKEN_VARIABLE}: ${variableReference(PYPI_TOKEN_VARIABLE)}` : "";
|
|
1330
1420
|
}
|
|
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
1421
|
function npmAuthEnvVariable(registryKind, variableReference) {
|
|
1344
1422
|
return registryKind === "npm" ? ["NODE_AUTH_TOKEN", variableReference("NPM_TOKEN")] : [AZURE_PAT_VARIABLE, variableReference(AZURE_PAT_VARIABLE)];
|
|
1345
1423
|
}
|
|
@@ -1483,66 +1561,27 @@ steps:
|
|
|
1483
1561
|
${npmAuthenticateStep} - script: npm ci
|
|
1484
1562
|
displayName: Install dependencies${npmCiEnv}
|
|
1485
1563
|
|
|
1564
|
+
${slotMarkers("after-install", " ")}
|
|
1565
|
+
|
|
1566
|
+
# Everything from here to the release is a command of the mnci CLI, a
|
|
1567
|
+
# devDependency of this workspace, so the version is the lockfile's and a
|
|
1568
|
+
# developer reproduces CI by running the same line. The guards that used to be
|
|
1569
|
+
# inline here are tested TypeScript in the CLI's ci-pipeline slice. Node, the
|
|
1570
|
+
# checkout and 'npm ci' stay above: Node must exist before 'npx' can run.
|
|
1571
|
+
|
|
1572
|
+
# The language toolchains the workspace needs: Python (ruff, pytest, build,
|
|
1573
|
+
# twine, and every project installed into one shared environment), Go (the
|
|
1574
|
+
# modules and a checksum-verified golangci-lint, put on PATH) and Flutter (the
|
|
1575
|
+
# pinned SDK, put on PATH, and one pub get). Each is skipped cleanly when the
|
|
1576
|
+
# workspace has no project in that language.
|
|
1577
|
+
- script: npx mnci ci setup
|
|
1578
|
+
displayName: Set up the language toolchains (Python, Go, Flutter)
|
|
1579
|
+
|
|
1486
1580
|
# 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)
|
|
1581
|
+
# anything upstream has not fixed is printed and passes. pip-audit only
|
|
1582
|
+
# reports: its output has no "a fix exists" field to draw that line with.
|
|
1583
|
+
- script: npx mnci ci audit
|
|
1584
|
+
displayName: Audit dependencies (fails on an actionable npm advisory)
|
|
1546
1585
|
|
|
1547
1586
|
# .NET, if the workspace has any. Azure's 'condition:' expression language
|
|
1548
1587
|
# has no glob function, so this script step detects C# projects first and
|
|
@@ -1558,29 +1597,19 @@ ${npmAuthenticateStep} - script: npm ci
|
|
|
1558
1597
|
inputs:
|
|
1559
1598
|
version: ${DOTNET_SDK_VERSION}
|
|
1560
1599
|
|
|
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)
|
|
1600
|
+
# Fails fast on a stale TypeScript project reference, then the one verify
|
|
1601
|
+
# step, and deliberately the only one: affected projects on a pull request,
|
|
1602
|
+
# EVERY project on anything else (a push to main included, so a release is
|
|
1603
|
+
# always verified in full). Every fallback takes the full path, because a run
|
|
1604
|
+
# that verifies too little still reports green. Apps that need a C toolchain
|
|
1605
|
+
# are left to the native job.
|
|
1606
|
+
- script: npx mnci ci verify
|
|
1607
|
+
displayName: Verify (sync check, then affected on a PR, every project on main)
|
|
1580
1608
|
|
|
1609
|
+
${phaseStart("pack", " ")}
|
|
1581
1610
|
# Pack every app into dist/drop/<type>-<name>.zip via each app's 'package'
|
|
1582
|
-
# target
|
|
1583
|
-
- script:
|
|
1611
|
+
# target; skipped cleanly when the workspace has no apps yet.
|
|
1612
|
+
- script: npx mnci ci pack
|
|
1584
1613
|
displayName: Pack all apps (one zip per app -> dist/drop)
|
|
1585
1614
|
condition: ${onMain}
|
|
1586
1615
|
|
|
@@ -1597,57 +1626,28 @@ ${npmAuthenticateStep} - script: npm ci
|
|
|
1597
1626
|
- script: node -e "const fs=require('node:fs');const path=require('node:path');for(const f of fs.globSync('dist/drop/*.zip')){console.log('##vso[build.addbuildtag]'+path.basename(f,'.zip'))}"
|
|
1598
1627
|
displayName: Tag the run per app (type-name)
|
|
1599
1628
|
condition: ${onMain}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
#
|
|
1606
|
-
#
|
|
1607
|
-
#
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
#
|
|
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).
|
|
1629
|
+
${phaseEnd("pack", " ")}
|
|
1630
|
+
|
|
1631
|
+
${slotMarkers("before-release", " ")}
|
|
1632
|
+
|
|
1633
|
+
${phaseStart("release", " ")}
|
|
1634
|
+
# The release, in the order that keeps a failure safe: refuse a shallow
|
|
1635
|
+
# clone, prove the npm token and name the PyPI projects it would create (all
|
|
1636
|
+
# BEFORE anything is tagged), then version + tag + publish in one 'nx release'
|
|
1637
|
+
# for npm, Python, C# and VS Code, then push the tags explicitly (nx release's
|
|
1638
|
+
# own push never runs without a remote GitHub/GitLab Release configured, which
|
|
1639
|
+
# this pipeline never does). Which registry is in use is read from the 'mnci'
|
|
1640
|
+
# block of nx.json, not baked into this file. The secrets are mapped here, in
|
|
1641
|
+
# the one place a command cannot reach.
|
|
1638
1642
|
#
|
|
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))}
|
|
1643
|
+
# RELEASE_SPECIFIER (the pipeline variable above) overrides the bump nx would
|
|
1644
|
+
# compute from conventional commits for THIS run: 'major', 'minor', 'patch', or
|
|
1645
|
+
# an exact version ('1.2.3'). With more than one releasable package a bare
|
|
1646
|
+
# keyword is unreliable (nx versions interdependent packages in two passes, and
|
|
1647
|
+
# the second computes from a version cached before the first), so the command
|
|
1648
|
+
# fails the run rather than under-bumping silently: prefer an exact version,
|
|
1649
|
+
# and clear the variable back to '' once the override is no longer needed.
|
|
1650
|
+
- script: npx mnci ci release
|
|
1651
1651
|
displayName: Release \u2014 version, tag and publish (npm + Python + C# + VS Code)
|
|
1652
1652
|
condition: ${onMain}
|
|
1653
1653
|
env:
|
|
@@ -1656,15 +1656,9 @@ ${npmAuthPreflight(registryKind) ? `
|
|
|
1656
1656
|
# A VS Code extension publishes with it; tools/vscode-extension.cjs skips the
|
|
1657
1657
|
# Marketplace when it is unset, including when Azure leaves it as '$(VSCE_PAT)'.
|
|
1658
1658
|
VSCE_PAT: $(VSCE_PAT)
|
|
1659
|
+
${phaseEnd("release", " ")}
|
|
1659
1660
|
|
|
1660
|
-
|
|
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}
|
|
1661
|
+
${slotMarkers("after-release", " ")}
|
|
1668
1662
|
`;
|
|
1669
1663
|
if (!nativeApps) {
|
|
1670
1664
|
return document;
|
|
@@ -1687,14 +1681,10 @@ ${npmAuthPreflight(registryKind) ? `
|
|
|
1687
1681
|
${npmAuthenticateStep} - script: npm ci
|
|
1688
1682
|
displayName: Install dependencies${npmCiEnv}
|
|
1689
1683
|
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
displayName: Install golangci-lint
|
|
1695
|
-
|
|
1696
|
-
- script: ${GO_TOOL_PATH_AZURE}
|
|
1697
|
-
displayName: Add Go tool bin to PATH
|
|
1684
|
+
# The Go toolchain: the modules, and a checksum-verified golangci-lint put on
|
|
1685
|
+
# PATH. The other toolchains are skipped cleanly when absent.
|
|
1686
|
+
- script: npx mnci ci setup
|
|
1687
|
+
displayName: Set up the Go toolchain
|
|
1698
1688
|
|
|
1699
1689
|
# A C compiler and pkg-config, which is all mnci can know a cgo app needs. Add the
|
|
1700
1690
|
# -dev packages your app links to this line, for example libgtk-3-dev and
|
|
@@ -1704,7 +1694,7 @@ ${npmAuthenticateStep} - script: npm ci
|
|
|
1704
1694
|
displayName: Install native prerequisites (Linux)
|
|
1705
1695
|
condition: eq(variables['Agent.OS'], 'Linux')
|
|
1706
1696
|
|
|
1707
|
-
- script:
|
|
1697
|
+
- script: npx mnci ci native
|
|
1708
1698
|
displayName: Lint, test, build and package the native apps
|
|
1709
1699
|
|
|
1710
1700
|
- task: PublishBuildArtifacts@1
|
|
@@ -1795,66 +1785,27 @@ jobs:
|
|
|
1795
1785
|
env:
|
|
1796
1786
|
${npmAuthName}: ${npmAuthValue}
|
|
1797
1787
|
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
#
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
#
|
|
1805
|
-
|
|
1806
|
-
#
|
|
1807
|
-
#
|
|
1808
|
-
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
#
|
|
1815
|
-
#
|
|
1816
|
-
|
|
1817
|
-
|
|
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)
|
|
1788
|
+
${slotMarkers("after-install", " ".repeat(6))}
|
|
1789
|
+
|
|
1790
|
+
# Everything from here to the release is a command of the mnci CLI, a
|
|
1791
|
+
# devDependency of this workspace, so the version is the lockfile's and a
|
|
1792
|
+
# developer reproduces CI by running the same line. The guards that used to be
|
|
1793
|
+
# inline here are tested TypeScript in the CLI's ci-pipeline slice. Node,
|
|
1794
|
+
# the checkout and 'npm ci' stay above: Node must exist before 'npx' can run.
|
|
1795
|
+
|
|
1796
|
+
# The language toolchains the workspace needs: Python (ruff, pytest, build,
|
|
1797
|
+
# twine, and every project installed into one shared environment), Go (the
|
|
1798
|
+
# modules and a checksum-verified golangci-lint, put on PATH) and Flutter (the
|
|
1799
|
+
# pinned SDK, put on PATH, and one pub get). Each is skipped cleanly when the
|
|
1800
|
+
# workspace has no project in that language.
|
|
1801
|
+
- run: npx mnci ci setup
|
|
1802
|
+
name: Set up the language toolchains (Python, Go, Flutter)
|
|
1803
|
+
|
|
1804
|
+
# Fails ONLY on an advisory that has a published fix, at moderate or above;
|
|
1805
|
+
# anything upstream has not fixed is printed and passes. pip-audit only
|
|
1806
|
+
# reports: its output has no "a fix exists" field to draw that line with.
|
|
1807
|
+
- run: npx mnci ci audit
|
|
1808
|
+
name: Audit dependencies (fails on an actionable npm advisory)
|
|
1858
1809
|
|
|
1859
1810
|
# .NET, if the workspace has any. actions/setup-dotnet is the
|
|
1860
1811
|
# maintained, cached installer GitHub itself ships, so this uses it
|
|
@@ -1866,30 +1817,19 @@ jobs:
|
|
|
1866
1817
|
with:
|
|
1867
1818
|
dotnet-version: ${DOTNET_SDK_VERSION}
|
|
1868
1819
|
|
|
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)
|
|
1820
|
+
# Fails fast on a stale TypeScript project reference, then the one verify
|
|
1821
|
+
# step, and deliberately the only one: affected projects on a pull request,
|
|
1822
|
+
# EVERY project on anything else (a push to main included, so a release is
|
|
1823
|
+
# always verified in full). Every fallback takes the full path, because a run
|
|
1824
|
+
# that verifies too little still reports green. Apps that need a C toolchain
|
|
1825
|
+
# are left to the native job.
|
|
1826
|
+
- run: npx mnci ci verify
|
|
1827
|
+
name: Verify (sync check, then affected on a PR, every project on main)
|
|
1889
1828
|
|
|
1829
|
+
${phaseStart("pack", " ".repeat(6))}
|
|
1890
1830
|
# Pack every app into dist/drop/<type>-<name>.zip via each app's 'package'
|
|
1891
|
-
# target
|
|
1892
|
-
- run:
|
|
1831
|
+
# target; skipped cleanly when the workspace has no apps yet.
|
|
1832
|
+
- run: npx mnci ci pack
|
|
1893
1833
|
name: Pack all apps (one zip per app -> dist/drop)
|
|
1894
1834
|
if: \${{ ${onMain} }}
|
|
1895
1835
|
|
|
@@ -1899,66 +1839,11 @@ jobs:
|
|
|
1899
1839
|
name: drop
|
|
1900
1840
|
path: dist/drop
|
|
1901
1841
|
if-no-files-found: ignore
|
|
1842
|
+
${phaseEnd("pack", " ".repeat(6))}
|
|
1902
1843
|
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
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.` : ""}
|
|
1844
|
+
${slotMarkers("before-release", " ".repeat(6))}
|
|
1845
|
+
|
|
1846
|
+
${phaseStart("release", " ".repeat(6))}
|
|
1962
1847
|
# Signs in to Microsoft Entra ID as the Marketplace publishing identity (an
|
|
1963
1848
|
# app registration with a federated credential for this repository's main
|
|
1964
1849
|
# branch), so the release step publishes VS Code extensions with
|
|
@@ -1974,7 +1859,23 @@ ${npmAuthPreflight(registryKind) ? `
|
|
|
1974
1859
|
tenant-id: \${{ vars.AZURE_TENANT_ID }}
|
|
1975
1860
|
allow-no-subscriptions: true
|
|
1976
1861
|
|
|
1977
|
-
|
|
1862
|
+
# The release, in the order that keeps a failure safe: refuse a shallow
|
|
1863
|
+
# clone, prove the npm token and name the PyPI projects it would create (all
|
|
1864
|
+
# BEFORE anything is tagged), then version + tag + publish in one 'nx release'
|
|
1865
|
+
# for npm, Python, C# and VS Code, then attach a releasable Go app's zips and
|
|
1866
|
+
# push the tags. Which registry, and whether GitHub Releases are on, are read
|
|
1867
|
+
# from the 'mnci' block of nx.json, not baked into this file. The secrets are
|
|
1868
|
+
# mapped here, in the one place a command cannot reach.
|
|
1869
|
+
#
|
|
1870
|
+
# RELEASE_SPECIFIER (repository variable, Settings -> Secrets and
|
|
1871
|
+
# variables -> Actions -> Variables) overrides the bump nx would compute
|
|
1872
|
+
# from conventional commits for THIS run: 'major', 'minor', 'patch', or an
|
|
1873
|
+
# exact version ('1.2.3'). With more than one releasable package a bare
|
|
1874
|
+
# keyword is unreliable (nx versions interdependent packages in two passes,
|
|
1875
|
+
# and the second computes from a version cached before the first), so the
|
|
1876
|
+
# command fails the run rather than under-bumping silently: prefer an exact
|
|
1877
|
+
# version, and clear the variable once the override is no longer needed.
|
|
1878
|
+
- run: npx mnci ci release
|
|
1978
1879
|
name: Release \u2014 version, tag${githubReleases ? ", publish and GitHub Release" : " and publish"} (npm + Python + C# + VS Code)
|
|
1979
1880
|
if: \${{ ${onMain} }}
|
|
1980
1881
|
env:
|
|
@@ -1985,28 +1886,12 @@ ${npmAuthPreflight(registryKind) ? `
|
|
|
1985
1886
|
# tools/vscode-extension.cjs skips the Marketplace with neither.
|
|
1986
1887
|
VSCE_AUTH: \${{ vars.AZURE_CLIENT_ID != '' && 'entra' || '' }}
|
|
1987
1888
|
VSCE_PAT: \${{ secrets.VSCE_PAT }}${githubReleases ? `
|
|
1988
|
-
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
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) : ""}`;
|
|
1889
|
+
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
1890
|
+
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}` : ""}
|
|
1891
|
+
${phaseEnd("release", " ".repeat(6))}
|
|
1892
|
+
|
|
1893
|
+
${slotMarkers("after-release", " ".repeat(6))}
|
|
1894
|
+
${nativeApps ? githubNativeJob(npmAuthName, npmAuthValue) : ""}`;
|
|
2010
1895
|
}
|
|
2011
1896
|
var PIP_PROJECT_GLOBS = ["apps", "python-packages", "libs"];
|
|
2012
1897
|
var PUB_PROJECT_GLOBS = ["apps", "packages", "libs"];
|
|
@@ -2326,8 +2211,9 @@ function applyOverlay(workspaceRoot, options, onProgress = () => {
|
|
|
2326
2211
|
const nativeApps = hasNativeGoApp(workspaceRoot);
|
|
2327
2212
|
if (options.ci === "azure" || options.ci === "both") {
|
|
2328
2213
|
onProgress("azure-pipelines.yml \u2014 build, verify, pack and release");
|
|
2329
|
-
|
|
2214
|
+
writePipeline(
|
|
2330
2215
|
(0, import_node_path3.join)(workspaceRoot, "azure-pipelines.yml"),
|
|
2216
|
+
onProgress,
|
|
2331
2217
|
azurePipelinesYaml(
|
|
2332
2218
|
options.agent,
|
|
2333
2219
|
options.variableGroup,
|
|
@@ -2341,8 +2227,9 @@ function applyOverlay(workspaceRoot, options, onProgress = () => {
|
|
|
2341
2227
|
}
|
|
2342
2228
|
if (options.ci === "github" || options.ci === "both") {
|
|
2343
2229
|
onProgress(".github/workflows/ci.yml and dependabot.yml");
|
|
2344
|
-
|
|
2230
|
+
writePipeline(
|
|
2345
2231
|
(0, import_node_path3.join)(workspaceRoot, ".github/workflows/ci.yml"),
|
|
2232
|
+
onProgress,
|
|
2346
2233
|
githubActionsYaml(options.agent, publishUrl, options.registry.kind, options.ci, nugetUrl, nativeApps)
|
|
2347
2234
|
);
|
|
2348
2235
|
writeFileEnsured((0, import_node_path3.join)(workspaceRoot, ".github/dependabot.yml"), dependabotConfig(workspaceRoot));
|
|
@@ -2373,7 +2260,7 @@ function runNative(workspaceRoot, dependencies = {}) {
|
|
|
2373
2260
|
if (built !== 0) {
|
|
2374
2261
|
return built;
|
|
2375
2262
|
}
|
|
2376
|
-
if (!isMainPush(environment) || !(0, import_node_fs5.existsSync)((0, import_node_path4.join)(workspaceRoot, "tools", "go-app-release.cjs"))) {
|
|
2263
|
+
if (host !== "github" || !isMainPush(environment) || !(0, import_node_fs5.existsSync)((0, import_node_path4.join)(workspaceRoot, "tools", "go-app-release.cjs"))) {
|
|
2377
2264
|
return 0;
|
|
2378
2265
|
}
|
|
2379
2266
|
return inGroup("Attach this OS's zip to the GitHub Release", "node", ["tools/go-app-release.cjs", "assets", "--native"]);
|
|
@@ -2557,14 +2444,22 @@ function runReleaseCommand(workspaceRoot, registry, counts, environment, process
|
|
|
2557
2444
|
}
|
|
2558
2445
|
return processes.run("npx", ["nx", "release", ...extra, "--yes"]);
|
|
2559
2446
|
}
|
|
2560
|
-
function
|
|
2561
|
-
|
|
2447
|
+
function nxPushesTags(workspaceRoot) {
|
|
2448
|
+
try {
|
|
2449
|
+
const nx = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path6.join)(workspaceRoot, "nx.json"), "utf8"));
|
|
2450
|
+
return nx.release?.git?.push === true;
|
|
2451
|
+
} catch {
|
|
2452
|
+
return false;
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
function afterRelease(workspaceRoot, processes, githubReleases) {
|
|
2456
|
+
if (githubReleases && (0, import_node_fs7.existsSync)((0, import_node_path6.join)(workspaceRoot, "tools", "go-app-release.cjs"))) {
|
|
2562
2457
|
const assets = processes.run("node", ["tools/go-app-release.cjs", "assets"]);
|
|
2563
2458
|
if (assets !== 0) {
|
|
2564
2459
|
return assets;
|
|
2565
2460
|
}
|
|
2566
2461
|
}
|
|
2567
|
-
return processes.run("git", ["push", "origin", "--tags"]);
|
|
2462
|
+
return githubReleases ? 0 : processes.run("git", ["push", "origin", "--tags"]);
|
|
2568
2463
|
}
|
|
2569
2464
|
async function runRelease(workspaceRoot, dependencies = {}) {
|
|
2570
2465
|
const environment = dependencies.environment ?? process.env;
|
|
@@ -2605,7 +2500,7 @@ async function runRelease(workspaceRoot, dependencies = {}) {
|
|
|
2605
2500
|
if (released !== 0) {
|
|
2606
2501
|
return close(released);
|
|
2607
2502
|
}
|
|
2608
|
-
return close(afterRelease(workspaceRoot, processes));
|
|
2503
|
+
return close(afterRelease(workspaceRoot, processes, nxPushesTags(workspaceRoot)));
|
|
2609
2504
|
}
|
|
2610
2505
|
|
|
2611
2506
|
// src/ci-pipeline/setup.use-case.ts
|
|
@@ -7892,6 +7787,26 @@ function checkDeclarationSpecifiers(workspaceRoot) {
|
|
|
7892
7787
|
];
|
|
7893
7788
|
});
|
|
7894
7789
|
}
|
|
7790
|
+
function checkPipelinePhases(workspaceRoot) {
|
|
7791
|
+
const files = ["azure-pipelines.yml", ".github/workflows/ci.yml"];
|
|
7792
|
+
return files.filter((file) => fileExists((0, import_node_path31.join)(workspaceRoot, file))).flatMap((file) => {
|
|
7793
|
+
const { verifyActive, switchedOff } = inspectPipeline((0, import_node_fs25.readFileSync)((0, import_node_path31.join)(workspaceRoot, file), "utf8"));
|
|
7794
|
+
const verify = {
|
|
7795
|
+
check: `${file} runs the enforced verify phase`,
|
|
7796
|
+
ok: verifyActive,
|
|
7797
|
+
detail: `${file} has no active \`mnci ci verify\` call, so CI would build and release code nothing checked`,
|
|
7798
|
+
remedy: "run `mnci upgrade`, which restores it"
|
|
7799
|
+
};
|
|
7800
|
+
const warnings = switchedOff.map((phase) => ({
|
|
7801
|
+
check: `${file}: the ${phase} phase is switched off`,
|
|
7802
|
+
ok: true,
|
|
7803
|
+
warning: true,
|
|
7804
|
+
detail: `${file} does not run \`mnci ci ${phase}\``,
|
|
7805
|
+
remedy: `uncomment the ${phase} block in ${file} to run it again`
|
|
7806
|
+
}));
|
|
7807
|
+
return [verify, ...warnings];
|
|
7808
|
+
});
|
|
7809
|
+
}
|
|
7895
7810
|
function collectFindings(workspaceRoot) {
|
|
7896
7811
|
const nxJsonPath = (0, import_node_path31.join)(workspaceRoot, "nx.json");
|
|
7897
7812
|
if (!fileExists(nxJsonPath)) {
|
|
@@ -7916,7 +7831,8 @@ function collectFindings(workspaceRoot) {
|
|
|
7916
7831
|
checkSync(workspaceRoot),
|
|
7917
7832
|
checkGoPluginRegistered(workspaceRoot),
|
|
7918
7833
|
checkGoWorkInSync(workspaceRoot),
|
|
7919
|
-
...checkNativeApps(workspaceRoot)
|
|
7834
|
+
...checkNativeApps(workspaceRoot),
|
|
7835
|
+
...checkPipelinePhases(workspaceRoot)
|
|
7920
7836
|
].filter((finding) => finding !== void 0);
|
|
7921
7837
|
}
|
|
7922
7838
|
function runDoctor(workspaceRoot, options = {}) {
|
|
@@ -7930,7 +7846,12 @@ function runDoctor(workspaceRoot, options = {}) {
|
|
|
7930
7846
|
return;
|
|
7931
7847
|
}
|
|
7932
7848
|
for (const finding of findings) {
|
|
7933
|
-
if (finding.ok) {
|
|
7849
|
+
if (finding.ok && finding.warning === true) {
|
|
7850
|
+
logger.warn(`${finding.check} \u2014 ${finding.detail ?? ""}`);
|
|
7851
|
+
if (finding.remedy) {
|
|
7852
|
+
logger.info(` to change it: ${finding.remedy}`);
|
|
7853
|
+
}
|
|
7854
|
+
} else if (finding.ok) {
|
|
7934
7855
|
logger.success(finding.check);
|
|
7935
7856
|
} else {
|
|
7936
7857
|
logger.error(`${finding.check} \u2014 ${finding.detail ?? "failed"}`);
|