@mnci/cli 4.4.0 → 4.6.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/dist/cli.js +109 -18
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -313,8 +313,24 @@ function npmrcContent(registry, scope) {
|
|
|
313
313
|
if (registry.kind === "npm") {
|
|
314
314
|
return `; Publish authentication for the public npm registry.
|
|
315
315
|
;
|
|
316
|
-
; NODE_AUTH_TOKEN is exported by the generated CI's release step
|
|
317
|
-
;
|
|
316
|
+
; NODE_AUTH_TOKEN is exported by the generated CI's release step. Nothing needs
|
|
317
|
+
; it for day-to-day work here - installing, building and testing never
|
|
318
|
+
; authenticate.
|
|
319
|
+
;
|
|
320
|
+
; It is NOT harmless the moment you DO want to authenticate from this directory,
|
|
321
|
+
; which an earlier version of this comment claimed. A project .npmrc wins over
|
|
322
|
+
; your user one, so with NODE_AUTH_TOKEN unset this line hands npm an empty
|
|
323
|
+
; token and overrides 'npm login': inside this repo 'npm whoami' fails with a
|
|
324
|
+
; 401 and any write is refused. The refusal is easy to misread, because for a
|
|
325
|
+
; scoped package the registry answers a write you are not allowed to make with
|
|
326
|
+
;
|
|
327
|
+
; npm error 404 Not Found - PUT https://registry.npmjs.org/@scope%2fthing
|
|
328
|
+
;
|
|
329
|
+
; and everything above it is npm listing what it intended to send, so a long
|
|
330
|
+
; healthy-looking run still changes nothing.
|
|
331
|
+
;
|
|
332
|
+
; To publish or deprecate by hand, run it from outside this directory, or export
|
|
333
|
+
; a real token first.
|
|
318
334
|
//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}
|
|
319
335
|
|
|
320
336
|
; There is deliberately NO '${scope}:registry' line here. npmjs.org is already
|
|
@@ -415,6 +431,14 @@ function withSharedGlobals(nxJson) {
|
|
|
415
431
|
namedInputs: { ...namedInputs, sharedGlobals: [...existing, ...missing] }
|
|
416
432
|
};
|
|
417
433
|
}
|
|
434
|
+
var PYTHON_GRAPH_PLUGIN = "@mnci/nx-python-pip/graph";
|
|
435
|
+
function withPythonGraphPlugin(nxJson, pluginInstalled) {
|
|
436
|
+
const plugins = nxJson.plugins ?? [];
|
|
437
|
+
if (!pluginInstalled || plugins.some((entry) => pluginName(entry) === PYTHON_GRAPH_PLUGIN)) {
|
|
438
|
+
return nxJson.plugins === void 0 ? nxJson : { ...nxJson, plugins };
|
|
439
|
+
}
|
|
440
|
+
return { ...nxJson, plugins: [...plugins, PYTHON_GRAPH_PLUGIN] };
|
|
441
|
+
}
|
|
418
442
|
function withReleaseConfig(nxJson, ci) {
|
|
419
443
|
return { ...nxJson, defaultBase: "main", release: releaseConfig(ci) };
|
|
420
444
|
}
|
|
@@ -865,12 +889,27 @@ var NPM_AUDIT_STEP = `node -e "const cp=require('node:child_process');const r=cp
|
|
|
865
889
|
var VERIFY_TARGETS = "lint,typecheck,test,build";
|
|
866
890
|
var AFFECTED_OR_ALL_GUARD = `node -e "const cp=require('node:child_process');const T='${VERIFY_TARGETS}';const all=()=>process.exit(cp.spawnSync('npx nx run-many -t '+T,{stdio:'inherit',shell:true}).status ?? 1);const ref=process.env.GITHUB_BASE_REF||process.env.SYSTEM_PULLREQUEST_TARGETBRANCH||'';if(!ref){console.log('Not a pull request - verifying EVERY project.');all()}const target=ref.replace('refs/heads/','');const mergeBase=r=>{const o=cp.spawnSync('git',['merge-base',r,'HEAD'],{encoding:'utf8'});return o.status===0?o.stdout.trim():''};let base=mergeBase('origin/'+target);if(!base){console.log('No origin/'+target+' ref - fetching it to resolve a merge-base.');cp.spawnSync('git',['fetch','--no-tags','origin',target],{stdio:'inherit'});base=mergeBase('FETCH_HEAD')}if(!base){console.log('Could not resolve a merge-base with '+target+' - verifying EVERY project.');all()}console.log('Pull request against '+target+' - verifying projects affected since '+base);process.exit(cp.spawnSync('npx nx affected -t '+T+' --base='+base,{stdio:'inherit',shell:true}).status ?? 1)"`;
|
|
867
891
|
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)"`;
|
|
892
|
+
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())"`;
|
|
893
|
+
function npmAuthPreflight(registryKind) {
|
|
894
|
+
return registryKind === "npm" ? NPM_AUTH_PREFLIGHT : "";
|
|
895
|
+
}
|
|
868
896
|
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)}"`;
|
|
869
897
|
function releaseGuard(pythonPublishEnv, nugetPublishEnv) {
|
|
870
898
|
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 hasNpm=npmCount>0;const hasPython=pythonCount>0;const hasCsharp=csharpCount>0;const hasDart=dartCount>0;if(!hasNpm&&!hasPython&&!hasCsharp&&!hasDart){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;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)"`;
|
|
871
899
|
}
|
|
872
|
-
|
|
873
|
-
|
|
900
|
+
var PYPI_TOKEN_VARIABLE = "PYPI_TOKEN";
|
|
901
|
+
function pypiTokenEnvLine(registryKind, variableReference, indent) {
|
|
902
|
+
return registryKind === "npm" ? `
|
|
903
|
+
${indent}${PYPI_TOKEN_VARIABLE}: ${variableReference(PYPI_TOKEN_VARIABLE)}` : "";
|
|
904
|
+
}
|
|
905
|
+
function pythonPublishEnvFragment(pythonPublishUrl2, registryKind) {
|
|
906
|
+
if (pythonPublishUrl2 !== void 0) {
|
|
907
|
+
return `if(hasPython){env.TWINE_REPOSITORY_URL='${pythonPublishUrl2}';env.TWINE_USERNAME='AzureArtifacts';env.TWINE_PASSWORD=Buffer.from(process.env.PAT,'base64').toString()}`;
|
|
908
|
+
}
|
|
909
|
+
if (registryKind !== "npm") {
|
|
910
|
+
return "";
|
|
911
|
+
}
|
|
912
|
+
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'}`;
|
|
874
913
|
}
|
|
875
914
|
function nugetPublishEnvFragment(nugetFeedUrl2) {
|
|
876
915
|
return nugetFeedUrl2 ? "if(hasCsharp){env.NUGET_PAT=Buffer.from(process.env.PAT,'base64').toString()}" : "";
|
|
@@ -1129,7 +1168,17 @@ steps:
|
|
|
1129
1168
|
- script: ${SHALLOW_CLONE_GUARD}
|
|
1130
1169
|
displayName: Verify this is a full checkout (nx release needs the real tag history)
|
|
1131
1170
|
condition: ${onMain}
|
|
1132
|
-
|
|
1171
|
+
${npmAuthPreflight(registryKind) ? `
|
|
1172
|
+
# Prove npm will accept us BEFORE anything irreversible happens. nx release
|
|
1173
|
+
# tags first and publishes last, so a rejected token leaves a version tagged
|
|
1174
|
+
# with nothing on the registry - and that number is then skipped forever,
|
|
1175
|
+
# because the next run resolves the current version from the newest tag.
|
|
1176
|
+
- script: ${npmAuthPreflight(registryKind)}
|
|
1177
|
+
displayName: Preflight \u2014 npm must accept the token before anything is tagged
|
|
1178
|
+
condition: ${onMain}
|
|
1179
|
+
env:
|
|
1180
|
+
NODE_AUTH_TOKEN: $(NPM_TOKEN)
|
|
1181
|
+
` : ""}
|
|
1133
1182
|
# Version + tag + publish, in one release, for npm (packages/*), Python
|
|
1134
1183
|
# (python-packages/*) AND C# (packages/*/*.csproj) \u2014 conventional commits,
|
|
1135
1184
|
# tag-only push. Portable guard: nx release errors on an empty scope, so
|
|
@@ -1148,11 +1197,11 @@ steps:
|
|
|
1148
1197
|
# it whenever more than one package is releasable. The guard below fails
|
|
1149
1198
|
# the run rather than under-bumping silently: clear the variable back to
|
|
1150
1199
|
# '' once the override is no longer needed.
|
|
1151
|
-
- script: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl2), nugetPublishEnvFragment(nugetFeedUrl2))}
|
|
1200
|
+
- script: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl2, registryKind), nugetPublishEnvFragment(nugetFeedUrl2))}
|
|
1152
1201
|
displayName: Release \u2014 version, tag and publish (npm + Python + C#)
|
|
1153
1202
|
condition: ${onMain}
|
|
1154
1203
|
env:
|
|
1155
|
-
${npmAuthName}: ${npmAuthValue}
|
|
1204
|
+
${npmAuthName}: ${npmAuthValue}${pypiTokenEnvLine(registryKind, (name) => `$(${name})`, " ".repeat(6))}
|
|
1156
1205
|
RELEASE_SPECIFIER: $(RELEASE_SPECIFIER)
|
|
1157
1206
|
|
|
1158
1207
|
# nx release's own git push (release.git.push) is deliberately left off: it
|
|
@@ -1355,7 +1404,18 @@ jobs:
|
|
|
1355
1404
|
- run: ${SHALLOW_CLONE_GUARD}
|
|
1356
1405
|
name: Verify this is a full checkout (nx release needs the real tag history)
|
|
1357
1406
|
if: \${{ ${onMain} }}
|
|
1358
|
-
|
|
1407
|
+
${npmAuthPreflight(registryKind) ? `
|
|
1408
|
+
# Prove npm will accept us BEFORE anything irreversible happens. nx
|
|
1409
|
+
# release tags first and publishes last, so a rejected token leaves a
|
|
1410
|
+
# version tagged with nothing on the registry - and that number is then
|
|
1411
|
+
# skipped forever, because the next run resolves the current version
|
|
1412
|
+
# from the newest tag.
|
|
1413
|
+
- run: ${npmAuthPreflight(registryKind)}
|
|
1414
|
+
name: Preflight \u2014 npm must accept the token before anything is tagged
|
|
1415
|
+
if: \${{ ${onMain} }}
|
|
1416
|
+
env:
|
|
1417
|
+
NODE_AUTH_TOKEN: \${{ secrets.NPM_TOKEN }}
|
|
1418
|
+
` : ""}
|
|
1359
1419
|
# Version + tag + publish, in one release, for npm (packages/*), Python
|
|
1360
1420
|
# (python-packages/*) AND C# (packages/*/*.csproj) \u2014 conventional
|
|
1361
1421
|
# commits, tag-only push. Portable guard: nx release errors on an empty
|
|
@@ -1381,11 +1441,11 @@ jobs:
|
|
|
1381
1441
|
# which is why 'nx release' pushes the tag itself here \u2014 see
|
|
1382
1442
|
# releaseConfig's remarks for why that is safe on this Nx version and
|
|
1383
1443
|
# why every other provider combination keeps the explicit push step below.` : ""}
|
|
1384
|
-
- run: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl2), nugetPublishEnvFragment(nugetFeedUrl2))}
|
|
1444
|
+
- run: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl2, registryKind), nugetPublishEnvFragment(nugetFeedUrl2))}
|
|
1385
1445
|
name: Release \u2014 version, tag${githubReleases ? ", publish and GitHub Release" : " and publish"} (npm + Python + C#)
|
|
1386
1446
|
if: \${{ ${onMain} }}
|
|
1387
1447
|
env:
|
|
1388
|
-
${npmAuthName}: ${npmAuthValue}
|
|
1448
|
+
${npmAuthName}: ${npmAuthValue}${pypiTokenEnvLine(registryKind, (name) => `\${{ secrets.${name} }}`, " ".repeat(10))}
|
|
1389
1449
|
RELEASE_SPECIFIER: \${{ vars.RELEASE_SPECIFIER }}${githubReleases ? `
|
|
1390
1450
|
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}` : ""}
|
|
1391
1451
|
${githubReleases ? "" : `
|
|
@@ -1498,7 +1558,13 @@ function applyOverlay(workspaceRoot, options, onProgress = () => {
|
|
|
1498
1558
|
};
|
|
1499
1559
|
const sync = { ...nxJson.sync, ...SYNC_CONFIG };
|
|
1500
1560
|
const mnci = { ...nxJson.mnci, ...mnciConfig(options) };
|
|
1501
|
-
const
|
|
1561
|
+
const rootManifest = readJson((0, import_node_path2.join)(workspaceRoot, "package.json"));
|
|
1562
|
+
const hasPythonPlugin = (rootManifest.devDependencies ?? {})["@mnci/nx-python-pip"] !== void 0 || (rootManifest.dependencies ?? {})["@mnci/nx-python-pip"] !== void 0;
|
|
1563
|
+
const withRelease = withReleaseConfig(nxJson, options.ci);
|
|
1564
|
+
const patched = withPythonGraphPlugin(
|
|
1565
|
+
withSharedGlobals(withEslintPlugin(withRelease)),
|
|
1566
|
+
hasPythonPlugin
|
|
1567
|
+
);
|
|
1502
1568
|
onProgress("nx.json \u2014 release, sync, generators, shared inputs, mnci block");
|
|
1503
1569
|
writeFileEnsured(nxJsonPath, toJson({ ...patched, generators, sync, mnci }));
|
|
1504
1570
|
onProgress("package.json \u2014 root scripts, TS compilers, ESLint toolchain, overrides");
|
|
@@ -1892,14 +1958,22 @@ function pascalScope(scope) {
|
|
|
1892
1958
|
var CSHARP_VERSION_ACTIONS_PATH = "tools/csharp-version-actions.cjs";
|
|
1893
1959
|
var CSHARP_VERSION_ACTIONS = `'use strict'
|
|
1894
1960
|
|
|
1895
|
-
const {
|
|
1961
|
+
const { posix } = require('node:path')
|
|
1896
1962
|
const { VersionActions } = require('nx/release')
|
|
1897
1963
|
|
|
1898
1964
|
const VERSION_TAG = /<Version>([^<]*)<\\/Version>/
|
|
1899
1965
|
|
|
1966
|
+
/*
|
|
1967
|
+
* posix.join, never plain join. An Nx Tree path is workspace-relative and
|
|
1968
|
+
* forward-slashed on EVERY platform, while join emits a backslashed path on
|
|
1969
|
+
* Windows. This value is handed back to nx release for changelog and manifest
|
|
1970
|
+
* bookkeeping, and interpolated into the user-facing error below, so it was
|
|
1971
|
+
* wrong on every Windows machine. The same bug was already fixed in the
|
|
1972
|
+
* Python and Flutter plugins' release actions; this third copy was missed.
|
|
1973
|
+
*/
|
|
1900
1974
|
function findCsproj (tree, root) {
|
|
1901
1975
|
const name = tree.children(root).find((entry) => entry.endsWith('.csproj'))
|
|
1902
|
-
return name ? join(root, name) : null
|
|
1976
|
+
return name ? posix.join(root, name) : null
|
|
1903
1977
|
}
|
|
1904
1978
|
|
|
1905
1979
|
/**
|
|
@@ -2836,18 +2910,35 @@ function pythonPipPluginSpec() {
|
|
|
2836
2910
|
return process.env.MNCI2_PYTHON_PIP_SPEC ?? "@mnci/nx-python-pip";
|
|
2837
2911
|
}
|
|
2838
2912
|
function ensurePythonPipPlugin(workspaceRoot) {
|
|
2839
|
-
if (hasPlugin(workspaceRoot, "@mnci/nx-python-pip")) {
|
|
2913
|
+
if (!hasPlugin(workspaceRoot, "@mnci/nx-python-pip")) {
|
|
2914
|
+
const spec = pythonPipPluginSpec();
|
|
2915
|
+
logger.step(`Installing the Python toolchain plugin (${spec})`);
|
|
2916
|
+
if (runShell("npm", ["install", "--save-dev", spec, "--no-audit", "--no-fund"], workspaceRoot) !== 0) {
|
|
2917
|
+
throw new Error("npm install of @mnci/nx-python-pip failed");
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
registerPythonGraphPlugin(workspaceRoot);
|
|
2921
|
+
}
|
|
2922
|
+
function registerPythonGraphPlugin(workspaceRoot) {
|
|
2923
|
+
const nxJsonPath = (0, import_node_path12.join)(workspaceRoot, "nx.json");
|
|
2924
|
+
if (!fileExists(nxJsonPath)) {
|
|
2840
2925
|
return;
|
|
2841
2926
|
}
|
|
2842
|
-
const
|
|
2843
|
-
|
|
2844
|
-
if (
|
|
2845
|
-
|
|
2927
|
+
const nxJson = readJson(nxJsonPath);
|
|
2928
|
+
const patched = withPythonGraphPlugin(nxJson, true);
|
|
2929
|
+
if (patched.plugins?.includes(PYTHON_GRAPH_PLUGIN) !== true) {
|
|
2930
|
+
return;
|
|
2931
|
+
}
|
|
2932
|
+
if (nxJson.plugins?.includes(PYTHON_GRAPH_PLUGIN) === true) {
|
|
2933
|
+
return;
|
|
2846
2934
|
}
|
|
2935
|
+
logger.step(`Registering ${PYTHON_GRAPH_PLUGIN} so nx affected sees Python dependencies`);
|
|
2936
|
+
writeFileEnsured(nxJsonPath, toJson(patched));
|
|
2847
2937
|
}
|
|
2848
2938
|
var PYTHON_REQUIREMENTS_DEV = `build
|
|
2849
2939
|
twine
|
|
2850
2940
|
ruff
|
|
2941
|
+
mypy
|
|
2851
2942
|
pytest
|
|
2852
2943
|
pip-audit
|
|
2853
2944
|
`;
|