@mnci/cli 1.7.0 → 1.9.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"
@@ -755,32 +1034,109 @@ function npmrcContent(registry, scope) {
755
1034
  lines.push("");
756
1035
  return lines.join("\n");
757
1036
  }
758
- var RELEASE_CONFIG = {
759
- projectsRelationship: "independent",
760
- projects: ["packages/*", "python-packages/*"],
761
- releaseTag: { pattern: "{projectName}@{version}" },
762
- git: { commit: false, tag: true, push: false },
763
- version: {
764
- conventionalCommits: true,
765
- fallbackCurrentVersionResolver: "disk",
766
- // Build only what is being released. Without this, @nx/js:lib's generator
767
- // defaults the pre-version command to building EVERY project, so a broken
768
- // (or merely slow) app build would block releasing unrelated packages.
769
- // Set here at `new` time it wins: the generator only fills this in when
770
- // absent (it spreads the existing release.version over its default). Both
771
- // globs are listed; `nx run-many` no-ops cleanly when one matches nothing.
772
- preVersionCommand: "npx nx run-many -t build --projects=packages/*,python-packages/*"
773
- },
774
- changelog: { workspaceChangelog: false }
775
- };
1037
+ function releaseConfig(ci) {
1038
+ const githubReleases = ci === "github";
1039
+ return {
1040
+ projectsRelationship: "independent",
1041
+ projects: ["packages/*", "python-packages/*"],
1042
+ releaseTag: { pattern: "{projectName}@{version}" },
1043
+ git: { commit: false, tag: true, push: githubReleases },
1044
+ version: {
1045
+ conventionalCommits: true,
1046
+ fallbackCurrentVersionResolver: "disk",
1047
+ // Build only what is being released. Without this, @nx/js:lib's generator
1048
+ // defaults the pre-version command to building EVERY project, so a broken
1049
+ // (or merely slow) app build would block releasing unrelated packages.
1050
+ // Set here at `new` time it wins: the generator only fills this in when
1051
+ // absent (it spreads the existing release.version over its default). Both
1052
+ // globs are listed; `nx run-many` no-ops cleanly when one matches nothing.
1053
+ preVersionCommand: "npx nx run-many -t build --projects=packages/*,python-packages/*"
1054
+ },
1055
+ changelog: githubReleases ? {
1056
+ workspaceChangelog: false,
1057
+ projectChangelogs: { createRelease: "github", file: false }
1058
+ } : { workspaceChangelog: false }
1059
+ };
1060
+ }
776
1061
  var SYNC_CONFIG = { applyChanges: true };
