@williamthorsen/nmr 0.14.2 → 0.16.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/AGENTS.md +16 -16
- package/README.md +26 -4
- package/bin/nmr-compile.js +11 -0
- package/dist/esm/.cache +1 -1
- package/dist/esm/cli-build.d.ts +1 -0
- package/dist/esm/cli-build.js +8 -0
- package/dist/esm/cli-ensure-prepublish-hooks.js +13 -31
- package/dist/esm/cli-report-overrides.js +3 -2
- package/dist/esm/cli-sync-agent-files.js +19 -19
- package/dist/esm/cli-sync-pnpm-version.js +3 -2
- package/dist/esm/cli.js +2 -2
- package/dist/esm/commands/build.d.ts +15 -0
- package/dist/esm/commands/build.js +122 -0
- package/dist/esm/commands/sync-agent-files.d.ts +4 -5
- package/dist/esm/commands/sync-agent-files.js +37 -51
- package/dist/esm/commands/sync-pnpm-version.js +16 -11
- package/dist/esm/default-scripts.js +2 -1
- package/dist/esm/help.js +0 -1
- package/dist/esm/helpers/code-quality-pnpm-action.d.ts +2 -1
- package/dist/esm/helpers/code-quality-pnpm-action.js +12 -3
- package/dist/esm/resolver.d.ts +1 -0
- package/dist/esm/resolver.js +5 -1
- package/dist/esm/runCli.js +12 -14
- package/dist/esm/runner.js +2 -2
- package/package.json +8 -2
- package/dist/esm/helpers/yaml-utils.d.ts +0 -1
- package/dist/esm/helpers/yaml-utils.js +0 -14
package/AGENTS.md
CHANGED
|
@@ -4,11 +4,11 @@ source: '@williamthorsen/nmr@0.0.0-source'
|
|
|
4
4
|
|
|
5
5
|
# nmr: agent guidance
|
|
6
6
|
|
|
7
|
-
This file is managed by `@williamthorsen/nmr`. Do not edit
|
|
7
|
+
This file is managed by `@williamthorsen/nmr`. Do not edit; run `nmr sync-agent-files` to refresh it after an nmr upgrade that changes this guidance.
|
|
8
8
|
|
|
9
9
|
## Discover scripts by running nmr
|
|
10
10
|
|
|
11
|
-
Run `nmr` with no command (from the monorepo root or any workspace package) to list every available script, including composite expansions and resolved shell commands. Check this before guessing a script name from another repo
|
|
11
|
+
Run `nmr` with no command (from the monorepo root or any workspace package) to list every available script, including composite expansions and resolved shell commands. Check this before guessing a script name from another repo: The registry is authoritative.
|
|
12
12
|
|
|
13
13
|
## Invocation rules
|
|
14
14
|
|
|
@@ -20,17 +20,13 @@ Run `nmr` with no command (from the monorepo root or any workspace package) to l
|
|
|
20
20
|
`nmr` ships as a workspace bin. The bare `nmr` command works only when your shell can find `<root>/node_modules/.bin/nmr`. Choose one:
|
|
21
21
|
|
|
22
22
|
- **direnv** (recommended for contributors). With [direnv](https://direnv.net/) installed, the repo's `.envrc` adds `node_modules/.bin` to your `PATH` automatically. From any subdirectory, bare `nmr` works.
|
|
23
|
-
- **`pnpm exec nmr <command
|
|
23
|
+
- **`pnpm exec nmr <command>`**: Works without setup. pnpm resolves the bin from the workspace root.
|
|
24
24
|
|
|
25
25
|
Avoid `npx nmr`. Inside git worktrees, `npx` can resolve a different nmr binary from outside the working tree.
|
|
26
26
|
|
|
27
|
-
### Bootstrap fallback
|
|
28
|
-
|
|
29
|
-
If `nmr` itself fails to run (fresh clone, missing build output), run `pnpm run bootstrap` from the repo root first.
|
|
30
|
-
|
|
31
27
|
## Root vs. workspace context
|
|
32
28
|
|
|
33
|
-
nmr walks up to find `pnpm-workspace.yaml`, then decides which registry to use based on whether your cwd is inside a workspace package. The same command name (e.g. `build`, `test`, `check:strict`) often exists in both registries with different behavior
|
|
29
|
+
nmr walks up to find `pnpm-workspace.yaml`, then decides which registry to use based on whether your cwd is inside a workspace package. The same command name (e.g. `build`, `test`, `check:strict`) often exists in both registries with different behavior; the root version typically delegates across all workspaces. Use `-w` to force the root registry from inside a package dir, and `-F <pkg>` to run a single package's script from anywhere.
|
|
34
30
|
|
|
35
31
|
## Composite scripts
|
|
36
32
|
|
|
@@ -40,13 +36,17 @@ A script value shown in `nmr` output as `[a, b, c]` is a composite: it runs `nmr
|
|
|
40
36
|
|
|
41
37
|
Some root scripts (e.g. `lint`, `typecheck`, `test`) expand to `nmr root:X && pnpm --recursive exec nmr X`. The `root:X` variant runs only against root-level files; the plain name runs everywhere. Use `root:X` directly when you want to isolate a failure to the root code.
|
|
42
38
|
|
|
39
|
+
## Managed build
|
|
40
|
+
|
|
41
|
+
The default `compile` script runs `nmr-compile`, a standalone bin that esbuild-compiles a package's `src` to `dist/esm`, rewriting `~/` (package-root) import aliases and `.ts`→`.js` specifiers, and skipping work when inputs are unchanged. There is no repo-local build script to maintain; `nmr build` runs `compile` then `generate-typings`. To find or debug the build, look to `nmr-compile`, not a `config/build.ts` in the consuming repo.
|
|
42
|
+
|
|
43
43
|
## Override behaviors
|
|
44
44
|
|
|
45
45
|
In `.config/nmr.config.ts` or a package's `package.json`, override values have special semantics:
|
|
46
46
|
|
|
47
|
-
- `""` (empty string)
|
|
48
|
-
- `":"
|
|
49
|
-
- Any other string
|
|
47
|
+
- `""` (empty string): Skip the script with a "Skipping" message; exit 0.
|
|
48
|
+
- `":"`: No-op; exit 0. Prefer this over `""` if your repo enforces non-empty script values.
|
|
49
|
+
- Any other string: Runs in place of the default.
|
|
50
50
|
|
|
51
51
|
## Pre and post hooks
|
|
52
52
|
|
|
@@ -54,11 +54,11 @@ Every `nmr X` invocation auto-wraps as the equivalent of `nmr X:pre && nmr X &&
|
|
|
54
54
|
|
|
55
55
|
Behaviors worth knowing:
|
|
56
56
|
|
|
57
|
-
- **Silent when absent
|
|
58
|
-
- **Skip overrides apply to hooks
|
|
59
|
-
- **Skipping the main command skips its hooks
|
|
60
|
-
- **Recursion guard
|
|
61
|
-
- **Passthrough args attach only to the main command
|
|
57
|
+
- **Silent when absent**: Missing hooks produce no error and no output.
|
|
58
|
+
- **Skip overrides apply to hooks**: A hook value of `""` or `":"` is treated the same as not defining the hook. No console message.
|
|
59
|
+
- **Skipping the main command skips its hooks**: When `X` is overridden to `""` or `":"`, neither `X:pre` nor `X:post` fires.
|
|
60
|
+
- **Recursion guard**: Direct invocation of a hook (e.g., `nmr build:pre`) is treated as a leaf operation. It does NOT itself attempt to resolve `build:pre:pre` or `build:pre:post`.
|
|
61
|
+
- **Passthrough args attach only to the main command**: `nmr X --flag value` runs hooks without `--flag value`.
|
|
62
62
|
|
|
63
63
|
Worked examples:
|
|
64
64
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,21 @@
|
|
|
2
2
|
|
|
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
|
-
<!-- section:release-notes
|
|
5
|
+
<!-- section:release-notes -->
|
|
6
|
+
## Release notes — v0.16.0 (2026-06-30)
|
|
7
|
+
|
|
8
|
+
### 🎉 Features
|
|
9
|
+
|
|
10
|
+
- 🚨 **Breaking:** Auto-activate integration test variant from config presence (#448)
|
|
11
|
+
|
|
12
|
+
A package can now separate its integration tests from its standalone suite simply by including a `vitest.integration.config.ts` (alongside a `vitest.standalone.config.ts`). The `--int-test` flag that previously enabled this is removed — that config-file pairing is now the only way to activate the separation. In such a package, `test` and `test:coverage` run only the standalone suite and skip integration tests, while a new `test:all` runs both suites together. The separation now holds even when tests run across every package at once, so a full-workspace `test` run still keeps integration tests out of the default suite. Packages that previously hand-copied these test scripts no longer need to.
|
|
13
|
+
|
|
14
|
+
### 🐛 Bug fixes
|
|
15
|
+
|
|
16
|
+
- Prevent CLI output truncation when piped before exit (#446)
|
|
17
|
+
|
|
18
|
+
Fixes an issue where large output from the `nmr` and `v11y` commands could be truncated when captured through a pipe — for example, by a CI job.
|
|
19
|
+
<!-- /section:release-notes -->
|
|
6
20
|
|
|
7
21
|
## Installation
|
|
8
22
|
|
|
@@ -137,7 +151,7 @@ These scripts are available out of the box. Repo-wide config (tier 2) and per-pa
|
|
|
137
151
|
| `check` | `typecheck`, `fmt:check`, `lint:check`, `test` |
|
|
138
152
|
| `check:strict` | `typecheck`, `fmt:check`, `lint:strict`, `test:coverage` |
|
|
139
153
|
| `clean` | `pnpm exec rimraf dist/*` |
|
|
140
|
-
| `compile` | `
|
|
154
|
+
| `compile` | `nmr-compile` |
|
|
141
155
|
| `fix` | `lint`, `fmt` |
|
|
142
156
|
| `fix:check` | `fmt:check`, `lint:check` |
|
|
143
157
|
| `fmt` | `prettier --list-different --write .` |
|
|
@@ -154,11 +168,12 @@ These scripts are available out of the box. Repo-wide config (tier 2) and per-pa
|
|
|
154
168
|
|
|
155
169
|
#### Integration test variant
|
|
156
170
|
|
|
157
|
-
|
|
171
|
+
A package gets this variant automatically when it contains a `vitest.integration.config.ts` file: `test` and `test:coverage` run the standalone (non-integration) suite, and integration tests run only when invoked explicitly. Activation is detected per package, so it applies under the recursive `nmr ci` fan-out. Opting in requires **both** `vitest.integration.config.ts` and `vitest.standalone.config.ts`, since the standalone scripts reference the latter.
|
|
158
172
|
|
|
159
173
|
| Command | Runs |
|
|
160
174
|
| ------------------ | ------------------------------------------------------------------ |
|
|
161
175
|
| `test` | `pnpm exec vitest --config=vitest.standalone.config.ts` |
|
|
176
|
+
| `test:all` | `pnpm exec vitest` |
|
|
162
177
|
| `test:coverage` | `pnpm exec vitest --config=vitest.standalone.config.ts --coverage` |
|
|
163
178
|
| `test:integration` | `pnpm exec vitest --config=vitest.integration.config.ts` |
|
|
164
179
|
| `test:watch` | `pnpm exec vitest --config=vitest.standalone.config.ts --watch` |
|
|
@@ -272,7 +287,6 @@ nmr [flags] <command> [args...]
|
|
|
272
287
|
| `-q, --quiet` | Suppress info messages; show full output on failure | — |
|
|
273
288
|
| `-?, --help` | Show available commands | — |
|
|
274
289
|
| `-V, --version` | Show version number | — |
|
|
275
|
-
| `--int-test` | Use integration test scripts | — |
|
|
276
290
|
|
|
277
291
|
### Examples
|
|
278
292
|
|
|
@@ -334,6 +348,14 @@ nmr sync-pnpm-version
|
|
|
334
348
|
|
|
335
349
|
## Standalone utilities
|
|
336
350
|
|
|
351
|
+
### `nmr-compile`
|
|
352
|
+
|
|
353
|
+
Compile a single package's `src` tree to `dist/esm` with esbuild. Rewrites `~/` (package-root) import aliases and `.ts`→`.js` specifiers to match the emitted output, and skips the build when no input has changed (a content-and-path hash is cached in `dist/esm/.cache`). This is the default `compile` script — run it from a package directory.
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
nmr-compile
|
|
357
|
+
```
|
|
358
|
+
|
|
337
359
|
### `ensure-prepublish-hooks`
|
|
338
360
|
|
|
339
361
|
Verify that all publishable workspace packages have a `prepublishOnly` script. Exits non-zero if any are missing.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
try {
|
|
3
|
+
await import('../dist/esm/cli-build.js');
|
|
4
|
+
} catch (error) {
|
|
5
|
+
if (error.code === 'ERR_MODULE_NOT_FOUND') {
|
|
6
|
+
process.stderr.write('nmr-compile: build output not found — run `pnpm run build` first\n');
|
|
7
|
+
} else {
|
|
8
|
+
process.stderr.write(`nmr-compile: failed to load: ${error.message}\n`);
|
|
9
|
+
}
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
package/dist/esm/.cache
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
47af840104236371ef384e3368ae463ca3d05d525fcffd96134d4c9c11257796
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,31 +1,12 @@
|
|
|
1
|
+
import { parseArgsOrExit, reportError } from "@williamthorsen/nmr-core";
|
|
1
2
|
import { DEFAULT_HOOK, ensurePrepublishHooks } from "./commands/ensure-prepublish-hooks.js";
|
|
2
3
|
import { findMonorepoRoot } from "./context.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
switch (arg) {
|
|
10
|
-
case "--fix":
|
|
11
|
-
fix = true;
|
|
12
|
-
break;
|
|
13
|
-
case "--dry-run":
|
|
14
|
-
dryRun = true;
|
|
15
|
-
break;
|
|
16
|
-
case "--command":
|
|
17
|
-
i++;
|
|
18
|
-
command = args[i];
|
|
19
|
-
if (!command) {
|
|
20
|
-
console.error("--command requires a value");
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
break;
|
|
24
|
-
default:
|
|
25
|
-
console.error(`Unknown argument: ${arg}`);
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
4
|
+
const flagSchema = {
|
|
5
|
+
fix: { long: "--fix", type: "boolean" },
|
|
6
|
+
dryRun: { long: "--dry-run", type: "boolean" },
|
|
7
|
+
command: { long: "--command", type: "string" }
|
|
8
|
+
};
|
|
9
|
+
const { fix, dryRun, command } = parseArgsOrExit(process.argv.slice(2), flagSchema).flags;
|
|
29
10
|
try {
|
|
30
11
|
const monorepoRoot = findMonorepoRoot();
|
|
31
12
|
const hookCommand = command ?? DEFAULT_HOOK;
|
|
@@ -52,11 +33,12 @@ try {
|
|
|
52
33
|
}
|
|
53
34
|
if (result.hasFailures) {
|
|
54
35
|
const missing = result.packages.filter((p) => p.action === "missing").length;
|
|
55
|
-
|
|
56
|
-
${missing} package(s) missing prepublishOnly. Use --fix to add it
|
|
57
|
-
|
|
36
|
+
process.stderr.write(`
|
|
37
|
+
${missing} package(s) missing prepublishOnly. Use --fix to add it.
|
|
38
|
+
`);
|
|
39
|
+
process.exitCode = 1;
|
|
58
40
|
}
|
|
59
41
|
} catch (error) {
|
|
60
|
-
|
|
61
|
-
process.
|
|
42
|
+
reportError(error instanceof Error ? error.message : String(error));
|
|
43
|
+
process.exitCode = 1;
|
|
62
44
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { reportError } from "@williamthorsen/nmr-core";
|
|
1
2
|
import { reportOverrides } from "./commands/report-overrides.js";
|
|
2
3
|
import { findMonorepoRoot } from "./context.js";
|
|
3
4
|
try {
|
|
4
5
|
const monorepoRoot = findMonorepoRoot();
|
|
5
6
|
reportOverrides(monorepoRoot);
|
|
6
7
|
} catch (error) {
|
|
7
|
-
|
|
8
|
-
process.
|
|
8
|
+
reportError(error instanceof Error ? error.message : String(error));
|
|
9
|
+
process.exitCode = 1;
|
|
9
10
|
}
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
+
import { parseArgsOrExit, reportError } from "@williamthorsen/nmr-core";
|
|
1
2
|
import { check, sync } from "./commands/sync-agent-files.js";
|
|
2
3
|
import { findMonorepoRoot } from "./context.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return { error: `Usage: nmr sync-agent-files [--check]` };
|
|
8
|
-
}
|
|
4
|
+
const flagSchema = {
|
|
5
|
+
check: { long: "--check", type: "boolean" }
|
|
6
|
+
};
|
|
7
|
+
const { flags } = parseArgsOrExit(process.argv.slice(2), flagSchema);
|
|
9
8
|
try {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
runCommand(flags.check);
|
|
10
|
+
} catch (error) {
|
|
11
|
+
reportError(error instanceof Error ? error.message : String(error));
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
function runCommand(checkOnly) {
|
|
15
15
|
const monorepoRoot = findMonorepoRoot();
|
|
16
|
-
if (
|
|
17
|
-
const {
|
|
18
|
-
console.info(
|
|
16
|
+
if (!checkOnly) {
|
|
17
|
+
const { path: destinationPath, packageSpecifier, changed } = sync(monorepoRoot);
|
|
18
|
+
console.info(
|
|
19
|
+
changed ? `\u2713 Wrote ${destinationPath} (${packageSpecifier})` : `\u2713 ${destinationPath} already up to date`
|
|
20
|
+
);
|
|
19
21
|
process.exit(0);
|
|
20
22
|
}
|
|
21
23
|
const result = check(monorepoRoot);
|
|
22
24
|
if (result.ok) {
|
|
23
|
-
console.info(
|
|
25
|
+
console.info("\u2713 .agents/nmr/AGENTS.md is in sync");
|
|
24
26
|
process.exit(0);
|
|
25
27
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
} catch (error) {
|
|
29
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
28
|
+
process.stderr.write(`${result.reason}
|
|
29
|
+
`);
|
|
30
30
|
process.exit(1);
|
|
31
31
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { reportError } from "@williamthorsen/nmr-core";
|
|
1
2
|
import { syncPnpmVersion } from "./commands/sync-pnpm-version.js";
|
|
2
3
|
import { findMonorepoRoot } from "./context.js";
|
|
3
4
|
try {
|
|
4
5
|
const monorepoRoot = findMonorepoRoot();
|
|
5
6
|
syncPnpmVersion(monorepoRoot);
|
|
6
7
|
} catch (error) {
|
|
7
|
-
|
|
8
|
-
process.
|
|
8
|
+
reportError(error instanceof Error ? error.message : String(error));
|
|
9
|
+
process.exitCode = 1;
|
|
9
10
|
}
|
package/dist/esm/cli.js
CHANGED
|
@@ -8,9 +8,9 @@ try {
|
|
|
8
8
|
stdout: process.stdout,
|
|
9
9
|
stderr: process.stderr
|
|
10
10
|
});
|
|
11
|
-
process.
|
|
11
|
+
process.exitCode = exitCode;
|
|
12
12
|
} catch (error) {
|
|
13
13
|
process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}
|
|
14
14
|
`);
|
|
15
|
-
process.
|
|
15
|
+
process.exitCode = 1;
|
|
16
16
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Format, type Platform } from 'esbuild';
|
|
2
|
+
export interface BuildOptions {
|
|
3
|
+
aliases?: Record<string, string>;
|
|
4
|
+
entryGlobs?: string[];
|
|
5
|
+
ignore?: string[];
|
|
6
|
+
outdir?: string;
|
|
7
|
+
cacheFile?: string;
|
|
8
|
+
format?: Format;
|
|
9
|
+
platform?: Platform;
|
|
10
|
+
target?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function buildPackage(packageDir: string, options?: BuildOptions): Promise<void>;
|
|
13
|
+
export declare function computeBuildHash(packageDir: string, files: string[], outputConfig: object): Promise<string>;
|
|
14
|
+
export declare function resolveAliasImports(code: string, fileDir: string, aliasMap: Record<string, string>, packageDir: string): string;
|
|
15
|
+
export declare function rewriteTsImportExtensions(code: string): string;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { build } from "esbuild";
|
|
6
|
+
import { glob } from "glob";
|
|
7
|
+
const PACKAGE_ICON = "\u{1F4E6}";
|
|
8
|
+
const SKIPPED_ICON = "\u23ED\uFE0F";
|
|
9
|
+
const DEFAULT_ALIASES = { "~/": "." };
|
|
10
|
+
const DEFAULT_ENTRY_GLOBS = ["src/**/*.ts"];
|
|
11
|
+
const DEFAULT_IGNORE = ["**/__tests__/**"];
|
|
12
|
+
const DEFAULT_OUTDIR = "dist/esm/";
|
|
13
|
+
const DEFAULT_CACHE_FILE = "dist/esm/.cache";
|
|
14
|
+
const DEFAULT_FORMAT = "esm";
|
|
15
|
+
const DEFAULT_PLATFORM = "node";
|
|
16
|
+
const DEFAULT_TARGET = "es2022";
|
|
17
|
+
async function buildPackage(packageDir, options = {}) {
|
|
18
|
+
const aliases = options.aliases ?? DEFAULT_ALIASES;
|
|
19
|
+
const cacheFile = options.cacheFile ?? DEFAULT_CACHE_FILE;
|
|
20
|
+
const outputConfig = {
|
|
21
|
+
format: options.format ?? DEFAULT_FORMAT,
|
|
22
|
+
platform: options.platform ?? DEFAULT_PLATFORM,
|
|
23
|
+
target: [options.target ?? DEFAULT_TARGET]
|
|
24
|
+
};
|
|
25
|
+
const entryPoints = await glob(options.entryGlobs ?? DEFAULT_ENTRY_GLOBS, {
|
|
26
|
+
cwd: packageDir,
|
|
27
|
+
ignore: options.ignore ?? DEFAULT_IGNORE
|
|
28
|
+
});
|
|
29
|
+
const dependencies = ["package.json"];
|
|
30
|
+
const { changed, currentHash } = await detectBuildChanges(
|
|
31
|
+
packageDir,
|
|
32
|
+
[...entryPoints, ...dependencies],
|
|
33
|
+
outputConfig,
|
|
34
|
+
cacheFile
|
|
35
|
+
);
|
|
36
|
+
if (!changed) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
await build({
|
|
40
|
+
absWorkingDir: packageDir,
|
|
41
|
+
entryPoints,
|
|
42
|
+
outdir: options.outdir ?? DEFAULT_OUTDIR,
|
|
43
|
+
bundle: false,
|
|
44
|
+
sourcemap: false,
|
|
45
|
+
plugins: [rewriteTsExtensions(packageDir, aliases)],
|
|
46
|
+
...outputConfig
|
|
47
|
+
});
|
|
48
|
+
await writeBuildCache(packageDir, cacheFile, currentHash);
|
|
49
|
+
}
|
|
50
|
+
async function computeBuildHash(packageDir, files, outputConfig) {
|
|
51
|
+
const hash = createHash("sha256");
|
|
52
|
+
for (const file of [...files].sort()) {
|
|
53
|
+
hash.update(file);
|
|
54
|
+
hash.update("\0");
|
|
55
|
+
hash.update(await readFile(path.join(packageDir, file)));
|
|
56
|
+
}
|
|
57
|
+
hash.update(JSON.stringify(outputConfig));
|
|
58
|
+
return hash.digest("hex");
|
|
59
|
+
}
|
|
60
|
+
function resolveAliasImports(code, fileDir, aliasMap, packageDir) {
|
|
61
|
+
let result = code;
|
|
62
|
+
for (const [alias, targetDir] of Object.entries(aliasMap)) {
|
|
63
|
+
const escaped = alias.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
64
|
+
const regex = new RegExp(String.raw`(?<=from\s+['"])${escaped}([^'"]+)(?=['"])`, "g");
|
|
65
|
+
result = result.replace(regex, (_, subpath) => {
|
|
66
|
+
const absolute = path.resolve(packageDir, targetDir, subpath);
|
|
67
|
+
const relative = path.relative(fileDir, absolute);
|
|
68
|
+
return relative.startsWith(".") ? relative : `./${relative}`;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
function rewriteTsImportExtensions(code) {
|
|
74
|
+
return code.replaceAll(/(?<=from\s+['"])(\.{1,2}\/[^'"]+)\.ts(?=['"])/g, "$1.js");
|
|
75
|
+
}
|
|
76
|
+
async function detectBuildChanges(packageDir, files, outputConfig, cacheFile) {
|
|
77
|
+
const packageName = path.basename(packageDir);
|
|
78
|
+
const cachePath = path.join(packageDir, cacheFile);
|
|
79
|
+
const previousHash = existsSync(cachePath) ? readFileSync(cachePath, "utf8") : void 0;
|
|
80
|
+
const currentHash = await computeBuildHash(packageDir, files, outputConfig);
|
|
81
|
+
if (previousHash === currentHash) {
|
|
82
|
+
console.info(`${SKIPPED_ICON} ${packageName}: No changes detected. Skipping build.`);
|
|
83
|
+
return { changed: false, currentHash };
|
|
84
|
+
}
|
|
85
|
+
console.info(`${PACKAGE_ICON} ${packageName}: Changes detected.`);
|
|
86
|
+
return { changed: true, currentHash };
|
|
87
|
+
}
|
|
88
|
+
async function writeBuildCache(packageDir, cacheFile, hash) {
|
|
89
|
+
const cachePath = path.join(packageDir, cacheFile);
|
|
90
|
+
await mkdir(path.dirname(cachePath), { recursive: true });
|
|
91
|
+
await writeFile(cachePath, hash);
|
|
92
|
+
}
|
|
93
|
+
function rewriteTsExtensions(packageDir, aliasMap) {
|
|
94
|
+
const aliasBase = realpathSync(packageDir);
|
|
95
|
+
return {
|
|
96
|
+
name: "rewrite-ts-extensions",
|
|
97
|
+
setup(pluginBuild) {
|
|
98
|
+
pluginBuild.onLoad({ filter: /\.ts$/ }, async (args) => {
|
|
99
|
+
const fileDir = path.dirname(args.path);
|
|
100
|
+
let code = await readFile(args.path, "utf8");
|
|
101
|
+
let shebang = "";
|
|
102
|
+
if (code.startsWith("#!")) {
|
|
103
|
+
const newlineIndex = code.indexOf("\n");
|
|
104
|
+
if (newlineIndex === -1) {
|
|
105
|
+
return { contents: code, loader: "ts" };
|
|
106
|
+
}
|
|
107
|
+
shebang = code.slice(0, newlineIndex + 1);
|
|
108
|
+
code = code.slice(newlineIndex + 1);
|
|
109
|
+
}
|
|
110
|
+
code = resolveAliasImports(code, fileDir, aliasMap, aliasBase);
|
|
111
|
+
code = rewriteTsImportExtensions(code);
|
|
112
|
+
return { contents: `${shebang}${code}`, loader: "ts" };
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export {
|
|
118
|
+
buildPackage,
|
|
119
|
+
computeBuildHash,
|
|
120
|
+
resolveAliasImports,
|
|
121
|
+
rewriteTsImportExtensions
|
|
122
|
+
};
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
export declare function
|
|
1
|
+
export declare function check(destinationDir: string): CheckResult;
|
|
2
2
|
export interface SyncResult {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
path: string;
|
|
4
|
+
packageSpecifier: string;
|
|
5
|
+
changed: boolean;
|
|
5
6
|
}
|
|
6
7
|
export declare function sync(destinationDir: string): SyncResult;
|
|
7
8
|
export type CheckResult = {
|
|
8
9
|
ok: true;
|
|
9
|
-
stamp: string;
|
|
10
10
|
} | {
|
|
11
11
|
ok: false;
|
|
12
12
|
reason: string;
|
|
13
13
|
};
|
|
14
|
-
export declare function check(destinationDir: string): CheckResult;
|
|
@@ -6,73 +6,59 @@ const VERSION = readPackageVersion(import.meta.url);
|
|
|
6
6
|
const PACKAGE_NAME = "@williamthorsen/nmr";
|
|
7
7
|
const DESTINATION_RELATIVE_PATH = ".agents/nmr/AGENTS.md";
|
|
8
8
|
const SOURCE_FILENAME = "AGENTS.md";
|
|
9
|
-
function getSourcePath() {
|
|
10
|
-
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
while (dir !== path.dirname(dir)) {
|
|
12
|
-
const candidate = path.join(dir, SOURCE_FILENAME);
|
|
13
|
-
if (existsSync(candidate)) return candidate;
|
|
14
|
-
dir = path.dirname(dir);
|
|
15
|
-
}
|
|
16
|
-
throw new Error(`Could not locate ${SOURCE_FILENAME} in any parent of ${fileURLToPath(import.meta.url)}`);
|
|
17
|
-
}
|
|
18
|
-
function getDestinationPath(destinationDir) {
|
|
19
|
-
return path.join(destinationDir, DESTINATION_RELATIVE_PATH);
|
|
20
|
-
}
|
|
21
|
-
function currentSourceStamp() {
|
|
22
|
-
return `${PACKAGE_NAME}@${VERSION}`;
|
|
23
|
-
}
|
|
24
9
|
const FRONTMATTER_REGEX = /^---\n((?:.*\n)*?)---\n/;
|
|
25
|
-
function stripFrontmatter(content) {
|
|
26
|
-
return content.replace(FRONTMATTER_REGEX, "");
|
|
27
|
-
}
|
|
28
|
-
function parseSourceStamp(content) {
|
|
29
|
-
const frontmatterMatch = FRONTMATTER_REGEX.exec(content);
|
|
30
|
-
if (!frontmatterMatch) return null;
|
|
31
|
-
const frontmatterBody = frontmatterMatch[1] ?? "";
|
|
32
|
-
const sourceMatch = /^source:\s*['"]([^'"]+)['"]\s*$/m.exec(frontmatterBody);
|
|
33
|
-
return sourceMatch?.[1] ?? null;
|
|
34
|
-
}
|
|
35
|
-
function sync(destinationDir) {
|
|
36
|
-
const sourcePath = getSourcePath();
|
|
37
|
-
const sourceContent = readFileSync(sourcePath, "utf8");
|
|
38
|
-
const body = stripFrontmatter(sourceContent);
|
|
39
|
-
const stamp = currentSourceStamp();
|
|
40
|
-
const output = `---
|
|
41
|
-
source: '${stamp}'
|
|
42
|
-
---
|
|
43
|
-
${body}`;
|
|
44
|
-
const destinationPath = getDestinationPath(destinationDir);
|
|
45
|
-
mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
46
|
-
writeFileSync(destinationPath, output, "utf8");
|
|
47
|
-
return { written: destinationPath, stamp };
|
|
48
|
-
}
|
|
49
10
|
function check(destinationDir) {
|
|
50
11
|
const destinationPath = getDestinationPath(destinationDir);
|
|
51
|
-
const expected = currentSourceStamp();
|
|
52
12
|
if (!existsSync(destinationPath)) {
|
|
53
13
|
return {
|
|
54
14
|
ok: false,
|
|
55
15
|
reason: `${DESTINATION_RELATIVE_PATH} is missing. Run \`nmr sync-agent-files\`.`
|
|
56
16
|
};
|
|
57
17
|
}
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
18
|
+
const expectedBody = stripFrontmatter(readFileSync(getSourcePath(), "utf8"));
|
|
19
|
+
const foundBody = stripFrontmatter(readFileSync(destinationPath, "utf8"));
|
|
20
|
+
if (foundBody !== expectedBody) {
|
|
61
21
|
return {
|
|
62
22
|
ok: false,
|
|
63
|
-
reason:
|
|
23
|
+
reason: `${DESTINATION_RELATIVE_PATH} content is out of date. Run \`nmr sync-agent-files\`.`
|
|
64
24
|
};
|
|
65
25
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
26
|
+
return { ok: true };
|
|
27
|
+
}
|
|
28
|
+
function sync(destinationDir) {
|
|
29
|
+
const body = stripFrontmatter(readFileSync(getSourcePath(), "utf8"));
|
|
30
|
+
const packageSpecifier = buildPackageSpecifier();
|
|
31
|
+
const destinationPath = getDestinationPath(destinationDir);
|
|
32
|
+
if (existsSync(destinationPath) && stripFrontmatter(readFileSync(destinationPath, "utf8")) === body) {
|
|
33
|
+
return { path: destinationPath, packageSpecifier, changed: false };
|
|
71
34
|
}
|
|
72
|
-
|
|
35
|
+
const output = `---
|
|
36
|
+
source: '${packageSpecifier}'
|
|
37
|
+
---
|
|
38
|
+
${body}`;
|
|
39
|
+
mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
40
|
+
writeFileSync(destinationPath, output, "utf8");
|
|
41
|
+
return { path: destinationPath, packageSpecifier, changed: true };
|
|
42
|
+
}
|
|
43
|
+
function buildPackageSpecifier() {
|
|
44
|
+
return `${PACKAGE_NAME}@${VERSION}`;
|
|
45
|
+
}
|
|
46
|
+
function getDestinationPath(destinationDir) {
|
|
47
|
+
return path.join(destinationDir, DESTINATION_RELATIVE_PATH);
|
|
48
|
+
}
|
|
49
|
+
function getSourcePath() {
|
|
50
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
51
|
+
while (dir !== path.dirname(dir)) {
|
|
52
|
+
const candidate = path.join(dir, SOURCE_FILENAME);
|
|
53
|
+
if (existsSync(candidate)) return candidate;
|
|
54
|
+
dir = path.dirname(dir);
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`Could not locate ${SOURCE_FILENAME} in any parent of ${fileURLToPath(import.meta.url)}`);
|
|
57
|
+
}
|
|
58
|
+
function stripFrontmatter(content) {
|
|
59
|
+
return content.replace(FRONTMATTER_REGEX, "");
|
|
73
60
|
}
|
|
74
61
|
export {
|
|
75
62
|
check,
|
|
76
|
-
parseSourceStamp,
|
|
77
63
|
sync
|
|
78
64
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { parseDocument } from "yaml";
|
|
4
|
+
import { CodeQualityPnpmWorkflowSchema, getPnpmVersionNodes } from "../helpers/code-quality-pnpm-action.js";
|
|
4
5
|
import { readPackageJson } from "../helpers/package-json.js";
|
|
5
|
-
import { readYamlFile } from "../helpers/yaml-utils.js";
|
|
6
6
|
const WORKFLOW_RELATIVE_PATH = ".github/workflows/code-quality.yaml";
|
|
7
7
|
function extractPnpmVersion(packageManager) {
|
|
8
8
|
if (!packageManager) {
|
|
@@ -23,18 +23,23 @@ packageManager field: ${pkg.packageManager ?? "(not set)"}`
|
|
|
23
23
|
}
|
|
24
24
|
console.info(`Package.json pnpm version: ${pnpmVersion}`);
|
|
25
25
|
const workflowPath = path.join(monorepoRoot, WORKFLOW_RELATIVE_PATH);
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
const doc = parseDocument(readFileSync(workflowPath, "utf8"));
|
|
27
|
+
const [parseError] = doc.errors;
|
|
28
|
+
if (parseError) {
|
|
29
|
+
throw new Error(`Failed to parse workflow file: ${workflowPath}
|
|
30
|
+
${parseError.message}`);
|
|
31
|
+
}
|
|
32
|
+
CodeQualityPnpmWorkflowSchema.parse(doc.toJS());
|
|
33
|
+
const outdatedNodes = getPnpmVersionNodes(doc).filter((node) => String(node.value) !== pnpmVersion);
|
|
34
|
+
if (outdatedNodes.length === 0) {
|
|
31
35
|
console.info("Workflow pnpm version is already up to date");
|
|
32
36
|
return;
|
|
33
37
|
}
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
for (const node of outdatedNodes) {
|
|
39
|
+
node.value = pnpmVersion;
|
|
40
|
+
}
|
|
41
|
+
writeFileSync(workflowPath, doc.toString(), "utf8");
|
|
42
|
+
console.info(`\u2713 Updated ${outdatedNodes.length} pnpm-version occurrence(s) \u2192 ${pnpmVersion}`);
|
|
38
43
|
}
|
|
39
44
|
export {
|
|
40
45
|
extractPnpmVersion,
|
|
@@ -3,7 +3,7 @@ const commonWorkspaceScripts = {
|
|
|
3
3
|
check: ["typecheck", "fmt:check", "lint:check", "test"],
|
|
4
4
|
"check:strict": ["typecheck", "fmt:check", "lint:strict", "test:coverage"],
|
|
5
5
|
clean: "pnpm exec rimraf dist/*",
|
|
6
|
-
compile: "
|
|
6
|
+
compile: "nmr-compile",
|
|
7
7
|
fix: ["lint", "fmt"],
|
|
8
8
|
"fix:check": ["fmt:check", "lint:check"],
|
|
9
9
|
fmt: "prettier --list-different --write .",
|
|
@@ -17,6 +17,7 @@ const commonWorkspaceScripts = {
|
|
|
17
17
|
};
|
|
18
18
|
const integrationTestScripts = {
|
|
19
19
|
test: "pnpm exec vitest --config=vitest.standalone.config.ts",
|
|
20
|
+
"test:all": "pnpm exec vitest",
|
|
20
21
|
"test:coverage": "pnpm exec vitest --config=vitest.standalone.config.ts --coverage",
|
|
21
22
|
"test:integration": "pnpm exec vitest --config=vitest.integration.config.ts",
|
|
22
23
|
"test:watch": "pnpm exec vitest --config=vitest.standalone.config.ts --watch"
|
package/dist/esm/help.js
CHANGED
|
@@ -16,7 +16,6 @@ function generateHelp(config, packageDir, useRoot) {
|
|
|
16
16
|
" -w, --workspace-root Run root command regardless of cwd",
|
|
17
17
|
" -q, --quiet Suppress output on success; show full output on failure",
|
|
18
18
|
" -?, --help Show this help",
|
|
19
|
-
" --int-test Use integration test scripts",
|
|
20
19
|
"",
|
|
21
20
|
"Workspace commands:"
|
|
22
21
|
];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Document, type Scalar } from 'yaml';
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
export declare const CodeQualityPnpmWorkflowSchema: z.ZodObject<{
|
|
3
4
|
jobs: z.ZodObject<{
|
|
@@ -9,4 +10,4 @@ export declare const CodeQualityPnpmWorkflowSchema: z.ZodObject<{
|
|
|
9
10
|
}, z.core.$loose>;
|
|
10
11
|
}, z.core.$loose>;
|
|
11
12
|
export type CodeQualityPnpmWorkflow = z.infer<typeof CodeQualityPnpmWorkflowSchema>;
|
|
12
|
-
export declare function
|
|
13
|
+
export declare function getPnpmVersionNodes(doc: Document): Scalar[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isScalar, visit } from "yaml";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
const CodeQualityPnpmWorkflowSchema = z.looseObject({
|
|
3
4
|
jobs: z.looseObject({
|
|
@@ -8,10 +9,18 @@ const CodeQualityPnpmWorkflowSchema = z.looseObject({
|
|
|
8
9
|
})
|
|
9
10
|
})
|
|
10
11
|
});
|
|
11
|
-
function
|
|
12
|
-
|
|
12
|
+
function getPnpmVersionNodes(doc) {
|
|
13
|
+
const nodes = [];
|
|
14
|
+
visit(doc, {
|
|
15
|
+
Pair(_, pair) {
|
|
16
|
+
if (isScalar(pair.key) && pair.key.value === "pnpm-version" && isScalar(pair.value)) {
|
|
17
|
+
nodes.push(pair.value);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
return nodes;
|
|
13
22
|
}
|
|
14
23
|
export {
|
|
15
24
|
CodeQualityPnpmWorkflowSchema,
|
|
16
|
-
|
|
25
|
+
getPnpmVersionNodes
|
|
17
26
|
};
|
package/dist/esm/resolver.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface ResolvedScript {
|
|
|
8
8
|
export declare function expandScript(script: ScriptValue, workspaceRoot: boolean): string;
|
|
9
9
|
export declare function describeScript(script: ScriptValue): string;
|
|
10
10
|
export declare function readPackageJsonScripts(packageDir: string): Record<string, string> | undefined;
|
|
11
|
+
export declare function hasIntegrationTestConfig(packageDir: string): boolean;
|
|
11
12
|
export declare function buildWorkspaceRegistry(config: NmrConfig, useIntTests: boolean): ScriptRegistry;
|
|
12
13
|
export declare function buildRootRegistry(config: NmrConfig): ScriptRegistry;
|
|
13
14
|
export declare function isSelfReferential(script: string, commandName: string): boolean;
|
package/dist/esm/resolver.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { isObject } from "./helpers/type-guards.js";
|
|
4
4
|
import { getDefaultRootScripts, getDefaultWorkspaceScripts } from "./resolve-scripts.js";
|
|
@@ -54,6 +54,9 @@ function readPackageJsonScripts(packageDir) {
|
|
|
54
54
|
throw error;
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
+
function hasIntegrationTestConfig(packageDir) {
|
|
58
|
+
return existsSync(path.join(packageDir, "vitest.integration.config.ts"));
|
|
59
|
+
}
|
|
57
60
|
function buildWorkspaceRegistry(config, useIntTests) {
|
|
58
61
|
return {
|
|
59
62
|
...getDefaultWorkspaceScripts(useIntTests),
|
|
@@ -92,6 +95,7 @@ export {
|
|
|
92
95
|
buildWorkspaceRegistry,
|
|
93
96
|
describeScript,
|
|
94
97
|
expandScript,
|
|
98
|
+
hasIntegrationTestConfig,
|
|
95
99
|
isSelfReferential,
|
|
96
100
|
readPackageJsonScripts,
|
|
97
101
|
resolveScript
|
package/dist/esm/runCli.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { readPackageVersion } from "@williamthorsen/nmr-core";
|
|
2
|
+
import { readPackageVersion, reportError } from "@williamthorsen/nmr-core";
|
|
3
3
|
import { resolveContext } from "./context.js";
|
|
4
4
|
import { generateHelp } from "./help.js";
|
|
5
5
|
import { isHookName } from "./helpers/hook-name.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
applyDevBin,
|
|
8
|
+
buildRootRegistry,
|
|
9
|
+
buildWorkspaceRegistry,
|
|
10
|
+
hasIntegrationTestConfig,
|
|
11
|
+
resolveScript
|
|
12
|
+
} from "./resolver.js";
|
|
7
13
|
import { runCommand } from "./runner.js";
|
|
8
14
|
const VERSION = readPackageVersion(import.meta.url);
|
|
9
15
|
async function runCli(options) {
|
|
10
16
|
const { args, cwd, env, stdout, stderr } = options;
|
|
11
17
|
const parseResult = parseArgs(args);
|
|
12
18
|
if (!parseResult.ok) {
|
|
13
|
-
|
|
14
|
-
`);
|
|
19
|
+
reportError(parseResult.error, stderr);
|
|
15
20
|
return { exitCode: 1 };
|
|
16
21
|
}
|
|
17
22
|
const { parsed } = parseResult;
|
|
@@ -42,14 +47,13 @@ async function runCli(options) {
|
|
|
42
47
|
const exitCode2 = runCommand(delegateCmd, context.monorepoRoot, { ...runOptions, env: childEnv });
|
|
43
48
|
return { exitCode: exitCode2 };
|
|
44
49
|
}
|
|
45
|
-
const registry = useRoot ? buildRootRegistry(context.config) : buildWorkspaceRegistry(context.config,
|
|
50
|
+
const registry = useRoot ? buildRootRegistry(context.config) : buildWorkspaceRegistry(context.config, hasIntegrationTestConfig(packageDir));
|
|
46
51
|
const resolved = resolveScript(command, registry, packageDir, parsed.workspaceRoot);
|
|
47
52
|
if (!resolved) {
|
|
48
53
|
if (env.NMR_RUN_IF_PRESENT === "1") {
|
|
49
54
|
return { exitCode: 0 };
|
|
50
55
|
}
|
|
51
|
-
|
|
52
|
-
`);
|
|
56
|
+
reportError(`Unknown command: ${command}`, stderr);
|
|
53
57
|
return { exitCode: 1 };
|
|
54
58
|
}
|
|
55
59
|
const skipExitCode = handleSkipMessage(resolved.command, packageDir, parsed.quiet, stdout);
|
|
@@ -74,7 +78,6 @@ function parseArgs(args) {
|
|
|
74
78
|
workspaceRoot: false,
|
|
75
79
|
help: false,
|
|
76
80
|
version: false,
|
|
77
|
-
intTest: false,
|
|
78
81
|
passthrough: []
|
|
79
82
|
};
|
|
80
83
|
let i = 0;
|
|
@@ -85,7 +88,7 @@ function parseArgs(args) {
|
|
|
85
88
|
i++;
|
|
86
89
|
const filterValue = args[i];
|
|
87
90
|
if (filterValue === void 0) {
|
|
88
|
-
return { ok: false, error: "
|
|
91
|
+
return { ok: false, error: "-F/--filter requires a pattern argument" };
|
|
89
92
|
}
|
|
90
93
|
parsed.filter = filterValue;
|
|
91
94
|
i++;
|
|
@@ -116,11 +119,6 @@ function parseArgs(args) {
|
|
|
116
119
|
i++;
|
|
117
120
|
continue;
|
|
118
121
|
}
|
|
119
|
-
if (arg === "--int-test") {
|
|
120
|
-
parsed.intTest = true;
|
|
121
|
-
i++;
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
122
|
parsed.command = arg;
|
|
125
123
|
parsed.passthrough = args.slice(i + 1);
|
|
126
124
|
break;
|
package/dist/esm/runner.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import process from "node:process";
|
|
3
|
+
import { reportError } from "@williamthorsen/nmr-core";
|
|
3
4
|
function runCommand(command, cwd, options) {
|
|
4
5
|
const quiet = options?.quiet === true;
|
|
5
6
|
const stdout = options?.stdout ?? process.stdout;
|
|
@@ -14,8 +15,7 @@ function runCommand(command, cwd, options) {
|
|
|
14
15
|
env
|
|
15
16
|
});
|
|
16
17
|
if (result.error) {
|
|
17
|
-
|
|
18
|
-
`);
|
|
18
|
+
reportError(result.error.message, stderr);
|
|
19
19
|
return 1;
|
|
20
20
|
}
|
|
21
21
|
if (quiet) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/nmr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Context-aware script runner for PNPM monorepos",
|
|
6
6
|
"keywords": [
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"bin": {
|
|
30
30
|
"ensure-prepublish-hooks": "bin/ensure-prepublish-hooks.js",
|
|
31
31
|
"nmr": "bin/nmr.js",
|
|
32
|
+
"nmr-compile": "bin/nmr-compile.js",
|
|
32
33
|
"nmr-report-overrides": "bin/nmr-report-overrides.js",
|
|
33
34
|
"nmr-sync-agent-files": "bin/nmr-sync-agent-files.js",
|
|
34
35
|
"nmr-sync-pnpm-version": "bin/nmr-sync-pnpm-version.js"
|
|
@@ -39,10 +40,15 @@
|
|
|
39
40
|
"dist"
|
|
40
41
|
],
|
|
41
42
|
"dependencies": {
|
|
43
|
+
"esbuild": "0.28.1",
|
|
44
|
+
"glob": "13.0.6",
|
|
42
45
|
"jiti": "2.7.0",
|
|
43
46
|
"yaml": "2.9.0",
|
|
44
47
|
"zod": "4.4.3",
|
|
45
|
-
"@williamthorsen/nmr-core": "0.
|
|
48
|
+
"@williamthorsen/nmr-core": "0.5.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@williamthorsen/toolbelt.strings": "3.1.4"
|
|
46
52
|
},
|
|
47
53
|
"publishConfig": {
|
|
48
54
|
"access": "public"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function readYamlFile(filepath: string): unknown;
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { parse } from "yaml";
|
|
3
|
-
function readYamlFile(filepath) {
|
|
4
|
-
try {
|
|
5
|
-
const content = readFileSync(filepath, "utf8");
|
|
6
|
-
return parse(content);
|
|
7
|
-
} catch (error) {
|
|
8
|
-
throw new Error(`Failed to read YAML file: ${filepath}
|
|
9
|
-
${error instanceof Error ? error.message : String(error)}`);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
export {
|
|
13
|
-
readYamlFile
|
|
14
|
-
};
|