@mnci/cli 4.3.0 → 4.3.2

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
@@ -859,8 +859,9 @@ var NPM_AUDIT_STEP = `node -e "const cp=require('node:child_process');const r=cp
859
859
  var VERIFY_TARGETS = "lint,typecheck,test,build";
860
860
  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)"`;
861
861
  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)"`;
862
+ 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)}"`;
862
863
  function releaseGuard(pythonPublishEnv, nugetPublishEnv) {
863
- 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 hasNpm=npmCount>0;const hasPython=pythonCount>0;const hasCsharp=csharpCount>0;if(!hasNpm&&!hasPython&&!hasCsharp){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;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)"`;
864
+ 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)"`;
864
865
  }
865
866
  function pythonPublishEnvFragment(pythonPublishUrl2) {
866
867
  return pythonPublishUrl2 ? `if(hasPython){env.TWINE_REPOSITORY_URL='${pythonPublishUrl2}';env.TWINE_USERNAME='AzureArtifacts';env.TWINE_PASSWORD=Buffer.from(process.env.PAT,'base64').toString()}` : "";
@@ -1112,6 +1113,17 @@ steps:
1112
1113
  displayName: Tag the run per app (type-name)
1113
1114
  condition: ${onMain}
1114
1115
 
1116
+ # nx release resolves each package's current version from its git tag, and
1117
+ # falls back to the (permanently stale, since git.commit is false) on-disk
1118
+ # version when a tag cannot be resolved \u2014 which happens whenever this
1119
+ # checkout lacks full history. This is currently guaranteed by fetchDepth 0
1120
+ # above plus the explicit fetch below, but neither is expressed as a
1121
+ # dependency anywhere else, so this makes it a hard, loud precondition
1122
+ # instead of a silently wrong version discovered after release.
1123
+ - script: ${SHALLOW_CLONE_GUARD}
1124
+ displayName: Verify this is a full checkout (nx release needs the real tag history)
1125
+ condition: ${onMain}
1126
+
1115
1127
  # Version + tag + publish, in one release, for npm (packages/*), Python
1116
1128
  # (python-packages/*) AND C# (packages/*/*.csproj) \u2014 conventional commits,
1117
1129
  # tag-only push. Portable guard: nx release errors on an empty scope, so
@@ -1326,6 +1338,18 @@ jobs:
1326
1338
  path: dist/drop
1327
1339
  if-no-files-found: ignore
1328
1340
 
1341
+ # nx release resolves each package's current version from its git tag,
1342
+ # and falls back to the (permanently stale, since git.commit is false)
1343
+ # on-disk version when a tag cannot be resolved \u2014 which happens
1344
+ # whenever this checkout lacks full history. This is currently
1345
+ # guaranteed by fetch-depth 0 above plus the explicit fetch below, but
1346
+ # neither is expressed as a dependency anywhere else, so this makes it
1347
+ # a hard, loud precondition instead of a silently wrong version
1348
+ # discovered after release.
1349
+ - run: ${SHALLOW_CLONE_GUARD}
1350
+ name: Verify this is a full checkout (nx release needs the real tag history)
1351
+ if: \${{ ${onMain} }}
1352
+
1329
1353
  # Version + tag + publish, in one release, for npm (packages/*), Python
1330
1354
  # (python-packages/*) AND C# (packages/*/*.csproj) \u2014 conventional
1331
1355
  # commits, tag-only push. Portable guard: nx release errors on an empty
@@ -1717,11 +1741,18 @@ var SOURCEMAP_PATH_TRANSFORM = [
1717
1741
  ].join("\n");
1718
1742
  var DECLARATION_SPECIFIER_PLUGIN_MARKER = "name: 'mnci-normalise-declaration-specifiers'";
1719
1743
  var DECLARATION_SPECIFIER_EXTENSION_MARKER = "bareRelativeSpecifier";
1744
+ var DECLARATION_SPECIFIER_DIRECTORY_MARKER = "resolveSpecifierSuffix";
1745
+ function hasDeclarationSpecifierPlugin(config) {
1746
+ return config.includes(DECLARATION_SPECIFIER_PLUGIN_MARKER);
1747
+ }
1748
+ function hasDirectoryAwareDeclarationSpecifiers(config) {
1749
+ return config.includes(DECLARATION_SPECIFIER_DIRECTORY_MARKER);
1750
+ }
1720
1751
  var DECLARATION_SPECIFIER_PLUGIN = [
1721
1752
  " {",
1722
1753
  " name: 'mnci-normalise-declaration-specifiers',",
1723
1754
  " writeBundle (outputOptions) {",
1724
- " const { readdirSync, readFileSync, writeFileSync } = require('node:fs')",
1755
+ " const { existsSync, readdirSync, readFileSync, writeFileSync } = require('node:fs')",
1725
1756
  " const { join } = require('node:path')",
1726
1757
  " const dir = outputOptions.dir ?? './dist'",
1727
1758
  " const stub = join(dir, 'index.d.ts')",
@@ -1749,17 +1780,31 @@ var DECLARATION_SPECIFIER_PLUGIN = [
1749
1780
  " }",
1750
1781
  " for (const entry of entries) {",
1751
1782
  " if (!entry.name.endsWith('.d.ts')) continue",
1752
- " const filePath = join(entry.parentPath ?? entry.path, entry.name)",
1783
+ " const from = entry.parentPath ?? entry.path",
1784
+ " const filePath = join(from, entry.name)",
1753
1785
  " let declaration",
1754
1786
  " try {",
1755
1787
  " declaration = readFileSync(filePath, 'utf8')",
1756
1788
  " } catch {",
1757
1789
  " continue",
1758
1790
  " }",
1791
+ " // A bare specifier may name a FILE or a DIRECTORY BARREL - resolved",
1792
+ " // against what rollup actually emitted next to this file, never",
1793
+ " // guessed. A directory needs /index.js, not .js (a file that was",
1794
+ " // never written); anything neither form matches is left alone rather",
1795
+ " // than rewritten to a specifier that cannot resolve.",
1796
+ ` const ${DECLARATION_SPECIFIER_DIRECTORY_MARKER} = (specifier) => {`,
1797
+ " if (hasExtension.test(specifier)) return null",
1798
+ " if (existsSync(join(from, specifier + '.d.ts'))) return specifier + '.js'",
1799
+ " if (existsSync(join(from, specifier, 'index.d.ts'))) return specifier + '/index.js'",
1800
+ " return null",
1801
+ " }",
1759
1802
  " const withExtensions = declaration.replace(",
1760
1803
  ` ${DECLARATION_SPECIFIER_EXTENSION_MARKER},`,
1761
- " (match, space, quote, specifier) =>",
1762
- " hasExtension.test(specifier) ? match : `from${space}${quote}${specifier}.js${quote}`,",
1804
+ " (match, space, quote, specifier) => {",
1805
+ ` const resolved = ${DECLARATION_SPECIFIER_DIRECTORY_MARKER}(specifier)`,
1806
+ " return resolved === null ? match : `from${space}${quote}${resolved}${quote}`",
1807
+ " },",
1763
1808
  " )",
1764
1809
  " if (withExtensions !== declaration) writeFileSync(filePath, withExtensions)",
1765
1810
  " }",
@@ -1818,7 +1863,7 @@ function findEnclosingBraceSpan(text, needle) {
1818
1863
  return void 0;
1819
1864
  }
1820
1865
  function withUpgradedDeclarationSpecifierPlugin(config) {
1821
- if (!config.includes(DECLARATION_SPECIFIER_PLUGIN_MARKER) || config.includes(DECLARATION_SPECIFIER_EXTENSION_MARKER)) {
1866
+ if (!config.includes(DECLARATION_SPECIFIER_PLUGIN_MARKER) || config.includes(DECLARATION_SPECIFIER_DIRECTORY_MARKER)) {
1822
1867
  return config;
1823
1868
  }
1824
1869
  const span = findEnclosingBraceSpan(config, DECLARATION_SPECIFIER_PLUGIN_MARKER);
@@ -4059,6 +4104,7 @@ function checkNpmrc(workspaceRoot, registry, scope) {
4059
4104
  function checkVersionActions(workspaceRoot) {
4060
4105
  const candidates = [
4061
4106
  ...(0, import_node_fs9.globSync)("packages/*/pubspec.yaml", { cwd: workspaceRoot }),
4107
+ ...(0, import_node_fs9.globSync)("packages/*/*.csproj", { cwd: workspaceRoot }),
4062
4108
  ...(0, import_node_fs9.globSync)("python-packages/*/pyproject.toml", { cwd: workspaceRoot })
4063
4109
  ].map((manifest) => toPosix2(manifest));
4064
4110
  return candidates.flatMap((manifest) => {
@@ -4187,6 +4233,44 @@ function checkRollupSourceMaps(workspaceRoot) {
4187
4233
  ];
4188
4234
  });
4189
4235
  }
4236
+ function checkDeclarationSpecifiers(workspaceRoot) {
4237
+ const configs = (0, import_node_fs9.globSync)(["packages/*/rollup.config.cjs", "libs/*/rollup.config.cjs"], {
4238
+ cwd: workspaceRoot
4239
+ }).map((config) => toPosix2(config));
4240
+ return configs.flatMap((relativePath) => {
4241
+ const configPath = (0, import_node_path15.join)(workspaceRoot, relativePath);
4242
+ if (!fileExists(configPath)) {
4243
+ return [];
4244
+ }
4245
+ const resolved = resolveRollupConfigText(configPath);
4246
+ if (!hasDeclarationSpecifierPlugin(resolved)) {
4247
+ return [
4248
+ {
4249
+ check: `${relativePath} normalises declaration specifiers`,
4250
+ ok: false,
4251
+ detail: "no mnci-normalise-declaration-specifiers plugin \u2014 the emitted .d.ts files keep the extensionless imports tsconfig.lib.json allows, which a nodenext consumer cannot resolve",
4252
+ // Deliberately NOT `mnci upgrade`, which would send the user in a
4253
+ // circle: `upgradeDeclarationSpecifierPlugins` only rewrites a
4254
+ // plugin that is already there, and `repairDeclarationSpecifiers`
4255
+ // is anchored on the generator's own placeholder comment, which a
4256
+ // config in this state no longer has. Verified by running the
4257
+ // command against exactly this shape — it no-ops. Naming a command
4258
+ // that silently does nothing is the failure mode this file's own
4259
+ // source-map check already goes out of its way to avoid.
4260
+ remedy: "add the plugin to withNx's second argument by hand (copy it from a freshly `mnci add npm-lib`'d project) \u2014 `mnci upgrade` only updates a plugin that is already present, it cannot restore a missing one"
4261
+ }
4262
+ ];
4263
+ }
4264
+ return [
4265
+ {
4266
+ check: `${relativePath} resolves directory barrels in declarations`,
4267
+ ok: hasDirectoryAwareDeclarationSpecifiers(resolved),
4268
+ detail: "the plugin appends .js to every bare specifier, including ones naming a directory barrel, which needs /index.js \u2014 the named file is never emitted, so every export behind it publishes as `any` with nothing reporting it",
4269
+ remedy: "run `mnci upgrade`, which replaces the plugin body with the current one"
4270
+ }
4271
+ ];
4272
+ });
4273
+ }
4190
4274
  function collectFindings(workspaceRoot) {
4191
4275
  const nxJsonPath = (0, import_node_path15.join)(workspaceRoot, "nx.json");
4192
4276
  if (!fileExists(nxJsonPath)) {
@@ -4203,6 +4287,7 @@ function collectFindings(workspaceRoot) {
4203
4287
  checkNpmrc(workspaceRoot, nxJson.mnci?.registry, nxJson.mnci?.scope),
4204
4288
  checkNoRootRuntimeDependencies(workspaceRoot),
4205
4289
  ...checkRollupSourceMaps(workspaceRoot),
4290
+ ...checkDeclarationSpecifiers(workspaceRoot),
4206
4291
  ...checkVersionActions(workspaceRoot),
4207
4292
  ...checkTargetFilesExist(workspaceRoot),
4208
4293
  checkSync(workspaceRoot)