@williamthorsen/nmr 0.35.0 → 0.36.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 +1416 -0
- package/.readyup/kits/default.js +86 -4
- package/.readyup/manifest.json +5 -5
- package/CHANGELOG.md +894 -0
- package/README.md +22 -15
- package/dist/esm/commands/build.js +1 -1
- package/dist/esm/config.d.ts +0 -8
- package/dist/esm/config.js +66 -9
- package/dist/esm/helpers/config-path.d.ts +8 -0
- package/dist/esm/helpers/config-path.js +7 -0
- package/dist/esm/helpers/findClosestName.d.ts +7 -0
- package/dist/esm/helpers/findClosestName.js +38 -0
- package/dist/esm/helpers/package-json.js +1 -1
- package/dist/esm/runCli.js +1 -1
- package/dist/esm/types.d.ts +3 -2
- package/dist/esm/vitest-git-isolation.js +6 -0
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -3,33 +3,36 @@
|
|
|
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.36.0 (2026-09-08)
|
|
7
7
|
|
|
8
8
|
### 🎉 Features
|
|
9
9
|
|
|
10
|
-
-
|
|
10
|
+
- Report a package whose tarball would omit its changelog (#768)
|
|
11
11
|
|
|
12
|
-
Adds
|
|
12
|
+
Adds ReadyUp checks to verify that every published package bundles its changelog files: the human-readable `CHANGELOG.md` and the machine-readable `changelog.json`.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
- Reject a checkCache command name that matches no command (#778)
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Modifies `nmr` so that it rejects a `checkCache.extraCommands` or `checkCache.excludeCommands` name that matches no command, so a misspelt entry fails config load instead of silently having no effect.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Modifies `nmr` so that it resolves names listed in either field against nmr's default scripts, the repo config's script registries, and each package's `package.json` scripts, so a name that only a package declares is accepted.
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Migration: Correct or remove any `checkCache.extraCommands` or `checkCache.excludeCommands` entry that names no command, and any entry that names a hook, which is rejected too. The error reports the field, the offending name, and the closest command where there is one.
|
|
21
21
|
|
|
22
|
-
-
|
|
22
|
+
- Report a bin target that is not a committed wrapper (#780)
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
- Adds `every bin target is a committed wrapper` to nmr's readyup kit, which reports a workspace `bin` target naming a path under `dist/`, or one that git does not track, because pnpm links a package's bins before anything is built and never retries a failed link.
|
|
25
|
+
- Adds `every bin wrapper's build-output target is covered by files`, which reports a wrapper loading build output that `files` omits, since that publishes a bin resolving to nothing.
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
Migration: Replace a `bin` target naming a path under `dist/` with a committed wrapper under `bin/` that imports the build entry, commit the wrapper, and extend `files` to cover the output that the wrapper loads.
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
### 🐛 Bug fixes
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
- Keep global git ignore rules and attributes out of test subprocesses (#773)
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
Fixes an issue where a test that spawns `git` read ignore rules and attributes from outside the repository, so a suite could pass or fail on whatever a developer or their machine happened to have configured. The setup file that `defineVitestConfig` loads by default now isolates both, alongside the git configuration already covered.
|
|
34
|
+
|
|
35
|
+
Migration: Where a test needs its own `core.excludesFile` or `core.attributesFile`, pass it on the invocation as `git -c core.excludesFile=<path>`. An environment-injected key now outranks repository-local config.
|
|
33
36
|
<!-- /section:release-notes -->
|
|
34
37
|
|
|
35
38
|
## Installation
|
|
@@ -519,6 +522,10 @@ export default defineConfig({
|
|
|
519
522
|
|
|
520
523
|
`extraCommands` extends the default set rather than replacing it, so naming one command cannot silently drop the rest; `excludeCommands` is applied afterwards, so a name in both is excluded. `enabled: false` turns the gate off entirely. The key is `checkCache`, in the monorepo-root config.
|
|
521
524
|
|
|
525
|
+
**A name in either list must resolve to a command**, whether through nmr's defaults, the repo config's script records, or a package's own `package.json` scripts. Both lists are read by name alone, so a misspelt entry would otherwise be inert: the command it meant to name would go on running, indistinguishable from one that cannot be cached at all. A name that resolves nowhere fails config load, naming the closest command where there is one. A hook name is rejected too, since a hook is never gated on its own -- it runs as part of the chain of the command it wraps, and is skipped or run with it.
|
|
526
|
+
|
|
527
|
+
The test is resolvability rather than membership of the cacheable set, so an `excludeCommands` entry keeps standing after a release moves its name out of the defaults.
|
|
528
|
+
|
|
522
529
|
```ts
|
|
523
530
|
export default defineConfig({
|
|
524
531
|
output: {
|
|
@@ -945,7 +952,7 @@ ensure-prepublish-hooks
|
|
|
945
952
|
|
|
946
953
|
## Conformance checks
|
|
947
954
|
|
|
948
|
-
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. A repo that wants the tier convention in its own gate rather than in a command run by hand declares the [conventions check](#gating-the-test-file-conventions) instead, which also covers the half the kit does not.
|
|
955
|
+
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), that every `bin` target is a committed wrapper rather than build output that pnpm cannot link at install time, that `files` covers the output each wrapper loads, 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. A repo that wants the tier convention in its own gate rather than in a command run by hand declares the [conventions check](#gating-the-test-file-conventions) instead, which also covers the half the kit does not.
|
|
949
956
|
|
|
950
957
|
Add `readyup` as a devDependency, then name nmr in its config:
|
|
951
958
|
|
|
@@ -1141,7 +1148,7 @@ Expect the first run to fail in a repo that has never gated this. Vitest 4 exclu
|
|
|
1141
1148
|
Two settings a workspace test runner needs almost universally are part of what the factory produces, so a config file declares neither:
|
|
1142
1149
|
|
|
1143
1150
|
- **Workspace packages resolve from source.** `source` leads the emitted `resolve.conditions` and `ssr.resolve.conditions`, so a cross-package import reaches the dependency's `.ts` rather than its `dist/` and a suite runs without a prior build. A package declaring no such condition is unaffected.
|
|
1144
|
-
- **Git subprocesses are isolated.** Every project loads a setup file that points `GIT_CONFIG_GLOBAL` and `GIT_CONFIG_SYSTEM` at the null device
|
|
1151
|
+
- **Git subprocesses are isolated.** Every project loads a setup file that points `GIT_CONFIG_GLOBAL` and `GIT_CONFIG_SYSTEM` at the null device, sets `GIT_CONFIG_NOSYSTEM` and `GIT_ATTR_NOSYSTEM`, and injects `core.excludesFile` and `core.attributesFile` through `GIT_CONFIG_COUNT` and its key/value pairs, so a test that spawns `git` reads neither the developer's identity, nor a signing config that can block on a passphrase, nor their global ignore rules and attributes. Those last two need a step of their own: neither path is config-derived, so git resolves each from `$XDG_CONFIG_HOME/git/`, or from `~/.config/git/` where that variable is unset, whatever the config variables say. `GIT_CONFIG_GLOBAL` already replaces `$XDG_CONFIG_HOME/git/config`, so those two are the rest of what that directory supplies; the system attributes file at `$(prefix)/etc/gitattributes` is reached by no config variable at all, which is what `GIT_ATTR_NOSYSTEM` covers. Redirecting `XDG_CONFIG_HOME` would reach the per-user pair as well, and is not used, because other tools spawned by a test read that variable for configuration of their own: pnpm fails to start under `XDG_CONFIG_HOME=/dev/null`. An injected key outranks repository-local config, so a repository setting its own `core.excludesFile` is overridden under the isolation; a test needing its own value passes it on the invocation as `git -c core.excludesFile=...`, which outranks the injection. The setup file leads each project's `setupFiles`, ahead of every entry the layers add, because a shared setup establishes the environment the rest run in.
|
|
1145
1152
|
|
|
1146
1153
|
Both defaults exist because omitting either is silent rather than loud: the tiers still exist and the suite still runs green, testing `dist/` and the developer's git identity instead. Turn either off through its own option, which folds across layers like any other, the last layer to declare it winning:
|
|
1147
1154
|
|
|
@@ -5,7 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import { readCacheEntry, writeCacheEntry } from '@williamthorsen/nmr-core';
|
|
6
6
|
import { glob } from 'glob';
|
|
7
7
|
import * as ts from 'typescript';
|
|
8
|
-
import { resolveConfigPath } from "../config.js";
|
|
8
|
+
import { resolveConfigPath } from "../helpers/config-path.js";
|
|
9
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 = '⏭️';
|
package/dist/esm/config.d.ts
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
import type { NmrConfig } from './types.js';
|
|
2
|
-
/** The config's path relative to the directory holding it, for a message composed without one in hand. */
|
|
3
|
-
export declare const CONFIG_RELATIVE_PATH: string;
|
|
4
|
-
/**
|
|
5
|
-
* Resolves the config-file path for a directory, whether that is the monorepo root or a package.
|
|
6
|
-
* Callers that only need to know whether a config exists -- the build, which folds it into its cache digest -- go
|
|
7
|
-
* through this rather than spelling the path again, so that the two cannot drift into hashing a file nothing reads.
|
|
8
|
-
*/
|
|
9
|
-
export declare function resolveConfigPath(baseDir: string): string;
|
|
10
2
|
/**
|
|
11
3
|
* Loads the nmr configuration from `.config/nmr.config.ts` under `baseDir`, which is the monorepo root for
|
|
12
4
|
* the repo-wide config and a package directory for the workspace tier. Returns an empty config if the file
|
package/dist/esm/config.js
CHANGED
|
@@ -7,15 +7,16 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
9
|
import { existsSync } from 'node:fs';
|
|
10
|
-
import path from 'node:path';
|
|
11
10
|
import { pathToFileURL } from 'node:url';
|
|
11
|
+
import { resolveConfigPath } from "./helpers/config-path.js";
|
|
12
|
+
import { findClosestName } from "./helpers/findClosestName.js";
|
|
13
|
+
import { isHookName } from "./helpers/hook-name.js";
|
|
12
14
|
import { isObject, isStringRecord } from "./helpers/type-guards.js";
|
|
15
|
+
import { buildRootRegistry, buildWorkspaceRegistry, readPackageJsonScripts } from "./resolver.js";
|
|
13
16
|
import { findUnexpressibleToken } from "./steps.js";
|
|
14
17
|
import { UserError } from "./UserError.js";
|
|
15
18
|
import { formatVerbosityRejection, isCommandVerbosity } from "./verbosity.js";
|
|
16
|
-
|
|
17
|
-
const CONFIG_DIR = '.config';
|
|
18
|
-
export const CONFIG_RELATIVE_PATH = path.join(CONFIG_DIR, CONFIG_FILENAME);
|
|
19
|
+
import { getWorkspacePackageDirs, isMonorepoRoot } from "./workspace.js";
|
|
19
20
|
const CONFIG_TIERS = {
|
|
20
21
|
root: {
|
|
21
22
|
label: 'a monorepo-root config',
|
|
@@ -33,9 +34,6 @@ const RECOGNIZED_BUILD_KEYS = ['extraIgnorePatterns'];
|
|
|
33
34
|
const RECOGNIZED_CHECK_CACHE_KEYS = ['enabled', 'excludeCommands', 'extraCommands'];
|
|
34
35
|
const RECOGNIZED_OUTPUT_KEYS = ['commandVerbosity', 'extraAgentEnvVars'];
|
|
35
36
|
const RECOGNIZED_STEP_KEYS = ['declinesArgs', 'run'];
|
|
36
|
-
export function resolveConfigPath(baseDir) {
|
|
37
|
-
return path.join(baseDir, CONFIG_DIR, CONFIG_FILENAME);
|
|
38
|
-
}
|
|
39
37
|
function isScriptRecord(value) {
|
|
40
38
|
if (!isObject(value))
|
|
41
39
|
return false;
|
|
@@ -132,6 +130,64 @@ function validateCheckCacheField(value, configPath) {
|
|
|
132
130
|
}
|
|
133
131
|
return config;
|
|
134
132
|
}
|
|
133
|
+
function assertResolvableCheckCacheCommands(config, configPath, baseDir) {
|
|
134
|
+
const { checkCache } = config;
|
|
135
|
+
if (checkCache === undefined || !isMonorepoRoot(baseDir)) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const fields = ['excludeCommands', 'extraCommands'];
|
|
139
|
+
const entries = fields.flatMap((field) => (checkCache[field] ?? []).map((command) => ({ command, field })));
|
|
140
|
+
const hook = entries.find(({ command }) => isHookName(command));
|
|
141
|
+
if (hook !== undefined) {
|
|
142
|
+
throw new UserError(`Invalid nmr config at ${configPath}: \`checkCache.${hook.field}\` names the hook \`${hook.command}\`, ` +
|
|
143
|
+
'which is never gated on its own: it runs as part of the chain of the command it wraps.');
|
|
144
|
+
}
|
|
145
|
+
const registered = new Set([
|
|
146
|
+
...Object.keys(buildRootRegistry(config)),
|
|
147
|
+
...Object.keys(buildWorkspaceRegistry(config)),
|
|
148
|
+
]);
|
|
149
|
+
const missing = entries.filter(({ command }) => !registered.has(command));
|
|
150
|
+
if (missing.length === 0) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const declared = readDeclaredScriptNames(baseDir);
|
|
154
|
+
const unresolvable = missing.find(({ command }) => !declared.has(command));
|
|
155
|
+
if (unresolvable === undefined) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const closest = findClosestName(unresolvable.command, [...registered, ...declared]);
|
|
159
|
+
throw new UserError(`Invalid nmr config at ${configPath}: \`checkCache.${unresolvable.field}\` names no command: ` +
|
|
160
|
+
`\`${unresolvable.command}\`.${closest === undefined ? '' : ` Did you mean \`${closest}\`?`}`);
|
|
161
|
+
}
|
|
162
|
+
function readDeclaredScriptNames(monorepoRoot) {
|
|
163
|
+
let packageDirs;
|
|
164
|
+
try {
|
|
165
|
+
packageDirs = getWorkspacePackageDirs(monorepoRoot);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
packageDirs = [];
|
|
169
|
+
}
|
|
170
|
+
const names = new Set();
|
|
171
|
+
const dirs = [monorepoRoot, ...packageDirs];
|
|
172
|
+
for (const dir of dirs) {
|
|
173
|
+
const scripts = readPackageScripts(dir);
|
|
174
|
+
for (const name of Object.keys(scripts)) {
|
|
175
|
+
names.add(name);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return names;
|
|
179
|
+
}
|
|
180
|
+
function readPackageScripts(packageDir) {
|
|
181
|
+
try {
|
|
182
|
+
return readPackageJsonScripts(packageDir) ?? {};
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
if (error instanceof UserError) {
|
|
186
|
+
return {};
|
|
187
|
+
}
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
135
191
|
function validateOutputField(value, configPath) {
|
|
136
192
|
const output = value['output'];
|
|
137
193
|
if (output === undefined) {
|
|
@@ -168,7 +224,7 @@ function validateStringRecordField(value, fieldName, configPath) {
|
|
|
168
224
|
}
|
|
169
225
|
return value[fieldName];
|
|
170
226
|
}
|
|
171
|
-
function validateConfig(value, configPath) {
|
|
227
|
+
function validateConfig(value, configPath, baseDir) {
|
|
172
228
|
if (!isObject(value)) {
|
|
173
229
|
throw new UserError(`Invalid nmr config at ${configPath}: expected an object, got ${typeof value}`);
|
|
174
230
|
}
|
|
@@ -192,6 +248,7 @@ function validateConfig(value, configPath) {
|
|
|
192
248
|
const rootScripts = validateScriptField(value, 'rootScripts', configPath);
|
|
193
249
|
if (rootScripts)
|
|
194
250
|
config.rootScripts = rootScripts;
|
|
251
|
+
assertResolvableCheckCacheCommands(config, configPath, baseDir);
|
|
195
252
|
return config;
|
|
196
253
|
}
|
|
197
254
|
export async function loadConfig(baseDir) {
|
|
@@ -201,7 +258,7 @@ export async function loadConfig(baseDir) {
|
|
|
201
258
|
}
|
|
202
259
|
const imported = await import(__rewriteRelativeImportExtension(pathToFileURL(configPath).href));
|
|
203
260
|
const loaded = isObject(imported) ? imported['default'] : undefined;
|
|
204
|
-
return validateConfig(loaded, configPath);
|
|
261
|
+
return validateConfig(loaded, configPath, baseDir);
|
|
205
262
|
}
|
|
206
263
|
export async function loadRootConfig(monorepoRoot) {
|
|
207
264
|
const config = await loadConfig(monorepoRoot);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The config's path relative to the directory holding it, for a message composed without one in hand. */
|
|
2
|
+
export declare const CONFIG_RELATIVE_PATH: string;
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the config-file path for a directory, whether that is the monorepo root or a package.
|
|
5
|
+
* Callers that only need to know whether a config exists -- the build, which folds it into its cache digest -- go
|
|
6
|
+
* through this rather than spelling the path again, so that the two cannot drift into hashing a file nothing reads.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveConfigPath(baseDir: string): string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
const CONFIG_DIR = '.config';
|
|
3
|
+
const CONFIG_FILENAME = 'nmr.config.ts';
|
|
4
|
+
export const CONFIG_RELATIVE_PATH = path.join(CONFIG_DIR, CONFIG_FILENAME);
|
|
5
|
+
export function resolveConfigPath(baseDir) {
|
|
6
|
+
return path.join(baseDir, CONFIG_DIR, CONFIG_FILENAME);
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finds the candidate closest to a name, or `undefined` where none is near enough to be worth naming.
|
|
3
|
+
*
|
|
4
|
+
* The ceiling scales with the name's length, so a short name admits one slip and a long one a few. A fixed
|
|
5
|
+
* ceiling would either miss a typo in `root:test:coverage` or offer `fix` for `ci`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function findClosestName(name: string, candidates: Iterable<string>): string | undefined;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function findClosestName(name, candidates) {
|
|
2
|
+
const ceiling = Math.max(1, Math.floor(name.length / 4));
|
|
3
|
+
let closest;
|
|
4
|
+
let shortest = Infinity;
|
|
5
|
+
for (const candidate of candidates) {
|
|
6
|
+
const distance = measureDistance(name, candidate);
|
|
7
|
+
if (distance < shortest) {
|
|
8
|
+
shortest = distance;
|
|
9
|
+
closest = candidate;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return shortest <= ceiling ? closest : undefined;
|
|
13
|
+
}
|
|
14
|
+
function measureDistance(first, second) {
|
|
15
|
+
if (first.length === 0 || second.length === 0) {
|
|
16
|
+
return Math.max(first.length, second.length);
|
|
17
|
+
}
|
|
18
|
+
let previous = Array.from({ length: second.length + 1 }, (_unused, column) => column);
|
|
19
|
+
let current = Array.from({ length: second.length + 1 }, () => 0);
|
|
20
|
+
for (let row = 1; row <= first.length; row += 1) {
|
|
21
|
+
current[0] = row;
|
|
22
|
+
for (let column = 1; column <= second.length; column += 1) {
|
|
23
|
+
const substitution = readCell(previous, column - 1) + (first[row - 1] === second[column - 1] ? 0 : 1);
|
|
24
|
+
current[column] = Math.min(readCell(current, column - 1) + 1, readCell(previous, column) + 1, substitution);
|
|
25
|
+
}
|
|
26
|
+
const completed = current;
|
|
27
|
+
current = previous;
|
|
28
|
+
previous = completed;
|
|
29
|
+
}
|
|
30
|
+
return readCell(previous, second.length);
|
|
31
|
+
}
|
|
32
|
+
function readCell(row, index) {
|
|
33
|
+
const value = row[index];
|
|
34
|
+
if (value === undefined) {
|
|
35
|
+
throw new RangeError(`No cell at index ${index} in a row of ${row.length}`);
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
4
|
-
import { CONFIG_RELATIVE_PATH } from "../config.js";
|
|
5
4
|
import { UserError } from "../UserError.js";
|
|
6
5
|
import { isMonorepoRoot } from "../workspace.js";
|
|
6
|
+
import { CONFIG_RELATIVE_PATH } from "./config-path.js";
|
|
7
7
|
import { readStringValues } from "./readStringValues.js";
|
|
8
8
|
import { isObject } from "./type-guards.js";
|
|
9
9
|
export const DEPENDENCY_FIELDS = [
|
package/dist/esm/runCli.js
CHANGED
|
@@ -2,9 +2,9 @@ import path from 'node:path';
|
|
|
2
2
|
import { readPackageVersion, reportError } from '@williamthorsen/nmr-core';
|
|
3
3
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
4
4
|
import { certifyRetention, computeCacheKey, computeRetentionKey, CURRENT_RUNTIME, encodeTreeSnapshot, findStaleBuildOutput, formatMisplacedNoCacheWarning, isCacheableCommand, NO_CACHE_ENV_VAR, readBuildOutputState, readCheckCacheEntry, recordTranscript, resolveRunId, resolveTreeSnapshot, RUN_ID_ENV_VAR, TREE_SNAPSHOT_ENV_VAR, writeCheckCacheEntry, writeDebugNote, } from "./check-cache.js";
|
|
5
|
-
import { resolveConfigPath } from "./config.js";
|
|
6
5
|
import { resolveContext } from "./context.js";
|
|
7
6
|
import { generateHelp } from "./help.js";
|
|
7
|
+
import { resolveConfigPath } from "./helpers/config-path.js";
|
|
8
8
|
import { deriveExcerpt } from "./helpers/deriveExcerpt.js";
|
|
9
9
|
import { isHookName } from "./helpers/hook-name.js";
|
|
10
10
|
import { resolvePackageJsonPath } from "./helpers/package-json.js";
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -17,13 +17,14 @@ export interface CheckCacheConfig {
|
|
|
17
17
|
enabled?: boolean;
|
|
18
18
|
/**
|
|
19
19
|
* Command names removed from the cacheable set, applied after `extraCommands`. This is how a repo retires a
|
|
20
|
-
* name whose chain turned out to do more than report an exit status.
|
|
20
|
+
* name whose chain turned out to do more than report an exit status. Every name has to resolve to a command.
|
|
21
21
|
*/
|
|
22
22
|
excludeCommands?: string[];
|
|
23
23
|
/**
|
|
24
24
|
* Command names added to the cacheable set. Extends rather than replaces, so that declaring one command
|
|
25
25
|
* cannot silently drop the defaults. A name listed here promises exit-status-only semantics through its whole
|
|
26
|
-
* chain, hooks included
|
|
26
|
+
* chain, hooks included, and has to resolve to a command: both lists are read by name alone, so a misspelt
|
|
27
|
+
* entry would otherwise be inert.
|
|
27
28
|
*/
|
|
28
29
|
extraCommands?: string[];
|
|
29
30
|
}
|
|
@@ -2,3 +2,9 @@ import os from 'node:os';
|
|
|
2
2
|
process.env['GIT_CONFIG_GLOBAL'] = os.devNull;
|
|
3
3
|
process.env['GIT_CONFIG_SYSTEM'] = os.devNull;
|
|
4
4
|
process.env['GIT_CONFIG_NOSYSTEM'] = '1';
|
|
5
|
+
process.env['GIT_ATTR_NOSYSTEM'] = '1';
|
|
6
|
+
process.env['GIT_CONFIG_COUNT'] = '2';
|
|
7
|
+
process.env['GIT_CONFIG_KEY_0'] = 'core.excludesFile';
|
|
8
|
+
process.env['GIT_CONFIG_VALUE_0'] = os.devNull;
|
|
9
|
+
process.env['GIT_CONFIG_KEY_1'] = 'core.attributesFile';
|
|
10
|
+
process.env['GIT_CONFIG_VALUE_1'] = os.devNull;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/nmr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Context-aware script runner for pnpm monorepos",
|
|
6
6
|
"keywords": [
|
|
@@ -66,24 +66,26 @@
|
|
|
66
66
|
"nmr-taze": "bin/nmr-taze.js"
|
|
67
67
|
},
|
|
68
68
|
"files": [
|
|
69
|
+
".meta/changelog.json",
|
|
69
70
|
".readyup/kits/*.js",
|
|
70
71
|
".readyup/manifest.json",
|
|
72
|
+
"CHANGELOG.md",
|
|
71
73
|
"agents",
|
|
72
74
|
"bin",
|
|
73
75
|
"dist"
|
|
74
76
|
],
|
|
75
77
|
"dependencies": {
|
|
76
|
-
"@williamthorsen/nmr-core": "0.
|
|
77
|
-
"@williamthorsen/toolbelt.errors": "0.
|
|
78
|
+
"@williamthorsen/nmr-core": "0.11.0",
|
|
79
|
+
"@williamthorsen/toolbelt.errors": "0.7.0",
|
|
78
80
|
"glob": "13.0.6",
|
|
79
81
|
"prettier-plugin-sh": "0.19.0",
|
|
80
82
|
"taze": "21.1.0",
|
|
81
83
|
"yaml": "2.9.0"
|
|
82
84
|
},
|
|
83
85
|
"devDependencies": {
|
|
84
|
-
"@williamthorsen/toolbelt.filesystem": "0.8.
|
|
85
|
-
"@williamthorsen/toolbelt.testing": "0.5.
|
|
86
|
-
"@williamthorsen/toolbelt.vitest": "0.
|
|
86
|
+
"@williamthorsen/toolbelt.filesystem": "0.8.3",
|
|
87
|
+
"@williamthorsen/toolbelt.testing": "0.5.1",
|
|
88
|
+
"@williamthorsen/toolbelt.vitest": "0.9.0",
|
|
87
89
|
"prettier": "3.9.6",
|
|
88
90
|
"readyup": "0.35.0",
|
|
89
91
|
"tinyglobby": "0.2.17",
|