@mnci/cli 1.6.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/README.md +182 -98
- package/dist/cli.js +794 -233
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -35,13 +35,11 @@ __export(cli_exports, {
|
|
|
35
35
|
main: () => main
|
|
36
36
|
});
|
|
37
37
|
module.exports = __toCommonJS(cli_exports);
|
|
38
|
-
var import_node_fs3 = 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
|
|
44
|
-
var
|
|
41
|
+
var import_node_path8 = require("path");
|
|
42
|
+
var import_prompts4 = require("@inquirer/prompts");
|
|
45
43
|
|
|
46
44
|
// src/nx.ts
|
|
47
45
|
var import_cross_spawn = __toESM(require("cross-spawn"));
|
|
@@ -100,14 +98,13 @@ async function promptCi() {
|
|
|
100
98
|
]
|
|
101
99
|
});
|
|
102
100
|
}
|
|
103
|
-
async function
|
|
104
|
-
|
|
105
|
-
message: "
|
|
106
|
-
|
|
107
|
-
{ name: "ESLint", value: "eslint" },
|
|
108
|
-
{ name: "Oxlint", value: "oxlint" }
|
|
109
|
-
]
|
|
101
|
+
async function promptNxCloud() {
|
|
102
|
+
return await (0, import_prompts.confirm)({
|
|
103
|
+
message: "Connect this workspace to Nx Cloud (remote caching + CI insights)?",
|
|
104
|
+
default: false
|
|
110
105
|
});
|
|
106
|
+
}
|
|
107
|
+
async function promptStack() {
|
|
111
108
|
const testRunner = await (0, import_prompts.select)({
|
|
112
109
|
message: "Unit-test runner",
|
|
113
110
|
choices: [
|
|
@@ -115,7 +112,7 @@ async function promptStack() {
|
|
|
115
112
|
{ name: "Vitest", value: "vitest" }
|
|
116
113
|
]
|
|
117
114
|
});
|
|
118
|
-
return {
|
|
115
|
+
return { testRunner };
|
|
119
116
|
}
|
|
120
117
|
|
|
121
118
|
// src/util/fsx.ts
|
|
@@ -161,11 +158,14 @@ var logger = {
|
|
|
161
158
|
// src/util/names.ts
|
|
162
159
|
function assertValidProjectName(name, label) {
|
|
163
160
|
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
|
|
164
|
-
throw new Error(
|
|
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
|
+
);
|
|
165
164
|
}
|
|
166
165
|
}
|
|
167
166
|
|
|
168
|
-
// src/commands/add/
|
|
167
|
+
// src/commands/add/go.ts
|
|
168
|
+
var import_node_fs2 = require("fs");
|
|
169
169
|
var import_node_path3 = require("path");
|
|
170
170
|
|
|
171
171
|
// src/commands/add/shared.ts
|
|
@@ -180,7 +180,11 @@ function ensureAdmZip(workspaceRoot) {
|
|
|
180
180
|
return;
|
|
181
181
|
}
|
|
182
182
|
logger.step("Installing the app packager (adm-zip)");
|
|
183
|
-
if (runShell(
|
|
183
|
+
if (runShell(
|
|
184
|
+
"npm",
|
|
185
|
+
["install", "--save-dev", "adm-zip", "--no-audit", "--no-fund"],
|
|
186
|
+
workspaceRoot
|
|
187
|
+
) !== 0) {
|
|
184
188
|
throw new Error("npm install of adm-zip failed");
|
|
185
189
|
}
|
|
186
190
|
}
|
|
@@ -193,10 +197,179 @@ function addNxTargets(manifestPath, newTargets) {
|
|
|
193
197
|
const manifest = fileExists(manifestPath) ? readJson(manifestPath) : {};
|
|
194
198
|
const nx = manifest.nx ?? {};
|
|
195
199
|
const targets = nx.targets ?? {};
|
|
196
|
-
writeFileEnsured(
|
|
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
|
+
}
|
|
197
369
|
}
|
|
198
370
|
|
|
199
371
|
// src/commands/add/node.ts
|
|
372
|
+
var import_node_path4 = require("path");
|
|
200
373
|
function ensurePlugin(workspaceRoot, packageName) {
|
|
201
374
|
if (hasPlugin(workspaceRoot, packageName)) {
|
|
202
375
|
return;
|
|
@@ -206,17 +379,20 @@ function ensurePlugin(workspaceRoot, packageName) {
|
|
|
206
379
|
}
|
|
207
380
|
function runNodeApp(workspaceRoot, name, stack, framework) {
|
|
208
381
|
ensurePlugin(workspaceRoot, "@nx/node");
|
|
209
|
-
runNx(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
+
);
|
|
220
396
|
}
|
|
221
397
|
function nodeAppPackageTarget(name) {
|
|
222
398
|
const zip = `dist/drop/node-app-${name}.zip`;
|
|
@@ -232,7 +408,9 @@ function nodeAppPackageTarget(name) {
|
|
|
232
408
|
function addNodeApp(workspaceRoot, name, stack, framework = "none") {
|
|
233
409
|
runNodeApp(workspaceRoot, name, stack, framework);
|
|
234
410
|
ensureAdmZip(workspaceRoot);
|
|
235
|
-
addNxTargets((0,
|
|
411
|
+
addNxTargets((0, import_node_path4.join)(workspaceRoot, "apps", name, "package.json"), {
|
|
412
|
+
package: nodeAppPackageTarget(name)
|
|
413
|
+
});
|
|
236
414
|
}
|
|
237
415
|
function ensureAzureFunctionsPackage(workspaceRoot) {
|
|
238
416
|
if (hasPlugin(workspaceRoot, "@azure/functions")) {
|
|
@@ -283,15 +461,20 @@ describe('buildGreeting', () => {
|
|
|
283
461
|
})
|
|
284
462
|
`;
|
|
285
463
|
function repairNodeFunctionAppManifest(nodeFunctionAppRoot, workspaceRoot) {
|
|
286
|
-
const manifestPath = (0,
|
|
464
|
+
const manifestPath = (0, import_node_path4.join)(nodeFunctionAppRoot, "package.json");
|
|
287
465
|
const manifest = readJson(manifestPath);
|
|
288
|
-
const azureFunctionsVersion = readJson(
|
|
466
|
+
const azureFunctionsVersion = readJson(
|
|
467
|
+
(0, import_node_path4.join)(workspaceRoot, "node_modules/@azure/functions/package.json")
|
|
468
|
+
).version;
|
|
289
469
|
const dependencies = manifest.dependencies ?? {};
|
|
290
|
-
writeFileEnsured(
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
470
|
+
writeFileEnsured(
|
|
471
|
+
manifestPath,
|
|
472
|
+
toJson({
|
|
473
|
+
...manifest,
|
|
474
|
+
main: "main.js",
|
|
475
|
+
dependencies: { ...dependencies, "@azure/functions": `^${azureFunctionsVersion}` }
|
|
476
|
+
})
|
|
477
|
+
);
|
|
295
478
|
}
|
|
296
479
|
function nodeFunctionAppPackageTarget(name) {
|
|
297
480
|
const zip = `dist/drop/node-function-app-${name}.zip`;
|
|
@@ -308,19 +491,27 @@ function nodeFunctionAppPackageTarget(name) {
|
|
|
308
491
|
function addNodeFunctionApp(workspaceRoot, name, stack) {
|
|
309
492
|
runNodeApp(workspaceRoot, name, stack, "none");
|
|
310
493
|
ensureAzureFunctionsPackage(workspaceRoot);
|
|
311
|
-
const nodeFunctionAppRoot = (0,
|
|
312
|
-
writeFileEnsured((0,
|
|
313
|
-
writeFileEnsured((0,
|
|
314
|
-
writeFileEnsured(
|
|
315
|
-
|
|
316
|
-
|
|
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);
|
|
317
506
|
repairNodeFunctionAppManifest(nodeFunctionAppRoot, workspaceRoot);
|
|
318
507
|
ensureAdmZip(workspaceRoot);
|
|
319
|
-
addNxTargets((0,
|
|
508
|
+
addNxTargets((0, import_node_path4.join)(nodeFunctionAppRoot, "package.json"), {
|
|
509
|
+
package: nodeFunctionAppPackageTarget(name)
|
|
510
|
+
});
|
|
320
511
|
}
|
|
321
512
|
|
|
322
513
|
// src/commands/add/npmLib.ts
|
|
323
|
-
var
|
|
514
|
+
var import_node_path5 = require("path");
|
|
324
515
|
var NPM_LIB_ESLINT_CONFIG = `import { globSync, readFileSync } from 'node:fs';
|
|
325
516
|
import { dirname, join } from 'node:path';
|
|
326
517
|
import { fileURLToPath } from 'node:url';
|
|
@@ -363,21 +554,25 @@ export default [
|
|
|
363
554
|
`;
|
|
364
555
|
async function addNpmLib(workspaceRoot, name, options, kindProvided, stack) {
|
|
365
556
|
const scope = options.scope ?? (kindProvided ? defaultScope(workspaceRoot) : await promptText("npm scope for the published package", defaultScope(workspaceRoot)));
|
|
366
|
-
runNx(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
+
);
|
|
381
576
|
}
|
|
382
577
|
function markPublic(manifestPath) {
|
|
383
578
|
const manifest = readJson(manifestPath);
|
|
@@ -385,11 +580,14 @@ function markPublic(manifestPath) {
|
|
|
385
580
|
}
|
|
386
581
|
|
|
387
582
|
// src/commands/add/python.ts
|
|
388
|
-
var
|
|
583
|
+
var import_node_fs3 = require("fs");
|
|
584
|
+
var import_node_path6 = require("path");
|
|
389
585
|
function ensurePython(workspaceRoot) {
|
|
390
586
|
const hasPython = runShell("python3", ["--version"], workspaceRoot) === 0 || runShell("python", ["--version"], workspaceRoot) === 0;
|
|
391
587
|
if (!hasPython) {
|
|
392
|
-
throw new Error(
|
|
588
|
+
throw new Error(
|
|
589
|
+
"Python not found. Install Python 3.9+ first: https://www.python.org/downloads/"
|
|
590
|
+
);
|
|
393
591
|
}
|
|
394
592
|
}
|
|
395
593
|
function pythonPipPluginSpec() {
|
|
@@ -412,7 +610,7 @@ pytest
|
|
|
412
610
|
pip-audit
|
|
413
611
|
`;
|
|
414
612
|
function ensureRequirementsDev(workspaceRoot) {
|
|
415
|
-
const requirementsDevPath = (0,
|
|
613
|
+
const requirementsDevPath = (0, import_node_path6.join)(workspaceRoot, "requirements-dev.txt");
|
|
416
614
|
if (!fileExists(requirementsDevPath)) {
|
|
417
615
|
writeFileEnsured(requirementsDevPath, PYTHON_REQUIREMENTS_DEV);
|
|
418
616
|
}
|
|
@@ -420,7 +618,7 @@ function ensureRequirementsDev(workspaceRoot) {
|
|
|
420
618
|
function pythonModuleDirectory(name) {
|
|
421
619
|
return name.replaceAll("-", "_");
|
|
422
620
|
}
|
|
423
|
-
function
|
|
621
|
+
function addProjectJsonTargets2(projectJsonPath, newTargets) {
|
|
424
622
|
const project = readJson(projectJsonPath);
|
|
425
623
|
const targets = project.targets ?? {};
|
|
426
624
|
writeFileEnsured(projectJsonPath, toJson({ ...project, targets: { ...targets, ...newTargets } }));
|
|
@@ -452,34 +650,129 @@ function addPythonApp(workspaceRoot, name) {
|
|
|
452
650
|
ensurePythonPipPlugin(workspaceRoot);
|
|
453
651
|
ensureRequirementsDev(workspaceRoot);
|
|
454
652
|
ensureAdmZip(workspaceRoot);
|
|
455
|
-
runNx(
|
|
456
|
-
|
|
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
|
+
});
|
|
457
660
|
}
|
|
458
661
|
function addPythonFunctionApp(workspaceRoot, name) {
|
|
459
662
|
ensurePython(workspaceRoot);
|
|
460
663
|
ensurePythonPipPlugin(workspaceRoot);
|
|
461
664
|
ensureRequirementsDev(workspaceRoot);
|
|
462
665
|
ensureAdmZip(workspaceRoot);
|
|
463
|
-
runNx(
|
|
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
|
+
);
|
|
464
676
|
const moduleDirectory = pythonModuleDirectory(name);
|
|
465
|
-
|
|
677
|
+
addProjectJsonTargets2((0, import_node_path6.join)(workspaceRoot, "apps", name, "project.json"), {
|
|
678
|
+
package: pythonFunctionAppPackageTarget(name, moduleDirectory)
|
|
679
|
+
});
|
|
466
680
|
}
|
|
467
681
|
function addPythonLib(workspaceRoot, name) {
|
|
468
682
|
ensurePython(workspaceRoot);
|
|
469
683
|
ensurePythonPipPlugin(workspaceRoot);
|
|
470
684
|
ensureRequirementsDev(workspaceRoot);
|
|
471
|
-
runNx(
|
|
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
|
+
);
|
|
472
695
|
}
|
|
473
696
|
function addPythonInternalLib(workspaceRoot, name) {
|
|
474
697
|
ensurePython(workspaceRoot);
|
|
475
698
|
ensurePythonPipPlugin(workspaceRoot);
|
|
476
699
|
ensureRequirementsDev(workspaceRoot);
|
|
477
|
-
runNx(
|
|
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
|
+
);
|
|
710
|
+
}
|
|
711
|
+
var PYTHON_PROJECT_DIRS = ["apps", "python-packages", "libs"];
|
|
712
|
+
function findPythonProjectDirectory(workspaceRoot, name) {
|
|
713
|
+
return PYTHON_PROJECT_DIRS.find(
|
|
714
|
+
(directory) => fileExists((0, import_node_path6.join)(workspaceRoot, directory, name, "pyproject.toml"))
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
function parseVendorEntries(pyprojectToml) {
|
|
718
|
+
const match = /^\s*vendor\s*=\s*\[([^\]]*)\]/m.exec(pyprojectToml);
|
|
719
|
+
if (!match) {
|
|
720
|
+
return [];
|
|
721
|
+
}
|
|
722
|
+
return match[1].split(",").map((entry) => entry.trim().replaceAll(/^["']|["']$/g, "")).filter(Boolean);
|
|
723
|
+
}
|
|
724
|
+
function addVendorEntry(pyprojectToml, lib) {
|
|
725
|
+
const existing = parseVendorEntries(pyprojectToml);
|
|
726
|
+
if (existing.length > 0) {
|
|
727
|
+
const merged = [...existing, lib].map((name) => `"${name}"`).join(", ");
|
|
728
|
+
return pyprojectToml.replace(
|
|
729
|
+
/^(\s*vendor\s*=\s*)\[([^\]]*)\]/m,
|
|
730
|
+
(_match, prefix) => `${prefix}[${merged}]`
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
const table = `[tool.mnci-python-pip]
|
|
734
|
+
vendor = ["${lib}"]
|
|
735
|
+
|
|
736
|
+
`;
|
|
737
|
+
return pyprojectToml.includes("[tool.pytest.ini_options]") ? pyprojectToml.replace("[tool.pytest.ini_options]", () => `${table}[tool.pytest.ini_options]`) : `${pyprojectToml}
|
|
738
|
+
${table}`;
|
|
739
|
+
}
|
|
740
|
+
async function addPythonVendor(workspaceRoot, consumer, options, kindProvided) {
|
|
741
|
+
const lib = options.lib ?? (kindProvided ? void 0 : await promptText("Internal Python library to vendor (an existing libs/<name>)"));
|
|
742
|
+
if (!lib) {
|
|
743
|
+
throw new Error(
|
|
744
|
+
"`add python-vendor` needs the library to vendor: pass --lib <name> (an existing libs/<name> internal library)."
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
const consumerDirectory = findPythonProjectDirectory(workspaceRoot, consumer);
|
|
748
|
+
if (!consumerDirectory) {
|
|
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
|
+
);
|
|
752
|
+
}
|
|
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
|
+
);
|
|
757
|
+
}
|
|
758
|
+
if (consumerDirectory === "libs" && consumer === lib) {
|
|
759
|
+
throw new Error(`'${consumer}' cannot vendor itself.`);
|
|
760
|
+
}
|
|
761
|
+
const pyprojectPath = (0, import_node_path6.join)(workspaceRoot, consumerDirectory, consumer, "pyproject.toml");
|
|
762
|
+
const pyprojectToml = (0, import_node_fs3.readFileSync)(pyprojectPath, "utf8");
|
|
763
|
+
if (parseVendorEntries(pyprojectToml).includes(lib)) {
|
|
764
|
+
logger.info(`'${consumer}' already vendors '${lib}' \u2014 nothing to do.`);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
writeFileEnsured(pyprojectPath, addVendorEntry(pyprojectToml, lib));
|
|
768
|
+
logger.success(
|
|
769
|
+
`'${consumer}' now vendors '${lib}' \u2014 its wheel will include '${lib}''s module on the next build.`
|
|
770
|
+
);
|
|
478
771
|
}
|
|
479
772
|
|
|
480
773
|
// src/commands/add/reactApp.ts
|
|
481
|
-
var
|
|
482
|
-
var
|
|
774
|
+
var import_node_fs4 = require("fs");
|
|
775
|
+
var import_node_path7 = require("path");
|
|
483
776
|
function ensurePlugin2(workspaceRoot, packageName) {
|
|
484
777
|
if (hasPlugin(workspaceRoot, packageName)) {
|
|
485
778
|
return;
|
|
@@ -502,26 +795,35 @@ function reactAppTargets(name) {
|
|
|
502
795
|
executor: "nx:run-commands",
|
|
503
796
|
// eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation -- {workspaceRoot} is an Nx output token
|
|
504
797
|
outputs: [`{workspaceRoot}/apps/${name}/dist-${environment}`],
|
|
505
|
-
options: {
|
|
798
|
+
options: {
|
|
799
|
+
command: `vite build --mode ${environment} --outDir dist-${environment}`,
|
|
800
|
+
cwd: `apps/${name}`
|
|
801
|
+
}
|
|
506
802
|
};
|
|
507
803
|
}
|
|
508
|
-
const zipStatements = REACT_ENVIRONMENTS.map(
|
|
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(";");
|
|
509
807
|
targets.package = {
|
|
510
808
|
executor: "nx:run-commands",
|
|
511
809
|
dependsOn: REACT_ENVIRONMENTS.map((environment) => `build-${environment}`),
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
+
}
|
|
515
817
|
};
|
|
516
818
|
return targets;
|
|
517
819
|
}
|
|
518
820
|
function allowEnvFiles(workspaceRoot) {
|
|
519
|
-
const gitignorePath = (0,
|
|
821
|
+
const gitignorePath = (0, import_node_path7.join)(workspaceRoot, ".gitignore");
|
|
520
822
|
if (!fileExists(gitignorePath)) {
|
|
521
823
|
return;
|
|
522
824
|
}
|
|
523
825
|
const marker = "# MoNecromanCI: keep the committed per-environment Vite config (public VITE_ values)";
|
|
524
|
-
const current = (0,
|
|
826
|
+
const current = (0, import_node_fs4.readFileSync)(gitignorePath, "utf8");
|
|
525
827
|
if (current.includes(marker)) {
|
|
526
828
|
return;
|
|
527
829
|
}
|
|
@@ -533,24 +835,27 @@ ${allow}
|
|
|
533
835
|
}
|
|
534
836
|
function addReactApp(workspaceRoot, name, stack) {
|
|
535
837
|
ensurePlugin2(workspaceRoot, "@nx/react");
|
|
536
|
-
runNx(
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
+
);
|
|
547
852
|
ensureAdmZip(workspaceRoot);
|
|
548
|
-
const reactAppRoot = (0,
|
|
853
|
+
const reactAppRoot = (0, import_node_path7.join)(workspaceRoot, "apps", name);
|
|
549
854
|
for (const environment of REACT_ENVIRONMENTS) {
|
|
550
|
-
writeFileEnsured((0,
|
|
855
|
+
writeFileEnsured((0, import_node_path7.join)(reactAppRoot, `.env.${environment}`), reactEnvironmentFile(environment));
|
|
551
856
|
}
|
|
552
857
|
allowEnvFiles(workspaceRoot);
|
|
553
|
-
addNxTargets((0,
|
|
858
|
+
addNxTargets((0, import_node_path7.join)(reactAppRoot, "package.json"), reactAppTargets(name));
|
|
554
859
|
}
|
|
555
860
|
|
|
556
861
|
// src/commands/add.ts
|
|
@@ -563,16 +868,21 @@ var PROJECT_KINDS = [
|
|
|
563
868
|
"python-app",
|
|
564
869
|
"python-function-app",
|
|
565
870
|
"python-lib",
|
|
566
|
-
"python-internal-lib"
|
|
871
|
+
"python-internal-lib",
|
|
872
|
+
"python-vendor",
|
|
873
|
+
"go-app",
|
|
874
|
+
"go-function-app",
|
|
875
|
+
"go-lib",
|
|
876
|
+
"go-internal-lib"
|
|
567
877
|
];
|
|
568
878
|
async function runAdd(kind, name, options) {
|
|
569
879
|
const workspaceRoot = process.cwd();
|
|
570
|
-
if (!fileExists((0,
|
|
880
|
+
if (!fileExists((0, import_node_path8.join)(workspaceRoot, "nx.json"))) {
|
|
571
881
|
throw new Error("No nx.json found here. Run `add` from the workspace root.");
|
|
572
882
|
}
|
|
573
883
|
const stack = readWorkspaceStack(workspaceRoot);
|
|
574
884
|
const kindProvided = kind !== void 0;
|
|
575
|
-
const resolvedKind = kind ?? await (0,
|
|
885
|
+
const resolvedKind = kind ?? await (0, import_prompts4.select)({
|
|
576
886
|
message: "What kind of project?",
|
|
577
887
|
choices: PROJECT_KINDS.map((value) => ({ name: value, value }))
|
|
578
888
|
});
|
|
@@ -596,16 +906,19 @@ async function runAdd(kind, name, options) {
|
|
|
596
906
|
break;
|
|
597
907
|
}
|
|
598
908
|
case "internal-lib": {
|
|
599
|
-
runNx(
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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"));
|
|
609
922
|
break;
|
|
610
923
|
}
|
|
611
924
|
case "python-app": {
|
|
@@ -624,9 +937,32 @@ async function runAdd(kind, name, options) {
|
|
|
624
937
|
addPythonInternalLib(workspaceRoot, resolvedName);
|
|
625
938
|
break;
|
|
626
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
|
+
}
|
|
956
|
+
case "python-vendor": {
|
|
957
|
+
await addPythonVendor(workspaceRoot, resolvedName, options, kindProvided);
|
|
958
|
+
syncProjectReferences(workspaceRoot);
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
627
961
|
default: {
|
|
628
962
|
const exhaustive = resolvedKind;
|
|
629
|
-
throw new Error(
|
|
963
|
+
throw new Error(
|
|
964
|
+
`Unknown project kind '${exhaustive}'. Expected one of: ${PROJECT_KINDS.join(", ")}.`
|
|
965
|
+
);
|
|
630
966
|
}
|
|
631
967
|
}
|
|
632
968
|
syncProjectReferences(workspaceRoot);
|
|
@@ -635,14 +971,17 @@ async function runAdd(kind, name, options) {
|
|
|
635
971
|
function syncProjectReferences(workspaceRoot) {
|
|
636
972
|
logger.step("Syncing TypeScript project references (nx sync)");
|
|
637
973
|
if (runShell("npx", ["nx", "sync"], workspaceRoot) !== 0) {
|
|
638
|
-
logger.warn(
|
|
974
|
+
logger.warn(
|
|
975
|
+
"nx sync did not complete \u2014 run `npx nx sync` yourself so cross-project imports resolve in your editor."
|
|
976
|
+
);
|
|
639
977
|
}
|
|
640
978
|
}
|
|
641
979
|
function readWorkspaceStack(workspaceRoot) {
|
|
642
|
-
const nxJson = readJson(
|
|
980
|
+
const nxJson = readJson(
|
|
981
|
+
(0, import_node_path8.join)(workspaceRoot, "nx.json")
|
|
982
|
+
);
|
|
643
983
|
const stack = nxJson.mnci?.stack;
|
|
644
984
|
return {
|
|
645
|
-
linter: stack?.linter === "oxlint" ? "none" : "eslint",
|
|
646
985
|
testRunner: stack?.testRunner === "vitest" ? "vitest" : "jest"
|
|
647
986
|
};
|
|
648
987
|
}
|
|
@@ -652,15 +991,16 @@ function markPrivate(manifestPath) {
|
|
|
652
991
|
}
|
|
653
992
|
|
|
654
993
|
// src/commands/interactive.ts
|
|
655
|
-
var
|
|
656
|
-
var
|
|
994
|
+
var import_node_path12 = require("path");
|
|
995
|
+
var import_prompts7 = require("@inquirer/prompts");
|
|
657
996
|
|
|
658
997
|
// src/commands/new.ts
|
|
659
|
-
var
|
|
998
|
+
var import_node_path10 = require("path");
|
|
660
999
|
|
|
661
1000
|
// src/overlay.ts
|
|
662
|
-
var
|
|
663
|
-
var DEFAULT_STACK = {
|
|
1001
|
+
var import_node_path9 = require("path");
|
|
1002
|
+
var DEFAULT_STACK = { testRunner: "jest" };
|
|
1003
|
+
var PRETTIER_VERSION = "^3.8.1";
|
|
664
1004
|
var TS_COMPILER_DEPENDENCIES = {
|
|
665
1005
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
666
1006
|
typescript: "npm:@typescript/typescript6@^6.0.2"
|
|
@@ -720,6 +1060,77 @@ var COMMITLINT_CONFIG = `export default { extends: ['@commitlint/config-conventi
|
|
|
720
1060
|
`;
|
|
721
1061
|
var COMMIT_MSG_HOOK = `npx --no -- commitlint --edit "$1"
|
|
722
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
|
+
}
|
|
723
1134
|
var ROOT_SCRIPTS = {
|
|
724
1135
|
build: "nx run-many -t build",
|
|
725
1136
|
lint: "nx run-many -t lint",
|
|
@@ -729,19 +1140,19 @@ var ROOT_SCRIPTS = {
|
|
|
729
1140
|
"release:preview": "nx release --dry-run",
|
|
730
1141
|
prepare: "husky"
|
|
731
1142
|
};
|
|
732
|
-
function rootScripts(
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
};
|
|
740
|
-
}
|
|
741
|
-
return { ...ROOT_SCRIPTS };
|
|
1143
|
+
function rootScripts() {
|
|
1144
|
+
return {
|
|
1145
|
+
...ROOT_SCRIPTS,
|
|
1146
|
+
format: "prettier --write .",
|
|
1147
|
+
"format:check": "prettier --check .",
|
|
1148
|
+
"python:install": `${PYTHON_INSTALL_GUARD} && ${PYTHON_WORKSPACE_INSTALL_GUARD}`
|
|
1149
|
+
};
|
|
742
1150
|
}
|
|
743
1151
|
function generatorDefaults(stack) {
|
|
744
|
-
const shared = {
|
|
1152
|
+
const shared = {
|
|
1153
|
+
linter: "eslint",
|
|
1154
|
+
unitTestRunner: stack.testRunner
|
|
1155
|
+
};
|
|
745
1156
|
return {
|
|
746
1157
|
"@nx/react:application": shared,
|
|
747
1158
|
"@nx/react:library": shared,
|
|
@@ -755,40 +1166,13 @@ function mnciConfig(options) {
|
|
|
755
1166
|
agent: options.agent,
|
|
756
1167
|
variableGroup: options.variableGroup,
|
|
757
1168
|
ci: options.ci,
|
|
758
|
-
stack: {
|
|
1169
|
+
stack: { testRunner: options.stack.testRunner }
|
|
759
1170
|
};
|
|
760
1171
|
}
|
|
761
1172
|
function readMnciConfig(workspaceRoot) {
|
|
762
|
-
const nxJson = readJson((0,
|
|
1173
|
+
const nxJson = readJson((0, import_node_path9.join)(workspaceRoot, "nx.json"));
|
|
763
1174
|
return nxJson.mnci ?? {};
|
|
764
1175
|
}
|
|
765
|
-
var OXLINT_CONFIG = `import { defineConfig } from 'oxlint'
|
|
766
|
-
import standard from 'oxc-standard/.oxlintrc.json' with { type: 'json' }
|
|
767
|
-
|
|
768
|
-
// Generated by MoNecromanCI. JavaScript Standard Style linting via the
|
|
769
|
-
// oxc-standard preset (unicorn + React + react-perf + TypeScript + oxc).
|
|
770
|
-
// Add project-specific rule overrides in the \`rules\` block below.
|
|
771
|
-
export default defineConfig({
|
|
772
|
-
extends: [standard],
|
|
773
|
-
rules: {
|
|
774
|
-
// The modern JSX transform (Nx's default) needs no React import in scope.
|
|
775
|
-
'react/react-in-jsx-scope': 'off',
|
|
776
|
-
},
|
|
777
|
-
})
|
|
778
|
-
`;
|
|
779
|
-
var OXFMT_CONFIG = `import { defineConfig } from 'oxfmt'
|
|
780
|
-
|
|
781
|
-
// Generated by MoNecromanCI. JavaScript Standard Style formatting, matching the
|
|
782
|
-
// oxc-standard lint preset: no semicolons, single quotes, 2-space indent.
|
|
783
|
-
export default defineConfig({
|
|
784
|
-
singleQuote: true,
|
|
785
|
-
semi: false,
|
|
786
|
-
printWidth: 100,
|
|
787
|
-
tabWidth: 2,
|
|
788
|
-
trailingComma: 'es5',
|
|
789
|
-
arrowParens: 'avoid',
|
|
790
|
-
})
|
|
791
|
-
`;
|
|
792
1176
|
function pythonPublishUrl(registry) {
|
|
793
1177
|
if (registry.kind === "azure-artifacts") {
|
|
794
1178
|
return `https://pkgs.dev.azure.com/${registry.organization}/${registry.project}/_packaging/${registry.artifactsFeed}/pypi/upload/`;
|
|
@@ -798,6 +1182,11 @@ function pythonPublishUrl(registry) {
|
|
|
798
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)"`;
|
|
799
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)"`;
|
|
800
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'`})"`;
|
|
801
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";
|
|
802
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)"`;
|
|
803
1192
|
function releaseGuard(pythonPublishEnv) {
|
|
@@ -897,6 +1286,22 @@ steps:
|
|
|
897
1286
|
- script: ${PIP_AUDIT_GUARD}
|
|
898
1287
|
displayName: pip-audit (non-blocking)
|
|
899
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
|
+
|
|
900
1305
|
# Fails fast, with an unambiguous message, when a stale TypeScript project
|
|
901
1306
|
# reference (or another sync generator's drift) was never synced+committed
|
|
902
1307
|
# locally \u2014 sync.applyChanges (nx.json) only auto-applies interactively, so
|
|
@@ -960,7 +1365,10 @@ steps:
|
|
|
960
1365
|
}
|
|
961
1366
|
function githubActionsYaml(agent, pythonPublishUrl2, registryKind = "azure-artifacts") {
|
|
962
1367
|
const onMain = `github.event_name != 'pull_request' && github.ref_name == 'main'`;
|
|
963
|
-
const [npmAuthName, npmAuthValue] = npmAuthEnvVariable(
|
|
1368
|
+
const [npmAuthName, npmAuthValue] = npmAuthEnvVariable(
|
|
1369
|
+
registryKind,
|
|
1370
|
+
(name) => `\${{ secrets.${name} }}`
|
|
1371
|
+
);
|
|
964
1372
|
return `name: CI
|
|
965
1373
|
|
|
966
1374
|
# Generated by MoNecromanCI. Deliberately thin: Nx builds, 'nx release'
|
|
@@ -1030,6 +1438,22 @@ jobs:
|
|
|
1030
1438
|
- run: ${PIP_AUDIT_GUARD}
|
|
1031
1439
|
name: pip-audit (non-blocking)
|
|
1032
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
|
+
|
|
1033
1457
|
# Fails fast, with an unambiguous message, when a stale TypeScript project
|
|
1034
1458
|
# reference (or another sync generator's drift) was never synced+committed
|
|
1035
1459
|
# locally \u2014 sync.applyChanges (nx.json) only auto-applies interactively, so
|
|
@@ -1111,50 +1535,70 @@ updates:
|
|
|
1111
1535
|
interval: weekly
|
|
1112
1536
|
`;
|
|
1113
1537
|
function applyOverlay(workspaceRoot, options) {
|
|
1114
|
-
const nxJsonPath = (0,
|
|
1538
|
+
const nxJsonPath = (0, import_node_path9.join)(workspaceRoot, "nx.json");
|
|
1115
1539
|
const nxJson = readJson(nxJsonPath);
|
|
1116
|
-
const generators = {
|
|
1540
|
+
const generators = {
|
|
1541
|
+
...nxJson.generators,
|
|
1542
|
+
...generatorDefaults(options.stack)
|
|
1543
|
+
};
|
|
1117
1544
|
const sync = { ...nxJson.sync, ...SYNC_CONFIG };
|
|
1118
1545
|
const mnci = { ...nxJson.mnci, ...mnciConfig(options) };
|
|
1119
1546
|
writeFileEnsured(nxJsonPath, toJson({ ...withReleaseConfig(nxJson), generators, sync, mnci }));
|
|
1120
|
-
const manifestPath = (0,
|
|
1547
|
+
const manifestPath = (0, import_node_path9.join)(workspaceRoot, "package.json");
|
|
1121
1548
|
const manifest = readJson(manifestPath);
|
|
1122
|
-
const scripts = {
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
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");
|
|
1128
1565
|
writeFileEnsured(hookPath, COMMIT_MSG_HOOK);
|
|
1129
1566
|
markExecutable(hookPath);
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
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
|
+
);
|
|
1134
1573
|
const publishUrl = pythonPublishUrl(options.registry);
|
|
1135
1574
|
if (options.ci === "azure" || options.ci === "both") {
|
|
1136
|
-
writeFileEnsured(
|
|
1575
|
+
writeFileEnsured(
|
|
1576
|
+
(0, import_node_path9.join)(workspaceRoot, "azure-pipelines.yml"),
|
|
1577
|
+
azurePipelinesYaml(options.agent, options.variableGroup, publishUrl, options.registry.kind)
|
|
1578
|
+
);
|
|
1137
1579
|
}
|
|
1138
1580
|
if (options.ci === "github" || options.ci === "both") {
|
|
1139
|
-
writeFileEnsured(
|
|
1140
|
-
|
|
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);
|
|
1141
1586
|
}
|
|
1142
1587
|
}
|
|
1143
1588
|
|
|
1144
1589
|
// src/commands/new.ts
|
|
1590
|
+
function nxCloudProviderValue(ci) {
|
|
1591
|
+
return ci === "azure" ? "azure" : "github";
|
|
1592
|
+
}
|
|
1145
1593
|
async function resolveStack(options) {
|
|
1146
|
-
|
|
1147
|
-
const complete = Boolean(fromFlags.linter && fromFlags.testRunner);
|
|
1148
|
-
if (complete || options.yes) {
|
|
1594
|
+
if (options.testRunner || options.yes) {
|
|
1149
1595
|
return {
|
|
1150
|
-
|
|
1151
|
-
testRunner: fromFlags.testRunner ?? DEFAULT_STACK.testRunner
|
|
1596
|
+
testRunner: options.testRunner ?? DEFAULT_STACK.testRunner
|
|
1152
1597
|
};
|
|
1153
1598
|
}
|
|
1154
1599
|
const prompted = await promptStack();
|
|
1155
1600
|
return {
|
|
1156
|
-
|
|
1157
|
-
testRunner: fromFlags.testRunner ?? prompted.testRunner
|
|
1601
|
+
testRunner: prompted.testRunner
|
|
1158
1602
|
};
|
|
1159
1603
|
}
|
|
1160
1604
|
var CI_PROVIDERS = /* @__PURE__ */ new Set(["azure", "github", "both"]);
|
|
@@ -1187,52 +1631,52 @@ async function runNew(name, options) {
|
|
|
1187
1631
|
const scope = options.scope ?? (options.yes ? `@${workspaceName}` : await promptText("npm scope for publishable packages", `@${workspaceName}`));
|
|
1188
1632
|
const registry = await resolveRegistry(options);
|
|
1189
1633
|
const ci = await resolveCi(options);
|
|
1190
|
-
const agent = options.agent ?? (options.yes ? "ubuntu-latest" : await promptText(
|
|
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
|
+
));
|
|
1191
1638
|
const variableGroup = ci === "github" ? options.variableGroup ?? "Build" : options.variableGroup ?? (options.yes ? "Build" : await promptText("Azure DevOps variable group holding the npm PAT", "Build"));
|
|
1192
1639
|
const stack = await resolveStack(options);
|
|
1640
|
+
const nxCloud = options.nxCloud ?? (options.yes ? false : await promptNxCloud());
|
|
1193
1641
|
logger.step(`Creating Nx workspace '${workspaceName}' (preset: ts)`);
|
|
1194
|
-
runNpx(
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
const
|
|
1207
|
-
logger.step(
|
|
1208
|
-
|
|
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
|
+
);
|
|
1209
1665
|
if (installStatus !== 0) {
|
|
1210
1666
|
throw new Error(`npm install of the toolchain failed with exit code ${installStatus}`);
|
|
1211
1667
|
}
|
|
1212
1668
|
runShell("npx", ["husky"], workspaceRoot);
|
|
1213
1669
|
logger.success("Done. Next steps:");
|
|
1214
1670
|
logger.info(` cd ${workspaceName}`);
|
|
1215
|
-
logger.info(
|
|
1216
|
-
|
|
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
|
+
);
|
|
1217
1677
|
logger.info(' git add -A && git commit -m "feat: initial workspace"');
|
|
1218
1678
|
}
|
|
1219
1679
|
|
|
1220
|
-
// src/commands/interactive.ts
|
|
1221
|
-
async function runInteractive() {
|
|
1222
|
-
const inWorkspace = fileExists((0, import_node_path10.join)(process.cwd(), "nx.json"));
|
|
1223
|
-
const newChoice = { name: "Create a new monorepo", value: "new" };
|
|
1224
|
-
const addChoice = { name: "Add a project to this workspace", value: "add" };
|
|
1225
|
-
const action = await (0, import_prompts6.select)({
|
|
1226
|
-
message: "What would you like to do?",
|
|
1227
|
-
choices: inWorkspace ? [addChoice, newChoice] : [newChoice, addChoice]
|
|
1228
|
-
});
|
|
1229
|
-
if (action === "new") {
|
|
1230
|
-
await runNew(void 0, {});
|
|
1231
|
-
} else {
|
|
1232
|
-
await runAdd(void 0, void 0, {});
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
1680
|
// src/commands/upgrade.ts
|
|
1237
1681
|
var import_node_path11 = require("path");
|
|
1238
1682
|
function resolveRegistry2(options, persisted) {
|
|
@@ -1242,7 +1686,9 @@ function resolveRegistry2(options, persisted) {
|
|
|
1242
1686
|
const project = options.project ?? persistedAzure?.project;
|
|
1243
1687
|
const artifactsFeed = options.artifactsFeed ?? persistedAzure?.artifactsFeed;
|
|
1244
1688
|
if (!organization || !project || !artifactsFeed) {
|
|
1245
|
-
throw new Error(
|
|
1689
|
+
throw new Error(
|
|
1690
|
+
"Azure Artifacts registry needs --organization, --project and --artifacts-feed (none found in nx.json's persisted config either)."
|
|
1691
|
+
);
|
|
1246
1692
|
}
|
|
1247
1693
|
return { kind: "azure-artifacts", organization, project, artifactsFeed };
|
|
1248
1694
|
}
|
|
@@ -1252,7 +1698,9 @@ function resolveRegistry2(options, persisted) {
|
|
|
1252
1698
|
if (persisted) {
|
|
1253
1699
|
return persisted;
|
|
1254
1700
|
}
|
|
1255
|
-
throw new Error(
|
|
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
|
+
);
|
|
1256
1704
|
}
|
|
1257
1705
|
function resolveOverlayOptions(options, persisted) {
|
|
1258
1706
|
const scope = options.scope ?? persisted.scope;
|
|
@@ -1262,52 +1710,157 @@ function resolveOverlayOptions(options, persisted) {
|
|
|
1262
1710
|
const registry = resolveRegistry2(options, persisted.registry);
|
|
1263
1711
|
const ci = options.ci ?? persisted.ci;
|
|
1264
1712
|
if (!ci) {
|
|
1265
|
-
throw new Error(
|
|
1713
|
+
throw new Error(
|
|
1714
|
+
"No CI provider found in nx.json's persisted config. Pass --ci azure|github|both explicitly."
|
|
1715
|
+
);
|
|
1266
1716
|
}
|
|
1267
1717
|
const agent = options.agent ?? persisted.agent;
|
|
1268
1718
|
if (!agent) {
|
|
1269
|
-
throw new Error(
|
|
1719
|
+
throw new Error(
|
|
1720
|
+
"No CI build agent found in nx.json's persisted config. Pass --agent explicitly."
|
|
1721
|
+
);
|
|
1270
1722
|
}
|
|
1271
1723
|
const variableGroup = options.variableGroup ?? persisted.variableGroup ?? "Build";
|
|
1272
|
-
const linter = options.linter ?? persisted.stack?.linter;
|
|
1273
1724
|
const testRunner = options.testRunner ?? persisted.stack?.testRunner;
|
|
1274
|
-
if (!
|
|
1275
|
-
throw new Error(
|
|
1725
|
+
if (!testRunner) {
|
|
1726
|
+
throw new Error(
|
|
1727
|
+
"No testRunner found in nx.json's persisted config. Pass --test-runner explicitly."
|
|
1728
|
+
);
|
|
1276
1729
|
}
|
|
1277
|
-
return { scope, registry, agent, variableGroup, ci, stack: {
|
|
1730
|
+
return { scope, registry, agent, variableGroup, ci, stack: { testRunner } };
|
|
1278
1731
|
}
|
|
1279
1732
|
function runUpgrade(workspaceRoot, options) {
|
|
1280
1733
|
if (!fileExists((0, import_node_path11.join)(workspaceRoot, "nx.json"))) {
|
|
1281
|
-
throw new Error(
|
|
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
|
+
);
|
|
1282
1737
|
}
|
|
1283
1738
|
const persisted = readMnciConfig(workspaceRoot);
|
|
1284
1739
|
const resolved = resolveOverlayOptions(options, persisted);
|
|
1285
|
-
logger.step(
|
|
1740
|
+
logger.step(
|
|
1741
|
+
"Re-applying the MoNecromanCI overlay (release config, .npmrc, commitlint, pipeline, stack)"
|
|
1742
|
+
);
|
|
1286
1743
|
applyOverlay(workspaceRoot, resolved);
|
|
1287
1744
|
logger.success("Done. Review the changes with `git diff` before committing.");
|
|
1288
1745
|
}
|
|
1289
1746
|
|
|
1290
|
-
// src/
|
|
1291
|
-
function
|
|
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) {
|
|
1292
1826
|
try {
|
|
1293
|
-
const
|
|
1294
|
-
|
|
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";
|
|
1295
1830
|
} catch {
|
|
1296
1831
|
return "0.0.0";
|
|
1297
1832
|
}
|
|
1298
1833
|
}
|
|
1299
|
-
|
|
1834
|
+
|
|
1835
|
+
// src/cli.ts
|
|
1836
|
+
function buildProgram(cliVersion) {
|
|
1300
1837
|
const program = new import_commander.Command();
|
|
1301
|
-
program.name("mnci").description("MoNecromanCI \u2014 a thin CLI over official Nx plugins").version(
|
|
1302
|
-
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(
|
|
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) => {
|
|
1303
1843
|
await runNew(name, options);
|
|
1304
1844
|
});
|
|
1305
|
-
program.command("upgrade").description(
|
|
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) => {
|
|
1306
1851
|
runUpgrade(process.cwd(), options);
|
|
1307
1852
|
});
|
|
1308
|
-
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(
|
|
1309
|
-
|
|
1310
|
-
|
|
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
|
+
);
|
|
1311
1864
|
program.action(async () => {
|
|
1312
1865
|
await runInteractive();
|
|
1313
1866
|
});
|
|
@@ -1315,7 +1868,15 @@ function buildProgram() {
|
|
|
1315
1868
|
}
|
|
1316
1869
|
async function main() {
|
|
1317
1870
|
try {
|
|
1318
|
-
|
|
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);
|
|
1319
1880
|
} catch (error) {
|
|
1320
1881
|
logger.error(error instanceof Error ? error.message : String(error));
|
|
1321
1882
|
process.exitCode = 1;
|