@mnci/cli 4.9.0 → 4.10.1
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 +54 -3
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1015,6 +1015,10 @@ var NPM_AUTH_PREFLIGHT = `node -e "const fs=require('node:fs'),cp=require('node:
|
|
|
1015
1015
|
function npmAuthPreflight(registryKind) {
|
|
1016
1016
|
return registryKind === "npm" ? NPM_AUTH_PREFLIGHT : "";
|
|
1017
1017
|
}
|
|
1018
|
+
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.')})()"`;
|
|
1019
|
+
function pypiReleasePreflight(registryKind) {
|
|
1020
|
+
return registryKind === "npm" ? PYPI_RELEASE_PREFLIGHT : "";
|
|
1021
|
+
}
|
|
1018
1022
|
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)}"`;
|
|
1019
1023
|
function releaseGuard(pythonPublishEnv, nugetPublishEnv) {
|
|
1020
1024
|
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)"`;
|
|
@@ -1317,6 +1321,16 @@ ${npmAuthPreflight(registryKind) ? `
|
|
|
1317
1321
|
condition: ${onMain}
|
|
1318
1322
|
env:
|
|
1319
1323
|
NODE_AUTH_TOKEN: $(NPM_TOKEN)
|
|
1324
|
+
` : ""}${pypiReleasePreflight(registryKind) ? `
|
|
1325
|
+
# PyPI cannot be asked whether a token is good - the only endpoint that
|
|
1326
|
+
# authenticates is the upload itself - so this checks the token's SHAPE and
|
|
1327
|
+
# names the projects this release would have to CREATE. Project creation is
|
|
1328
|
+
# rate limited per account, and a 429 arrives AFTER nx release has tagged.
|
|
1329
|
+
- script: ${pypiReleasePreflight(registryKind)}
|
|
1330
|
+
displayName: Preflight \u2014 name the PyPI projects this release would create
|
|
1331
|
+
condition: ${onMain}
|
|
1332
|
+
env:
|
|
1333
|
+
PYPI_TOKEN: $(PYPI_TOKEN)
|
|
1320
1334
|
` : ""}
|
|
1321
1335
|
# Version + tag + publish, in one release, for npm (packages/*), Python
|
|
1322
1336
|
# (python-packages/*) AND C# (packages/*/*.csproj) \u2014 conventional commits,
|
|
@@ -1554,6 +1568,17 @@ ${npmAuthPreflight(registryKind) ? `
|
|
|
1554
1568
|
if: \${{ ${onMain} }}
|
|
1555
1569
|
env:
|
|
1556
1570
|
NODE_AUTH_TOKEN: \${{ secrets.NPM_TOKEN }}
|
|
1571
|
+
` : ""}${pypiReleasePreflight(registryKind) ? `
|
|
1572
|
+
# PyPI cannot be asked whether a token is good - the only endpoint that
|
|
1573
|
+
# authenticates is the upload itself - so this checks the token's SHAPE
|
|
1574
|
+
# and names the projects this release would have to CREATE. Project
|
|
1575
|
+
# creation is rate limited per account, and a 429 arrives AFTER nx
|
|
1576
|
+
# release has tagged.
|
|
1577
|
+
- run: ${pypiReleasePreflight(registryKind)}
|
|
1578
|
+
name: Preflight \u2014 name the PyPI projects this release would create
|
|
1579
|
+
if: \${{ ${onMain} }}
|
|
1580
|
+
env:
|
|
1581
|
+
PYPI_TOKEN: \${{ secrets.PYPI_TOKEN }}
|
|
1557
1582
|
` : ""}
|
|
1558
1583
|
# Version + tag + publish, in one release, for npm (packages/*), Python
|
|
1559
1584
|
# (python-packages/*) AND C# (packages/*/*.csproj) \u2014 conventional
|
|
@@ -1661,7 +1686,6 @@ var NX_SCAFFOLDING_TO_REMOVE = [
|
|
|
1661
1686
|
// The AI-agent set, in full. See the remarks above: a partial delete leaves
|
|
1662
1687
|
// every `nx` command printing an "outdated configuration" nag.
|
|
1663
1688
|
".agents",
|
|
1664
|
-
".claude",
|
|
1665
1689
|
".codex",
|
|
1666
1690
|
".cursor",
|
|
1667
1691
|
".gemini",
|
|
@@ -1669,10 +1693,15 @@ var NX_SCAFFOLDING_TO_REMOVE = [
|
|
|
1669
1693
|
".github/agents",
|
|
1670
1694
|
".github/prompts",
|
|
1671
1695
|
".github/skills",
|
|
1672
|
-
"AGENTS.md",
|
|
1673
|
-
"CLAUDE.md",
|
|
1674
1696
|
"opencode.json"
|
|
1697
|
+
// `.claude`, `AGENTS.md` and `CLAUDE.md` are NOT here. See
|
|
1698
|
+
// {@link NX_AUTHORED_AGENT_FILES} and {@link removeNxAuthoredAgentFiles}:
|
|
1699
|
+
// all three are paths a user writes by hand, so they are removed by what
|
|
1700
|
+
// they CONTAIN rather than by where they are.
|
|
1675
1701
|
];
|
|
1702
|
+
var NX_AGENT_RULES_MARKER = "<!-- nx configuration start-->";
|
|
1703
|
+
var NX_AUTHORED_AGENT_FILES = ["AGENTS.md", "CLAUDE.md"];
|
|
1704
|
+
var NX_CLAUDE_SETTINGS = ".claude/settings.json";
|
|
1676
1705
|
function withoutKey(record, key) {
|
|
1677
1706
|
return Object.fromEntries(Object.entries(record).filter(([name]) => name !== key));
|
|
1678
1707
|
}
|
|
@@ -1707,8 +1736,30 @@ function removeNxScaffolding(workspaceRoot) {
|
|
|
1707
1736
|
for (const entry of NX_SCAFFOLDING_TO_REMOVE) {
|
|
1708
1737
|
(0, import_node_fs2.rmSync)((0, import_node_path2.join)(workspaceRoot, entry), { recursive: true, force: true });
|
|
1709
1738
|
}
|
|
1739
|
+
removeNxAuthoredAgentFiles(workspaceRoot);
|
|
1710
1740
|
removeProjectEslintConfigs(workspaceRoot);
|
|
1711
1741
|
}
|
|
1742
|
+
function removeNxAuthoredAgentFiles(workspaceRoot) {
|
|
1743
|
+
(0, import_node_fs2.rmSync)((0, import_node_path2.join)(workspaceRoot, NX_CLAUDE_SETTINGS), { force: true });
|
|
1744
|
+
for (const name of NX_AUTHORED_AGENT_FILES) {
|
|
1745
|
+
const path = (0, import_node_path2.join)(workspaceRoot, name);
|
|
1746
|
+
if (!(0, import_node_fs2.existsSync)(path)) continue;
|
|
1747
|
+
const kept = withoutNxAgentRules((0, import_node_fs2.readFileSync)(path, "utf8"));
|
|
1748
|
+
if (kept === void 0) continue;
|
|
1749
|
+
if (kept.trim() === "") (0, import_node_fs2.rmSync)(path, { force: true });
|
|
1750
|
+
else writeFileEnsured(path, kept);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
function withoutNxAgentRules(content) {
|
|
1754
|
+
const start = content.indexOf(NX_AGENT_RULES_MARKER);
|
|
1755
|
+
if (start === -1) return void 0;
|
|
1756
|
+
const endMarker = "<!-- nx configuration end";
|
|
1757
|
+
const end = content.indexOf(endMarker, start);
|
|
1758
|
+
if (end === -1) return void 0;
|
|
1759
|
+
const afterEnd = content.indexOf(">", end);
|
|
1760
|
+
const rest = afterEnd === -1 ? "" : content.slice(afterEnd + 1);
|
|
1761
|
+
return `${content.slice(0, start)}${rest}`.replace(/^\n+/, "");
|
|
1762
|
+
}
|
|
1712
1763
|
function removeIfPresent(path) {
|
|
1713
1764
|
(0, import_node_fs2.rmSync)(path, { recursive: true, force: true });
|
|
1714
1765
|
}
|