@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/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.35.0 (2026-09-01)
6
+ ## Release notes — v0.36.0 (2026-09-08)
7
7
 
8
8
  ### 🎉 Features
9
9
 
10
- - Expose tsconfigPaths as a first-class Vitest config flag (#751)
10
+ - Report a package whose tarball would omit its changelog (#768)
11
11
 
12
- Adds a `tsconfigPaths` option to `defineVitestConfig` and `defineRootVitestConfig` in `@williamthorsen/nmr/vitest`. It emits Vite's `resolve.tsconfigPaths`, so a test resolves an aliased specifier through the `paths` its own `tsconfig.json` declares, the way `tsc` does.
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
- The option defaults to `false` and requires Vite 8, which `@williamthorsen/nmr` now declares as an optional peer dependency at `>=8.0.0 <9`.
14
+ - Reject a checkCache command name that matches no command (#778)
15
15
 
16
- - Publish a test-file conventions check consuming repos can gate in CI (#753)
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
- Adds `checkTestFileConventions` at `@williamthorsen/nmr/tests`, which a consuming repo calls from a test of its own. It fails the run on any test file that names no isolation tier or sits outside `__tests__`. Both passed silently before.
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
- Adds `testCollectionExclude` to `defineVitestConfig` and `defineRootVitestConfig`. The same array of directory names goes to the check's `exclude`, so one declaration scopes both the sweep and what Vitest collects.
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
- - Keep doc comments in published declaration files (#762)
22
+ - Report a bin target that is not a committed wrapper (#780)
23
23
 
24
- Changes the behavior of `nmr-compile` so that it preserves doc comments in `.d.ts` files.
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
- TypeScript's `removeComments` governs a whole emit, so a TS configuration that kept comments out of JavaScript also kept them out of declaration files. `nmr-compile` now emits from two programs: The first produces the `.js` under the package's own `removeComments`, and the second produces the declarations with comments.
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
- - Report workspaces whose Vitest config bypasses the projects model (#763)
29
+ ### 🐛 Bug fixes
29
30
 
30
- Adds a check to nmr's ReadyUp kit to report every workspace that holds a Vite config without a Vitest config beside it, and widens the existing content check from the repo root to every workspace, so a Vitest config that does not build on `defineVitestConfig` is reported. The purpose of these checks is to ensure that tier-selecting test commands are supported.
31
+ - Keep global git ignore rules and attributes out of test subprocesses (#773)
31
32
 
32
- Also fixes an issue where the `no package re-exports the ancestor Vitest config` check incorrectly advised a consumer to delete a config.
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 and sets `GIT_CONFIG_NOSYSTEM`, so a test that spawns `git` reads neither the developer's identity nor a signing config that can block on a passphrase. It leads each project's `setupFiles`, ahead of every entry the layers add, because a shared setup establishes the environment the rest run in.
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 = '⏭️';
@@ -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
@@ -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
- const CONFIG_FILENAME = 'nmr.config.ts';
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 = [
@@ -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";
@@ -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.35.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.10.4",
77
- "@williamthorsen/toolbelt.errors": "0.6.3",
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.2",
85
- "@williamthorsen/toolbelt.testing": "0.5.0",
86
- "@williamthorsen/toolbelt.vitest": "0.8.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",