as-test 1.5.1 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/bin/commands/build-core.js +4 -63
- package/bin/commands/run-core.js +4 -64
- package/bin/index.js +4 -24
- package/bin/selectors.js +208 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 2026-06-01 - v1.5.2
|
|
4
|
+
|
|
5
|
+
### Selectors resolve folders, files, and globs consistently
|
|
6
|
+
|
|
7
|
+
- feat: positional selectors for `ast test`/`ast run`/`ast build` now resolve through a single shared resolver (`cli/selectors.ts:resolveSpecFiles`), replacing three drifting private copies of `resolveInputPatterns` (in `build-core`, `run-core`, and `index`). Three input shapes are supported:
|
|
8
|
+
- **Bare folders/files/globs** (no leading `./`) resolve against the configured input root(s) — the static prefix of each `input` glob, e.g. `assembly/__tests__` — searched recursively, and fall back to the cwd only if nothing matched there: `ast test rfc/` → `<root>/**/rfc/**/*.spec.ts`; `ast test foo` → `<root>/**/foo.spec.ts`; `ast test 'rfc/*.spec.ts'` → `<root>/**/rfc/*.spec.ts` (the user's glob appended verbatim). A bare path shorthand like `nested/array` is tried as a cwd path first, then anchored to the test folder.
|
|
9
|
+
- **`./`-prefixed** selectors (and absolute / `~` paths) are cwd-relative only; on a miss we emit a `did you mean "rfc/*.spec.ts"` hint pointing at the test-folder form when that would have matched.
|
|
10
|
+
- feat: a bare selector that matches under more than one configured input root is flagged with a `WARN` (it still runs everything that matched), and a selector that matches nothing emits a `WARN` naming where it looked. Warnings are deduped by text across the orchestrator + per-file build/run passes (`emitSelectorWarnings`), so each prints once per invocation. Folder selectors (`rfc/`) and `,`-joined bare names (`a,b`) are recognized; selectors with an internal path separator (e.g. the orchestrator's own `assembly/__tests__/foo.spec.ts`) are still treated as direct cwd paths, preserving existing per-file dispatch.
|
|
11
|
+
|
|
3
12
|
## 2026-05-30 - v1.5.1
|
|
4
13
|
|
|
5
14
|
### An early-exiting runtime now fails instead of warning
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
} from "../util.js";
|
|
23
23
|
import { persistCrashRecord } from "../crash-store.js";
|
|
24
24
|
import { BuildWorkerPool } from "../build-worker-pool.js";
|
|
25
|
+
import { resolveSpecFiles, emitSelectorWarnings } from "../selectors.js";
|
|
25
26
|
const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
|
|
26
27
|
export const buildRecorderStorage = new AsyncLocalStorage();
|
|
27
28
|
export class BuildFailureError extends Error {
|
|
@@ -67,14 +68,9 @@ export async function build(
|
|
|
67
68
|
const pkgRunner = getPkgRunner();
|
|
68
69
|
const sourceInputPatterns =
|
|
69
70
|
overrides.kind === "fuzz" ? config.fuzz.input : config.input;
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
.filter((p) => p.startsWith("!"))
|
|
74
|
-
.map((p) => p.slice(1));
|
|
75
|
-
const inputFiles = (
|
|
76
|
-
await glob(includePatterns, { ignore: ignorePatterns })
|
|
77
|
-
).sort((a, b) => a.localeCompare(b));
|
|
71
|
+
const { files: inputFiles, warnings: selectorWarnings } =
|
|
72
|
+
await resolveSpecFiles(sourceInputPatterns, selectors);
|
|
73
|
+
emitSelectorWarnings(selectorWarnings);
|
|
78
74
|
await assertNoArtifactCollisions(sourceInputPatterns);
|
|
79
75
|
warnOnUnknownModeReferences(inputFiles, loadedConfig.modes ?? {});
|
|
80
76
|
const coverageEnabled = resolveCoverageEnabled(
|
|
@@ -540,61 +536,6 @@ async function assertNoArtifactCollisions(configured) {
|
|
|
540
536
|
seen.set(artifact, file);
|
|
541
537
|
}
|
|
542
538
|
}
|
|
543
|
-
function resolveInputPatterns(configured, selectors) {
|
|
544
|
-
const configuredInputs = Array.isArray(configured)
|
|
545
|
-
? configured
|
|
546
|
-
: [configured];
|
|
547
|
-
if (!selectors.length) return configuredInputs;
|
|
548
|
-
const patterns = new Set();
|
|
549
|
-
for (const selector of expandSelectors(selectors)) {
|
|
550
|
-
if (!selector) continue;
|
|
551
|
-
if (isBareSuiteSelector(selector)) {
|
|
552
|
-
const base = stripSuiteSuffix(selector);
|
|
553
|
-
for (const configuredInput of configuredInputs) {
|
|
554
|
-
patterns.add(
|
|
555
|
-
path.join(path.dirname(configuredInput), `${base}.spec.ts`),
|
|
556
|
-
);
|
|
557
|
-
}
|
|
558
|
-
continue;
|
|
559
|
-
}
|
|
560
|
-
patterns.add(selector);
|
|
561
|
-
}
|
|
562
|
-
return [...patterns];
|
|
563
|
-
}
|
|
564
|
-
function expandSelectors(selectors) {
|
|
565
|
-
const expanded = [];
|
|
566
|
-
for (const selector of selectors) {
|
|
567
|
-
if (!selector) continue;
|
|
568
|
-
if (!shouldSplitSelector(selector)) {
|
|
569
|
-
expanded.push(selector);
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
for (const token of selector.split(",")) {
|
|
573
|
-
const trimmed = token.trim();
|
|
574
|
-
if (!trimmed.length) continue;
|
|
575
|
-
expanded.push(trimmed);
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
return expanded;
|
|
579
|
-
}
|
|
580
|
-
function shouldSplitSelector(selector) {
|
|
581
|
-
return (
|
|
582
|
-
selector.includes(",") &&
|
|
583
|
-
!selector.includes("/") &&
|
|
584
|
-
!selector.includes("\\") &&
|
|
585
|
-
!/[*?[\]{}]/.test(selector)
|
|
586
|
-
);
|
|
587
|
-
}
|
|
588
|
-
function isBareSuiteSelector(selector) {
|
|
589
|
-
return (
|
|
590
|
-
!selector.includes("/") &&
|
|
591
|
-
!selector.includes("\\") &&
|
|
592
|
-
!/[*?[\]{}]/.test(selector)
|
|
593
|
-
);
|
|
594
|
-
}
|
|
595
|
-
function stripSuiteSuffix(selector) {
|
|
596
|
-
return selector.replace(/\.spec\.ts$/, "").replace(/\.ts$/, "");
|
|
597
|
-
}
|
|
598
539
|
function ensureDeps(config) {
|
|
599
540
|
if (config.buildOptions.target == "wasi") {
|
|
600
541
|
if (!resolveWasiShim()) {
|
package/bin/commands/run-core.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { spawn } from "child_process";
|
|
3
|
-
import { glob } from "glob";
|
|
4
3
|
import { minimatch } from "minimatch";
|
|
5
4
|
import { Channel, MessageType } from "../wipc.js";
|
|
6
5
|
import {
|
|
@@ -21,6 +20,7 @@ import { PassThrough } from "stream";
|
|
|
21
20
|
import { buildWebRunnerSource } from "./web-runner-source.js";
|
|
22
21
|
import { PersistentWebSessionHost } from "./web-session.js";
|
|
23
22
|
import { build } from "./build-core.js";
|
|
23
|
+
import { resolveSpecFiles, emitSelectorWarnings } from "../selectors.js";
|
|
24
24
|
import { createReporter as createDefaultReporter } from "../reporters/default.js";
|
|
25
25
|
import { createTapReporter } from "../reporters/tap.js";
|
|
26
26
|
import { persistCrashRecord } from "../crash-store.js";
|
|
@@ -733,14 +733,9 @@ export async function run(
|
|
|
733
733
|
}
|
|
734
734
|
}
|
|
735
735
|
}
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
.filter((p) => p.startsWith("!"))
|
|
740
|
-
.map((p) => p.slice(1));
|
|
741
|
-
const inputFiles = (
|
|
742
|
-
await glob(includePatterns, { ignore: ignorePatterns })
|
|
743
|
-
).sort((a, b) => a.localeCompare(b));
|
|
736
|
+
const { files: inputFiles, warnings: selectorWarnings } =
|
|
737
|
+
await resolveSpecFiles(config.input, selectors);
|
|
738
|
+
emitSelectorWarnings(selectorWarnings);
|
|
744
739
|
const snapshotEnabled = flags.snapshot !== false;
|
|
745
740
|
const createSnapshots = Boolean(flags.createSnapshots);
|
|
746
741
|
const overwriteSnapshots = Boolean(flags.overwriteSnapshots);
|
|
@@ -1364,61 +1359,6 @@ function runtimeNameFromCommand(command) {
|
|
|
1364
1359
|
const token = command.trim().split(/\s+/)[0];
|
|
1365
1360
|
return token && token.length ? token : "runtime";
|
|
1366
1361
|
}
|
|
1367
|
-
function resolveInputPatterns(configured, selectors) {
|
|
1368
|
-
const configuredInputs = Array.isArray(configured)
|
|
1369
|
-
? configured
|
|
1370
|
-
: [configured];
|
|
1371
|
-
if (!selectors.length) return configuredInputs;
|
|
1372
|
-
const patterns = new Set();
|
|
1373
|
-
for (const selector of expandSelectors(selectors)) {
|
|
1374
|
-
if (!selector) continue;
|
|
1375
|
-
if (isBareSuiteSelector(selector)) {
|
|
1376
|
-
const base = stripSuiteSuffix(selector);
|
|
1377
|
-
for (const configuredInput of configuredInputs) {
|
|
1378
|
-
patterns.add(
|
|
1379
|
-
path.join(path.dirname(configuredInput), `${base}.spec.ts`),
|
|
1380
|
-
);
|
|
1381
|
-
}
|
|
1382
|
-
continue;
|
|
1383
|
-
}
|
|
1384
|
-
patterns.add(selector);
|
|
1385
|
-
}
|
|
1386
|
-
return [...patterns];
|
|
1387
|
-
}
|
|
1388
|
-
function expandSelectors(selectors) {
|
|
1389
|
-
const expanded = [];
|
|
1390
|
-
for (const selector of selectors) {
|
|
1391
|
-
if (!selector) continue;
|
|
1392
|
-
if (!shouldSplitSelector(selector)) {
|
|
1393
|
-
expanded.push(selector);
|
|
1394
|
-
continue;
|
|
1395
|
-
}
|
|
1396
|
-
for (const token of selector.split(",")) {
|
|
1397
|
-
const trimmed = token.trim();
|
|
1398
|
-
if (!trimmed.length) continue;
|
|
1399
|
-
expanded.push(trimmed);
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
return expanded;
|
|
1403
|
-
}
|
|
1404
|
-
function shouldSplitSelector(selector) {
|
|
1405
|
-
return (
|
|
1406
|
-
selector.includes(",") &&
|
|
1407
|
-
!selector.includes("/") &&
|
|
1408
|
-
!selector.includes("\\") &&
|
|
1409
|
-
!/[*?[\]{}]/.test(selector)
|
|
1410
|
-
);
|
|
1411
|
-
}
|
|
1412
|
-
function isBareSuiteSelector(selector) {
|
|
1413
|
-
return (
|
|
1414
|
-
!selector.includes("/") &&
|
|
1415
|
-
!selector.includes("\\") &&
|
|
1416
|
-
!/[*?[\]{}]/.test(selector)
|
|
1417
|
-
);
|
|
1418
|
-
}
|
|
1419
|
-
function stripSuiteSuffix(selector) {
|
|
1420
|
-
return selector.replace(/\.spec\.ts$/, "").replace(/\.ts$/, "");
|
|
1421
|
-
}
|
|
1422
1362
|
function normalizeReport(raw) {
|
|
1423
1363
|
if (Array.isArray(raw)) {
|
|
1424
1364
|
return {
|
package/bin/index.js
CHANGED
|
@@ -41,6 +41,7 @@ import { BuildWorkerPool } from "./build-worker-pool.js";
|
|
|
41
41
|
import { PersistentWebSessionHost } from "./commands/web-session.js";
|
|
42
42
|
import { buildRecorderStorage } from "./commands/build-core.js";
|
|
43
43
|
import { DependencyGraph } from "./dependency-graph.js";
|
|
44
|
+
import { resolveSpecFiles, emitSelectorWarnings } from "./selectors.js";
|
|
44
45
|
const _args = process.argv.slice(2);
|
|
45
46
|
const flags = [];
|
|
46
47
|
const args = [];
|
|
@@ -3855,9 +3856,9 @@ async function resolveSelectedFiles(configPath, selectors, warn = true) {
|
|
|
3855
3856
|
const resolvedConfigPath =
|
|
3856
3857
|
configPath ?? path.join(process.cwd(), "./as-test.config.json");
|
|
3857
3858
|
const config = loadConfig(resolvedConfigPath, warn);
|
|
3858
|
-
const
|
|
3859
|
-
|
|
3860
|
-
const specs =
|
|
3859
|
+
const { files, warnings } = await resolveSpecFiles(config.input, selectors);
|
|
3860
|
+
if (warn) emitSelectorWarnings(warnings);
|
|
3861
|
+
const specs = files.filter((file) => file.endsWith(".spec.ts"));
|
|
3861
3862
|
return [...new Set(specs)].sort((a, b) => a.localeCompare(b));
|
|
3862
3863
|
}
|
|
3863
3864
|
async function resolveSelectedFuzzFiles(
|
|
@@ -3996,27 +3997,6 @@ function levenshteinDistance(left, right) {
|
|
|
3996
3997
|
}
|
|
3997
3998
|
return matrix[left.length][right.length];
|
|
3998
3999
|
}
|
|
3999
|
-
function resolveInputPatterns(configured, selectors) {
|
|
4000
|
-
const configuredInputs = Array.isArray(configured)
|
|
4001
|
-
? configured
|
|
4002
|
-
: [configured];
|
|
4003
|
-
if (!selectors.length) return configuredInputs;
|
|
4004
|
-
const patterns = new Set();
|
|
4005
|
-
for (const selector of expandSelectors(selectors)) {
|
|
4006
|
-
if (!selector) continue;
|
|
4007
|
-
if (isBareSuiteSelector(selector)) {
|
|
4008
|
-
const base = stripSuiteSuffix(selector);
|
|
4009
|
-
for (const configuredInput of configuredInputs) {
|
|
4010
|
-
patterns.add(
|
|
4011
|
-
path.join(path.dirname(configuredInput), `${base}.spec.ts`),
|
|
4012
|
-
);
|
|
4013
|
-
}
|
|
4014
|
-
continue;
|
|
4015
|
-
}
|
|
4016
|
-
patterns.add(selector);
|
|
4017
|
-
}
|
|
4018
|
-
return [...patterns];
|
|
4019
|
-
}
|
|
4020
4000
|
function resolveFuzzPatterns(configured, selectors) {
|
|
4021
4001
|
const configuredInputs = Array.isArray(configured)
|
|
4022
4002
|
? configured
|
package/bin/selectors.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { glob } from "glob";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
// Selector resolution runs in several places per command (the orchestrator,
|
|
5
|
+
// then build/run cores per file); dedupe by text so a warning prints once per
|
|
6
|
+
// process regardless of how many resolvers see the same selector.
|
|
7
|
+
const reportedSelectorWarnings = new Set();
|
|
8
|
+
export function emitSelectorWarnings(warnings) {
|
|
9
|
+
for (const warning of warnings) {
|
|
10
|
+
if (reportedSelectorWarnings.has(warning)) continue;
|
|
11
|
+
reportedSelectorWarnings.add(warning);
|
|
12
|
+
process.stderr.write(`${chalk.yellow.bold("WARN")} ${warning}\n`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const GLOB_MAGIC = /[*?[\]{}]/;
|
|
16
|
+
function hasGlobMagic(selector) {
|
|
17
|
+
return GLOB_MAGIC.test(selector);
|
|
18
|
+
}
|
|
19
|
+
function endsWithSlash(selector) {
|
|
20
|
+
return /[\\/]$/.test(selector);
|
|
21
|
+
}
|
|
22
|
+
function stripTrailingSlash(selector) {
|
|
23
|
+
return selector.replace(/[\\/]+$/, "");
|
|
24
|
+
}
|
|
25
|
+
function stripSuiteSuffix(selector) {
|
|
26
|
+
return selector.replace(/\.spec\.ts$/, "").replace(/\.ts$/, "");
|
|
27
|
+
}
|
|
28
|
+
function isCwdRelative(selector) {
|
|
29
|
+
return (
|
|
30
|
+
selector.startsWith("./") ||
|
|
31
|
+
selector.startsWith("../") ||
|
|
32
|
+
selector.startsWith(".\\") ||
|
|
33
|
+
selector.startsWith("..\\") ||
|
|
34
|
+
selector.startsWith("/") ||
|
|
35
|
+
selector.startsWith("~") ||
|
|
36
|
+
path.isAbsolute(selector)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
// A selector with a path separator that is not merely a single trailing slash
|
|
40
|
+
// (e.g. `assembly/__tests__/foo.spec.ts`, passed verbatim by the orchestrator)
|
|
41
|
+
// is treated as a direct cwd-relative path rather than a test-folder alias.
|
|
42
|
+
function hasInternalSlash(selector) {
|
|
43
|
+
return /[\\/]/.test(stripTrailingSlash(selector));
|
|
44
|
+
}
|
|
45
|
+
// The longest leading run of path segments containing no glob magic — the
|
|
46
|
+
// static "test folder" of an input pattern (`assembly/__tests__/**/*.spec.ts`
|
|
47
|
+
// -> `assembly/__tests__`).
|
|
48
|
+
function globBase(pattern) {
|
|
49
|
+
const segments = pattern.split("/");
|
|
50
|
+
const base = [];
|
|
51
|
+
for (const segment of segments) {
|
|
52
|
+
if (hasGlobMagic(segment)) break;
|
|
53
|
+
base.push(segment);
|
|
54
|
+
}
|
|
55
|
+
return base.join("/") || ".";
|
|
56
|
+
}
|
|
57
|
+
function uniqueInputRoots(configuredInputs) {
|
|
58
|
+
const roots = new Set();
|
|
59
|
+
for (const pattern of configuredInputs) {
|
|
60
|
+
if (pattern.startsWith("!")) continue;
|
|
61
|
+
roots.add(globBase(pattern));
|
|
62
|
+
}
|
|
63
|
+
return [...roots];
|
|
64
|
+
}
|
|
65
|
+
// Turn a cwd-relative selector into the spec glob(s) it stands for.
|
|
66
|
+
function cwdPatterns(selector) {
|
|
67
|
+
if (endsWithSlash(selector)) {
|
|
68
|
+
return [`${stripTrailingSlash(selector)}/**/*.spec.ts`];
|
|
69
|
+
}
|
|
70
|
+
if (/\.ts$/.test(selector)) return [selector];
|
|
71
|
+
return [`${stripSuiteSuffix(selector)}.spec.ts`];
|
|
72
|
+
}
|
|
73
|
+
// Turn a bare selector into the spec glob(s) it stands for, anchored to a
|
|
74
|
+
// configured input root and searched recursively beneath it. A selector that
|
|
75
|
+
// already carries glob magic (`rfc/*.spec.ts`, `*.spec.ts`) is appended
|
|
76
|
+
// verbatim so the user's pattern controls the match; a plain folder/name has
|
|
77
|
+
// the spec suffix supplied.
|
|
78
|
+
function barePatterns(root, selector) {
|
|
79
|
+
if (hasGlobMagic(selector)) {
|
|
80
|
+
return [`${root}/**/${selector}`];
|
|
81
|
+
}
|
|
82
|
+
if (endsWithSlash(selector)) {
|
|
83
|
+
return [`${root}/**/${stripTrailingSlash(selector)}/**/*.spec.ts`];
|
|
84
|
+
}
|
|
85
|
+
return [`${root}/**/${stripSuiteSuffix(selector)}.spec.ts`];
|
|
86
|
+
}
|
|
87
|
+
// Split comma-joined bare selectors (`a,b,c`) while leaving paths and globs
|
|
88
|
+
// (which can legitimately contain commas, e.g. `{a,b}`) intact.
|
|
89
|
+
function expandSelectors(selectors) {
|
|
90
|
+
const expanded = [];
|
|
91
|
+
for (const selector of selectors) {
|
|
92
|
+
if (!selector) continue;
|
|
93
|
+
if (
|
|
94
|
+
selector.includes(",") &&
|
|
95
|
+
!hasInternalSlash(selector) &&
|
|
96
|
+
!endsWithSlash(selector) &&
|
|
97
|
+
!hasGlobMagic(selector)
|
|
98
|
+
) {
|
|
99
|
+
for (const token of selector.split(",")) {
|
|
100
|
+
const trimmed = token.trim();
|
|
101
|
+
if (trimmed.length) expanded.push(trimmed);
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
expanded.push(selector);
|
|
106
|
+
}
|
|
107
|
+
return expanded;
|
|
108
|
+
}
|
|
109
|
+
async function globFiles(patterns) {
|
|
110
|
+
return glob(patterns);
|
|
111
|
+
}
|
|
112
|
+
async function resolveSelector(selector, inputRoots) {
|
|
113
|
+
const warnings = [];
|
|
114
|
+
const isGlob = hasGlobMagic(selector);
|
|
115
|
+
// Explicit cwd-relative selector (`./`, `../`, absolute, `~`) — resolve from
|
|
116
|
+
// the cwd only. A glob is matched verbatim; a plain path gets the spec suffix.
|
|
117
|
+
if (isCwdRelative(selector)) {
|
|
118
|
+
const files = await globFiles(isGlob ? [selector] : cwdPatterns(selector));
|
|
119
|
+
if (!files.length) {
|
|
120
|
+
const bare = selector.replace(/^\.[\\/]/, "");
|
|
121
|
+
let suggestion = null;
|
|
122
|
+
for (const root of inputRoots) {
|
|
123
|
+
const inRoot = await globFiles(barePatterns(root, bare));
|
|
124
|
+
if (inRoot.length) {
|
|
125
|
+
suggestion = bare;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
warnings.push(
|
|
130
|
+
suggestion
|
|
131
|
+
? `"${selector}" not found relative to the current directory — did you mean "${suggestion}" (searches the configured test folder)?`
|
|
132
|
+
: `"${selector}" not found relative to the current directory`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return { files, warnings };
|
|
136
|
+
}
|
|
137
|
+
// A plain path with an internal separator (e.g. the orchestrator's own
|
|
138
|
+
// `assembly/__tests__/foo.spec.ts`) resolves from the cwd verbatim. Globs
|
|
139
|
+
// skip this and fall through to test-folder anchoring below.
|
|
140
|
+
if (!isGlob && hasInternalSlash(selector)) {
|
|
141
|
+
const direct = await globFiles(cwdPatterns(selector));
|
|
142
|
+
if (direct.length) return { files: direct, warnings };
|
|
143
|
+
// Fall through to test-folder resolution for user shorthands like
|
|
144
|
+
// `nested/array` that aren't a real cwd path.
|
|
145
|
+
}
|
|
146
|
+
// Bare name/folder or relative glob — configured input root(s) first.
|
|
147
|
+
const perRoot = [];
|
|
148
|
+
for (const root of inputRoots) {
|
|
149
|
+
const files = await globFiles(barePatterns(root, selector));
|
|
150
|
+
if (files.length) perRoot.push({ root, files });
|
|
151
|
+
}
|
|
152
|
+
if (perRoot.length) {
|
|
153
|
+
if (perRoot.length > 1) {
|
|
154
|
+
warnings.push(
|
|
155
|
+
`selector "${selector}" matched specs under ${perRoot.length} input roots (${perRoot
|
|
156
|
+
.map((entry) => entry.root)
|
|
157
|
+
.join(", ")}) — running all of them`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return { files: perRoot.flatMap((entry) => entry.files), warnings };
|
|
161
|
+
}
|
|
162
|
+
// Fall back to the cwd before giving up.
|
|
163
|
+
const cwdFiles = await globFiles(
|
|
164
|
+
isGlob ? [selector] : cwdPatterns(`./${selector}`),
|
|
165
|
+
);
|
|
166
|
+
if (cwdFiles.length) {
|
|
167
|
+
return { files: cwdFiles, warnings };
|
|
168
|
+
}
|
|
169
|
+
warnings.push(
|
|
170
|
+
inputRoots.length
|
|
171
|
+
? `no spec files matched "${selector}" in ${inputRoots.join(", ")} or the current directory`
|
|
172
|
+
: `no spec files matched "${selector}"`,
|
|
173
|
+
);
|
|
174
|
+
return { files: [], warnings };
|
|
175
|
+
}
|
|
176
|
+
// Resolve configured input patterns + positional selectors into the concrete
|
|
177
|
+
// set of spec files to act on, along with any human-readable warnings. With no
|
|
178
|
+
// selectors this is just the configured globs (honoring `!`-negations); with
|
|
179
|
+
// selectors the per-selector rules above apply and config negations are
|
|
180
|
+
// intentionally bypassed so an explicit pick always wins.
|
|
181
|
+
export async function resolveSpecFiles(configured, selectors) {
|
|
182
|
+
const configuredInputs = Array.isArray(configured)
|
|
183
|
+
? configured
|
|
184
|
+
: [configured];
|
|
185
|
+
if (!selectors.length) {
|
|
186
|
+
const include = configuredInputs.filter((p) => !p.startsWith("!"));
|
|
187
|
+
const ignore = configuredInputs
|
|
188
|
+
.filter((p) => p.startsWith("!"))
|
|
189
|
+
.map((p) => p.slice(1));
|
|
190
|
+
const files = (await glob(include, { ignore })).sort((a, b) =>
|
|
191
|
+
a.localeCompare(b),
|
|
192
|
+
);
|
|
193
|
+
return { files, warnings: [] };
|
|
194
|
+
}
|
|
195
|
+
const inputRoots = uniqueInputRoots(configuredInputs);
|
|
196
|
+
const files = new Set();
|
|
197
|
+
const warnings = [];
|
|
198
|
+
for (const selector of expandSelectors(selectors)) {
|
|
199
|
+
if (!selector) continue;
|
|
200
|
+
const resolved = await resolveSelector(selector, inputRoots);
|
|
201
|
+
for (const file of resolved.files) files.add(file);
|
|
202
|
+
warnings.push(...resolved.warnings);
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
files: [...files].sort((a, b) => a.localeCompare(b)),
|
|
206
|
+
warnings,
|
|
207
|
+
};
|
|
208
|
+
}
|