@vibe-agent-toolkit/discovery 0.1.0-rc.7 → 0.1.0-rc.9
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/dist/detectors/format-detector.d.ts +15 -0
- package/dist/detectors/format-detector.d.ts.map +1 -0
- package/dist/detectors/format-detector.js +29 -0
- package/dist/detectors/format-detector.js.map +1 -0
- package/dist/filters/pattern-filter.d.ts +16 -0
- package/dist/filters/pattern-filter.d.ts.map +1 -0
- package/dist/filters/pattern-filter.js +32 -0
- package/dist/filters/pattern-filter.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/{src/index.ts → dist/index.js} +1 -0
- package/dist/index.js.map +1 -0
- package/dist/scanners/local-scanner.d.ts +9 -0
- package/dist/scanners/local-scanner.d.ts.map +1 -0
- package/dist/scanners/local-scanner.js +87 -0
- package/dist/scanners/local-scanner.js.map +1 -0
- package/dist/types.d.ts +48 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +6 -2
- package/src/detectors/format-detector.ts +0 -35
- package/src/filters/pattern-filter.ts +0 -47
- package/src/scanners/local-scanner.ts +0 -102
- package/src/types.ts +0 -65
- package/test/format-detector.test.ts +0 -45
- package/test/local-scanner.test.ts +0 -109
- package/test/pattern-filter.test.ts +0 -74
- package/tsconfig.json +0 -12
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DetectedFormat } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Detect file format based on filename
|
|
4
|
+
*
|
|
5
|
+
* Detection rules:
|
|
6
|
+
* - Exact match "SKILL.md" → claude-skill
|
|
7
|
+
* - Exact match "agent.yaml" or "agent.yml" → vat-agent
|
|
8
|
+
* - Extension ".md" → markdown
|
|
9
|
+
* - Everything else → unknown
|
|
10
|
+
*
|
|
11
|
+
* @param filePath - Path to file (can be relative or absolute)
|
|
12
|
+
* @returns Detected format
|
|
13
|
+
*/
|
|
14
|
+
export declare function detectFormat(filePath: string): DetectedFormat;
|
|
15
|
+
//# sourceMappingURL=format-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-detector.d.ts","sourceRoot":"","sources":["../../src/detectors/format-detector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAkB7D"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
/**
|
|
3
|
+
* Detect file format based on filename
|
|
4
|
+
*
|
|
5
|
+
* Detection rules:
|
|
6
|
+
* - Exact match "SKILL.md" → claude-skill
|
|
7
|
+
* - Exact match "agent.yaml" or "agent.yml" → vat-agent
|
|
8
|
+
* - Extension ".md" → markdown
|
|
9
|
+
* - Everything else → unknown
|
|
10
|
+
*
|
|
11
|
+
* @param filePath - Path to file (can be relative or absolute)
|
|
12
|
+
* @returns Detected format
|
|
13
|
+
*/
|
|
14
|
+
export function detectFormat(filePath) {
|
|
15
|
+
const basename = path.basename(filePath);
|
|
16
|
+
// Exact matches (case-sensitive)
|
|
17
|
+
if (basename === 'SKILL.md') {
|
|
18
|
+
return 'claude-skill';
|
|
19
|
+
}
|
|
20
|
+
if (basename === 'agent.yaml' || basename === 'agent.yml') {
|
|
21
|
+
return 'vat-agent';
|
|
22
|
+
}
|
|
23
|
+
// Extension-based detection (case-insensitive)
|
|
24
|
+
if (basename.toLowerCase().endsWith('.md')) {
|
|
25
|
+
return 'markdown';
|
|
26
|
+
}
|
|
27
|
+
return 'unknown';
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=format-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-detector.js","sourceRoot":"","sources":["../../src/detectors/format-detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAIlC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB;IAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAEzC,iCAAiC;IACjC,IAAI,QAAQ,KAAK,UAAU,EAAE,CAAC;QAC5B,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,IAAI,QAAQ,KAAK,YAAY,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;QAC1D,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,+CAA+C;IAC/C,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3C,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface PatternFilterOptions {
|
|
2
|
+
/** Include patterns (if specified, only matching files included) */
|
|
3
|
+
include?: string[];
|
|
4
|
+
/** Exclude patterns (applied after include) */
|
|
5
|
+
exclude?: string[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Create a filter function for include/exclude patterns
|
|
9
|
+
*
|
|
10
|
+
* Uses picomatch for fast glob matching. Exclude patterns are applied after include.
|
|
11
|
+
*
|
|
12
|
+
* @param options - Pattern filter options
|
|
13
|
+
* @returns Filter function (returns true if file should be included)
|
|
14
|
+
*/
|
|
15
|
+
export declare function createPatternFilter(options: PatternFilterOptions): (filePath: string) => boolean;
|
|
16
|
+
//# sourceMappingURL=pattern-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pattern-filter.d.ts","sourceRoot":"","sources":["../../src/filters/pattern-filter.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,oBAAoB;IACnC,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,oBAAoB,GAC5B,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CA0B/B"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import picomatch from 'picomatch';
|
|
2
|
+
/**
|
|
3
|
+
* Create a filter function for include/exclude patterns
|
|
4
|
+
*
|
|
5
|
+
* Uses picomatch for fast glob matching. Exclude patterns are applied after include.
|
|
6
|
+
*
|
|
7
|
+
* @param options - Pattern filter options
|
|
8
|
+
* @returns Filter function (returns true if file should be included)
|
|
9
|
+
*/
|
|
10
|
+
export function createPatternFilter(options) {
|
|
11
|
+
const { include, exclude } = options;
|
|
12
|
+
// Compile patterns once for performance
|
|
13
|
+
// Use { contains: true } to match paths containing patterns (e.g., 'node_modules' matches 'path/node_modules/file')
|
|
14
|
+
const includeMatcher = include?.length
|
|
15
|
+
? picomatch(include, { contains: true })
|
|
16
|
+
: null;
|
|
17
|
+
const excludeMatcher = exclude?.length
|
|
18
|
+
? picomatch(exclude, { contains: true })
|
|
19
|
+
: null;
|
|
20
|
+
return (filePath) => {
|
|
21
|
+
// If include patterns specified, file must match at least one
|
|
22
|
+
if (includeMatcher && !includeMatcher(filePath)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
// If exclude patterns specified, file must not match any
|
|
26
|
+
if (excludeMatcher?.(filePath)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=pattern-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pattern-filter.js","sourceRoot":"","sources":["../../src/filters/pattern-filter.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,WAAW,CAAC;AAUlC;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAA6B;IAE7B,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAErC,wCAAwC;IACxC,oHAAoH;IACpH,MAAM,cAAc,GAAG,OAAO,EAAE,MAAM;QACpC,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QACxC,CAAC,CAAC,IAAI,CAAC;IAET,MAAM,cAAc,GAAG,OAAO,EAAE,MAAM;QACpC,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QACxC,CAAC,CAAC,IAAI,CAAC;IAET,OAAO,CAAC,QAAgB,EAAW,EAAE;QACnC,8DAA8D;QAC9D,IAAI,cAAc,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;YAChD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,yDAAyD;QACzD,IAAI,cAAc,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/B,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ScanOptions, ScanSummary } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Scan local filesystem for VAT agents and Claude Skills
|
|
4
|
+
*
|
|
5
|
+
* @param options - Scan options
|
|
6
|
+
* @returns Scan summary with all discovered files
|
|
7
|
+
*/
|
|
8
|
+
export declare function scan(options: ScanOptions): Promise<ScanSummary>;
|
|
9
|
+
//# sourceMappingURL=local-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-scanner.d.ts","sourceRoot":"","sources":["../../src/scanners/local-scanner.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAc,WAAW,EAAE,MAAM,aAAa,CAAC;AAExE;;;;;GAKG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAsFrE"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { crawlDirectory, isGitIgnored } from '@vibe-agent-toolkit/utils';
|
|
4
|
+
import { detectFormat } from '../detectors/format-detector.js';
|
|
5
|
+
import { createPatternFilter } from '../filters/pattern-filter.js';
|
|
6
|
+
/**
|
|
7
|
+
* Scan local filesystem for VAT agents and Claude Skills
|
|
8
|
+
*
|
|
9
|
+
* @param options - Scan options
|
|
10
|
+
* @returns Scan summary with all discovered files
|
|
11
|
+
*/
|
|
12
|
+
export async function scan(options) {
|
|
13
|
+
const { path: targetPath, recursive = false, include, exclude } = options;
|
|
14
|
+
// Resolve to absolute path
|
|
15
|
+
const absolutePath = path.resolve(targetPath);
|
|
16
|
+
// Check if target exists
|
|
17
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- absolutePath is validated user input
|
|
18
|
+
if (!fs.existsSync(absolutePath)) {
|
|
19
|
+
throw new Error(`Path does not exist: ${absolutePath}`);
|
|
20
|
+
}
|
|
21
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- absolutePath validated above
|
|
22
|
+
const stat = fs.statSync(absolutePath);
|
|
23
|
+
// Determine scan root for relative paths
|
|
24
|
+
const scanRoot = stat.isDirectory() ? absolutePath : path.dirname(absolutePath);
|
|
25
|
+
// Get file list
|
|
26
|
+
let filePaths;
|
|
27
|
+
if (stat.isFile()) {
|
|
28
|
+
filePaths = [absolutePath];
|
|
29
|
+
}
|
|
30
|
+
else if (stat.isDirectory()) {
|
|
31
|
+
if (recursive) {
|
|
32
|
+
filePaths = await crawlDirectory({
|
|
33
|
+
baseDir: absolutePath,
|
|
34
|
+
respectGitignore: false, // We handle gitignore separately
|
|
35
|
+
exclude: [], // Don't exclude anything - we handle filtering ourselves
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// Non-recursive: only immediate children
|
|
40
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- absolutePath validated above
|
|
41
|
+
filePaths = fs.readdirSync(absolutePath)
|
|
42
|
+
.map(name => path.join(absolutePath, name))
|
|
43
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- paths are from validated directory
|
|
44
|
+
.filter(p => fs.statSync(p).isFile());
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
throw new Error(`Path is neither file nor directory: ${absolutePath}`);
|
|
49
|
+
}
|
|
50
|
+
// Create pattern filter
|
|
51
|
+
const patternFilter = createPatternFilter({
|
|
52
|
+
...(include && { include }),
|
|
53
|
+
...(exclude && { exclude }),
|
|
54
|
+
});
|
|
55
|
+
// Process each file
|
|
56
|
+
const results = [];
|
|
57
|
+
for (const filePath of filePaths) {
|
|
58
|
+
const relativePath = path.relative(scanRoot, filePath);
|
|
59
|
+
// Apply pattern filter
|
|
60
|
+
if (!patternFilter(relativePath)) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const format = detectFormat(filePath);
|
|
64
|
+
const gitIgnored = isGitIgnored(filePath, scanRoot);
|
|
65
|
+
results.push({
|
|
66
|
+
path: filePath,
|
|
67
|
+
format,
|
|
68
|
+
isGitIgnored: gitIgnored,
|
|
69
|
+
relativePath,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// Build summary
|
|
73
|
+
const byFormat = results.reduce((acc, r) => {
|
|
74
|
+
acc[r.format] = (acc[r.format] ?? 0) + 1;
|
|
75
|
+
return acc;
|
|
76
|
+
}, {});
|
|
77
|
+
const sourceFiles = results.filter(r => !r.isGitIgnored);
|
|
78
|
+
const buildOutputs = results.filter(r => r.isGitIgnored);
|
|
79
|
+
return {
|
|
80
|
+
results,
|
|
81
|
+
totalScanned: results.length,
|
|
82
|
+
byFormat: byFormat,
|
|
83
|
+
sourceFiles,
|
|
84
|
+
buildOutputs,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=local-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-scanner.js","sourceRoot":"","sources":["../../src/scanners/local-scanner.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format types that discovery can detect
|
|
3
|
+
*/
|
|
4
|
+
export type DetectedFormat = 'claude-skill' | 'vat-agent' | 'markdown' | 'unknown';
|
|
5
|
+
/**
|
|
6
|
+
* Options for scanning/discovery operations
|
|
7
|
+
*/
|
|
8
|
+
export interface ScanOptions {
|
|
9
|
+
/** Path to scan (file or directory) */
|
|
10
|
+
path: string;
|
|
11
|
+
/** Recursive scan (search subdirectories) */
|
|
12
|
+
recursive?: boolean;
|
|
13
|
+
/** Include patterns (glob) */
|
|
14
|
+
include?: string[];
|
|
15
|
+
/** Exclude patterns (glob) */
|
|
16
|
+
exclude?: string[];
|
|
17
|
+
/** Follow symbolic links */
|
|
18
|
+
followSymlinks?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Result of scanning a single file
|
|
22
|
+
*/
|
|
23
|
+
export interface ScanResult {
|
|
24
|
+
/** Absolute path to file */
|
|
25
|
+
path: string;
|
|
26
|
+
/** Detected format */
|
|
27
|
+
format: DetectedFormat;
|
|
28
|
+
/** Is this file gitignored (likely build output) */
|
|
29
|
+
isGitIgnored: boolean;
|
|
30
|
+
/** Relative path from scan root */
|
|
31
|
+
relativePath: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Summary of scan operation
|
|
35
|
+
*/
|
|
36
|
+
export interface ScanSummary {
|
|
37
|
+
/** All discovered files */
|
|
38
|
+
results: ScanResult[];
|
|
39
|
+
/** Total files scanned */
|
|
40
|
+
totalScanned: number;
|
|
41
|
+
/** Files by format */
|
|
42
|
+
byFormat: Record<DetectedFormat, number>;
|
|
43
|
+
/** Source files (not gitignored) */
|
|
44
|
+
sourceFiles: ScanResult[];
|
|
45
|
+
/** Build outputs (gitignored) */
|
|
46
|
+
buildOutputs: ScanResult[];
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,cAAc,GACtB,cAAc,GACd,WAAW,GACX,UAAU,GACV,SAAS,CAAC;AAEd;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IAEb,6CAA6C;IAC7C,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB,4BAA4B;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IAEb,sBAAsB;IACtB,MAAM,EAAE,cAAc,CAAC;IAEvB,oDAAoD;IACpD,YAAY,EAAE,OAAO,CAAC;IAEtB,mCAAmC;IACnC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,2BAA2B;IAC3B,OAAO,EAAE,UAAU,EAAE,CAAC;IAEtB,0BAA0B;IAC1B,YAAY,EAAE,MAAM,CAAC;IAErB,sBAAsB;IACtB,QAAQ,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IAEzC,oCAAoC;IACpC,WAAW,EAAE,UAAU,EAAE,CAAC;IAE1B,iCAAiC;IACjC,YAAY,EAAE,UAAU,EAAE,CAAC;CAC5B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibe-agent-toolkit/discovery",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.9",
|
|
4
4
|
"description": "Intelligent file discovery for VAT agents and Claude Skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
"default": "./dist/index.js"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
14
18
|
"scripts": {
|
|
15
19
|
"build": "tsc",
|
|
16
20
|
"test": "vitest run",
|
|
@@ -18,7 +22,7 @@
|
|
|
18
22
|
"typecheck": "tsc --noEmit"
|
|
19
23
|
},
|
|
20
24
|
"dependencies": {
|
|
21
|
-
"@vibe-agent-toolkit/utils": "
|
|
25
|
+
"@vibe-agent-toolkit/utils": "0.1.0-rc.9",
|
|
22
26
|
"picomatch": "^4.0.2"
|
|
23
27
|
},
|
|
24
28
|
"devDependencies": {
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import * as path from 'node:path';
|
|
2
|
-
|
|
3
|
-
import type { DetectedFormat } from '../types.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Detect file format based on filename
|
|
7
|
-
*
|
|
8
|
-
* Detection rules:
|
|
9
|
-
* - Exact match "SKILL.md" → claude-skill
|
|
10
|
-
* - Exact match "agent.yaml" or "agent.yml" → vat-agent
|
|
11
|
-
* - Extension ".md" → markdown
|
|
12
|
-
* - Everything else → unknown
|
|
13
|
-
*
|
|
14
|
-
* @param filePath - Path to file (can be relative or absolute)
|
|
15
|
-
* @returns Detected format
|
|
16
|
-
*/
|
|
17
|
-
export function detectFormat(filePath: string): DetectedFormat {
|
|
18
|
-
const basename = path.basename(filePath);
|
|
19
|
-
|
|
20
|
-
// Exact matches (case-sensitive)
|
|
21
|
-
if (basename === 'SKILL.md') {
|
|
22
|
-
return 'claude-skill';
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (basename === 'agent.yaml' || basename === 'agent.yml') {
|
|
26
|
-
return 'vat-agent';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Extension-based detection (case-insensitive)
|
|
30
|
-
if (basename.toLowerCase().endsWith('.md')) {
|
|
31
|
-
return 'markdown';
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return 'unknown';
|
|
35
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import picomatch from 'picomatch';
|
|
2
|
-
|
|
3
|
-
export interface PatternFilterOptions {
|
|
4
|
-
/** Include patterns (if specified, only matching files included) */
|
|
5
|
-
include?: string[];
|
|
6
|
-
|
|
7
|
-
/** Exclude patterns (applied after include) */
|
|
8
|
-
exclude?: string[];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Create a filter function for include/exclude patterns
|
|
13
|
-
*
|
|
14
|
-
* Uses picomatch for fast glob matching. Exclude patterns are applied after include.
|
|
15
|
-
*
|
|
16
|
-
* @param options - Pattern filter options
|
|
17
|
-
* @returns Filter function (returns true if file should be included)
|
|
18
|
-
*/
|
|
19
|
-
export function createPatternFilter(
|
|
20
|
-
options: PatternFilterOptions
|
|
21
|
-
): (filePath: string) => boolean {
|
|
22
|
-
const { include, exclude } = options;
|
|
23
|
-
|
|
24
|
-
// Compile patterns once for performance
|
|
25
|
-
// Use { contains: true } to match paths containing patterns (e.g., 'node_modules' matches 'path/node_modules/file')
|
|
26
|
-
const includeMatcher = include?.length
|
|
27
|
-
? picomatch(include, { contains: true })
|
|
28
|
-
: null;
|
|
29
|
-
|
|
30
|
-
const excludeMatcher = exclude?.length
|
|
31
|
-
? picomatch(exclude, { contains: true })
|
|
32
|
-
: null;
|
|
33
|
-
|
|
34
|
-
return (filePath: string): boolean => {
|
|
35
|
-
// If include patterns specified, file must match at least one
|
|
36
|
-
if (includeMatcher && !includeMatcher(filePath)) {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// If exclude patterns specified, file must not match any
|
|
41
|
-
if (excludeMatcher?.(filePath)) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return true;
|
|
46
|
-
};
|
|
47
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
|
|
4
|
-
import { crawlDirectory , isGitIgnored } from '@vibe-agent-toolkit/utils';
|
|
5
|
-
|
|
6
|
-
import { detectFormat } from '../detectors/format-detector.js';
|
|
7
|
-
import { createPatternFilter } from '../filters/pattern-filter.js';
|
|
8
|
-
import type { ScanOptions, ScanResult, ScanSummary } from '../types.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Scan local filesystem for VAT agents and Claude Skills
|
|
12
|
-
*
|
|
13
|
-
* @param options - Scan options
|
|
14
|
-
* @returns Scan summary with all discovered files
|
|
15
|
-
*/
|
|
16
|
-
export async function scan(options: ScanOptions): Promise<ScanSummary> {
|
|
17
|
-
const { path: targetPath, recursive = false, include, exclude } = options;
|
|
18
|
-
|
|
19
|
-
// Resolve to absolute path
|
|
20
|
-
const absolutePath = path.resolve(targetPath);
|
|
21
|
-
|
|
22
|
-
// Check if target exists
|
|
23
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- absolutePath is validated user input
|
|
24
|
-
if (!fs.existsSync(absolutePath)) {
|
|
25
|
-
throw new Error(`Path does not exist: ${absolutePath}`);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- absolutePath validated above
|
|
29
|
-
const stat = fs.statSync(absolutePath);
|
|
30
|
-
|
|
31
|
-
// Determine scan root for relative paths
|
|
32
|
-
const scanRoot = stat.isDirectory() ? absolutePath : path.dirname(absolutePath);
|
|
33
|
-
|
|
34
|
-
// Get file list
|
|
35
|
-
let filePaths: string[];
|
|
36
|
-
|
|
37
|
-
if (stat.isFile()) {
|
|
38
|
-
filePaths = [absolutePath];
|
|
39
|
-
} else if (stat.isDirectory()) {
|
|
40
|
-
if (recursive) {
|
|
41
|
-
filePaths = await crawlDirectory({
|
|
42
|
-
baseDir: absolutePath,
|
|
43
|
-
respectGitignore: false, // We handle gitignore separately
|
|
44
|
-
exclude: [], // Don't exclude anything - we handle filtering ourselves
|
|
45
|
-
});
|
|
46
|
-
} else {
|
|
47
|
-
// Non-recursive: only immediate children
|
|
48
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- absolutePath validated above
|
|
49
|
-
filePaths = fs.readdirSync(absolutePath)
|
|
50
|
-
.map(name => path.join(absolutePath, name))
|
|
51
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- paths are from validated directory
|
|
52
|
-
.filter(p => fs.statSync(p).isFile());
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
55
|
-
throw new Error(`Path is neither file nor directory: ${absolutePath}`);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Create pattern filter
|
|
59
|
-
const patternFilter = createPatternFilter({
|
|
60
|
-
...(include && { include }),
|
|
61
|
-
...(exclude && { exclude }),
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// Process each file
|
|
65
|
-
const results: ScanResult[] = [];
|
|
66
|
-
|
|
67
|
-
for (const filePath of filePaths) {
|
|
68
|
-
const relativePath = path.relative(scanRoot, filePath);
|
|
69
|
-
|
|
70
|
-
// Apply pattern filter
|
|
71
|
-
if (!patternFilter(relativePath)) {
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const format = detectFormat(filePath);
|
|
76
|
-
const gitIgnored = isGitIgnored(filePath, scanRoot);
|
|
77
|
-
|
|
78
|
-
results.push({
|
|
79
|
-
path: filePath,
|
|
80
|
-
format,
|
|
81
|
-
isGitIgnored: gitIgnored,
|
|
82
|
-
relativePath,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Build summary
|
|
87
|
-
const byFormat = results.reduce((acc, r) => {
|
|
88
|
-
acc[r.format] = (acc[r.format] ?? 0) + 1;
|
|
89
|
-
return acc;
|
|
90
|
-
}, {} as Record<string, number>);
|
|
91
|
-
|
|
92
|
-
const sourceFiles = results.filter(r => !r.isGitIgnored);
|
|
93
|
-
const buildOutputs = results.filter(r => r.isGitIgnored);
|
|
94
|
-
|
|
95
|
-
return {
|
|
96
|
-
results,
|
|
97
|
-
totalScanned: results.length,
|
|
98
|
-
byFormat: byFormat as ScanSummary['byFormat'],
|
|
99
|
-
sourceFiles,
|
|
100
|
-
buildOutputs,
|
|
101
|
-
};
|
|
102
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Format types that discovery can detect
|
|
3
|
-
*/
|
|
4
|
-
export type DetectedFormat =
|
|
5
|
-
| 'claude-skill' // SKILL.md
|
|
6
|
-
| 'vat-agent' // agent.yaml
|
|
7
|
-
| 'markdown' // *.md (resource file)
|
|
8
|
-
| 'unknown'; // Other files
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Options for scanning/discovery operations
|
|
12
|
-
*/
|
|
13
|
-
export interface ScanOptions {
|
|
14
|
-
/** Path to scan (file or directory) */
|
|
15
|
-
path: string;
|
|
16
|
-
|
|
17
|
-
/** Recursive scan (search subdirectories) */
|
|
18
|
-
recursive?: boolean;
|
|
19
|
-
|
|
20
|
-
/** Include patterns (glob) */
|
|
21
|
-
include?: string[];
|
|
22
|
-
|
|
23
|
-
/** Exclude patterns (glob) */
|
|
24
|
-
exclude?: string[];
|
|
25
|
-
|
|
26
|
-
/** Follow symbolic links */
|
|
27
|
-
followSymlinks?: boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Result of scanning a single file
|
|
32
|
-
*/
|
|
33
|
-
export interface ScanResult {
|
|
34
|
-
/** Absolute path to file */
|
|
35
|
-
path: string;
|
|
36
|
-
|
|
37
|
-
/** Detected format */
|
|
38
|
-
format: DetectedFormat;
|
|
39
|
-
|
|
40
|
-
/** Is this file gitignored (likely build output) */
|
|
41
|
-
isGitIgnored: boolean;
|
|
42
|
-
|
|
43
|
-
/** Relative path from scan root */
|
|
44
|
-
relativePath: string;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Summary of scan operation
|
|
49
|
-
*/
|
|
50
|
-
export interface ScanSummary {
|
|
51
|
-
/** All discovered files */
|
|
52
|
-
results: ScanResult[];
|
|
53
|
-
|
|
54
|
-
/** Total files scanned */
|
|
55
|
-
totalScanned: number;
|
|
56
|
-
|
|
57
|
-
/** Files by format */
|
|
58
|
-
byFormat: Record<DetectedFormat, number>;
|
|
59
|
-
|
|
60
|
-
/** Source files (not gitignored) */
|
|
61
|
-
sourceFiles: ScanResult[];
|
|
62
|
-
|
|
63
|
-
/** Build outputs (gitignored) */
|
|
64
|
-
buildOutputs: ScanResult[];
|
|
65
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { detectFormat } from '../src/detectors/format-detector.js';
|
|
4
|
-
|
|
5
|
-
const CLAUDE_SKILL_FORMAT = 'claude-skill';
|
|
6
|
-
|
|
7
|
-
describe('detectFormat', () => {
|
|
8
|
-
it('should detect SKILL.md as claude-skill', () => {
|
|
9
|
-
expect(detectFormat('SKILL.md')).toBe(CLAUDE_SKILL_FORMAT);
|
|
10
|
-
expect(detectFormat('/path/to/SKILL.md')).toBe(CLAUDE_SKILL_FORMAT);
|
|
11
|
-
expect(detectFormat('./my-skill/SKILL.md')).toBe(CLAUDE_SKILL_FORMAT);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('should detect agent.yaml as vat-agent', () => {
|
|
15
|
-
expect(detectFormat('agent.yaml')).toBe('vat-agent');
|
|
16
|
-
expect(detectFormat('/path/to/agent.yaml')).toBe('vat-agent');
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('should detect agent.yml as vat-agent', () => {
|
|
20
|
-
expect(detectFormat('agent.yml')).toBe('vat-agent');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('should detect .md files as markdown', () => {
|
|
24
|
-
expect(detectFormat('README.md')).toBe('markdown');
|
|
25
|
-
expect(detectFormat('docs/guide.md')).toBe('markdown');
|
|
26
|
-
expect(detectFormat('reference/api.md')).toBe('markdown');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should not detect SKILL.md as markdown', () => {
|
|
30
|
-
expect(detectFormat('SKILL.md')).not.toBe('markdown');
|
|
31
|
-
expect(detectFormat('SKILL.md')).toBe(CLAUDE_SKILL_FORMAT);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('should return unknown for other files', () => {
|
|
35
|
-
expect(detectFormat('package.json')).toBe('unknown');
|
|
36
|
-
expect(detectFormat('index.ts')).toBe('unknown');
|
|
37
|
-
expect(detectFormat('test.txt')).toBe('unknown');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('should be case-sensitive for SKILL.md', () => {
|
|
41
|
-
expect(detectFormat('skill.md')).toBe('markdown');
|
|
42
|
-
expect(detectFormat('Skill.md')).toBe('markdown');
|
|
43
|
-
expect(detectFormat('SKILL.MD')).toBe('markdown');
|
|
44
|
-
});
|
|
45
|
-
});
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/* eslint-disable security/detect-non-literal-fs-filename -- test file uses controlled temp directory */
|
|
2
|
-
import { spawnSync } from 'node:child_process';
|
|
3
|
-
import * as fs from 'node:fs';
|
|
4
|
-
import * as os from 'node:os';
|
|
5
|
-
import * as path from 'node:path';
|
|
6
|
-
|
|
7
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
8
|
-
|
|
9
|
-
import { scan } from '../src/scanners/local-scanner.js';
|
|
10
|
-
|
|
11
|
-
describe('scan', () => {
|
|
12
|
-
let tempDir: string;
|
|
13
|
-
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'discovery-test-'));
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
afterEach(() => {
|
|
19
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should scan single SKILL.md file', async () => {
|
|
23
|
-
const skillPath = path.join(tempDir, 'SKILL.md');
|
|
24
|
-
fs.writeFileSync(skillPath, '# Test Skill');
|
|
25
|
-
|
|
26
|
-
const result = await scan({ path: skillPath });
|
|
27
|
-
|
|
28
|
-
expect(result.totalScanned).toBe(1);
|
|
29
|
-
expect(result.results).toHaveLength(1);
|
|
30
|
-
expect(result.results[0]?.format).toBe('claude-skill');
|
|
31
|
-
expect(result.results[0]?.path).toBe(skillPath);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('should scan directory non-recursively', async () => {
|
|
35
|
-
fs.writeFileSync(path.join(tempDir, 'SKILL.md'), '# Skill');
|
|
36
|
-
fs.writeFileSync(path.join(tempDir, 'README.md'), '# Readme');
|
|
37
|
-
fs.mkdirSync(path.join(tempDir, 'sub'));
|
|
38
|
-
fs.writeFileSync(path.join(tempDir, 'sub', 'agent.yaml'), 'name: test');
|
|
39
|
-
|
|
40
|
-
const result = await scan({ path: tempDir, recursive: false });
|
|
41
|
-
|
|
42
|
-
expect(result.totalScanned).toBe(2);
|
|
43
|
-
expect(result.byFormat['claude-skill']).toBe(1);
|
|
44
|
-
expect(result.byFormat['markdown']).toBe(1);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should scan directory recursively', async () => {
|
|
48
|
-
fs.writeFileSync(path.join(tempDir, 'SKILL.md'), '# Skill');
|
|
49
|
-
fs.mkdirSync(path.join(tempDir, 'sub'));
|
|
50
|
-
fs.writeFileSync(path.join(tempDir, 'sub', 'agent.yaml'), 'name: test');
|
|
51
|
-
|
|
52
|
-
const result = await scan({ path: tempDir, recursive: true });
|
|
53
|
-
|
|
54
|
-
expect(result.totalScanned).toBe(2);
|
|
55
|
-
expect(result.byFormat['claude-skill']).toBe(1);
|
|
56
|
-
expect(result.byFormat['vat-agent']).toBe(1);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should respect include patterns', async () => {
|
|
60
|
-
fs.writeFileSync(path.join(tempDir, 'test.md'), '# Test');
|
|
61
|
-
fs.writeFileSync(path.join(tempDir, 'test.ts'), 'code');
|
|
62
|
-
|
|
63
|
-
const result = await scan({
|
|
64
|
-
path: tempDir,
|
|
65
|
-
include: ['*.md']
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
expect(result.totalScanned).toBe(1);
|
|
69
|
-
expect(result.results[0]?.format).toBe('markdown');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should respect exclude patterns', async () => {
|
|
73
|
-
fs.mkdirSync(path.join(tempDir, 'node_modules'));
|
|
74
|
-
fs.writeFileSync(path.join(tempDir, 'README.md'), '# Readme');
|
|
75
|
-
fs.writeFileSync(path.join(tempDir, 'node_modules', 'pkg.md'), '# Pkg');
|
|
76
|
-
|
|
77
|
-
const result = await scan({
|
|
78
|
-
path: tempDir,
|
|
79
|
-
recursive: true,
|
|
80
|
-
exclude: ['**/node_modules/**']
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
expect(result.totalScanned).toBe(1);
|
|
84
|
-
expect(result.results[0]?.relativePath).toBe('README.md');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('should detect gitignored files', async () => {
|
|
88
|
-
// Initialize git repo for git check-ignore to work
|
|
89
|
-
const gitPath = 'git';
|
|
90
|
-
// eslint-disable-next-line sonarjs/no-os-command-from-path -- test setup uses git from PATH
|
|
91
|
-
spawnSync(gitPath, ['init'], { cwd: tempDir, stdio: 'pipe' });
|
|
92
|
-
// eslint-disable-next-line sonarjs/no-os-command-from-path -- test setup uses git from PATH
|
|
93
|
-
spawnSync(gitPath, ['config', 'user.email', 'test@example.com'], { cwd: tempDir, stdio: 'pipe' });
|
|
94
|
-
// eslint-disable-next-line sonarjs/no-os-command-from-path -- test setup uses git from PATH
|
|
95
|
-
spawnSync(gitPath, ['config', 'user.name', 'Test User'], { cwd: tempDir, stdio: 'pipe' });
|
|
96
|
-
|
|
97
|
-
fs.writeFileSync(path.join(tempDir, '.gitignore'), 'dist/\n');
|
|
98
|
-
fs.mkdirSync(path.join(tempDir, 'dist'));
|
|
99
|
-
fs.writeFileSync(path.join(tempDir, 'dist', 'SKILL.md'), '# Built');
|
|
100
|
-
fs.writeFileSync(path.join(tempDir, 'SKILL.md'), '# Source');
|
|
101
|
-
|
|
102
|
-
const result = await scan({ path: tempDir, recursive: true });
|
|
103
|
-
|
|
104
|
-
expect(result.totalScanned).toBe(2);
|
|
105
|
-
expect(result.sourceFiles).toHaveLength(1);
|
|
106
|
-
expect(result.buildOutputs).toHaveLength(1);
|
|
107
|
-
expect(result.sourceFiles[0]?.relativePath).toBe('SKILL.md');
|
|
108
|
-
});
|
|
109
|
-
});
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { createPatternFilter } from '../src/filters/pattern-filter.js';
|
|
4
|
-
|
|
5
|
-
describe('createPatternFilter', () => {
|
|
6
|
-
describe('include patterns', () => {
|
|
7
|
-
it('should include files matching pattern', () => {
|
|
8
|
-
const filter = createPatternFilter({ include: ['*.md'] });
|
|
9
|
-
|
|
10
|
-
expect(filter('test.md')).toBe(true);
|
|
11
|
-
expect(filter('docs/guide.md')).toBe(true);
|
|
12
|
-
expect(filter('test.ts')).toBe(false);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('should support glob patterns', () => {
|
|
16
|
-
const filter = createPatternFilter({ include: ['**/*.test.ts'] });
|
|
17
|
-
|
|
18
|
-
expect(filter('src/utils.test.ts')).toBe(true);
|
|
19
|
-
expect(filter('test/unit/parser.test.ts')).toBe(true);
|
|
20
|
-
expect(filter('src/utils.ts')).toBe(false);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('should support multiple include patterns', () => {
|
|
24
|
-
const filter = createPatternFilter({
|
|
25
|
-
include: ['*.md', '*.yaml']
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
expect(filter('README.md')).toBe(true);
|
|
29
|
-
expect(filter('config.yaml')).toBe(true);
|
|
30
|
-
expect(filter('index.ts')).toBe(false);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe('exclude patterns', () => {
|
|
35
|
-
it('should exclude files matching pattern', () => {
|
|
36
|
-
const filter = createPatternFilter({ exclude: ['node_modules'] });
|
|
37
|
-
|
|
38
|
-
expect(filter('node_modules/pkg/index.js')).toBe(false);
|
|
39
|
-
expect(filter('src/index.ts')).toBe(true);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should support glob patterns', () => {
|
|
43
|
-
const filter = createPatternFilter({
|
|
44
|
-
exclude: ['**/dist/**', '**/*.test.ts']
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
expect(filter('packages/cli/dist/index.js')).toBe(false);
|
|
48
|
-
expect(filter('src/utils.test.ts')).toBe(false);
|
|
49
|
-
expect(filter('src/utils.ts')).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe('include + exclude patterns', () => {
|
|
54
|
-
it('should apply exclude after include', () => {
|
|
55
|
-
const filter = createPatternFilter({
|
|
56
|
-
include: ['**/*.md'],
|
|
57
|
-
exclude: ['**/node_modules/**'],
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
expect(filter('README.md')).toBe(true);
|
|
61
|
-
expect(filter('docs/guide.md')).toBe(true);
|
|
62
|
-
expect(filter('node_modules/pkg/README.md')).toBe(false);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
describe('no patterns', () => {
|
|
67
|
-
it('should include all files when no patterns specified', () => {
|
|
68
|
-
const filter = createPatternFilter({});
|
|
69
|
-
|
|
70
|
-
expect(filter('any-file.txt')).toBe(true);
|
|
71
|
-
expect(filter('path/to/file.js')).toBe(true);
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
});
|