impacted 0.0.1 → 0.0.4
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/README.md +20 -104
- package/bin/impacted.js +0 -0
- package/package.json +10 -4
- package/src/index.d.ts +80 -0
- package/src/index.js +2 -1
package/README.md
CHANGED
|
@@ -1,63 +1,32 @@
|
|
|
1
1
|
# impacted
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
It takes a list of changed files, builds a dependency graph from your test files by statically analyzing their imports, and returns only the test files that depend on the changed files.
|
|
4
4
|
|
|
5
|
-
[
|
|
6
|
-
[](https://www.npmjs.com/package/impacted)
|
|
7
|
-
|
|
8
|
-
## About
|
|
9
|
-
|
|
10
|
-
`impacted` analyzes your codebase's import graph to determine which test files are affected by a set of changed files. Run only the tests that matter instead of your entire test suite.
|
|
11
|
-
|
|
12
|
-
This project is a userland implementation of [predictive test selection for Node.js test runner](https://github.com/nodejs/node/issues/54173).
|
|
13
|
-
|
|
14
|
-
## Features
|
|
15
|
-
|
|
16
|
-
- Static analysis of ESM and CommonJS (import, require, dynamic import, re-exports)
|
|
17
|
-
- Supports Node.js subpath imports (`#imports`)
|
|
18
|
-
- Transitive dependency tracking
|
|
19
|
-
- Optional caching for faster repeated runs
|
|
20
|
-
- CLI and programmatic API
|
|
21
|
-
- GitHub Action for CI integration
|
|
5
|
+
A userland implementation of [predictive test selection for Node.js test runner](https://github.com/nodejs/node/issues/54173).
|
|
22
6
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
npm install impacted
|
|
27
|
-
```
|
|
7
|
+
[](https://www.npmjs.com/package/impacted)
|
|
28
8
|
|
|
29
9
|
## Usage
|
|
30
10
|
|
|
31
|
-
### CLI
|
|
32
|
-
|
|
33
|
-
Pipe changed files to `impacted`:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
git diff --name-only HEAD~1 | npx impacted
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
With a custom test pattern:
|
|
40
|
-
|
|
41
11
|
```bash
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Use with any test runner:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
# Node.js test runner
|
|
12
|
+
# Run only impacted tests
|
|
49
13
|
node --test $(git diff --name-only main | npx impacted)
|
|
50
14
|
|
|
51
|
-
#
|
|
15
|
+
# Works with any test runner
|
|
52
16
|
vitest $(git diff --name-only main | npx impacted)
|
|
53
|
-
|
|
54
|
-
# Jest
|
|
55
17
|
jest $(git diff --name-only main | npx impacted)
|
|
18
|
+
|
|
19
|
+
# Custom test pattern
|
|
20
|
+
git diff --name-only main | npx impacted -p "src/**/*.spec.js"
|
|
56
21
|
```
|
|
57
22
|
|
|
58
|
-
|
|
23
|
+
## GitHub Action
|
|
59
24
|
|
|
60
25
|
```yaml
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
with:
|
|
28
|
+
fetch-depth: 0
|
|
29
|
+
|
|
61
30
|
- uses: sozua/impacted@v1
|
|
62
31
|
id: impacted
|
|
63
32
|
with:
|
|
@@ -68,78 +37,25 @@ jest $(git diff --name-only main | npx impacted)
|
|
|
68
37
|
run: node --test ${{ steps.impacted.outputs.files }}
|
|
69
38
|
```
|
|
70
39
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
| Input | Description | Default |
|
|
74
|
-
|-------|-------------|---------|
|
|
75
|
-
| `base` | Base ref to compare against | PR base or `HEAD~1` |
|
|
76
|
-
| `head` | Head ref | `HEAD` |
|
|
77
|
-
| `pattern` | Glob pattern for test files | `**/*.test.js` |
|
|
78
|
-
| `working-directory` | Working directory | `.` |
|
|
79
|
-
|
|
80
|
-
#### Outputs
|
|
40
|
+
See [action.yml](https://github.com/sozua/impacted/blob/main/action.yml) for all inputs and outputs.
|
|
81
41
|
|
|
82
|
-
|
|
83
|
-
|--------|-------------|
|
|
84
|
-
| `files` | Space-separated list of impacted test files |
|
|
85
|
-
| `files-json` | JSON array of impacted test files |
|
|
86
|
-
| `count` | Number of impacted test files |
|
|
87
|
-
| `has-impacted` | Whether any tests are impacted (`true`/`false`) |
|
|
88
|
-
|
|
89
|
-
### Programmatic API
|
|
42
|
+
## Programmatic API
|
|
90
43
|
|
|
91
44
|
```javascript
|
|
92
45
|
import { findImpacted } from 'impacted';
|
|
93
46
|
|
|
94
|
-
const tests = await findImpacted({
|
|
95
|
-
changedFiles: ['src/utils.js', 'src/parser.js'],
|
|
96
|
-
testFiles: 'test/**/*.test.js',
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
console.log(tests);
|
|
100
|
-
// ['/project/test/utils.test.js', '/project/test/parser.test.js']
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
#### With caching
|
|
104
|
-
|
|
105
|
-
```javascript
|
|
106
47
|
const tests = await findImpacted({
|
|
107
48
|
changedFiles: ['src/utils.js'],
|
|
108
49
|
testFiles: 'test/**/*.test.js',
|
|
109
|
-
cacheFile: '.impacted-cache.json',
|
|
50
|
+
cacheFile: '.impacted-cache.json', // optional
|
|
110
51
|
});
|
|
111
52
|
```
|
|
112
53
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
| Option | Type | Description |
|
|
116
|
-
|--------|------|-------------|
|
|
117
|
-
| `changedFiles` | `string[]` | Files that changed (relative or absolute) |
|
|
118
|
-
| `testFiles` | `string \| string[]` | Glob pattern(s) or explicit file list |
|
|
119
|
-
| `cwd` | `string` | Working directory (default: `process.cwd()`) |
|
|
120
|
-
| `excludePaths` | `string[]` | Paths to exclude (default: `['node_modules', 'dist']`) |
|
|
121
|
-
| `cacheFile` | `string` | Path to persist cache between runs |
|
|
122
|
-
|
|
123
|
-
## How it works
|
|
124
|
-
|
|
125
|
-
1. Build a dependency graph starting from your test files
|
|
126
|
-
2. Invert the graph to map each file to its dependents
|
|
127
|
-
3. Walk the inverted graph from changed files to find impacted tests
|
|
128
|
-
|
|
129
|
-
```
|
|
130
|
-
src/utils.js (changed)
|
|
131
|
-
↓ imported by
|
|
132
|
-
src/parser.js
|
|
133
|
-
↓ imported by
|
|
134
|
-
test/parser.test.js (impacted)
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
## Known limitations
|
|
54
|
+
## Limitations
|
|
138
55
|
|
|
139
|
-
-
|
|
140
|
-
-
|
|
141
|
-
-
|
|
142
|
-
- **No TypeScript path aliases** - Only Node.js subpath imports (`#imports` in package.json) are supported, not `tsconfig.json` paths.
|
|
56
|
+
- JavaScript only (`.js`, `.mjs`, `.cjs`, `.jsx`) — no TypeScript yet
|
|
57
|
+
- Static analysis only — dynamic `require(variable)` not supported
|
|
58
|
+
- Local files only — `node_modules` changes won't trigger tests
|
|
143
59
|
|
|
144
60
|
## Requirements
|
|
145
61
|
|
package/bin/impacted.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "impacted",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Find test files impacted by code changes using static dependency analysis",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
7
8
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.d.ts",
|
|
11
|
+
"default": "./src/index.js"
|
|
12
|
+
}
|
|
9
13
|
},
|
|
10
14
|
"bin": {
|
|
11
15
|
"impacted": "./bin/impacted.js"
|
|
@@ -15,7 +19,8 @@
|
|
|
15
19
|
"bin"
|
|
16
20
|
],
|
|
17
21
|
"scripts": {
|
|
18
|
-
"test": "node --test"
|
|
22
|
+
"test": "node --test",
|
|
23
|
+
"test:examples": "node --test examples/test.js"
|
|
19
24
|
},
|
|
20
25
|
"keywords": [
|
|
21
26
|
"test",
|
|
@@ -44,6 +49,7 @@
|
|
|
44
49
|
"dependencies": {
|
|
45
50
|
"acorn": "^8.14.0",
|
|
46
51
|
"acorn-walk": "^8.3.4",
|
|
47
|
-
"fast-glob": "^3.3.3"
|
|
52
|
+
"fast-glob": "^3.3.3",
|
|
53
|
+
"is-glob": "^4.0.3"
|
|
48
54
|
}
|
|
49
55
|
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export interface FindImpactedOptions {
|
|
2
|
+
/** Files that changed (relative or absolute) */
|
|
3
|
+
changedFiles: string[];
|
|
4
|
+
/** Test file glob pattern(s) or explicit file list */
|
|
5
|
+
testFiles: string | string[];
|
|
6
|
+
/** Working directory (default: process.cwd()) */
|
|
7
|
+
cwd?: string;
|
|
8
|
+
/** Paths to exclude from graph (default: ['node_modules', 'dist']) */
|
|
9
|
+
excludePaths?: string[];
|
|
10
|
+
/** Cache instance for import extraction */
|
|
11
|
+
cache?: ImportCache;
|
|
12
|
+
/** Path to persist cache (creates cache automatically) */
|
|
13
|
+
cacheFile?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface GraphOptions {
|
|
17
|
+
/** Paths to exclude from graph (default: ['node_modules', 'dist']) */
|
|
18
|
+
excludePaths?: string[];
|
|
19
|
+
/** Cache instance for import extraction */
|
|
20
|
+
cache?: ImportCache;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CacheOptions {
|
|
24
|
+
/** Path to persist cache */
|
|
25
|
+
cacheFile?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface CacheStats {
|
|
29
|
+
hits: number;
|
|
30
|
+
misses: number;
|
|
31
|
+
size: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Find test files impacted by code changes */
|
|
35
|
+
export function findImpacted(options: FindImpactedOptions): Promise<string[]>;
|
|
36
|
+
|
|
37
|
+
/** Build a dependency graph starting from entry files */
|
|
38
|
+
export function buildDependencyGraph(
|
|
39
|
+
entryFiles: string[],
|
|
40
|
+
options?: GraphOptions,
|
|
41
|
+
): Map<string, Set<string>>;
|
|
42
|
+
|
|
43
|
+
/** Find test files impacted by changed files given a dependency graph */
|
|
44
|
+
export function findImpactedTests(
|
|
45
|
+
changedFiles: string[],
|
|
46
|
+
testFiles: string[],
|
|
47
|
+
graph: Map<string, Set<string>>,
|
|
48
|
+
): string[];
|
|
49
|
+
|
|
50
|
+
/** Invert a dependency graph (file → deps becomes dep → dependents) */
|
|
51
|
+
export function invertGraph(
|
|
52
|
+
graph: Map<string, Set<string>>,
|
|
53
|
+
): Map<string, Set<string>>;
|
|
54
|
+
|
|
55
|
+
/** Extract import/require specifiers from source code */
|
|
56
|
+
export function parseImports(source: string): string[];
|
|
57
|
+
|
|
58
|
+
/** Resolve an import specifier to an absolute file path */
|
|
59
|
+
export function resolveSpecifier(
|
|
60
|
+
specifier: string,
|
|
61
|
+
parentPath: string,
|
|
62
|
+
): string | null;
|
|
63
|
+
|
|
64
|
+
/** Create a cache instance */
|
|
65
|
+
export function createCache(options?: CacheOptions): ImportCache;
|
|
66
|
+
|
|
67
|
+
/** Cache for import extraction results with mtime-based invalidation */
|
|
68
|
+
export class ImportCache {
|
|
69
|
+
constructor(options?: CacheOptions);
|
|
70
|
+
/** Get cached imports for a file, or null if not cached/stale */
|
|
71
|
+
get(filePath: string): string[] | null;
|
|
72
|
+
/** Store imports for a file */
|
|
73
|
+
set(filePath: string, imports: string[]): void;
|
|
74
|
+
/** Get cache statistics */
|
|
75
|
+
stats(): CacheStats;
|
|
76
|
+
/** Persist cache to disk */
|
|
77
|
+
save(): void;
|
|
78
|
+
/** Clear all cached data */
|
|
79
|
+
clear(): void;
|
|
80
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
2
|
import fg from 'fast-glob';
|
|
3
|
+
import isGlob from 'is-glob';
|
|
3
4
|
import { getImpactedTests } from './graph.js';
|
|
4
5
|
import { createCache, ImportCache } from './cache.js';
|
|
5
6
|
|
|
@@ -50,7 +51,7 @@ export async function findImpacted(options) {
|
|
|
50
51
|
|
|
51
52
|
// Resolve test files - either glob or explicit list
|
|
52
53
|
let absoluteTestFiles;
|
|
53
|
-
if (typeof testFiles === 'string' || (Array.isArray(testFiles) && testFiles.some((t) => t
|
|
54
|
+
if (typeof testFiles === 'string' || (Array.isArray(testFiles) && testFiles.some((t) => isGlob(t)))) {
|
|
54
55
|
// It's a glob pattern
|
|
55
56
|
const patterns = Array.isArray(testFiles) ? testFiles : [testFiles];
|
|
56
57
|
absoluteTestFiles = await fg(patterns, { cwd, absolute: true });
|