@mnci/cli 4.10.6 → 4.10.8

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
@@ -1371,8 +1371,21 @@ pipeline installs `golangci-lint` itself (see below).
1371
1371
  generators, in that order — `convert-to-one-mod` refuses once `go.work`
1372
1372
  lists any module, so it has to happen before the first Go project exists.
1373
1373
  Every Go project then shares that module and imports its siblings as
1374
- `<module>/libs/<name>`, with no per-project manifests and no `replace`
1375
- directives.
1374
+ `<module>/libs/<name>/<slice>`, with no per-project manifests and no
1375
+ `replace` directives.
1376
+ - **A Go library is a capability of slice packages.** The plugin's library
1377
+ generator writes `<name>.go` at the project root, which makes the root
1378
+ package the whole library. mnci replaces it with a root `doc.go` and moves
1379
+ the sample into one starter slice package (`libs/markdown-workspace/
1380
+ markdownworkspace/markdown_workspace_use_case.go`), the Go spelling of the
1381
+ `<kebab>.<role>.ts` files the TypeScript scaffolds get. Rename the starter
1382
+ after the outcome it delivers and add one package per further outcome.
1383
+ - **`test` and `lint` reach every slice package.** The plugin's executors run
1384
+ `go test ./...` and `golangci-lint run ./...` from the project root
1385
+ (measured on `@nx-go/nx-go` 4.1.1), so mnci passes no package list. The e2e
1386
+ plants a failing test and a lint finding in a nested package and asserts
1387
+ both fail the project target, so a plugin change that stopped recursing
1388
+ would surface there rather than as a silently green lib.
1376
1389
  - **The `go.work` multi-module layout was rejected deliberately.** Besides
1377
1390
  splitting dependencies across per-project manifests, it is brittle: a
1378
1391
  single stale `use` entry — a project directory removed by hand — makes
