@useblacksmith/vitest 0.0.0-stage → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Blacksmith Software, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,59 @@
1
- # Temporary Holding Version
1
+ # @useblacksmith/vitest
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Speed up vitest execution by only running tests impacted by a change, with automatic caching when run on Blacksmith.
4
+
5
+ > **Alpha.** The plugin is under active development; options and the cache format may change between releases.
6
+
7
+ ## Usage
8
+
9
+ ```sh
10
+ npm install --save-dev @useblacksmith/vitest
11
+ ```
12
+
13
+ ```ts
14
+ // vitest.config.ts
15
+ import { defineConfig } from 'vitest/config'
16
+ import blacksmith from '@useblacksmith/vitest'
17
+
18
+ export default defineConfig({
19
+ plugins: [blacksmith()],
20
+ })
21
+ ```
22
+
23
+ With the plugin, vitest only runs tests impacted by changes since the last successful run. `--shard=i/n` works as usual; the plugin assigns files to shards itself so each shard's cache stays stable.
24
+
25
+ Requires Vitest 5.1 or newer.
26
+
27
+ ## Running locally
28
+
29
+ The same selection applies locally: a run reruns what changed on disk since the last successful run, committed or not. To run every test locally and select only in CI:
30
+
31
+ ```ts
32
+ plugins: [blacksmith({ runAllTests: !process.env.CI })],
33
+ ```
34
+
35
+ ## How it works
36
+
37
+ The plugin uses Vitest's dependency tracking: each run records which modules and files every test depends on, and the next run compares the checkout against that record to calculate the impacted test set; a test the last successful run did not cover always runs. The record is stored in the Actions cache when run on Blacksmith, so runs on different runners pick up where the last successful run left off.
38
+
39
+ ## Options
40
+
41
+ | option | default | effect |
42
+ |---|---|---|
43
+ | `runAllTests` | `false` | Run every test instead of the affected selection. |
44
+ | `shadow` | `false` | Run every test, but log what the affected selection would have run and which failing test files it would have skipped, i.e. failures the selection would have missed. Use it to evaluate the plugin on a repo before relying on the selection. |
45
+ | `dependencies` | `{}` | Dependencies the tracking cannot see, as globs of test files mapped to the glob(s) of the files they depend on, relative to the config root: `{ 'tests/**/*.spec.ts': 'fixtures/**' }`. A change to a matching file reruns the mapped tests. |
46
+
47
+ ## Caveats
48
+
49
+ - **Only dependencies in the test process are tracked.** `import`, `require`, and `fs` are automatically tracked. Files read from a child process, `globalSetup`, or outside of node are not; declare them with the `dependencies` option.
50
+ - **Shards must be stable for maximal efficacy.** The plugin assigns test files to shards by a hash of their path, so a file stays in its shard as others are added or removed. A custom `test.sequence.sequencer` is honoured, but every test that moves to another shard reruns there.
51
+
52
+ ## Development
53
+
54
+ ```sh
55
+ npm ci
56
+ npm run check:types
57
+ npm run build # dist/ (not committed; built on publish)
58
+ npm test # runs the fixtures under test/ with a fake backend
59
+ ```
@@ -0,0 +1,124 @@
1
+ import { BloomFilter } from './bloom.js';
2
+ import type { Reporter, TestModule, TestRunEndReason, TestSpecification, VCSProvider, VCSProviderOptions, Vitest } from 'vitest/node';
3
+ import { type Tree } from './git.js';
4
+ export declare const SNAPSHOT_FILE = "blacksmith.json";
5
+ declare const SNAPSHOT_VERSION = 4;
6
+ /** A `dependencies` entry, compiled: test files matching `tests` depend on files matching `deps`; both root-relative. */
7
+ export interface DependencyRule {
8
+ tests: (file: string) => boolean;
9
+ deps: (file: string) => boolean;
10
+ }
11
+ export declare function compileDependencies(dependencies: Readonly<Record<string, string | readonly string[]>> | undefined): DependencyRule[];
12
+ export interface Shard {
13
+ index: number;
14
+ count: number;
15
+ }
16
+ /**
17
+ * Written beside vitest's `dependencies.json` after a run that covered every
18
+ * test the shard owns; `tree` anchors every path the dependency records name.
19
+ */
20
+ export interface Snapshot {
21
+ version: typeof SNAPSHOT_VERSION;
22
+ head: string;
23
+ shard: Shard | null;
24
+ /** Names of the projects the run covered (`--project` narrows them). */
25
+ projects: string[];
26
+ tree: Tree;
27
+ /** Repo-relative test files the run covered: ran, or deselected by our provider as unchanged. A test outside this set reruns whatever its record says. */
28
+ tests: BloomFilter;
29
+ }
30
+ /**
31
+ * `blacksmith.json`, or `blacksmith.<hash>.json` when `--project` narrowed the
32
+ * run: invocations covering different project sets keep separate anchors.
33
+ */
34
+ export declare function snapshotPath(vitest: Vitest, dir: string): string;
35
+ /**
36
+ * Decides which files changed since the snapshot was written by diffing the
37
+ * stored anchor against the recorded paths as they are now. Falls back
38
+ * to "everything" whenever the snapshot cannot vouch for this shard.
39
+ */
40
+ declare const PROVIDER_BRAND: unique symbol;
41
+ /** True for a `TreeSnapshotProvider` from any copy of this module (a project with its own root re-evaluates the config file, so it gets its own copy). */
42
+ export declare function isTreeSnapshotProvider(value: unknown): value is TreeSnapshotProvider;
43
+ /** The provider vitest loaded: the root config's `experimental.vcsProvider`, when it is ours. */
44
+ export declare function configuredProvider(vitest: Vitest): TreeSnapshotProvider | null;
45
+ export declare class TreeSnapshotProvider implements VCSProvider {
46
+ readonly [PROVIDER_BRAND] = true;
47
+ vitest: Vitest;
48
+ /**
49
+ * Recorded test files none of whose recorded imports changed: the provider
50
+ * vouches that they need not run, whatever filters the run also applies.
51
+ */
52
+ deselected: Set<string>;
53
+ /** The plugin's `dependencies` option, compiled. */
54
+ dependencies: DependencyRule[];
55
+ findChangedFiles(options: VCSProviderOptions): Promise<string[]>;
56
+ /**
57
+ * Changed files to hand vitest, or `null` when every test of the shard has
58
+ * to run; `dir` is where the record and anchor live (vitest's `cacheDir`,
59
+ * or a shadow run's copy of it).
60
+ */
61
+ select({ root }: Pick<VCSProviderOptions, 'root'>, dependencies: ReadonlyArray<{
62
+ file: string;
63
+ recordedAt?: number | undefined;
64
+ }>, dir: string, log: (message: string) => void): Promise<string[] | null>;
65
+ }
66
+ export interface ShadowResult {
67
+ /** Test files (absolute) the affected selection would have run, `'all'` when it would have run everything, `null` when there was nothing to compare against. */
68
+ wouldRun: Set<string> | 'all' | null;
69
+ changed: number;
70
+ head: string | null;
71
+ key: string | null;
72
+ /** Why there is nothing to compare, or why everything would have run. */
73
+ reason: string | null;
74
+ }
75
+ /**
76
+ * Shadow mode: fetches the shard's last record and anchor into a temporary
77
+ * directory while the full run executes, and works out what the affected
78
+ * selection would have run, without touching vitest's `cacheDir` or the run.
79
+ */
80
+ export declare class ShadowAnalysis {
81
+ private readonly vitest;
82
+ private readonly dependencies;
83
+ readonly result: Promise<ShadowResult>;
84
+ constructor(vitest: Vitest, dependencies: DependencyRule[]);
85
+ private run;
86
+ }
87
+ /**
88
+ * Writes the snapshot once a passing run has covered every test the shard owns,
89
+ * so the next `--changed` run can trust the dependency records for all of them.
90
+ */
91
+ declare const RECORDER_BRAND: unique symbol;
92
+ /** True for a `SnapshotRecorder` from any copy of this module (a project's config bundles its own). */
93
+ export declare function isSnapshotRecorder(reporter: unknown): boolean;
94
+ export declare class SnapshotRecorder implements Reporter {
95
+ private readonly provider;
96
+ private readonly options;
97
+ readonly [RECORDER_BRAND] = true;
98
+ private vitest;
99
+ private started;
100
+ private partial;
101
+ constructor(provider: TreeSnapshotProvider, options?: {
102
+ shadow?: ShadowAnalysis | undefined;
103
+ });
104
+ /** Shadow mode's verdict for this run, logged once the run has finished. */
105
+ private reportShadow;
106
+ /** The provider vitest is using: the root config's, which may come from another copy of this module. */
107
+ private activeProvider;
108
+ onInit(vitest: Vitest): void;
109
+ onTestRunStart(specifications: ReadonlyArray<TestSpecification>): void;
110
+ onTestRunEnd(modules: ReadonlyArray<TestModule>, _errors: ReadonlyArray<unknown>, reason: TestRunEndReason): Promise<void>;
111
+ /** Puts the record and this anchor in the Actions cache when a run can reach it. */
112
+ private publish;
113
+ /** The tests this shard owns, when every one of them ran or was deselected by our own provider; `null` when the run covered less. */
114
+ private coveredTests;
115
+ }
116
+ /**
117
+ * Before selection: when the Actions cache is reachable and Vitest's cache
118
+ * directory (`cacheDir`, wherever the config puts it) holds no record yet,
119
+ * pulls the newest record for this shard and project set into it. The
120
+ * directory itself may exist: every `vitest run` on one config shares it
121
+ * (its hash is the root `test.name`, not the `--project` filter).
122
+ */
123
+ export declare function restoreDependencyCache(vitest: Vitest): Promise<void>;
124
+ export {};