777
- function withReleaseConfig(nxJson) {
778
- return { ...nxJson, defaultBase: "main", release: RELEASE_CONFIG };
1062
+ function withReleaseConfig(nxJson, ci) {
1063
+ return { ...nxJson, defaultBase: "main", release: releaseConfig(ci) };
779
1064
  }
780
1065
  var COMMITLINT_CONFIG = `export default { extends: ['@commitlint/config-conventional'] }
781
1066
  `;
782
1067
  var COMMIT_MSG_HOOK = `npx --no -- commitlint --edit "$1"
783
1068
  `;
1069
+ var PRETTIER_CONFIG = `{
1070
+ "semi": false,
1071
+ "singleQuote": true,
1072
+ "trailingComma": "es5",
1073
+ "arrowParens": "avoid",
1074
+ "printWidth": 100,
1075
+ "tabWidth": 2,
1076
+ "useTabs": false
1077
+ }
1078
+ `;
1079
+ var PRETTIER_IGNORE = `node_modules
1080
+ dist
1081
+ dist-dev
1082
+ dist-uat
1083
+ dist-prod
1084
+ coverage
1085
+ .next
1086
+ out
1087
+ .nx
1088
+ tmp
1089
+ package-lock.json
1090
+ *.lock
1091
+ `;
1092
+ function vscodeWorkspace(workspaceName) {
1093
+ return JSON.stringify(
1094
+ {
1095
+ folders: [
1096
+ { path: ".", name: workspaceName },
1097
+ { path: "packages/cli", name: "@mnci/cli" },
1098
+ { path: "packages/nx-python-pip", name: "@mnci/nx-python-pip" },
1099
+ { path: "libs/monecromanci-v2", name: "@mnci/monecromanci" }
1100
+ ],
1101
+ settings: {
1102
+ "eslint.validate": [
1103
+ "javascript",
1104
+ "javascriptreact",
1105
+ "typescript",
1106
+ "typescriptreact",
1107
+ "json",
1108
+ "jsonc",
1109
+ "markdown",
1110
+ "yaml"
1111
+ ],
1112
+ "editor.codeActionsOnSave": {
1113
+ "source.fixAll.eslint": "explicit"
1114
+ },
1115
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
1116
+ "editor.formatOnSave": true,
1117
+ "[json]": {
1118
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
1119
+ },
1120
+ "[jsonc]": {
1121
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
1122
+ },
1123
+ "[yaml]": {
1124
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
1125
+ }
1126
+ },
1127
+ extensions: {
1128
+ recommendations: [
1129
+ "dbaeumer.vscode-eslint",
1130
+ "esbenp.prettier-vscode",
1131
+ "nrwl.angular-console",
1132
+ "firsttris.vscode-jest-runner"
1133
+ ]
1134
+ }
1135
+ },
1136
+ null,
1137
+ 2
1138
+ );
1139
+ }
784
1140
  var ROOT_SCRIPTS = {
785
1141
  build: "nx run-many -t build",
786
1142
  lint: "nx run-many -t lint",
@@ -790,17 +1146,19 @@ var ROOT_SCRIPTS = {
790
1146
  "release:preview": "nx release --dry-run",
791
1147
  prepare: "husky"
792
1148
  };
793
- function rootScripts(stack) {
794
- const scripts = stack.linter === "oxlint" ? {
1149
+ function rootScripts() {
1150
+ return {
795
1151
  ...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}` };
1152
+ format: "prettier --write .",
1153
+ "format:check": "prettier --check .",
1154
+ "python:install": `${PYTHON_INSTALL_GUARD} && ${PYTHON_WORKSPACE_INSTALL_GUARD}`
1155
+ };
801
1156
  }
802
1157
  function generatorDefaults(stack) {
803
- const shared = { linter: stack.linter === "oxlint" ? "none" : "eslint", unitTestRunner: stack.testRunner };
1158
+ const shared = {
1159
+ linter: "eslint",
1160
+ unitTestRunner: stack.testRunner
1161
+ };
804
1162
  return {
805
1163
  "@nx/react:application": shared,
806
1164
  "@nx/react:library": shared,
@@ -814,40 +1172,13 @@ function mnciConfig(options) {
814
1172
  agent: options.agent,
815
1173
  variableGroup: options.variableGroup,
816
1174
  ci: options.ci,
817
- stack: { linter: options.stack.linter, testRunner: options.stack.testRunner }
1175
+ stack: { testRunner: options.stack.testRunner }
818
1176
  };
819
1177
  }
820
1178
  function readMnciConfig(workspaceRoot) {
821
- const nxJson = readJson((0, import_node_path8.join)(workspaceRoot, "nx.json"));
1179
+ const nxJson = readJson((0, import_node_path9.join)(workspaceRoot, "nx.json"));
822
1180
  return nxJson.mnci ?? {};
823
1181
  }
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
1182
  function pythonPublishUrl(registry) {
852
1183
  if (registry.kind === "azure-artifacts") {
853
1184
  return `https://pkgs.dev.azure.com/${registry.organization}/${registry.project}/_packaging/${registry.artifactsFeed}/pypi/upload/`;
@@ -857,6 +1188,11 @@ function pythonPublishUrl(registry) {
857
1188
  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
1189
  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
1190
  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)"`;
1191
+ 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)"`;
1192
+ 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)"`;
1193
+ 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');`;
1194
+ var GO_TOOL_PATH_AZURE = `node -e "${GO_TOOL_PATH_PRELUDE}console.log('##vso[task.prependpath]'+bin)"`;
1195
+ 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
1196
  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
1197
  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
1198
  function releaseGuard(pythonPublishEnv) {
@@ -956,6 +1292,22 @@ steps:
956
1292
  - script: ${PIP_AUDIT_GUARD}
957
1293
  displayName: pip-audit (non-blocking)
958
1294
 
1295
+ # Go, if the workspace has any: hosted agents ship the toolchain, so only
1296
+ # the module cache and the linter need seeding. Both guards skip cleanly on
1297
+ # a workspace with no root go.mod.
1298
+ - script: ${GO_MODULE_DOWNLOAD_GUARD}
1299
+ displayName: Download Go module dependencies
1300
+
1301
+ # golangci-lint is what the generated Go lint target actually runs (the
1302
+ # plugin's own default is 'go fmt', which only reformats). 'go install'
1303
+ # drops it in GOPATH/bin, which is not on PATH by default on a hosted
1304
+ # agent \u2014 so prepend it for every later step in the job.
1305
+ - script: ${GOLANGCI_LINT_INSTALL_GUARD}
1306
+ displayName: Install golangci-lint
1307
+
1308
+ - script: ${GO_TOOL_PATH_AZURE}
1309
+ displayName: Add Go tool bin to PATH
1310
+
959
1311
  # Fails fast, with an unambiguous message, when a stale TypeScript project
960
1312
  # reference (or another sync generator's drift) was never synced+committed
961
1313
  # locally \u2014 sync.applyChanges (nx.json) only auto-applies interactively, so
@@ -1017,9 +1369,13 @@ steps:
1017
1369
  condition: ${onMain}
1018
1370
  `;
1019
1371
  }
1020
- function githubActionsYaml(agent, pythonPublishUrl2, registryKind = "azure-artifacts") {
1372
+ function githubActionsYaml(agent, pythonPublishUrl2, registryKind = "azure-artifacts", ci = "github") {
1021
1373
  const onMain = `github.event_name != 'pull_request' && github.ref_name == 'main'`;
1022
- const [npmAuthName, npmAuthValue] = npmAuthEnvVariable(registryKind, (name) => `\${{ secrets.${name} }}`);
1374
+ const [npmAuthName, npmAuthValue] = npmAuthEnvVariable(
1375
+ registryKind,
1376
+ (name) => `\${{ secrets.${name} }}`
1377
+ );
1378
+ const githubReleases = ci === "github";
1023
1379
  return `name: CI
1024
1380
 
1025
1381
  # Generated by MoNecromanCI. Deliberately thin: Nx builds, 'nx release'
@@ -1035,7 +1391,8 @@ on:
1035
1391
  branches: [main]
1036
1392
 
1037
1393
  permissions:
1038
- # Lets the release step push the version tag nx release creates back to main.
1394
+ # Lets the release step push the version tag nx release creates back to main,
1395
+ # and (github-only provider) create the GitHub Release itself.
1039
1396
  contents: write
1040
1397
 
1041
1398
  jobs:
@@ -1089,6 +1446,22 @@ jobs:
1089
1446
  - run: ${PIP_AUDIT_GUARD}
1090
1447
  name: pip-audit (non-blocking)
1091
1448
 
1449
+ # Go, if the workspace has any: hosted runners ship the toolchain, so
1450
+ # only the module cache and the linter need seeding. Both guards skip
1451
+ # cleanly on a workspace with no root go.mod.
1452
+ - run: ${GO_MODULE_DOWNLOAD_GUARD}
1453
+ name: Download Go module dependencies
1454
+
1455
+ # golangci-lint is what the generated Go lint target actually runs (the
1456
+ # plugin's own default is 'go fmt', which only reformats). 'go install'
1457
+ # drops it in GOPATH/bin, which is not on PATH by default on a hosted
1458
+ # runner \u2014 so publish it for every later step in the job.
1459
+ - run: ${GOLANGCI_LINT_INSTALL_GUARD}
1460
+ name: Install golangci-lint
1461
+
1462
+ - run: ${GO_TOOL_PATH_GITHUB}
1463
+ name: Add Go tool bin to PATH
1464
+
1092
1465
  # Fails fast, with an unambiguous message, when a stale TypeScript project
1093
1466
  # reference (or another sync generator's drift) was never synced+committed
1094
1467
  # locally \u2014 sync.applyChanges (nx.json) only auto-applies interactively, so
@@ -1126,13 +1499,19 @@ jobs:
1126
1499
  # (python-packages/*) \u2014 conventional commits, tag-only push. Portable guard:
1127
1500
  # nx release errors on an empty scope, so skip cleanly when there is nothing
1128
1501
  # to release. When there are Python packages and an Azure feed, twine
1129
- # publish credentials are exported (raw PAT, decoded from the base64 secret).
1502
+ # publish credentials are exported (raw PAT, decoded from the base64 secret).${githubReleases ? `
1503
+ # This provider also creates a per-project GitHub Release (changelog
1504
+ # generated from conventional commits since that project's last tag),
1505
+ # which is why 'nx release' pushes the tag itself here \u2014 see
1506
+ # releaseConfig's remarks for why that is safe on this Nx version and
1507
+ # why every other provider combination keeps the explicit push step below.` : ""}
1130
1508
  - run: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl2))}
1131
- name: Release \u2014 version, tag and publish (npm + Python)
1509
+ name: Release \u2014 version, tag${githubReleases ? ", publish and GitHub Release" : " and publish"} (npm + Python)
1132
1510
  if: \${{ ${onMain} }}
1133
1511
  env:
1134
- ${npmAuthName}: ${npmAuthValue}
1135
-
1512
+ ${npmAuthName}: ${npmAuthValue}${githubReleases ? `
1513
+ GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}` : ""}
1514
+ ${githubReleases ? "" : `
1136
1515
  # nx release's own git push (release.git.push) is deliberately left off: it
1137
1516
  # only runs when a remote GitHub/GitLab Release is configured, which this
1138
1517
  # workflow never does, so it would never push the tag the step above just
@@ -1141,7 +1520,7 @@ jobs:
1141
1520
  - run: git push origin --tags
1142
1521
  name: Push release tags (nx release's own push never runs without a remote Release configured)
1143
1522
  if: \${{ ${onMain} }}
1144
- `;
1523
+ `}`;
1145
1524
  }
1146
1525
  var DEPENDABOT_CONFIG = `# Generated by MoNecromanCI. Weekly dependency-update PRs so vulnerable or
1147
1526
  # stale dependencies surface as a reviewable PR instead of only a push-time
@@ -1170,33 +1549,57 @@ updates:
1170
1549
  interval: weekly
1171
1550
  `;
1172
1551
  function applyOverlay(workspaceRoot, options) {
1173
- const nxJsonPath = (0, import_node_path8.join)(workspaceRoot, "nx.json");
1552
+ const nxJsonPath = (0, import_node_path9.join)(workspaceRoot, "nx.json");
1174
1553
  const nxJson = readJson(nxJsonPath);
1175
- const generators = { ...nxJson.generators, ...generatorDefaults(options.stack) };
1554
+ const generators = {
1555
+ ...nxJson.generators,
1556
+ ...generatorDefaults(options.stack)
1557
+ };
1176
1558
  const sync = { ...nxJson.sync, ...SYNC_CONFIG };
1177
1559
  const mnci = { ...nxJson.mnci, ...mnciConfig(options) };
1178
- writeFileEnsured(nxJsonPath, toJson({ ...withReleaseConfig(nxJson), generators, sync, mnci }));
1179
- const manifestPath = (0, import_node_path8.join)(workspaceRoot, "package.json");
1560
+ writeFileEnsured(
1561
+ nxJsonPath,
1562
+ toJson({ ...withReleaseConfig(nxJson, options.ci), generators, sync, mnci })
1563
+ );
1564
+ const manifestPath = (0, import_node_path9.join)(workspaceRoot, "package.json");
1180
1565
  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");
1566
+ const scripts = {
1567
+ ...manifest.scripts,
1568
+ ...rootScripts()
1569
+ };
1570
+ const devDeps = {
1571
+ ...manifest.devDependencies,
1572
+ ...TS_COMPILER_DEPENDENCIES,
1573
+ prettier: PRETTIER_VERSION
1574
+ };
1575
+ writeFileEnsured(
1576
+ manifestPath,
1577
+ toJson({ ...manifest, name: `${options.scope}/source`, scripts, devDependencies: devDeps })
1578
+ );
1579
+ writeFileEnsured((0, import_node_path9.join)(workspaceRoot, ".npmrc"), npmrcContent(options.registry, options.scope));
1580
+ writeFileEnsured((0, import_node_path9.join)(workspaceRoot, "commitlint.config.mjs"), COMMITLINT_CONFIG);
1581
+ const hookPath = (0, import_node_path9.join)(workspaceRoot, ".husky/commit-msg");
1187
1582
  writeFileEnsured(hookPath, COMMIT_MSG_HOOK);
1188
1583
  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
- }
1584
+ writeFileEnsured((0, import_node_path9.join)(workspaceRoot, ".prettierrc.json"), PRETTIER_CONFIG);
1585
+ writeFileEnsured((0, import_node_path9.join)(workspaceRoot, ".prettierignore"), PRETTIER_IGNORE);
1586
+ writeFileEnsured(
1587
+ (0, import_node_path9.join)(workspaceRoot, `${options.workspaceName}.code-workspace`),
1588
+ vscodeWorkspace(options.workspaceName)
1589
+ );
1193
1590
  const publishUrl = pythonPublishUrl(options.registry);
1194
1591
  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));