@@ -1407,9 +1420,12 @@ build` links statically, so the binary in the drop already contains
1407
1420
  - **CI** runs Go through the same `nx run-many -t lint,test,build` as
1408
1421
  everything else. Two guarded steps precede it: `go mod download` (so a
1409
1422
  network failure reads as a dependency failure rather than a confusing
1410
- build error), and a `golangci-lint` install via `go install` — no package
1411
- manager, no sudo, same command on every agent OS — whose `GOPATH/bin` is
1412
- then added to `PATH` for later steps. All three skip cleanly when the
1423
+ build error), and a `golangci-lint` install whose `GOPATH/bin` is then added
1424
+ to `PATH` for later steps. The linter is **pinned** (`GOLANGCI_LINT_VERSION`)
1425
+ and installed from its prebuilt release, verified against the release's
1426
+ SHA-256 checksums, in about a second; compiling it with `go install` took
1427
+ over a minute per run. Any download or verification failure falls back to
1428
+ `go install` at the same pinned version. All three skip cleanly when the
1413
1429
  workspace has no root `go.mod`, and the linter install also skips when the
1414
1430
  agent already provides it.
1415
1431
 
@@ -1473,7 +1489,7 @@ build agents, so the generated pipeline installs it itself (see below).
1473
1489
  under the agent's home directory: the SDK ships dozens of its own
1474
1490
  `pubspec.yaml` files, which inside the tree would pollute pub's resolution and
1475
1491
  give Nx thousands of extra files to glob. The SDK version is **pinned**
1476
- (unlike `golangci-lint`'s `@latest`) because it determines the Dart version,
1492
+ (as is `golangci-lint`'s) because it determines the Dart version,
1477
1493
  and pub workspaces need Dart 3.6+. Three guarded steps precede the build —
1478
1494
  install, add to `PATH`, and one root `flutter pub get` — and all three skip
1479
1495
  cleanly when the workspace has no root `pubspec.yaml`; the install also skips
package/dist/cli.js CHANGED
@@ -614,7 +614,7 @@ var ESLINT_PEER_OVERRIDES = {
614
614
  // both — and forcing those to v5 breaks them. So the blast radius is one
615
615
  // dependency edge per named parent, and a test asserts there is no top-level
616
616
  // entry.
617
- "nx": { "brace-expansion": "^5.0.9", "smol-toml": "^1.7.1" },
617
+ "nx": { "brace-expansion": "^5.0.9", "smol-toml": "^1.7.1", "axios": "^1.20.0" },
618
618
  "@nx/js": { "brace-expansion": "^5.0.9" },
619
619
  "@nx/eslint": { "brace-expansion": "^5.0.9" },
620
620
  "@nx/eslint-plugin": { "brace-expansion": "^5.0.9" },
@@ -857,7 +857,7 @@ function vscodeWorkspace(workspaceName, existingTasks, existingLaunch, existingS
857
857
  (configuration) => !String(configuration.name ?? "").startsWith(LAUNCH_CONFIG_PREFIX)
858
858
  );
859
859
  const settings = { ...existingSettings, ...vscodeSettings() };
860
- return JSON.stringify(
860
+ return toJson(
861
861
  {
862
862
  folders: [{ path: ".", name: workspaceName }],
863
863
  settings,
@@ -870,9 +870,7 @@ function vscodeWorkspace(workspaceName, existingTasks, existingLaunch, existingS
870
870
  version: existingLaunch?.version ?? "0.2.0",
871
871
  configurations: [...launchConfigurations(workspaceName), ...userConfigurations]
872
872
  }
873
- },
874
- null,
875
- 2
873
+ }
876
874
  );
877
875
  }
878
876
  var ROOT_SCRIPTS = {
@@ -1005,7 +1003,8 @@ var PYTHON_INSTALL_GUARD = `node -e "if(!require('node:fs').existsSync('requirem
1005
1003
  var PYTHON_WORKSPACE_INSTALL_GUARD = `node -e "const fs=require('node:fs'),path=require('node:path');const editableDirs=[...fs.globSync('apps/*/pyproject.toml'),...fs.globSync('python-packages/*/pyproject.toml'),...fs.globSync('libs/*/pyproject.toml')].map((p)=>path.dirname(p));const requirementsFiles=fs.globSync('apps/*/requirements.txt');if(editableDirs.length===0&&requirementsFiles.length===0){console.log('No Python projects - skipping.');process.exit(0)}const args=['-m','pip','install','--quiet',...editableDirs.flatMap((d)=>['-e',d]),...requirementsFiles.flatMap((f)=>['-r',f])];const py=process.platform==='win32'?'python':'python3';process.exit(require('node:child_process').spawnSync(py,args,{stdio:'inherit'}).status ?? 1)"`;
1006
1004
  var PIP_AUDIT_GUARD = `node -e "if(!require('node:fs').existsSync('requirements-dev.txt')){console.log('No Python projects - skipping.');process.exit(0)}const py=process.platform==='win32'?'python':'python3';require('node:child_process').spawnSync(py,['-m','pip_audit'],{stdio:'inherit'});process.exit(0)"`;
1007
1005
  var GO_MODULE_DOWNLOAD_GUARD = `node -e "if(!require('node:fs').existsSync('go.mod')){console.log('No Go projects - skipping.');process.exit(0)}process.exit(require('node:child_process').spawnSync('go',['mod','download'],{stdio:'inherit'}).status ?? 1)"`;
