@williamthorsen/nmr 0.34.0 → 0.35.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/.readyup/kits/default.js +92 -19
- package/.readyup/manifest.json +11 -8
- package/README.md +83 -17
- package/agents/guidance/rulebooks/nmr.md +1 -1
- package/dist/esm/UserError.d.ts +6 -0
- package/dist/esm/check-cache.d.ts +145 -0
- package/dist/esm/commands/build-output.d.ts +64 -0
- package/dist/esm/commands/build.d.ts +29 -1
- package/dist/esm/commands/build.js +25 -5
- package/dist/esm/commands/clean.d.ts +15 -0
- package/dist/esm/commands/ensure-prepublish-hooks.d.ts +11 -0
- package/dist/esm/commands/fmt.d.ts +32 -0
- package/dist/esm/commands/report-catalog.d.ts +10 -0
- package/dist/esm/commands/report-overrides.d.ts +6 -0
- package/dist/esm/commands/taze.d.ts +18 -0
- package/dist/esm/config.d.ts +25 -0
- package/dist/esm/context.d.ts +11 -0
- package/dist/esm/default-scripts.d.ts +15 -0
- package/dist/esm/defineConfig.d.ts +9 -0
- package/dist/esm/help.d.ts +13 -0
- package/dist/esm/helpers/clampToBytes.d.ts +12 -0
- package/dist/esm/helpers/createBoundedBuffer.d.ts +7 -0
- package/dist/esm/helpers/deriveExcerpt.d.ts +10 -0
- package/dist/esm/helpers/duration.d.ts +15 -0
- package/dist/esm/helpers/hook-name.d.ts +8 -0
- package/dist/esm/helpers/package-json.d.ts +25 -0
- package/dist/esm/helpers/readStringValues.d.ts +7 -0
- package/dist/esm/helpers/reportClosing.d.ts +15 -0
- package/dist/esm/helpers/transcript.d.ts +25 -0
- package/dist/esm/helpers/type-guards.d.ts +1 -0
- package/dist/esm/helpers/workspace-patterns.d.ts +7 -0
- package/dist/esm/prettier.d.ts +8 -0
- package/dist/esm/recording.d.ts +39 -1
- package/dist/esm/replay-assembly.d.ts +18 -2
- package/dist/esm/report-format.d.ts +33 -0
- package/dist/esm/reportCliFailure.d.ts +8 -0
- package/dist/esm/resolve-scripts.d.ts +10 -1
- package/dist/esm/resolver.d.ts +67 -2
- package/dist/esm/resolver.js +1 -1
- package/dist/esm/runCli.d.ts +17 -0
- package/dist/esm/runCli.js +1 -1
- package/dist/esm/runner.d.ts +52 -0
- package/dist/esm/scripts.d.ts +1 -0
- package/dist/esm/steps.d.ts +71 -0
- package/dist/esm/taze.d.ts +16 -0
- package/dist/esm/tests.d.ts +40 -0
- package/dist/esm/tests.js +25 -0
- package/dist/esm/tiers.d.ts +56 -1
- package/dist/esm/tiers.js +22 -9
- package/dist/esm/types.d.ts +27 -0
- package/dist/esm/verbosity.d.ts +37 -0
- package/dist/esm/verdict.d.ts +45 -0
- package/dist/esm/vitest.d.ts +72 -2
- package/dist/esm/vitest.js +17 -9
- package/dist/esm/workspace.d.ts +18 -0
- package/package.json +18 -10
package/dist/esm/steps.d.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One element of a resolved command chain, carrying how it was composed rather than how its text reads.
|
|
3
|
+
* A `structural` step is nmr's own composition, held as argv; an `opaque` step is a command nmr does not parse.
|
|
4
|
+
*
|
|
5
|
+
* A structural step's argv leads with the file to spawn, so the runner has one to hand `spawn` without a shell.
|
|
6
|
+
*
|
|
7
|
+
* `declinesArgs` travels from the composite element that composed the step to the one reader that acts on it,
|
|
8
|
+
* the binding of the invocation's trailing arguments. Every stage between the two -- the devBin substitution,
|
|
9
|
+
* the chain rendering, the replay assembly, the runner -- passes it through and asks nothing of it.
|
|
10
|
+
*/
|
|
1
11
|
export type Step = {
|
|
2
12
|
kind: 'opaque';
|
|
3
13
|
command: string;
|
|
@@ -6,19 +16,80 @@ export type Step = {
|
|
|
6
16
|
argv: readonly [string, ...(readonly string[])];
|
|
7
17
|
declinesArgs?: boolean;
|
|
8
18
|
};
|
|
19
|
+
/** What a structural step asks of the nmr process it spawns. */
|
|
9
20
|
export interface NmrStepTarget {
|
|
10
21
|
command: string;
|
|
22
|
+
/** Whether the command is fanned out to other scopes, where a `-R` or `-F` sends it. */
|
|
11
23
|
isDelegate: boolean;
|
|
24
|
+
/** Whether the command runs against the root registry, where a `-w` anchors it. */
|
|
12
25
|
isWorkspaceRoot: boolean;
|
|
13
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* How a `package.json` entry names the command it is declared under: as its whole value, or alongside other
|
|
29
|
+
* steps. `sole` covers an entry carrying trailing arguments, which declare no step of their own.
|
|
30
|
+
*/
|
|
14
31
|
export type SelfReference = 'chained' | 'sole';
|
|
32
|
+
/**
|
|
33
|
+
* Composes the structural step that re-invokes nmr for one composite element.
|
|
34
|
+
*
|
|
35
|
+
* The element tokenizes on whitespace, so it may carry nmr's own flags but cannot carry a space-bearing token.
|
|
36
|
+
* `-w` is prepended as its own token, so the child selects the root registry on its own.
|
|
37
|
+
*
|
|
38
|
+
* `declinesArgs` is set only where it holds, so a step that takes the trailing arguments renders and compares
|
|
39
|
+
* exactly as it did before any element declared anything.
|
|
40
|
+
*/
|
|
15
41
|
export declare function composeNmrStep(element: string, workspaceRoot: boolean, declinesArgs?: boolean): Step;
|
|
42
|
+
/**
|
|
43
|
+
* Returns the text of the first opaque step that reaches nmr through a shell, or `undefined` when none does.
|
|
44
|
+
*
|
|
45
|
+
* Recognizes nmr in command position: at the start of the step, after `&&`, `||`, `;`, `|`, or a newline, past
|
|
46
|
+
* any leading environment assignments, and behind a launcher such as `npx` or `pnpm exec`. A separator inside
|
|
47
|
+
* quotes opens no position, so a command merely naming nmr in an argument is not a crossing.
|
|
48
|
+
*
|
|
49
|
+
* Partial by construction, and partial in stated ways rather than arbitrary ones: a value-taking flag standing
|
|
50
|
+
* immediately before the program name hides it (`npx -p foo nmr`), and a launcher outside `LAUNCHERS` goes
|
|
51
|
+
* unreported. What it finds is the boundary below which nmr cannot tell its own processes from the tools it
|
|
52
|
+
* runs.
|
|
53
|
+
*/
|
|
16
54
|
export declare function findNmrCrossing(steps: readonly Step[]): string | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Returns the first token of a composite element that falls outside the grammar, or `undefined` when the
|
|
57
|
+
* element is a command name optionally preceded by nmr's own flags. A token the shell would not read
|
|
58
|
+
* literally is one the rendering would quote whole, turning the element into a command nobody wrote.
|
|
59
|
+
*/
|
|
17
60
|
export declare function findUnexpressibleToken(element: string): string | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Returns the command a structural step's nmr process runs, whether the step hands that command to other
|
|
63
|
+
* scopes rather than running it where it stands, and whether it anchors the command at the monorepo root.
|
|
64
|
+
* Reports nothing for a step that names no nmr command.
|
|
65
|
+
*
|
|
66
|
+
* The inverse of `composeNmrStep`, and beside it because the two share one grammar: an element may lead with
|
|
67
|
+
* nmr's own flags, and the command is the first token that is not one.
|
|
68
|
+
*/
|
|
18
69
|
export declare function readNmrStep(step: Step): NmrStepTarget | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Reports whether a `package.json` entry re-invokes the command it is declared under, and whether it declares
|
|
72
|
+
* anything besides. Reports nothing for an entry that names another command, or none.
|
|
73
|
+
*
|
|
74
|
+
* Honouring such an entry would spawn a shell running the same command in the same directory, reaching the
|
|
75
|
+
* same entry again without bound, so resolution discards it however it reads. `chained` is what separates an
|
|
76
|
+
* entry that thereby loses steps from one that declares nothing to lose.
|
|
77
|
+
*
|
|
78
|
+
* A segment that delegates carries the command to other scopes rather than back to this one. `-w` re-enters
|
|
79
|
+
* only from the root, a package's entry reaching the root's registry and `package.json` instead of its own.
|
|
80
|
+
*
|
|
81
|
+
* Partial in the same ways `findNmrCrossing` is, and for the same reason: what goes unrecognized re-enters
|
|
82
|
+
* without bound, which hangs rather than passing quietly.
|
|
83
|
+
*/
|
|
19
84
|
export declare function readSelfReference(options: {
|
|
20
85
|
anchoredAtRoot: boolean;
|
|
21
86
|
commandName: string;
|
|
22
87
|
script: string;
|
|
23
88
|
}): SelfReference | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* Renders a step list as the `&&` chain a shell runs.
|
|
91
|
+
*
|
|
92
|
+
* The sole producer of a chain string: the check-result cache keys on that string, so a change to the rendering
|
|
93
|
+
* invalidates every recorded pass by construction rather than by anyone remembering to.
|
|
94
|
+
*/
|
|
24
95
|
export declare function renderChain(steps: readonly Step[]): string;
|
package/dist/esm/taze.d.ts
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import type { CheckOptions } from 'taze';
|
|
2
|
+
/**
|
|
3
|
+
* A taze configuration.
|
|
4
|
+
*
|
|
5
|
+
* Every property admits `undefined` explicitly, unlike `Partial<CheckOptions>`, which rejects it under
|
|
6
|
+
* `exactOptionalPropertyTypes`. Passing `undefined` is how a consumer clears `maturityPeriod`, so the type
|
|
7
|
+
* has to allow it.
|
|
8
|
+
*/
|
|
2
9
|
export type TazeConfig = {
|
|
3
10
|
[K in keyof CheckOptions]?: CheckOptions[K] | undefined;
|
|
4
11
|
};
|
|
12
|
+
/**
|
|
13
|
+
* Builds a taze configuration from nmr's shared upgrade policy and a repo's own settings.
|
|
14
|
+
*
|
|
15
|
+
* Any property the caller supplies wins, including `0` and an explicit `undefined` — the latter restores
|
|
16
|
+
* taze's inheritance of pnpm's `minimumReleaseAge`, which only applies while `maturityPeriod` is unset.
|
|
17
|
+
*
|
|
18
|
+
* The merge is shallow, which suits a policy of scalar defaults. A nested default would clobber rather
|
|
19
|
+
* than merge, so adding one means revisiting this.
|
|
20
|
+
*/
|
|
5
21
|
export declare function defineConfig(config: TazeConfig): TazeConfig;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declares a suite asserting that a repo's test files hold to nmr's conventions: every collected file names an
|
|
3
|
+
* isolation tier, and every test file sits under a `__tests__` directory.
|
|
4
|
+
*
|
|
5
|
+
* Wire it from a one-line test of the repo's own, so the file stays under the repo's `__tests__` and the repo
|
|
6
|
+
* scopes what the sweep covers. Both halves are silent without it: `unit` is the residual project, so an untiered
|
|
7
|
+
* file runs and passes, and a file outside `__tests__` is collected by nothing at all.
|
|
8
|
+
*
|
|
9
|
+
* `exclude` names directory basenames the sweep prunes at any depth, additive to the ones nmr always prunes. Pass
|
|
10
|
+
* the same array to `defineVitestConfig`'s `testCollectionExclude`, or the sweep and the collection glob describe
|
|
11
|
+
* different trees: a directory pruned here alone still runs the files this suite stopped reporting.
|
|
12
|
+
*/
|
|
13
|
+
export declare function checkTestFileConventions(options?: TestFileConventionsOptions): void;
|
|
14
|
+
/**
|
|
15
|
+
* Sweeps a repo for both halves of the convention, reporting the root it swept alongside what it found.
|
|
16
|
+
*
|
|
17
|
+
* The root is what makes the result trustworthy, and it is the one thing an assertion cannot check: a sweep
|
|
18
|
+
* started from the directory Vitest supplies covers one package, finds nothing, and passes.
|
|
19
|
+
*
|
|
20
|
+
* @internal - Exported only to enable testing
|
|
21
|
+
*/
|
|
22
|
+
export declare function reportTestFileConventions({ exclude, rootDir, }?: TestFileConventionsOptions): TestFileConventionsReport;
|
|
23
|
+
export interface TestFileConventionsOptions {
|
|
24
|
+
/**
|
|
25
|
+
* Directory basenames the sweep prunes at any depth, additive to the ones nmr always prunes. The array a repo
|
|
26
|
+
* passes here is the array it passes to `defineVitestConfig`'s `testCollectionExclude`.
|
|
27
|
+
*/
|
|
28
|
+
exclude?: readonly string[];
|
|
29
|
+
/**
|
|
30
|
+
* The directory to sweep. Defaults to the monorepo root found from the working directory, so one call covers the
|
|
31
|
+
* whole repo from whichever package Vitest started in.
|
|
32
|
+
*/
|
|
33
|
+
rootDir?: string;
|
|
34
|
+
}
|
|
35
|
+
/** What one sweep found, and the root it found it in. Paths are relative to that root and POSIX-separated. */
|
|
36
|
+
export interface TestFileConventionsReport {
|
|
37
|
+
misplaced: string[];
|
|
38
|
+
rootDir: string;
|
|
39
|
+
untiered: string[];
|
|
40
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { findMisplacedTestFiles, findUntieredTestFiles, TIER_NAMES } from "./tiers.js";
|
|
3
|
+
import { findMonorepoRoot } from "./workspace.js";
|
|
4
|
+
const MISPLACED_REMEDY = 'Every test file must sit under a `__tests__` directory, the only place the shared Vitest config collects from. Move each file below into one, or name its directory in `exclude`.';
|
|
5
|
+
const UNTIERED_REMEDY = `Every collected test file must name its isolation tier in the segment before \`.test.\`, one of: ${TIER_NAMES.join(', ')}. Rename each file below to <subject>[.<aspect>].<tier>.test.ts.`;
|
|
6
|
+
export function checkTestFileConventions(options = {}) {
|
|
7
|
+
describe('test file conventions', () => {
|
|
8
|
+
const { misplaced, rootDir, untiered } = reportTestFileConventions(options);
|
|
9
|
+
it('every collected test file names its tier', () => {
|
|
10
|
+
expect(untiered, `${UNTIERED_REMEDY} Swept from ${rootDir}.`).toStrictEqual([]);
|
|
11
|
+
});
|
|
12
|
+
it('every test file sits under a __tests__ directory', () => {
|
|
13
|
+
expect(misplaced, `${MISPLACED_REMEDY} Swept from ${rootDir}.`).toStrictEqual([]);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export function reportTestFileConventions({ exclude, rootDir, } = {}) {
|
|
18
|
+
const sweptRoot = rootDir ?? findMonorepoRoot();
|
|
19
|
+
const scanOptions = { ...(exclude !== undefined && { exclude }) };
|
|
20
|
+
return {
|
|
21
|
+
misplaced: findMisplacedTestFiles(sweptRoot, scanOptions),
|
|
22
|
+
rootDir: sweptRoot,
|
|
23
|
+
untiered: findUntieredTestFiles(sweptRoot, scanOptions),
|
|
24
|
+
};
|
|
25
|
+
}
|
package/dist/esm/tiers.d.ts
CHANGED
|
@@ -1,7 +1,62 @@
|
|
|
1
|
+
/** Collection patterns for the residual project, which claims every test file the named tiers leave. */
|
|
1
2
|
export declare const ALL_TEST_PATTERNS: string[];
|
|
3
|
+
/** Builds the collection patterns for one tier. */
|
|
2
4
|
export declare function buildTierPatterns(tier: string): string[];
|
|
3
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Walks `rootDir` for every `*.test.ts` or `*.test.tsx` file sitting outside a `__tests__` directory, returning
|
|
7
|
+
* paths relative to it, POSIX-separated and sorted.
|
|
8
|
+
*
|
|
9
|
+
* No project collects such a file, so it runs nowhere and reports nothing. Only a sweep of the tree tells it apart
|
|
10
|
+
* from a file that runs and passes.
|
|
11
|
+
*/
|
|
12
|
+
export declare function findMisplacedTestFiles(rootDir: string, options?: TestFileScanOptions): string[];
|
|
13
|
+
/**
|
|
14
|
+
* Walks `rootDir` for every test file the shared config's projects collect, returning paths relative to it,
|
|
15
|
+
* POSIX-separated and sorted.
|
|
16
|
+
*
|
|
17
|
+
* A walk, because `node:fs` `globSync` does not descend into a dot-directory and honours no option that would make
|
|
18
|
+
* it. Vitest's globber does, so the same pattern string means different things to the two: a check built on the glob
|
|
19
|
+
* would report clean over every test file under a dot-directory.
|
|
20
|
+
*/
|
|
21
|
+
export declare function findTestFiles(rootDir: string, options?: TestFileScanOptions): string[];
|
|
22
|
+
/**
|
|
23
|
+
* Walks `rootDir` for every collected test file whose name selects no tier, returning paths relative to it,
|
|
24
|
+
* POSIX-separated and sorted.
|
|
25
|
+
*
|
|
26
|
+
* `unit` is the residual project, so such a file runs under it and reports success: no test run tells it apart from
|
|
27
|
+
* a conformant file.
|
|
28
|
+
*/
|
|
29
|
+
export declare function findUntieredTestFiles(rootDir: string, options?: TestFileScanOptions): string[];
|
|
30
|
+
/**
|
|
31
|
+
* Reports whether a test file's name selects a tier.
|
|
32
|
+
*
|
|
33
|
+
* Only the dot-delimited segment immediately before `.test.` selects one, so any earlier segment is free-form
|
|
34
|
+
* documentation: `scaffold.packaged.unit.test.ts` names the `unit` tier.
|
|
35
|
+
*/
|
|
4
36
|
export declare function hasTierInfix(filePath: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Directory names that hold no test worth collecting: dependencies, build output, and generated reports.
|
|
39
|
+
*
|
|
40
|
+
* The one point where the collection glob and the walk must agree about scope. Over-reporting is a failure a
|
|
41
|
+
* consumer cannot fix; under-reporting is the silence a conformance check exists to end.
|
|
42
|
+
*/
|
|
5
43
|
export declare const TEST_COLLECTION_EXCLUDE: string[];
|
|
44
|
+
/** Options every sweep over a repo's test files takes. */
|
|
45
|
+
export interface TestFileScanOptions {
|
|
46
|
+
/**
|
|
47
|
+
* Directory basenames pruned at any depth, additive to `TEST_COLLECTION_EXCLUDE`.
|
|
48
|
+
*
|
|
49
|
+
* Basenames rather than globs, so the array a repo passes here is the array it passes to the shared Vitest
|
|
50
|
+
* config's `testCollectionExclude`. A directory pruned from the sweep but still collected by Vitest is the
|
|
51
|
+
* silence these sweeps exist to end.
|
|
52
|
+
*/
|
|
53
|
+
exclude?: readonly string[];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The isolation ladder, ordered by the furthest thing a test reaches. A tier names what a test reaches, never how it
|
|
57
|
+
* invokes it: a test driving a compiler through its JavaScript API is `tool`, exactly as one spawning `tsc` would be.
|
|
58
|
+
* Each named tier's name is also its filename infix, so `parse.tool.test.ts` lands in `tool`.
|
|
59
|
+
*/
|
|
6
60
|
export declare const TIER_NAMES: readonly ["unit", "tool", "localhost", "remote"];
|
|
61
|
+
/** One of the four isolation tiers. */
|
|
7
62
|
export type TierName = (typeof TIER_NAMES)[number];
|
package/dist/esm/tiers.js
CHANGED
|
@@ -8,10 +8,14 @@ export const ALL_TEST_PATTERNS = [`${TEST_GLOB_PREFIX}/*.test.${TEST_EXTENSIONS}
|
|
|
8
8
|
export function buildTierPatterns(tier) {
|
|
9
9
|
return [`${TEST_GLOB_PREFIX}/*.${tier}.test.${TEST_EXTENSIONS}`];
|
|
10
10
|
}
|
|
11
|
-
export function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
export function findMisplacedTestFiles(rootDir, options = {}) {
|
|
12
|
+
return walkTestFiles(rootDir, options, true);
|
|
13
|
+
}
|
|
14
|
+
export function findTestFiles(rootDir, options = {}) {
|
|
15
|
+
return walkTestFiles(rootDir, options, false);
|
|
16
|
+
}
|
|
17
|
+
export function findUntieredTestFiles(rootDir, options = {}) {
|
|
18
|
+
return findTestFiles(rootDir, options).filter((file) => !hasTierInfix(file));
|
|
15
19
|
}
|
|
16
20
|
export function hasTierInfix(filePath) {
|
|
17
21
|
const tiers = TIER_NAMES;
|
|
@@ -19,17 +23,26 @@ export function hasTierInfix(filePath) {
|
|
|
19
23
|
}
|
|
20
24
|
export const TEST_COLLECTION_EXCLUDE = ['.git', 'coverage', 'dist', 'node_modules'];
|
|
21
25
|
export const TIER_NAMES = ['unit', 'tool', 'localhost', 'remote'];
|
|
22
|
-
function collectTestFiles(dir, relativeDir, inTestDir,
|
|
26
|
+
function collectTestFiles(dir, relativeDir, inTestDir, context) {
|
|
23
27
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
24
28
|
for (const entry of entries) {
|
|
25
29
|
const relativePath = relativeDir === '' ? entry.name : `${relativeDir}/${entry.name}`;
|
|
26
30
|
if (entry.isDirectory()) {
|
|
27
|
-
if (
|
|
31
|
+
if (context.pruned.has(entry.name))
|
|
28
32
|
continue;
|
|
29
|
-
collectTestFiles(path.join(dir, entry.name), relativePath, inTestDir || entry.name === TEST_DIR,
|
|
33
|
+
collectTestFiles(path.join(dir, entry.name), relativePath, inTestDir || entry.name === TEST_DIR, context);
|
|
30
34
|
}
|
|
31
|
-
else if (inTestDir && TEST_FILE_PATTERN.test(entry.name)) {
|
|
32
|
-
found.push(relativePath);
|
|
35
|
+
else if (inTestDir !== context.misplaced && TEST_FILE_PATTERN.test(entry.name)) {
|
|
36
|
+
context.found.push(relativePath);
|
|
33
37
|
}
|
|
34
38
|
}
|
|
35
39
|
}
|
|
40
|
+
function walkTestFiles(rootDir, { exclude = [] }, misplaced) {
|
|
41
|
+
const context = {
|
|
42
|
+
found: [],
|
|
43
|
+
misplaced,
|
|
44
|
+
pruned: new Set([...TEST_COLLECTION_EXCLUDE, ...exclude]),
|
|
45
|
+
};
|
|
46
|
+
collectTestFiles(rootDir, '', false, context);
|
|
47
|
+
return context.found.toSorted();
|
|
48
|
+
}
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -2,16 +2,43 @@ import type { ScriptValue } from './default-scripts.js';
|
|
|
2
2
|
import type { CommandVerbosity } from './verbosity.js';
|
|
3
3
|
export type { ScriptValue, StepSpec } from './default-scripts.js';
|
|
4
4
|
export type { CommandVerbosity } from './verbosity.js';
|
|
5
|
+
/** Build settings honored by `nmr-compile`. */
|
|
5
6
|
export interface BuildConfig {
|
|
7
|
+
/**
|
|
8
|
+
* Patterns added to the build's default ignore set. Extends rather than replaces, so that declaring one pattern
|
|
9
|
+
* cannot silently drop the defaults and start shipping a package's own tests.
|
|
10
|
+
* The programmatic `buildPackage` option of the same name behaves identically; its bare `ignorePatterns` replaces.
|
|
11
|
+
*/
|
|
6
12
|
extraIgnorePatterns?: string[];
|
|
7
13
|
}
|
|
14
|
+
/** Check-result cache settings honored at the monorepo root. */
|
|
8
15
|
export interface CheckCacheConfig {
|
|
16
|
+
/** Set to `false` to turn the gate off entirely, so every command runs. */
|
|
9
17
|
enabled?: boolean;
|
|
18
|
+
/**
|
|
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.
|
|
21
|
+
*/
|
|
10
22
|
excludeCommands?: string[];
|
|
23
|
+
/**
|
|
24
|
+
* Command names added to the cacheable set. Extends rather than replaces, so that declaring one command
|
|
25
|
+
* cannot silently drop the defaults. A name listed here promises exit-status-only semantics through its whole
|
|
26
|
+
* chain, hooks included.
|
|
27
|
+
*/
|
|
11
28
|
extraCommands?: string[];
|
|
12
29
|
}
|
|
30
|
+
/** Output settings honored at the monorepo root. */
|
|
13
31
|
export interface OutputConfig {
|
|
32
|
+
/**
|
|
33
|
+
* The verbosity a run takes when neither `-q` nor the environment named one. Both of those outrank it, and it
|
|
34
|
+
* outranks agent-harness detection, so `full` here is also how a repo declines to be detected.
|
|
35
|
+
*/
|
|
14
36
|
commandVerbosity?: CommandVerbosity;
|
|
37
|
+
/**
|
|
38
|
+
* Environment-variable names added to the set whose presence marks an agent harness. Extends rather than
|
|
39
|
+
* replaces, so that declaring one name cannot silently drop the shipped list, and a repo absorbs a harness's
|
|
40
|
+
* rename without waiting for an nmr release.
|
|
41
|
+
*/
|
|
15
42
|
extraAgentEnvVars?: string[];
|
|
16
43
|
}
|
|
17
44
|
export interface NmrConfig {
|
package/dist/esm/verbosity.d.ts
CHANGED
|
@@ -1,18 +1,55 @@
|
|
|
1
1
|
import type { OutputConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Environment variables a known agent harness sets, whose presence selects quiet when no level above them chose.
|
|
4
|
+
* The identifiers belong to ecosystems nmr does not control and that rename, so `output.extraAgentEnvVars` extends
|
|
5
|
+
* this list rather than a repo waiting for a release. `ROVO_CLI` anticipates a rename of `ROVODEV_CLI` that Rovo
|
|
6
|
+
* has signalled but not made; a variable nobody sets never fires.
|
|
7
|
+
*/
|
|
2
8
|
export declare const AGENT_ENV_VARS: readonly ["CLAUDECODE", "ROVODEV_CLI", "ROVO_CLI"];
|
|
9
|
+
/**
|
|
10
|
+
* The points on the loudness ladder. Both are spelled out rather than leaving `full` implicit in the variable's
|
|
11
|
+
* absence, so a repo-level default is overridable from the environment in either direction.
|
|
12
|
+
*/
|
|
3
13
|
export declare const COMMAND_VERBOSITIES: readonly ["full", "quiet"];
|
|
14
|
+
/**
|
|
15
|
+
* Carries the resolved verbosity down the spawned chain, so `-q` reaches every process rather than the first.
|
|
16
|
+
* Deliberately not a keyed variable: it changes what a run prints and never what a command concludes, so folding
|
|
17
|
+
* it into the cache key would stop a quiet run from hitting a pass a loud one recorded.
|
|
18
|
+
*/
|
|
4
19
|
export declare const COMMAND_VERBOSITY_ENV_VAR = "NMR_COMMAND_VERBOSITY";
|
|
20
|
+
/** How loudly nmr reports the output of the commands it runs, which is never its own verdicts. */
|
|
5
21
|
export type CommandVerbosity = (typeof COMMAND_VERBOSITIES)[number];
|
|
22
|
+
/**
|
|
23
|
+
* Renders the rejection of a value off the loudness ladder, naming where it was written. Every source that accepts
|
|
24
|
+
* a verbosity renders through this, so no two of them can come to name different ladders.
|
|
25
|
+
*/
|
|
6
26
|
export declare function formatVerbosityRejection(source: string, value: string): string;
|
|
27
|
+
/** Narrows a raw value to a point on the loudness ladder. */
|
|
7
28
|
export declare function isCommandVerbosity(value: string): value is CommandVerbosity;
|
|
29
|
+
/**
|
|
30
|
+
* Reads the verbosity the environment names. An unrecognized value resolves to nothing at all, since falling back
|
|
31
|
+
* would pick a mode nobody chose and hide a misspelling for the life of the shell.
|
|
32
|
+
*
|
|
33
|
+
* An unset or empty variable names no verbosity rather than resolving to `full`, which is what leaves the config
|
|
34
|
+
* and detection levels reachable: a floor written in here would outrank both of them.
|
|
35
|
+
*/
|
|
8
36
|
export declare function readVerbosityEnv(env: NodeJS.ProcessEnv): VerbosityRead;
|
|
9
37
|
export interface ResolveVerbosityOptions {
|
|
38
|
+
/** Read for the detection level, and for that alone: the variable this module owns arrives as `envVerbosity`. */
|
|
10
39
|
env: NodeJS.ProcessEnv;
|
|
11
40
|
envVerbosity: CommandVerbosity | undefined;
|
|
12
41
|
output: OutputConfig | undefined;
|
|
13
42
|
quietFlag: boolean;
|
|
14
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Resolves the verbosity this process runs at: the flag, then the environment, then the repo's config, then
|
|
46
|
+
* detection of a known agent harness, then `full`.
|
|
47
|
+
*
|
|
48
|
+
* Total by construction. The environment was validated as it was read and the config as it was loaded, so no
|
|
49
|
+
* rejection is left for this to report and the whole ladder reads in one place.
|
|
50
|
+
*/
|
|
15
51
|
export declare function resolveVerbosity(options: ResolveVerbosityOptions): CommandVerbosity;
|
|
52
|
+
/** The verbosity the environment names, if any, or the message naming why its value could not be read. */
|
|
16
53
|
export type VerbosityRead = {
|
|
17
54
|
ok: true;
|
|
18
55
|
verbosity?: CommandVerbosity;
|
package/dist/esm/verdict.d.ts
CHANGED
|
@@ -1,11 +1,35 @@
|
|
|
1
1
|
import type { Writable } from 'node:stream';
|
|
2
2
|
import type { ReplayLine } from './check-cache.js';
|
|
3
3
|
import type { ReportFormat } from './report-format.js';
|
|
4
|
+
/**
|
|
5
|
+
* The ceiling a rendered verdict, its newline included, is held to.
|
|
6
|
+
*
|
|
7
|
+
* POSIX guarantees a write at or below `PIPE_BUF` reaches a pipe whole, and 512 is the smallest bound the
|
|
8
|
+
* standard permits, so one write of a line this size cannot be interleaved by a concurrent scope under
|
|
9
|
+
* `pnpm --recursive`. No Node API reports the platform's own bound, and a per-platform table would move the
|
|
10
|
+
* truncation point from one machine to the next. The ceiling governs pipes: a terminal offers no such
|
|
11
|
+
* guarantee at any size.
|
|
12
|
+
*/
|
|
4
13
|
export declare const VERDICT_LINE_LIMIT = 512;
|
|
14
|
+
/**
|
|
15
|
+
* What a command nmr ran came to, holding the facts a reporting line is rendered from rather than the line
|
|
16
|
+
* itself, so a machine-readable rendering spends the same record a human-readable one does.
|
|
17
|
+
*
|
|
18
|
+
* A recalled pass carries its saving as a duration and not as a decision about whether to mention it: whether
|
|
19
|
+
* one is worth naming belongs to rendering, and a consumer bypassing the renderer inherits neither the
|
|
20
|
+
* threshold nor an obligation to restate it.
|
|
21
|
+
*/
|
|
5
22
|
export type Verdict = {
|
|
6
23
|
command: string;
|
|
7
24
|
scope: string;
|
|
8
25
|
} & VerdictOutcome;
|
|
26
|
+
/**
|
|
27
|
+
* How a command ended, together with the facts that ending carries and no other does, and the trailing detail
|
|
28
|
+
* a line reserves room for.
|
|
29
|
+
*
|
|
30
|
+
* A recalled pass carries the excerpts it replays rather than a composed detail string, so the marker naming
|
|
31
|
+
* them a recording, and the ceiling they are held to, stay with the module that owns the line's grammar.
|
|
32
|
+
*/
|
|
9
33
|
export type VerdictOutcome = {
|
|
10
34
|
detail?: string;
|
|
11
35
|
} & ({
|
|
@@ -24,6 +48,27 @@ export type VerdictOutcome = {
|
|
|
24
48
|
outcome: 'no-op';
|
|
25
49
|
reason: 'empty-override' | 'noop-override';
|
|
26
50
|
});
|
|
51
|
+
/**
|
|
52
|
+
* Renders a verdict as the line nmr reports it on, without the newline that terminates it.
|
|
53
|
+
*
|
|
54
|
+
* The line ends without terminal punctuation and reserves its tail for `detail`, so a later change appends to
|
|
55
|
+
* the grammar rather than rewriting it. A detail carrying nothing but line breaks takes its whole clause with
|
|
56
|
+
* it, rather than leaving a separator pointing at nothing.
|
|
57
|
+
*/
|
|
27
58
|
export declare function renderVerdict(verdict: Verdict): string;
|
|
59
|
+
/**
|
|
60
|
+
* Renders a verdict as the JSON object a machine consumer reads, without the newline that terminates it.
|
|
61
|
+
*
|
|
62
|
+
* Held to the ceiling the prose line is held to, so one write still reaches a pipe whole where concurrent
|
|
63
|
+
* scopes share a descriptor. Cuts land inside the record's text rather than across its structure, which is
|
|
64
|
+
* what leaves the line parseable, and a record is fitted by rungs, each shedding what a reader can better
|
|
65
|
+
* spare than the rung below it. What the ceiling costs is [documented](../README.md#reporting-for-a-machine)
|
|
66
|
+
* in the same order.
|
|
67
|
+
*/
|
|
28
68
|
export declare function serializeVerdict(verdict: Verdict): string;
|
|
69
|
+
/**
|
|
70
|
+
* Writes a verdict to a stream as a single write, which is what holds a line together when concurrent scopes
|
|
71
|
+
* share one descriptor. Both renderings spend the one record, so neither can come to report what the other
|
|
72
|
+
* does not.
|
|
73
|
+
*/
|
|
29
74
|
export declare function writeVerdict(verdict: Verdict, stream: Writable, format: ReportFormat): void;
|
package/dist/esm/vitest.d.ts
CHANGED
|
@@ -1,21 +1,91 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ViteUserConfig } from 'vitest/config';
|
|
2
2
|
import type { InlineConfig, ProjectConfig } from 'vitest/node';
|
|
3
|
-
import type
|
|
3
|
+
import { type TierName } from './tiers.js';
|
|
4
4
|
export type { TierName } from './tiers.js';
|
|
5
|
+
/**
|
|
6
|
+
* Test options Vitest honours only at the root of a `projects` config. Derived from Vitest's own types rather than
|
|
7
|
+
* hand-listed, so an option that changes scope in a later release changes scope here too.
|
|
8
|
+
*/
|
|
5
9
|
export type RootTestOptions = Omit<InlineConfig, keyof ProjectConfig>;
|
|
10
|
+
/** Root-scoped overrides: Vite-level options plus the test options that survive at the root. */
|
|
6
11
|
export type RootOverrides = Omit<ViteUserConfig, 'test'> & {
|
|
7
12
|
test?: RootTestOptions;
|
|
8
13
|
};
|
|
9
14
|
export interface VitestConfigOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Merged into the root config, which every project inherits because each declares `extends: true`.
|
|
17
|
+
*
|
|
18
|
+
* `resolve` is per-environment. Vitest resolves a test's own imports through the server environment, so a
|
|
19
|
+
* condition meant for them goes under `ssr`; a top-level `resolve.conditions` entry reaches the client
|
|
20
|
+
* environment, which only browser-mode tests resolve through.
|
|
21
|
+
*/
|
|
10
22
|
root?: RootOverrides;
|
|
23
|
+
/** Merged into every declared project. This is where per-project options such as `setupFiles` go. */
|
|
11
24
|
project?: ProjectConfig;
|
|
25
|
+
/**
|
|
26
|
+
* Merged into one tier's project alone, after this layer's `project` block. Raising a budget for `tool` here
|
|
27
|
+
* leaves `unit` on the tight default that makes a hung unit test fail fast, which a uniform `project` override
|
|
28
|
+
* would flatten.
|
|
29
|
+
*/
|
|
12
30
|
tiers?: Partial<Record<TierName, ProjectConfig>>;
|
|
31
|
+
/**
|
|
32
|
+
* Directory basenames kept out of every project's collection, additive to the ones the shared config always
|
|
33
|
+
* prunes. Each name is matched at any depth, and every layer's entries are concatenated rather than the last
|
|
34
|
+
* winning, matching the config's rule for arrays.
|
|
35
|
+
*
|
|
36
|
+
* The same array declares the scope of nmr's exported test-file conventions check, so the sweep and the
|
|
37
|
+
* collection glob cannot drift. Excluding a directory from the sweep alone leaves a test file that runs and
|
|
38
|
+
* reports nothing; excluding it from collection alone leaves a report a consumer cannot act on. A repo wanting
|
|
39
|
+
* a glob rather than a directory name has the `project` seam's own `exclude`.
|
|
40
|
+
*/
|
|
41
|
+
testCollectionExclude?: readonly string[];
|
|
42
|
+
/**
|
|
43
|
+
* Loads nmr's git-isolation setup file into every project, ahead of any the layers supply. Defaults to `true`.
|
|
44
|
+
* Turn it off only where a suite is meant to read the developer's own git configuration.
|
|
45
|
+
*/
|
|
13
46
|
isolateGit?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Resolves workspace packages through their `source` export condition, so a suite runs without a prior build.
|
|
49
|
+
* Defaults to `true`. A package declaring no such condition is unaffected.
|
|
50
|
+
*/
|
|
14
51
|
resolveFromSource?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Resolves a specifier through the `paths` a `tsconfig.json` declares, so a test reaches an alias the way `tsc`
|
|
54
|
+
* does. Defaults to `false`, matching Vite, and requires Vite 8.
|
|
55
|
+
*
|
|
56
|
+
* Not a default, because both directions are safe to leave to the consumer: omitting it fails loudly with an
|
|
57
|
+
* unresolved import, while turning it on for a repo that declares `paths` for `tsc` alone changes which module a
|
|
58
|
+
* specifier reaches with nothing in the run reporting it. A repo declaring no `paths` is unaffected either way.
|
|
59
|
+
*/
|
|
60
|
+
tsconfigPaths?: boolean;
|
|
15
61
|
}
|
|
16
62
|
export interface RootVitestConfigOptions extends VitestConfigOptions {
|
|
63
|
+
/**
|
|
64
|
+
* An absolute path to the monorepo root, which must hold `pnpm-workspace.yaml`. A root config sits at that
|
|
65
|
+
* directory by construction, so this is `import.meta.dirname`. Stated rather than searched for: resolving it
|
|
66
|
+
* from the working directory would make the config describe whichever monorepo the run happened to start in.
|
|
67
|
+
*/
|
|
17
68
|
monorepoRoot: string;
|
|
18
69
|
}
|
|
70
|
+
/** Any number of shared layers, then the config file's own, which states the monorepo root. */
|
|
19
71
|
type RootConfigLayers = [...(VitestConfigOptions | undefined)[], RootVitestConfigOptions];
|
|
72
|
+
/**
|
|
73
|
+
* Builds the shared Vitest config for a workspace package, declaring the `unit`, `tool`, `localhost`, and `remote`
|
|
74
|
+
* projects. Select them at run time with `--project`, which unions when repeated and accepts negation.
|
|
75
|
+
*
|
|
76
|
+
* Layers fold left to right, later winning and arrays composing, so a config file shares settings by passing a
|
|
77
|
+
* layer ahead of its own. Merging two of this function's *outputs* is not the way: both declare the same four
|
|
78
|
+
* project names, which Vitest rejects at startup.
|
|
79
|
+
*
|
|
80
|
+
* An `undefined` layer is skipped, so `defineVitestConfig(shared, isCI ? ciLayer : undefined)` composes.
|
|
81
|
+
*/
|
|
20
82
|
export declare function defineVitestConfig(...layers: (VitestConfigOptions | undefined)[]): ViteUserConfig;
|
|
83
|
+
/**
|
|
84
|
+
* Builds the shared Vitest config for repo-root tests. Excludes every workspace package from all projects, and
|
|
85
|
+
* reports no coverage of its own — packages cover their own sources.
|
|
86
|
+
*
|
|
87
|
+
* `monorepoRoot` rides on the last layer, which is the config file's own: a shared layer describes settings, not
|
|
88
|
+
* which repo they belong to, and only the root config's `import.meta.dirname` states this one. The guard below
|
|
89
|
+
* still catches the JavaScript config that types never reach.
|
|
90
|
+
*/
|
|
21
91
|
export declare function defineRootVitestConfig(...layers: RootConfigLayers): ViteUserConfig;
|
package/dist/esm/vitest.js
CHANGED
|
@@ -11,7 +11,6 @@ const GIT_ISOLATION_SETUP_FILE = resolveGitIsolationSetupFile();
|
|
|
11
11
|
const SOURCE_CLIENT_CONDITIONS = ['source', 'module', 'browser', 'development|production'];
|
|
12
12
|
const SOURCE_SERVER_CONDITIONS = ['source', 'module', 'node', 'development|production'];
|
|
13
13
|
const COVERAGE_EXCLUDE = ['**/__{fixtures,mocks,tests}__/**', '**/index.ts', '**/*.d.ts'];
|
|
14
|
-
const COLLECTION_EXCLUDE = [...new Set([...defaultExclude, ...TEST_COLLECTION_EXCLUDE.map((dir) => `**/${dir}/**`)])];
|
|
15
14
|
const PACKAGE_COVERAGE_INCLUDE = ['**/src/**/*.{ts,tsx}'];
|
|
16
15
|
const MISSING_MONOREPO_ROOT = 'defineRootVitestConfig requires `monorepoRoot`, an absolute path to the directory holding pnpm-workspace.yaml. Pass `import.meta.dirname` in the last options layer, from the root config.';
|
|
17
16
|
export function defineVitestConfig(...layers) {
|
|
@@ -32,11 +31,14 @@ export function defineRootVitestConfig(...layers) {
|
|
|
32
31
|
function buildConfig(declaredLayers, { coverageInclude, projectExclude = [], projectRoot }) {
|
|
33
32
|
const layers = declaredLayers.filter((layer) => layer !== undefined);
|
|
34
33
|
assertKnownTiers(layers);
|
|
34
|
+
const resolveFromSource = resolveFlag(layers, 'resolveFromSource', true);
|
|
35
|
+
const resolve = {
|
|
36
|
+
...(resolveFromSource && { conditions: SOURCE_CLIENT_CONDITIONS }),
|
|
37
|
+
...(resolveFlag(layers, 'tsconfigPaths', false) && { tsconfigPaths: true }),
|
|
38
|
+
};
|
|
35
39
|
const config = {
|
|
36
|
-
...(
|
|
37
|
-
|
|
38
|
-
ssr: { resolve: { conditions: SOURCE_SERVER_CONDITIONS } },
|
|
39
|
-
}),
|
|
40
|
+
...(Object.keys(resolve).length > 0 && { resolve }),
|
|
41
|
+
...(resolveFromSource && { ssr: { resolve: { conditions: SOURCE_SERVER_CONDITIONS } } }),
|
|
40
42
|
test: {
|
|
41
43
|
coverage: {
|
|
42
44
|
enabled: false,
|
|
@@ -67,13 +69,14 @@ function buildProjects(layers, extraExclude, projectRoot) {
|
|
|
67
69
|
timeout: TIER_TIMEOUT,
|
|
68
70
|
})),
|
|
69
71
|
];
|
|
70
|
-
const
|
|
72
|
+
const collectionExclude = buildCollectionExclude(layers);
|
|
73
|
+
const isolateGit = resolveFlag(layers, 'isolateGit', true);
|
|
71
74
|
return projectTiers.map(({ exclude, include, name, timeout }) => {
|
|
72
75
|
const project = {
|
|
73
76
|
extends: true,
|
|
74
77
|
...(projectRoot !== undefined && { root: projectRoot }),
|
|
75
78
|
test: {
|
|
76
|
-
exclude: [...
|
|
79
|
+
exclude: [...collectionExclude, ...exclude, ...extraExclude],
|
|
77
80
|
include,
|
|
78
81
|
name,
|
|
79
82
|
...(isolateGit && { setupFiles: [GIT_ISOLATION_SETUP_FILE] }),
|
|
@@ -103,13 +106,18 @@ function assertKnownTiers(layers) {
|
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
108
|
}
|
|
109
|
+
function buildCollectionExclude(layers) {
|
|
110
|
+
const declared = layers.flatMap((layer) => layer.testCollectionExclude ?? []);
|
|
111
|
+
const globs = [...TEST_COLLECTION_EXCLUDE, ...declared].map((dir) => `**/${dir}/**`);
|
|
112
|
+
return [...new Set([...defaultExclude, ...globs])];
|
|
113
|
+
}
|
|
106
114
|
function getWorkspaceExcludePatterns(monorepoRoot) {
|
|
107
115
|
return getWorkspacePackageDirs(monorepoRoot)
|
|
108
116
|
.map((dir) => `${path.relative(monorepoRoot, dir).split(path.sep).join('/')}/**`)
|
|
109
117
|
.toSorted();
|
|
110
118
|
}
|
|
111
|
-
function resolveFlag(layers, name) {
|
|
112
|
-
let resolved =
|
|
119
|
+
function resolveFlag(layers, name, fallback) {
|
|
120
|
+
let resolved = fallback;
|
|
113
121
|
for (const layer of layers) {
|
|
114
122
|
const declared = layer[name];
|
|
115
123
|
if (declared !== undefined)
|
package/dist/esm/workspace.d.ts
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
|
+
/** Reports whether a directory is the monorepo root, which the workspace manifest's presence marks. */
|
|
1
2
|
export declare function isMonorepoRoot(dir: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Finds the monorepo root by walking up from `startDir` to find `pnpm-workspace.yaml`.
|
|
5
|
+
* Throws if no workspace root is found.
|
|
6
|
+
*/
|
|
2
7
|
export declare function findMonorepoRoot(startDir?: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Reads the workspace patterns from `pnpm-workspace.yaml` and resolves them to absolute package
|
|
10
|
+
* directories, applying pnpm's pattern semantics — including `!`-prefixed exclusions.
|
|
11
|
+
*
|
|
12
|
+
* Returns an empty array when the manifest declares no usable `packages` list, and throws when
|
|
13
|
+
* `monorepoRoot` holds no manifest at all — the caller named a directory that is not a monorepo root.
|
|
14
|
+
*/
|
|
3
15
|
export declare function getWorkspacePackageDirs(monorepoRoot: string): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Reads the `overrides` block from the monorepo root's `pnpm-workspace.yaml`, the site pnpm reads an override
|
|
18
|
+
* from.
|
|
19
|
+
*
|
|
20
|
+
* Returns nothing when the manifest or the block is missing, and drops an entry whose value is not a string.
|
|
21
|
+
*/
|
|
4
22
|
export declare function readWorkspaceOverrides(monorepoRoot: string): Record<string, string> | undefined;
|