1592
+ writeFileEnsured(
1593
+ (0, import_node_path9.join)(workspaceRoot, "azure-pipelines.yml"),
1594
+ azurePipelinesYaml(options.agent, options.variableGroup, publishUrl, options.registry.kind)
1595
+ );
1196
1596
  }
1197
1597
  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);
1598
+ writeFileEnsured(
1599
+ (0, import_node_path9.join)(workspaceRoot, ".github/workflows/ci.yml"),
1600
+ githubActionsYaml(options.agent, publishUrl, options.registry.kind, options.ci)
1601
+ );
1602
+ writeFileEnsured((0, import_node_path9.join)(workspaceRoot, ".github/dependabot.yml"), DEPENDABOT_CONFIG);
1200
1603
  }
1201
1604
  }
1202
1605
 
@@ -1205,18 +1608,14 @@ function nxCloudProviderValue(ci) {
1205
1608
  return ci === "azure" ? "azure" : "github";
1206
1609
  }
1207
1610
  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) {
1611
+ if (options.testRunner || options.yes) {
1211
1612
  return {
1212
- linter: fromFlags.linter ?? DEFAULT_STACK.linter,
1213
- testRunner: fromFlags.testRunner ?? DEFAULT_STACK.testRunner
1613
+ testRunner: options.testRunner ?? DEFAULT_STACK.testRunner
1214
1614
  };
1215
1615
  }
1216
1616
  const prompted = await promptStack();
1217
1617
  return {
1218
- linter: fromFlags.linter ?? prompted.linter,
1219
- testRunner: fromFlags.testRunner ?? prompted.testRunner
1618
+ testRunner: prompted.testRunner
1220
1619
  };
1221
1620
  }