1008
- var GOLANGCI_LINT_INSTALL_GUARD = `node -e "const fs=require('node:fs'),cp=require('node:child_process');if(!fs.existsSync('go.mod')){console.log('No Go projects - skipping.');process.exit(0)}if(cp.spawnSync('golangci-lint',['--version'],{stdio:'ignore'}).status===0){console.log('golangci-lint already installed - skipping.');process.exit(0)}process.exit(cp.spawnSync('go',['install','github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest'],{stdio:'inherit'}).status ?? 1)"`;
1006
+ var GOLANGCI_LINT_VERSION = "2.14.0";
1007
+ var GOLANGCI_LINT_INSTALL_GUARD = `node -e "const fs=require('node:fs'),cp=require('node:child_process'),path=require('node:path'),os=require('node:os'),crypto=require('node:crypto');if(!fs.existsSync('go.mod')){console.log('No Go projects - skipping.');process.exit(0)}if(cp.spawnSync('golangci-lint',['--version'],{stdio:'ignore'}).status===0){console.log('golangci-lint already installed - skipping.');process.exit(0)}const v='${GOLANGCI_LINT_VERSION}';const fallback=reason=>{console.log('Prebuilt golangci-lint '+v+' unavailable ('+reason+') - building it with go install instead.');process.exit(cp.spawnSync('go',['install','github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v'+v],{stdio:'inherit'}).status ?? 1)};const plat={linux:'linux',darwin:'darwin',win32:'windows'}[process.platform],arch={x64:'amd64',arm64:'arm64'}[process.arch];if(!plat||!arch)fallback('no prebuilt release for '+process.platform+'/'+process.arch);const gopath=cp.spawnSync('go',['env','GOPATH'],{encoding:'utf8'});if(gopath.status!==0)fallback('go env GOPATH failed');const name='golangci-lint-'+v+'-'+plat+'-'+arch,file=name+(plat==='windows'?'.zip':'.tar.gz'),base='https://github.com/golangci/golangci-lint/releases/download/v'+v+'/';const get=async u=>{const r=await fetch(u,{signal:AbortSignal.timeout(60000)});if(!r.ok)throw new Error('HTTP '+r.status+' for '+u);return Buffer.from(await r.arrayBuffer())};(async()=>{` + String.raw`const sums=(await get(base+'golangci-lint-'+v+'-checksums.txt')).toString('utf8').split('\n').map(l=>l.trim().split(' ').filter(Boolean));` + `const entry=sums.find(p=>p[1]===file);if(!entry)throw new Error(file+' is not in the release checksums');const archive=await get(base+file);if(crypto.createHash('sha256').update(archive).digest('hex')!==entry[0])throw new Error('checksum mismatch for '+file);const tmp=fs.mkdtempSync(path.join(os.tmpdir(),'golangci-lint-')),archivePath=path.join(tmp,file);fs.writeFileSync(archivePath,archive);const tar=plat==='windows'?path.join(process.env.SystemRoot||'','System32','tar.exe'):'tar';const x=cp.spawnSync(tar,['-xf',archivePath,'-C',tmp],{stdio:'inherit'});if(x.status!==0)throw new Error('could not extract '+file);const exe='golangci-lint'+(plat==='windows'?'.exe':''),bin=path.join(gopath.stdout.trim(),'bin');fs.mkdirSync(bin,{recursive:true});fs.copyFileSync(path.join(tmp,name,exe),path.join(bin,exe));fs.chmodSync(path.join(bin,exe),0o755);fs.rmSync(tmp,{recursive:true,force:true});console.log('golangci-lint '+v+' installed from its checksum-verified prebuilt release into '+bin)})().catch(error=>fallback(error.message))"`;
1009
1008
  var GO_TOOL_PATH_PRELUDE = "const fs=require('node:fs'),cp=require('node:child_process');if(!fs.existsSync('go.mod')){console.log('No Go projects - skipping.');process.exit(0)}const r=cp.spawnSync('go',['env','GOPATH'],{encoding:'utf8'});if(r.status!==0){console.log('Could not resolve GOPATH - skipping.');process.exit(0)}const bin=require('node:path').join(r.stdout.trim(),'bin');";
1010
1009
  var GO_TOOL_PATH_AZURE = `node -e "${GO_TOOL_PATH_PRELUDE}console.log('##vso[task.prependpath]'+bin)"`;
1011
1010
  var GO_TOOL_PATH_GITHUB = `node -e "${GO_TOOL_PATH_PRELUDE}if(!process.env.GITHUB_PATH){console.log('Not running in GitHub Actions - skipping.');process.exit(0)}fs.appendFileSync(process.env.GITHUB_PATH,bin+${String.raw`'\n'`})"`;
