@mnci/cli 1.7.0 → 1.8.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/dist/cli.js CHANGED
@@ -35,12 +35,10 @@ __export(cli_exports, {
35
35
  main: () => main
36
36
  });
37
37
  module.exports = __toCommonJS(cli_exports);
38
- var import_node_fs4 = require("fs");
39
- var import_node_path12 = require("path");
40
38
  var import_commander = require("commander");
41
39
 
42
40
  // src/commands/add.ts
43
- var import_node_path7 = require("path");
41
+ var import_node_path8 = require("path");
44
42
  var import_prompts4 = require("@inquirer/prompts");
45
43
 
46
44
  // src/nx.ts
@@ -107,13 +105,6 @@ async function promptNxCloud() {
107
105
  });
108
106
  }
109
107
  async function promptStack() {
110
- const linter = await (0, import_prompts.select)({
111
- message: "Linter",
112
- choices: [
113
- { name: "ESLint", value: "eslint" },
114
- { name: "Oxlint", value: "oxlint" }
115
- ]
116
- });
117
108
  const testRunner = await (0, import_prompts.select)({
118
109
  message: "Unit-test runner",
119
110
  choices: [
@@ -121,7 +112,7 @@ async function promptStack() {
121
112
  { name: "Vitest", value: "vitest" }
122
113
  ]
123
114
  });
124
- return { linter, testRunner };
115
+ return { testRunner };
125
116
  }
126
117
 
127
118
  // src/util/fsx.ts
@@ -167,11 +158,14 @@ var logger = {
167
158
  // src/util/names.ts
168
159
  function assertValidProjectName(name, label) {
169
160
  if (!/^[a-z][a-z0-9-]*$/.test(name)) {
170
- throw new Error(`${label} '${name}' is invalid \u2014 use lowercase letters, digits and hyphens, starting with a letter (e.g. 'my-project').`);
161
+ throw new Error(
162
+ `${label} '${name}' is invalid \u2014 use lowercase letters, digits and hyphens, starting with a letter (e.g. 'my-project').`
163
+ );
171
164
  }
172
165
  }
173
166
 
174
- // src/commands/add/node.ts
167
+ // src/commands/add/go.ts
168
+ var import_node_fs2 = require("fs");
175
169
  var import_node_path3 = require("path");
176
170
 
177
171
  // src/commands/add/shared.ts
@@ -186,7 +180,11 @@ function ensureAdmZip(workspaceRoot) {
186
180
  return;
187
181
  }
188
182
  logger.step("Installing the app packager (adm-zip)");
189
- if (runShell("npm", ["install", "--save-dev", "adm-zip", "--no-audit", "--no-fund"], workspaceRoot) !== 0) {
183
+ if (runShell(
184
+ "npm",
185
+ ["install", "--save-dev", "adm-zip", "--no-audit", "--no-fund"],
186
+ workspaceRoot
187
+ ) !== 0) {
190
188
  throw new Error("npm install of adm-zip failed");
191
189
  }
192
190
  }
@@ -199,10 +197,179 @@ function addNxTargets(manifestPath, newTargets) {
199
197
  const manifest = fileExists(manifestPath) ? readJson(manifestPath) : {};
200
198
  const nx = manifest.nx ?? {};
201
199
  const targets = nx.targets ?? {};
202
- writeFileEnsured(manifestPath, toJson({ ...manifest, nx: { ...nx, targets: { ...targets, ...newTargets } } }));
200
+ writeFileEnsured(
201
+ manifestPath,
202
+ toJson({ ...manifest, nx: { ...nx, targets: { ...targets, ...newTargets } } })
203
+ );
204
+ }
205
+
206
+ // src/commands/add/go.ts
207
+ function ensureGo(workspaceRoot) {
208
+ if (runShell("go", ["version"], workspaceRoot) !== 0) {
209
+ throw new Error("Go not found. Install Go 1.21+ first: https://go.dev/dl/");
210
+ }
211
+ }
212
+ function warnIfNoGolangciLint(workspaceRoot) {
213
+ if (runShell("golangci-lint", ["--version"], workspaceRoot) !== 0) {
214
+ logger.warn(
215
+ "golangci-lint not found \u2014 the generated lint target needs it. Install: https://golangci-lint.run/welcome/install/"
216
+ );
217
+ }
218
+ }
219
+ function nxGoPluginSpec() {
220
+ return process.env.MNCI_NX_GO_SPEC ?? "@nx-go/nx-go";
221
+ }
222
+ function ensureNxGoPlugin(workspaceRoot) {
223
+ if (hasPlugin(workspaceRoot, "@nx-go/nx-go")) {
224
+ return;
225
+ }
226
+ const spec = nxGoPluginSpec();
227
+ logger.step(`Installing the Go toolchain plugin (${spec})`);
228
+ if (runShell("npm", ["install", "--save-dev", spec, "--no-audit", "--no-fund"], workspaceRoot) !== 0) {
229
+ throw new Error("npm install of @nx-go/nx-go failed");
230
+ }
231
+ }
232
+ function ensureGoModule(workspaceRoot) {
233
+ if (fileExists((0, import_node_path3.join)(workspaceRoot, "go.mod"))) {
234
+ return;
235
+ }
236
+ logger.step("Bootstrapping the workspace Go module (single root go.mod)");
237
+ runNx(["g", "@nx-go/nx-go:init", "--no-interactive"], workspaceRoot);
238
+ runNx(["g", "@nx-go/nx-go:convert-to-one-mod", "--no-interactive"], workspaceRoot);
239
+ }
240
+ function goModulePath(workspaceRoot) {
241
+ try {
242
+ const contents = (0, import_node_fs2.readFileSync)((0, import_node_path3.join)(workspaceRoot, "go.mod"), "utf8");
243
+ return /^module\s+(\S+)/m.exec(contents)?.[1];
244
+ } catch {
245
+ return void 0;
246
+ }
247
+ }
248
+ function addProjectJsonTargets(projectJsonPath, newTargets) {
249
+ const project = readJson(projectJsonPath);
250
+ const targets = project.targets ?? {};
251
+ writeFileEnsured(projectJsonPath, toJson({ ...project, targets: { ...targets, ...newTargets } }));
252
+ }
253
+ function goTestTarget() {
254
+ return { executor: "@nx-go/nx-go:test" };
255
+ }
256
+ function goLintTarget() {
257
+ return { executor: "@nx-go/nx-go:lint", options: { linter: "golangci-lint", args: ["run"] } };
258
+ }
259
+ function goBuildTarget(name) {
260
+ return {
261
+ executor: "@nx-go/nx-go:build",
262
+ // eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation -- {workspaceRoot} is an Nx output token
263
+ outputs: [`{workspaceRoot}/dist/apps/${name}`],
264
+ options: { outputPath: `../../dist/apps/${name}/${name}` }
265
+ };
266
+ }
267
+ function goPackageTarget(tag, name) {
268
+ const zip = `dist/drop/${tag}-${name}.zip`;
269
+ const command = `node -e "const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');const z=new A();z.addLocalFolder('dist/apps/${name}');z.writeZip('${zip}')"`;
270
+ return {
271
+ executor: "nx:run-commands",
272
+ dependsOn: ["build"],
273
+ // eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation -- {workspaceRoot} is an Nx output token
274
+ outputs: [`{workspaceRoot}/${zip}`],
275
+ options: { command }
276
+ };
277
+ }
278
+ function prepareGo(workspaceRoot) {
279
+ ensureGo(workspaceRoot);
280
+ warnIfNoGolangciLint(workspaceRoot);
281
+ ensureNxGoPlugin(workspaceRoot);
282
+ ensureGoModule(workspaceRoot);
283
+ }
284
+ function addGoApp(workspaceRoot, name) {
285
+ prepareGo(workspaceRoot);
286
+ ensureAdmZip(workspaceRoot);
287
+ runNx(
288
+ [
289
+ "g",
290
+ "@nx-go/nx-go:application",
291
+ `apps/${name}`,
292
+ `--name=${name}`,
293
+ "--tags=type:go-app",
294
+ "--no-interactive"
295
+ ],
296
+ workspaceRoot
297
+ );
298
+ addProjectJsonTargets((0, import_node_path3.join)(workspaceRoot, "apps", name, "project.json"), {
299
+ build: goBuildTarget(name),
300
+ test: goTestTarget(),
301
+ lint: goLintTarget(),
302
+ package: goPackageTarget("go-app", name)
303
+ });
304
+ }
305
+ function addGoFunctionApp(workspaceRoot, name) {
306
+ prepareGo(workspaceRoot);
307
+ ensureAdmZip(workspaceRoot);
308
+ runNx(
309
+ [
310
+ "g",
311
+ "@nx-go/nx-go:application",
312
+ `apps/${name}`,
313
+ `--name=${name}`,
314
+ "--tags=type:go-function-app",
315
+ "--no-interactive"
316
+ ],
317
+ workspaceRoot
318
+ );
319
+ addProjectJsonTargets((0, import_node_path3.join)(workspaceRoot, "apps", name, "project.json"), {
320
+ build: goBuildTarget(name),
321
+ test: goTestTarget(),
322
+ lint: goLintTarget(),
323
+ package: goPackageTarget("go-function-app", name)
324
+ });
325
+ }
326
+ function addGoLib(workspaceRoot, name) {
327
+ prepareGo(workspaceRoot);
328
+ runNx(
329
+ [
330
+ "g",
331
+ "@nx-go/nx-go:library",
332
+ `packages/${name}`,
333
+ `--name=${name}`,
334
+ "--tags=type:go-lib",
335
+ "--no-interactive"
336
+ ],
337
+ workspaceRoot
338
+ );
339
+ addProjectJsonTargets((0, import_node_path3.join)(workspaceRoot, "packages", name, "project.json"), {
340
+ test: goTestTarget(),
341
+ lint: goLintTarget()
342
+ });
343
+ const module2 = goModulePath(workspaceRoot);
344
+ if (module2) {
345
+ logger.step(`Import it as ${module2}/packages/${name}`);
346
+ }
347
+ }
348
+ function addGoInternalLib(workspaceRoot, name) {
349
+ prepareGo(workspaceRoot);
350
+ runNx(
351
+ [
352
+ "g",
353
+ "@nx-go/nx-go:library",
354
+ `libs/${name}`,
355
+ `--name=${name}`,
356
+ "--tags=type:go-internal-lib",
357
+ "--no-interactive"
358
+ ],
359
+ workspaceRoot
360
+ );
361
+ addProjectJsonTargets((0, import_node_path3.join)(workspaceRoot, "libs", name, "project.json"), {
362
+ test: goTestTarget(),
363
+ lint: goLintTarget()
364
+ });
365
+ const module2 = goModulePath(workspaceRoot);
366
+ if (module2) {
367
+ logger.step(`Import it as ${module2}/libs/${name}`);
368
+ }
203
369
  }
204
370
 
205
371
  // src/commands/add/node.ts
372
+ var import_node_path4 = require("path");
206
373
  function ensurePlugin(workspaceRoot, packageName) {
207
374
  if (hasPlugin(workspaceRoot, packageName)) {
208
375
  return;
@@ -212,17 +379,20 @@ function ensurePlugin(workspaceRoot, packageName) {
212
379
  }
213
380
  function runNodeApp(workspaceRoot, name, stack, framework) {
214
381
  ensurePlugin(workspaceRoot, "@nx/node");
215
- runNx([
216
- "g",
217
- "@nx/node:application",
218
- `apps/${name}`,
219
- "--bundler=esbuild",
220
- `--unitTestRunner=${stack.testRunner}`,
221
- `--linter=${stack.linter}`,
222
- "--e2eTestRunner=none",
223
- `--framework=${framework}`,
224
- "--no-interactive"
225
- ], workspaceRoot);
382
+ runNx(
383
+ [
384
+ "g",
385
+ "@nx/node:application",
386
+ `apps/${name}`,
387
+ "--bundler=esbuild",
388
+ `--unitTestRunner=${stack.testRunner}`,
389
+ "--linter=eslint",
390
+ "--e2eTestRunner=none",
391
+ `--framework=${framework}`,
392
+ "--no-interactive"
393
+ ],
394
+ workspaceRoot
395
+ );
226
396
  }
227
397
  function nodeAppPackageTarget(name) {
228
398
  const zip = `dist/drop/node-app-${name}.zip`;
@@ -238,7 +408,9 @@ function nodeAppPackageTarget(name) {
238
408
  function addNodeApp(workspaceRoot, name, stack, framework = "none") {
239
409
  runNodeApp(workspaceRoot, name, stack, framework);
240
410
  ensureAdmZip(workspaceRoot);
241
- addNxTargets((0, import_node_path3.join)(workspaceRoot, "apps", name, "package.json"), { package: nodeAppPackageTarget(name) });
411
+ addNxTargets((0, import_node_path4.join)(workspaceRoot, "apps", name, "package.json"), {
412
+ package: nodeAppPackageTarget(name)
413
+ });
242
414
  }
243
415
  function ensureAzureFunctionsPackage(workspaceRoot) {
244
416
  if (hasPlugin(workspaceRoot, "@azure/functions")) {
@@ -289,15 +461,20 @@ describe('buildGreeting', () => {
289
461
  })
290
462
  `;
291
463
  function repairNodeFunctionAppManifest(nodeFunctionAppRoot, workspaceRoot) {
292
- const manifestPath = (0, import_node_path3.join)(nodeFunctionAppRoot, "package.json");
464
+ const manifestPath = (0, import_node_path4.join)(nodeFunctionAppRoot, "package.json");
293
465
  const manifest = readJson(manifestPath);
294
- const azureFunctionsVersion = readJson((0, import_node_path3.join)(workspaceRoot, "node_modules/@azure/functions/package.json")).version;
466
+ const azureFunctionsVersion = readJson(
467
+ (0, import_node_path4.join)(workspaceRoot, "node_modules/@azure/functions/package.json")
468
+ ).version;
295
469
  const dependencies = manifest.dependencies ?? {};
296
- writeFileEnsured(manifestPath, toJson({
297
- ...manifest,
298
- main: "main.js",
299
- dependencies: { ...dependencies, "@azure/functions": `^${azureFunctionsVersion}` }
300
- }));
470
+ writeFileEnsured(
471
+ manifestPath,
472
+ toJson({
473
+ ...manifest,
474
+ main: "main.js",
475
+ dependencies: { ...dependencies, "@azure/functions": `^${azureFunctionsVersion}` }
476
+ })
477
+ );
301
478
  }
302
479
  function nodeFunctionAppPackageTarget(name) {
303
480
  const zip = `dist/drop/node-function-app-${name}.zip`;
@@ -314,19 +491,27 @@ function nodeFunctionAppPackageTarget(name) {
314
491
  function addNodeFunctionApp(workspaceRoot, name, stack) {
315
492
  runNodeApp(workspaceRoot, name, stack, "none");
316
493
  ensureAzureFunctionsPackage(workspaceRoot);
317
- const nodeFunctionAppRoot = (0, import_node_path3.join)(workspaceRoot, "apps", name);
318
- writeFileEnsured((0, import_node_path3.join)(nodeFunctionAppRoot, "src/main.ts"), NODE_FUNCTION_APP_MAIN);
319
- writeFileEnsured((0, import_node_path3.join)(nodeFunctionAppRoot, "src/functions/hello.ts"), NODE_FUNCTION_APP_HELLO);
320
- writeFileEnsured((0, import_node_path3.join)(nodeFunctionAppRoot, "src/functions/greeting.ts"), NODE_FUNCTION_APP_GREETING);
321
- writeFileEnsured((0, import_node_path3.join)(nodeFunctionAppRoot, "src/functions/greeting.spec.ts"), NODE_FUNCTION_APP_GREETING_SPEC);
322
- writeFileEnsured((0, import_node_path3.join)(nodeFunctionAppRoot, "host.json"), NODE_FUNCTION_APP_HOST_JSON);
494
+ const nodeFunctionAppRoot = (0, import_node_path4.join)(workspaceRoot, "apps", name);
495
+ writeFileEnsured((0, import_node_path4.join)(nodeFunctionAppRoot, "src/main.ts"), NODE_FUNCTION_APP_MAIN);
496
+ writeFileEnsured((0, import_node_path4.join)(nodeFunctionAppRoot, "src/functions/hello.ts"), NODE_FUNCTION_APP_HELLO);
497
+ writeFileEnsured(
498
+ (0, import_node_path4.join)(nodeFunctionAppRoot, "src/functions/greeting.ts"),
499
+ NODE_FUNCTION_APP_GREETING
500
+ );
501
+ writeFileEnsured(
502
+ (0, import_node_path4.join)(nodeFunctionAppRoot, "src/functions/greeting.spec.ts"),
503
+ NODE_FUNCTION_APP_GREETING_SPEC
504
+ );
505
+ writeFileEnsured((0, import_node_path4.join)(nodeFunctionAppRoot, "host.json"), NODE_FUNCTION_APP_HOST_JSON);
323
506
  repairNodeFunctionAppManifest(nodeFunctionAppRoot, workspaceRoot);
324
507
  ensureAdmZip(workspaceRoot);
325
- addNxTargets((0, import_node_path3.join)(nodeFunctionAppRoot, "package.json"), { package: nodeFunctionAppPackageTarget(name) });
508
+ addNxTargets((0, import_node_path4.join)(nodeFunctionAppRoot, "package.json"), {
509
+ package: nodeFunctionAppPackageTarget(name)
510
+ });
326
511
  }
327
512
 
328
513
  // src/commands/add/npmLib.ts
329
- var import_node_path4 = require("path");
514
+ var import_node_path5 = require("path");
330
515
  var NPM_LIB_ESLINT_CONFIG = `import { globSync, readFileSync } from 'node:fs';
331
516
  import { dirname, join } from 'node:path';
332
517
  import { fileURLToPath } from 'node:url';
@@ -369,21 +554,25 @@ export default [
369
554
  `;
370
555
  async function addNpmLib(workspaceRoot, name, options, kindProvided, stack) {
371
556
  const scope = options.scope ?? (kindProvided ? defaultScope(workspaceRoot) : await promptText("npm scope for the published package", defaultScope(workspaceRoot)));
372
- runNx([
373
- "g",
374
- "@nx/js:lib",
375
- `packages/${name}`,
376
- "--publishable",
377
- `--importPath=${scope}/${name}`,
378
- "--bundler=rollup",
379
- `--unitTestRunner=${stack.testRunner}`,
380
- `--linter=${stack.linter}`,
381
- "--no-interactive"
382
- ], workspaceRoot);
383
- markPublic((0, import_node_path4.join)(workspaceRoot, "packages", name, "package.json"));
384
- if (stack.linter === "eslint") {
385
- writeFileEnsured((0, import_node_path4.join)(workspaceRoot, "packages", name, "eslint.config.mjs"), NPM_LIB_ESLINT_CONFIG);
386
- }
557
+ runNx(
558
+ [
559
+ "g",
560
+ "@nx/js:lib",
561
+ `packages/${name}`,
562
+ "--publishable",
563
+ `--importPath=${scope}/${name}`,
564
+ "--bundler=rollup",
565
+ `--unitTestRunner=${stack.testRunner}`,
566
+ "--linter=eslint",
567
+ "--no-interactive"
568
+ ],
569
+ workspaceRoot
570
+ );
571
+ markPublic((0, import_node_path5.join)(workspaceRoot, "packages", name, "package.json"));
572
+ writeFileEnsured(
573
+ (0, import_node_path5.join)(workspaceRoot, "packages", name, "eslint.config.mjs"),
574
+ NPM_LIB_ESLINT_CONFIG
575
+ );
387
576
  }
388
577
  function markPublic(manifestPath) {
389
578
  const manifest = readJson(manifestPath);
@@ -391,12 +580,14 @@ function markPublic(manifestPath) {
391
580
  }
392
581
 
393
582
  // src/commands/add/python.ts
394
- var import_node_fs2 = require("fs");
395
- var import_node_path5 = require("path");
583
+ var import_node_fs3 = require("fs");
584
+ var import_node_path6 = require("path");
396
585
  function ensurePython(workspaceRoot) {
397
586
  const hasPython = runShell("python3", ["--version"], workspaceRoot) === 0 || runShell("python", ["--version"], workspaceRoot) === 0;
398
587
  if (!hasPython) {
399
- throw new Error("Python not found. Install Python 3.9+ first: https://www.python.org/downloads/");
588
+ throw new Error(
589
+ "Python not found. Install Python 3.9+ first: https://www.python.org/downloads/"
590
+ );
400
591
  }
401
592
  }
402
593
  function pythonPipPluginSpec() {
@@ -419,7 +610,7 @@ pytest
419
610
  pip-audit
420
611
  `;
421
612
  function ensureRequirementsDev(workspaceRoot) {
422
- const requirementsDevPath = (0, import_node_path5.join)(workspaceRoot, "requirements-dev.txt");
613
+ const requirementsDevPath = (0, import_node_path6.join)(workspaceRoot, "requirements-dev.txt");
423
614
  if (!fileExists(requirementsDevPath)) {
424
615
  writeFileEnsured(requirementsDevPath, PYTHON_REQUIREMENTS_DEV);
425
616
  }
@@ -427,7 +618,7 @@ function ensureRequirementsDev(workspaceRoot) {
427
618
  function pythonModuleDirectory(name) {
428
619
  return name.replaceAll("-", "_");
429
620
  }
430
- function addProjectJsonTargets(projectJsonPath, newTargets) {
621
+ function addProjectJsonTargets2(projectJsonPath, newTargets) {
431
622
  const project = readJson(projectJsonPath);
432
623
  const targets = project.targets ?? {};
433
624
  writeFileEnsured(projectJsonPath, toJson({ ...project, targets: { ...targets, ...newTargets } }));
@@ -459,33 +650,69 @@ function addPythonApp(workspaceRoot, name) {
459
650
  ensurePythonPipPlugin(workspaceRoot);
460
651
  ensureRequirementsDev(workspaceRoot);
461
652
  ensureAdmZip(workspaceRoot);
462
- runNx(["g", "@mnci/nx-python-pip:application", name, `--directory=apps/${name}`, "--no-interactive"], workspaceRoot);
463
- addProjectJsonTargets((0, import_node_path5.join)(workspaceRoot, "apps", name, "project.json"), { package: pythonAppPackageTarget(name) });
653
+ runNx(
654
+ ["g", "@mnci/nx-python-pip:application", name, `--directory=apps/${name}`, "--no-interactive"],
655
+ workspaceRoot
656
+ );
657
+ addProjectJsonTargets2((0, import_node_path6.join)(workspaceRoot, "apps", name, "project.json"), {
658
+ package: pythonAppPackageTarget(name)
659
+ });
464
660
  }
465
661
  function addPythonFunctionApp(workspaceRoot, name) {
466
662
  ensurePython(workspaceRoot);
467
663
  ensurePythonPipPlugin(workspaceRoot);
468
664
  ensureRequirementsDev(workspaceRoot);
469
665
  ensureAdmZip(workspaceRoot);
470
- runNx(["g", "@mnci/nx-python-pip:function-application", name, `--directory=apps/${name}`, "--no-interactive"], workspaceRoot);
666
+ runNx(
667
+ [
668
+ "g",
669
+ "@mnci/nx-python-pip:function-application",
670
+ name,
671
+ `--directory=apps/${name}`,
672
+ "--no-interactive"
673
+ ],
674
+ workspaceRoot
675
+ );
471
676
  const moduleDirectory = pythonModuleDirectory(name);
472
- addProjectJsonTargets((0, import_node_path5.join)(workspaceRoot, "apps", name, "project.json"), { package: pythonFunctionAppPackageTarget(name, moduleDirectory) });
677
+ addProjectJsonTargets2((0, import_node_path6.join)(workspaceRoot, "apps", name, "project.json"), {
678
+ package: pythonFunctionAppPackageTarget(name, moduleDirectory)
679
+ });
473
680
  }
474
681
  function addPythonLib(workspaceRoot, name) {
475
682
  ensurePython(workspaceRoot);
476
683
  ensurePythonPipPlugin(workspaceRoot);
477
684
  ensureRequirementsDev(workspaceRoot);
478
- runNx(["g", "@mnci/nx-python-pip:library", name, `--directory=python-packages/${name}`, "--no-interactive"], workspaceRoot);
685
+ runNx(
686
+ [
687
+ "g",
688
+ "@mnci/nx-python-pip:library",
689
+ name,
690
+ `--directory=python-packages/${name}`,
691
+ "--no-interactive"
692
+ ],
693
+ workspaceRoot
694
+ );
479
695
  }
480
696
  function addPythonInternalLib(workspaceRoot, name) {
481
697
  ensurePython(workspaceRoot);
482
698
  ensurePythonPipPlugin(workspaceRoot);
483
699
  ensureRequirementsDev(workspaceRoot);
484
- runNx(["g", "@mnci/nx-python-pip:internal-library", name, `--directory=libs/${name}`, "--no-interactive"], workspaceRoot);
700
+ runNx(
701
+ [
702
+ "g",
703
+ "@mnci/nx-python-pip:internal-library",
704
+ name,
705
+ `--directory=libs/${name}`,
706
+ "--no-interactive"
707
+ ],
708
+ workspaceRoot
709
+ );
485
710
  }
486
711
  var PYTHON_PROJECT_DIRS = ["apps", "python-packages", "libs"];
487
712
  function findPythonProjectDirectory(workspaceRoot, name) {
488
- return PYTHON_PROJECT_DIRS.find((directory) => fileExists((0, import_node_path5.join)(workspaceRoot, directory, name, "pyproject.toml")));
713
+ return PYTHON_PROJECT_DIRS.find(
714
+ (directory) => fileExists((0, import_node_path6.join)(workspaceRoot, directory, name, "pyproject.toml"))
715
+ );
489
716
  }
490
717
  function parseVendorEntries(pyprojectToml) {
491
718
  const match = /^\s*vendor\s*=\s*\[([^\]]*)\]/m.exec(pyprojectToml);
@@ -498,7 +725,10 @@ function addVendorEntry(pyprojectToml, lib) {
498
725
  const existing = parseVendorEntries(pyprojectToml);
499
726
  if (existing.length > 0) {
500
727
  const merged = [...existing, lib].map((name) => `"${name}"`).join(", ");
501
- return pyprojectToml.replace(/^(\s*vendor\s*=\s*)\[([^\]]*)\]/m, (_match, prefix) => `${prefix}[${merged}]`);
728
+ return pyprojectToml.replace(
729
+ /^(\s*vendor\s*=\s*)\[([^\]]*)\]/m,
730
+ (_match, prefix) => `${prefix}[${merged}]`
731
+ );
502
732
  }
503
733
  const table = `[tool.mnci-python-pip]
504
734
  vendor = ["${lib}"]
@@ -510,31 +740,39 @@ ${table}`;
510
740
  async function addPythonVendor(workspaceRoot, consumer, options, kindProvided) {
511
741
  const lib = options.lib ?? (kindProvided ? void 0 : await promptText("Internal Python library to vendor (an existing libs/<name>)"));
512
742
  if (!lib) {
513
- throw new Error("`add python-vendor` needs the library to vendor: pass --lib <name> (an existing libs/<name> internal library).");
743
+ throw new Error(
744
+ "`add python-vendor` needs the library to vendor: pass --lib <name> (an existing libs/<name> internal library)."
745
+ );
514
746
  }
515
747
  const consumerDirectory = findPythonProjectDirectory(workspaceRoot, consumer);
516
748
  if (!consumerDirectory) {
517
- throw new Error(`No Python project named '${consumer}' with a pyproject.toml found (checked apps/, python-packages/, libs/). A Python function app has no pyproject.toml \u2014 there is nothing to vendor into.`);
749
+ throw new Error(
750
+ `No Python project named '${consumer}' with a pyproject.toml found (checked apps/, python-packages/, libs/). A Python function app has no pyproject.toml \u2014 there is nothing to vendor into.`
751
+ );
518
752
  }
519
- if (!fileExists((0, import_node_path5.join)(workspaceRoot, "libs", lib, "pyproject.toml"))) {
520
- throw new Error(`No internal Python library named '${lib}' found at libs/${lib}/pyproject.toml. Only an internal library (added via 'mnci add python-internal-lib') can be vendored.`);
753
+ if (!fileExists((0, import_node_path6.join)(workspaceRoot, "libs", lib, "pyproject.toml"))) {
754
+ throw new Error(
755
+ `No internal Python library named '${lib}' found at libs/${lib}/pyproject.toml. Only an internal library (added via 'mnci add python-internal-lib') can be vendored.`
756
+ );
521
757
  }
522
758
  if (consumerDirectory === "libs" && consumer === lib) {
523
759
  throw new Error(`'${consumer}' cannot vendor itself.`);
524
760
  }
525
- const pyprojectPath = (0, import_node_path5.join)(workspaceRoot, consumerDirectory, consumer, "pyproject.toml");
526
- const pyprojectToml = (0, import_node_fs2.readFileSync)(pyprojectPath, "utf8");
761
+ const pyprojectPath = (0, import_node_path6.join)(workspaceRoot, consumerDirectory, consumer, "pyproject.toml");
762
+ const pyprojectToml = (0, import_node_fs3.readFileSync)(pyprojectPath, "utf8");
527
763
  if (parseVendorEntries(pyprojectToml).includes(lib)) {
528
764
  logger.info(`'${consumer}' already vendors '${lib}' \u2014 nothing to do.`);
529
765
  return;
530
766
  }
531
767
  writeFileEnsured(pyprojectPath, addVendorEntry(pyprojectToml, lib));
532
- logger.success(`'${consumer}' now vendors '${lib}' \u2014 its wheel will include '${lib}''s module on the next build.`);
768
+ logger.success(
769
+ `'${consumer}' now vendors '${lib}' \u2014 its wheel will include '${lib}''s module on the next build.`
770
+ );
533
771
  }
534
772
 
535
773
  // src/commands/add/reactApp.ts
536
- var import_node_fs3 = require("fs");
537
- var import_node_path6 = require("path");
774
+ var import_node_fs4 = require("fs");
775
+ var import_node_path7 = require("path");
538
776
  function ensurePlugin2(workspaceRoot, packageName) {
539
777
  if (hasPlugin(workspaceRoot, packageName)) {
540
778
  return;
@@ -557,26 +795,35 @@ function reactAppTargets(name) {
557
795
  executor: "nx:run-commands",
558
796
  // eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation -- {workspaceRoot} is an Nx output token
559
797
  outputs: [`{workspaceRoot}/apps/${name}/dist-${environment}`],
560
- options: { command: `vite build --mode ${environment} --outDir dist-${environment}`, cwd: `apps/${name}` }
798
+ options: {
799
+ command: `vite build --mode ${environment} --outDir dist-${environment}`,
800
+ cwd: `apps/${name}`
801
+ }
561
802
  };
562
803
  }
563
- const zipStatements = REACT_ENVIRONMENTS.map((environment) => `z=new A();z.addLocalFolder('apps/${name}/dist-${environment}');z.writeZip('dist/drop/react-app-${name}-${environment}.zip')`).join(";");
804
+ const zipStatements = REACT_ENVIRONMENTS.map(
805
+ (environment) => `z=new A();z.addLocalFolder('apps/${name}/dist-${environment}');z.writeZip('dist/drop/react-app-${name}-${environment}.zip')`
806
+ ).join(";");
564
807
  targets.package = {
565
808
  executor: "nx:run-commands",
566
809
  dependsOn: REACT_ENVIRONMENTS.map((environment) => `build-${environment}`),
567
- // eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation -- {workspaceRoot} tokens, not JS interpolation
568
- outputs: REACT_ENVIRONMENTS.map((environment) => `{workspaceRoot}/dist/drop/react-app-${name}-${environment}.zip`),
569
- options: { command: `node -e "const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');let z;${zipStatements}"` }
810
+ outputs: REACT_ENVIRONMENTS.map(
811
+ // eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation
812
+ (environment) => `{workspaceRoot}/dist/drop/react-app-${name}-${environment}.zip`
813
+ ),
814
+ options: {
815
+ command: `node -e "const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');let z;${zipStatements}"`
816
+ }
570
817
  };
571
818
  return targets;
572
819
  }
573
820
  function allowEnvFiles(workspaceRoot) {
574
- const gitignorePath = (0, import_node_path6.join)(workspaceRoot, ".gitignore");
821
+ const gitignorePath = (0, import_node_path7.join)(workspaceRoot, ".gitignore");
575
822
  if (!fileExists(gitignorePath)) {
576
823
  return;
577
824
  }
578
825
  const marker = "# MoNecromanCI: keep the committed per-environment Vite config (public VITE_ values)";
579
- const current = (0, import_node_fs3.readFileSync)(gitignorePath, "utf8");
826
+ const current = (0, import_node_fs4.readFileSync)(gitignorePath, "utf8");
580
827
  if (current.includes(marker)) {
581
828
  return;
582
829
  }
@@ -588,24 +835,27 @@ ${allow}
588
835
  }
589
836
  function addReactApp(workspaceRoot, name, stack) {
590
837
  ensurePlugin2(workspaceRoot, "@nx/react");
591
- runNx([
592
- "g",
593
- "@nx/react:app",
594
- `apps/${name}`,
595
- "--bundler=vite",
596
- `--unitTestRunner=${stack.testRunner}`,
597
- `--linter=${stack.linter}`,
598
- "--style=css",
599
- "--e2eTestRunner=none",
600
- "--no-interactive"
601
- ], workspaceRoot);
838
+ runNx(
839
+ [
840
+ "g",
841
+ "@nx/react:app",
842
+ `apps/${name}`,
843
+ "--bundler=vite",
844
+ `--unitTestRunner=${stack.testRunner}`,
845
+ "--linter=eslint",
846
+ "--style=css",
847
+ "--e2eTestRunner=none",
848
+ "--no-interactive"
849
+ ],
850
+ workspaceRoot
851
+ );
602
852
  ensureAdmZip(workspaceRoot);
603
- const reactAppRoot = (0, import_node_path6.join)(workspaceRoot, "apps", name);
853
+ const reactAppRoot = (0, import_node_path7.join)(workspaceRoot, "apps", name);
604
854
  for (const environment of REACT_ENVIRONMENTS) {
605
- writeFileEnsured((0, import_node_path6.join)(reactAppRoot, `.env.${environment}`), reactEnvironmentFile(environment));
855
+ writeFileEnsured((0, import_node_path7.join)(reactAppRoot, `.env.${environment}`), reactEnvironmentFile(environment));
606
856
  }
607
857
  allowEnvFiles(workspaceRoot);
608
- addNxTargets((0, import_node_path6.join)(reactAppRoot, "package.json"), reactAppTargets(name));
858
+ addNxTargets((0, import_node_path7.join)(reactAppRoot, "package.json"), reactAppTargets(name));
609
859
  }
610
860
 
611
861
  // src/commands/add.ts
@@ -619,11 +869,15 @@ var PROJECT_KINDS = [
619
869
  "python-function-app",
620
870
  "python-lib",
621
871
  "python-internal-lib",
622
- "python-vendor"
872
+ "python-vendor",
873
+ "go-app",
874
+ "go-function-app",
875
+ "go-lib",
876
+ "go-internal-lib"
623
877
  ];
624
878
  async function runAdd(kind, name, options) {
625
879
  const workspaceRoot = process.cwd();
626
- if (!fileExists((0, import_node_path7.join)(workspaceRoot, "nx.json"))) {
880
+ if (!fileExists((0, import_node_path8.join)(workspaceRoot, "nx.json"))) {
627
881
  throw new Error("No nx.json found here. Run `add` from the workspace root.");
628
882
  }
629
883
  const stack = readWorkspaceStack(workspaceRoot);
@@ -652,16 +906,19 @@ async function runAdd(kind, name, options) {
652
906
  break;
653
907
  }
654
908
  case "internal-lib": {
655
- runNx([
656
- "g",
657
- "@nx/js:lib",
658
- `libs/${resolvedName}`,
659
- "--bundler=tsc",
660
- `--unitTestRunner=${stack.testRunner}`,
661
- `--linter=${stack.linter}`,
662
- "--no-interactive"
663
- ], workspaceRoot);
664
- markPrivate((0, import_node_path7.join)(workspaceRoot, "libs", resolvedName, "package.json"));
909
+ runNx(
910
+ [
911
+ "g",
912
+ "@nx/js:lib",
913
+ `libs/${resolvedName}`,
914
+ "--bundler=tsc",
915
+ `--unitTestRunner=${stack.testRunner}`,
916
+ "--linter=eslint",
917
+ "--no-interactive"
918
+ ],
919
+ workspaceRoot
920
+ );
921
+ markPrivate((0, import_node_path8.join)(workspaceRoot, "libs", resolvedName, "package.json"));
665
922
  break;
666
923
  }
667
924
  case "python-app": {
@@ -680,6 +937,22 @@ async function runAdd(kind, name, options) {
680
937
  addPythonInternalLib(workspaceRoot, resolvedName);
681
938
  break;
682
939
  }
940
+ case "go-app": {
941
+ addGoApp(workspaceRoot, resolvedName);
942
+ break;
943
+ }
944
+ case "go-function-app": {
945
+ addGoFunctionApp(workspaceRoot, resolvedName);
946
+ break;
947
+ }
948
+ case "go-lib": {
949
+ addGoLib(workspaceRoot, resolvedName);
950
+ break;
951
+ }
952
+ case "go-internal-lib": {
953
+ addGoInternalLib(workspaceRoot, resolvedName);
954
+ break;
955
+ }
683
956
  case "python-vendor": {
684
957
  await addPythonVendor(workspaceRoot, resolvedName, options, kindProvided);
685
958
  syncProjectReferences(workspaceRoot);
@@ -687,7 +960,9 @@ async function runAdd(kind, name, options) {
687
960
  }
688
961
  default: {
689
962
  const exhaustive = resolvedKind;
690
- throw new Error(`Unknown project kind '${exhaustive}'. Expected one of: ${PROJECT_KINDS.join(", ")}.`);
963
+ throw new Error(
964
+ `Unknown project kind '${exhaustive}'. Expected one of: ${PROJECT_KINDS.join(", ")}.`
965
+ );
691
966
  }
692
967
  }
693
968
  syncProjectReferences(workspaceRoot);
@@ -696,14 +971,17 @@ async function runAdd(kind, name, options) {
696
971
  function syncProjectReferences(workspaceRoot) {
697
972
  logger.step("Syncing TypeScript project references (nx sync)");
698
973
  if (runShell("npx", ["nx", "sync"], workspaceRoot) !== 0) {
699
- logger.warn("nx sync did not complete \u2014 run `npx nx sync` yourself so cross-project imports resolve in your editor.");
974
+ logger.warn(
975
+ "nx sync did not complete \u2014 run `npx nx sync` yourself so cross-project imports resolve in your editor."
976
+ );
700
977
  }
701
978
  }
702
979
  function readWorkspaceStack(workspaceRoot) {
703
- const nxJson = readJson((0, import_node_path7.join)(workspaceRoot, "nx.json"));
980
+ const nxJson = readJson(
981
+ (0, import_node_path8.join)(workspaceRoot, "nx.json")
982
+ );
704
983
  const stack = nxJson.mnci?.stack;
705
984
  return {
706
- linter: stack?.linter === "oxlint" ? "none" : "eslint",
707
985
  testRunner: stack?.testRunner === "vitest" ? "vitest" : "jest"
708
986
  };
709
987
  }
@@ -713,15 +991,16 @@ function markPrivate(manifestPath) {
713
991
  }
714
992
 
715
993
  // src/commands/interactive.ts
716
- var import_node_path10 = require("path");
994
+ var import_node_path12 = require("path");
717
995
  var import_prompts7 = require("@inquirer/prompts");
718
996
 
719
997
  // src/commands/new.ts
720
- var import_node_path9 = require("path");
998
+ var import_node_path10 = require("path");
721
999
 
722
1000
  // src/overlay.ts
723
- var import_node_path8 = require("path");
724
- var DEFAULT_STACK = { linter: "eslint", testRunner: "jest" };
1001
+ var import_node_path9 = require("path");
1002
+ var DEFAULT_STACK = { testRunner: "jest" };
1003
+ var PRETTIER_VERSION = "^3.8.1";
725
1004
  var TS_COMPILER_DEPENDENCIES = {
726
1005
  "@typescript/native": "npm:typescript@^7.0.2",
727
1006
  typescript: "npm:@typescript/typescript6@^6.0.2"
@@ -781,6 +1060,77 @@ var COMMITLINT_CONFIG = `export default { extends: ['@commitlint/config-conventi
781
1060
  `;
782
1061
  var COMMIT_MSG_HOOK = `npx --no -- commitlint --edit "$1"
783
1062
  `;
1063
+ var PRETTIER_CONFIG = `{
1064
+ "semi": false,
1065
+ "singleQuote": true,
1066
+ "trailingComma": "es5",
1067
+ "arrowParens": "avoid",
1068
+ "printWidth": 100,
1069
+ "tabWidth": 2,
1070
+ "useTabs": false
1071
+ }
1072
+ `;
1073
+ var PRETTIER_IGNORE = `node_modules
1074
+ dist
1075
+ dist-dev
1076
+ dist-uat
1077
+ dist-prod
1078
+ coverage
1079
+ .next
1080
+ out
1081
+ .nx
1082
+ tmp
1083
+ package-lock.json
1084
+ *.lock
1085
+ `;
1086
+ function vscodeWorkspace(workspaceName) {
1087
+ return JSON.stringify(
1088
+ {
1089
+ folders: [
1090
+ { path: ".", name: workspaceName },
1091
+ { path: "packages/cli", name: "@mnci/cli" },
1092
+ { path: "packages/nx-python-pip", name: "@mnci/nx-python-pip" },
1093
+ { path: "libs/monecromanci-v2", name: "@mnci/monecromanci" }
1094
+ ],
1095
+ settings: {
1096
+ "eslint.validate": [
1097
+ "javascript",
1098
+ "javascriptreact",
1099
+ "typescript",
1100
+ "typescriptreact",
1101
+ "json",
1102
+ "jsonc",
1103
+ "markdown",
1104
+ "yaml"
1105
+ ],
1106
+ "editor.codeActionsOnSave": {
1107
+ "source.fixAll.eslint": "explicit"
1108
+ },
1109
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
1110
+ "editor.formatOnSave": true,
1111
+ "[json]": {
1112
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
1113
+ },
1114
+ "[jsonc]": {
1115
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
1116
+ },
1117
+ "[yaml]": {
1118
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
1119
+ }
1120
+ },
1121
+ extensions: {
1122
+ recommendations: [
1123
+ "dbaeumer.vscode-eslint",
1124
+ "esbenp.prettier-vscode",
1125
+ "nrwl.angular-console",
1126
+ "firsttris.vscode-jest-runner"
1127
+ ]
1128
+ }
1129
+ },
1130
+ null,
1131
+ 2
1132
+ );
1133
+ }
784
1134
  var ROOT_SCRIPTS = {
785
1135
  build: "nx run-many -t build",
786
1136
  lint: "nx run-many -t lint",
@@ -790,17 +1140,19 @@ var ROOT_SCRIPTS = {
790
1140
  "release:preview": "nx release --dry-run",
791
1141
  prepare: "husky"
792
1142
  };
793
- function rootScripts(stack) {
794
- const scripts = stack.linter === "oxlint" ? {
1143
+ function rootScripts() {
1144
+ return {
795
1145
  ...ROOT_SCRIPTS,
796
- lint: "oxlint",
797
- format: "oxfmt -c oxfmt.config.mts .",
798
- "format:check": "oxfmt -c oxfmt.config.mts --check ."
799
- } : { ...ROOT_SCRIPTS };
800
- return { ...scripts, "python:install": `${PYTHON_INSTALL_GUARD} && ${PYTHON_WORKSPACE_INSTALL_GUARD}` };
1146
+ format: "prettier --write .",
1147
+ "format:check": "prettier --check .",
1148
+ "python:install": `${PYTHON_INSTALL_GUARD} && ${PYTHON_WORKSPACE_INSTALL_GUARD}`
1149
+ };
801
1150
  }
802
1151
  function generatorDefaults(stack) {
803
- const shared = { linter: stack.linter === "oxlint" ? "none" : "eslint", unitTestRunner: stack.testRunner };
1152
+ const shared = {
1153
+ linter: "eslint",
1154
+ unitTestRunner: stack.testRunner
1155
+ };
804
1156
  return {
805
1157
  "@nx/react:application": shared,
806
1158
  "@nx/react:library": shared,
@@ -814,40 +1166,13 @@ function mnciConfig(options) {
814
1166
  agent: options.agent,
815
1167
  variableGroup: options.variableGroup,
816
1168
  ci: options.ci,
817
- stack: { linter: options.stack.linter, testRunner: options.stack.testRunner }
1169
+ stack: { testRunner: options.stack.testRunner }
818
1170
  };
819
1171
  }
820
1172
  function readMnciConfig(workspaceRoot) {
821
- const nxJson = readJson((0, import_node_path8.join)(workspaceRoot, "nx.json"));
1173
+ const nxJson = readJson((0, import_node_path9.join)(workspaceRoot, "nx.json"));
822
1174
  return nxJson.mnci ?? {};
823
1175
  }
824
- var OXLINT_CONFIG = `import { defineConfig } from 'oxlint'
825
- import standard from 'oxc-standard/.oxlintrc.json' with { type: 'json' }
826
-
827
- // Generated by MoNecromanCI. JavaScript Standard Style linting via the
828
- // oxc-standard preset (unicorn + React + react-perf + TypeScript + oxc).
829
- // Add project-specific rule overrides in the \`rules\` block below.
830
- export default defineConfig({
831
- extends: [standard],
832
- rules: {
833
- // The modern JSX transform (Nx's default) needs no React import in scope.
834
- 'react/react-in-jsx-scope': 'off',
835
- },
836
- })
837
- `;
838
- var OXFMT_CONFIG = `import { defineConfig } from 'oxfmt'
839
-
840
- // Generated by MoNecromanCI. JavaScript Standard Style formatting, matching the
841
- // oxc-standard lint preset: no semicolons, single quotes, 2-space indent.
842
- export default defineConfig({
843
- singleQuote: true,
844
- semi: false,
845
- printWidth: 100,
846
- tabWidth: 2,
847
- trailingComma: 'es5',
848
- arrowParens: 'avoid',
849
- })
850
- `;
851
1176
  function pythonPublishUrl(registry) {
852
1177
  if (registry.kind === "azure-artifacts") {
853
1178
  return `https://pkgs.dev.azure.com/${registry.organization}/${registry.project}/_packaging/${registry.artifactsFeed}/pypi/upload/`;
@@ -857,6 +1182,11 @@ function pythonPublishUrl(registry) {
857
1182
  var PYTHON_INSTALL_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';process.exit(require('node:child_process').spawnSync(py+' -m pip install -r requirements-dev.txt',{stdio:'inherit',shell:true}).status ?? 1)"`;
858
1183
  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)"`;
859
1184
  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)"`;
1185
+ 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)"`;
1186
+ 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)"`;
1187
+ 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');`;
1188
+ var GO_TOOL_PATH_AZURE = `node -e "${GO_TOOL_PATH_PRELUDE}console.log('##vso[task.prependpath]'+bin)"`;
1189
+ 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'`})"`;
860
1190
  var NPM_AUDIT_STEP = "npm audit --audit-level=high || echo npm audit found vulnerabilities, see log above - non-blocking, run npm audit locally to inspect";
861
1191
  var PACK_APPS_GUARD = `node -e "const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});if(fs.globSync('apps/*/project.json').length===0){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
1192
  function releaseGuard(pythonPublishEnv) {
@@ -956,6 +1286,22 @@ steps:
956
1286
  - script: ${PIP_AUDIT_GUARD}
957
1287
  displayName: pip-audit (non-blocking)
958
1288
 
1289
+ # Go, if the workspace has any: hosted agents ship the toolchain, so only
1290
+ # the module cache and the linter need seeding. Both guards skip cleanly on
1291
+ # a workspace with no root go.mod.
1292
+ - script: ${GO_MODULE_DOWNLOAD_GUARD}
1293
+ displayName: Download Go module dependencies
1294
+
1295
+ # golangci-lint is what the generated Go lint target actually runs (the
1296
+ # plugin's own default is 'go fmt', which only reformats). 'go install'
1297
+ # drops it in GOPATH/bin, which is not on PATH by default on a hosted
1298
+ # agent \u2014 so prepend it for every later step in the job.
1299
+ - script: ${GOLANGCI_LINT_INSTALL_GUARD}
1300
+ displayName: Install golangci-lint
1301
+
1302
+ - script: ${GO_TOOL_PATH_AZURE}
1303
+ displayName: Add Go tool bin to PATH
1304
+
959
1305
  # Fails fast, with an unambiguous message, when a stale TypeScript project
960
1306
  # reference (or another sync generator's drift) was never synced+committed
961
1307
  # locally \u2014 sync.applyChanges (nx.json) only auto-applies interactively, so
@@ -1019,7 +1365,10 @@ steps:
1019
1365
  }
1020
1366
  function githubActionsYaml(agent, pythonPublishUrl2, registryKind = "azure-artifacts") {
1021
1367
  const onMain = `github.event_name != 'pull_request' && github.ref_name == 'main'`;
1022
- const [npmAuthName, npmAuthValue] = npmAuthEnvVariable(registryKind, (name) => `\${{ secrets.${name} }}`);
1368
+ const [npmAuthName, npmAuthValue] = npmAuthEnvVariable(
1369
+ registryKind,
1370
+ (name) => `\${{ secrets.${name} }}`
1371
+ );
1023
1372
  return `name: CI
1024
1373
 
1025
1374
  # Generated by MoNecromanCI. Deliberately thin: Nx builds, 'nx release'
@@ -1089,6 +1438,22 @@ jobs:
1089
1438
  - run: ${PIP_AUDIT_GUARD}
1090
1439
  name: pip-audit (non-blocking)
1091
1440
 
1441
+ # Go, if the workspace has any: hosted runners ship the toolchain, so
1442
+ # only the module cache and the linter need seeding. Both guards skip
1443
+ # cleanly on a workspace with no root go.mod.
1444
+ - run: ${GO_MODULE_DOWNLOAD_GUARD}
1445
+ name: Download Go module dependencies
1446
+
1447
+ # golangci-lint is what the generated Go lint target actually runs (the
1448
+ # plugin's own default is 'go fmt', which only reformats). 'go install'
1449
+ # drops it in GOPATH/bin, which is not on PATH by default on a hosted
1450
+ # runner \u2014 so publish it for every later step in the job.
1451
+ - run: ${GOLANGCI_LINT_INSTALL_GUARD}
1452
+ name: Install golangci-lint
1453
+
1454
+ - run: ${GO_TOOL_PATH_GITHUB}
1455
+ name: Add Go tool bin to PATH
1456
+
1092
1457
  # Fails fast, with an unambiguous message, when a stale TypeScript project
1093
1458
  # reference (or another sync generator's drift) was never synced+committed
1094
1459
  # locally \u2014 sync.applyChanges (nx.json) only auto-applies interactively, so
@@ -1170,33 +1535,54 @@ updates:
1170
1535
  interval: weekly
1171
1536
  `;
1172
1537
  function applyOverlay(workspaceRoot, options) {
1173
- const nxJsonPath = (0, import_node_path8.join)(workspaceRoot, "nx.json");
1538
+ const nxJsonPath = (0, import_node_path9.join)(workspaceRoot, "nx.json");
1174
1539
  const nxJson = readJson(nxJsonPath);
1175
- const generators = { ...nxJson.generators, ...generatorDefaults(options.stack) };
1540
+ const generators = {
1541
+ ...nxJson.generators,
1542
+ ...generatorDefaults(options.stack)
1543
+ };
1176
1544
  const sync = { ...nxJson.sync, ...SYNC_CONFIG };
1177
1545
  const mnci = { ...nxJson.mnci, ...mnciConfig(options) };
1178
1546
  writeFileEnsured(nxJsonPath, toJson({ ...withReleaseConfig(nxJson), generators, sync, mnci }));
1179
- const manifestPath = (0, import_node_path8.join)(workspaceRoot, "package.json");
1547
+ const manifestPath = (0, import_node_path9.join)(workspaceRoot, "package.json");
1180
1548
  const manifest = readJson(manifestPath);
1181
- const scripts = { ...manifest.scripts, ...rootScripts(options.stack) };
1182
- const devDependencies = { ...manifest.devDependencies, ...TS_COMPILER_DEPENDENCIES };
1183
- writeFileEnsured(manifestPath, toJson({ ...manifest, name: `${options.scope}/source`, scripts, devDependencies }));
1184
- writeFileEnsured((0, import_node_path8.join)(workspaceRoot, ".npmrc"), npmrcContent(options.registry, options.scope));
1185
- writeFileEnsured((0, import_node_path8.join)(workspaceRoot, "commitlint.config.mjs"), COMMITLINT_CONFIG);
1186
- const hookPath = (0, import_node_path8.join)(workspaceRoot, ".husky/commit-msg");
1549
+ const scripts = {
1550
+ ...manifest.scripts,
1551
+ ...rootScripts()
1552
+ };
1553
+ const devDeps = {
1554
+ ...manifest.devDependencies,
1555
+ ...TS_COMPILER_DEPENDENCIES,
1556
+ prettier: PRETTIER_VERSION
1557
+ };
1558
+ writeFileEnsured(
1559
+ manifestPath,
1560
+ toJson({ ...manifest, name: `${options.scope}/source`, scripts, devDependencies: devDeps })
1561
+ );
1562
+ writeFileEnsured((0, import_node_path9.join)(workspaceRoot, ".npmrc"), npmrcContent(options.registry, options.scope));
1563
+ writeFileEnsured((0, import_node_path9.join)(workspaceRoot, "commitlint.config.mjs"), COMMITLINT_CONFIG);
1564
+ const hookPath = (0, import_node_path9.join)(workspaceRoot, ".husky/commit-msg");
1187
1565
  writeFileEnsured(hookPath, COMMIT_MSG_HOOK);
1188
1566
  markExecutable(hookPath);
1189
- if (options.stack.linter === "oxlint") {
1190
- writeFileEnsured((0, import_node_path8.join)(workspaceRoot, "oxlint.config.mts"), OXLINT_CONFIG);
1191
- writeFileEnsured((0, import_node_path8.join)(workspaceRoot, "oxfmt.config.mts"), OXFMT_CONFIG);
1192
- }
1567
+ writeFileEnsured((0, import_node_path9.join)(workspaceRoot, ".prettierrc.json"), PRETTIER_CONFIG);
1568
+ writeFileEnsured((0, import_node_path9.join)(workspaceRoot, ".prettierignore"), PRETTIER_IGNORE);
1569
+ writeFileEnsured(
1570
+ (0, import_node_path9.join)(workspaceRoot, `${options.workspaceName}.code-workspace`),
1571
+ vscodeWorkspace(options.workspaceName)
1572
+ );
1193
1573
  const publishUrl = pythonPublishUrl(options.registry);
1194
1574
  if (options.ci === "azure" || options.ci === "both") {
1195
- writeFileEnsured((0, import_node_path8.join)(workspaceRoot, "azure-pipelines.yml"), azurePipelinesYaml(options.agent, options.variableGroup, publishUrl, options.registry.kind));
1575
+ writeFileEnsured(
1576
+ (0, import_node_path9.join)(workspaceRoot, "azure-pipelines.yml"),
1577
+ azurePipelinesYaml(options.agent, options.variableGroup, publishUrl, options.registry.kind)
1578
+ );
1196
1579
  }
1197
1580
  if (options.ci === "github" || options.ci === "both") {
1198
- writeFileEnsured((0, import_node_path8.join)(workspaceRoot, ".github/workflows/ci.yml"), githubActionsYaml(options.agent, publishUrl, options.registry.kind));
1199
- writeFileEnsured((0, import_node_path8.join)(workspaceRoot, ".github/dependabot.yml"), DEPENDABOT_CONFIG);
1581
+ writeFileEnsured(
1582
+ (0, import_node_path9.join)(workspaceRoot, ".github/workflows/ci.yml"),
1583
+ githubActionsYaml(options.agent, publishUrl, options.registry.kind)
1584
+ );
1585
+ writeFileEnsured((0, import_node_path9.join)(workspaceRoot, ".github/dependabot.yml"), DEPENDABOT_CONFIG);
1200
1586
  }
1201
1587
  }
1202
1588
 
@@ -1205,18 +1591,14 @@ function nxCloudProviderValue(ci) {
1205
1591
  return ci === "azure" ? "azure" : "github";
1206
1592
  }
1207
1593
  async function resolveStack(options) {
1208
- const fromFlags = { linter: options.linter, testRunner: options.testRunner };
1209
- const complete = Boolean(fromFlags.linter && fromFlags.testRunner);
1210
- if (complete || options.yes) {
1594
+ if (options.testRunner || options.yes) {
1211
1595
  return {
1212
- linter: fromFlags.linter ?? DEFAULT_STACK.linter,
1213
- testRunner: fromFlags.testRunner ?? DEFAULT_STACK.testRunner
1596
+ testRunner: options.testRunner ?? DEFAULT_STACK.testRunner
1214
1597
  };
1215
1598
  }
1216
1599
  const prompted = await promptStack();
1217
1600
  return {
1218
- linter: fromFlags.linter ?? prompted.linter,
1219
- testRunner: fromFlags.testRunner ?? prompted.testRunner
1601
+ testRunner: prompted.testRunner
1220
1602
  };
1221
1603
  }
1222
1604
  var CI_PROVIDERS = /* @__PURE__ */ new Set(["azure", "github", "both"]);
@@ -1249,53 +1631,52 @@ async function runNew(name, options) {
1249
1631
  const scope = options.scope ?? (options.yes ? `@${workspaceName}` : await promptText("npm scope for publishable packages", `@${workspaceName}`));
1250
1632
  const registry = await resolveRegistry(options);
1251
1633
  const ci = await resolveCi(options);
1252
- const agent = options.agent ?? (options.yes ? "ubuntu-latest" : await promptText("CI build agent/runner (vmImage, GitHub Actions runner label, or self-hosted pool name)", "ubuntu-latest"));
1634
+ const agent = options.agent ?? (options.yes ? "ubuntu-latest" : await promptText(
1635
+ "CI build agent/runner (vmImage, GitHub Actions runner label, or self-hosted pool name)",
1636
+ "ubuntu-latest"
1637
+ ));
1253
1638
  const variableGroup = ci === "github" ? options.variableGroup ?? "Build" : options.variableGroup ?? (options.yes ? "Build" : await promptText("Azure DevOps variable group holding the npm PAT", "Build"));
1254
1639
  const stack = await resolveStack(options);
1255
1640
  const nxCloud = options.nxCloud ?? (options.yes ? false : await promptNxCloud());
1256
1641
  logger.step(`Creating Nx workspace '${workspaceName}' (preset: ts)`);
1257
- runNpx([
1258
- "--yes",
1259
- "create-nx-workspace@latest",
1260
- workspaceName,
1261
- "--preset=ts",
1262
- "--pm=npm",
1263
- nxCloud ? `--nxCloud=${nxCloudProviderValue(ci)}` : "--nxCloud=skip",
1264
- "--no-interactive"
1265
- ], process.cwd());
1266
- const workspaceRoot = (0, import_node_path9.join)(process.cwd(), workspaceName);
1267
- logger.step("Applying MoNecromanCI overlay (release config, .npmrc, commitlint, pipeline, stack)");
1268
- applyOverlay(workspaceRoot, { scope, registry, agent, variableGroup, ci, stack });
1269
- const stackDependencies = stack.linter === "oxlint" ? ["oxc-standard"] : [];
1270
- logger.step(`Installing the toolchain (${stack.linter === "oxlint" ? "oxc-standard" : "eslint"}, commit toolchain)`);
1271
- const installStatus = runShell("npm", ["install", "--save-dev", ...stackDependencies, "husky", "@commitlint/cli", "@commitlint/config-conventional"], workspaceRoot);
1642
+ runNpx(
1643
+ [
1644
+ "--yes",
1645
+ "create-nx-workspace@latest",
1646
+ workspaceName,
1647
+ "--preset=ts",
1648
+ "--pm=npm",
1649
+ nxCloud ? `--nxCloud=${nxCloudProviderValue(ci)}` : "--nxCloud=skip",
1650
+ "--no-interactive"
1651
+ ],
1652
+ process.cwd()
1653
+ );
1654
+ const workspaceRoot = (0, import_node_path10.join)(process.cwd(), workspaceName);
1655
+ logger.step(
1656
+ "Applying MoNecromanCI overlay (VS Code workspace, release config, .npmrc, commitlint, pipeline, stack)"
1657
+ );
1658
+ applyOverlay(workspaceRoot, { workspaceName, scope, registry, agent, variableGroup, ci, stack });
1659
+ logger.step("Installing the toolchain (commitlint + husky)");
1660
+ const installStatus = runShell(
1661
+ "npm",
1662
+ ["install", "--save-dev", "husky", "@commitlint/cli", "@commitlint/config-conventional"],
1663
+ workspaceRoot
1664
+ );
1272
1665
  if (installStatus !== 0) {
1273
1666
  throw new Error(`npm install of the toolchain failed with exit code ${installStatus}`);
1274
1667
  }
1275
1668
  runShell("npx", ["husky"], workspaceRoot);
1276
1669
  logger.success("Done. Next steps:");
1277
1670
  logger.info(` cd ${workspaceName}`);
1278
- logger.info(" mnci add react-app web # or: node-app, node-function-app, npm-lib, internal-lib,");
1279
- logger.info(" # python-app, python-function-app, python-lib, python-internal-lib");
1671
+ logger.info(
1672
+ " mnci add react-app web # or: node-app, node-function-app, npm-lib, internal-lib,"
1673
+ );
1674
+ logger.info(
1675
+ " # python-app, python-function-app, python-lib, python-internal-lib"
1676
+ );
1280
1677
  logger.info(' git add -A && git commit -m "feat: initial workspace"');
1281
1678
  }
1282
1679
 
1283
- // src/commands/interactive.ts
1284
- async function runInteractive() {
1285
- const inWorkspace = fileExists((0, import_node_path10.join)(process.cwd(), "nx.json"));
1286
- const newChoice = { name: "Create a new monorepo", value: "new" };
1287
- const addChoice = { name: "Add a project to this workspace", value: "add" };
1288
- const action = await (0, import_prompts7.select)({
1289
- message: "What would you like to do?",
1290
- choices: inWorkspace ? [addChoice, newChoice] : [newChoice, addChoice]
1291
- });
1292
- if (action === "new") {
1293
- await runNew(void 0, {});
1294
- } else {
1295
- await runAdd(void 0, void 0, {});
1296
- }
1297
- }
1298
-
1299
1680
  // src/commands/upgrade.ts
1300
1681
  var import_node_path11 = require("path");
1301
1682
  function resolveRegistry2(options, persisted) {
@@ -1305,7 +1686,9 @@ function resolveRegistry2(options, persisted) {
1305
1686
  const project = options.project ?? persistedAzure?.project;
1306
1687
  const artifactsFeed = options.artifactsFeed ?? persistedAzure?.artifactsFeed;
1307
1688
  if (!organization || !project || !artifactsFeed) {
1308
- throw new Error("Azure Artifacts registry needs --organization, --project and --artifacts-feed (none found in nx.json's persisted config either).");
1689
+ throw new Error(
1690
+ "Azure Artifacts registry needs --organization, --project and --artifacts-feed (none found in nx.json's persisted config either)."
1691
+ );
1309
1692
  }
1310
1693
  return { kind: "azure-artifacts", organization, project, artifactsFeed };
1311
1694
  }
@@ -1315,7 +1698,9 @@ function resolveRegistry2(options, persisted) {
1315
1698
  if (persisted) {
1316
1699
  return persisted;
1317
1700
  }
1318
- throw new Error("No registry found in nx.json's persisted config. Pass --registry npm or --registry azure-artifacts (with --organization/--project/--artifacts-feed).");
1701
+ throw new Error(
1702
+ "No registry found in nx.json's persisted config. Pass --registry npm or --registry azure-artifacts (with --organization/--project/--artifacts-feed)."
1703
+ );
1319
1704
  }
1320
1705
  function resolveOverlayOptions(options, persisted) {
1321
1706
  const scope = options.scope ?? persisted.scope;
@@ -1325,52 +1710,157 @@ function resolveOverlayOptions(options, persisted) {
1325
1710
  const registry = resolveRegistry2(options, persisted.registry);
1326
1711
  const ci = options.ci ?? persisted.ci;
1327
1712
  if (!ci) {
1328
- throw new Error("No CI provider found in nx.json's persisted config. Pass --ci azure|github|both explicitly.");
1713
+ throw new Error(
1714
+ "No CI provider found in nx.json's persisted config. Pass --ci azure|github|both explicitly."
1715
+ );
1329
1716
  }
1330
1717
  const agent = options.agent ?? persisted.agent;
1331
1718
  if (!agent) {
1332
- throw new Error("No CI build agent found in nx.json's persisted config. Pass --agent explicitly.");
1719
+ throw new Error(
1720
+ "No CI build agent found in nx.json's persisted config. Pass --agent explicitly."
1721
+ );
1333
1722
  }
1334
1723
  const variableGroup = options.variableGroup ?? persisted.variableGroup ?? "Build";
1335
- const linter = options.linter ?? persisted.stack?.linter;
1336
1724
  const testRunner = options.testRunner ?? persisted.stack?.testRunner;
1337
- if (!linter || !testRunner) {
1338
- throw new Error("No stack (linter/test runner) found in nx.json's persisted config. Pass --linter and --test-runner explicitly.");
1725
+ if (!testRunner) {
1726
+ throw new Error(
1727
+ "No testRunner found in nx.json's persisted config. Pass --test-runner explicitly."
1728
+ );
1339
1729
  }
1340
- return { scope, registry, agent, variableGroup, ci, stack: { linter, testRunner } };
1730
+ return { scope, registry, agent, variableGroup, ci, stack: { testRunner } };
1341
1731
  }
1342
1732
  function runUpgrade(workspaceRoot, options) {
1343
1733
  if (!fileExists((0, import_node_path11.join)(workspaceRoot, "nx.json"))) {
1344
- throw new Error(`No nx.json found in ${workspaceRoot} \u2014 this does not look like an Nx workspace. Run 'mnci upgrade' from the workspace root.`);
1734
+ throw new Error(
1735
+ `No nx.json found in ${workspaceRoot} \u2014 this does not look like an Nx workspace. Run 'mnci upgrade' from the workspace root.`
1736
+ );
1345
1737
  }
1346
1738
  const persisted = readMnciConfig(workspaceRoot);
1347
1739
  const resolved = resolveOverlayOptions(options, persisted);
1348
- logger.step("Re-applying the MoNecromanCI overlay (release config, .npmrc, commitlint, pipeline, stack)");
1740
+ logger.step(
1741
+ "Re-applying the MoNecromanCI overlay (release config, .npmrc, commitlint, pipeline, stack)"
1742
+ );
1349
1743
  applyOverlay(workspaceRoot, resolved);
1350
1744
  logger.success("Done. Review the changes with `git diff` before committing.");
1351
1745
  }
1352
1746
 
1353
- // src/cli.ts
1354
- function readVersion() {
1747
+ // src/commands/interactive.ts
1748
+ async function runInteractive() {
1749
+ const inWorkspace = fileExists((0, import_node_path12.join)(process.cwd(), "nx.json"));
1750
+ const newChoice = { name: "Create a new monorepo", value: "new" };
1751
+ const addChoice = { name: "Add a project to this workspace", value: "add" };
1752
+ const upgradeChoice = {
1753
+ name: "Upgrade this workspace (re-apply the latest overlay)",
1754
+ value: "upgrade"
1755
+ };
1756
+ const action = await (0, import_prompts7.select)({
1757
+ message: "What would you like to do?",
1758
+ choices: inWorkspace ? [addChoice, upgradeChoice, newChoice] : [newChoice, addChoice, upgradeChoice]
1759
+ });
1760
+ if (action === "new") {
1761
+ await runNew(void 0, {});
1762
+ } else if (action === "upgrade") {
1763
+ runUpgrade(process.cwd(), {});
1764
+ } else {
1765
+ await runAdd(void 0, void 0, {});
1766
+ }
1767
+ }
1768
+
1769
+ // src/util/versionChecker.ts
1770
+ var import_node_fs5 = require("fs");
1771
+ var import_node_path13 = require("path");
1772
+ var import_cross_spawn2 = __toESM(require("cross-spawn"));
1773
+ var PACKAGE_NAME = "@mnci/cli";
1774
+ var REGISTRY_TIMEOUT_MS = 2e3;
1775
+ function checkForUpdate(currentVersion) {
1776
+ try {
1777
+ if (!process.stdout.isTTY) {
1778
+ return;
1779
+ }
1780
+ setImmediate(() => {
1781
+ try {
1782
+ reportIfOutdated(currentVersion);
1783
+ } catch {
1784
+ }
1785
+ });
1786
+ } catch {
1787
+ }
1788
+ }
1789
+ function reportIfOutdated(currentVersion) {
1790
+ const result = import_cross_spawn2.default.sync("npm", ["view", PACKAGE_NAME, "version"], {
1791
+ encoding: "utf8",
1792
+ timeout: REGISTRY_TIMEOUT_MS,
1793
+ stdio: ["ignore", "pipe", "ignore"]
1794
+ });
1795
+ if (result.error || result.status !== 0) {
1796
+ return;
1797
+ }
1798
+ const latestVersion = (result.stdout ?? "").trim();
1799
+ if (!latestVersion || !isNewerVersion(latestVersion, currentVersion)) {
1800
+ return;
1801
+ }
1802
+ logger.info("");
1803
+ logger.info(`A new version of ${PACKAGE_NAME} is available: ${latestVersion}`);
1804
+ logger.info(` Current version: ${currentVersion}`);
1805
+ logger.info(` Update with: npm install -g ${PACKAGE_NAME}@latest`);
1806
+ logger.info("");
1807
+ }
1808
+ function isNewerVersion(candidate, current) {
1809
+ const candidateParts = releaseTriple(candidate);
1810
+ const currentParts = releaseTriple(current);
1811
+ if (!candidateParts || !currentParts) {
1812
+ return false;
1813
+ }
1814
+ for (const [index, candidatePart] of candidateParts.entries()) {
1815
+ if (candidatePart !== currentParts[index]) {
1816
+ return candidatePart > currentParts[index];
1817
+ }
1818
+ }
1819
+ return false;
1820
+ }
1821
+ function releaseTriple(version) {
1822
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
1823
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : void 0;
1824
+ }
1825
+ function readCliVersion(packageDirectory) {
1355
1826
  try {
1356
- const package_ = JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path12.join)(__dirname, "..", "package.json"), "utf8"));
1357
- return package_.version ?? "0.0.0";
1827
+ const manifestPath = (0, import_node_path13.join)(packageDirectory, "..", "package.json");
1828
+ const manifest = JSON.parse((0, import_node_fs5.readFileSync)(manifestPath, "utf8"));
1829
+ return manifest.version ?? "0.0.0";
1358
1830
  } catch {
1359
1831
  return "0.0.0";
1360
1832
  }
1361
1833
  }
1362
- function buildProgram() {
1834
+
1835
+ // src/cli.ts
1836
+ function buildProgram(cliVersion) {
1363
1837
  const program = new import_commander.Command();
1364
- program.name("mnci").description("MoNecromanCI \u2014 a thin CLI over official Nx plugins").version(readVersion(), "-v, --version", "display the version");
1365
- program.command("new").argument("[name]", "workspace name").description("Create a new monorepo (Nx TS preset + release/pipeline overlay)").option("-y, --yes", "accept defaults for anything not passed as a flag").option("--scope <scope>", "npm scope for publishable packages (e.g. @demo)").option("--registry <kind>", "azure-artifacts | npm").option("--organization <name>", "Azure DevOps organization").option("--project <name>", "Azure DevOps project").option("--artifacts-feed <name>", "Azure Artifacts feed").option("--agent <pool>", "CI build agent: a vmImage (e.g. ubuntu-latest) or self-hosted pool name").option("--variable-group <name>", "Azure DevOps variable group holding the npm PAT").option("--ci <provider>", "CI provider: azure | github | both").option("--linter <linter>", "linter: eslint | oxlint").option("--test-runner <runner>", "unit-test runner: jest | vitest").option("--nx-cloud", "connect the workspace to Nx Cloud (remote caching + CI insights)").action(async (name, options) => {
1838
+ program.name("mnci").description("MoNecromanCI \u2014 a thin CLI over official Nx plugins").version(cliVersion, "-v, --version", "display the version");
1839
+ program.command("new").argument("[name]", "workspace name").description("Create a new monorepo (Nx TS preset + release/pipeline overlay)").option("-y, --yes", "accept defaults for anything not passed as a flag").option("--scope <scope>", "npm scope for publishable packages (e.g. @demo)").option("--registry <kind>", "azure-artifacts | npm").option("--organization <name>", "Azure DevOps organization").option("--project <name>", "Azure DevOps project").option("--artifacts-feed <name>", "Azure Artifacts feed").option(
1840
+ "--agent <pool>",
1841
+ "CI build agent: a vmImage (e.g. ubuntu-latest) or self-hosted pool name"
1842
+ ).option("--variable-group <name>", "Azure DevOps variable group holding the npm PAT").option("--ci <provider>", "CI provider: azure | github | both").option("--test-runner <runner>", "unit-test runner: jest | vitest").option("--nx-cloud", "connect the workspace to Nx Cloud (remote caching + CI insights)").action(async (name, options) => {
1366
1843
  await runNew(name, options);
1367
1844
  });
1368
- program.command("upgrade").description("Re-apply the latest MoNecromanCI overlay to this workspace (release config, pipeline, npmrc, commitlint, husky hook, curated scripts)").option("--scope <scope>", "npm scope for publishable packages (overrides the persisted value)").option("--registry <kind>", "azure-artifacts | npm (overrides the persisted value)").option("--organization <name>", "Azure DevOps organization").option("--project <name>", "Azure DevOps project").option("--artifacts-feed <name>", "Azure Artifacts feed").option("--agent <pool>", "CI build agent (overrides the persisted value)").option("--variable-group <name>", "Azure DevOps variable group holding the npm PAT").option("--ci <provider>", "CI provider: azure | github | both (overrides the persisted value)").option("--linter <linter>", "linter: eslint | oxlint (overrides the persisted value)").option("--test-runner <runner>", "unit-test runner: jest | vitest (overrides the persisted value)").action((options) => {
1845
+ program.command("upgrade").description(
1846
+ "Re-apply the latest MoNecromanCI overlay to this workspace (release config, pipeline, npmrc, commitlint, husky hook, curated scripts)"
1847
+ ).option("--scope <scope>", "npm scope for publishable packages (overrides the persisted value)").option("--registry <kind>", "azure-artifacts | npm (overrides the persisted value)").option("--organization <name>", "Azure DevOps organization").option("--project <name>", "Azure DevOps project").option("--artifacts-feed <name>", "Azure Artifacts feed").option("--agent <pool>", "CI build agent (overrides the persisted value)").option("--variable-group <name>", "Azure DevOps variable group holding the npm PAT").option("--ci <provider>", "CI provider: azure | github | both (overrides the persisted value)").option(
1848
+ "--test-runner <runner>",
1849
+ "unit-test runner: jest | vitest (overrides the persisted value)"
1850
+ ).action((options) => {
1369
1851
  runUpgrade(process.cwd(), options);
1370
1852
  });
1371
- program.command("add").addArgument(new import_commander.Argument("[kind]", "project kind").choices(PROJECT_KINDS)).argument("[name]", "project name").description("Add a project by delegating to the matching Nx plugin generator").option("--scope <scope>", "npm scope for a publishable lib (defaults to @<workspace name>)").option("--framework <framework>", "node-app only: express | fastify | koa | nest | none (default: none)").option("--lib <name>", "python-vendor only: the internal Python library (libs/<name>) to vendor into <name>").action(async (kind, name, options) => {
1372
- await runAdd(kind, name, options);
1373
- });
1853
+ program.command("add").addArgument(new import_commander.Argument("[kind]", "project kind").choices(PROJECT_KINDS)).argument("[name]", "project name").description("Add a project by delegating to the matching Nx plugin generator").option("--scope <scope>", "npm scope for a publishable lib (defaults to @<workspace name>)").option(
1854
+ "--framework <framework>",
1855
+ "node-app only: express | fastify | koa | nest | none (default: none)"
1856
+ ).option(
1857
+ "--lib <name>",
1858
+ "python-vendor only: the internal Python library (libs/<name>) to vendor into <name>"
1859
+ ).action(
1860
+ async (kind, name, options) => {
1861
+ await runAdd(kind, name, options);
1862
+ }
1863
+ );
1374
1864
  program.action(async () => {
1375
1865
  await runInteractive();
1376
1866
  });
@@ -1378,7 +1868,15 @@ function buildProgram() {
1378
1868
  }
1379
1869
  async function main() {
1380
1870
  try {
1381
- await buildProgram().parseAsync(process.argv);
1871
+ const cliVersion = readCliVersion(__dirname);
1872
+ const program = buildProgram(cliVersion);
1873
+ checkForUpdate(cliVersion);
1874
+ const arguments_ = new Set(process.argv.slice(2));
1875
+ const requestedVersionFlag = arguments_.has("-v") || arguments_.has("--version");
1876
+ if (process.stdout.isTTY && !requestedVersionFlag) {
1877
+ logger.info(`mnci v${cliVersion}`);
1878
+ }
1879
+ await program.parseAsync(process.argv);
1382
1880
  } catch (error) {
1383
1881
  logger.error(error instanceof Error ? error.message : String(error));
1384
1882
  process.exitCode = 1;