1222
1621
  var CI_PROVIDERS = /* @__PURE__ */ new Set(["azure", "github", "both"]);
@@ -1249,53 +1648,52 @@ async function runNew(name, options) {
1249
1648
  const scope = options.scope ?? (options.yes ? `@${workspaceName}` : await promptText("npm scope for publishable packages", `@${workspaceName}`));
1250
1649
  const registry = await resolveRegistry(options);
1251
1650
  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"));
1651
+ const agent = options.agent ?? (options.yes ? "ubuntu-latest" : await promptText(
1652
+ "CI build agent/runner (vmImage, GitHub Actions runner label, or self-hosted pool name)",
1653
+ "ubuntu-latest"
1654
+ ));
1253
1655
  const variableGroup = ci === "github" ? options.variableGroup ?? "Build" : options.variableGroup ?? (options.yes ? "Build" : await promptText("Azure DevOps variable group holding the npm PAT", "Build"));
1254
1656
  const stack = await resolveStack(options);
1255
1657
  const nxCloud = options.nxCloud ?? (options.yes ? false : await promptNxCloud());
1256
1658
  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);
1659
+ runNpx(
1660
+ [
1661
+ "--yes",
1662
+ "create-nx-workspace@latest",
1663
+ workspaceName,
1664
+ "--preset=ts",
1665
+ "--pm=npm",
1666
+ nxCloud ? `--nxCloud=${nxCloudProviderValue(ci)}` : "--nxCloud=skip",
1667
+ "--no-interactive"
1668
+ ],
1669
+ process.cwd()
1670
+ );
1671
+ const workspaceRoot = (0, import_node_path10.join)(process.cwd(), workspaceName);
1672
+ logger.step(
1673
+ "Applying MoNecromanCI overlay (VS Code workspace, release config, .npmrc, commitlint, pipeline, stack)"
1674
+ );
1675
+ applyOverlay(workspaceRoot, { workspaceName, scope, registry, agent, variableGroup, ci, stack });
1676
+ logger.step("Installing the toolchain (commitlint + husky)");
1677
+ const installStatus = runShell(
1678
+ "npm",
1679
+ ["install", "--save-dev", "husky", "@commitlint/cli", "@commitlint/config-conventional"],
1680
+ workspaceRoot
1681
+ );
1272
1682
  if (installStatus !== 0) {
1273
1683
  throw new Error(`npm install of the toolchain failed with exit code ${installStatus}`);
1274
1684
  }