@@ -1240,8 +1239,9 @@ ${npmAuthenticateStep} - script: npm ci
1240
1239
  displayName: Download Go module dependencies
1241
1240
 
1242
1241
  # golangci-lint is what the generated Go lint target actually runs (the
1243
- # plugin's own default is 'go fmt', which only reformats). 'go install'
1244
- # drops it in GOPATH/bin, which is not on PATH by default on a hosted
1242
+ # plugin's own default is 'go fmt', which only reformats). The pinned,
1243
+ # checksum-verified prebuilt release lands in GOPATH/bin (go install at the
1244
+ # same version is the fallback), which is not on PATH by default on a hosted
1245
1245
  # agent \u2014 so prepend it for every later step in the job.
1246
1246
  - script: ${GOLANGCI_LINT_INSTALL_GUARD}
1247
1247
  displayName: Install golangci-lint
@@ -1495,9 +1495,10 @@ jobs:
1495
1495
  name: Download Go module dependencies
1496
1496
 
1497
1497
  # golangci-lint is what the generated Go lint target actually runs (the
1498
- # plugin's own default is 'go fmt', which only reformats). 'go install'
1499
- # drops it in GOPATH/bin, which is not on PATH by default on a hosted
1500
- # runner \u2014 so publish it for every later step in the job.
1498
+ # plugin's own default is 'go fmt', which only reformats). The pinned,
1499
+ # checksum-verified prebuilt release lands in GOPATH/bin (go install at the
1500
+ # same version is the fallback), which is not on PATH by default on a
1501
+ # hosted runner \u2014 so publish it for every later step in the job.
1501
1502
  - run: ${GOLANGCI_LINT_INSTALL_GUARD}
1502
1503
  name: Install golangci-lint
1503
1504
 
@@ -1760,6 +1761,10 @@ function removeNxScaffolding(workspaceRoot) {
1760
1761
  }
