@williamthorsen/nmr 2.2.0 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -58,6 +58,50 @@ export function defineRootVitestConfig(...layers) {
58
58
  projectRoot: monorepoRoot,
59
59
  });
60
60
  }
61
+ function applyLayer(project, layer, tier) {
62
+ const withProject = layer.project ? mergeConfig(project, { test: layer.project }) : project;
63
+ const targetedOverrides = layer.tiers?.[tier];
64
+ return targetedOverrides ? mergeConfig(withProject, { test: targetedOverrides }) : withProject;
65
+ }
66
+ function assertKnownTiers(layers) {
67
+ const knownTiers = TIER_NAMES;
68
+ for (const { tiers } of layers) {
69
+ const declaredTiers = Object.keys(tiers ?? {});
70
+ for (const name of declaredTiers) {
71
+ if (!knownTiers.includes(name)) {
72
+ throw new TypeError(`Unknown tier "${name}" in \`tiers\`. Valid tiers: ${TIER_NAMES.join(', ')}.`);
73
+ }
74
+ }
75
+ }
76
+ }
77
+ function assertNoRetiredOptions(layer) {
78
+ for (const key of Object.keys(layer)) {
79
+ const replacement = RETIRED_OPTION_KEYS.get(key);
80
+ if (replacement !== undefined) {
81
+ throw new TypeError(`Invalid Vitest config: \`${key}\` was renamed to \`${replacement}\`.`);
82
+ }
83
+ }
84
+ }
85
+ function assertOptionKeys(layers, lastLayerKeys) {
86
+ const lastIndex = layers.length - 1;
87
+ layers.forEach((layer, index) => {
88
+ if (layer === undefined)
89
+ return;
90
+ assertNoRetiredOptions(layer);
91
+ const recognizedKeys = index === lastIndex ? lastLayerKeys : RECOGNIZED_OPTION_KEYS;
92
+ const unrecognizedKeys = Object.keys(layer).filter((key) => !recognizedKeys.includes(key));
93
+ if (unrecognizedKeys.length === 0)
94
+ return;
95
+ throw new TypeError(`Invalid Vitest config: unrecognized ${unrecognizedKeys.length === 1 ? 'option' : 'options'} ` +
96
+ `${formatOptionKeys(unrecognizedKeys)}. Recognized: ${formatOptionKeys(recognizedKeys)}.`);
97
+ });
98
+ }
99
+ function buildCollectionExclude(layers, ignoredPathsRoot) {
100
+ const declaredDirs = layers.flatMap((layer) => layer.testCollectionExclude ?? []);
101
+ const globs = [...TEST_COLLECTION_EXCLUDE, ...declaredDirs].map((dir) => `**/${dir}/**`);
102
+ const ignoredGlobs = listGitIgnoredPaths(ignoredPathsRoot).map(buildIgnoredPathGlob);
103
+ return [...new Set([...defaultExclude, ...globs, ...ignoredGlobs])];
104
+ }
61
105
  function buildConfig(declaredLayers, { coverageInclude, ignoredPathsRoot, projectExclude = [], projectRoot }) {
62
106
  const layers = declaredLayers.filter((layer) => layer !== undefined);
63
107
  assertKnownTiers(layers);
@@ -90,6 +134,10 @@ function buildConfig(declaredLayers, { coverageInclude, ignoredPathsRoot, projec
90
134
  }
91
135
  return mergedConfig;
92
136
  }
137
+ function buildIgnoredPathGlob(ignoredPath) {
138
+ const escapedPath = ignoredPath.replaceAll(GLOB_SYNTAX_PATTERN, String.raw `\$&`);
139
+ return escapedPath.endsWith('/') ? `${escapedPath}**` : escapedPath;
140
+ }
93
141
  function buildProjects(layers, extraExclude, projectRoot, ignoredPathsRoot) {
94
142
  const projectTiers = [
95
143
  { exclude: TIERED_PATTERNS, include: ALL_TEST_PATTERNS, name: RESIDUAL_TIER },
@@ -129,54 +177,6 @@ function buildProjects(layers, extraExclude, projectRoot, ignoredPathsRoot) {
129
177
  : mergeConfig(mergedProject, { test: { maxWorkers: flagMaxWorkers } });
130
178
  });
131
179
  }
132
- function applyLayer(project, layer, tier) {
133
- const withProject = layer.project ? mergeConfig(project, { test: layer.project }) : project;
134
- const targetedOverrides = layer.tiers?.[tier];
135
- return targetedOverrides ? mergeConfig(withProject, { test: targetedOverrides }) : withProject;
136
- }
137
- function assertKnownTiers(layers) {
138
- const knownTiers = TIER_NAMES;
139
- for (const { tiers } of layers) {
140
- const declaredTiers = Object.keys(tiers ?? {});
141
- for (const name of declaredTiers) {
142
- if (!knownTiers.includes(name)) {
143
- throw new TypeError(`Unknown tier "${name}" in \`tiers\`. Valid tiers: ${TIER_NAMES.join(', ')}.`);
144
- }
145
- }
146
- }
147
- }
148
- function assertNoRetiredOptions(layer) {
149
- for (const key of Object.keys(layer)) {
150
- const replacement = RETIRED_OPTION_KEYS.get(key);
151
- if (replacement !== undefined) {
152
- throw new TypeError(`Invalid Vitest config: \`${key}\` was renamed to \`${replacement}\`.`);
153
- }
154
- }
155
- }
156
- function assertOptionKeys(layers, lastLayerKeys) {
157
- const lastIndex = layers.length - 1;
158
- layers.forEach((layer, index) => {
159
- if (layer === undefined)
160
- return;
161
- assertNoRetiredOptions(layer);
162
- const recognizedKeys = index === lastIndex ? lastLayerKeys : RECOGNIZED_OPTION_KEYS;
163
- const unrecognizedKeys = Object.keys(layer).filter((key) => !recognizedKeys.includes(key));
164
- if (unrecognizedKeys.length === 0)
165
- return;
166
- throw new TypeError(`Invalid Vitest config: unrecognized ${unrecognizedKeys.length === 1 ? 'option' : 'options'} ` +
167
- `${formatOptionKeys(unrecognizedKeys)}. Recognized: ${formatOptionKeys(recognizedKeys)}.`);
168
- });
169
- }
170
- function buildCollectionExclude(layers, ignoredPathsRoot) {
171
- const declaredDirs = layers.flatMap((layer) => layer.testCollectionExclude ?? []);
172
- const globs = [...TEST_COLLECTION_EXCLUDE, ...declaredDirs].map((dir) => `**/${dir}/**`);
173
- const ignoredGlobs = listGitIgnoredPaths(ignoredPathsRoot).map(buildIgnoredPathGlob);
174
- return [...new Set([...defaultExclude, ...globs, ...ignoredGlobs])];
175
- }
176
- function buildIgnoredPathGlob(ignoredPath) {
177
- const escapedPath = ignoredPath.replaceAll(GLOB_SYNTAX_PATTERN, String.raw `\$&`);
178
- return escapedPath.endsWith('/') ? `${escapedPath}**` : escapedPath;
179
- }
180
180
  function formatOptionKeys(keys) {
181
181
  return keys
182
182
  .toSorted()
package/docs/delegate.md CHANGED
@@ -162,5 +162,5 @@ Every other failure still exits 255: a delegation that `--delegate`, `--delegate
162
162
  ## Setting up the host
163
163
 
164
164
  - **SSH that does not prompt.** nmr runs the ssh command that `GIT_SSH_COMMAND` names, evaluated by `/bin/sh` as git evaluates it, or plain `ssh` when the variable is unset or empty, and adds `BatchMode=yes` and a connect timeout after the command's own options, so an option that the command sets itself takes precedence. Under `BatchMode=yes`, a key that needs a passphrase must be in the agent, and the host key must already be known. An unreachable host fails within 10 seconds.
165
- - **git, Node, and pnpm on the interactive login shell's `PATH`.** nmr reads `PATH` from the host's login shell started as an interactive one, which loads `~/.zshrc` or `~/.bashrc` as well as the login profile: That is where pnpm's installer and the common version managers put Node and pnpm. What the startup files print does not reach the output, but whatever else they do runs before each step. Node on the host must satisfy nmr's own `engines` range (`>=24.16.0`). When Node is not on that `PATH`, the check-out step fails, and when pnpm is not, the install step fails; each failure names `PATH`. The login shell must accept `-ilc`: bash and zsh do.
165
+ - **git, Node, and pnpm on the interactive login shell's `PATH`.** nmr reads `PATH` from the host's login shell started as an interactive one, which loads `~/.zshrc` or `~/.bashrc` as well as the login profile: That is where pnpm's installer and the common version managers put Node and pnpm. What the startup files print does not reach the output, but whatever else they do runs before each step. After it checks out the snapshot, nmr starts the install and the command on that `PATH`, so a version manager's shims select the Node and pnpm that the snapshot pins. Node on the host must satisfy nmr's own `engines` range (`>=24.16.0`). When Node is not on that `PATH`, the check-out step fails, and when pnpm is not, the install step fails; each failure names `PATH`. The login shell must accept `-ilc`: bash and zsh do.
166
166
  - **A sandboxed agent session needs network access to the host.** A sandbox that allows only HTTP egress refuses `ssh`. Under `when: 'agent'`, every check that the session runs then [runs here](#when-no-host-answers) after the connect timeout, and a requested delegation fails at `prepare`, until the session has that access. The Claude Code sandbox reaches a host only through the proxy that its `GIT_SSH_COMMAND` names, and it admits the host by the name that ssh connects to, which is the `HostName` that `~/.ssh/config` gives the alias (`ssh -G <host>` prints it), on port 22: For `imac2` with `HostName skypilot-imac2.local`, the sandbox must admit `skypilot-imac2.local:22`, not `imac2`. Nobody has confirmed whether the proxy resolves an mDNS name such as `skypilot-imac2.local`: When a delegation to such a host does not answer, and `ssh` reports `sandbox proxy: <name>:22: 502 Bad Gateway`, give the alias a `HostName` that is an IP address or a unicast DNS name, and have the sandbox admit that name's port 22.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@williamthorsen/nmr",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "private": false,
5
5
  "description": "Context-aware script runner for pnpm monorepos",
6
6
  "keywords": [
@@ -81,19 +81,19 @@
81
81
  ],
82
82
  "dependencies": {
83
83
  "@williamthorsen/nmr-core": "0.16.2",
84
- "@williamthorsen/toolbelt.errors": "0.10.0",
85
- "@williamthorsen/toolbelt.filesystem": "0.10.2",
86
- "glob": "14.0.4",
87
- "prettier": "3.10.0",
84
+ "@williamthorsen/toolbelt.errors": "0.10.1",
85
+ "@williamthorsen/toolbelt.filesystem": "0.11.0",
86
+ "glob": "14.0.6",
87
+ "prettier": "3.10.1",
88
88
  "prettier-plugin-sh": "0.20.2",
89
89
  "taze": "21.3.0",
90
90
  "unconfig": "7.5.0"
91
91
  },
92
92
  "devDependencies": {
93
- "@williamthorsen/toolbelt.testing": "0.8.2",
94
- "@williamthorsen/toolbelt.vitest": "0.13.4",
93
+ "@williamthorsen/toolbelt.testing": "0.8.3",
94
+ "@williamthorsen/toolbelt.vitest": "0.13.5",
95
95
  "esbuild": "0.28.2",
96
- "readyup": "0.46.1",
96
+ "readyup": "0.46.2",
97
97
  "tinyglobby": "0.2.17",
98
98
  "typescript": "6.0.3",
99
99
  "vite": "8.3.8",