1275
1685
  runShell("npx", ["husky"], workspaceRoot);
1276
1686
  logger.success("Done. Next steps:");
1277
1687
  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");
1688
+ logger.info(
1689
+ " mnci add react-app web # or: node-app, node-function-app, npm-lib, internal-lib,"
1690
+ );
1691
+ logger.info(
1692
+ " # python-app, python-function-app, python-lib, python-internal-lib"
1693
+ );
1280
1694
  logger.info(' git add -A && git commit -m "feat: initial workspace"');
1281
1695
  }
1282
1696
 
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
1697
  // src/commands/upgrade.ts
1300
1698
  var import_node_path11 = require("path");
1301
1699
  function resolveRegistry2(options, persisted) {
@@ -1305,7 +1703,9 @@ function resolveRegistry2(options, persisted) {
1305
1703
  const project = options.project ?? persistedAzure?.project;
1306
1704
  const artifactsFeed = options.artifactsFeed ?? persistedAzure?.artifactsFeed;
1307
1705
  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).");
1706
+ throw new Error(
1707
+ "Azure Artifacts registry needs --organization, --project and --artifacts-feed (none found in nx.json's persisted config either)."
1708
+ );
1309
1709
  }
1310
1710
  return { kind: "azure-artifacts", organization, project, artifactsFeed };
