@williamthorsen/nmr 0.31.0 → 0.32.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/.readyup/kits/default.js +31 -2
- package/.readyup/manifest.json +34 -3
- package/README.md +16 -52
- package/agents/guidance/rulebooks/nmr.md +2 -1
- package/dist/esm/cli-ensure-prepublish-hooks.js +2 -23
- package/dist/esm/commands/build-output.d.ts +1 -0
- package/dist/esm/commands/build-output.js +25 -2
- package/dist/esm/commands/build.d.ts +5 -1
- package/dist/esm/commands/build.js +22 -8
- package/dist/esm/commands/clean.js +12 -0
- package/dist/esm/commands/ensure-prepublish-hooks.d.ts +1 -0
- package/dist/esm/commands/ensure-prepublish-hooks.js +43 -0
- package/dist/esm/commands/report-catalog.js +9 -3
- package/dist/esm/commands/report-overrides.js +9 -1
- package/dist/esm/helpers/reportClosing.d.ts +1 -0
- package/dist/esm/helpers/reportClosing.js +3 -0
- package/dist/esm/reportCliFailure.js +2 -2
- package/dist/esm/resolver.js +2 -1
- package/package.json +7 -5
package/.readyup/kits/default.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @noformat — @generated. Do not edit. Compiled by rdy. */
|
|
2
2
|
/* eslint-disable */
|
|
3
|
-
export const __readyupVersion = "0.
|
|
3
|
+
export const __readyupVersion = "0.28.0";
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
// .readyup/kits/default.ts
|
|
@@ -179,6 +179,13 @@ var default_default = defineRdyKit({
|
|
|
179
179
|
check: toolVersionsHasNoPnpm,
|
|
180
180
|
fix: "Remove pnpm from .tool-versions \u2014 manage via packageManager field and corepack"
|
|
181
181
|
},
|
|
182
|
+
{
|
|
183
|
+
name: "no package.json declares a pnpm field",
|
|
184
|
+
severity: "error",
|
|
185
|
+
quiet: true,
|
|
186
|
+
check: () => noPnpmFieldInPackageJson(),
|
|
187
|
+
fix: "Move these settings into pnpm-workspace.yaml, quoting each version under `overrides`, or run `pnpx codemod run pnpm-v10-to-v11`. pnpm 11 reads no key from the `pnpm` field, so an override left there pins nothing while an upgrade run with `--write` goes on rewriting it"
|
|
188
|
+
},
|
|
182
189
|
{
|
|
183
190
|
name: ".config/nmr.config.ts uses defineConfig",
|
|
184
191
|
severity: "recommend",
|
|
@@ -387,7 +394,7 @@ function formatPaths(paths) {
|
|
|
387
394
|
${paths.map((path2) => ` ${path2}`).join("\n")}`;
|
|
388
395
|
}
|
|
389
396
|
function getMinVersion() {
|
|
390
|
-
const picked = { "version": "0.
|
|
397
|
+
const picked = { "version": "0.32.0" };
|
|
391
398
|
if (typeof picked["version"] !== "string") {
|
|
392
399
|
throw new TypeError("nmr/package.json: 'version' must be a string");
|
|
393
400
|
}
|
|
@@ -413,6 +420,15 @@ function isReExportOnly(content) {
|
|
|
413
420
|
const statements = content.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "").split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
414
421
|
return statements.length > 0 && statements.every((line) => RE_EXPORT_LINE_PATTERN.test(line));
|
|
415
422
|
}
|
|
423
|
+
function noPnpmFieldInPackageJson(cwd = process.cwd()) {
|
|
424
|
+
const declaring = findFiles(["**/package.json"], cwd).flatMap((relativePath) => {
|
|
425
|
+
const keys = readPnpmFieldKeys(readFileIn(cwd, relativePath));
|
|
426
|
+
if (keys === void 0) return [];
|
|
427
|
+
return [keys.length > 0 ? `${relativePath} (${keys.join(", ")})` : relativePath];
|
|
428
|
+
});
|
|
429
|
+
if (declaring.length === 0) return true;
|
|
430
|
+
return { ok: false, detail: formatPaths(declaring) };
|
|
431
|
+
}
|
|
416
432
|
function noReExportOnlyVitestConfigs(cwd = process.cwd()) {
|
|
417
433
|
const nonRootConfigs = findFiles([`**/vitest.config.${CONFIG_EXTENSIONS}`], cwd).filter((path2) => path2.includes("/"));
|
|
418
434
|
const reExports = nonRootConfigs.filter((path2) => isReExportOnly(readFileIn(cwd, path2)));
|
|
@@ -464,6 +480,18 @@ function readFileIn(cwd, relativePath) {
|
|
|
464
480
|
return void 0;
|
|
465
481
|
}
|
|
466
482
|
}
|
|
483
|
+
function readPnpmFieldKeys(content) {
|
|
484
|
+
if (content === void 0) return void 0;
|
|
485
|
+
let parsed;
|
|
486
|
+
try {
|
|
487
|
+
parsed = JSON.parse(content);
|
|
488
|
+
} catch {
|
|
489
|
+
return void 0;
|
|
490
|
+
}
|
|
491
|
+
if (!isRecord(parsed)) return void 0;
|
|
492
|
+
const pnpm = parsed["pnpm"];
|
|
493
|
+
return isRecord(pnpm) ? Object.keys(pnpm).toSorted() : void 0;
|
|
494
|
+
}
|
|
467
495
|
function resolvesVersionViaWorkspace(range) {
|
|
468
496
|
return WORKSPACE_VERSION_MARKERS.some((marker) => range.startsWith(marker));
|
|
469
497
|
}
|
|
@@ -483,6 +511,7 @@ export {
|
|
|
483
511
|
everyTestFileNamesItsTier,
|
|
484
512
|
hasSupportedEslintVersion,
|
|
485
513
|
hasSupportedStrictLintVersion,
|
|
514
|
+
noPnpmFieldInPackageJson,
|
|
486
515
|
noReExportOnlyVitestConfigs,
|
|
487
516
|
noRetiredVitestConfigs,
|
|
488
517
|
prettierConfigBuildsOnSharedConfig,
|
package/.readyup/manifest.json
CHANGED
|
@@ -5,12 +5,43 @@
|
|
|
5
5
|
"checklists": [
|
|
6
6
|
"nmr"
|
|
7
7
|
],
|
|
8
|
+
"esbuildVersion": "0.28.1",
|
|
9
|
+
"inputs": [
|
|
10
|
+
{
|
|
11
|
+
"hash": "67afdcb4",
|
|
12
|
+
"kind": "module",
|
|
13
|
+
"path": "kits/default.ts"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"hash": "2bd89ec9",
|
|
17
|
+
"kind": "inline",
|
|
18
|
+
"path": "../package.json",
|
|
19
|
+
"paths": [
|
|
20
|
+
"version"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"hash": "3dfc2ee7",
|
|
25
|
+
"kind": "module",
|
|
26
|
+
"path": "../src/default-scripts.ts"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"hash": "b42c7523",
|
|
30
|
+
"kind": "module",
|
|
31
|
+
"path": "../src/resolve-scripts.ts"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"hash": "c2d21075",
|
|
35
|
+
"kind": "module",
|
|
36
|
+
"path": "../src/tiers.ts"
|
|
37
|
+
}
|
|
38
|
+
],
|
|
8
39
|
"name": "default",
|
|
9
40
|
"path": "kits/default.js",
|
|
10
|
-
"readyupVersion": "0.
|
|
41
|
+
"readyupVersion": "0.28.0",
|
|
11
42
|
"source": "kits/default.ts",
|
|
12
|
-
"sourceHash": "
|
|
13
|
-
"targetHash": "
|
|
43
|
+
"sourceHash": "67afdcb4",
|
|
44
|
+
"targetHash": "b923a4b4"
|
|
14
45
|
}
|
|
15
46
|
]
|
|
16
47
|
}
|
package/README.md
CHANGED
|
@@ -3,69 +3,31 @@
|
|
|
3
3
|
Context-aware script runner for pnpm monorepos. Ships an `nmr` (node-monorepo run) binary that provides centralized, consistent script execution across workspace packages and the monorepo root.
|
|
4
4
|
|
|
5
5
|
<!-- section:release-notes -->
|
|
6
|
-
## Release notes — v0.
|
|
6
|
+
## Release notes — v0.32.0 (2026-08-17)
|
|
7
7
|
|
|
8
8
|
### 🎉 Features
|
|
9
9
|
|
|
10
|
-
-
|
|
10
|
+
- Carry the test-tier convention in the ambient rulebook (#689)
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- Resolve a run's verbosity from repo config and agent-harness detection (#660)
|
|
17
|
-
|
|
18
|
-
A verbosity preference for `nmr` can now be set in the repo's root config, and `nmr` now automatically switches to quiet mode when it detects a known agent harness. This makes four ways to set verbosity: the `-q` option, the `NMR_COMMAND_VERBOSITY` env var, config, and harness detection, consulted in that order and defaulting to `full` when none applies.
|
|
19
|
-
|
|
20
|
-
- 🚨 **Breaking:** Refine nmr upgrade to support catalogs (#661)
|
|
21
|
-
|
|
22
|
-
Adds `nmr-report-catalog`, which names the dependencies a package takes from a pnpm catalog, their `catalog:` specifiers, and the monorepo root a covering pass runs from. The workspace `upgrade` default chains it ahead of the upgrade tool, and `nmr report-catalog` runs it alone. Because taze reads `pnpm-workspace.yaml` only when that file sits at its working directory, changes to the catalog are not attempted during a package-scoped upgrade.
|
|
23
|
-
|
|
24
|
-
Separately, `nmr-report-overrides` now reports the `overrides` block in `pnpm-workspace.yaml` and fails on a `pnpm.overrides` block in the root `package.json`. Both `upgrade` and `root:upgrade` run the reporter ahead of taze. This avoids a scenario where taze would meaninglessly write to the latter block.
|
|
25
|
-
|
|
26
|
-
Migration: Move the block to `overrides` in `pnpm-workspace.yaml`, quoting each version, or run `pnpx codemod run pnpm-v10-to-v11`. `engines` declares `pnpm: ">=11"`.
|
|
27
|
-
|
|
28
|
-
- Replay an excerpt of the run a skip recalls (#667)
|
|
29
|
-
|
|
30
|
-
`nmr` can now show the result of the previous check when a check is skipped. When `nmr` runs a command, it stores the result, keyed by presentation environment. The result is deemed to be the last blank-line-delimited block written by the command. If the command is run again in the same presentation environment, the stored result is shown, labeled "replayed:" for clarity.
|
|
31
|
-
|
|
32
|
-
- Assemble a skipped composite's replay from its constituents' excerpts (#668)
|
|
33
|
-
|
|
34
|
-
A skipped composite now replays what its constituents recorded, with each line attributed to the scope and command that produced it:
|
|
35
|
-
|
|
36
|
-
```console
|
|
37
|
-
⏭️ nmr: check: passed 35.7s ago on this tree, saved ~10s — replayed: nmr: fmt:check: All matched files use Prettier code style!; nmr: test: Test Files 52 passed (52) Tests 987 passed (987)
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Nesting collapses. A constituent that fans out across packages contributes a line from every package that recorded one; a constituent that recorded nothing is omitted.
|
|
41
|
-
|
|
42
|
-
A new reserved variable, `NMR_RUN_ID`, carries a run's identity to every process below it. Every recorded excerpt names the run that last certified it. A composite summary includes saved results with the same status as a fresh result.
|
|
43
|
-
|
|
44
|
-
- Print a retained run with nmr --log (#669)
|
|
45
|
-
|
|
46
|
-
Adds `nmr --log <command>`, which prints what the last recorded pass wrote instead of running the command again. The whole transcript, up to a limit of 256 KiB, is now saved for any passing command that is not writing straight to a terminal; `--log` prints it under a header giving the instant, the duration, and the command chain that succeeded. The log of a composite is the sum of its constituents' excerpts. If a change disqualifies the recording, `--log` instead reports the change.
|
|
47
|
-
|
|
48
|
-
- Emit one JSON object per command for machine consumers (#672)
|
|
49
|
-
|
|
50
|
-
Adds `--json` to `nmr`, allowing the caller to request output in JSON format instead of prose. An env var carries the choice of `json` or `text` down a spawned chain. When the output is JSON, the consumer receives one JSON object per command; each object carries the command, scope, and outcome, and a recalled pass carries the recorded excerpts.
|
|
12
|
+
Adds the test-tier convention to `nmr.md`, the ambient rulebook `codeassembly sync` deploys to each consuming repo's agent guidance. The guidance describes the `<subject>[.<aspect>].<tier>.test.ts` grammar and the four tiers, and warns that tests outside a `__tests__` directory are ignored. Tests not explicitly attributed to a tier are flagged by the ReadyUp kit bundled with `nmr`.
|
|
13
|
+
|
|
14
|
+
The previous guidance is removed from `AGENTS.md`.
|
|
51
15
|
|
|
52
|
-
|
|
16
|
+
- Close each bin's output with a statement of what the run came to (#691)
|
|
53
17
|
|
|
54
|
-
|
|
18
|
+
Five nmr bins now end their output with a statement of the run's final result, rather than on whichever line the run happened to reach last, so the last line a reader sees carries the finding.
|
|
55
19
|
|
|
56
|
-
|
|
20
|
+
Separately, `ensure-prepublish-hooks` prints its whole report, including failure messages, on stdout, to avoid splitting the report across two streams. Failure still results in a non-zero exit code.
|
|
57
21
|
|
|
58
|
-
|
|
22
|
+
- Reject a dead pnpm field in nmr's ReadyUp kit (#692)
|
|
59
23
|
|
|
60
|
-
|
|
24
|
+
nmr's ReadyUp kit now checks for the presence of a `pnpm` field in any `package.json`. pnpm 11 no longer reads any key from that field, but taze (the engine underlying `nmr upgrade`) reads `pnpm.overrides`. To avoid confusion, the kit flags this as an error.
|
|
61
25
|
|
|
62
26
|
### 🐛 Bug fixes
|
|
63
27
|
|
|
64
|
-
-
|
|
28
|
+
- Fold the running nmr's fingerprint into the build cache key (#693)
|
|
65
29
|
|
|
66
|
-
Fixes
|
|
67
|
-
|
|
68
|
-
Separately, a configuration mistake now reports as a plain message rather than a Node stack trace.
|
|
30
|
+
Fixes the issue that build output could be stale, but not reported to be stale, after an edit to `nmr-compile`'s own emit logic. The build cache key now folds a fingerprint of the `nmr` running the build alongside the TypeScript version it already carried. Any build keys generated against an `nmr` with a different fingerprint will be invalidated. Repos consuming `nmr` get the same behavior.
|
|
69
31
|
<!-- /section:release-notes -->
|
|
70
32
|
|
|
71
33
|
## Installation
|
|
@@ -883,7 +845,7 @@ nmr-clean
|
|
|
883
845
|
|
|
884
846
|
### `nmr-compile`
|
|
885
847
|
|
|
886
|
-
Compile a single package's `src` tree to `dist/esm` with the TypeScript compiler API, emitting `.js` and `.d.ts` in one pass. Because the compiler parses each source file, every relative import form — static, re-export, dynamic `import()`, and bare side-effect — is rewritten from `.ts` to `.js` in both outputs, and `.ts` occurrences inside strings and comments are left intact. tsconfig `paths` aliases are resolved to runnable relative `.js` specifiers in both outputs, sourced from the package's tsconfig. An aliased import whose target resolves outside the package's `src/` and is not resolvable without the alias mapping fails the build with a diagnostic, rather than being emitted verbatim to produce output that fails at runtime. The build is skipped when no input has changed and the previous output is still on disk (
|
|
848
|
+
Compile a single package's `src` tree to `dist/esm` with the TypeScript compiler API, emitting `.js` and `.d.ts` in one pass. Because the compiler parses each source file, every relative import form — static, re-export, dynamic `import()`, and bare side-effect — is rewritten from `.ts` to `.js` in both outputs, and `.ts` occurrences inside strings and comments are left intact. tsconfig `paths` aliases are resolved to runnable relative `.js` specifiers in both outputs, sourced from the package's tsconfig. An aliased import whose target resolves outside the package's `src/` and is not resolvable without the alias mapping fails the build with a diagnostic, rather than being emitted verbatim to produce output that fails at runtime. The build is skipped when no input has changed and the previous output is still on disk (the key is cached under `node_modules/.cache/nmr-compile/`, outside the published output). Deleting the output by any means — `nmr clean`, `rm -rf dist`, `git clean` — therefore forces a rebuild rather than a skip. This is the default `compile` script — run it from a package directory.
|
|
887
849
|
|
|
888
850
|
**What it compiles.** Entry points are `src/**/*.ts` less the directories that hold test scaffolding rather than shipped code: `__fixtures__/`, `__mocks__/`, `__tests__/`, and `test-utils/`. Add to that list per package with [`build.extraIgnorePatterns`](#package-level-configuration). Ignoring a directory drops it as an _entry point_, not from the emit: the compiler still emits whatever the surviving entry points import, so a helper that production code uses is still compiled and its importer never emits a dangling specifier. This list is deliberately not the Vitest [coverage exclusions](#what-the-config-excludes) — helpers live in `test-utils/` precisely so they stay covered.
|
|
889
851
|
|
|
@@ -891,6 +853,8 @@ Compile a single package's `src` tree to `dist/esm` with the TypeScript compiler
|
|
|
891
853
|
|
|
892
854
|
**The replacement is atomic.** The emit is buffered in memory, written to a staging directory beside the output directory, and swapped into place by rename. A build that fails for any reason -- a malformed tsconfig, an unresolvable alias, a full disk -- therefore leaves the previous output exactly as it was. `dist/esm` is never observed mid-write: it holds the previous build or the new one, and is absent only between the two renames. This matters most when the package being rebuilt supplies a binary that other packages in the same recursive build invoke.
|
|
893
855
|
|
|
856
|
+
**What busts the cache.** The key folds each input's content and path, the emit options, the resolved TypeScript version, and a fingerprint of the nmr running the build -- its own build digest in a workspace checkout, its package version otherwise. Upgrading nmr in a consuming repo therefore rebuilds every package nmr compiles, and an edit to nmr's own build sources rebuilds each sibling once nmr itself has been rebuilt. A build of nmr's own package folds the version rather than the digest, which is the cache entry that build is about to write.
|
|
857
|
+
|
|
894
858
|
`typescript` is a peer dependency (`>=5.7.0 <7`); the consuming repo provides it. The floor is what `rewriteRelativeImportExtensions` requires; the ceiling is because TypeScript 7 ships no compiler API — its root export is a version constant, so `nmr-compile` cannot run on it. Relative imports in source must carry explicit `.ts` extensions for them to be rewritten.
|
|
895
859
|
|
|
896
860
|
```bash
|
|
@@ -940,7 +904,7 @@ ensure-prepublish-hooks
|
|
|
940
904
|
|
|
941
905
|
## Conformance checks
|
|
942
906
|
|
|
943
|
-
nmr publishes a `readyup` kit that checks a consuming repo against the current release: the shared Vitest and Prettier configs, the workspace layout, the root script registry,
|
|
907
|
+
nmr publishes a `readyup` kit that checks a consuming repo against the current release: the shared Vitest and Prettier configs, the workspace layout, the root script registry, the [test-tier convention](#test-tiers), and that no `package.json` in the tree declares a `pnpm` field, which pnpm 11 reads no key from. Those settings belong in `pnpm-workspace.yaml`, and `pnpx codemod run pnpm-v10-to-v11` moves them. The kit ships inside the package, so it checks against the nmr version installed rather than whatever a repository ref happens to point at, and a tier added or renamed in nmr reaches the repo on upgrade.
|
|
944
908
|
|
|
945
909
|
Add `readyup` as a devDependency, then name nmr in its config:
|
|
946
910
|
|
|
@@ -13,4 +13,5 @@ delivery: ambient
|
|
|
13
13
|
- A check that already passed on the current working tree skips, printing `⏭️ <scope>: <command>: passed …` and, where the recalled run left one, `— replayed: <excerpt>` of what that run concluded. Any edit, addition, or deletion re-runs it, so read the replayed excerpt rather than paying for the answer again: never pass `--no-cache` to get a "real" run, which costs the command's full runtime to reach the same answer. Its one use is a file rather than an exit status -- a skipped command writes none, so a fresh `coverage/` needs `nmr --no-cache test:coverage`. A composite replays its constituents' excerpts, each attributed as `<scope>: <command>: <excerpt>` and cut at the line's ceiling where there are too many to fit. A skip carrying no excerpt is one whose run wrote to a terminal, or one whose constituents recorded none.
|
|
14
14
|
- `nmr --log <command>` prints the whole run behind that excerpt -- up to 256 KiB, led by when it was recorded, how long it ran, and the command string that produced it -- so output you need in full is a recording to read rather than a run to pay for again. It runs nothing, and it is held to the tree rather than to the terminal: a recording a piped run made prints for a reader at one. A composite prints its constituents' excerpts, so the transcript of one of them is `nmr -F <pkg> --log <command>` or that constituent's own name. Nothing to show is said on one line and exits non-zero, naming which it is.
|
|
15
15
|
- Every command nmr runs reports one line -- `✅ <scope>: <command>: passed in 12.4s`, `❌ … failed in 1.2s (exit 1)`, `⏭️ … passed 4m ago on this tree`, or `⛔ … skipped, the override is empty` -- and a composite reports alongside every command it expands into. The lines print in every verbosity, so `-q` is the low-noise way to run rather than a silent one: it withholds the output of the commands nmr runs and keeps nmr's own verdicts, which is how a pass, a cached skip, and a `""`/`":"` override stay distinguishable. Expect a run to be quiet already without the flag: nmr recognizes a known agent harness in the environment and takes quiet from that, as it does from `NMR_COMMAND_VERBOSITY` or the repo's `output.commandVerbosity`. A command whose output you need in full is one to run with `NMR_COMMAND_VERBOSITY=full`, which outranks both.
|
|
16
|
-
-
|
|
16
|
+
- Every test file lives under a `__tests__` directory and names its isolation tier last: `<subject>[.<aspect>].<tier>.test.ts`, where any earlier segment is free-form documentation. A tier is the furthest thing the test reaches -- `tool` (a program the environment supplies), `localhost` (a service on this machine), `remote` (a machine that isn't this one), or `unit`, the residual claiming everything else. Neither a file outside `__tests__` nor one whose tier segment is missing or misspelt fails anything: the first is collected by nothing and the second runs under `unit`, and no test run distinguishes either from a conformant file. `rdy run --packages` reports the misspelt tier, and nothing reports the misplaced file.
|
|
17
|
+
- The rest is in `node_modules/@williamthorsen/nmr/README.md`: pre/post hooks (every `nmr X` auto-wraps as `nmr X:pre && nmr X && nmr X:post`), script overrides and their `""`/`":"` skip values, `nmr upgrade` ceilings (report-only until `nmr upgrade --write`), the `nmr-compile` build (there is no repo-local build script), the test tiers' timeout budgets and the `tiers` seam that raises one, which script reaches which tier, and the check-result cache's key, the retention key a replayed excerpt is additionally held to, the retention ceiling `--log` prints within, its `checkCache` config, and every condition that disables it.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
|
-
import { DEFAULT_HOOK, ensurePrepublishHooks } from "./commands/ensure-prepublish-hooks.js";
|
|
3
|
+
import { DEFAULT_HOOK, ensurePrepublishHooks, reportPrepublishHooks } from "./commands/ensure-prepublish-hooks.js";
|
|
4
4
|
import { findMonorepoRoot } from "./workspace.js";
|
|
5
5
|
const flagSchema = {
|
|
6
6
|
fix: { long: '--fix', type: 'boolean' },
|
|
@@ -10,31 +10,10 @@ const flagSchema = {
|
|
|
10
10
|
const { fix, dryRun, command } = parseArgsOrExit(process.argv.slice(2), flagSchema).flags;
|
|
11
11
|
try {
|
|
12
12
|
const monorepoRoot = findMonorepoRoot();
|
|
13
|
-
const hookCommand = command ?? DEFAULT_HOOK;
|
|
14
13
|
const options = command ? { fix, dryRun, command } : { fix, dryRun };
|
|
15
14
|
const result = ensurePrepublishHooks(monorepoRoot, options);
|
|
16
|
-
|
|
17
|
-
if (pkg.isPrivate) {
|
|
18
|
-
continue;
|
|
19
|
-
}
|
|
20
|
-
switch (pkg.action) {
|
|
21
|
-
case 'ok':
|
|
22
|
-
console.info(`✓ ${pkg.packageName}: prepublishOnly = "${pkg.prepublishOnly}"`);
|
|
23
|
-
break;
|
|
24
|
-
case 'missing':
|
|
25
|
-
console.warn(`✗ ${pkg.packageName}: missing prepublishOnly`);
|
|
26
|
-
break;
|
|
27
|
-
case 'fixed':
|
|
28
|
-
console.info(`✓ ${pkg.packageName}: added prepublishOnly = "${hookCommand}"`);
|
|
29
|
-
break;
|
|
30
|
-
case 'would-fix':
|
|
31
|
-
console.info(`~ ${pkg.packageName}: would add prepublishOnly = "${hookCommand}"`);
|
|
32
|
-
break;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
15
|
+
reportPrepublishHooks(result, command ?? DEFAULT_HOOK);
|
|
35
16
|
if (result.hasFailures) {
|
|
36
|
-
const missing = result.packages.filter((p) => p.action === 'missing').length;
|
|
37
|
-
process.stderr.write(`\n${missing} package(s) missing prepublishOnly. Use --fix to add it.\n`);
|
|
38
17
|
process.exitCode = 1;
|
|
39
18
|
}
|
|
40
19
|
}
|
|
@@ -16,3 +16,4 @@ export declare function hasExpectedBuildOutput(packageDir: string, outdir: strin
|
|
|
16
16
|
export declare function readBuildDigest(packageDir: string): Promise<string | undefined>;
|
|
17
17
|
export declare function resolveBuildCachePath(packageDir: string): string;
|
|
18
18
|
export declare function resolveScratchDirs(emitDir: string): ScratchDirs;
|
|
19
|
+
export declare function resolveToolchainFingerprint(packageDir: string, selfDir?: string): Promise<string>;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { existsSync, readdirSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readdirSync, realpathSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { readCacheEntry, resolveCacheEntryPath } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { findPackageRoot, readCacheEntry, resolveCacheEntryPath } from '@williamthorsen/nmr-core';
|
|
4
4
|
import { glob } from 'glob';
|
|
5
|
+
import { readPackageJson } from "../helpers/package-json.js";
|
|
5
6
|
export const DEFAULT_ENTRY_GLOBS = ['src/**/*.ts'];
|
|
6
7
|
export const DEFAULT_IGNORE_PATTERNS = ['**/__fixtures__/**', '**/__mocks__/**', '**/__tests__/**', '**/test-utils/**'];
|
|
7
8
|
export const DEFAULT_OUTDIR = 'dist/esm/';
|
|
8
9
|
const BUILD_CACHE_TOOL = 'nmr-compile';
|
|
10
|
+
const SELF_DIR = findPackageRoot(import.meta.url);
|
|
9
11
|
export async function hasBuildOutput(packageDir, options = {}) {
|
|
10
12
|
const outdir = options.outdir ?? DEFAULT_OUTDIR;
|
|
11
13
|
const entryPoints = await glob(options.entryGlobs ?? DEFAULT_ENTRY_GLOBS, {
|
|
@@ -42,3 +44,24 @@ export function resolveScratchDirs(emitDir) {
|
|
|
42
44
|
staging: path.join(parent, `.${name}.staging`),
|
|
43
45
|
};
|
|
44
46
|
}
|
|
47
|
+
export async function resolveToolchainFingerprint(packageDir, selfDir = SELF_DIR) {
|
|
48
|
+
if (!isSameDir(packageDir, selfDir)) {
|
|
49
|
+
const digest = await readBuildDigest(selfDir);
|
|
50
|
+
if (digest !== undefined) {
|
|
51
|
+
return digest;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return readPackageJson(selfDir).version ?? '';
|
|
55
|
+
}
|
|
56
|
+
function isSameDir(left, right) {
|
|
57
|
+
return toRealPath(left) === toRealPath(right);
|
|
58
|
+
}
|
|
59
|
+
function toRealPath(dir) {
|
|
60
|
+
const absolute = path.resolve(dir);
|
|
61
|
+
try {
|
|
62
|
+
return realpathSync(absolute);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return absolute;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { BuildOptions } from './build-output.js';
|
|
2
|
+
export interface BuildToolchain {
|
|
3
|
+
compilerVersion: string;
|
|
4
|
+
fingerprint: string;
|
|
5
|
+
}
|
|
2
6
|
export declare function buildPackage(packageDir: string, options?: BuildOptions): Promise<void>;
|
|
3
|
-
export declare function computeBuildHash(packageDir: string, files: string[], emitConfig: object,
|
|
7
|
+
export declare function computeBuildHash(packageDir: string, files: string[], emitConfig: object, toolchain: BuildToolchain): Promise<string>;
|
|
4
8
|
export declare function resolveTsconfigChain(packageDir: string, configFileName?: string): string[];
|
|
@@ -6,7 +6,7 @@ import { readCacheEntry, writeCacheEntry } from '@williamthorsen/nmr-core';
|
|
|
6
6
|
import { glob } from 'glob';
|
|
7
7
|
import * as ts from 'typescript';
|
|
8
8
|
import { resolveConfigPath } from "../config.js";
|
|
9
|
-
import { DEFAULT_ENTRY_GLOBS, DEFAULT_IGNORE_PATTERNS, DEFAULT_OUTDIR, hasExpectedBuildOutput, resolveBuildCachePath, resolveScratchDirs, } from "./build-output.js";
|
|
9
|
+
import { DEFAULT_ENTRY_GLOBS, DEFAULT_IGNORE_PATTERNS, DEFAULT_OUTDIR, hasExpectedBuildOutput, resolveBuildCachePath, resolveScratchDirs, resolveToolchainFingerprint, } from "./build-output.js";
|
|
10
10
|
const PACKAGE_ICON = '📦';
|
|
11
11
|
const SKIPPED_ICON = '⏭️';
|
|
12
12
|
const SOURCE_ROOT = 'src';
|
|
@@ -30,15 +30,20 @@ export async function buildPackage(packageDir, options = {}) {
|
|
|
30
30
|
...(existsSync(configPath) ? [path.relative(packageDir, configPath)] : []),
|
|
31
31
|
...resolveTsconfigChain(packageDir),
|
|
32
32
|
];
|
|
33
|
-
const
|
|
33
|
+
const toolchain = {
|
|
34
|
+
compilerVersion: ts.version,
|
|
35
|
+
fingerprint: await resolveToolchainFingerprint(packageDir),
|
|
36
|
+
};
|
|
37
|
+
const { changed, currentHash } = await detectBuildChanges(packageDir, [...entryPoints, ...dependencies], emitConfig, toolchain, cachePath, hasExpectedBuildOutput(packageDir, outdir, entryPoints));
|
|
34
38
|
if (!changed) {
|
|
35
39
|
await discardScratchDirs(resolveScratchDirs(emitDir));
|
|
36
40
|
return;
|
|
37
41
|
}
|
|
38
|
-
await emitPackage(packageDir, entryPoints, outdir);
|
|
42
|
+
const emittedFileCount = await emitPackage(packageDir, entryPoints, outdir);
|
|
39
43
|
await writeCacheEntry(cachePath, currentHash);
|
|
44
|
+
console.info(`${PACKAGE_ICON} ${path.basename(packageDir)}: ${describeEmit(emittedFileCount, outdir)}`);
|
|
40
45
|
}
|
|
41
|
-
export async function computeBuildHash(packageDir, files, emitConfig,
|
|
46
|
+
export async function computeBuildHash(packageDir, files, emitConfig, toolchain) {
|
|
42
47
|
const hash = createHash('sha256');
|
|
43
48
|
for (const file of files.toSorted()) {
|
|
44
49
|
hash.update(file);
|
|
@@ -47,7 +52,9 @@ export async function computeBuildHash(packageDir, files, emitConfig, compilerVe
|
|
|
47
52
|
}
|
|
48
53
|
hash.update(JSON.stringify(emitConfig));
|
|
49
54
|
hash.update('\0');
|
|
50
|
-
hash.update(compilerVersion);
|
|
55
|
+
hash.update(toolchain.compilerVersion);
|
|
56
|
+
hash.update('\0');
|
|
57
|
+
hash.update(toolchain.fingerprint);
|
|
51
58
|
return hash.digest('hex');
|
|
52
59
|
}
|
|
53
60
|
export function resolveTsconfigChain(packageDir, configFileName = 'tsconfig.json') {
|
|
@@ -92,10 +99,11 @@ async function emitPackage(packageDir, entryPoints, outdir) {
|
|
|
92
99
|
}
|
|
93
100
|
if (staged.size === 0) {
|
|
94
101
|
await rm(emitDir, { force: true, recursive: true });
|
|
95
|
-
return;
|
|
102
|
+
return 0;
|
|
96
103
|
}
|
|
97
104
|
writeStagedOutput(staged, emitDir, scratchDirs.staging);
|
|
98
105
|
await swapIntoPlace(emitDir, scratchDirs);
|
|
106
|
+
return staged.size;
|
|
99
107
|
}
|
|
100
108
|
async function discardScratchDirs(scratchDirs) {
|
|
101
109
|
try {
|
|
@@ -247,10 +255,10 @@ function getModuleSpecifier(node) {
|
|
|
247
255
|
}
|
|
248
256
|
return undefined;
|
|
249
257
|
}
|
|
250
|
-
async function detectBuildChanges(packageDir, files, emitConfig,
|
|
258
|
+
async function detectBuildChanges(packageDir, files, emitConfig, toolchain, cachePath, outputPresent) {
|
|
251
259
|
const packageName = path.basename(packageDir);
|
|
252
260
|
const previousHash = await readCacheEntry(cachePath);
|
|
253
|
-
const currentHash = await computeBuildHash(packageDir, files, emitConfig,
|
|
261
|
+
const currentHash = await computeBuildHash(packageDir, files, emitConfig, toolchain);
|
|
254
262
|
if (previousHash === currentHash) {
|
|
255
263
|
if (outputPresent) {
|
|
256
264
|
console.info(`${SKIPPED_ICON} ${packageName}: No changes detected. Skipping build.`);
|
|
@@ -278,6 +286,12 @@ function collectAliasPrefixes(compilerOptions) {
|
|
|
278
286
|
}
|
|
279
287
|
return Object.keys(compilerOptions.paths).map((pattern) => pattern.replace(/\*$/, ''));
|
|
280
288
|
}
|
|
289
|
+
function describeEmit(fileCount, outdir) {
|
|
290
|
+
if (fileCount === 0) {
|
|
291
|
+
return 'Emitted no output.';
|
|
292
|
+
}
|
|
293
|
+
return `Compiled ${fileCount} files to ${outdir.replace(/\/+$/u, '')}.`;
|
|
294
|
+
}
|
|
281
295
|
function formatDiagnostics(diagnostics) {
|
|
282
296
|
return ts.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
283
297
|
getCurrentDirectory: () => process.cwd(),
|
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { removeCheckCache } from "../check-cache.js";
|
|
4
4
|
import { loadRootConfig } from "../config.js";
|
|
5
5
|
import { findContainingPackageDir } from "../context.js";
|
|
6
|
+
import { reportClosing } from "../helpers/reportClosing.js";
|
|
6
7
|
import { applyDevBin, buildWorkspaceRegistry, resolveScript } from "../resolver.js";
|
|
7
8
|
import { resolveChannel, runCommand } from "../runner.js";
|
|
8
9
|
import { renderChain } from "../steps.js";
|
|
@@ -40,17 +41,26 @@ async function clearCheckCache(scopeDir) {
|
|
|
40
41
|
await removeCheckCache(scopeDir);
|
|
41
42
|
console.info(`${CLEAN_ICON} Removed all recorded check results.`);
|
|
42
43
|
}
|
|
44
|
+
function describeSweep(cleaned, skipped) {
|
|
45
|
+
const packages = cleaned === 1 ? '1 package' : `${cleaned} packages`;
|
|
46
|
+
const skipClause = skipped === 0 ? '' : `, skipping ${skipped} with an empty clean override`;
|
|
47
|
+
return `Cleaned ${packages}${skipClause}.`;
|
|
48
|
+
}
|
|
43
49
|
async function sweepWorkspace(monorepoRoot, workspacePackageDirs) {
|
|
44
50
|
const config = await loadRootConfig(monorepoRoot);
|
|
45
51
|
const registry = buildWorkspaceRegistry(config);
|
|
52
|
+
let cleaned = 0;
|
|
53
|
+
let skipped = 0;
|
|
46
54
|
for (const packageDir of workspacePackageDirs) {
|
|
47
55
|
const resolved = resolveScript(CLEAN_COMMAND, registry, packageDir, false);
|
|
48
56
|
const resolvedCommand = resolved === undefined ? undefined : renderChain(resolved.steps);
|
|
49
57
|
if (resolvedCommand === undefined || resolvedCommand === '') {
|
|
58
|
+
skipped++;
|
|
50
59
|
continue;
|
|
51
60
|
}
|
|
52
61
|
if (resolvedCommand === BUILT_IN_CLEAN) {
|
|
53
62
|
await cleanPackage(packageDir);
|
|
63
|
+
cleaned++;
|
|
54
64
|
continue;
|
|
55
65
|
}
|
|
56
66
|
const command = applyDevBin(resolvedCommand, config.devBin, monorepoRoot);
|
|
@@ -60,5 +70,7 @@ async function sweepWorkspace(monorepoRoot, workspacePackageDirs) {
|
|
|
60
70
|
if (exitCode !== 0) {
|
|
61
71
|
throw new Error(`nmr-clean: \`${command}\` failed in ${path.basename(packageDir)} with exit code ${exitCode}.`);
|
|
62
72
|
}
|
|
73
|
+
cleaned++;
|
|
63
74
|
}
|
|
75
|
+
reportClosing(`${CLEAN_ICON} ${describeSweep(cleaned, skipped)}`);
|
|
64
76
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { readPackageJson } from "../helpers/package-json.js";
|
|
4
|
+
import { reportClosing } from "../helpers/reportClosing.js";
|
|
4
5
|
import { isObject } from "../helpers/type-guards.js";
|
|
5
6
|
import { getWorkspacePackageDirs } from "../workspace.js";
|
|
6
7
|
export const DEFAULT_HOOK = 'npm run build';
|
|
@@ -59,6 +60,17 @@ export function ensurePrepublishHooks(monorepoRoot, options) {
|
|
|
59
60
|
const hasFailures = packages.some((p) => p.action === 'missing');
|
|
60
61
|
return { packages, hasFailures };
|
|
61
62
|
}
|
|
63
|
+
export function reportPrepublishHooks(result, hookCommand) {
|
|
64
|
+
const publishable = result.packages.filter((pkg) => !pkg.isPrivate);
|
|
65
|
+
if (publishable.length === 0) {
|
|
66
|
+
console.info('No publishable packages found.');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
for (const pkg of publishable) {
|
|
70
|
+
console.info(renderHookStatus(pkg, hookCommand));
|
|
71
|
+
}
|
|
72
|
+
reportClosing(describeHookRun(publishable));
|
|
73
|
+
}
|
|
62
74
|
function addPrepublishOnly(packageDir, command) {
|
|
63
75
|
const filePath = path.join(packageDir, 'package.json');
|
|
64
76
|
const raw = readFileSync(filePath, 'utf8');
|
|
@@ -71,3 +83,34 @@ function addPrepublishOnly(packageDir, command) {
|
|
|
71
83
|
parsed['scripts'] = scripts;
|
|
72
84
|
writeFileSync(filePath, JSON.stringify(parsed, null, 2) + '\n', 'utf8');
|
|
73
85
|
}
|
|
86
|
+
function countAction(publishable, action) {
|
|
87
|
+
return publishable.filter((pkg) => pkg.action === action).length;
|
|
88
|
+
}
|
|
89
|
+
function describeHookRun(publishable) {
|
|
90
|
+
const packages = publishable.length === 1 ? '1 publishable package' : `${publishable.length} publishable packages`;
|
|
91
|
+
const missing = countAction(publishable, 'missing');
|
|
92
|
+
if (missing > 0) {
|
|
93
|
+
return `${missing} of ${packages} ${missing === 1 ? 'is' : 'are'} missing prepublishOnly. Run with --fix to add it.`;
|
|
94
|
+
}
|
|
95
|
+
const fixed = countAction(publishable, 'fixed');
|
|
96
|
+
if (fixed > 0) {
|
|
97
|
+
return `Added prepublishOnly to ${fixed} of ${packages}.`;
|
|
98
|
+
}
|
|
99
|
+
const wouldFix = countAction(publishable, 'would-fix');
|
|
100
|
+
if (wouldFix > 0) {
|
|
101
|
+
return `Would add prepublishOnly to ${wouldFix} of ${packages}.`;
|
|
102
|
+
}
|
|
103
|
+
return `${packages} ${publishable.length === 1 ? 'has' : 'have'} prepublishOnly.`;
|
|
104
|
+
}
|
|
105
|
+
function renderHookStatus(pkg, hookCommand) {
|
|
106
|
+
switch (pkg.action) {
|
|
107
|
+
case 'ok':
|
|
108
|
+
return `✓ ${pkg.packageName}: prepublishOnly = "${pkg.prepublishOnly}"`;
|
|
109
|
+
case 'missing':
|
|
110
|
+
return `✗ ${pkg.packageName}: missing prepublishOnly`;
|
|
111
|
+
case 'fixed':
|
|
112
|
+
return `✓ ${pkg.packageName}: added prepublishOnly = "${hookCommand}"`;
|
|
113
|
+
case 'would-fix':
|
|
114
|
+
return `~ ${pkg.packageName}: would add prepublishOnly = "${hookCommand}"`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { findContainingPackageDir } from "../context.js";
|
|
2
2
|
import { DEPENDENCY_FIELDS, readPackageJson } from "../helpers/package-json.js";
|
|
3
|
+
import { reportClosing } from "../helpers/reportClosing.js";
|
|
3
4
|
import { findMonorepoRoot, getWorkspacePackageDirs } from "../workspace.js";
|
|
5
|
+
const CATALOG_ICON = '📚';
|
|
4
6
|
const CATALOG_PROTOCOL = 'catalog:';
|
|
5
7
|
export function reportCatalog(cwd) {
|
|
6
8
|
const monorepoRoot = findMonorepoRoot(cwd);
|
|
@@ -12,12 +14,16 @@ export function reportCatalog(cwd) {
|
|
|
12
14
|
if (dependencies.length === 0) {
|
|
13
15
|
return;
|
|
14
16
|
}
|
|
15
|
-
|
|
16
|
-
console.warn(`📚 WARN: ${subject} from a catalog, which a package-scoped upgrade does not read:`);
|
|
17
|
+
console.warn(`${CATALOG_ICON} WARN: A package-scoped upgrade does not read the catalogs these come from:`);
|
|
17
18
|
for (const { name, specifier } of dependencies) {
|
|
18
19
|
console.warn(`- ${name} → ${specifier}`);
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
+
reportClosing(`${CATALOG_ICON} ${describeCatalog(dependencies.length, monorepoRoot)}`, console.warn);
|
|
22
|
+
}
|
|
23
|
+
function describeCatalog(count, monorepoRoot) {
|
|
24
|
+
const subject = count === 1 ? '1 catalogued dependency went' : `${count} catalogued dependencies went`;
|
|
25
|
+
const object = count === 1 ? 'it' : 'them';
|
|
26
|
+
return `${subject} unread. Run \`nmr upgrade\` from ${monorepoRoot} to include ${object}.`;
|
|
21
27
|
}
|
|
22
28
|
function findCataloguedDependencies(packageDir) {
|
|
23
29
|
const pkg = readPackageJson(packageDir);
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import { getPnpmOverrides, readPackageJson } from "../helpers/package-json.js";
|
|
2
|
+
import { reportClosing } from "../helpers/reportClosing.js";
|
|
2
3
|
import { UserError } from "../UserError.js";
|
|
3
4
|
import { readWorkspaceOverrides } from "../workspace.js";
|
|
5
|
+
const OVERRIDES_ICON = '🔒';
|
|
4
6
|
export function reportOverrides(monorepoRoot) {
|
|
5
7
|
const declared = listEntries(readWorkspaceOverrides(monorepoRoot));
|
|
6
8
|
if (declared.length > 0) {
|
|
7
|
-
console.warn(
|
|
9
|
+
console.warn(`${OVERRIDES_ICON} WARN: pnpm overrides are active:`);
|
|
8
10
|
for (const [name, version] of declared) {
|
|
9
11
|
console.warn(`- ${name} → ${version}`);
|
|
10
12
|
}
|
|
13
|
+
reportClosing(`${OVERRIDES_ICON} ${describeOverrides(declared.length)}`, console.warn);
|
|
11
14
|
}
|
|
12
15
|
rejectLegacyOverrides(monorepoRoot);
|
|
13
16
|
}
|
|
17
|
+
function describeOverrides(count) {
|
|
18
|
+
return count === 1
|
|
19
|
+
? '1 override is active. Check whether it is still needed.'
|
|
20
|
+
: `${count} overrides are active. Check whether they are still needed.`;
|
|
21
|
+
}
|
|
14
22
|
function listEntries(overrides) {
|
|
15
23
|
return Object.entries(overrides ?? {}).toSorted(([a], [b]) => a.localeCompare(b));
|
|
16
24
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function reportClosing(message: string, log?: (line: string) => void): void;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { reportError } from '@williamthorsen/nmr-core';
|
|
2
|
-
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
2
|
+
import { describeError, isError } from '@williamthorsen/toolbelt.errors';
|
|
3
3
|
import { UserError } from "./UserError.js";
|
|
4
4
|
export function reportCliFailure(error, stderr) {
|
|
5
5
|
if (error instanceof UserError) {
|
|
6
6
|
reportError(error.message, stderr);
|
|
7
7
|
return;
|
|
8
8
|
}
|
|
9
|
-
const detail = error
|
|
9
|
+
const detail = isError(error) && error.stack !== undefined ? error.stack : describeError(error);
|
|
10
10
|
stderr.write(`${detail}\n`);
|
|
11
11
|
}
|
package/dist/esm/resolver.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { hasErrnoCode } from '@williamthorsen/nmr-core';
|
|
3
4
|
import { parsePackageJson, readScriptRecord, resolvePackageJsonPath } from "./helpers/package-json.js";
|
|
4
5
|
import { isObject } from "./helpers/type-guards.js";
|
|
5
6
|
import { getDefaultRootScripts, getDefaultWorkspaceScripts } from "./resolve-scripts.js";
|
|
@@ -62,7 +63,7 @@ export function readPackageJsonScripts(packageDir) {
|
|
|
62
63
|
raw = readFileSync(file, 'utf8');
|
|
63
64
|
}
|
|
64
65
|
catch (error) {
|
|
65
|
-
if (error
|
|
66
|
+
if (hasErrnoCode(error, 'ENOENT')) {
|
|
66
67
|
return undefined;
|
|
67
68
|
}
|
|
68
69
|
throw error;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/nmr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Context-aware script runner for pnpm monorepos",
|
|
6
6
|
"keywords": [
|
|
@@ -68,16 +68,18 @@
|
|
|
68
68
|
"dist"
|
|
69
69
|
],
|
|
70
70
|
"dependencies": {
|
|
71
|
-
"@williamthorsen/toolbelt.errors": "0.
|
|
71
|
+
"@williamthorsen/toolbelt.errors": "0.5.0",
|
|
72
72
|
"glob": "13.0.6",
|
|
73
73
|
"prettier-plugin-sh": "0.19.0",
|
|
74
|
-
"taze": "20.0.
|
|
74
|
+
"taze": "20.0.1",
|
|
75
75
|
"yaml": "2.9.0",
|
|
76
|
-
"@williamthorsen/nmr-core": "0.10.
|
|
76
|
+
"@williamthorsen/nmr-core": "0.10.1"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
|
+
"@williamthorsen/toolbelt.testing": "0.3.0",
|
|
80
|
+
"@williamthorsen/toolbelt.vitest": "0.4.0",
|
|
79
81
|
"prettier": "3.9.6",
|
|
80
|
-
"readyup": "0.
|
|
82
|
+
"readyup": "0.28.0",
|
|
81
83
|
"tinyglobby": "0.2.17",
|
|
82
84
|
"typescript": "6.0.3",
|
|
83
85
|
"vitest": "4.1.10"
|