@mnci/cli 4.0.0 → 4.0.2
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 +128 -0
- package/dist/cli.js +166 -30
- package/dist/cli.js.map +1 -1
- package/package.json +2 -9
package/README.md
CHANGED
|
@@ -119,6 +119,39 @@ accordingly, `start` marked `isBackground` since it runs a process that
|
|
|
119
119
|
doesn't exit on its own. Re-running `add` for the same project name
|
|
120
120
|
overwrites its own scripts/tasks rather than duplicating them.
|
|
121
121
|
|
|
122
|
+
#### Run and Debug: the `launch` section
|
|
123
|
+
|
|
124
|
+
Tasks are reachable **only** through Terminal → Run Task. The **Run and Debug**
|
|
125
|
+
panel reads a separate `launch` section, so a workspace with tasks alone offers
|
|
126
|
+
nothing in the dropdown people actually open. Every generated workspace therefore
|
|
127
|
+
also gets four launch configurations, one per verify target:
|
|
128
|
+
|
|
129
|
+
| configuration | runs |
|
|
130
|
+
| --- | --- |
|
|
131
|
+
| `mnci: build` | `npm run build` |
|
|
132
|
+
| `mnci: test` | `npm run test` |
|
|
133
|
+
| `mnci: lint` | `npm run lint` |
|
|
134
|
+
| `mnci: typecheck` | `npm run typecheck` |
|
|
135
|
+
|
|
136
|
+
Three details are load-bearing rather than incidental:
|
|
137
|
+
|
|
138
|
+
- **`type: node-terminal`, not `node`.** `nx run-many` executes every target in a
|
|
139
|
+
**child** process. A plain `node` launch attaches to the Nx parent alone, so a
|
|
140
|
+
breakpoint inside a spec never binds; `node-terminal` runs the command in VS
|
|
141
|
+
Code's JS Debug Terminal, which instruments children as they spawn. It also avoids
|
|
142
|
+
a second trap — a `node` launch defaults to `internalConsole`, which renders none
|
|
143
|
+
of Nx's progress output, so a build there looks like it has hung.
|
|
144
|
+
- **They drive `npm run <script>`, never a path into `node_modules`.** The obvious
|
|
145
|
+
`program: node_modules/nx/bin/nx.js` is wrong: Nx ships its bin at
|
|
146
|
+
`dist/bin/nx.js`, and that path moves between versions. Driving the root script
|
|
147
|
+
tracks whatever it does, so a change to the scripts reaches these for free.
|
|
148
|
+
- **`cwd` is `${workspaceFolder:<name>}`, scoped by folder name.** A bare
|
|
149
|
+
`${workspaceFolder}` is ambiguous the moment a second folder joins the workspace,
|
|
150
|
+
and VS Code then refuses to resolve it — breaking all four at once.
|
|
151
|
+
|
|
152
|
+
Your own configurations are safe: `mnci upgrade` replaces only the entries named
|
|
153
|
+
`mnci: *` and carries every other one through untouched.
|
|
154
|
+
|
|
122
155
|
`:start` resolves differently per kind — an existing generator target where
|
|
123
156
|
one already exists, a small `nx:run-commands` target mnci writes where none
|
|
124
157
|
did:
|
|
@@ -191,6 +224,12 @@ back and carried through unchanged**. Those tasks are per-project state written
|
|
|
191
224
|
`mnci add`, not overlay-owned, so regenerating them wholesale would wipe every
|
|
192
225
|
project's build/qa/start entry on upgrade.
|
|
193
226
|
|
|
227
|
+
The `launch` array is handled differently again — **merged, not carried through**.
|
|
228
|
+
mnci owns the four `mnci: *` configurations and replaces them, while any
|
|
229
|
+
configuration you added yourself survives. The asymmetry is deliberate: tasks are
|
|
230
|
+
written by `mnci add` and the overlay has no idea which projects exist, whereas the
|
|
231
|
+
launch entries are entirely overlay-authored and should track an upgrade.
|
|
232
|
+
|
|
194
233
|
`upgrade` also **deletes** things, which is stronger than the overwriting it
|
|
195
234
|
has always done — one more reason to run `git diff` first, as the command's own
|
|
196
235
|
output tells you to:
|
|
@@ -508,6 +547,95 @@ repo settings. Either way it's mapped as `env` on the npm steps and read by
|
|
|
508
547
|
the root `.npmrc`'s `_password` block — the PAT value never lands in a file.
|
|
509
548
|
No `npmAuthenticate@0` task (it would overwrite the hand-set password).
|
|
510
549
|
|
|
550
|
+
#### Why `_password` and never `_authToken` — read this before "fixing" it
|
|
551
|
+
|
|
552
|
+
Basic auth here is not a legacy choice. Azure Artifacts answers an unauthenticated
|
|
553
|
+
`PUT` to its npm publish endpoint with:
|
|
554
|
+
|
|
555
|
+
```
|
|
556
|
+
www-authenticate: Bearer authorization_uri=https://login.windows.net/<tenant>,
|
|
557
|
+
Basic realm="...", TFS-Federated
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
`authorization_uri` pointing at `login.windows.net` means its **Bearer scheme expects
|
|
561
|
+
an Entra ID access token**. A PAT is not one. npm sends `_authToken` *verbatim* as a
|
|
562
|
+
Bearer header (it base64-decodes only `_password`), so a PAT placed there is rejected
|
|
563
|
+
with:
|
|
564
|
+
|
|
565
|
+
```
|
|
566
|
+
npm publish error:
|
|
567
|
+
Unable to authenticate, your authentication token seems to be invalid.
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
**A PAT can only authenticate through the Basic scheme**, which is
|
|
571
|
+
`username` + base64 `_password`. That is what this file emits.
|
|
572
|
+
|
|
573
|
+
The trap worth knowing: a PAT *is* accepted as a Bearer token by the Packaging REST
|
|
574
|
+
API (`https://feeds.dev.azure.com/<org>/<proj>/_apis/packaging/feeds` returns 200),
|
|
575
|
+
so testing there looks like proof and is not. **Measure the endpoint the code
|
|
576
|
+
actually calls.** An `_authToken` change was shipped and reverted on exactly this.
|
|
577
|
+
|
|
578
|
+
Both feed path forms are keyed (`…/npm/registry/` and `…/npm/`) because npm matches
|
|
579
|
+
credentials by URL prefix and walks only *up* a path, so an entry on
|
|
580
|
+
`/npm/registry/` is never found for a request to `/npm/`.
|
|
581
|
+
|
|
582
|
+
#### The alternative on Azure: build-identity auth, no PAT at all
|
|
583
|
+
|
|
584
|
+
mnci does not generate this, but it is the better setup for an Azure Artifacts feed
|
|
585
|
+
in the **same organisation** as the pipeline, and it is what a real workspace uses
|
|
586
|
+
today. `npmAuthenticate@0` injects the build service identity's token — which *is*
|
|
587
|
+
Entra-issued, so it satisfies the Bearer scheme the feed advertises — and there is no
|
|
588
|
+
secret to store, encode, rotate or let expire.
|
|
589
|
+
|
|
590
|
+
To switch a generated workspace over by hand:
|
|
591
|
+
|
|
592
|
+
1. Delete the credential lines from the root `.npmrc`, keeping only the
|
|
593
|
+
`@scope:registry=` routing line. (`mnci upgrade` will rewrite them back — see the
|
|
594
|
+
caveat below.)
|
|
595
|
+
2. Add this step **before** `npm ci` in `azure-pipelines.yml`:
|
|
596
|
+
|
|
597
|
+
```yaml
|
|
598
|
+
- task: npmAuthenticate@0
|
|
599
|
+
displayName: Authenticate npm against the feed (build identity, no PAT)
|
|
600
|
+
inputs:
|
|
601
|
+
workingFile: .npmrc
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
3. Grant the build identity **Feed Publisher (Contributor)** on the feed (Artifacts →
|
|
605
|
+
Feed Settings → Permissions). Which identity depends on the job authorization
|
|
606
|
+
scope: `<Project> Build Service (<org>)` when scoped to the project (the default),
|
|
607
|
+
or `Project Collection Build Service (<org>)` when not. A **403** rather than a
|
|
608
|
+
401 is the tell that the wrong identity is in play.
|
|
609
|
+
|
|
610
|
+
Caveats, stated rather than glossed. This is **Azure-only** — GitHub Actions has no
|
|
611
|
+
equivalent task, so a `--ci=github` or `--ci=both` workspace still needs the PAT.
|
|
612
|
+
Local development then needs `npx vsts-npm-auth -config .npmrc` (Windows) or a
|
|
613
|
+
hand-added credential, since developers no longer inherit one from the file.
|
|
614
|
+
And `mnci upgrade` rewrites `.npmrc`, so it will restore the PAT block — `git diff`
|
|
615
|
+
before committing an upgrade, which the upgrade docs already tell you to do.
|
|
616
|
+
|
|
617
|
+
#### Diagnosing an auth failure
|
|
618
|
+
|
|
619
|
+
The error text distinguishes the two schemes, and that is the fastest signal:
|
|
620
|
+
|
|
621
|
+
| message | meaning |
|
|
622
|
+
| --- | --- |
|
|
623
|
+
| `E401 Incorrect or missing password` | Basic auth was used and rejected — check the base64 `_password` value |
|
|
624
|
+
| `Unable to authenticate, your authentication token seems to be invalid` | a token went out as Bearer and was rejected — a PAT cannot go here |
|
|
625
|
+
| `E403` | the credential is valid but lacks publish rights on the feed |
|
|
626
|
+
|
|
627
|
+
Two things are easy to get wrong when chasing this:
|
|
628
|
+
|
|
629
|
+
- **`nx release` publishes from the workspace root, not from each project.**
|
|
630
|
+
`@nx/js`'s `runPublish` uses `cwd: context.root` and passes `packageRoot` only as
|
|
631
|
+
the directory argument, so a missing per-package `.npmrc` is never the cause.
|
|
632
|
+
- **npm resolves `_authToken` before `username`/`_password` for a given registry
|
|
633
|
+
key** (`hasAuth` in `npm-registry-fetch/lib/auth.js`), and *key* precedence is
|
|
634
|
+
decided before *file* precedence. So a stale `_authToken` in a persistent build
|
|
635
|
+
agent's user-level `.npmrc` outranks the project-level `_password` and the
|
|
636
|
+
workspace's own credential never reaches the wire. Check
|
|
637
|
+
`npm config get userconfig` on the agent before blaming the secret.
|
|
638
|
+
|
|
511
639
|
On Azure, two one-time grants are required (project admin): **Contribute** on
|
|
512
640
|
the repo for the _Project Collection Build Service_ account (tag push), and
|
|
513
641
|
**publish** rights on the feed for the PAT's owner. On GitHub, the workflow's
|
package/dist/cli.js
CHANGED
|
@@ -207,6 +207,23 @@ function markPublic(manifestPath) {
|
|
|
207
207
|
const manifest = readJson(manifestPath);
|
|
208
208
|
writeFileEnsured(manifestPath, toJson({ ...manifest, publishConfig: { access: "public" } }));
|
|
209
209
|
}
|
|
210
|
+
var WRONG_TYPES_PATH = "./dist/index.esm.d.ts";
|
|
211
|
+
var ACTUAL_TYPES_PATH = "./dist/index.d.ts";
|
|
212
|
+
var DECLARATION_MAP_EXCLUSION = "!**/*.d.ts.map";
|
|
213
|
+
function repairPublishableManifest(manifestPath) {
|
|
214
|
+
const manifest = readJson(manifestPath);
|
|
215
|
+
if (manifest.types === WRONG_TYPES_PATH) {
|
|
216
|
+
manifest.types = ACTUAL_TYPES_PATH;
|
|
217
|
+
}
|
|
218
|
+
const dot = manifest.exports?.["."];
|
|
219
|
+
if (typeof dot === "object" && dot.types === WRONG_TYPES_PATH) {
|
|
220
|
+
dot.types = ACTUAL_TYPES_PATH;
|
|
221
|
+
}
|
|
222
|
+
if (manifest.files && !manifest.files.includes(DECLARATION_MAP_EXCLUSION)) {
|
|
223
|
+
manifest.files.push(DECLARATION_MAP_EXCLUSION);
|
|
224
|
+
}
|
|
225
|
+
writeFileEnsured(manifestPath, toJson(manifest));
|
|
226
|
+
}
|
|
210
227
|
function ensureAdmZip(workspaceRoot) {
|
|
211
228
|
if (hasPlugin(workspaceRoot, "adm-zip")) {
|
|
212
229
|
return;
|
|
@@ -714,7 +731,9 @@ async function addNpmLib(workspaceRoot, name, options, kindProvided, stack) {
|
|
|
714
731
|
],
|
|
715
732
|
workspaceRoot
|
|
716
733
|
);
|
|
717
|
-
|
|
734
|
+
const manifestPath = (0, import_node_path6.join)(workspaceRoot, "packages", name, "package.json");
|
|
735
|
+
markPublic(manifestPath);
|
|
736
|
+
repairPublishableManifest(manifestPath);
|
|
718
737
|
removeGeneratedEslintConfig(workspaceRoot, `packages/${name}`);
|
|
719
738
|
registerProjectCommands(workspaceRoot, name, { build: true });
|
|
720
739
|
}
|
|
@@ -1021,19 +1040,6 @@ function addReactApp(workspaceRoot, name, stack) {
|
|
|
1021
1040
|
|
|
1022
1041
|
// src/commands/add/reactLib.ts
|
|
1023
1042
|
var import_node_path9 = require("path");
|
|
1024
|
-
var WRONG_TYPES_PATH = "./dist/index.esm.d.ts";
|
|
1025
|
-
var ACTUAL_TYPES_PATH = "./dist/index.d.ts";
|
|
1026
|
-
function repairTypesPath(manifestPath) {
|
|
1027
|
-
const manifest = readJson(manifestPath);
|
|
1028
|
-
if (manifest.types === WRONG_TYPES_PATH) {
|
|
1029
|
-
manifest.types = ACTUAL_TYPES_PATH;
|
|
1030
|
-
}
|
|
1031
|
-
const dot = manifest.exports?.["."];
|
|
1032
|
-
if (typeof dot === "object" && dot.types === WRONG_TYPES_PATH) {
|
|
1033
|
-
dot.types = ACTUAL_TYPES_PATH;
|
|
1034
|
-
}
|
|
1035
|
-
writeFileEnsured(manifestPath, toJson(manifest));
|
|
1036
|
-
}
|
|
1037
1043
|
var REACT_LIB_BUNDLER = "rollup";
|
|
1038
1044
|
async function addReactLib(workspaceRoot, name, options, kindProvided, stack) {
|
|
1039
1045
|
ensurePlugin(workspaceRoot, "@nx/react");
|
|
@@ -1055,7 +1061,7 @@ async function addReactLib(workspaceRoot, name, options, kindProvided, stack) {
|
|
|
1055
1061
|
);
|
|
1056
1062
|
const publishableManifest = (0, import_node_path9.join)(workspaceRoot, "packages", name, "package.json");
|
|
1057
1063
|
markPublic(publishableManifest);
|
|
1058
|
-
|
|
1064
|
+
repairPublishableManifest(publishableManifest);
|
|
1059
1065
|
removeGeneratedEslintConfig(workspaceRoot, `packages/${name}`);
|
|
1060
1066
|
registerProjectCommands(workspaceRoot, name, { build: true });
|
|
1061
1067
|
}
|
|
@@ -1076,7 +1082,7 @@ function addReactInternalLib(workspaceRoot, name, stack) {
|
|
|
1076
1082
|
);
|
|
1077
1083
|
const privateManifest = (0, import_node_path9.join)(workspaceRoot, "libs", name, "package.json");
|
|
1078
1084
|
markPrivate(privateManifest);
|
|
1079
|
-
|
|
1085
|
+
repairPublishableManifest(privateManifest);
|
|
1080
1086
|
removeGeneratedEslintConfig(workspaceRoot, `libs/${name}`);
|
|
1081
1087
|
registerProjectCommands(workspaceRoot, name, { build: true });
|
|
1082
1088
|
}
|
|
@@ -1275,6 +1281,7 @@ function npmrcContent(registry, scope) {
|
|
|
1275
1281
|
}
|
|
1276
1282
|
const feedUrl = registryUrl(registry);
|
|
1277
1283
|
const feedKey = feedUrl.replace(/^https:/, "");
|
|
1284
|
+
const feedShortKey = feedKey.replace("npm/registry/", "npm/");
|
|
1278
1285
|
return `; Publish + resolution routing for this workspace's own scope.
|
|
1279
1286
|
;
|
|
1280
1287
|
; '${scope}:registry' sends BOTH resolution and 'npm publish' of ${scope}/* to the
|
|
@@ -1290,9 +1297,24 @@ ${scope}:registry=${feedUrl}
|
|
|
1290
1297
|
; instructions give you, which is exactly what _password expects, so it is used
|
|
1291
1298
|
; as-is. (twine wants the RAW token; the CI release step decodes it there.)
|
|
1292
1299
|
; Azure ignores the username, and npm requires an email it never uses.
|
|
1300
|
+
;
|
|
1301
|
+
; NOT _authToken, and that is measured rather than assumed. The feed answers an
|
|
1302
|
+
; unauthenticated PUT to the publish endpoint with:
|
|
1303
|
+
; www-authenticate: Bearer authorization_uri=https://login.windows.net/<tenant>,
|
|
1304
|
+
; Basic realm="...", TFS-Federated
|
|
1305
|
+
; so its Bearer scheme wants an Entra ID access token, NOT a PAT. npm sends
|
|
1306
|
+
; _authToken verbatim as a Bearer header, so a PAT there is rejected with
|
|
1307
|
+
; "Unable to authenticate, your authentication token seems to be invalid". A PAT
|
|
1308
|
+
; authenticates through the Basic scheme, which is username/_password. Note the
|
|
1309
|
+
; Packaging REST API DOES accept a PAT as Bearer - do not generalise from it.
|
|
1310
|
+
;
|
|
1311
|
+
; Both path forms are keyed because npm walks only UP a URL when matching.
|
|
1293
1312
|
${feedKey}:username=AzureArtifacts
|
|
1294
1313
|
${feedKey}:_password=\${PAT}
|
|
1295
1314
|
${feedKey}:email=npm-requires-this-and-never-uses-it
|
|
1315
|
+
${feedShortKey}:username=AzureArtifacts
|
|
1316
|
+
${feedShortKey}:_password=\${PAT}
|
|
1317
|
+
${feedShortKey}:email=npm-requires-this-and-never-uses-it
|
|
1296
1318
|
`;
|
|
1297
1319
|
}
|
|
1298
1320
|
function releaseConfig(ci) {
|
|
@@ -1433,6 +1455,9 @@ var ESLINT_PEER_OVERRIDES = {
|
|
|
1433
1455
|
// even a trade, just a version nobody had bumped yet.
|
|
1434
1456
|
postcss: { nanoid: "^3.3.18" }
|
|
1435
1457
|
};
|
|
1458
|
+
var SECURITY_OVERRIDES = {
|
|
1459
|
+
"brace-expansion@4.0.0 - 5.0.8": "^5.0.9"
|
|
1460
|
+
};
|
|
1436
1461
|
function eslintToolchainDependencies(nxVersion) {
|
|
1437
1462
|
return {
|
|
1438
1463
|
eslint: ESLINT_VERSION,
|
|
@@ -1588,7 +1613,27 @@ function vscodeSettings() {
|
|
|
1588
1613
|
)
|
|
1589
1614
|
};
|
|
1590
1615
|
}
|
|
1591
|
-
|
|
1616
|
+
var LAUNCH_CONFIG_PREFIX = "mnci: ";
|
|
1617
|
+
var LAUNCH_CONFIGURATIONS = ["build", "test", "lint", "typecheck"];
|
|
1618
|
+
function launchConfigurations(workspaceName) {
|
|
1619
|
+
return LAUNCH_CONFIGURATIONS.map((script, index) => ({
|
|
1620
|
+
type: "node-terminal",
|
|
1621
|
+
request: "launch",
|
|
1622
|
+
name: `${LAUNCH_CONFIG_PREFIX}${script}`,
|
|
1623
|
+
command: `npm run ${script}`,
|
|
1624
|
+
// Scoped by folder name rather than a bare ${workspaceFolder}: that variable is
|
|
1625
|
+
// ambiguous once a second folder is added to the workspace, and VS Code then
|
|
1626
|
+
// refuses to resolve it.
|
|
1627
|
+
cwd: `\${workspaceFolder:${workspaceName}}`,
|
|
1628
|
+
// One shared group keeps the four together in the dropdown; a per-entry group
|
|
1629
|
+
// would make four groups of one. `order` holds them in verify order.
|
|
1630
|
+
presentation: { group: "mnci", order: index + 1 }
|
|
1631
|
+
}));
|
|
1632
|
+
}
|
|
1633
|
+
function vscodeWorkspace(workspaceName, existingTasks, existingLaunch) {
|
|
1634
|
+
const userConfigurations = (existingLaunch?.configurations ?? []).filter(
|
|
1635
|
+
(configuration) => !String(configuration.name ?? "").startsWith(LAUNCH_CONFIG_PREFIX)
|
|
1636
|
+
);
|
|
1592
1637
|
return JSON.stringify(
|
|
1593
1638
|
{
|
|
1594
1639
|
folders: [{ path: ".", name: workspaceName }],
|
|
@@ -1597,6 +1642,10 @@ function vscodeWorkspace(workspaceName, existingTasks) {
|
|
|
1597
1642
|
tasks: {
|
|
1598
1643
|
version: existingTasks?.version ?? "2.0.0",
|
|
1599
1644
|
tasks: existingTasks?.tasks ?? []
|
|
1645
|
+
},
|
|
1646
|
+
launch: {
|
|
1647
|
+
version: existingLaunch?.version ?? "0.2.0",
|
|
1648
|
+
configurations: [...launchConfigurations(workspaceName), ...userConfigurations]
|
|
1600
1649
|
}
|
|
1601
1650
|
},
|
|
1602
1651
|
null,
|
|
@@ -1720,15 +1769,40 @@ trigger:
|
|
|
1720
1769
|
# reason: two concurrent 'nx release' runs would race to create the same tag.
|
|
1721
1770
|
batch: true
|
|
1722
1771
|
branches:
|
|
1723
|
-
include:
|
|
1724
|
-
|
|
1725
|
-
#
|
|
1726
|
-
#
|
|
1727
|
-
#
|
|
1728
|
-
#
|
|
1729
|
-
pr:
|
|
1730
|
-
|
|
1731
|
-
|
|
1772
|
+
include:
|
|
1773
|
+
- main
|
|
1774
|
+
# EVERY other branch too, and that breadth is a correction rather than
|
|
1775
|
+
# generosity. On Azure Repos Git a YAML 'pr:' block does nothing at all
|
|
1776
|
+
# (see the note below), so a CI trigger is the only pre-merge
|
|
1777
|
+
# verification this file can switch on by itself. Listing main alone \u2014
|
|
1778
|
+
# which is what this pipeline used to do, next to a 'pr:' block that
|
|
1779
|
+
# looked like it covered the rest \u2014 means a workspace verifies NOTHING
|
|
1780
|
+
# until a change has already reached the branch it was supposed to
|
|
1781
|
+
# protect.
|
|
1782
|
+
#
|
|
1783
|
+
# Release stays gated on main (the 'condition:' on every release step
|
|
1784
|
+
# below), so a topic-branch run installs, audits, syncs, formats,
|
|
1785
|
+
# verifies and packs, and publishes nothing.
|
|
1786
|
+
- '*'
|
|
1787
|
+
|
|
1788
|
+
# PR validation is deliberately NOT configured here, because on Azure Repos Git
|
|
1789
|
+
# it CANNOT be: "For an Azure Repos Git repo, you cannot configure a PR trigger
|
|
1790
|
+
# in the YAML file. You need to use branch policies." A 'pr:' block here is not
|
|
1791
|
+
# an error \u2014 it is silently ignored, which is worse, because the file then
|
|
1792
|
+
# documents a gate that does not exist. (The same block IS honoured for GitHub
|
|
1793
|
+
# and Bitbucket repos, which is why .github/workflows/ci.yml keeps its
|
|
1794
|
+
# 'pull_request:' trigger.)
|
|
1795
|
+
#
|
|
1796
|
+
# To get PR validation, configure it once in the UI: Project Settings ->
|
|
1797
|
+
# Repositories -> <repo> -> Policies -> <branch> -> Build Validation, pointing
|
|
1798
|
+
# at this pipeline. That is also the only thing that sets
|
|
1799
|
+
# System.PullRequest.TargetBranch, which the verify step below reads to scope
|
|
1800
|
+
# itself to affected projects \u2014 without a policy that variable is never set and
|
|
1801
|
+
# the step verifies every project instead. Correct either way, just slower.
|
|
1802
|
+
#
|
|
1803
|
+
# Cancelling a superseded PR run lives in that same policy ("automatically
|
|
1804
|
+
# cancel"), and likewise has no YAML expression.
|
|
1805
|
+
pr: none
|
|
1732
1806
|
|
|
1733
1807
|
pool:
|
|
1734
1808
|
${poolBlock(agent)}
|
|
@@ -2186,7 +2260,8 @@ function applyOverlay(workspaceRoot, options) {
|
|
|
2186
2260
|
});
|
|
2187
2261
|
const overrides = {
|
|
2188
2262
|
...manifest.overrides,
|
|
2189
|
-
...ESLINT_PEER_OVERRIDES
|
|
2263
|
+
...ESLINT_PEER_OVERRIDES,
|
|
2264
|
+
...SECURITY_OVERRIDES
|
|
2190
2265
|
};
|
|
2191
2266
|
const existingNx = manifest.nx;
|
|
2192
2267
|
const nx = {
|
|
@@ -2226,7 +2301,7 @@ function applyOverlay(workspaceRoot, options) {
|
|
|
2226
2301
|
const existing = readCodeWorkspace(codeWorkspacePath);
|
|
2227
2302
|
writeFileEnsured(
|
|
2228
2303
|
codeWorkspacePath,
|
|
2229
|
-
vscodeWorkspace(options.workspaceName, existing?.tasks)
|
|
2304
|
+
vscodeWorkspace(options.workspaceName, existing?.tasks, existing?.launch)
|
|
2230
2305
|
);
|
|
2231
2306
|
if (options.workspaceName !== "undefined") {
|
|
2232
2307
|
(0, import_node_fs6.rmSync)((0, import_node_path11.join)(workspaceRoot, "undefined.code-workspace"), { force: true });
|
|
@@ -2249,11 +2324,14 @@ function applyOverlay(workspaceRoot, options) {
|
|
|
2249
2324
|
|
|
2250
2325
|
// src/commands/doctor.ts
|
|
2251
2326
|
var SUPPORTED_ESLINT_MAJOR = ESLINT_VERSION.replace(/^\D*/, "").split(".", 1)[0];
|
|
2327
|
+
function toPosix(path) {
|
|
2328
|
+
return path.replaceAll("\\", "/");
|
|
2329
|
+
}
|
|
2252
2330
|
function checkEslintConfigs(workspaceRoot) {
|
|
2253
2331
|
const rootConfigs = (0, import_node_fs7.globSync)("eslint.config.{js,mjs,cjs,ts,mts,cts}", { cwd: workspaceRoot });
|
|
2254
2332
|
const projectConfigs = (0, import_node_fs7.globSync)("{apps,libs,packages}/*/eslint.config.{js,mjs,cjs,ts,mts,cts}", {
|
|
2255
2333
|
cwd: workspaceRoot
|
|
2256
|
-
});
|
|
2334
|
+
}).map((config) => toPosix(config));
|
|
2257
2335
|
return [
|
|
2258
2336
|
{
|
|
2259
2337
|
check: "root ESLint config",
|
|
@@ -2348,7 +2426,7 @@ function checkVersionActions(workspaceRoot) {
|
|
|
2348
2426
|
const candidates = [
|
|
2349
2427
|
...(0, import_node_fs7.globSync)("packages/*/pubspec.yaml", { cwd: workspaceRoot }),
|
|
2350
2428
|
...(0, import_node_fs7.globSync)("python-packages/*/pyproject.toml", { cwd: workspaceRoot })
|
|
2351
|
-
];
|
|
2429
|
+
].map((manifest) => toPosix(manifest));
|
|
2352
2430
|
return candidates.flatMap((manifest) => {
|
|
2353
2431
|
const projectRoot = manifest.slice(0, manifest.lastIndexOf("/"));
|
|
2354
2432
|
const projectJsonPath = (0, import_node_path12.join)(workspaceRoot, projectRoot, "project.json");
|
|
@@ -2368,6 +2446,63 @@ function checkVersionActions(workspaceRoot) {
|
|
|
2368
2446
|
];
|
|
2369
2447
|
});
|
|
2370
2448
|
}
|
|
2449
|
+
var TARGET_FILE_OPTIONS = ["main", "tsConfig", "packageJson"];
|
|
2450
|
+
function declaredTargets(workspaceRoot, projectRoot) {
|
|
2451
|
+
const sources = [];
|
|
2452
|
+
for (const [file, extract] of [
|
|
2453
|
+
["project.json", (json) => json["targets"]],
|
|
2454
|
+
[
|
|
2455
|
+
"package.json",
|
|
2456
|
+
(json) => json["nx"]?.["targets"]
|
|
2457
|
+
]
|
|
2458
|
+
]) {
|
|
2459
|
+
try {
|
|
2460
|
+
const targets = extract(
|
|
2461
|
+
readJson((0, import_node_path12.join)(workspaceRoot, projectRoot, file))
|
|
2462
|
+
);
|
|
2463
|
+
if (targets && typeof targets === "object") {
|
|
2464
|
+
sources.push({
|
|
2465
|
+
source: `${projectRoot}/${file}`,
|
|
2466
|
+
targets
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2469
|
+
} catch {
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
return sources;
|
|
2473
|
+
}
|
|
2474
|
+
function checkTargetFilesExist(workspaceRoot) {
|
|
2475
|
+
const projectRoots = new Set(
|
|
2476
|
+
[
|
|
2477
|
+
...(0, import_node_fs7.globSync)("{apps,libs,packages}/*/project.json", { cwd: workspaceRoot }),
|
|
2478
|
+
...(0, import_node_fs7.globSync)("{apps,libs,packages}/*/package.json", { cwd: workspaceRoot })
|
|
2479
|
+
].map((manifest) => toPosix(manifest).split("/").slice(0, 2).join("/"))
|
|
2480
|
+
);
|
|
2481
|
+
return [...projectRoots].flatMap(
|
|
2482
|
+
(projectRoot) => declaredTargets(workspaceRoot, projectRoot).flatMap(
|
|
2483
|
+
({ source, targets }) => Object.entries(targets).flatMap(
|
|
2484
|
+
([targetName, target]) => checkOneTargetsFiles(workspaceRoot, source, targetName, target)
|
|
2485
|
+
)
|
|
2486
|
+
)
|
|
2487
|
+
);
|
|
2488
|
+
}
|
|
2489
|
+
function checkOneTargetsFiles(workspaceRoot, source, targetName, target) {
|
|
2490
|
+
return TARGET_FILE_OPTIONS.flatMap((option) => {
|
|
2491
|
+
const value = target?.options?.[option];
|
|
2492
|
+
const checkable = typeof value === "string" && !value.includes("{");
|
|
2493
|
+
if (!checkable || fileExists((0, import_node_path12.join)(workspaceRoot, value))) {
|
|
2494
|
+
return [];
|
|
2495
|
+
}
|
|
2496
|
+
return [
|
|
2497
|
+
{
|
|
2498
|
+
check: `${source} \u2192 ${targetName}.${option} points at a real file`,
|
|
2499
|
+
ok: false,
|
|
2500
|
+
detail: `${value} does not exist \u2014 the target fails as if the compiler were misconfigured`,
|
|
2501
|
+
remedy: `create ${value}, or correct ${option} in ${source}`
|
|
2502
|
+
}
|
|
2503
|
+
];
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2371
2506
|
function checkSync(workspaceRoot) {
|
|
2372
2507
|
return {
|
|
2373
2508
|
check: "TypeScript project references synced",
|
|
@@ -2391,6 +2526,7 @@ function collectFindings(workspaceRoot) {
|
|
|
2391
2526
|
checkResolvedEslint(workspaceRoot),
|
|
2392
2527
|
checkNpmrc(workspaceRoot, nxJson.mnci?.registry, nxJson.mnci?.scope),
|
|
2393
2528
|
...checkVersionActions(workspaceRoot),
|
|
2529
|
+
...checkTargetFilesExist(workspaceRoot),
|
|
2394
2530
|
checkSync(workspaceRoot)
|
|
2395
2531
|
].filter((finding) => finding !== void 0);
|
|
2396
2532
|
}
|