1311
1711
  }
@@ -1315,7 +1715,9 @@ function resolveRegistry2(options, persisted) {
1315
1715
  if (persisted) {
1316
1716
  return persisted;
1317
1717
  }
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).");
1718
+ throw new Error(
1719
+ "No registry found in nx.json's persisted config. Pass --registry npm or --registry azure-artifacts (with --organization/--project/--artifacts-feed)."
1720
+ );
1319
1721
  }
1320
1722
  function resolveOverlayOptions(options, persisted) {
1321
1723
  const scope = options.scope ?? persisted.scope;
@@ -1325,52 +1727,157 @@ function resolveOverlayOptions(options, persisted) {
1325
1727
  const registry = resolveRegistry2(options, persisted.registry);
1326
1728
  const ci = options.ci ?? persisted.ci;
1327
1729
  if (!ci) {
1328
- throw new Error("No CI provider found in nx.json's persisted config. Pass --ci azure|github|both explicitly.");
1730
+ throw new Error(
1731
+ "No CI provider found in nx.json's persisted config. Pass --ci azure|github|both explicitly."
1732
+ );
1329
1733
  }
1330
1734
  const agent = options.agent ?? persisted.agent;
1331
1735
  if (!agent) {
1332
- throw new Error("No CI build agent found in nx.json's persisted config. Pass --agent explicitly.");
1736
+ throw new Error(
1737
+ "No CI build agent found in nx.json's persisted config. Pass --agent explicitly."
1738
+ );
1333
1739
  }
1334
1740
  const variableGroup = options.variableGroup ?? persisted.variableGroup ?? "Build";
1335
- const linter = options.linter ?? persisted.stack?.linter;
1336
1741
  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.");
1742
+ if (!testRunner) {
1743
+ throw new Error(
1744
+ "No testRunner found in nx.json's persisted config. Pass --test-runner explicitly."
1745
+ );
1339
1746
  }
1340
- return { scope, registry, agent, variableGroup, ci, stack: { linter, testRunner } };
1747
+ return { scope, registry, agent, variableGroup, ci, stack: { testRunner } };
1341
1748
  }
1342
1749
  function runUpgrade(workspaceRoot, options) {
1343
1750
  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.`);
1751
+ throw new Error(
1752
+ `No nx.json found in ${workspaceRoot} \u2014 this does not look like an Nx workspace. Run 'mnci upgrade' from the workspace root.`
1753
+ );
1345
1754
  }
1346
1755
  const persisted = readMnciConfig(workspaceRoot);
1347
1756
  const resolved = resolveOverlayOptions(options, persisted);
1348
- logger.step("Re-applying the MoNecromanCI overlay (release config, .npmrc, commitlint, pipeline, stack)");
1757
+ logger.step(
1758
+ "Re-applying the MoNecromanCI overlay (release config, .npmrc, commitlint, pipeline, stack)"
1759
+ );
1349
1760
  applyOverlay(workspaceRoot, resolved);
1350
1761
  logger.success("Done. Review the changes with `git diff` before committing.");
1351
1762
  }
1352
1763
 
1353
- // src/cli.ts
1354
- function readVersion() {
1764
+ // src/commands/interactive.ts
1765
+ async function runInteractive() {
1766
+ const inWorkspace = fileExists((0, import_node_path12.join)(process.cwd(), "nx.json"));
1767
+ const newChoice = { name: "Create a new monorepo", value: "new" };
1768
+ const addChoice = { name: "Add a project to this workspace", value: "add" };
1769
+ const upgradeChoice = {
1770
+ name: "Upgrade this workspace (re-apply the latest overlay)",
1771
+ value: "upgrade"
1772
+ };
1773
+ const action = await (0, import_prompts7.select)({
1774
+ message: "What would you like to do?",
1775
+ choices: inWorkspace ? [addChoice, upgradeChoice, newChoice] : [newChoice, addChoice, upgradeChoice]
1776
+ });
1777
+ if (action === "new") {
1778
+ await runNew(void 0, {});
1779
+ } else if (action === "upgrade") {
1780
+ runUpgrade(process.cwd(), {});
1781
+ } else {
1782
+ await runAdd(void 0, void 0, {});
1783
+ }
1784
+ }
1785
+
1786
+ // src/util/versionChecker.ts
1787
+ var import_node_fs5 = require("fs");
1788
+ var import_node_path13 = require("path");
1789
+ var import_cross_spawn2 = __toESM(require("cross-spawn"));
1790
+ var PACKAGE_NAME = "@mnci/cli";
1791
+ var REGISTRY_TIMEOUT_MS = 2e3;
1792
+ function checkForUpdate(currentVersion) {
1793
+ try {
1794
+ if (!process.stdout.isTTY) {
1795
+ return;
1796
+ }
1797
+ setImmediate(() => {
1798
+ try {
1799
+ reportIfOutdated(currentVersion);
1800
+ } catch {
1801
+ }
1802
+ });
1803
+ } catch {
1804
+ }
1805
+ }
1806
+ function reportIfOutdated(currentVersion) {
1807
+ const result = import_cross_spawn2.default.sync("npm", ["view", PACKAGE_NAME, "version"], {
1808
+ encoding: "utf8",
1809
+ timeout: REGISTRY_TIMEOUT_MS,
1810
+ stdio: ["ignore", "pipe", "ignore"]
1811
+ });
1812
+ if (result.error || result.status !== 0) {
1813
+ return;
1814
+ }
1815
+ const latestVersion = (result.stdout ?? "").trim();
1816
+ if (!latestVersion || !isNewerVersion(latestVersion, currentVersion)) {
1817
+ return;
1818
+ }
1819
+ logger.info("");
1820
+ logger.info(`A new version of ${PACKAGE_NAME} is available: ${latestVersion}`);
1821
+ logger.info(` Current version: ${currentVersion}`);
1822
+ logger.info(` Update with: npm install -g ${PACKAGE_NAME}@latest`);
1823
+ logger.info("");
1824
+ }
1825
+ function isNewerVersion(candidate, current) {
1826
+ const candidateParts = releaseTriple(candidate);
1827
+ const currentParts = releaseTriple(current);
1828
+ if (!candidateParts || !currentParts) {
1829
+ return false;
1830
+ }
1831
+ for (const [index, candidatePart] of candidateParts.entries()) {
1832
+ if (candidatePart !== currentParts[index]) {
1833
+ return candidatePart > currentParts[index];
1834
+ }
1835
+ }
1836
+ return false;
1837
+ }
1838
+ function releaseTriple(version) {
1839
+ const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
1840
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : void 0;
1841
+ }
1842
+ function readCliVersion(packageDirectory) {
1355
1843
  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";
1844
+ const manifestPath = (0, import_node_path13.join)(packageDirectory, "..", "package.json");
1845
+ const manifest = JSON.parse((0, import_node_fs5.readFileSync)(manifestPath, "utf8"));
1846
+ return manifest.version ?? "0.0.0";
1358
1847
  } catch {
1359
1848
  return "0.0.0";
1360
1849
  }
1361
1850
  }
1362
- function buildProgram() {
1851
+
1852
+ // src/cli.ts
1853
+ function buildProgram(cliVersion) {
1363
1854
  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) => {
1855
+ program.name("mnci").description("MoNecromanCI \u2014 a thin CLI over official Nx plugins").version(cliVersion, "-v, --version", "display the version");
1856
+ 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(
1857
+ "--agent <pool>",
1858
+ "CI build agent: a vmImage (e.g. ubuntu-latest) or self-hosted pool name"
1859
+ ).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
1860
  await runNew(name, options);
1367
1861
  });
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) => {
1862
+ program.command("upgrade").description(
1863
+ "Re-apply the latest MoNecromanCI overlay to this workspace (release config, pipeline, npmrc, commitlint, husky hook, curated scripts)"
1864
+ ).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(
1865
+ "--test-runner <runner>",
1866
+ "unit-test runner: jest | vitest (overrides the persisted value)"
1867
+ ).action((options) => {
1369
1868
  runUpgrade(process.cwd(), options);
1370
1869
  });
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
- });
1870
+ 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(
1871
+ "--framework <framework>",
1872
+ "node-app only: express | fastify | koa | nest | none (default: none)"
1873
+ ).option(
1874
+ "--lib <name>",
1875
+ "python-vendor only: the internal Python library (libs/<name>) to vendor into <name>"
1876
+ ).action(
1877
+ async (kind, name, options) => {
1878
+ await runAdd(kind, name, options);
1879
+ }
1880
+ );
1374
1881
  program.action(async () => {
1375
1882
  await runInteractive();
1376
1883
  });
@@ -1378,7 +1885,15 @@ function buildProgram() {
1378
1885
  }
1379
1886
  async function main() {
1380
1887
  try {
1381
- await buildProgram().parseAsync(process.argv);
1888
+ const cliVersion = readCliVersion(__dirname);
1889
+ const program = buildProgram(cliVersion);
1890
+ checkForUpdate(cliVersion);
1891
+ const arguments_ = new Set(process.argv.slice(2));
1892
+ const requestedVersionFlag = arguments_.has("-v") || arguments_.has("--version");
1893
+ if (process.stdout.isTTY && !requestedVersionFlag) {
1894
+ logger.info(`mnci v${cliVersion}`);
1895
+ }
1896
+ await program.parseAsync(process.argv);
1382
1897
  } catch (error) {
1383
1898
  logger.error(error instanceof Error ? error.message : String(error));
1384
1899
  process.exitCode = 1;