@mnci/cli 4.3.3 → 4.5.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 CHANGED
@@ -34,7 +34,7 @@ plays (`.use-case`, `.client`, `.repository`, `.algorithm`, `.validator`,
34
34
 
35
35
  ```
36
36
  src/
37
- cli.handler.ts the CLI transport — decodes argv, calls one use case
37
+ main.ts the CLI transport — decodes argv, calls one use case
38
38
  workspace-overlay/ the config files mnci owns and rewrites
39
39
  workspace-creation/ mnci new, and the interactive wizard
40
40
  workspace-upgrade/ mnci upgrade
@@ -49,9 +49,14 @@ src/
49
49
  cli-version/ the update check
50
50
  ```
51
51
 
52
- The dependency graph is acyclic and flows one way: `cli.handler` → the command
52
+ The dependency graph is acyclic and flows one way: `main` → the command
53
53
  slices → the infrastructure slices → `file-system` as a leaf. That is checked,
54
- not assumed.
54
+ not assumed — and now enforced: the root `eslint.config.mjs` turns on
55
+ `@mnci/eslint-config`'s `verticalSlices` rules for `packages/cli/src`, so a
56
+ deep sibling import, a file without a role suffix, a nested subfeature or a
57
+ cycle between slices fails `npm run lint` rather than needing someone to
58
+ re-run the checks by hand. It is scoped to this package deliberately; the
59
+ config file says which packages were measured and why they are excluded.
55
60
 
56
61
  ### Exceptions, and when they go away
57
62
 
package/dist/cli.js CHANGED
@@ -28,13 +28,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  ));
29
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
30
 
31
- // src/cli.handler.ts
32
- var cli_handler_exports = {};
33
- __export(cli_handler_exports, {
31
+ // src/main.ts
32
+ var main_exports = {};
33
+ __export(main_exports, {
34
34
  buildProgram: () => buildProgram,
35
35
  main: () => main
36
36
  });
37
- module.exports = __toCommonJS(cli_handler_exports);
37
+ module.exports = __toCommonJS(main_exports);
38
38
  var import_commander = require("commander");
39
39
 
40
40
  // src/project-scaffolding/add-project.use-case.ts
@@ -415,6 +415,14 @@ function withSharedGlobals(nxJson) {
415
415
  namedInputs: { ...namedInputs, sharedGlobals: [...existing, ...missing] }
416
416
  };
417
417
  }
418
+ var PYTHON_GRAPH_PLUGIN = "@mnci/nx-python-pip/graph";
419
+ function withPythonGraphPlugin(nxJson, pluginInstalled) {
420
+ const plugins = nxJson.plugins ?? [];
421
+ if (!pluginInstalled || plugins.some((entry) => pluginName(entry) === PYTHON_GRAPH_PLUGIN)) {
422
+ return nxJson.plugins === void 0 ? nxJson : { ...nxJson, plugins };
423
+ }
424
+ return { ...nxJson, plugins: [...plugins, PYTHON_GRAPH_PLUGIN] };
425
+ }
418
426
  function withReleaseConfig(nxJson, ci) {
419
427
  return { ...nxJson, defaultBase: "main", release: releaseConfig(ci) };
420
428
  }
@@ -869,8 +877,19 @@ var SHALLOW_CLONE_GUARD = `node -e "const r=require('node:child_process').spawnS
869
877
  function releaseGuard(pythonPublishEnv, nugetPublishEnv) {
870
878
  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
879
  }
872
- function pythonPublishEnvFragment(pythonPublishUrl2) {
873
- return pythonPublishUrl2 ? `if(hasPython){env.TWINE_REPOSITORY_URL='${pythonPublishUrl2}';env.TWINE_USERNAME='AzureArtifacts';env.TWINE_PASSWORD=Buffer.from(process.env.PAT,'base64').toString()}` : "";
880
+ var PYPI_TOKEN_VARIABLE = "PYPI_TOKEN";
881
+ function pypiTokenEnvLine(registryKind, variableReference, indent) {
882
+ return registryKind === "npm" ? `
883
+ ${indent}${PYPI_TOKEN_VARIABLE}: ${variableReference(PYPI_TOKEN_VARIABLE)}` : "";
884
+ }
885
+ function pythonPublishEnvFragment(pythonPublishUrl2, registryKind) {
886
+ if (pythonPublishUrl2 !== void 0) {
887
+ return `if(hasPython){env.TWINE_REPOSITORY_URL='${pythonPublishUrl2}';env.TWINE_USERNAME='AzureArtifacts';env.TWINE_PASSWORD=Buffer.from(process.env.PAT,'base64').toString()}`;
888
+ }
889
+ if (registryKind !== "npm") {
890
+ return "";
891
+ }
892
+ 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
893
  }
875
894
  function nugetPublishEnvFragment(nugetFeedUrl2) {
876
895
  return nugetFeedUrl2 ? "if(hasCsharp){env.NUGET_PAT=Buffer.from(process.env.PAT,'base64').toString()}" : "";
@@ -1148,11 +1167,11 @@ steps:
1148
1167
  # it whenever more than one package is releasable. The guard below fails
1149
1168
  # the run rather than under-bumping silently: clear the variable back to
1150
1169
  # '' once the override is no longer needed.
1151
- - script: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl2), nugetPublishEnvFragment(nugetFeedUrl2))}
1170
+ - script: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl2, registryKind), nugetPublishEnvFragment(nugetFeedUrl2))}
1152
1171
  displayName: Release \u2014 version, tag and publish (npm + Python + C#)
1153
1172
  condition: ${onMain}
1154
1173
  env:
1155
- ${npmAuthName}: ${npmAuthValue}
1174
+ ${npmAuthName}: ${npmAuthValue}${pypiTokenEnvLine(registryKind, (name) => `$(${name})`, " ".repeat(6))}
1156
1175
  RELEASE_SPECIFIER: $(RELEASE_SPECIFIER)
1157
1176
 
1158
1177
  # nx release's own git push (release.git.push) is deliberately left off: it
@@ -1381,11 +1400,11 @@ jobs:
1381
1400
  # which is why 'nx release' pushes the tag itself here \u2014 see
1382
1401
  # releaseConfig's remarks for why that is safe on this Nx version and
1383
1402
  # why every other provider combination keeps the explicit push step below.` : ""}
