@mnci/cli 1.12.2 → 1.14.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 +43 -8
- package/dist/cli.js +214 -62
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,6 +38,8 @@ mnci add node-app api --framework express # ...or fastify | koa | nest
|
|
|
38
38
|
mnci add node-function-app api # @nx/node + an Azure Functions v4 overlay
|
|
39
39
|
mnci add npm-lib sdk # @nx/js publishable lib -> packages/
|
|
40
40
|
mnci add internal-lib utils # @nx/js private lib -> libs/
|
|
41
|
+
mnci add react-lib ui # @nx/react publishable component lib -> packages/
|
|
42
|
+
mnci add react-internal-lib design # @nx/react private component lib -> libs/
|
|
41
43
|
|
|
42
44
|
# Python (@mnci/nx-python-pip — pip + Ruff + pytest + PyPA build/twine, no uv)
|
|
43
45
|
mnci add python-app svc # app -> apps/ (wheel, zipped into the drop)
|
|
@@ -124,7 +126,7 @@ that would just fail felt worse than being upfront that it doesn't exist yet.
|
|
|
124
126
|
is ever pushed to `main`; future runs resolve versions from tag names.
|
|
125
127
|
3. Writes `eslint.config.mjs` (three lines importing `@mnci/eslint-config` —
|
|
126
128
|
the whole linting opinion, in one root config), `.prettierrc.json` +
|
|
127
|
-
`.prettierignore`, `.npmrc` (
|
|
129
|
+
`.prettierignore`, `.npmrc` (publish auth — see **Publish auth** below),
|
|
128
130
|
`commitlint.config.mjs`, a husky `commit-msg` hook, the chosen CI provider's
|
|
129
131
|
pipeline file(s)
|
|
130
132
|
(`azure-pipelines.yml` and/or `.github/workflows/ci.yml`, `--ci`, default
|
|
@@ -402,6 +404,42 @@ lint,test,build`. Pushes to `main` then:
|
|
|
402
404
|
_only_ configured provider, since that's the one case a `GITHUB_TOKEN` is
|
|
403
405
|
guaranteed to exist.
|
|
404
406
|
|
|
407
|
+
### Publish auth
|
|
408
|
+
|
|
409
|
+
The generated `.npmrc` differs by registry kind, because the honest answer does.
|
|
410
|
+
|
|
411
|
+
**`--registry azure-artifacts`** routes the workspace's own scope to the feed and
|
|
412
|
+
supplies the feed's credentials:
|
|
413
|
+
|
|
414
|
+
```ini
|
|
415
|
+
@my:registry=https://pkgs.dev.azure.com/<org>/<proj>/_packaging/<feed>/npm/registry/
|
|
416
|
+
//pkgs.dev.azure.com/.../npm/registry/:username=AzureArtifacts
|
|
417
|
+
//pkgs.dev.azure.com/.../npm/registry/:_password=${PAT}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Scope routing is **real protection** here, not decoration: npm prefers a scope's
|
|
421
|
+
registry over the global one when publishing a scoped package, so `@my/*` cannot
|
|
422
|
+
reach npmjs.org by accident. Verified against a real registry — with only the
|
|
423
|
+
scope line set, npm reports `Publishing to <feed>` — and again from a generated
|
|
424
|
+
workspace, whose `npm publish --dry-run` targets the feed.
|
|
425
|
+
|
|
426
|
+
Only the scope is routed, deliberately. A global `registry=` would push every
|
|
427
|
+
install through the feed as well, so `npm ci` would need feed auth just to fetch
|
|
428
|
+
public packages; as generated, public dependencies still come from npmjs.org and
|
|
429
|
+
a developer with no `PAT` set can install normally.
|
|
430
|
+
|
|
431
|
+
**`--registry npm`** gets the auth line and nothing else:
|
|
432
|
+
|
|
433
|
+
```ini
|
|
434
|
+
//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
There is no `@scope:registry` line, and the generated file explains why: npmjs.org
|
|
438
|
+
is already the default, so routing the scope there changes nothing — and calling
|
|
439
|
+
it protection against an accidental public publish would be false, because the
|
|
440
|
+
public registry is the intended target. Worth stating plainly because this file
|
|
441
|
+
previously claimed exactly that protection while emitting no routing line at all.
|
|
442
|
+
|
|
405
443
|
**npm auth** is the base64 `PAT`, read the same way on both providers but from
|
|
406
444
|
a different place: on Azure Pipelines, a **variable group**
|
|
407
445
|
(`--variable-group`, default `Build`) exposes it as `$(PAT)`; on GitHub
|
|
@@ -514,13 +552,10 @@ than a surprise:
|
|
|
514
552
|
|
|
515
553
|
## Known gaps (accepted for the experiment)
|
|
516
554
|
|
|
517
|
-
- **
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
than ship half-wired auth, the CI token export (`NODE_AUTH_TOKEN`/`PAT`) is
|
|
522
|
-
left in place so wiring it later is one line, and the generated `.npmrc`
|
|
523
|
-
carries a comment saying so. Publish-path e2e assertions are relaxed to match.
|
|
555
|
+
- **A scoped package on public npm has no accidental-publish protection**, and
|
|
556
|
+
cannot: npmjs.org is the intended target there, so no `.npmrc` line could
|
|
557
|
+
prevent it. Generate with `--registry azure-artifacts` if you need a scope kept
|
|
558
|
+
off the public registry — that variant routes it (see **Publish auth** above).
|
|
524
559
|
- No `doctor`/`resurrect`/`spell` — out of scope until the model is proven.
|
|
525
560
|
- **Flutter apps build for web only.** Android would require the Android SDK and
|
|
526
561
|
NDK on every build agent; iOS is impossible on a Linux agent regardless. Add
|
package/dist/cli.js
CHANGED
|
@@ -38,8 +38,8 @@ module.exports = __toCommonJS(cli_exports);
|
|
|
38
38
|
var import_commander = require("commander");
|
|
39
39
|
|
|
40
40
|
// src/commands/add.ts
|
|
41
|
-
var
|
|
42
|
-
var
|
|
41
|
+
var import_node_path10 = require("path");
|
|
42
|
+
var import_prompts5 = require("@inquirer/prompts");
|
|
43
43
|
|
|
44
44
|
// src/nx.ts
|
|
45
45
|
var import_cross_spawn = __toESM(require("cross-spawn"));
|
|
@@ -192,6 +192,21 @@ function hasPlugin(workspaceRoot, packageName) {
|
|
|
192
192
|
const installed = { ...manifest.dependencies, ...manifest.devDependencies };
|
|
193
193
|
return Object.hasOwn(installed, packageName);
|
|
194
194
|
}
|
|
195
|
+
function ensurePlugin(workspaceRoot, packageName) {
|
|
196
|
+
if (hasPlugin(workspaceRoot, packageName)) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
logger.step(`Installing Nx plugin ${packageName}`);
|
|
200
|
+
runNx(["add", packageName], workspaceRoot);
|
|
201
|
+
}
|
|
202
|
+
function markPrivate(manifestPath) {
|
|
203
|
+
const manifest = readJson(manifestPath);
|
|
204
|
+
writeFileEnsured(manifestPath, toJson({ ...manifest, private: true }));
|
|
205
|
+
}
|
|
206
|
+
function markPublic(manifestPath) {
|
|
207
|
+
const manifest = readJson(manifestPath);
|
|
208
|
+
writeFileEnsured(manifestPath, toJson({ ...manifest, publishConfig: { access: "public" } }));
|
|
209
|
+
}
|
|
195
210
|
function ensureAdmZip(workspaceRoot) {
|
|
196
211
|
if (hasPlugin(workspaceRoot, "adm-zip")) {
|
|
197
212
|
return;
|
|
@@ -525,7 +540,7 @@ function addGoInternalLib(workspaceRoot, name) {
|
|
|
525
540
|
|
|
526
541
|
// src/commands/add/node.ts
|
|
527
542
|
var import_node_path5 = require("path");
|
|
528
|
-
function
|
|
543
|
+
function ensurePlugin2(workspaceRoot, packageName) {
|
|
529
544
|
if (hasPlugin(workspaceRoot, packageName)) {
|
|
530
545
|
return;
|
|
531
546
|
}
|
|
@@ -533,7 +548,7 @@ function ensurePlugin(workspaceRoot, packageName) {
|
|
|
533
548
|
runNx(["add", packageName], workspaceRoot);
|
|
534
549
|
}
|
|
535
550
|
function runNodeApp(workspaceRoot, name, stack, framework) {
|
|
536
|
-
|
|
551
|
+
ensurePlugin2(workspaceRoot, "@nx/node");
|
|
537
552
|
runNx(
|
|
538
553
|
[
|
|
539
554
|
"g",
|
|
@@ -699,10 +714,6 @@ async function addNpmLib(workspaceRoot, name, options, kindProvided, stack) {
|
|
|
699
714
|
removeGeneratedEslintConfig(workspaceRoot, `packages/${name}`);
|
|
700
715
|
registerProjectCommands(workspaceRoot, name, { build: true });
|
|
701
716
|
}
|
|
702
|
-
function markPublic(manifestPath) {
|
|
703
|
-
const manifest = readJson(manifestPath);
|
|
704
|
-
writeFileEnsured(manifestPath, toJson({ ...manifest, publishConfig: { access: "public" } }));
|
|
705
|
-
}
|
|
706
717
|
|
|
707
718
|
// src/commands/add/python.ts
|
|
708
719
|
var import_node_fs4 = require("fs");
|
|
@@ -926,13 +937,6 @@ async function addPythonVendor(workspaceRoot, consumer, options, kindProvided) {
|
|
|
926
937
|
// src/commands/add/reactApp.ts
|
|
927
938
|
var import_node_fs5 = require("fs");
|
|
928
939
|
var import_node_path8 = require("path");
|
|
929
|
-
function ensurePlugin2(workspaceRoot, packageName) {
|
|
930
|
-
if (hasPlugin(workspaceRoot, packageName)) {
|
|
931
|
-
return;
|
|
932
|
-
}
|
|
933
|
-
logger.step(`Installing Nx plugin ${packageName}`);
|
|
934
|
-
runNx(["add", packageName], workspaceRoot);
|
|
935
|
-
}
|
|
936
940
|
var REACT_ENVIRONMENTS = ["dev", "uat", "prod"];
|
|
937
941
|
function reactEnvironmentFile(environment) {
|
|
938
942
|
return `# Vite compiles VITE_-prefixed vars into the '${environment}' bundle at build time.
|
|
@@ -985,7 +989,7 @@ ${allow}
|
|
|
985
989
|
`);
|
|
986
990
|
}
|
|
987
991
|
function addReactApp(workspaceRoot, name, stack) {
|
|
988
|
-
|
|
992
|
+
ensurePlugin(workspaceRoot, "@nx/react");
|
|
989
993
|
runNx(
|
|
990
994
|
[
|
|
991
995
|
"g",
|
|
@@ -1011,9 +1015,73 @@ function addReactApp(workspaceRoot, name, stack) {
|
|
|
1011
1015
|
registerProjectCommands(workspaceRoot, name, { build: true, start: `nx run ${name}:serve` });
|
|
1012
1016
|
}
|
|
1013
1017
|
|
|
1018
|
+
// src/commands/add/reactLib.ts
|
|
1019
|
+
var import_node_path9 = require("path");
|
|
1020
|
+
var WRONG_TYPES_PATH = "./dist/index.esm.d.ts";
|
|
1021
|
+
var ACTUAL_TYPES_PATH = "./dist/index.d.ts";
|
|
1022
|
+
function repairTypesPath(manifestPath) {
|
|
1023
|
+
const manifest = readJson(manifestPath);
|
|
1024
|
+
if (manifest.types === WRONG_TYPES_PATH) {
|
|
1025
|
+
manifest.types = ACTUAL_TYPES_PATH;
|
|
1026
|
+
}
|
|
1027
|
+
const dot = manifest.exports?.["."];
|
|
1028
|
+
if (typeof dot === "object" && dot.types === WRONG_TYPES_PATH) {
|
|
1029
|
+
dot.types = ACTUAL_TYPES_PATH;
|
|
1030
|
+
}
|
|
1031
|
+
writeFileEnsured(manifestPath, toJson(manifest));
|
|
1032
|
+
}
|
|
1033
|
+
var REACT_LIB_BUNDLER = "rollup";
|
|
1034
|
+
async function addReactLib(workspaceRoot, name, options, kindProvided, stack) {
|
|
1035
|
+
ensurePlugin(workspaceRoot, "@nx/react");
|
|
1036
|
+
const scope = options.scope ?? (kindProvided ? defaultScope(workspaceRoot) : await promptText("npm scope for the published package", defaultScope(workspaceRoot)));
|
|
1037
|
+
runNx(
|
|
1038
|
+
[
|
|
1039
|
+
"g",
|
|
1040
|
+
"@nx/react:library",
|
|
1041
|
+
`packages/${name}`,
|
|
1042
|
+
`--name=${name}`,
|
|
1043
|
+
"--publishable",
|
|
1044
|
+
`--importPath=${scope}/${name}`,
|
|
1045
|
+
`--bundler=${REACT_LIB_BUNDLER}`,
|
|
1046
|
+
`--unitTestRunner=${stack.testRunner}`,
|
|
1047
|
+
"--linter=none",
|
|
1048
|
+
"--no-interactive"
|
|
1049
|
+
],
|
|
1050
|
+
workspaceRoot
|
|
1051
|
+
);
|
|
1052
|
+
const publishableManifest = (0, import_node_path9.join)(workspaceRoot, "packages", name, "package.json");
|
|
1053
|
+
markPublic(publishableManifest);
|
|
1054
|
+
repairTypesPath(publishableManifest);
|
|
1055
|
+
removeGeneratedEslintConfig(workspaceRoot, `packages/${name}`);
|
|
1056
|
+
registerProjectCommands(workspaceRoot, name, { build: true });
|
|
1057
|
+
}
|
|
1058
|
+
function addReactInternalLib(workspaceRoot, name, stack) {
|
|
1059
|
+
ensurePlugin(workspaceRoot, "@nx/react");
|
|
1060
|
+
runNx(
|
|
1061
|
+
[
|
|
1062
|
+
"g",
|
|
1063
|
+
"@nx/react:library",
|
|
1064
|
+
`libs/${name}`,
|
|
1065
|
+
`--name=${name}`,
|
|
1066
|
+
`--bundler=${REACT_LIB_BUNDLER}`,
|
|
1067
|
+
`--unitTestRunner=${stack.testRunner}`,
|
|
1068
|
+
"--linter=none",
|
|
1069
|
+
"--no-interactive"
|
|
1070
|
+
],
|
|
1071
|
+
workspaceRoot
|
|
1072
|
+
);
|
|
1073
|
+
const privateManifest = (0, import_node_path9.join)(workspaceRoot, "libs", name, "package.json");
|
|
1074
|
+
markPrivate(privateManifest);
|
|
1075
|
+
repairTypesPath(privateManifest);
|
|
1076
|
+
removeGeneratedEslintConfig(workspaceRoot, `libs/${name}`);
|
|
1077
|
+
registerProjectCommands(workspaceRoot, name, { build: true });
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1014
1080
|
// src/commands/add.ts
|
|
1015
1081
|
var PROJECT_KINDS = [
|
|
1016
1082
|
"react-app",
|
|
1083
|
+
"react-lib",
|
|
1084
|
+
"react-internal-lib",
|
|
1017
1085
|
"node-app",
|
|
1018
1086
|
"node-function-app",
|
|
1019
1087
|
"npm-lib",
|
|
@@ -1033,12 +1101,12 @@ var PROJECT_KINDS = [
|
|
|
1033
1101
|
];
|
|
1034
1102
|
async function runAdd(kind, name, options) {
|
|
1035
1103
|
const workspaceRoot = process.cwd();
|
|
1036
|
-
if (!fileExists((0,
|
|
1104
|
+
if (!fileExists((0, import_node_path10.join)(workspaceRoot, "nx.json"))) {
|
|
1037
1105
|
throw new Error("No nx.json found here. Run `add` from the workspace root.");
|
|
1038
1106
|
}
|
|
1039
1107
|
const stack = readWorkspaceStack(workspaceRoot);
|
|
1040
1108
|
const kindProvided = kind !== void 0;
|
|
1041
|
-
const resolvedKind = kind ?? await (0,
|
|
1109
|
+
const resolvedKind = kind ?? await (0, import_prompts5.select)({
|
|
1042
1110
|
message: "What kind of project?",
|
|
1043
1111
|
choices: PROJECT_KINDS.map((value) => ({ name: value, value }))
|
|
1044
1112
|
});
|
|
@@ -1049,6 +1117,14 @@ async function runAdd(kind, name, options) {
|
|
|
1049
1117
|
addReactApp(workspaceRoot, resolvedName, stack);
|
|
1050
1118
|
break;
|
|
1051
1119
|
}
|
|
1120
|
+
case "react-lib": {
|
|
1121
|
+
await addReactLib(workspaceRoot, resolvedName, options, kindProvided, stack);
|
|
1122
|
+
break;
|
|
1123
|
+
}
|
|
1124
|
+
case "react-internal-lib": {
|
|
1125
|
+
addReactInternalLib(workspaceRoot, resolvedName, stack);
|
|
1126
|
+
break;
|
|
1127
|
+
}
|
|
1052
1128
|
case "node-app": {
|
|
1053
1129
|
addNodeApp(workspaceRoot, resolvedName, stack, options.framework);
|
|
1054
1130
|
break;
|
|
@@ -1074,7 +1150,7 @@ async function runAdd(kind, name, options) {
|
|
|
1074
1150
|
],
|
|
1075
1151
|
workspaceRoot
|
|
1076
1152
|
);
|
|
1077
|
-
markPrivate((0,
|
|
1153
|
+
markPrivate((0, import_node_path10.join)(workspaceRoot, "libs", resolvedName, "package.json"));
|
|
1078
1154
|
removeGeneratedEslintConfig(workspaceRoot, `libs/${resolvedName}`);
|
|
1079
1155
|
registerProjectCommands(workspaceRoot, resolvedName, { build: true });
|
|
1080
1156
|
break;
|
|
@@ -1150,43 +1226,73 @@ function syncProjectReferences(workspaceRoot) {
|
|
|
1150
1226
|
}
|
|
1151
1227
|
function readWorkspaceStack(workspaceRoot) {
|
|
1152
1228
|
const nxJson = readJson(
|
|
1153
|
-
(0,
|
|
1229
|
+
(0, import_node_path10.join)(workspaceRoot, "nx.json")
|
|
1154
1230
|
);
|
|
1155
1231
|
const stack = nxJson.mnci?.stack;
|
|
1156
1232
|
return {
|
|
1157
1233
|
testRunner: stack?.testRunner === "vitest" ? "vitest" : "jest"
|
|
1158
1234
|
};
|
|
1159
1235
|
}
|
|
1160
|
-
function markPrivate(manifestPath) {
|
|
1161
|
-
const manifest = readJson(manifestPath);
|
|
1162
|
-
writeFileEnsured(manifestPath, toJson({ ...manifest, private: true }));
|
|
1163
|
-
}
|
|
1164
1236
|
|
|
1165
1237
|
// src/commands/interactive.ts
|
|
1166
|
-
var
|
|
1167
|
-
var
|
|
1238
|
+
var import_node_path14 = require("path");
|
|
1239
|
+
var import_prompts8 = require("@inquirer/prompts");
|
|
1168
1240
|
|
|
1169
1241
|
// src/commands/new.ts
|
|
1170
|
-
var
|
|
1242
|
+
var import_node_path12 = require("path");
|
|
1171
1243
|
|
|
1172
1244
|
// src/overlay.ts
|
|
1173
1245
|
var import_node_fs6 = require("fs");
|
|
1174
|
-
var
|
|
1246
|
+
var import_node_path11 = require("path");
|
|
1175
1247
|
var DEFAULT_STACK = { testRunner: "jest" };
|
|
1176
1248
|
var PRETTIER_VERSION = "^3.8.1";
|
|
1177
1249
|
var TS_COMPILER_DEPENDENCIES = {
|
|
1178
1250
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
1179
1251
|
typescript: "npm:@typescript/typescript6@^6.0.2"
|
|
1180
1252
|
};
|
|
1181
|
-
function
|
|
1182
|
-
|
|
1253
|
+
function registryUrl(registry) {
|
|
1254
|
+
if (registry.kind === "azure-artifacts") {
|
|
1255
|
+
return `https://pkgs.dev.azure.com/${registry.organization}/${registry.project}/_packaging/${registry.artifactsFeed}/npm/registry/`;
|
|
1256
|
+
}
|
|
1257
|
+
return void 0;
|
|
1258
|
+
}
|
|
1259
|
+
function npmrcContent(registry, scope) {
|
|
1260
|
+
if (registry.kind === "npm") {
|
|
1261
|
+
return `; Publish authentication for the public npm registry.
|
|
1183
1262
|
;
|
|
1184
|
-
;
|
|
1185
|
-
;
|
|
1186
|
-
|
|
1187
|
-
|
|
1263
|
+
; NODE_AUTH_TOKEN is exported by the generated CI's release step; an unset value
|
|
1264
|
+
; is harmless locally, so no token is needed for day-to-day work.
|
|
1265
|
+
//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}
|
|
1266
|
+
|
|
1267
|
+
; There is deliberately NO '${scope}:registry' line here. npmjs.org is already
|
|
1268
|
+
; the default registry, so routing the scope to it would change nothing \u2014 and
|
|
1269
|
+
; presenting that as protection against an accidental public publish would be
|
|
1270
|
+
; false, because the public registry is the intended target. (An earlier version
|
|
1271
|
+
; of this file made exactly that claim while emitting no routing line at all.)
|
|
1272
|
+
; To keep a scoped package off npmjs.org, use a private feed: generate with
|
|
1273
|
+
; --registry azure-artifacts, which does route the scope.
|
|
1274
|
+
`;
|
|
1275
|
+
}
|
|
1276
|
+
const feedUrl = registryUrl(registry);
|
|
1277
|
+
const feedKey = feedUrl.replace(/^https:/, "");
|
|
1278
|
+
return `; Publish + resolution routing for this workspace's own scope.
|
|
1279
|
+
;
|
|
1280
|
+
; '${scope}:registry' sends BOTH resolution and 'npm publish' of ${scope}/* to the
|
|
1281
|
+
; feed: npm prefers a scope's registry over the global one when publishing a
|
|
1282
|
+
; scoped package, so a ${scope}/* package cannot reach npmjs.org by accident.
|
|
1188
1283
|
;
|
|
1189
|
-
;
|
|
1284
|
+
; Only the scope is routed, on purpose. A global 'registry=' would send every
|
|
1285
|
+
; install through the feed as well, so 'npm ci' would need feed auth just to fetch
|
|
1286
|
+
; public packages.
|
|
1287
|
+
${scope}:registry=${feedUrl}
|
|
1288
|
+
|
|
1289
|
+
; Feed credentials. PAT is the BASE64 value Azure Artifacts' "Connect to feed"
|
|
1290
|
+
; instructions give you, which is exactly what _password expects, so it is used
|
|
1291
|
+
; as-is. (twine wants the RAW token; the CI release step decodes it there.)
|
|
1292
|
+
; Azure ignores the username, and npm requires an email it never uses.
|
|
1293
|
+
${feedKey}:username=AzureArtifacts
|
|
1294
|
+
${feedKey}:_password=\${PAT}
|
|
1295
|
+
${feedKey}:email=npm-requires-this-and-never-uses-it
|
|
1190
1296
|
`;
|
|
1191
1297
|
}
|
|
1192
1298
|
function releaseConfig(ci) {
|
|
@@ -1378,7 +1484,7 @@ function mnciConfig(options) {
|
|
|
1378
1484
|
};
|
|
1379
1485
|
}
|
|
1380
1486
|
function readMnciConfig(workspaceRoot) {
|
|
1381
|
-
const nxJson = readJson((0,
|
|
1487
|
+
const nxJson = readJson((0, import_node_path11.join)(workspaceRoot, "nx.json"));
|
|
1382
1488
|
return nxJson.mnci ?? {};
|
|
1383
1489
|
}
|
|
1384
1490
|
function pythonPublishUrl(registry) {
|
|
@@ -1430,9 +1536,18 @@ function azurePipelinesYaml(agent, variableGroup, pythonPublishUrl2, registryKin
|
|
|
1430
1536
|
# runs unchanged on Linux, macOS and Windows agents.
|
|
1431
1537
|
|
|
1432
1538
|
trigger:
|
|
1539
|
+
# While a run is in progress, queue further pushes and run them together rather
|
|
1540
|
+
# than concurrently. This is the closest thing Azure Pipelines YAML has to
|
|
1541
|
+
# GitHub's concurrency group, and it is load-bearing on main for the same
|
|
1542
|
+
# reason: two concurrent 'nx release' runs would race to create the same tag.
|
|
1543
|
+
batch: true
|
|
1433
1544
|
branches:
|
|
1434
1545
|
include: [main]
|
|
1435
1546
|
|
|
1547
|
+
# Note: cancelling a superseded PR validation run is NOT a YAML setting on Azure
|
|
1548
|
+
# \u2014 it lives in the branch policy ("Build validation -> automatically cancel").
|
|
1549
|
+
# Nothing here can express it, so it is left to whoever configures the policy
|
|
1550
|
+
# rather than faked with a batch setting that would only delay PR runs.
|
|
1436
1551
|
pr:
|
|
1437
1552
|
branches:
|
|
1438
1553
|
include: [main]
|
|
@@ -1445,6 +1560,12 @@ variables:
|
|
|
1445
1560
|
# for an Azure Artifacts feed, or a raw npm automation token as \`NPM_TOKEN\`
|
|
1446
1561
|
# for public npm. Mark it secret in Library. Add app build vars here too if needed.
|
|
1447
1562
|
- group: ${variableGroup}
|
|
1563
|
+
# Relocates npm's cache inside the pipeline workspace so Cache@2 can restore it
|
|
1564
|
+
# (the default ~/.npm is outside the cacheable area on hosted agents). npm reads
|
|
1565
|
+
# this as an ordinary env var, so it needs no config file and works on every
|
|
1566
|
+
# agent OS.
|
|
1567
|
+
- name: npm_config_cache
|
|
1568
|
+
value: $(Pipeline.Workspace)/.npm
|
|
1448
1569
|
|
|
1449
1570
|
steps:
|
|
1450
1571
|
- checkout: self
|
|
@@ -1469,6 +1590,20 @@ steps:
|
|
|
1469
1590
|
inputs:
|
|
1470
1591
|
version: 24.x
|
|
1471
1592
|
|
|
1593
|
+
# The Azure counterpart of \`cache: npm\` on actions/setup-node: restores npm's
|
|
1594
|
+
# download cache so \`npm ci\` does not re-fetch every tarball on every run.
|
|
1595
|
+
# Keyed on the lockfile, so a dependency change misses and anything else hits;
|
|
1596
|
+
# restoreKeys falls back to the newest cache for this OS on a miss, which still
|
|
1597
|
+
# avoids a fully cold install. Agent.OS is in the key because a cached native
|
|
1598
|
+
# module built for one OS is not reusable on another.
|
|
1599
|
+
- task: Cache@2
|
|
1600
|
+
displayName: Cache npm packages
|
|
1601
|
+
inputs:
|
|
1602
|
+
key: 'npm | "$(Agent.OS)" | package-lock.json'
|
|
1603
|
+
restoreKeys: |
|
|
1604
|
+
npm | "$(Agent.OS)"
|
|
1605
|
+
path: $(npm_config_cache)
|
|
1606
|
+
|
|
1472
1607
|
- script: npm ci
|
|
1473
1608
|
displayName: Install dependencies
|
|
1474
1609
|
env:
|
|
@@ -1626,6 +1761,19 @@ on:
|
|
|
1626
1761
|
pull_request:
|
|
1627
1762
|
branches: [main]
|
|
1628
1763
|
|
|
1764
|
+
concurrency:
|
|
1765
|
+
# One run at a time per ref, but \`cancel-in-progress\` is an **expression** on
|
|
1766
|
+
# purpose rather than a flat \`true\`.
|
|
1767
|
+
#
|
|
1768
|
+
# A superseded PR run is pure waste, so cancel it. A run on \`main\` must never
|
|
1769
|
+
# be cancelled: it publishes packages and pushes release tags, and killing it
|
|
1770
|
+
# part-way can leave a tag pushed with the publish only half done \u2014 a state no
|
|
1771
|
+
# rerun repairs cleanly, because the version is then already tagged. Those runs
|
|
1772
|
+
# queue instead, which also stops two \`nx release\` invocations racing to create
|
|
1773
|
+
# the same tag when two commits land close together.
|
|
1774
|
+
group: \${{ github.workflow }}-\${{ github.ref }}
|
|
1775
|
+
cancel-in-progress: \${{ github.event_name == 'pull_request' }}
|
|
1776
|
+
|
|
1629
1777
|
permissions:
|
|
1630
1778
|
# Lets the release step push the version tag nx release creates back to main,
|
|
1631
1779
|
# and (github-only provider) create the GitHub Release itself.
|
|
@@ -1649,6 +1797,11 @@ jobs:
|
|
|
1649
1797
|
- uses: actions/setup-node@v4
|
|
1650
1798
|
with:
|
|
1651
1799
|
node-version: 24
|
|
1800
|
+
# Caches ~/.npm keyed on package-lock.json, so \`npm ci\` restores from
|
|
1801
|
+
# the local cache instead of re-downloading every tarball on every run.
|
|
1802
|
+
# Nothing to invalidate by hand: the action keys on the lockfile, so a
|
|
1803
|
+
# dependency change misses the cache and a no-op change hits it.
|
|
1804
|
+
cache: npm
|
|
1652
1805
|
|
|
1653
1806
|
- run: npm ci
|
|
1654
1807
|
name: Install dependencies
|
|
@@ -1828,7 +1981,7 @@ updates:
|
|
|
1828
1981
|
var NX_SCAFFOLDING_TO_REMOVE = [".prettierrc", ".vscode"];
|
|
1829
1982
|
function removeNxScaffolding(workspaceRoot) {
|
|
1830
1983
|
for (const entry of NX_SCAFFOLDING_TO_REMOVE) {
|
|
1831
|
-
(0, import_node_fs6.rmSync)((0,
|
|
1984
|
+
(0, import_node_fs6.rmSync)((0, import_node_path11.join)(workspaceRoot, entry), { recursive: true, force: true });
|
|
1832
1985
|
}
|
|
1833
1986
|
removeProjectEslintConfigs(workspaceRoot);
|
|
1834
1987
|
}
|
|
@@ -1837,11 +1990,11 @@ function removeProjectEslintConfigs(workspaceRoot) {
|
|
|
1837
1990
|
cwd: workspaceRoot
|
|
1838
1991
|
});
|
|
1839
1992
|
for (const match of matches) {
|
|
1840
|
-
(0, import_node_fs6.rmSync)((0,
|
|
1993
|
+
(0, import_node_fs6.rmSync)((0, import_node_path11.join)(workspaceRoot, match), { force: true });
|
|
1841
1994
|
}
|
|
1842
1995
|
}
|
|
1843
1996
|
function applyOverlay(workspaceRoot, options) {
|
|
1844
|
-
const nxJsonPath = (0,
|
|
1997
|
+
const nxJsonPath = (0, import_node_path11.join)(workspaceRoot, "nx.json");
|
|
1845
1998
|
const nxJson = readJson(nxJsonPath);
|
|
1846
1999
|
const generators = {
|
|
1847
2000
|
...nxJson.generators,
|
|
@@ -1851,7 +2004,7 @@ function applyOverlay(workspaceRoot, options) {
|
|
|
1851
2004
|
const mnci = { ...nxJson.mnci, ...mnciConfig(options) };
|
|
1852
2005
|
const patched = withEslintPlugin(withReleaseConfig(nxJson, options.ci));
|
|
1853
2006
|
writeFileEnsured(nxJsonPath, toJson({ ...patched, generators, sync, mnci }));
|
|
1854
|
-
const manifestPath = (0,
|
|
2007
|
+
const manifestPath = (0, import_node_path11.join)(workspaceRoot, "package.json");
|
|
1855
2008
|
const manifest = readJson(manifestPath);
|
|
1856
2009
|
const scripts = {
|
|
1857
2010
|
...manifest.scripts,
|
|
@@ -1869,34 +2022,34 @@ function applyOverlay(workspaceRoot, options) {
|
|
|
1869
2022
|
manifestPath,
|
|
1870
2023
|
toJson({ ...manifest, name: `${options.scope}/source`, scripts, devDependencies: devDeps })
|
|
1871
2024
|
);
|
|
1872
|
-
writeFileEnsured((0,
|
|
1873
|
-
writeFileEnsured((0,
|
|
1874
|
-
const hookPath = (0,
|
|
2025
|
+
writeFileEnsured((0, import_node_path11.join)(workspaceRoot, ".npmrc"), npmrcContent(options.registry, options.scope));
|
|
2026
|
+
writeFileEnsured((0, import_node_path11.join)(workspaceRoot, "commitlint.config.mjs"), COMMITLINT_CONFIG);
|
|
2027
|
+
const hookPath = (0, import_node_path11.join)(workspaceRoot, ".husky/commit-msg");
|
|
1875
2028
|
writeFileEnsured(hookPath, COMMIT_MSG_HOOK);
|
|
1876
2029
|
markExecutable(hookPath);
|
|
1877
|
-
writeFileEnsured((0,
|
|
1878
|
-
writeFileEnsured((0,
|
|
1879
|
-
writeFileEnsured((0,
|
|
2030
|
+
writeFileEnsured((0, import_node_path11.join)(workspaceRoot, "eslint.config.mjs"), ESLINT_CONFIG);
|
|
2031
|
+
writeFileEnsured((0, import_node_path11.join)(workspaceRoot, ".prettierrc.json"), PRETTIER_CONFIG);
|
|
2032
|
+
writeFileEnsured((0, import_node_path11.join)(workspaceRoot, ".prettierignore"), PRETTIER_IGNORE);
|
|
1880
2033
|
removeNxScaffolding(workspaceRoot);
|
|
1881
|
-
const codeWorkspacePath = (0,
|
|
2034
|
+
const codeWorkspacePath = (0, import_node_path11.join)(workspaceRoot, `${options.workspaceName}.code-workspace`);
|
|
1882
2035
|
const existing = readCodeWorkspace(codeWorkspacePath);
|
|
1883
2036
|
writeFileEnsured(codeWorkspacePath, vscodeWorkspace(options.workspaceName, existing?.tasks));
|
|
1884
2037
|
if (options.workspaceName !== "undefined") {
|
|
1885
|
-
(0, import_node_fs6.rmSync)((0,
|
|
2038
|
+
(0, import_node_fs6.rmSync)((0, import_node_path11.join)(workspaceRoot, "undefined.code-workspace"), { force: true });
|
|
1886
2039
|
}
|
|
1887
2040
|
const publishUrl = pythonPublishUrl(options.registry);
|
|
1888
2041
|
if (options.ci === "azure" || options.ci === "both") {
|
|
1889
2042
|
writeFileEnsured(
|
|
1890
|
-
(0,
|
|
2043
|
+
(0, import_node_path11.join)(workspaceRoot, "azure-pipelines.yml"),
|
|
1891
2044
|
azurePipelinesYaml(options.agent, options.variableGroup, publishUrl, options.registry.kind)
|
|
1892
2045
|
);
|
|
1893
2046
|
}
|
|
1894
2047
|
if (options.ci === "github" || options.ci === "both") {
|
|
1895
2048
|
writeFileEnsured(
|
|
1896
|
-
(0,
|
|
2049
|
+
(0, import_node_path11.join)(workspaceRoot, ".github/workflows/ci.yml"),
|
|
1897
2050
|
githubActionsYaml(options.agent, publishUrl, options.registry.kind, options.ci)
|
|
1898
2051
|
);
|
|
1899
|
-
writeFileEnsured((0,
|
|
2052
|
+
writeFileEnsured((0, import_node_path11.join)(workspaceRoot, ".github/dependabot.yml"), DEPENDABOT_CONFIG);
|
|
1900
2053
|
}
|
|
1901
2054
|
}
|
|
1902
2055
|
|
|
@@ -1965,7 +2118,7 @@ async function runNew(name, options) {
|
|
|
1965
2118
|
],
|
|
1966
2119
|
process.cwd()
|
|
1967
2120
|
);
|
|
1968
|
-
const workspaceRoot = (0,
|
|
2121
|
+
const workspaceRoot = (0, import_node_path12.join)(process.cwd(), workspaceName);
|
|
1969
2122
|
logger.step(
|
|
1970
2123
|
"Applying MoNecromanCI overlay (VS Code workspace, release config, .npmrc, commitlint, pipeline, stack)"
|
|
1971
2124
|
);
|
|
@@ -1984,9 +2137,8 @@ async function runNew(name, options) {
|
|
|
1984
2137
|
runPrettier(workspaceRoot);
|
|
1985
2138
|
logger.success("Done. Next steps:");
|
|
1986
2139
|
logger.info(` cd ${workspaceName}`);
|
|
1987
|
-
logger.info(
|
|
1988
|
-
|
|
1989
|
-
);
|
|
2140
|
+
logger.info(" mnci add react-app web # or: react-lib, react-internal-lib, node-app,");
|
|
2141
|
+
logger.info(" # node-function-app, npm-lib, internal-lib,");
|
|
1990
2142
|
logger.info(
|
|
1991
2143
|
" # python-app, python-function-app, python-lib, python-internal-lib,"
|
|
1992
2144
|
);
|
|
@@ -2001,7 +2153,7 @@ async function runNew(name, options) {
|
|
|
2001
2153
|
|
|
2002
2154
|
// src/commands/upgrade.ts
|
|
2003
2155
|
var import_node_fs7 = require("fs");
|
|
2004
|
-
var
|
|
2156
|
+
var import_node_path13 = require("path");
|
|
2005
2157
|
function resolveRegistry2(options, persisted) {
|
|
2006
2158
|
if (options.registry === "azure-artifacts" || options.organization && options.artifactsFeed) {
|
|
2007
2159
|
const persistedAzure = persisted?.kind === "azure-artifacts" ? persisted : void 0;
|
|
@@ -2038,7 +2190,7 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
|
|
|
2038
2190
|
}
|
|
2039
2191
|
} catch {
|
|
2040
2192
|
}
|
|
2041
|
-
return (0,
|
|
2193
|
+
return (0, import_node_path13.basename)(workspaceRoot);
|
|
2042
2194
|
}
|
|
2043
2195
|
function resolveOverlayOptions(workspaceRoot, options, persisted) {
|
|
2044
2196
|
const scope = options.scope ?? persisted.scope;
|
|
@@ -2076,7 +2228,7 @@ function resolveOverlayOptions(workspaceRoot, options, persisted) {
|
|
|
2076
2228
|
};
|
|
2077
2229
|
}
|
|
2078
2230
|
function runUpgrade(workspaceRoot, options) {
|
|
2079
|
-
if (!fileExists((0,
|
|
2231
|
+
if (!fileExists((0, import_node_path13.join)(workspaceRoot, "nx.json"))) {
|
|
2080
2232
|
throw new Error(
|
|
2081
2233
|
`No nx.json found in ${workspaceRoot} \u2014 this does not look like an Nx workspace. Run 'mnci upgrade' from the workspace root.`
|
|
2082
2234
|
);
|
|
@@ -2093,14 +2245,14 @@ function runUpgrade(workspaceRoot, options) {
|
|
|
2093
2245
|
|
|
2094
2246
|
// src/commands/interactive.ts
|
|
2095
2247
|
async function runInteractive() {
|
|
2096
|
-
const inWorkspace = fileExists((0,
|
|
2248
|
+
const inWorkspace = fileExists((0, import_node_path14.join)(process.cwd(), "nx.json"));
|
|
2097
2249
|
const newChoice = { name: "Create a new monorepo", value: "new" };
|
|
2098
2250
|
const addChoice = { name: "Add a project to this workspace", value: "add" };
|
|
2099
2251
|
const upgradeChoice = {
|
|
2100
2252
|
name: "Upgrade this workspace (re-apply the latest overlay)",
|
|
2101
2253
|
value: "upgrade"
|
|
2102
2254
|
};
|
|
2103
|
-
const action = await (0,
|
|
2255
|
+
const action = await (0, import_prompts8.select)({
|
|
2104
2256
|
message: "What would you like to do?",
|
|
2105
2257
|
choices: inWorkspace ? [addChoice, upgradeChoice, newChoice] : [newChoice, addChoice, upgradeChoice]
|
|
2106
2258
|
});
|
|
@@ -2115,7 +2267,7 @@ async function runInteractive() {
|
|
|
2115
2267
|
|
|
2116
2268
|
// src/util/versionChecker.ts
|
|
2117
2269
|
var import_node_fs8 = require("fs");
|
|
2118
|
-
var
|
|
2270
|
+
var import_node_path15 = require("path");
|
|
2119
2271
|
var import_cross_spawn2 = __toESM(require("cross-spawn"));
|
|
2120
2272
|
var PACKAGE_NAME = "@mnci/cli";
|
|
2121
2273
|
var REGISTRY_TIMEOUT_MS = 2e3;
|
|
@@ -2171,7 +2323,7 @@ function releaseTriple(version) {
|
|
|
2171
2323
|
}
|
|
2172
2324
|
function readCliVersion(packageDirectory) {
|
|
2173
2325
|
try {
|
|
2174
|
-
const manifestPath = (0,
|
|
2326
|
+
const manifestPath = (0, import_node_path15.join)(packageDirectory, "..", "package.json");
|
|
2175
2327
|
const manifest = JSON.parse((0, import_node_fs8.readFileSync)(manifestPath, "utf8"));
|
|
2176
2328
|
return manifest.version ?? "0.0.0";
|
|
2177
2329
|
} catch {
|