@williamthorsen/nmr 0.37.0 → 0.39.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/.meta/changelog.json +114 -0
- package/.readyup/kits/default.js +92 -87
- package/.readyup/manifest.json +8 -8
- package/CHANGELOG.md +89 -0
- package/README.md +37 -28
- package/agents/guidance/rulebooks/nmr.md +2 -2
- package/dist/esm/check-cache.d.ts +4 -3
- package/dist/esm/check-cache.js +28 -28
- package/dist/esm/cli-build.js +3 -0
- package/dist/esm/cli-clean.js +3 -1
- package/dist/esm/cli-ensure-prepublish-hooks.js +7 -5
- package/dist/esm/cli-report-catalog.js +2 -1
- package/dist/esm/cli-report-overrides.js +2 -1
- package/dist/esm/commands/build-output.d.ts +2 -2
- package/dist/esm/commands/build-output.js +9 -9
- package/dist/esm/commands/build.d.ts +6 -1
- package/dist/esm/commands/build.js +73 -72
- package/dist/esm/commands/clean.d.ts +3 -2
- package/dist/esm/commands/clean.js +25 -24
- package/dist/esm/commands/ensure-prepublish-hooks.d.ts +5 -4
- package/dist/esm/commands/ensure-prepublish-hooks.js +44 -42
- package/dist/esm/commands/fmt.js +33 -33
- package/dist/esm/commands/report-catalog.d.ts +2 -1
- package/dist/esm/commands/report-catalog.js +14 -11
- package/dist/esm/commands/report-overrides.d.ts +2 -1
- package/dist/esm/commands/report-overrides.js +11 -10
- package/dist/esm/config.js +39 -29
- package/dist/esm/context.js +8 -8
- package/dist/esm/default-scripts.d.ts +2 -2
- package/dist/esm/default-scripts.js +5 -5
- package/dist/esm/glyphs.d.ts +9 -0
- package/dist/esm/glyphs.js +9 -0
- package/dist/esm/help.d.ts +2 -2
- package/dist/esm/help.js +36 -30
- package/dist/esm/helpers/clampToBytes.js +3 -3
- package/dist/esm/helpers/createBoundedBuffer.d.ts +1 -1
- package/dist/esm/helpers/createBoundedBuffer.js +2 -2
- package/dist/esm/helpers/duration.d.ts +2 -2
- package/dist/esm/helpers/duration.js +7 -7
- package/dist/esm/helpers/filter-selection.js +12 -12
- package/dist/esm/helpers/findClosestName.js +15 -15
- package/dist/esm/helpers/package-json.d.ts +2 -2
- package/dist/esm/helpers/package-json.js +26 -25
- package/dist/esm/helpers/transcript.d.ts +2 -2
- package/dist/esm/helpers/transcript.js +14 -14
- package/dist/esm/output-style.d.ts +41 -0
- package/dist/esm/output-style.js +17 -0
- package/dist/esm/prettier.js +5 -5
- package/dist/esm/recording.d.ts +8 -5
- package/dist/esm/recording.js +27 -17
- package/dist/esm/report-format.d.ts +1 -1
- package/dist/esm/report-format.js +7 -7
- package/dist/esm/resolveBinStyles.d.ts +12 -0
- package/dist/esm/resolveBinStyles.js +16 -0
- package/dist/esm/resolver.d.ts +2 -2
- package/dist/esm/resolver.js +16 -16
- package/dist/esm/runCli.js +309 -221
- package/dist/esm/runner.d.ts +1 -1
- package/dist/esm/runner.js +18 -18
- package/dist/esm/steps.d.ts +17 -5
- package/dist/esm/steps.js +37 -28
- package/dist/esm/tests.d.ts +7 -7
- package/dist/esm/tests.js +20 -8
- package/dist/esm/tiers.d.ts +1 -1
- package/dist/esm/tiers.js +8 -8
- package/dist/esm/verbosity.d.ts +1 -1
- package/dist/esm/verbosity.js +7 -7
- package/dist/esm/verdict.d.ts +15 -4
- package/dist/esm/verdict.js +78 -59
- package/dist/esm/vitest-source-resolution.js +48 -46
- package/dist/esm/vitest.d.ts +2 -2
- package/dist/esm/vitest.js +74 -25
- package/dist/esm/workspace.d.ts +2 -18
- package/dist/esm/workspace.js +9 -67
- package/docs/check-cache.md +5 -5
- package/docs/reporting.md +20 -2
- package/docs/scripts.md +2 -2
- package/docs/vitest.md +6 -6
- package/docs/workspace.md +24 -1
- package/package.json +9 -10
- package/dist/esm/helpers/workspace-patterns.d.ts +0 -8
- package/dist/esm/helpers/workspace-patterns.js +0 -29
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,95 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.39.0 — 2026-09-22
|
|
6
|
+
|
|
7
|
+
### 🎉 Features
|
|
8
|
+
|
|
9
|
+
- Report why a workspace selects no package, and no-op nmr's own -R steps (#841)
|
|
10
|
+
|
|
11
|
+
- Drops a `-R` step that nmr composed into a script where the workspace holds no package, so `typecheck` and the five `test` scripts run there instead of failing the run.
|
|
12
|
+
- Adds a no-op verdict for a command that the drop leaves with no step at all.
|
|
13
|
+
- Replaces the single refusal for a workspace that selects no package with one message per cause.
|
|
14
|
+
|
|
15
|
+
Migration: In a repo that declares a workspace package pattern and deliberately holds no package, delete the `.config/nmr.config.ts` entries added to work around the `-R` refusal, the `build: ':'` override included.
|
|
16
|
+
|
|
17
|
+
- 🚨 **Breaking:** Prefix the booleans and reject unrecognized Vitest option keys (#849)
|
|
18
|
+
|
|
19
|
+
- Renames two boolean options of `defineVitestConfig` and `defineRootVitestConfig`, the factories that build a repo's Vitest config: `isolateGit` becomes `shouldIsolateGit`, and `resolveFromSource` becomes `shouldResolveFromSource`.
|
|
20
|
+
- Makes both factories reject an option key outside the recognized set, naming the offending keys and the recognized ones, rather than ignoring a misspelled or retired key and letting its option fall back to the default with nothing in the run reporting it.
|
|
21
|
+
- Adds a retired-key check that runs ahead of the unrecognized-key check, reporting the new name for a config that still sets `isolateGit` or `resolveFromSource`.
|
|
22
|
+
|
|
23
|
+
Migration: In each options object passed to `defineVitestConfig` or `defineRootVitestConfig`, rename `isolateGit` to `shouldIsolateGit` and `resolveFromSource` to `shouldResolveFromSource`, and correct any other key that the factory now rejects. `defineRootVitestConfig` recognizes `monorepoRoot` on the last layer alone, so a config that sets it on an earlier layer now throws where the old code ignored it.
|
|
24
|
+
|
|
25
|
+
- 🚨 **Breaking:** End nmr's source value names with the kind of the value (#850)
|
|
26
|
+
|
|
27
|
+
- Renames two fields of `TestFileConventionsReport`, `misplaced` and `untiered`, to `misplacedFiles` and `untieredFiles`, and renames `TestFileConventionsOptions`'s `exclude` to `excludedBasenames`, in the `@williamthorsen/nmr/tests` entry point.
|
|
28
|
+
- Adds a guard to `checkTestFileConventions` and `reportTestFileConventions` that throws a `TypeError` naming `excludedBasenames` when a call still sets `exclude`.
|
|
29
|
+
|
|
30
|
+
Migration: Read `report.misplacedFiles` and `report.untieredFiles` in place of `report.misplaced` and `report.untiered`, and pass `excludedBasenames` in place of `exclude`. A call that still sets `exclude` throws rather than running with no exclusions, so the failure surfaces where Vitest loads the call site.
|
|
31
|
+
|
|
32
|
+
- 🚨 **Breaking:** Hoist the pnpm-workspace resolver into nmr-core as a total API (#864)
|
|
33
|
+
|
|
34
|
+
- Adds a `./workspace` subpath export to `@williamthorsen/nmr-core`, whose `resolveWorkspace` returns a pnpm workspace's package directories, or the cause of an empty result, as a value rather than as a thrown error.
|
|
35
|
+
- Reports a `pnpm-workspace.yaml` that the YAML parser rejects under a cause of its own, `unreadable-manifest`, so `nmr -R build` names the syntax error and its remedy in place of failing with the parser's own error.
|
|
36
|
+
- Removes `diagnoseEmptyWorkspace` and `EmptyWorkspaceDiagnosis` from `@williamthorsen/nmr/workspace`, since `resolveWorkspace` reports the same cause in one call.
|
|
37
|
+
|
|
38
|
+
Migration: Replace each `diagnoseEmptyWorkspace` call with `resolveWorkspace`, which reports the cause on the empty result it already returns, and rename the `no-manifest` cause to `no-package`. `EmptyWorkspaceCause` also gains `unreadable-manifest`, so an exhaustive switch over it stops compiling until it handles the new member; every other export of `@williamthorsen/nmr/workspace` keeps its signature and its `UserError` throws.
|
|
39
|
+
|
|
40
|
+
- 🚨 **Breaking:** Adopt the shared workspace resolver in release-kit, honoring exclusions (#865)
|
|
41
|
+
|
|
42
|
+
- 🚨 **Breaking:** Narrows `no-pattern` in `@williamthorsen/nmr-core`'s `EmptyWorkspaceCause`, moving an absent or empty `packages` list onto `no-packages-list` and a value that is not a list of strings onto `unreadable-packages`.
|
|
43
|
+
- Stops `release-kit` from tagging, publishing, and label-syncing a package that a `!` entry in `pnpm-workspace.yaml` excludes, which its previous `glob`-based resolution could not remove from the match.
|
|
44
|
+
- Replaces the release of the root package alone with a failure naming the condition that emptied the workspace, when `pnpm-workspace.yaml` does not hold valid YAML or declares a workspace that does not resolve to a package.
|
|
45
|
+
- Replaces the `no-pattern` remedy that `nmr` printed for a `pnpm-workspace.yaml` declaring no `packages` list, and for one declaring a `packages` value that is not a list of strings.
|
|
46
|
+
- Raises `engines.node` to `>=24.16.0` in `@williamthorsen/nmr-core` and `@williamthorsen/release-kit`, because Node 24.0 through 24.15 ignores the `followSymlinks` option that the workspace matcher passes to `globSync` and drops a symlinked package directory from the resolution without reporting it.
|
|
47
|
+
|
|
48
|
+
Migration: Add a `no-packages-list` arm and an `unreadable-packages` arm to any `switch` that covers `EmptyWorkspaceCause` exhaustively. `no-pattern` now names only a list that the resolver read in full and whose entries all failed to become a positive pattern, so move the handling of an absent `packages` key, an empty list, and a value that is not a list of strings out of the `no-pattern` arm into the two new ones. Route `no-packages-list` to single-package behavior rather than to a failure path: pnpm resolves such a manifest to the root package alone.
|
|
49
|
+
|
|
50
|
+
### ♻️ Refactoring
|
|
51
|
+
|
|
52
|
+
- Align nmr's function names with naming conventions (#852)
|
|
53
|
+
|
|
54
|
+
- Renames the functions in `packages/nmr` to align with naming conventions.
|
|
55
|
+
|
|
56
|
+
- Suffix nmr's unit-measuring numbers and expand its abbreviations (#854)
|
|
57
|
+
|
|
58
|
+
- Renames every unit-measuring number and abbreviated identifier under `packages/nmr/src`, adding `Ms`, `Bytes`, and `Offset` suffixes to the quantities and expanding `pkg`, `Len`, and `Idx` to `package`, `Length`, and `Index`. The renames are not part of any public API.
|
|
59
|
+
|
|
60
|
+
### 🧪 Tests
|
|
61
|
+
|
|
62
|
+
- End nmr's test value names with the kind of the value (#851)
|
|
63
|
+
|
|
64
|
+
- Aligns the name of every non-boolean value in `packages/nmr/src/**/__tests__` with naming conventions.
|
|
65
|
+
- Renames `inline` to `inlineBody` in the repo-root `__tests__/reusable-workflow-runtime.app.unit.test.ts`.
|
|
66
|
+
|
|
67
|
+
### 👷 CI
|
|
68
|
+
|
|
69
|
+
- Make the release workflow Corepack-agnostic (#842)
|
|
70
|
+
|
|
71
|
+
- Removes the `Disable Corepack` step from `release.reusable.yaml`, which Node 25's removal of Corepack from the distribution would make fail with `command not found` once `actions/setup-node` moves past Node 24, and keeps `COREPACK_ENABLE_STRICT: '0'`, which guards the source-build path where the pnpm `packageManager` pin survives.
|
|
72
|
+
- Adds a case to `reusable-workflow-runtime.app.unit.test.ts` that fails the test run on any reusable workflow whose `run:` commands invoke `corepack`, enumerated from `.github/workflows` so that a workflow added later is covered without editing a list.
|
|
73
|
+
- Drops the Corepack recommendation from the README's pnpm prerequisite, which now points at pnpm's installation page, and from the fix text of the `.tool-versions does not list pnpm` check in nmr's readyup kit, which now names the `packageManager` field.
|
|
74
|
+
|
|
75
|
+
## 0.38.0 — 2026-09-18
|
|
76
|
+
|
|
77
|
+
### 🎉 Features
|
|
78
|
+
|
|
79
|
+
- Give nmr plain output in pipes and CI logs (#838)
|
|
80
|
+
|
|
81
|
+
- Stops `nmr` from printing emoji to a pipe or a CI log: When the output stream is not a terminal or `CI` is set, a verdict reads `PASS`, `FAIL`, `SKIP`, or `NOOP` and a command icon is omitted, so that a log can be searched for a verdict.
|
|
82
|
+
- Adds `--output-style <auto|plain|rich>` to `nmr` and the `NMR_OUTPUT_STYLE` variable, each forcing either style; the flag outranks the variable, a value that names no style exits 1 with a usage error, and neither enters a check-cache key: A rich run and a plain run share a recorded pass.
|
|
83
|
+
- Replaces several rich glyphs, taking the status markers from the set that release-kit and v11y-check print so that one status has one glyph across the three tools: a recalled pass shows ⏩ in place of ⏭️, a skipped empty override shows ⚪ in place of ⛔, a warning and the `--log` refusal show 🟠 in place of ⚠️ and 📭, the `--log` header shows 💾 in place of 📼, and `nmr-ensure-prepublish-hooks` reports ✅ and ❌ in place of ✓ and ✗.
|
|
84
|
+
|
|
85
|
+
### 🪦 Removed
|
|
86
|
+
|
|
87
|
+
- 🚨 **Breaking:** Rename the declinesArgs step declaration to shouldDeclineArguments (#822)
|
|
88
|
+
|
|
89
|
+
- Makes a `.config/nmr.config.ts` that still declares `declinesArgs` on a composite script's `{ run, declinesArgs }` step fail to load, with an error that names `shouldDeclineArguments` as the replacement.
|
|
90
|
+
- Renames the matching field of the `StepSpec` type exported by `@williamthorsen/nmr/config` and `@williamthorsen/nmr/scripts`.
|
|
91
|
+
|
|
92
|
+
Migration: Rename `declinesArgs` to `shouldDeclineArguments` in each composite script step of `.config/nmr.config.ts`, and in any code that sets or reads the field on a `StepSpec`.
|
|
93
|
+
|
|
5
94
|
## 0.37.0 — 2026-09-15
|
|
6
95
|
|
|
7
96
|
### 🎉 Features
|
package/README.md
CHANGED
|
@@ -5,43 +5,50 @@
|
|
|
5
5
|
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.
|
|
6
6
|
|
|
7
7
|
<!-- section:release-notes -->
|
|
8
|
-
## Release notes — v0.
|
|
8
|
+
## Release notes — v0.39.0 (2026-09-22)
|
|
9
9
|
|
|
10
10
|
### 🎉 Features
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- Report why a workspace selects no package, and no-op nmr's own -R steps (#841)
|
|
13
13
|
|
|
14
|
-
-
|
|
15
|
-
-
|
|
14
|
+
- Drops a `-R` step that nmr composed into a script where the workspace holds no package, so `typecheck` and the five `test` scripts run there instead of failing the run.
|
|
15
|
+
- Adds a no-op verdict for a command that the drop leaves with no step at all.
|
|
16
|
+
- Replaces the single refusal for a workspace that selects no package with one message per cause.
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
Migration: In a repo that declares a workspace package pattern and deliberately holds no package, delete the `.config/nmr.config.ts` entries added to work around the `-R` refusal, the `build: ':'` override included.
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
- Adds a `warn` check that passes once a test file under `__tests__` imports `checkTestFileConventions` from `@williamthorsen/nmr/tests`, and whose fix shows the one-line guard test to add.
|
|
21
|
-
- Makes the kit skip both test-file checks once it finds that guard, because the guard reports the same files and applies the repo's `exclude` list, which the kit cannot read.
|
|
20
|
+
- 🚨 **Breaking:** Prefix the booleans and reject unrecognized Vitest option keys (#849)
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
- Renames two boolean options of `defineVitestConfig` and `defineRootVitestConfig`, the factories that build a repo's Vitest config: `isolateGit` becomes `shouldIsolateGit`, and `resolveFromSource` becomes `shouldResolveFromSource`.
|
|
23
|
+
- Makes both factories reject an option key outside the recognized set, naming the offending keys and the recognized ones, rather than ignoring a misspelled or retired key and letting its option fall back to the default with nothing in the run reporting it.
|
|
24
|
+
- Adds a retired-key check that runs ahead of the unrecognized-key check, reporting the new name for a config that still sets `isolateGit` or `resolveFromSource`.
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
Migration: In each options object passed to `defineVitestConfig` or `defineRootVitestConfig`, rename `isolateGit` to `shouldIsolateGit` and `resolveFromSource` to `shouldResolveFromSource`, and correct any other key that the factory now rejects. `defineRootVitestConfig` recognizes `monorepoRoot` on the last layer alone, so a config that sets it on an earlier layer now throws where the old code ignored it.
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
- Covers three further selections that ran nothing and exited 0: an exclusion that leaves only the workspace root, such as `nmr -F '!./packages/*'`; `-R` in a workspace that declares no package; and an empty `-F ''`.
|
|
28
|
+
- 🚨 **Breaking:** End nmr's source value names with the kind of the value (#850)
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
- Renames two fields of `TestFileConventionsReport`, `misplaced` and `untiered`, to `misplacedFiles` and `untieredFiles`, and renames `TestFileConventionsOptions`'s `exclude` to `excludedBasenames`, in the `@williamthorsen/nmr/tests` entry point.
|
|
31
|
+
- Adds a guard to `checkTestFileConventions` and `reportTestFileConventions` that throws a `TypeError` naming `excludedBasenames` when a call still sets `exclude`.
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
Migration: Read `report.misplacedFiles` and `report.untieredFiles` in place of `report.misplaced` and `report.untiered`, and pass `excludedBasenames` in place of `exclude`. A call that still sets `exclude` throws rather than running with no exclusions, so the failure surfaces where Vitest loads the call site.
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
- Limits the source resolution enabled by `resolveFromSource` to packages whose real path lies outside every `node_modules`, which covers workspace packages and packages linked into the repo from elsewhere.
|
|
36
|
-
- Makes an import of a workspace package fail when that package's `source` condition names a missing file, instead of resolving to the build output without warning.
|
|
37
|
-
- Makes conditions that a config adds to `resolve.conditions` or `ssr.resolve.conditions` extend Vite's defaults when `resolveFromSource` is `false`, instead of replacing them.
|
|
35
|
+
- 🚨 **Breaking:** Hoist the pnpm-workspace resolver into nmr-core as a total API (#864)
|
|
38
36
|
|
|
39
|
-
|
|
37
|
+
- Adds a `./workspace` subpath export to `@williamthorsen/nmr-core`, whose `resolveWorkspace` returns a pnpm workspace's package directories, or the cause of an empty result, as a value rather than as a thrown error.
|
|
38
|
+
- Reports a `pnpm-workspace.yaml` that the YAML parser rejects under a cause of its own, `unreadable-manifest`, so `nmr -R build` names the syntax error and its remedy in place of failing with the parser's own error.
|
|
39
|
+
- Removes `diagnoseEmptyWorkspace` and `EmptyWorkspaceDiagnosis` from `@williamthorsen/nmr/workspace`, since `resolveWorkspace` reports the same cause in one call.
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
Migration: Replace each `diagnoseEmptyWorkspace` call with `resolveWorkspace`, which reports the cause on the empty result it already returns, and rename the `no-manifest` cause to `no-package`. `EmptyWorkspaceCause` also gains `unreadable-manifest`, so an exhaustive switch over it stops compiling until it handles the new member; every other export of `@williamthorsen/nmr/workspace` keeps its signature and its `UserError` throws.
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
- 🚨 **Breaking:** Adopt the shared workspace resolver in release-kit, honoring exclusions (#865)
|
|
44
|
+
|
|
45
|
+
- 🚨 **Breaking:** Narrows `no-pattern` in `@williamthorsen/nmr-core`'s `EmptyWorkspaceCause`, moving an absent or empty `packages` list onto `no-packages-list` and a value that is not a list of strings onto `unreadable-packages`.
|
|
46
|
+
- Stops `release-kit` from tagging, publishing, and label-syncing a package that a `!` entry in `pnpm-workspace.yaml` excludes, which its previous `glob`-based resolution could not remove from the match.
|
|
47
|
+
- Replaces the release of the root package alone with a failure naming the condition that emptied the workspace, when `pnpm-workspace.yaml` does not hold valid YAML or declares a workspace that does not resolve to a package.
|
|
48
|
+
- Replaces the `no-pattern` remedy that `nmr` printed for a `pnpm-workspace.yaml` declaring no `packages` list, and for one declaring a `packages` value that is not a list of strings.
|
|
49
|
+
- Raises `engines.node` to `>=24.16.0` in `@williamthorsen/nmr-core` and `@williamthorsen/release-kit`, because Node 24.0 through 24.15 ignores the `followSymlinks` option that the workspace matcher passes to `globSync` and drops a symlinked package directory from the resolution without reporting it.
|
|
50
|
+
|
|
51
|
+
Migration: Add a `no-packages-list` arm and an `unreadable-packages` arm to any `switch` that covers `EmptyWorkspaceCause` exhaustively. `no-pattern` now names only a list that the resolver read in full and whose entries all failed to become a positive pattern, so move the handling of an absent `packages` key, an empty list, and a value that is not a list of strings out of the `no-pattern` arm into the two new ones. Route `no-packages-list` to single-package behavior rather than to a failure path: pnpm resolves such a manifest to the root package alone.
|
|
45
52
|
<!-- /section:release-notes -->
|
|
46
53
|
|
|
47
54
|
## Installation
|
|
@@ -176,13 +183,15 @@ Every command nmr runs reports one line naming the scope it ran at, the command,
|
|
|
176
183
|
```console
|
|
177
184
|
✅ nmr-core: test: passed in 12.4s
|
|
178
185
|
❌ nmr: build: failed in 1.2s (exit 1)
|
|
179
|
-
|
|
180
|
-
|
|
186
|
+
⏩ nmr-core: test: passed 4m ago on this tree, saved ~12s
|
|
187
|
+
⚪ nmr-core: lint: skipped, the override is empty
|
|
181
188
|
```
|
|
182
189
|
|
|
183
|
-
**
|
|
190
|
+
**A pipe and a CI log get words rather than emoji.** The same four lines read `PASS`, `FAIL`, `SKIP`, and `NOOP` there, which `grep FAIL` finds. `--output-style` and `NMR_OUTPUT_STYLE` each force either style.
|
|
191
|
+
|
|
192
|
+
**Verdicts print in every verbosity.** `-q` withholds the output of the commands nmr runs, never nmr's own words: a passing quiet run reports its verdicts and nothing else. A command that ran nothing reports the skip rather than exiting 0 in silence, which is what separates it from a command that passed: an override resolving to `""` or `":"`, and a fan-out step that a package-free workspace left nothing to do. An `NMR_RUN_IF_PRESENT` miss reports nothing, having no command to report on.
|
|
184
193
|
|
|
185
|
-
[What nmr reports](docs/reporting.md) covers how verdicts nest, the `--json` format, and where the verbosity of a run comes from.
|
|
194
|
+
[What nmr reports](docs/reporting.md) covers how verdicts nest, the `--json` format, the output style, and where the verbosity of a run comes from.
|
|
186
195
|
|
|
187
196
|
## Check-result cache
|
|
188
197
|
|
|
@@ -193,7 +202,7 @@ $ nmr ci
|
|
|
193
202
|
# ... four minutes of build, typecheck, lint, and tests ...
|
|
194
203
|
|
|
195
204
|
$ nmr ci
|
|
196
|
-
|
|
205
|
+
⏩ node-monorepo-tools: ci: passed 2m ago on this tree, saved ~4m
|
|
197
206
|
```
|
|
198
207
|
|
|
199
208
|
[Check-result cache](docs/check-cache.md) covers what is cached, what a hit requires, replaying and reading a recorded run, and bypassing or configuring the cache.
|
|
@@ -297,7 +306,7 @@ rdy list --from npm:@williamthorsen/nmr # what nmr publishes
|
|
|
297
306
|
## Documentation
|
|
298
307
|
|
|
299
308
|
- [Scripts and configuration](docs/scripts.md): override tiers, script values, hooks, `defineConfig` fields, `devBin`, and the default commands
|
|
300
|
-
- [What nmr reports](docs/reporting.md): verdicts, `--json`, and verbosity
|
|
309
|
+
- [What nmr reports](docs/reporting.md): verdicts, `--json`, output style, and verbosity
|
|
301
310
|
- [Check-result cache](docs/check-cache.md): what is skipped, what a hit requires, and replaying, reading, and bypassing recorded runs
|
|
302
311
|
- [Dependency upgrades](docs/upgrades.md): `nmr upgrade`, the upgrade policy, `report-catalog`, `report-overrides`, and `nmr-taze`
|
|
303
312
|
- [Standalone utilities](docs/utilities.md): `nmr-clean`, `nmr-compile`, `nmr-fmt`, and `ensure-prepublish-hooks`
|
|
@@ -10,8 +10,8 @@ delivery: ambient
|
|
|
10
10
|
- Scope follows cwd, and bare `nmr` lists both registries: from the repo root a command covers root files and every workspace; from inside a package, that package alone. `nmr -F <pkg> <command>` targets one package from anywhere; `nmr root:<command>` targets root files alone, which isolates a failure to root code.
|
|
11
11
|
- Never `npx nmr`: inside a git worktree it can resolve a different nmr from outside the tree. Fall back to `pnpm exec nmr`.
|
|
12
12
|
- `nmr ci` runs what a code-quality workflow runs (build, then strict checks) and not the dependency audit, which belongs to a workflow of its own; `nmr prepush` runs the audit first, then `ci`, so a vulnerability stops the run before the long gate. Neither installs or binds a git hook.
|
|
13
|
-
- A check that already passed on the current working tree skips, printing
|
|
13
|
+
- A check that already passed on the current working tree skips, printing `SKIP <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
|
-
- Every command nmr runs reports one line --
|
|
15
|
+
- Every command nmr runs reports one line -- `PASS <scope>: <command>: passed in 12.4s`, `FAIL … failed in 1.2s (exit 1)`, `SKIP … passed 4m ago on this tree`, or `NOOP … skipped, the override is empty` -- and a composite reports alongside every command it expands into. Those are the words a pipe and a CI log get; a terminal gets ✅, ❌, ⏩, and ⚪ in their place, and `--output-style rich` or `NMR_OUTPUT_STYLE=rich` forces the emoji on any stream. 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. A file outside `__tests__`, or one whose tier segment is missing or misspelt, fails the test run in a repo that declares nmr's exported conventions check, which reports both. In a repo that has not, neither fails anything -- the first is collected by nothing and the second runs under `unit` -- and `rdy run --packages` reports both and warns that the check is missing.
|
|
17
17
|
- The rest is in `node_modules/@williamthorsen/nmr/docs/`: `scripts.md` for pre/post hooks (every `nmr X` auto-wraps as `nmr X:pre && nmr X && nmr X:post`) and script overrides and their `""`/`":"` skip values; `upgrades.md` for `nmr upgrade` ceilings (report-only until `nmr upgrade --write`); `utilities.md` for the `nmr-compile` build (there is no repo-local build script); `vitest.md` for the test tiers' timeout budgets, the `tiers` seam that raises one, and which script reaches which tier; and `check-cache.md` for 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,4 +1,5 @@
|
|
|
1
1
|
import type { Writable } from 'node:stream';
|
|
2
|
+
import { type OutputStyle } from '@williamthorsen/nmr-core';
|
|
2
3
|
import type { OutputChannels } from './runner.js';
|
|
3
4
|
import type { CheckCacheConfig, NmrConfig } from './types.js';
|
|
4
5
|
/** A recorded pass: what ran, on which tree, and what it cost. */
|
|
@@ -52,7 +53,7 @@ export interface Retention {
|
|
|
52
53
|
/** What nmr's own build has left on disk across the workspace. */
|
|
53
54
|
export interface BuildOutputState {
|
|
54
55
|
/** Covered packages whose output is absent. */
|
|
55
|
-
|
|
56
|
+
missingPackages: string[];
|
|
56
57
|
/** The digest of the inputs each covered package's output was built from, keyed by package name. */
|
|
57
58
|
digests: Record<string, string>;
|
|
58
59
|
}
|
|
@@ -153,13 +154,13 @@ export declare function encodeTreeSnapshot(snapshot: TreeSnapshot): string;
|
|
|
153
154
|
* package agrees. A package that has appeared or disappeared between them counts as a disagreement, because
|
|
154
155
|
* the output the earlier observation describes is not the output the later one found.
|
|
155
156
|
*/
|
|
156
|
-
export declare function findStaleBuildOutput(
|
|
157
|
+
export declare function findStaleBuildOutput(earlierDigests: Record<string, string>, laterDigests: Record<string, string>): string | undefined;
|
|
157
158
|
/**
|
|
158
159
|
* Renders the warning for a `--no-cache` that landed after the command name, where it is an argument to the
|
|
159
160
|
* command rather than a flag to nmr. Passing it on unchanged is the honest thing to do with an argument, so
|
|
160
161
|
* the warning is all that separates this from a silently un-bypassed run.
|
|
161
162
|
*/
|
|
162
|
-
export declare function formatMisplacedNoCacheWarning(command: string): string;
|
|
163
|
+
export declare function formatMisplacedNoCacheWarning(command: string, style: OutputStyle): string;
|
|
163
164
|
/**
|
|
164
165
|
* Reports whether a command's passes are recorded at all, which is what separates a command with no recording
|
|
165
166
|
* from one that could never have had one.
|
package/dist/esm/check-cache.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { readFileSync, realpathSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import process from 'node:process';
|
|
5
|
-
import { hashWorkingTree, readCacheEntry, readHeadSha, readJsonCacheEntry, removeCacheDir, removeCacheEntry, resolveCacheEntryPath, writeCacheEntry, } from '@williamthorsen/nmr-core';
|
|
5
|
+
import { hashWorkingTree, readCacheEntry, readHeadSha, readJsonCacheEntry, removeCacheDir, removeCacheEntry, resolveCacheEntryPath, STATUS_GLYPHS, writeCacheEntry, } from '@williamthorsen/nmr-core';
|
|
6
6
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
7
7
|
import { hasBuildOutput, readBuildDigest } from "./commands/build-output.js";
|
|
8
8
|
import { loadWorkspaceConfig } from "./config.js";
|
|
@@ -103,19 +103,19 @@ export function computeRetentionKey(options) {
|
|
|
103
103
|
export function encodeTreeSnapshot(snapshot) {
|
|
104
104
|
return `${snapshot.hash} ${snapshot.headSha}`;
|
|
105
105
|
}
|
|
106
|
-
export function findStaleBuildOutput(
|
|
107
|
-
const names = [...new Set([...Object.keys(
|
|
108
|
-
return names.find((name) =>
|
|
106
|
+
export function findStaleBuildOutput(earlierDigests, laterDigests) {
|
|
107
|
+
const names = [...new Set([...Object.keys(earlierDigests), ...Object.keys(laterDigests)])].toSorted();
|
|
108
|
+
return names.find((name) => earlierDigests[name] !== laterDigests[name]);
|
|
109
109
|
}
|
|
110
|
-
export function formatMisplacedNoCacheWarning(command) {
|
|
111
|
-
return (
|
|
112
|
-
`Did you mean \`nmr --no-cache ${command}\`?`);
|
|
110
|
+
export function formatMisplacedNoCacheWarning(command, style) {
|
|
111
|
+
return (`${STATUS_GLYPHS[style].warning.text} --no-cache after the command name is passed to \`${command}\`, ` +
|
|
112
|
+
`not read by nmr. Did you mean \`nmr --no-cache ${command}\`?`);
|
|
113
113
|
}
|
|
114
114
|
export function isCacheableCommand(checkCache, command) {
|
|
115
115
|
return !isHookName(command) && checkCache?.enabled !== false && resolveCacheableCommands(checkCache).has(command);
|
|
116
116
|
}
|
|
117
117
|
export async function readBuildOutputState(monorepoRoot, config) {
|
|
118
|
-
const state = {
|
|
118
|
+
const state = { missingPackages: [], digests: {} };
|
|
119
119
|
let packageDirs;
|
|
120
120
|
try {
|
|
121
121
|
packageDirs = getWorkspacePackageDirs(monorepoRoot);
|
|
@@ -138,17 +138,17 @@ export async function readBuildOutputState(monorepoRoot, config) {
|
|
|
138
138
|
state.digests[name] = (await readBuildDigest(packageDir)) ?? '';
|
|
139
139
|
}
|
|
140
140
|
else {
|
|
141
|
-
state.
|
|
141
|
+
state.missingPackages.push(name);
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
return state;
|
|
145
145
|
}
|
|
146
146
|
export async function readCheckCacheEntry(options) {
|
|
147
|
-
const
|
|
148
|
-
if (
|
|
147
|
+
const parsedEntry = await readJsonCacheEntry(resolveEntryPath(options), isParsedCheckCacheEntry);
|
|
148
|
+
if (parsedEntry === undefined) {
|
|
149
149
|
return undefined;
|
|
150
150
|
}
|
|
151
|
-
const { retention, ...pass } =
|
|
151
|
+
const { retention, ...pass } = parsedEntry;
|
|
152
152
|
return isRetention(retention) ? { ...pass, retention } : pass;
|
|
153
153
|
}
|
|
154
154
|
export async function readTranscript(ref) {
|
|
@@ -168,23 +168,23 @@ export async function removeCheckCache(scopeDir) {
|
|
|
168
168
|
export function resolveCacheableCommands(checkCache) {
|
|
169
169
|
const commands = new Set([...DEFAULT_CACHEABLE_COMMANDS, ...(checkCache?.extraCommands ?? [])]);
|
|
170
170
|
const excludedCommands = checkCache?.excludeCommands ?? [];
|
|
171
|
-
for (const
|
|
172
|
-
commands.delete(
|
|
171
|
+
for (const excludedCommand of excludedCommands) {
|
|
172
|
+
commands.delete(excludedCommand);
|
|
173
173
|
}
|
|
174
174
|
return commands;
|
|
175
175
|
}
|
|
176
176
|
export function resolveRunId(env) {
|
|
177
|
-
const
|
|
178
|
-
return
|
|
177
|
+
const inheritedRunId = env[RUN_ID_ENV_VAR];
|
|
178
|
+
return inheritedRunId === undefined || inheritedRunId === '' ? randomUUID() : inheritedRunId;
|
|
179
179
|
}
|
|
180
180
|
export function resolveTreeSnapshot(options) {
|
|
181
|
-
const
|
|
182
|
-
if (
|
|
183
|
-
return { ok: true, snapshot:
|
|
181
|
+
const inheritedSnapshot = decodeTreeSnapshot(options.env[TREE_SNAPSHOT_ENV_VAR]);
|
|
182
|
+
if (inheritedSnapshot !== undefined && readHeadSha(options.monorepoRoot) === inheritedSnapshot.headSha) {
|
|
183
|
+
return { ok: true, snapshot: inheritedSnapshot };
|
|
184
184
|
}
|
|
185
|
-
const
|
|
186
|
-
if (!
|
|
187
|
-
return
|
|
185
|
+
const hashedTree = hashWorkingTree(options.monorepoRoot);
|
|
186
|
+
if (!hashedTree.ok) {
|
|
187
|
+
return hashedTree;
|
|
188
188
|
}
|
|
189
189
|
let physicalRoot;
|
|
190
190
|
try {
|
|
@@ -193,10 +193,10 @@ export function resolveTreeSnapshot(options) {
|
|
|
193
193
|
catch {
|
|
194
194
|
return { ok: false, reason: `could not resolve ${options.monorepoRoot}` };
|
|
195
195
|
}
|
|
196
|
-
if (
|
|
197
|
-
return { ok: false, reason: `the monorepo root is not the git toplevel (${
|
|
196
|
+
if (hashedTree.toplevel !== physicalRoot) {
|
|
197
|
+
return { ok: false, reason: `the monorepo root is not the git toplevel (${hashedTree.toplevel})` };
|
|
198
198
|
}
|
|
199
|
-
return { ok: true, snapshot: { hash:
|
|
199
|
+
return { ok: true, snapshot: { hash: hashedTree.hash, headSha: hashedTree.headSha } };
|
|
200
200
|
}
|
|
201
201
|
export async function writeCheckCacheEntry(options) {
|
|
202
202
|
await writeCacheEntry(resolveEntryPath(options), JSON.stringify(options.entry, undefined, 2));
|
|
@@ -212,11 +212,11 @@ function composeEnvParts(names, env) {
|
|
|
212
212
|
return [name, value === undefined ? 'unset' : 'set', value ?? ''];
|
|
213
213
|
});
|
|
214
214
|
}
|
|
215
|
-
function decodeTreeSnapshot(
|
|
216
|
-
if (
|
|
215
|
+
function decodeTreeSnapshot(encodedSnapshot) {
|
|
216
|
+
if (encodedSnapshot === undefined) {
|
|
217
217
|
return undefined;
|
|
218
218
|
}
|
|
219
|
-
const [hash, headSha] =
|
|
219
|
+
const [hash, headSha] = encodedSnapshot.split(' ', 2);
|
|
220
220
|
if (hash === undefined || headSha === undefined || hash === '' || headSha === '') {
|
|
221
221
|
return undefined;
|
|
222
222
|
}
|
package/dist/esm/cli-build.js
CHANGED
|
@@ -2,10 +2,13 @@ import { reportError } from '@williamthorsen/nmr-core';
|
|
|
2
2
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
3
|
import { buildPackage } from "./commands/build.js";
|
|
4
4
|
import { loadWorkspaceConfig } from "./config.js";
|
|
5
|
+
import { resolveBinStyles } from "./resolveBinStyles.js";
|
|
5
6
|
try {
|
|
7
|
+
const { stdout } = resolveBinStyles();
|
|
6
8
|
const packageDir = process.cwd();
|
|
7
9
|
const { build } = await loadWorkspaceConfig(packageDir);
|
|
8
10
|
await buildPackage(packageDir, {
|
|
11
|
+
style: stdout,
|
|
9
12
|
...(build?.extraIgnorePatterns !== undefined && { extraIgnorePatterns: build.extraIgnorePatterns }),
|
|
10
13
|
});
|
|
11
14
|
}
|
package/dist/esm/cli-clean.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
1
2
|
import { reportError } from '@williamthorsen/nmr-core';
|
|
2
3
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
4
|
import { runClean } from "./commands/clean.js";
|
|
5
|
+
import { resolveBinStyles } from "./resolveBinStyles.js";
|
|
4
6
|
try {
|
|
5
|
-
await runClean(process.cwd());
|
|
7
|
+
await runClean(process.cwd(), resolveBinStyles().stdout);
|
|
6
8
|
}
|
|
7
9
|
catch (error) {
|
|
8
10
|
reportError(describeError(error));
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
3
|
import { DEFAULT_HOOK, ensurePrepublishHooks, reportPrepublishHooks } from "./commands/ensure-prepublish-hooks.js";
|
|
4
|
+
import { resolveBinStyles } from "./resolveBinStyles.js";
|
|
4
5
|
import { findMonorepoRoot } from "./workspace.js";
|
|
5
6
|
const flagSchema = {
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
shouldFix: { long: '--fix', type: 'boolean' },
|
|
8
|
+
isDryRun: { long: '--dry-run', type: 'boolean' },
|
|
8
9
|
command: { long: '--command', type: 'string' },
|
|
9
10
|
};
|
|
10
|
-
const {
|
|
11
|
+
const { shouldFix, isDryRun, command } = parseArgsOrExit(process.argv.slice(2), flagSchema).flags;
|
|
11
12
|
try {
|
|
13
|
+
const { stdout } = resolveBinStyles();
|
|
12
14
|
const monorepoRoot = findMonorepoRoot();
|
|
13
|
-
const options = command ? {
|
|
15
|
+
const options = command ? { shouldFix, isDryRun, command } : { shouldFix, isDryRun };
|
|
14
16
|
const result = ensurePrepublishHooks(monorepoRoot, options);
|
|
15
|
-
reportPrepublishHooks(result, command ?? DEFAULT_HOOK);
|
|
17
|
+
reportPrepublishHooks(result, command ?? DEFAULT_HOOK, stdout);
|
|
16
18
|
if (result.hasFailures) {
|
|
17
19
|
process.exitCode = 1;
|
|
18
20
|
}
|
|
@@ -2,8 +2,9 @@ import process from 'node:process';
|
|
|
2
2
|
import { reportError } from '@williamthorsen/nmr-core';
|
|
3
3
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
4
4
|
import { reportCatalog } from "./commands/report-catalog.js";
|
|
5
|
+
import { resolveBinStyles } from "./resolveBinStyles.js";
|
|
5
6
|
try {
|
|
6
|
-
reportCatalog(process.cwd());
|
|
7
|
+
reportCatalog(process.cwd(), resolveBinStyles().stderr);
|
|
7
8
|
}
|
|
8
9
|
catch (error) {
|
|
9
10
|
reportError(describeError(error));
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { reportError } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
3
|
import { reportOverrides } from "./commands/report-overrides.js";
|
|
4
|
+
import { resolveBinStyles } from "./resolveBinStyles.js";
|
|
4
5
|
import { findMonorepoRoot } from "./workspace.js";
|
|
5
6
|
try {
|
|
6
7
|
const monorepoRoot = findMonorepoRoot();
|
|
7
|
-
reportOverrides(monorepoRoot);
|
|
8
|
+
reportOverrides(monorepoRoot, resolveBinStyles().stderr);
|
|
8
9
|
}
|
|
9
10
|
catch (error) {
|
|
10
11
|
reportError(describeError(error));
|
|
@@ -8,8 +8,8 @@ export interface BuildOptions {
|
|
|
8
8
|
}
|
|
9
9
|
/** The pair of directories a build publishes through, both siblings of the emit directory. */
|
|
10
10
|
export interface ScratchDirs {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
previousDir: string;
|
|
12
|
+
stagingDir: string;
|
|
13
13
|
}
|
|
14
14
|
export declare const DEFAULT_ENTRY_GLOBS: string[];
|
|
15
15
|
/**
|
|
@@ -17,8 +17,8 @@ export async function hasBuildOutput(packageDir, options = {}) {
|
|
|
17
17
|
return hasExpectedBuildOutput(packageDir, outdir, entryPoints);
|
|
18
18
|
}
|
|
19
19
|
export function hasExpectedBuildOutput(packageDir, outdir, entryPoints) {
|
|
20
|
-
const
|
|
21
|
-
if (!
|
|
20
|
+
const doesEmitOutput = entryPoints.some((entry) => !entry.endsWith('.d.ts'));
|
|
21
|
+
if (!doesEmitOutput) {
|
|
22
22
|
return true;
|
|
23
23
|
}
|
|
24
24
|
const outputDir = path.resolve(packageDir, outdir);
|
|
@@ -40,8 +40,8 @@ export function resolveScratchDirs(emitDir) {
|
|
|
40
40
|
const parent = path.dirname(emitDir);
|
|
41
41
|
const name = path.basename(emitDir);
|
|
42
42
|
return {
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
previousDir: path.join(parent, `.${name}.previous`),
|
|
44
|
+
stagingDir: path.join(parent, `.${name}.staging`),
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
export async function resolveToolchainFingerprint(packageDir, selfDir = SELF_DIR) {
|
|
@@ -54,14 +54,14 @@ export async function resolveToolchainFingerprint(packageDir, selfDir = SELF_DIR
|
|
|
54
54
|
return readPackageJson(selfDir).version ?? '';
|
|
55
55
|
}
|
|
56
56
|
function isSameDir(left, right) {
|
|
57
|
-
return
|
|
57
|
+
return resolveRealPath(left) === resolveRealPath(right);
|
|
58
58
|
}
|
|
59
|
-
function
|
|
60
|
-
const
|
|
59
|
+
function resolveRealPath(dir) {
|
|
60
|
+
const absoluteDir = path.resolve(dir);
|
|
61
61
|
try {
|
|
62
|
-
return realpathSync(
|
|
62
|
+
return realpathSync(absoluteDir);
|
|
63
63
|
}
|
|
64
64
|
catch {
|
|
65
|
-
return
|
|
65
|
+
return absoluteDir;
|
|
66
66
|
}
|
|
67
67
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type OutputStyle } from '@williamthorsen/nmr-core';
|
|
1
2
|
import { type BuildOptions } from './build-output.js';
|
|
2
3
|
/**
|
|
3
4
|
* The toolchain a build's emit comes from: the resolved compiler and the nmr running it. Both join the build
|
|
@@ -7,6 +8,10 @@ export interface BuildToolchain {
|
|
|
7
8
|
compilerVersion: string;
|
|
8
9
|
fingerprint: string;
|
|
9
10
|
}
|
|
11
|
+
/** What a build needs beyond its sources: the options shaping the emit, and the style it reports in. */
|
|
12
|
+
export interface BuildPackageOptions extends BuildOptions {
|
|
13
|
+
style: OutputStyle;
|
|
14
|
+
}
|
|
10
15
|
/**
|
|
11
16
|
* Compiles a package's `src` tree to `dist/esm` with the TypeScript compiler API, emitting `.js` and
|
|
12
17
|
* `.d.ts` from two programs and rewriting relative `.ts` specifiers and tsconfig `paths` aliases to
|
|
@@ -17,7 +22,7 @@ export interface BuildToolchain {
|
|
|
17
22
|
* current inputs rather than an accumulation of every build that ever ran. Assets belong outside it, or in a
|
|
18
23
|
* `build:post` hook, which runs after the output is published.
|
|
19
24
|
*/
|
|
20
|
-
export declare function buildPackage(packageDir: string, options
|
|
25
|
+
export declare function buildPackage(packageDir: string, options: BuildPackageOptions): Promise<void>;
|
|
21
26
|
/**
|
|
22
27
|
* Produces a digest of the given files (paths and contents), the emit config, and the toolchain.
|
|
23
28
|
* The file list is sorted so the digest is order-invariant, and each path is folded in so renames are detected.
|