1384
- - run: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl2), nugetPublishEnvFragment(nugetFeedUrl2))}
1403
+ - run: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl2, registryKind), nugetPublishEnvFragment(nugetFeedUrl2))}
1385
1404
  name: Release \u2014 version, tag${githubReleases ? ", publish and GitHub Release" : " and publish"} (npm + Python + C#)
1386
1405
  if: \${{ ${onMain} }}
1387
1406
  env:
1388
- ${npmAuthName}: ${npmAuthValue}
1407
+ ${npmAuthName}: ${npmAuthValue}${pypiTokenEnvLine(registryKind, (name) => `\${{ secrets.${name} }}`, " ".repeat(10))}
1389
1408
  RELEASE_SPECIFIER: \${{ vars.RELEASE_SPECIFIER }}${githubReleases ? `
1390
1409
  GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}` : ""}
1391
1410
  ${githubReleases ? "" : `
@@ -1498,7 +1517,13 @@ function applyOverlay(workspaceRoot, options, onProgress = () => {
1498
1517
  };
1499
1518
  const sync = { ...nxJson.sync, ...SYNC_CONFIG };
1500
1519
  const mnci = { ...nxJson.mnci, ...mnciConfig(options) };
1501
- const patched = withSharedGlobals(withEslintPlugin(withReleaseConfig(nxJson, options.ci)));
1520
+ const rootManifest = readJson((0, import_node_path2.join)(workspaceRoot, "package.json"));
1521
+ const hasPythonPlugin = (rootManifest.devDependencies ?? {})["@mnci/nx-python-pip"] !== void 0 || (rootManifest.dependencies ?? {})["@mnci/nx-python-pip"] !== void 0;
1522
+ const withRelease = withReleaseConfig(nxJson, options.ci);
1523
+ const patched = withPythonGraphPlugin(
1524
+ withSharedGlobals(withEslintPlugin(withRelease)),
1525
+ hasPythonPlugin
1526
+ );
1502
1527
  onProgress("nx.json \u2014 release, sync, generators, shared inputs, mnci block");
1503
1528
  writeFileEnsured(nxJsonPath, toJson({ ...patched, generators, sync, mnci }));
1504
1529
  onProgress("package.json \u2014 root scripts, TS compilers, ESLint toolchain, overrides");
@@ -1892,14 +1917,22 @@ function pascalScope(scope) {
1892
1917
  var CSHARP_VERSION_ACTIONS_PATH = "tools/csharp-version-actions.cjs";
1893
1918
  var CSHARP_VERSION_ACTIONS = `'use strict'
1894
1919
 
1895
- const { join } = require('node:path')
1920
+ const { posix } = require('node:path')
1896
1921
  const { VersionActions } = require('nx/release')
1897
1922
 
1898
1923
  const VERSION_TAG = /<Version>([^<]*)<\\/Version>/
1899
1924
 
1925
+ /*
1926
+ * posix.join, never plain join. An Nx Tree path is workspace-relative and
1927
+ * forward-slashed on EVERY platform, while join emits a backslashed path on
1928
+ * Windows. This value is handed back to nx release for changelog and manifest
1929
+ * bookkeeping, and interpolated into the user-facing error below, so it was
1930
+ * wrong on every Windows machine. The same bug was already fixed in the
1931
+ * Python and Flutter plugins' release actions; this third copy was missed.
1932
+ */
1900
1933
  function findCsproj (tree, root) {
1901
1934
  const name = tree.children(root).find((entry) => entry.endsWith('.csproj'))
1902
- return name ? join(root, name) : null
1935
+ return name ? posix.join(root, name) : null
1903
1936
  }
1904
1937
 
1905
1938
  /**
@@ -2836,18 +2869,35 @@ function pythonPipPluginSpec() {
2836
2869
  return process.env.MNCI2_PYTHON_PIP_SPEC ?? "@mnci/nx-python-pip";
2837
2870
  }
2838
2871
  function ensurePythonPipPlugin(workspaceRoot) {
2839
- if (hasPlugin(workspaceRoot, "@mnci/nx-python-pip")) {
2872
+ if (!hasPlugin(workspaceRoot, "@mnci/nx-python-pip")) {
2873
+ const spec = pythonPipPluginSpec();
2874
+ logger.step(`Installing the Python toolchain plugin (${spec})`);
2875
+ if (runShell("npm", ["install", "--save-dev", spec, "--no-audit", "--no-fund"], workspaceRoot) !== 0) {
2876
+ throw new Error("npm install of @mnci/nx-python-pip failed");
2877
+ }
2878
+ }
2879
+ registerPythonGraphPlugin(workspaceRoot);
2880
+ }
2881
+ function registerPythonGraphPlugin(workspaceRoot) {
2882
+ const nxJsonPath = (0, import_node_path12.join)(workspaceRoot, "nx.json");
2883
+ if (!fileExists(nxJsonPath)) {
2840
2884
  return;
2841
2885
  }
2842
- const spec = pythonPipPluginSpec();
2843
- logger.step(`Installing the Python toolchain plugin (${spec})`);
2844
- if (runShell("npm", ["install", "--save-dev", spec, "--no-audit", "--no-fund"], workspaceRoot) !== 0) {
2845
- throw new Error("npm install of @mnci/nx-python-pip failed");
2886
+ const nxJson = readJson(nxJsonPath);
2887
+ const patched = withPythonGraphPlugin(nxJson, true);
2888
+ if (patched.plugins?.includes(PYTHON_GRAPH_PLUGIN) !== true) {
2889
+ return;
2890
+ }
2891
+ if (nxJson.plugins?.includes(PYTHON_GRAPH_PLUGIN) === true) {
2892
+ return;
2846
2893
  }
2894
+ logger.step(`Registering ${PYTHON_GRAPH_PLUGIN} so nx affected sees Python dependencies`);
2895
+ writeFileEnsured(nxJsonPath, toJson(patched));
2847
2896
  }
2848
2897
  var PYTHON_REQUIREMENTS_DEV = `build
2849
2898
  twine
2850
2899
  ruff
2900
+ mypy
2851
2901
  pytest
2852
2902
  pip-audit
2853
2903
  `;
@@ -4993,7 +5043,7 @@ function readCliVersion(packageDirectory) {
4993
5043
  }
4994
5044
  }
4995
5045
 
4996
- // src/cli.handler.ts
5046
+ // src/main.ts
4997
5047
  function buildProgram(cliVersion) {
4998
5048
  const program = new import_commander.Command();
4999
5049
  program.name("mnci").description("MoNecromanCI \u2014 a thin CLI over official Nx plugins").version(cliVersion, "-v, --version", "display the version");