1761
1762
  function removeNxAuthoredAgentFiles(workspaceRoot) {
1762
1763
  (0, import_node_fs2.rmSync)((0, import_node_path2.join)(workspaceRoot, NX_CLAUDE_SETTINGS), { force: true });
1764
+ const claudeDirectory = (0, import_node_path2.join)(workspaceRoot, (0, import_node_path2.dirname)(NX_CLAUDE_SETTINGS));
1765
+ if ((0, import_node_fs2.existsSync)(claudeDirectory) && (0, import_node_fs2.readdirSync)(claudeDirectory).length === 0) {
1766
+ (0, import_node_fs2.rmSync)(claudeDirectory, { recursive: true, force: true });
1767
+ }
1763
1768
  for (const name of NX_AUTHORED_AGENT_FILES) {
1764
1769
  const path = (0, import_node_path2.join)(workspaceRoot, name);
1765
1770
  if (!(0, import_node_fs2.existsSync)(path)) continue;
@@ -2638,6 +2643,65 @@ function goStartTarget(name) {
2638
2643
  options: { command: "go run .", cwd: `apps/${name}` }
2639
2644
  };
2640
2645
  }
2646
+ function goLibraryIdentifiers(projectName) {
2647
+ const words = projectName.split(/[^a-z0-9]+/i).filter((word) => word.length > 0);
2648
+ return {
2649
+ packageName: words.join("").toLowerCase(),
2650
+ functionName: words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(""),
2651
+ fileStem: words.join("_").toLowerCase()
2652
+ };
2653
+ }
2654
+ function reshapeGoLibraryScaffold(projectRoot, projectName) {
2655
+ const { packageName, functionName, fileStem } = goLibraryIdentifiers(projectName);
2656
+ (0, import_node_fs5.rmSync)((0, import_node_path6.join)(projectRoot, `${projectName}.go`), { force: true });
2657
+ (0, import_node_fs5.rmSync)((0, import_node_path6.join)(projectRoot, `${projectName}_test.go`), { force: true });
2658
+ const files = [
2659
+ [
2660
+ "doc.go",
2661
+ `// Package ${packageName} is the ${projectName} capability. Its code lives in the
2662
+ // slice packages below this directory, one package per outcome.
2663
+ package ${packageName}
2664
+ `
2665
+ ],
2666
+ [
2667
+ (0, import_node_path6.join)(packageName, "doc.go"),
2668
+ `// Package ${packageName} is the starter slice of ${projectName}: rename it after
2669
+ // the outcome it delivers, and add one package per further outcome.
2670
+ package ${packageName}
2671
+ `
2672
+ ],
2673
+ [
2674
+ (0, import_node_path6.join)(packageName, `${fileStem}_use_case.go`),
2675
+ `package ${packageName}
2676
+
2677
+ // ${functionName} is the generator's sample behaviour, kept so the slice builds and tests.
2678
+ func ${functionName}(name string) string {
2679
+ return "${functionName} " + name
2680
+ }
2681
+ `
2682
+ ],
2683
+ [
2684
+ (0, import_node_path6.join)(packageName, `${fileStem}_use_case_test.go`),
2685
+ `package ${packageName}
2686
+
2687
+ import "testing"
2688
+
2689
+ func Test${functionName}(t *testing.T) {
2690
+ if got := ${functionName}("works"); got != "${functionName} works" {
2691
+ t.Fatalf("got %q", got)
2692
+ }
2693
+ }
2694
+ `
2695
+ ]
2696
+ ];
2697
+ for (const [relativePath, content] of files) {
2698
+ const path = (0, import_node_path6.join)(projectRoot, relativePath);
2699
+ if (!fileExists(path)) {
2700
+ writeFileEnsured(path, content);
2701
+ }
2702
+ }
2703
+ return packageName;
2704
+ }
2641
2705
  function prepareGo(workspaceRoot) {
2642
2706
  ensureGo(workspaceRoot);
2643
2707
  warnIfNoGolangciLint(workspaceRoot);
@@ -2706,10 +2770,11 @@ function addGoLib(workspaceRoot, name) {
2706
2770
  test: goTestTarget(),
2707
2771
  lint: goLintTarget()
2708
2772
  });
2773
+ const slice = reshapeGoLibraryScaffold((0, import_node_path6.join)(workspaceRoot, "packages", name), name);
2709
2774
  registerProjectCommands(workspaceRoot, name, { build: false });
2710
2775
  const module2 = goModulePath(workspaceRoot);
2711
2776
  if (module2) {
2712
- logger.step(`Import it as ${module2}/packages/${name}`);
2777
+ logger.step(`Import its starter slice as ${module2}/packages/${name}/${slice}`);
2713
2778
  }
2714
2779
  }
2715
2780
  function addGoInternalLib(workspaceRoot, name) {
@@ -2729,10 +2794,11 @@ function addGoInternalLib(workspaceRoot, name) {
2729
2794
  test: goTestTarget(),
2730
2795
  lint: goLintTarget()
2731
2796
  });
2797
+ const slice = reshapeGoLibraryScaffold((0, import_node_path6.join)(workspaceRoot, "libs", name), name);
2732
2798
  registerProjectCommands(workspaceRoot, name, { build: false });
2733
2799
  const module2 = goModulePath(workspaceRoot);
2734
2800
  if (module2) {
2735
- logger.step(`Import it as ${module2}/libs/${name}`);
2801
+ logger.step(`Import its starter slice as ${module2}/libs/${name}/${slice}`);
2736
2802
  }
2737
2803
  }
2738
2804
 
