@williamthorsen/nmr 0.39.0 → 0.41.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/.meta/changelog.json +145 -4
- package/.readyup/kits/default.js +47 -32
- package/.readyup/manifest.json +12 -7
- package/CHANGELOG.md +44 -0
- package/README.md +36 -75
- package/agents/README.md +8 -8
- package/agents/guidance/rulebooks/nmr.md +8 -8
- package/bin/ensure-prepublish-hooks.js +1 -1
- package/bin/nmr-clean.js +1 -1
- package/bin/nmr-compile.js +1 -1
- package/bin/nmr-fmt.js +1 -1
- package/bin/nmr-report-catalog.js +1 -1
- package/bin/nmr-report-overrides.js +1 -1
- package/bin/nmr-taze.js +1 -1
- package/bin/nmr.js +1 -1
- package/dist/esm/UserError.d.ts +1 -2
- package/dist/esm/check-cache.d.ts +59 -61
- package/dist/esm/commands/build-output.d.ts +35 -36
- package/dist/esm/commands/build.d.ts +13 -14
- package/dist/esm/commands/build.js +3 -3
- package/dist/esm/commands/clean.d.ts +6 -6
- package/dist/esm/commands/ensure-prepublish-hooks.d.ts +5 -5
- package/dist/esm/commands/fmt.d.ts +8 -8
- package/dist/esm/commands/fmt.js +1 -1
- package/dist/esm/commands/report-catalog.d.ts +5 -7
- package/dist/esm/commands/report-catalog.js +1 -1
- package/dist/esm/commands/report-overrides.d.ts +0 -2
- package/dist/esm/commands/report-overrides.js +1 -1
- package/dist/esm/commands/taze.d.ts +9 -8
- package/dist/esm/config.d.ts +5 -5
- package/dist/esm/config.js +3 -3
- package/dist/esm/context.d.ts +1 -2
- package/dist/esm/default-scripts.d.ts +5 -6
- package/dist/esm/defineConfig.d.ts +1 -1
- package/dist/esm/git-ignored-paths.d.ts +9 -0
- package/dist/esm/git-ignored-paths.js +10 -0
- package/dist/esm/glyphs.d.ts +3 -3
- package/dist/esm/help.d.ts +2 -2
- package/dist/esm/helpers/clampToBytes.d.ts +4 -5
- package/dist/esm/helpers/config-path.d.ts +0 -2
- package/dist/esm/helpers/createBoundedBuffer.d.ts +2 -2
- package/dist/esm/helpers/deriveExcerpt.d.ts +4 -5
- package/dist/esm/helpers/duration.d.ts +8 -9
- package/dist/esm/helpers/filter-selection.d.ts +19 -19
- package/dist/esm/helpers/findClosestName.d.ts +1 -1
- package/dist/esm/helpers/hook-name.d.ts +2 -5
- package/dist/esm/helpers/package-json.d.ts +9 -8
- package/dist/esm/helpers/readStringValues.d.ts +2 -3
- package/dist/esm/helpers/reportClosing.d.ts +8 -9
- package/dist/esm/helpers/transcript.d.ts +13 -13
- package/dist/esm/helpers/type-guards.d.ts +1 -0
- package/dist/esm/output-style.d.ts +12 -13
- package/dist/esm/prettier.d.ts +2 -2
- package/dist/esm/recording.d.ts +20 -15
- package/dist/esm/recording.js +7 -7
- package/dist/esm/replay-assembly.d.ts +10 -10
- package/dist/esm/report-format.d.ts +14 -19
- package/dist/esm/reportCliFailure.d.ts +2 -3
- package/dist/esm/resolve-scripts.d.ts +4 -5
- package/dist/esm/resolveBinStyles.d.ts +4 -5
- package/dist/esm/resolver.d.ts +14 -18
- package/dist/esm/runCli.d.ts +6 -6
- package/dist/esm/runCli.js +104 -27
- package/dist/esm/runner.d.ts +19 -19
- package/dist/esm/steps.d.ts +45 -42
- package/dist/esm/steps.js +6 -6
- package/dist/esm/taze.d.ts +1 -1
- package/dist/esm/tests.d.ts +18 -13
- package/dist/esm/tests.js +2 -2
- package/dist/esm/tiers.d.ts +27 -23
- package/dist/esm/tiers.js +6 -2
- package/dist/esm/types.d.ts +5 -5
- package/dist/esm/verbosity.d.ts +22 -20
- package/dist/esm/verdict.d.ts +33 -33
- package/dist/esm/verdict.js +1 -1
- package/dist/esm/vitest-source-resolution.d.ts +11 -11
- package/dist/esm/vitest-source-resolution.js +2 -2
- package/dist/esm/vitest.d.ts +25 -24
- package/dist/esm/vitest.js +16 -8
- package/dist/esm/workspace.d.ts +4 -4
- package/docs/check-cache.md +57 -57
- package/docs/prettier.md +10 -10
- package/docs/reporting.md +46 -46
- package/docs/scripts.md +29 -29
- package/docs/upgrades.md +18 -18
- package/docs/utilities.md +13 -13
- package/docs/vitest.md +64 -62
- package/docs/workspace.md +19 -19
- package/package.json +6 -5
package/dist/esm/tiers.d.ts
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
/** Collection patterns for the residual project, which claims every test file the named tiers leave. */
|
|
1
|
+
/** Collection patterns for the residual project, which claims every test file that the named tiers leave. */
|
|
2
2
|
export declare const ALL_TEST_PATTERNS: string[];
|
|
3
3
|
/** Builds the collection patterns for one tier. */
|
|
4
4
|
export declare function buildTierPatterns(tier: string): string[];
|
|
5
5
|
/**
|
|
6
|
-
* Walks `rootDir` for every `*.test.ts` or `*.test.tsx` file
|
|
7
|
-
*
|
|
6
|
+
* Walks `rootDir` for every `*.test.ts` or `*.test.tsx` file outside a `__tests__` directory, returning paths
|
|
7
|
+
* relative to it, POSIX-separated and sorted.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* from a file that runs and passes.
|
|
9
|
+
* The shared config's projects do not collect such a file, so it runs nowhere and reports nothing. Only a sweep of
|
|
10
|
+
* the tree tells it apart from a file that runs and passes.
|
|
11
11
|
*/
|
|
12
12
|
export declare function findMisplacedTestFiles(rootDir: string, options?: TestFileScanOptions): string[];
|
|
13
13
|
/**
|
|
14
|
-
* Walks `rootDir` for every test file the shared config's projects collect, returning paths relative to it,
|
|
14
|
+
* Walks `rootDir` for every test file that the shared config's projects collect, returning paths relative to it,
|
|
15
15
|
* POSIX-separated and sorted.
|
|
16
16
|
*
|
|
17
|
-
* A walk, because `node:fs` `globSync` does not descend into a dot-directory and
|
|
18
|
-
* it. Vitest's globber does, so the same pattern string means different things to the two:
|
|
19
|
-
* would report clean over every test file under a dot-directory.
|
|
17
|
+
* A walk, because `node:fs` `globSync` does not descend into a dot-directory and does not honour any option that
|
|
18
|
+
* would make it. Vitest's globber does, so the same pattern string means different things to the two: A check built
|
|
19
|
+
* on the glob would report clean over every test file under a dot-directory.
|
|
20
20
|
*/
|
|
21
21
|
export declare function findTestFiles(rootDir: string, options?: TestFileScanOptions): string[];
|
|
22
22
|
/**
|
|
23
|
-
* Walks `rootDir` for every collected test file whose name
|
|
23
|
+
* Walks `rootDir` for every collected test file whose name does not select a tier, returning paths relative to it,
|
|
24
24
|
* POSIX-separated and sorted.
|
|
25
25
|
*
|
|
26
|
-
* `unit` is the residual project,
|
|
27
|
-
* a conformant file.
|
|
26
|
+
* Because `unit` is the residual project, such a file runs under it and reports success: A test run cannot tell it
|
|
27
|
+
* apart from a conformant file.
|
|
28
28
|
*/
|
|
29
29
|
export declare function findUntieredTestFiles(rootDir: string, options?: TestFileScanOptions): string[];
|
|
30
30
|
/**
|
|
@@ -35,27 +35,31 @@ export declare function findUntieredTestFiles(rootDir: string, options?: TestFil
|
|
|
35
35
|
*/
|
|
36
36
|
export declare function hasTierInfix(filePath: string): boolean;
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
38
|
+
* Names of directories that do not contain any test worth collecting: dependencies, build output, and generated
|
|
39
|
+
* reports.
|
|
39
40
|
*
|
|
40
|
-
*
|
|
41
|
-
* consumer cannot fix; under-reporting
|
|
41
|
+
* These and the paths that git ignores are the scope on which the collection glob and the walk must agree.
|
|
42
|
+
* Over-reporting is a failure that a consumer cannot fix; under-reporting hides the violations that a conformance
|
|
43
|
+
* check exists to report.
|
|
42
44
|
*/
|
|
43
45
|
export declare const TEST_COLLECTION_EXCLUDE: string[];
|
|
44
|
-
/** Options every sweep over a repo's test files takes. */
|
|
46
|
+
/** Options that every sweep over a repo's test files takes. */
|
|
45
47
|
export interface TestFileScanOptions {
|
|
46
48
|
/**
|
|
47
|
-
* Directory basenames pruned at any depth, additive to `TEST_COLLECTION_EXCLUDE
|
|
49
|
+
* Directory basenames pruned at any depth, additive to `TEST_COLLECTION_EXCLUDE` and to the paths that git
|
|
50
|
+
* ignores.
|
|
48
51
|
*
|
|
49
|
-
* Basenames rather than globs, so
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
+
* Basenames rather than globs, so that a repo can pass the same array here and to the shared Vitest config's
|
|
53
|
+
* `testCollectionExclude`. Vitest still runs the files in a directory pruned from the sweep alone, and the sweep
|
|
54
|
+
* does not report them.
|
|
52
55
|
*/
|
|
53
56
|
excludedBasenames?: readonly string[];
|
|
54
57
|
}
|
|
55
58
|
/**
|
|
56
|
-
* The isolation ladder, ordered by the furthest thing a test reaches. A tier names what a test reaches, never
|
|
57
|
-
* invokes it:
|
|
58
|
-
* Each named tier's name is also its filename infix, so `parse.tool.test.ts`
|
|
59
|
+
* The isolation ladder, ordered by the furthest thing that a test reaches. A tier names what a test reaches, never
|
|
60
|
+
* how it invokes it: A test driving a compiler through its JavaScript API is `tool`, exactly as one spawning `tsc`
|
|
61
|
+
* would be. Each named tier's name is also its filename infix, so `parse.tool.test.ts` is collected by the `tool`
|
|
62
|
+
* project.
|
|
59
63
|
*/
|
|
60
64
|
export declare const TIER_NAMES: readonly ["unit", "tool", "localhost", "remote"];
|
|
61
65
|
/** One of the four isolation tiers. */
|
package/dist/esm/tiers.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readdirSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { listGitIgnoredPaths } from "./git-ignored-paths.js";
|
|
3
4
|
const TEST_DIR = '__tests__';
|
|
4
5
|
const TEST_EXTENSIONS = '{ts,tsx}';
|
|
5
6
|
const TEST_GLOB_PREFIX = `**/${TEST_DIR}/**`;
|
|
@@ -28,11 +29,13 @@ function collectTestFiles(dir, relativeDir, isInTestDir, context) {
|
|
|
28
29
|
for (const entry of entries) {
|
|
29
30
|
const relativePath = relativeDir === '' ? entry.name : `${relativeDir}/${entry.name}`;
|
|
30
31
|
if (entry.isDirectory()) {
|
|
31
|
-
if (context.pruned.has(entry.name))
|
|
32
|
+
if (context.pruned.has(entry.name) || context.ignoredPaths.has(`${relativePath}/`))
|
|
32
33
|
continue;
|
|
33
34
|
collectTestFiles(path.join(dir, entry.name), relativePath, isInTestDir || entry.name === TEST_DIR, context);
|
|
34
35
|
}
|
|
35
|
-
else if (isInTestDir !== context.misplaced &&
|
|
36
|
+
else if (isInTestDir !== context.misplaced &&
|
|
37
|
+
TEST_FILE_PATTERN.test(entry.name) &&
|
|
38
|
+
!context.ignoredPaths.has(relativePath)) {
|
|
36
39
|
context.foundPaths.push(relativePath);
|
|
37
40
|
}
|
|
38
41
|
}
|
|
@@ -40,6 +43,7 @@ function collectTestFiles(dir, relativeDir, isInTestDir, context) {
|
|
|
40
43
|
function walkTestFiles(rootDir, { excludedBasenames = [] }, misplaced) {
|
|
41
44
|
const context = {
|
|
42
45
|
foundPaths: [],
|
|
46
|
+
ignoredPaths: new Set(listGitIgnoredPaths(rootDir)),
|
|
43
47
|
misplaced,
|
|
44
48
|
pruned: new Set([...TEST_COLLECTION_EXCLUDE, ...excludedBasenames]),
|
|
45
49
|
};
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export type { CommandVerbosity } from './verbosity.js';
|
|
|
6
6
|
export interface BuildConfig {
|
|
7
7
|
/**
|
|
8
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
|
|
9
|
+
* cannot silently drop the defaults and start publishing a package's own tests.
|
|
10
10
|
* The programmatic `buildPackage` option of the same name behaves identically; its bare `ignorePatterns` replaces.
|
|
11
11
|
*/
|
|
12
12
|
extraIgnorePatterns?: string[];
|
|
@@ -23,7 +23,7 @@ export interface CheckCacheConfig {
|
|
|
23
23
|
/**
|
|
24
24
|
* Command names added to the cacheable set. Extends rather than replaces, so that declaring one command
|
|
25
25
|
* cannot silently drop the defaults. A name listed here promises exit-status-only semantics through its whole
|
|
26
|
-
* chain, hooks included, and has to resolve to a command:
|
|
26
|
+
* chain, hooks included, and has to resolve to a command: Both lists are read by name alone, so a misspelt
|
|
27
27
|
* entry would otherwise be inert.
|
|
28
28
|
*/
|
|
29
29
|
extraCommands?: string[];
|
|
@@ -31,13 +31,13 @@ export interface CheckCacheConfig {
|
|
|
31
31
|
/** Output settings honored at the monorepo root. */
|
|
32
32
|
export interface OutputConfig {
|
|
33
33
|
/**
|
|
34
|
-
* The verbosity a run takes when neither `-q` nor the environment named one. Both of those outrank it
|
|
35
|
-
* outranks agent-harness detection,
|
|
34
|
+
* The verbosity that a run takes when neither `-q` nor the environment named one. Both of those outrank it.
|
|
35
|
+
* Because it outranks agent-harness detection, `full` here is also how a repo declines to be detected.
|
|
36
36
|
*/
|
|
37
37
|
commandVerbosity?: CommandVerbosity;
|
|
38
38
|
/**
|
|
39
39
|
* Environment-variable names added to the set whose presence marks an agent harness. Extends rather than
|
|
40
|
-
* replaces, so that declaring one name cannot silently drop the
|
|
40
|
+
* replaces, so that declaring one name cannot silently drop the built-in list, and a repo handles a harness's
|
|
41
41
|
* rename without waiting for an nmr release.
|
|
42
42
|
*/
|
|
43
43
|
extraAgentEnvVars?: string[];
|
package/dist/esm/verbosity.d.ts
CHANGED
|
@@ -1,55 +1,57 @@
|
|
|
1
1
|
import type { OutputConfig } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Environment variables a known agent harness sets, whose presence selects quiet when
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* has signalled but not made; a variable nobody sets never fires.
|
|
3
|
+
* Environment variables that a known agent harness sets, whose presence selects quiet when every level above them
|
|
4
|
+
* left the verbosity unset. `output.extraAgentEnvVars` extends the list. `ROVO_CLI` anticipates a rename of
|
|
5
|
+
* `ROVODEV_CLI` that Rovo has signalled but not made.
|
|
7
6
|
*/
|
|
8
7
|
export declare const AGENT_ENV_VARS: readonly ["CLAUDECODE", "ROVODEV_CLI", "ROVO_CLI"];
|
|
9
8
|
/**
|
|
10
9
|
* 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.
|
|
10
|
+
* absence, so that a repo-level default is overridable from the environment in either direction.
|
|
12
11
|
*/
|
|
13
12
|
export declare const COMMAND_VERBOSITIES: readonly ["full", "quiet"];
|
|
14
13
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* Passes the resolved verbosity down the chain of spawned processes, so that `-q` applies to every process rather
|
|
15
|
+
* than only the first. The pass key does not include it: It changes what a run prints and never what a command
|
|
16
|
+
* concludes, so a quiet run can reuse a pass that a full run recorded.
|
|
18
17
|
*/
|
|
19
18
|
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. */
|
|
19
|
+
/** How loudly nmr reports the output of the commands that it runs, which is never its own verdicts. */
|
|
21
20
|
export type CommandVerbosity = (typeof COMMAND_VERBOSITIES)[number];
|
|
22
21
|
/**
|
|
23
|
-
* Renders the rejection of a value off the loudness ladder, naming where it was written.
|
|
24
|
-
* a verbosity renders through this,
|
|
22
|
+
* Renders the rejection of a value off the loudness ladder, naming where it was written. Because every source that
|
|
23
|
+
* accepts a verbosity renders through this, the sources cannot come to name different ladders.
|
|
25
24
|
*/
|
|
26
25
|
export declare function formatVerbosityRejection(source: string, value: string): string;
|
|
27
26
|
/** Narrows a raw value to a point on the loudness ladder. */
|
|
28
27
|
export declare function isCommandVerbosity(value: string): value is CommandVerbosity;
|
|
29
28
|
/**
|
|
30
|
-
* Reads the verbosity the environment names. An unrecognized value
|
|
31
|
-
* would pick a mode nobody chose and hide a misspelling for the life of the shell.
|
|
29
|
+
* Reads the verbosity that the environment names. An unrecognized value is returned as an error, since a fallback
|
|
30
|
+
* would pick a mode that nobody chose and hide a misspelling for the life of the shell.
|
|
32
31
|
*
|
|
33
|
-
*
|
|
34
|
-
* and detection levels reachable:
|
|
32
|
+
* For an unset or empty variable, this returns a result without a verbosity rather than resolving to `full`, which
|
|
33
|
+
* is what leaves the config and detection levels reachable: A floor written in here would outrank both of them.
|
|
35
34
|
*/
|
|
36
35
|
export declare function readVerbosityEnv(env: NodeJS.ProcessEnv): VerbosityRead;
|
|
37
36
|
export interface ResolveVerbosityOptions {
|
|
38
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Read for the detection level, and for that alone: The caller passes the variable that this module owns as
|
|
39
|
+
* `envVerbosity`.
|
|
40
|
+
*/
|
|
39
41
|
env: NodeJS.ProcessEnv;
|
|
40
42
|
envVerbosity: CommandVerbosity | undefined;
|
|
41
43
|
hasQuietFlag: boolean;
|
|
42
44
|
output: OutputConfig | undefined;
|
|
43
45
|
}
|
|
44
46
|
/**
|
|
45
|
-
* Resolves the verbosity this process runs
|
|
47
|
+
* Resolves the verbosity at which this process runs: the flag, then the environment, then the repo's config, then
|
|
46
48
|
* detection of a known agent harness, then `full`.
|
|
47
49
|
*
|
|
48
|
-
* Total by construction. The environment was validated as it was read and the config as it was loaded, so
|
|
49
|
-
*
|
|
50
|
+
* Total by construction. The environment was validated as it was read and the config as it was loaded, so this does
|
|
51
|
+
* not have any rejection left to report, and the whole ladder can be read in one place.
|
|
50
52
|
*/
|
|
51
53
|
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. */
|
|
54
|
+
/** The verbosity that the environment names, if any, or the message naming why its value could not be read. */
|
|
53
55
|
export type VerbosityRead = {
|
|
54
56
|
ok: true;
|
|
55
57
|
verbosity?: CommandVerbosity;
|
package/dist/esm/verdict.d.ts
CHANGED
|
@@ -3,40 +3,41 @@ import { type OutputStyle } from '@williamthorsen/nmr-core';
|
|
|
3
3
|
import type { ReplayLine } from './check-cache.js';
|
|
4
4
|
import type { ReportFormat } from './report-format.js';
|
|
5
5
|
/**
|
|
6
|
-
* The ceiling a rendered verdict, its newline included, is held
|
|
6
|
+
* The ceiling to which a rendered verdict, its newline included, is held.
|
|
7
7
|
*
|
|
8
|
-
* POSIX guarantees a write at or below `PIPE_BUF`
|
|
9
|
-
* standard permits, so one write of a line this size cannot be interleaved by a concurrent scope under
|
|
10
|
-
* `pnpm --recursive`.
|
|
11
|
-
* truncation point from one machine to the next. The ceiling
|
|
12
|
-
* guarantee at any size.
|
|
8
|
+
* POSIX guarantees that a write at or below `PIPE_BUF` arrives in a pipe whole, and 512 is the smallest bound that
|
|
9
|
+
* the standard permits, so one write of a line this size cannot be interleaved by a concurrent scope under
|
|
10
|
+
* `pnpm --recursive`. Node does not have an API that reports the platform's own bound, and a per-platform table
|
|
11
|
+
* would move the truncation point from one machine to the next. The ceiling applies to pipes: A terminal does not
|
|
12
|
+
* offer that guarantee at any size.
|
|
13
13
|
*/
|
|
14
14
|
export declare const VERDICT_LINE_LIMIT_BYTES = 512;
|
|
15
15
|
/**
|
|
16
|
-
* Why a command ran nothing, which the `no-op` outcome
|
|
16
|
+
* Why a command ran nothing, which the `no-op` outcome includes and `--json` serializes.
|
|
17
17
|
*
|
|
18
|
-
* `empty-workspace` and `empty-override` both leave a chain
|
|
19
|
-
*
|
|
18
|
+
* `empty-workspace` and `empty-override` both leave a chain without any step in it, so the reason tells a command
|
|
19
|
+
* emptied by the workspace apart from one emptied by an override.
|
|
20
20
|
*/
|
|
21
21
|
export type NoOpReason = 'empty-override' | 'empty-workspace' | 'noop-override';
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
* itself,
|
|
23
|
+
* The result of a command that nmr ran. It contains the facts from which a reporting line is rendered rather than
|
|
24
|
+
* the line itself, which lets a machine-readable rendering use the same record as a human-readable one.
|
|
25
25
|
*
|
|
26
|
-
* A recalled pass
|
|
27
|
-
* one is worth naming belongs to rendering, and a consumer bypassing the renderer inherits neither the
|
|
28
|
-
*
|
|
26
|
+
* A recalled pass records its saving as a duration and not as a decision about whether to mention it: Whether
|
|
27
|
+
* one is worth naming belongs to rendering, and a consumer bypassing the renderer inherits neither the threshold
|
|
28
|
+
* nor an obligation to restate it.
|
|
29
29
|
*/
|
|
30
30
|
export type Verdict = {
|
|
31
31
|
command: string;
|
|
32
32
|
scope: string;
|
|
33
33
|
} & VerdictOutcome;
|
|
34
34
|
/**
|
|
35
|
-
* How a command ended, together with the facts that ending
|
|
36
|
-
*
|
|
35
|
+
* How a command ended, together with the facts specific to that ending, and the trailing detail for which a line
|
|
36
|
+
* reserves room.
|
|
37
37
|
*
|
|
38
|
-
* A recalled pass
|
|
39
|
-
* them a recording, and the ceiling they are held
|
|
38
|
+
* A recalled pass contains the excerpts that it replays rather than a composed detail string, so that the marker
|
|
39
|
+
* naming them a recording, and the ceiling to which they are held, stay with the module that owns the line's
|
|
40
|
+
* grammar.
|
|
40
41
|
*/
|
|
41
42
|
export type VerdictOutcome = {
|
|
42
43
|
detail?: string;
|
|
@@ -57,29 +58,28 @@ export type VerdictOutcome = {
|
|
|
57
58
|
reason: NoOpReason;
|
|
58
59
|
});
|
|
59
60
|
/**
|
|
60
|
-
* Renders a verdict as the line nmr reports it
|
|
61
|
+
* Renders a verdict as the line on which nmr reports it, without the newline that terminates it.
|
|
61
62
|
*
|
|
62
|
-
* The line ends without terminal punctuation and reserves its tail for `detail`, so a later change appends to
|
|
63
|
-
* the grammar rather than rewriting it.
|
|
64
|
-
*
|
|
63
|
+
* The line ends without terminal punctuation and reserves its tail for `detail`, so that a later change appends to
|
|
64
|
+
* the grammar rather than rewriting it. When a detail contains nothing but line breaks, the line omits the detail's
|
|
65
|
+
* whole clause rather than leaving a separator pointing at nothing.
|
|
65
66
|
*
|
|
66
|
-
* The marker
|
|
67
|
-
* lines align by construction. `formatStatusLine` pads to a column that `BLOCK` widens, and
|
|
67
|
+
* The marker does not take a padding column. Because every plain marker that a verdict uses is four characters,
|
|
68
|
+
* the lines align by construction. `formatStatusLine` pads to a column that `BLOCK` widens, and a verdict is never
|
|
69
|
+
* one.
|
|
68
70
|
*/
|
|
69
71
|
export declare function renderVerdict(verdict: Verdict, style: OutputStyle): string;
|
|
70
72
|
/**
|
|
71
|
-
* Renders a verdict as the JSON object a machine consumer reads, without the newline that terminates it.
|
|
73
|
+
* Renders a verdict as the JSON object that a machine consumer reads, without the newline that terminates it.
|
|
72
74
|
*
|
|
73
|
-
* Held to the ceiling the prose line
|
|
74
|
-
* scopes share a descriptor.
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* in the same order.
|
|
75
|
+
* Held to the same ceiling as the prose line, so that one write still arrives in a pipe whole when concurrent
|
|
76
|
+
* scopes share a descriptor. It cuts inside the record's text rather than across its structure, which leaves the
|
|
77
|
+
* line parseable, and fits a record by rungs, each shedding what a reader can better spare than the rung below it.
|
|
78
|
+
* Each rung is [documented](../docs/reporting.md#reporting-for-a-machine) in the same order.
|
|
78
79
|
*/
|
|
79
80
|
export declare function serializeVerdict(verdict: Verdict): string;
|
|
80
81
|
/**
|
|
81
|
-
* Writes a verdict to a stream as a single write, which
|
|
82
|
-
*
|
|
83
|
-
* does not.
|
|
82
|
+
* Writes a verdict to a stream as a single write, which keeps a line intact when concurrent scopes share one
|
|
83
|
+
* descriptor. Both renderings use the one record, so neither can come to report what the other does not.
|
|
84
84
|
*/
|
|
85
85
|
export declare function writeVerdict(verdict: Verdict, stream: Writable, format: ReportFormat, style: OutputStyle): void;
|
package/dist/esm/verdict.js
CHANGED
|
@@ -52,7 +52,7 @@ function describeNoOpReason(reason) {
|
|
|
52
52
|
case 'empty-override':
|
|
53
53
|
return 'the override is empty';
|
|
54
54
|
case 'empty-workspace':
|
|
55
|
-
return 'the workspace
|
|
55
|
+
return 'the workspace does not declare any package';
|
|
56
56
|
case 'noop-override':
|
|
57
57
|
return 'the override is a no-op';
|
|
58
58
|
default: {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { Plugin } from 'vite';
|
|
2
2
|
/**
|
|
3
|
-
* Builds the plugin that resolves a package's `source` export condition, for a package whose files
|
|
3
|
+
* Builds the plugin that resolves a package's `source` export condition, for a package whose files are outside
|
|
4
4
|
* every `node_modules`.
|
|
5
5
|
*
|
|
6
6
|
* The condition cannot be emitted as a `resolve.conditions` entry instead: Vitest turns the server environment's
|
|
7
|
-
* conditions into `--conditions` flags on the worker process, where Node applies them to every package it
|
|
7
|
+
* conditions into `--conditions` flags on the worker process, where Node applies them to every package that it
|
|
8
8
|
* resolves natively. Node refuses to strip types from a file under `node_modules`, so a dependency declaring a
|
|
9
9
|
* TypeScript `source` entry fails to load as soon as the condition reaches that far.
|
|
10
10
|
*
|
|
@@ -22,16 +22,16 @@ export interface SourceResolutionContext {
|
|
|
22
22
|
packageDirs?: Map<string, string | undefined>;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
* Resolves a bare specifier to the file that its package's `source` condition names, or nothing
|
|
26
|
-
* does not reach it.
|
|
25
|
+
* Resolves a bare specifier to the file that its package's `source` condition names, or to nothing when the
|
|
26
|
+
* condition does not reach it.
|
|
27
27
|
*
|
|
28
|
-
* Answers a bare specifier naming a package whose real directory
|
|
29
|
-
*
|
|
30
|
-
* `node_modules` -- so Vite resolves the specifier through its own conditions. Throws
|
|
31
|
-
* file, because falling through to the build output is the staleness that resolving from source exists
|
|
32
|
-
* and nothing in a run reports it.
|
|
28
|
+
* Answers a bare specifier naming a package whose real directory is outside every `node_modules`. Returns nothing
|
|
29
|
+
* for every other specifier -- such as a relative or absolute path, a virtual module, a builtin, or a package under
|
|
30
|
+
* `node_modules` -- so Vite resolves the specifier through its own conditions. Throws when the condition does not
|
|
31
|
+
* reach any file, because falling through to the build output is the staleness that resolving from source exists
|
|
32
|
+
* to prevent, and nothing in a run reports it.
|
|
33
33
|
*
|
|
34
|
-
*
|
|
35
|
-
* does,
|
|
34
|
+
* Because a query or hash suffix is split off before the `exports` lookup and re-appended to the resolved path, as
|
|
35
|
+
* Vite does, `pkg/icon.svg?raw` matches the `./icon.svg` entry and still reaches the plugin that reads the suffix.
|
|
36
36
|
*/
|
|
37
37
|
export declare function resolveSourceTarget(specifier: string, importer: string | undefined, context: SourceResolutionContext): string | undefined;
|
|
@@ -114,9 +114,9 @@ function findTargetFile(declaredPath) {
|
|
|
114
114
|
}
|
|
115
115
|
function formatMissingTarget(name, subpath, declaredTarget, target) {
|
|
116
116
|
return [
|
|
117
|
-
`Package "${name}" declares a \`source\` export for "${subpath}" at "${declaredTarget}", which
|
|
117
|
+
`Package "${name}" declares a \`source\` export for "${subpath}" at "${declaredTarget}", which does not reach any file.`,
|
|
118
118
|
`Looked for ${target}, the same path under each of ${TARGET_EXTENSIONS.join(', ')}, and an index under it.`,
|
|
119
|
-
'Point the condition at a file that exists, or remove it so the package resolves through its other conditions.',
|
|
119
|
+
'Point the condition at a file that exists, or remove it so that the package resolves through its other conditions.',
|
|
120
120
|
].join(' ');
|
|
121
121
|
}
|
|
122
122
|
function isFile(candidate) {
|
package/dist/esm/vitest.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ import type { InlineConfig, ProjectConfig } from 'vitest/node';
|
|
|
3
3
|
import { type TierName } from './tiers.js';
|
|
4
4
|
export type { TierName } from './tiers.js';
|
|
5
5
|
/**
|
|
6
|
-
* Test options Vitest honours only at the root of a `projects` config. Derived from Vitest's own types
|
|
7
|
-
*
|
|
6
|
+
* Test options that Vitest honours only at the root of a `projects` config. Derived from Vitest's own types, so an
|
|
7
|
+
* option that changes scope in a later release changes scope here too.
|
|
8
8
|
*/
|
|
9
9
|
export type RootTestOptions = Omit<InlineConfig, keyof ProjectConfig>;
|
|
10
10
|
/** Root-scoped overrides: Vite-level options plus the test options that survive at the root. */
|
|
@@ -16,7 +16,7 @@ export interface VitestConfigOptions {
|
|
|
16
16
|
* Merged into the root config, which every project inherits because each declares `extends: true`.
|
|
17
17
|
*
|
|
18
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
|
|
19
|
+
* condition meant for them goes under `ssr`; a top-level `resolve.conditions` entry applies to the client
|
|
20
20
|
* environment, which only browser-mode tests resolve through.
|
|
21
21
|
*/
|
|
22
22
|
root?: RootOverrides;
|
|
@@ -29,41 +29,42 @@ export interface VitestConfigOptions {
|
|
|
29
29
|
*/
|
|
30
30
|
tiers?: Partial<Record<TierName, ProjectConfig>>;
|
|
31
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
|
|
34
|
-
* winning, matching the config's rule for arrays.
|
|
32
|
+
* Directory basenames kept out of every project's collection, additive to the ones that the shared config always
|
|
33
|
+
* prunes and to the paths that git ignores. Each name is matched at any depth, and every layer's entries are
|
|
34
|
+
* concatenated rather than the last winning, matching the config's rule for arrays.
|
|
35
35
|
*
|
|
36
36
|
* The same array declares the scope of nmr's exported test-file conventions check, so the sweep and the
|
|
37
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
|
|
38
|
+
* reports nothing; excluding it from collection alone leaves a report that a consumer cannot act on. A repo wanting
|
|
39
39
|
* a glob rather than a directory name has the `project` seam's own `exclude`.
|
|
40
40
|
*/
|
|
41
41
|
testCollectionExclude?: readonly string[];
|
|
42
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
|
|
43
|
+
* Loads nmr's git-isolation setup file into every project, ahead of any that the layers supply. Defaults to `true`.
|
|
44
|
+
* Turn it off only when a suite is meant to read the developer's own git configuration.
|
|
45
45
|
*/
|
|
46
46
|
shouldIsolateGit?: boolean;
|
|
47
47
|
/**
|
|
48
|
-
* Resolves a package whose files
|
|
49
|
-
* suite runs without a prior build. Defaults to `true`. A package
|
|
50
|
-
* and so is every package under `node_modules`, which Node resolves and which the condition never
|
|
48
|
+
* Resolves a package whose files are outside every `node_modules` through its `source` export condition, so a
|
|
49
|
+
* suite runs without a prior build. Defaults to `true`. A package that does not declare a `source` condition is
|
|
50
|
+
* unaffected, and so is every package under `node_modules`, which Node resolves and to which the condition never
|
|
51
|
+
* applies.
|
|
51
52
|
*/
|
|
52
53
|
shouldResolveFromSource?: boolean;
|
|
53
54
|
/**
|
|
54
|
-
* Resolves a specifier through the `paths` a `tsconfig.json` declares,
|
|
55
|
-
*
|
|
55
|
+
* Resolves a test's aliased specifier through the `paths` that a `tsconfig.json` declares, the way `tsc` does.
|
|
56
|
+
* Defaults to `false`, matching Vite, and requires Vite 8.
|
|
56
57
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
58
|
+
* Without it, an alias fails loudly as an unresolved import. With it, a repo that declares `paths` for `tsc` alone
|
|
59
|
+
* changes which module a specifier reaches, and nothing in the run reports the change. A repo that does not declare
|
|
60
|
+
* `paths` is unaffected either way.
|
|
60
61
|
*/
|
|
61
62
|
tsconfigPaths?: boolean;
|
|
62
63
|
}
|
|
63
64
|
export interface RootVitestConfigOptions extends VitestConfigOptions {
|
|
64
65
|
/**
|
|
65
|
-
* An absolute path to the monorepo root, which must
|
|
66
|
-
* directory by construction, so this is `import.meta.dirname`. Stated rather than searched for:
|
|
66
|
+
* An absolute path to the monorepo root, which must contain `pnpm-workspace.yaml`. A root config is in that
|
|
67
|
+
* directory by construction, so this is `import.meta.dirname`. Stated rather than searched for: Resolving it
|
|
67
68
|
* from the working directory would make the config describe whichever monorepo the run happened to start in.
|
|
68
69
|
*/
|
|
69
70
|
monorepoRoot: string;
|
|
@@ -75,18 +76,18 @@ type RootConfigLayers = [...(VitestConfigOptions | undefined)[], RootVitestConfi
|
|
|
75
76
|
* projects. Select them at run time with `--project`, which unions when repeated and accepts negation.
|
|
76
77
|
*
|
|
77
78
|
* Layers fold left to right, later winning and arrays composing, so a config file shares settings by passing a
|
|
78
|
-
* layer ahead of its own. Merging two of this function's *outputs* is not the way:
|
|
79
|
+
* layer ahead of its own. Merging two of this function's *outputs* is not the way: Both declare the same four
|
|
79
80
|
* project names, which Vitest rejects at startup.
|
|
80
81
|
*
|
|
81
|
-
* An `undefined` layer is skipped,
|
|
82
|
+
* An `undefined` layer is skipped, which lets `defineVitestConfig(shared, isCI ? ciLayer : undefined)` compose.
|
|
82
83
|
*/
|
|
83
84
|
export declare function defineVitestConfig(...layers: (VitestConfigOptions | undefined)[]): ViteUserConfig;
|
|
84
85
|
/**
|
|
85
86
|
* Builds the shared Vitest config for repo-root tests. Excludes every workspace package from all projects, and
|
|
86
|
-
*
|
|
87
|
+
* does not report any coverage of its own; packages cover their own sources.
|
|
87
88
|
*
|
|
88
|
-
* `monorepoRoot`
|
|
89
|
+
* `monorepoRoot` is set on the last layer, which is the config file's own: A shared layer describes settings, not
|
|
89
90
|
* which repo they belong to, and only the root config's `import.meta.dirname` states this one. The guard below
|
|
90
|
-
* still catches the JavaScript config that
|
|
91
|
+
* still catches the JavaScript config that type checking never covers.
|
|
91
92
|
*/
|
|
92
93
|
export declare function defineRootVitestConfig(...layers: RootConfigLayers): ViteUserConfig;
|
package/dist/esm/vitest.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
import { defaultExclude, mergeConfig } from 'vitest/config';
|
|
4
|
+
import { listGitIgnoredPaths } from "./git-ignored-paths.js";
|
|
4
5
|
import { isObject } from "./helpers/type-guards.js";
|
|
5
6
|
import { ALL_TEST_PATTERNS, buildTierPatterns, TEST_COLLECTION_EXCLUDE, TIER_NAMES } from "./tiers.js";
|
|
6
7
|
import { createSourceResolutionPlugin } from "./vitest-source-resolution.js";
|
|
@@ -13,6 +14,7 @@ const CLIENT_CONDITIONS = ['module', 'browser', 'development|production'];
|
|
|
13
14
|
const SERVER_CONDITIONS = ['module', 'node', 'development|production'];
|
|
14
15
|
const COVERAGE_EXCLUDE = ['**/__{fixtures,mocks,tests}__/**', '**/index.ts', '**/*.d.ts'];
|
|
15
16
|
const PACKAGE_COVERAGE_INCLUDE = ['**/src/**/*.{ts,tsx}'];
|
|
17
|
+
const GLOB_SYNTAX_PATTERN = /[!()*+?@[\]{}]/g;
|
|
16
18
|
const RECOGNIZED_OPTIONS = {
|
|
17
19
|
project: true,
|
|
18
20
|
root: true,
|
|
@@ -32,10 +34,10 @@ const RETIRED_OPTION_KEYS = new Map([
|
|
|
32
34
|
['isolateGit', 'shouldIsolateGit'],
|
|
33
35
|
['resolveFromSource', 'shouldResolveFromSource'],
|
|
34
36
|
]);
|
|
35
|
-
const MISSING_MONOREPO_ROOT = 'defineRootVitestConfig requires `monorepoRoot`, an absolute path to the directory
|
|
37
|
+
const MISSING_MONOREPO_ROOT = 'defineRootVitestConfig requires `monorepoRoot`, an absolute path to the directory that contains pnpm-workspace.yaml. Pass `import.meta.dirname` in the last options layer, from the root config.';
|
|
36
38
|
export function defineVitestConfig(...layers) {
|
|
37
39
|
assertOptionKeys(layers, RECOGNIZED_OPTION_KEYS);
|
|
38
|
-
return buildConfig(layers, { coverageInclude: PACKAGE_COVERAGE_INCLUDE });
|
|
40
|
+
return buildConfig(layers, { coverageInclude: PACKAGE_COVERAGE_INCLUDE, ignoredPathsRoot: process.cwd() });
|
|
39
41
|
}
|
|
40
42
|
export function defineRootVitestConfig(...layers) {
|
|
41
43
|
const lastLayer = layers.at(-1);
|
|
@@ -46,11 +48,12 @@ export function defineRootVitestConfig(...layers) {
|
|
|
46
48
|
assertOptionKeys(layers, RECOGNIZED_ROOT_OPTION_KEYS);
|
|
47
49
|
return buildConfig(layers, {
|
|
48
50
|
coverageInclude: [],
|
|
51
|
+
ignoredPathsRoot: monorepoRoot,
|
|
49
52
|
projectExclude: getWorkspaceExcludePatterns(monorepoRoot),
|
|
50
53
|
projectRoot: monorepoRoot,
|
|
51
54
|
});
|
|
52
55
|
}
|
|
53
|
-
function buildConfig(declaredLayers, { coverageInclude, projectExclude = [], projectRoot }) {
|
|
56
|
+
function buildConfig(declaredLayers, { coverageInclude, ignoredPathsRoot, projectExclude = [], projectRoot }) {
|
|
54
57
|
const layers = declaredLayers.filter((layer) => layer !== undefined);
|
|
55
58
|
assertKnownTiers(layers);
|
|
56
59
|
const resolve = {
|
|
@@ -69,7 +72,7 @@ function buildConfig(declaredLayers, { coverageInclude, projectExclude = [], pro
|
|
|
69
72
|
provider: 'v8',
|
|
70
73
|
},
|
|
71
74
|
passWithNoTests: true,
|
|
72
|
-
projects: buildProjects(layers, projectExclude, projectRoot),
|
|
75
|
+
projects: buildProjects(layers, projectExclude, projectRoot, ignoredPathsRoot),
|
|
73
76
|
silent: 'passed-only',
|
|
74
77
|
watch: false,
|
|
75
78
|
},
|
|
@@ -81,7 +84,7 @@ function buildConfig(declaredLayers, { coverageInclude, projectExclude = [], pro
|
|
|
81
84
|
}
|
|
82
85
|
return mergedConfig;
|
|
83
86
|
}
|
|
84
|
-
function buildProjects(layers, extraExclude, projectRoot) {
|
|
87
|
+
function buildProjects(layers, extraExclude, projectRoot, ignoredPathsRoot) {
|
|
85
88
|
const projectTiers = [
|
|
86
89
|
{ exclude: TIERED_PATTERNS, include: ALL_TEST_PATTERNS, name: RESIDUAL_TIER },
|
|
87
90
|
...NAMED_TIERS.map((tier) => ({
|
|
@@ -91,7 +94,7 @@ function buildProjects(layers, extraExclude, projectRoot) {
|
|
|
91
94
|
timeoutMs: TIER_TIMEOUT_MS,
|
|
92
95
|
})),
|
|
93
96
|
];
|
|
94
|
-
const collectionExclude = buildCollectionExclude(layers);
|
|
97
|
+
const collectionExclude = buildCollectionExclude(layers, ignoredPathsRoot);
|
|
95
98
|
const shouldIsolateGit = resolveFlag(layers, 'shouldIsolateGit', true);
|
|
96
99
|
return projectTiers.map(({ exclude, include, name, timeoutMs }) => {
|
|
97
100
|
const project = {
|
|
@@ -150,10 +153,15 @@ function assertOptionKeys(layers, lastLayerKeys) {
|
|
|
150
153
|
`${formatOptionKeys(unrecognizedKeys)}. Recognized: ${formatOptionKeys(recognizedKeys)}.`);
|
|
151
154
|
});
|
|
152
155
|
}
|
|
153
|
-
function buildCollectionExclude(layers) {
|
|
156
|
+
function buildCollectionExclude(layers, ignoredPathsRoot) {
|
|
154
157
|
const declaredDirs = layers.flatMap((layer) => layer.testCollectionExclude ?? []);
|
|
155
158
|
const globs = [...TEST_COLLECTION_EXCLUDE, ...declaredDirs].map((dir) => `**/${dir}/**`);
|
|
156
|
-
|
|
159
|
+
const ignoredGlobs = listGitIgnoredPaths(ignoredPathsRoot).map(buildIgnoredPathGlob);
|
|
160
|
+
return [...new Set([...defaultExclude, ...globs, ...ignoredGlobs])];
|
|
161
|
+
}
|
|
162
|
+
function buildIgnoredPathGlob(ignoredPath) {
|
|
163
|
+
const escapedPath = ignoredPath.replaceAll(GLOB_SYNTAX_PATTERN, String.raw `\$&`);
|
|
164
|
+
return escapedPath.endsWith('/') ? `${escapedPath}**` : escapedPath;
|
|
157
165
|
}
|
|
158
166
|
function formatOptionKeys(keys) {
|
|
159
167
|
return keys
|
package/dist/esm/workspace.d.ts
CHANGED
|
@@ -2,14 +2,14 @@ export type { EmptyWorkspaceCause, WorkspaceResolution } from '@williamthorsen/n
|
|
|
2
2
|
export { isMonorepoRoot, readWorkspaceOverrides, readWorkspacePackageNames, resolveWorkspace, } from '@williamthorsen/nmr-core/workspace';
|
|
3
3
|
/**
|
|
4
4
|
* Finds the monorepo root by walking up from `startDir` to find `pnpm-workspace.yaml`.
|
|
5
|
-
* Throws if
|
|
5
|
+
* Throws if it does not find a workspace root in `startDir` or any of its parent directories.
|
|
6
6
|
*/
|
|
7
7
|
export declare function findMonorepoRoot(startDir?: string): string;
|
|
8
8
|
/**
|
|
9
9
|
* Reads the workspace patterns from `pnpm-workspace.yaml` and resolves them to absolute package
|
|
10
|
-
* directories, applying pnpm's pattern semantics
|
|
10
|
+
* directories, applying pnpm's pattern semantics, including `!`-prefixed exclusions.
|
|
11
11
|
*
|
|
12
|
-
* Returns an empty array when the manifest
|
|
13
|
-
* `monorepoRoot`
|
|
12
|
+
* Returns an empty array when the manifest does not declare a usable `packages` list, and throws when
|
|
13
|
+
* `monorepoRoot` does not contain a manifest at all.
|
|
14
14
|
*/
|
|
15
15
|
export declare function getWorkspacePackageDirs(monorepoRoot: string): string[];
|