@williamthorsen/nmr 2.0.0 โ†’ 2.0.1

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.
@@ -1,4 +1,78 @@
1
1
  [
2
+ {
3
+ "version": "2.0.1",
4
+ "date": "2026-11-11",
5
+ "sections": [
6
+ {
7
+ "title": "๐Ÿ› Bug fixes",
8
+ "audience": "all",
9
+ "items": [
10
+ {
11
+ "description": "Fixes the issue that `nmr-clean`, `nmr-compile`, `nmr-report-catalog`, and `nmr-report-overrides` ignored their arguments, so `--help` or a mistyped option ran the command, and `nmr-clean --help` removed build output instead of printing usage. (#1273)",
12
+ "hash": "a4bfd2bc9c298a919c15070bae4437188c0edb37",
13
+ "entry": 1
14
+ }
15
+ ]
16
+ },
17
+ {
18
+ "title": "โ™ป๏ธ Refactoring",
19
+ "audience": "dev",
20
+ "items": [
21
+ {
22
+ "description": "Adds an optional `spawn` adapter to `runPrettier` in `packages/nmr/src/commands/fmt.ts`, which defaults to `spawnSync` with inherited stdio, so that a test can replace the Prettier process without mocking `node:child_process`. (#1271)",
23
+ "hash": "18227bf9c7d8fb0fb11bf3382602be131603419a",
24
+ "entry": 1
25
+ },
26
+ {
27
+ "description": "Extracts the unchanged-build skip report from `detectBuildChanges` into the exported `reportUnchangedBuild`, which takes the environment as a parameter rather than reading `process.env`. (#1272)",
28
+ "hash": "7708c82fe5b528709dd3c822c1449b6a843292c4",
29
+ "entry": 1
30
+ },
31
+ {
32
+ "description": "Replaces the copies of the entry-point `glob` call in `buildPackage` and `hasBuildOutput` with one function, `resolveEntryPoints` in `build-output.ts`, so the build and the check cache's probe for build output cannot select different files. (#1274)",
33
+ "hash": "aadd160714f8a4b00aa9965897b97fb15ee8500b",
34
+ "entry": 1
35
+ },
36
+ {
37
+ "description": "Moves the printing of the build's progress and outcome lines from `buildPackage` to `nmr-compile`, which renders the `BuildOutcome` returned by `buildPackage` and the events from its new `onProgress` callback through `build-report.ts`. (#1277)",
38
+ "hash": "7204e9ebbb7ef2d60f0ddf74a1ea4de4b62e50c0",
39
+ "entry": 1
40
+ },
41
+ {
42
+ "description": "Moves the emit's staging, swap by rename, and scratch-directory handling from `build.ts` and `build-output.ts` into `publish-output.ts`, which `build.ts` now calls with the emit directory. (#1278)",
43
+ "hash": "27bc6915156ae739206679b977baf91267f73fa9",
44
+ "entry": 1
45
+ },
46
+ {
47
+ "description": "Makes `cleanFrom` in `clean.ts` return a `CleanOutcome` and report each completed step through an `onProgress` callback rather than print, leaving `runClean` as the only function that prints, through the renderers in the new `clean-report.ts`. (#1279)",
48
+ "hash": "5188dd2411cd737d337ae8bf232fc68d5bc911c6",
49
+ "entry": 1
50
+ }
51
+ ]
52
+ },
53
+ {
54
+ "title": "๐Ÿงช Tests",
55
+ "audience": "dev",
56
+ "items": [
57
+ {
58
+ "description": "Moves the `runPrettier` cases from `fmt.tool.test.ts` to a new `fmt.unit.test.ts` that asserts on the invocations recorded by a spawn fake, and adds cases for a spawn error and for a Prettier process killed by a signal. (#1271)",
59
+ "hash": "18227bf9c7d8fb0fb11bf3382602be131603419a",
60
+ "entry": 2
61
+ },
62
+ {
63
+ "description": "Replaces six skip-report cases in `build.publication.tool.test.ts`, each of which compiled a package to seed the cache, with unit tests of `reportUnchangedBuild` in `build.unit.test.ts` that write the recorded emit duration directly. (#1272)",
64
+ "hash": "7708c82fe5b528709dd3c822c1449b6a843292c4",
65
+ "entry": 2
66
+ },
67
+ {
68
+ "description": "Tests `resolveEntryPoints` directly in `build-output.unit.test.ts` and removes the tool-tier tests in `build.tool.test.ts` that checked the same default exclusions, `ignorePatterns` override, and `extraIgnorePatterns` composition through a full build. (#1274)",
69
+ "hash": "aadd160714f8a4b00aa9965897b97fb15ee8500b",
70
+ "entry": 2
71
+ }
72
+ ]
73
+ }
74
+ ]
75
+ },
2
76
  {
3
77
  "version": "2.0.0",
4
78
  "date": "2026-11-10",
@@ -1,6 +1,6 @@
1
1
  /** @noformat -- @generated. Do not edit. Compiled by rdy. */
2
2
  /* eslint-disable */
3
- export const __readyupVersion = "0.45.0";
3
+ export const __readyupVersion = "0.45.1";
4
4
  import { createRequire as __rdyCreateRequire } from 'node:module';
5
5
  const require = __rdyCreateRequire(import.meta.url);
6
6
 
@@ -7382,12 +7382,12 @@ import { isRecord, listTrackedFiles } from "readyup/check-utils";
7382
7382
  import { globSync } from "node:fs";
7383
7383
  import { basename, join, sep } from "node:path";
7384
7384
 
7385
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.errors@0.9.0/node_modules/@williamthorsen/toolbelt.errors/dist/esm/4-release/isError.js
7385
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.errors@0.9.1/node_modules/@williamthorsen/toolbelt.errors/dist/esm/4-release/isError.js
7386
7386
  function isError(error) {
7387
7387
  return error instanceof Error || Object.prototype.toString.call(error) === "[object Error]";
7388
7388
  }
7389
7389
 
7390
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.errors@0.9.0/node_modules/@williamthorsen/toolbelt.errors/dist/esm/4-release/describeError.js
7390
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.errors@0.9.1/node_modules/@williamthorsen/toolbelt.errors/dist/esm/4-release/describeError.js
7391
7391
  function describeError(error) {
7392
7392
  try {
7393
7393
  if (isError(error) && typeof error.message === "string" && error.message !== "") {
@@ -8162,11 +8162,11 @@ function formatMalformedScript(dir, key, value) {
8162
8162
  return `${rejection} A step list belongs in \`${CONFIG_RELATIVE_PATH}\` under \`${field}\`.`;
8163
8163
  }
8164
8164
 
8165
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/parseArgs.js
8165
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/parseArgs.js
8166
8166
  import path6 from "node:path";
8167
8167
  import process2 from "node:process";
8168
8168
 
8169
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/UsageError.js
8169
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/UsageError.js
8170
8170
  var UsageError = class extends Error {
8171
8171
  constructor(message, options) {
8172
8172
  super(message, options);
@@ -8174,7 +8174,7 @@ var UsageError = class extends Error {
8174
8174
  }
8175
8175
  };
8176
8176
 
8177
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/ParseError.js
8177
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/ParseError.js
8178
8178
  var ParseError = class extends UsageError {
8179
8179
  kind;
8180
8180
  token;
@@ -8186,12 +8186,12 @@ var ParseError = class extends UsageError {
8186
8186
  }
8187
8187
  };
8188
8188
 
8189
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/resolveLongName.js
8189
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/resolveLongName.js
8190
8190
  function resolveLongName(key, definition) {
8191
8191
  return definition.long ?? key.replaceAll(/[A-Z]/g, (capital) => `-${capital.toLowerCase()}`);
8192
8192
  }
8193
8193
 
8194
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/tokenizeArgs.js
8194
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/tokenizeArgs.js
8195
8195
  import { parseArgs as nodeParseArgs } from "node:util";
8196
8196
  function tokenizeArgs(argv, schema, extraBooleans = []) {
8197
8197
  const nodeOptions = {};
@@ -8221,7 +8221,7 @@ function findFlagEntry(flagsByName, token) {
8221
8221
  return entry.definition.short === token.rawName.slice(1) ? entry : void 0;
8222
8222
  }
8223
8223
 
8224
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/validateSpec.js
8224
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/validateSpec.js
8225
8225
  var CAMEL_CASE_KEY = /^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]+)*$/;
8226
8226
  function validateSpec(spec) {
8227
8227
  validateFlags(spec.flags ?? {});
@@ -8302,7 +8302,7 @@ function validateOperands(operands) {
8302
8302
  }
8303
8303
  }
8304
8304
 
8305
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/parseArgs.js
8305
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/parseArgs.js
8306
8306
  function parseArgs(argv, spec, options = {}) {
8307
8307
  validateSpec(spec);
8308
8308
  return parseValidatedArgs(argv, spec, options);
@@ -9595,7 +9595,7 @@ function hasSupportedStrictLintVersion() {
9595
9595
  });
9596
9596
  }
9597
9597
  function getMinVersion() {
9598
- const pickedFields = { "version": "2.0.0" };
9598
+ const pickedFields = { "version": "2.0.1" };
9599
9599
  if (typeof pickedFields["version"] !== "string") {
9600
9600
  throw new TypeError("nmr/package.json: 'version' must be a string");
9601
9601
  }
@@ -3,19 +3,19 @@
3
3
  "kits": [
4
4
  {
5
5
  "bundledDependencies": {
6
- "@williamthorsen/toolbelt.arrays": "6.4.1",
7
- "@williamthorsen/toolbelt.cli": "0.8.0",
8
- "@williamthorsen/toolbelt.datetime": "4.1.1",
9
- "@williamthorsen/toolbelt.errors": "0.9.0",
10
- "@williamthorsen/toolbelt.filesystem": "0.10.0",
11
- "@williamthorsen/toolbelt.guards": "4.2.0",
12
- "@williamthorsen/toolbelt.numbers": "7.3.1",
13
- "@williamthorsen/toolbelt.objects": "4.4.0",
14
- "@williamthorsen/toolbelt.packaging": "0.7.0",
6
+ "@williamthorsen/toolbelt.arrays": "6.4.2",
7
+ "@williamthorsen/toolbelt.cli": "0.8.1",
8
+ "@williamthorsen/toolbelt.datetime": "4.1.2",
9
+ "@williamthorsen/toolbelt.errors": "0.9.1",
10
+ "@williamthorsen/toolbelt.filesystem": "0.10.1",
11
+ "@williamthorsen/toolbelt.guards": "4.2.1",
12
+ "@williamthorsen/toolbelt.numbers": "7.3.2",
13
+ "@williamthorsen/toolbelt.objects": "4.4.1",
14
+ "@williamthorsen/toolbelt.packaging": "0.7.1",
15
15
  "@williamthorsen/toolbelt.sets": "3.1.0",
16
- "@williamthorsen/toolbelt.strings": "8.2.1",
16
+ "@williamthorsen/toolbelt.strings": "8.2.2",
17
17
  "@williamthorsen/toolbelt.terminal": "0.3.0",
18
- "ansi-regex": "6.5.0",
18
+ "ansi-regex": "6.6.0",
19
19
  "ansi-styles": "6.2.3",
20
20
  "cli-truncate": "6.1.1",
21
21
  "diff": "9.0.0",
@@ -32,7 +32,7 @@
32
32
  "checklist": "nmr",
33
33
  "checks": [
34
34
  "@williamthorsen/nmr in devDependencies",
35
- "@williamthorsen/nmr >= 2.0.0",
35
+ "@williamthorsen/nmr >= 2.0.1",
36
36
  "pnpm-workspace.yaml exists",
37
37
  "catalogued dependencies use catalog:",
38
38
  "package.json has packageManager field",
@@ -236,7 +236,7 @@
236
236
  "path": "lib/workspace-build.ts"
237
237
  },
238
238
  {
239
- "hash": "6933f29e",
239
+ "hash": "c0b5a504",
240
240
  "kind": "inline",
241
241
  "path": "../package.json",
242
242
  "paths": [
@@ -356,10 +356,10 @@
356
356
  ],
357
357
  "name": "default",
358
358
  "path": "kits/default.js",
359
- "readyupVersion": "0.45.0",
359
+ "readyupVersion": "0.45.1",
360
360
  "source": "kits/default.ts",
361
361
  "sourceHash": "9fd61bf4",
362
- "targetHash": "7ddb0518"
362
+ "targetHash": "8eca943d"
363
363
  }
364
364
  ]
365
365
  }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 2.0.1 โ€” 2026-11-11
6
+
7
+ ### ๐Ÿ› Bug fixes
8
+
9
+ - Fixes the issue that `nmr-clean`, `nmr-compile`, `nmr-report-catalog`, and `nmr-report-overrides` ignored their arguments, so `--help` or a mistyped option ran the command, and `nmr-clean --help` removed build output instead of printing usage. (#1273)
10
+
11
+ ### โ™ป๏ธ Refactoring
12
+
13
+ - Adds an optional `spawn` adapter to `runPrettier` in `packages/nmr/src/commands/fmt.ts`, which defaults to `spawnSync` with inherited stdio, so that a test can replace the Prettier process without mocking `node:child_process`. (#1271)
14
+ - Extracts the unchanged-build skip report from `detectBuildChanges` into the exported `reportUnchangedBuild`, which takes the environment as a parameter rather than reading `process.env`. (#1272)
15
+ - Replaces the copies of the entry-point `glob` call in `buildPackage` and `hasBuildOutput` with one function, `resolveEntryPoints` in `build-output.ts`, so the build and the check cache's probe for build output cannot select different files. (#1274)
16
+ - Moves the printing of the build's progress and outcome lines from `buildPackage` to `nmr-compile`, which renders the `BuildOutcome` returned by `buildPackage` and the events from its new `onProgress` callback through `build-report.ts`. (#1277)
17
+ - Moves the emit's staging, swap by rename, and scratch-directory handling from `build.ts` and `build-output.ts` into `publish-output.ts`, which `build.ts` now calls with the emit directory. (#1278)
18
+ - Makes `cleanFrom` in `clean.ts` return a `CleanOutcome` and report each completed step through an `onProgress` callback rather than print, leaving `runClean` as the only function that prints, through the renderers in the new `clean-report.ts`. (#1279)
19
+
20
+ ### ๐Ÿงช Tests
21
+
22
+ - Moves the `runPrettier` cases from `fmt.tool.test.ts` to a new `fmt.unit.test.ts` that asserts on the invocations recorded by a spawn fake, and adds cases for a spawn error and for a Prettier process killed by a signal. (#1271)
23
+ - Replaces six skip-report cases in `build.publication.tool.test.ts`, each of which compiled a package to seed the cache, with unit tests of `reportUnchangedBuild` in `build.unit.test.ts` that write the recorded emit duration directly. (#1272)
24
+ - Tests `resolveEntryPoints` directly in `build-output.unit.test.ts` and removes the tool-tier tests in `build.tool.test.ts` that checked the same default exclusions, `ignorePatterns` override, and `extraIgnorePatterns` composition through a full build. (#1274)
25
+
5
26
  ## 2.0.0 โ€” 2026-11-10
6
27
 
7
28
  ### ๐Ÿชฆ Removed
package/README.md CHANGED
@@ -5,11 +5,11 @@
5
5
  Context-aware script runner for pnpm monorepos. Includes an `nmr` (node-monorepo run) binary that provides centralized, consistent script execution across workspace packages and the monorepo root.
6
6
 
7
7
  <!-- section:release-notes -->
8
- ## Release notes โ€” v2.0.0 (2026-11-10)
8
+ ## Release notes โ€” v2.0.1 (2026-11-11)
9
9
 
10
- ### ๐Ÿชฆ Removed
10
+ ### ๐Ÿ› Bug fixes
11
11
 
12
- - ๐Ÿšจ **Breaking:** Removes the `devBin` root-config field of `@williamthorsen/nmr`, which replaced the first token of a resolved command with a command that ran a bin from source. (#1269)
12
+ - Fixes the issue that `nmr-clean`, `nmr-compile`, `nmr-report-catalog`, and `nmr-report-overrides` ignored their arguments, so `--help` or a mistyped option ran the command, and `nmr-clean --help` removed build output instead of printing usage. (#1273)
13
13
  <!-- /section:release-notes -->
14
14
 
15
15
  ## Installation
@@ -1,16 +1,9 @@
1
+ import process from 'node:process';
1
2
  import { reportFailure } from '@williamthorsen/nmr-core';
2
3
  import { NMR_COMPILE_BIN } from "./bins.js";
3
- import { buildPackage } from "./commands/build.js";
4
- import { loadWorkspaceConfig } from "./config.js";
5
- import { resolveBinStyles } from "./resolveBinStyles.js";
4
+ import { runCompile } from "./commands/compile.js";
6
5
  try {
7
- const { stdout } = resolveBinStyles();
8
- const packageDir = process.cwd();
9
- const { build } = await loadWorkspaceConfig(packageDir);
10
- await buildPackage(packageDir, {
11
- style: stdout,
12
- ...(build?.extraIgnorePatterns !== undefined && { extraIgnorePatterns: build.extraIgnorePatterns }),
13
- });
6
+ process.exitCode = await runCompile(process.argv.slice(2), process.cwd());
14
7
  }
15
8
  catch (error) {
16
9
  reportFailure(NMR_COMPILE_BIN, error);
@@ -2,9 +2,8 @@ import process from 'node:process';
2
2
  import { reportFailure } from '@williamthorsen/nmr-core';
3
3
  import { NMR_CLEAN_BIN } from "./bins.js";
4
4
  import { runClean } from "./commands/clean.js";
5
- import { resolveBinStyles } from "./resolveBinStyles.js";
6
5
  try {
7
- await runClean(process.cwd(), resolveBinStyles().stdout);
6
+ process.exitCode = await runClean(process.argv.slice(2), process.cwd());
8
7
  }
9
8
  catch (error) {
10
9
  reportFailure(NMR_CLEAN_BIN, error);
@@ -1,10 +1,9 @@
1
1
  import process from 'node:process';
2
2
  import { reportFailure } from '@williamthorsen/nmr-core';
3
3
  import { NMR_REPORT_CATALOG_BIN } from "./bins.js";
4
- import { reportCatalog } from "./commands/report-catalog.js";
5
- import { resolveBinStyles } from "./resolveBinStyles.js";
4
+ import { runReportCatalog } from "./commands/report-catalog.js";
6
5
  try {
7
- reportCatalog(process.cwd(), resolveBinStyles().stderr);
6
+ process.exitCode = await runReportCatalog(process.argv.slice(2), process.cwd());
8
7
  }
9
8
  catch (error) {
10
9
  reportFailure(NMR_REPORT_CATALOG_BIN, error);
@@ -1,11 +1,9 @@
1
+ import process from 'node:process';
1
2
  import { reportFailure } from '@williamthorsen/nmr-core';
2
3
  import { NMR_REPORT_OVERRIDES_BIN } from "./bins.js";
3
- import { reportOverrides } from "./commands/report-overrides.js";
4
- import { resolveBinStyles } from "./resolveBinStyles.js";
5
- import { findMonorepoRoot } from "./workspace.js";
4
+ import { runReportOverrides } from "./commands/report-overrides.js";
6
5
  try {
7
- const monorepoRoot = findMonorepoRoot();
8
- reportOverrides(monorepoRoot, resolveBinStyles().stderr);
6
+ process.exitCode = await runReportOverrides(process.argv.slice(2), process.cwd());
9
7
  }
10
8
  catch (error) {
11
9
  reportFailure(NMR_REPORT_OVERRIDES_BIN, error);
@@ -6,28 +6,13 @@ export interface BuildOptions {
6
6
  ignorePatterns?: string[];
7
7
  outdir?: string;
8
8
  }
9
- /** The pair of directories through which a build publishes, both siblings of the emit directory. */
10
- export interface ScratchDirs {
11
- previousDir: string;
12
- stagingDir: string;
13
- }
14
- export declare const DEFAULT_ENTRY_GLOBS: string[];
15
- /**
16
- * Directories holding test scaffolding rather than published code, excluded from entry-point selection so that a
17
- * package does not publish its own helpers. The list differs from the vitest factory's `COVERAGE_EXCLUDE`, which
18
- * leaves `test-utils/` inside the coverage include set, and neither list can be derived from the other.
19
- *
20
- * Ignoring a file removes it as an entry point, not from the emit. The compiler still emits whatever the
21
- * surviving entry points import, which keeps a production module that uses a helper from emitting a dangling
22
- * specifier. Widening this list can therefore only drop files that production code does not import.
23
- */
24
- export declare const DEFAULT_IGNORE_PATTERNS: string[];
25
9
  export declare const DEFAULT_OUTDIR = "dist/esm/";
26
10
  /**
27
- * Reports whether the output that a build of `packageDir` would produce is currently on disk. Applies the build's
28
- * own rule, on the same options that the build was given, so a caller probing for output and the build deciding
29
- * whether to rebuild cannot disagree: A package whose entry points emit nothing does not expect any output, and
30
- * reports present. Passing options that the build was not given would make the two disagree.
11
+ * Reports whether the output that a build of `packageDir` would produce is currently on disk. Selects entry points
12
+ * through `resolveEntryPoints` and tests them through `hasExpectedBuildOutput`, as the build does, so a caller
13
+ * probing for output and the build deciding whether to rebuild cannot disagree: A package whose entry points emit
14
+ * nothing does not expect any output, and reports present. Passing options that the build was not given would make
15
+ * the two disagree.
31
16
  */
32
17
  export declare function hasBuildOutput(packageDir: string, options?: BuildOptions): Promise<boolean>;
33
18
  /**
@@ -65,18 +50,8 @@ export declare function resolveBuildCachePath(packageDir: string): string;
65
50
  * `readBuildDigest` returns.
66
51
  */
67
52
  export declare function resolveEmitDurationPath(packageDir: string): string;
68
- /**
69
- * Resolves the two scratch directories through which a build publishes: `staging`, which the emit is written to,
70
- * and `previous`, which the outgoing output is renamed aside to. Both are siblings of the emit directory, so a
71
- * rename between them never crosses a filesystem, and both are dot-prefixed so that a leftover stays out of the
72
- * globs that select sources.
73
- *
74
- * The names are fixed rather than unique because the build removes both before use. A unique name would need a
75
- * sweep to accomplish that removal, and a sweep cannot tell a directory orphaned by a killed run from one that a
76
- * concurrent build is still writing. Because a directory left behind by a failed build is cleared the same way,
77
- * the failure path does not need any cleanup of its own.
78
- */
79
- export declare function resolveScratchDirs(emitDir: string): ScratchDirs;
53
+ /** Selects the entry points that a build of `packageDir` compiles, as paths relative to `packageDir`. */
54
+ export declare function resolveEntryPoints(packageDir: string, options?: BuildOptions): Promise<string[]>;
80
55
  /**
81
56
  * Resolves the fingerprint of the nmr compiling `packageDir`, the `BuildToolchain` member that identifies nmr. It
82
57
  * is nmr's own build digest when one is on disk, which changes on a dev-loop edit that the version does not
@@ -3,17 +3,14 @@ import path from 'node:path';
3
3
  import { findPackageRoot, readCacheEntry, readGuardedCacheEntry, resolveCacheEntryPath, writeGuardedCacheEntry, } from '@williamthorsen/nmr-core';
4
4
  import { glob } from 'glob';
5
5
  import { readPackageJson } from "../helpers/package-json.js";
6
- export const DEFAULT_ENTRY_GLOBS = ['src/**/*.{ts,tsx}'];
7
- export const DEFAULT_IGNORE_PATTERNS = ['**/__fixtures__/**', '**/__mocks__/**', '**/__tests__/**', '**/test-utils/**'];
6
+ const DEFAULT_ENTRY_GLOBS = ['src/**/*.{ts,tsx}'];
7
+ const DEFAULT_IGNORE_PATTERNS = ['**/__fixtures__/**', '**/__mocks__/**', '**/__tests__/**', '**/test-utils/**'];
8
8
  export const DEFAULT_OUTDIR = 'dist/esm/';
9
9
  const BUILD_CACHE_TOOL = 'nmr-compile';
10
10
  const SELF_DIR = findPackageRoot(import.meta.url);
11
11
  export async function hasBuildOutput(packageDir, options = {}) {
12
12
  const outdir = options.outdir ?? DEFAULT_OUTDIR;
13
- const entryPoints = await glob(options.entryGlobs ?? DEFAULT_ENTRY_GLOBS, {
14
- cwd: packageDir,
15
- ignore: [...(options.ignorePatterns ?? DEFAULT_IGNORE_PATTERNS), ...(options.extraIgnorePatterns ?? [])],
16
- });
13
+ const entryPoints = await resolveEntryPoints(packageDir, options);
17
14
  return hasExpectedBuildOutput(packageDir, outdir, entryPoints);
18
15
  }
19
16
  export function hasExpectedBuildOutput(packageDir, outdir, entryPoints) {
@@ -49,13 +46,11 @@ export function resolveEmitDurationPath(packageDir) {
49
46
  extension: '.duration.json',
50
47
  });
51
48
  }
52
- export function resolveScratchDirs(emitDir) {
53
- const parent = path.dirname(emitDir);
54
- const name = path.basename(emitDir);
55
- return {
56
- previousDir: path.join(parent, `.${name}.previous`),
57
- stagingDir: path.join(parent, `.${name}.staging`),
58
- };
49
+ export async function resolveEntryPoints(packageDir, options = {}) {
50
+ return glob(options.entryGlobs ?? DEFAULT_ENTRY_GLOBS, {
51
+ cwd: packageDir,
52
+ ignore: [...(options.ignorePatterns ?? DEFAULT_IGNORE_PATTERNS), ...(options.extraIgnorePatterns ?? [])],
53
+ });
59
54
  }
60
55
  export async function resolveToolchainFingerprint(packageDir, selfDir = SELF_DIR) {
61
56
  if (!isSameDir(packageDir, selfDir)) {
@@ -0,0 +1,6 @@
1
+ import { type OutputStyle } from '@williamthorsen/nmr-core';
2
+ import type { BuildOutcome, BuildProgress } from './build.js';
3
+ /** Renders the line that closes a build: the skip, naming the saving when it is worth naming, or the emit. */
4
+ export declare function renderBuildOutcome(outcome: BuildOutcome, packageName: string, style: OutputStyle): string;
5
+ /** Renders the line that announces an emit, naming why the build is about to run. */
6
+ export declare function renderBuildProgress(progress: BuildProgress, packageName: string, style: OutputStyle): string;
@@ -0,0 +1,21 @@
1
+ import { formatGlyphLine, STATUS_GLYPHS } from '@williamthorsen/nmr-core';
2
+ import { NMR_GLYPHS } from "../glyphs.js";
3
+ import { formatSaving } from "../helpers/duration.js";
4
+ export function renderBuildOutcome(outcome, packageName, style) {
5
+ if (outcome.kind === 'unchanged') {
6
+ const saving = formatSaving(outcome.savedMs ?? 0);
7
+ const savingClause = saving === undefined ? '' : `, ${saving}`;
8
+ return `${STATUS_GLYPHS[style].skipped.text} ${packageName}: No changes detected. Skipping build${savingClause}.`;
9
+ }
10
+ return formatGlyphLine(NMR_GLYPHS, style, 'package', `${packageName}: ${describeEmit(outcome.fileCount, outcome.outdir)}`);
11
+ }
12
+ export function renderBuildProgress(progress, packageName, style) {
13
+ const message = progress.kind === 'output-missing' ? 'Build output is missing. Rebuilding.' : 'Changes detected.';
14
+ return formatGlyphLine(NMR_GLYPHS, style, 'package', `${packageName}: ${message}`);
15
+ }
16
+ function describeEmit(fileCount, outdir) {
17
+ if (fileCount === 0) {
18
+ return 'Did not emit any output.';
19
+ }
20
+ return `Compiled ${fileCount} files to ${outdir.replace(/\/+$/u, '')}.`;
21
+ }
@@ -1,6 +1,23 @@
1
- import { type OutputStyle } from '@williamthorsen/nmr-core';
2
1
  import type * as TypeScript from 'typescript';
3
2
  import { type BuildOptions } from './build-output.js';
3
+ /**
4
+ * How a build ended: skipped because its inputs and output were unchanged, with the emit time recorded for those
5
+ * inputs when one was; or emitted, with the count of files that it published to `outdir`.
6
+ */
7
+ export type BuildOutcome = {
8
+ kind: 'unchanged';
9
+ savedMs?: number;
10
+ } | {
11
+ kind: 'emitted';
12
+ fileCount: number;
13
+ outdir: string;
14
+ };
15
+ /** Why a build is about to emit: its inputs changed, or its inputs match the cache and its output is missing. */
16
+ export type BuildProgress = {
17
+ kind: 'inputs-changed';
18
+ } | {
19
+ kind: 'output-missing';
20
+ };
4
21
  /**
5
22
  * The toolchain from which a build's emit comes: the resolved compiler and the nmr running it. `computeBuildHash`
6
23
  * folds both into the build hash, because the same sources can compile to different output under a different
@@ -10,9 +27,10 @@ export interface BuildToolchain {
10
27
  compilerVersion: string;
11
28
  fingerprint: string;
12
29
  }
13
- /** What a build needs beyond its sources: the options shaping the emit, and the style in which it reports. */
30
+ /** What a build needs beyond its sources: the options shaping the emit, and a listener for its progress. */
14
31
  export interface BuildPackageOptions extends BuildOptions {
15
- style: OutputStyle;
32
+ /** Called before the emit begins, so that a caller can report the build ahead of a slow compile. */
33
+ onProgress?: (progress: BuildProgress) => void;
16
34
  /**
17
35
  * Overridable so that a missing `typescript` peer is reachable without uninstalling it.
18
36
  *
@@ -30,10 +48,15 @@ export interface BuildPackageOptions extends BuildOptions {
30
48
  * current inputs rather than an accumulation of every build that ever ran. Assets belong outside it, or in a
31
49
  * `build:post` hook, which runs after the output is published.
32
50
  */
33
- export declare function buildPackage(packageDir: string, options: BuildPackageOptions): Promise<void>;
51
+ export declare function buildPackage(packageDir: string, options?: BuildPackageOptions): Promise<BuildOutcome>;
34
52
  /**
35
53
  * Produces a digest of the given files (paths and contents), the emit config, and the toolchain.
36
54
  * The file list is sorted so that the digest is order-invariant, and each path is folded in so that renames are
37
55
  * detected.
38
56
  */
39
57
  export declare function computeBuildHash(packageDir: string, files: string[], emitConfig: object, toolchain: BuildToolchain): Promise<string>;
58
+ /**
59
+ * Records a build skipped on unchanged inputs: states the `build-unchanged` outcome with the emit time recorded under
60
+ * `digest`, and returns that time, which is absent when none was recorded.
61
+ */
62
+ export declare function recordUnchangedBuild(packageDir: string, digest: string, env: NodeJS.ProcessEnv): Promise<number | undefined>;