@@ -5271,7 +5337,25 @@ function runDoctor(workspaceRoot) {
5271
5337
  var import_node_fs13 = require("fs");
5272
5338
  var import_node_path20 = require("path");
5273
5339
  var NEVER_COPIED = /* @__PURE__ */ new Set([".git", "node_modules"]);
5274
- var EXISTING_WINS = /* @__PURE__ */ new Set(["README.md", "LICENSE", "LICENSE.md", "LICENSE.txt"]);
5340
+ var AGENT_AND_CI_ENTRIES = [
5341
+ "AGENTS.md",
5342
+ "CLAUDE.md",
5343
+ ".claude",
5344
+ ".agents",
5345
+ ".codex",
5346
+ ".cursor",
5347
+ ".gemini",
5348
+ ".opencode",
5349
+ "opencode.json",
5350
+ ".github"
5351
+ ];
5352
+ var EXISTING_WINS = /* @__PURE__ */ new Set([
5353
+ "README.md",
5354
+ "LICENSE",
5355
+ "LICENSE.md",
5356
+ "LICENSE.txt",
5357
+ ...AGENT_AND_CI_ENTRIES
5358
+ ]);
5275
5359
  var PREFLIGHT_BLOCKERS = [
5276
5360
  "package.json",
5277
5361
  "nx.json",
@@ -5398,27 +5482,30 @@ async function runNew(name, options) {
5398
5482
  const stack = await resolveStack(options);
5399
5483
  const nxCloud = options.nxCloud ?? (options.yes ? false : await promptNxCloud());
5400
5484
  const stagingParent = adoptTarget === void 0 ? process.cwd() : (0, import_node_fs14.mkdtempSync)((0, import_node_path21.join)((0, import_node_os.tmpdir)(), "mnci-new-"));
5401
- logger.step(`Creating Nx workspace '${workspaceName}' (preset: ts)`);
5402
- runNpx(
5403
- [
5404
- "--yes",
5405
- "create-nx-workspace@latest",
5406
- workspaceName,
5407
- "--preset=ts",
5408
- "--pm=npm",
5409
- nxCloud ? `--nxCloud=${nxCloudProviderValue(ci)}` : "--nxCloud=skip",
5410
- "--no-interactive"
5411
- ],
5412
- stagingParent
5413
- );
5414
5485
  let workspaceRoot = (0, import_node_path21.join)(stagingParent, workspaceName);
5415
- if (adoptTarget !== void 0) {
5416
- logger.step(`Adopting ${adoptTarget} (keeping its .git)`);
5417
- const adoption = adoptGeneratedWorkspace(workspaceRoot, adoptTarget);
5418
- for (const kept of adoption.kept) logger.detail(`kept the existing ${kept}`);
5419
- if (adoption.mergedGitignore) logger.detail("merged the generated .gitignore into the existing one");
5420
- (0, import_node_fs14.rmSync)(stagingParent, { recursive: true, force: true });
5421
- workspaceRoot = adoptTarget;
5486
+ try {
5487
+ logger.step(`Creating Nx workspace '${workspaceName}' (preset: ts)`);
5488
+ runNpx(
5489
+ [
5490
+ "--yes",
5491
+ "create-nx-workspace@latest",
5492
+ workspaceName,
5493
+ "--preset=ts",
5494
+ "--pm=npm",
5495
+ nxCloud ? `--nxCloud=${nxCloudProviderValue(ci)}` : "--nxCloud=skip",
5496
+ "--no-interactive"
5497
+ ],
5498
+ stagingParent
5499
+ );
5500
+ if (adoptTarget !== void 0) {
5501
+ logger.step(`Adopting ${adoptTarget} (keeping its .git)`);
5502
+ const adoption = adoptGeneratedWorkspace(workspaceRoot, adoptTarget);
5503
+ for (const kept of adoption.kept) logger.detail(`kept the existing ${kept}`);
5504
+ if (adoption.mergedGitignore) logger.detail("merged the generated .gitignore into the existing one");
5505
+ workspaceRoot = adoptTarget;
5506
+ }
5507
+ } finally {
5508
+ if (adoptTarget !== void 0) (0, import_node_fs14.rmSync)(stagingParent, { recursive: true, force: true });
5422
5509
  }
5423
5510
  logger.step(
5424
5511
  "Applying MoNecromanCI overlay (VS Code workspace, release config, .npmrc, commitlint, pipeline, stack)"