@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 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
  }