@flagshark/core 1.0.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/dist/detection/config-adapter.d.ts +16 -0
- package/dist/detection/config-adapter.d.ts.map +1 -0
- package/dist/detection/config-adapter.js +60 -0
- package/dist/detection/config-adapter.js.map +1 -0
- package/dist/detection/detectors/cpp.d.ts +17 -0
- package/dist/detection/detectors/cpp.d.ts.map +1 -0
- package/dist/detection/detectors/cpp.js +113 -0
- package/dist/detection/detectors/cpp.js.map +1 -0
- package/dist/detection/detectors/csharp.d.ts +17 -0
- package/dist/detection/detectors/csharp.d.ts.map +1 -0
- package/dist/detection/detectors/csharp.js +258 -0
- package/dist/detection/detectors/csharp.js.map +1 -0
- package/dist/detection/detectors/go.d.ts +17 -0
- package/dist/detection/detectors/go.d.ts.map +1 -0
- package/dist/detection/detectors/go.js +319 -0
- package/dist/detection/detectors/go.js.map +1 -0
- package/dist/detection/detectors/index.d.ts +14 -0
- package/dist/detection/detectors/index.d.ts.map +1 -0
- package/dist/detection/detectors/index.js +14 -0
- package/dist/detection/detectors/index.js.map +1 -0
- package/dist/detection/detectors/java.d.ts +17 -0
- package/dist/detection/detectors/java.d.ts.map +1 -0
- package/dist/detection/detectors/java.js +264 -0
- package/dist/detection/detectors/java.js.map +1 -0
- package/dist/detection/detectors/javascript.d.ts +18 -0
- package/dist/detection/detectors/javascript.d.ts.map +1 -0
- package/dist/detection/detectors/javascript.js +32 -0
- package/dist/detection/detectors/javascript.js.map +1 -0
- package/dist/detection/detectors/kotlin.d.ts +17 -0
- package/dist/detection/detectors/kotlin.d.ts.map +1 -0
- package/dist/detection/detectors/kotlin.js +210 -0
- package/dist/detection/detectors/kotlin.js.map +1 -0
- package/dist/detection/detectors/objectivec.d.ts +17 -0
- package/dist/detection/detectors/objectivec.d.ts.map +1 -0
- package/dist/detection/detectors/objectivec.js +244 -0
- package/dist/detection/detectors/objectivec.js.map +1 -0
- package/dist/detection/detectors/php.d.ts +17 -0
- package/dist/detection/detectors/php.d.ts.map +1 -0
- package/dist/detection/detectors/php.js +210 -0
- package/dist/detection/detectors/php.js.map +1 -0
- package/dist/detection/detectors/python.d.ts +17 -0
- package/dist/detection/detectors/python.d.ts.map +1 -0
- package/dist/detection/detectors/python.js +284 -0
- package/dist/detection/detectors/python.js.map +1 -0
- package/dist/detection/detectors/ruby.d.ts +17 -0
- package/dist/detection/detectors/ruby.d.ts.map +1 -0
- package/dist/detection/detectors/ruby.js +259 -0
- package/dist/detection/detectors/ruby.js.map +1 -0
- package/dist/detection/detectors/rust.d.ts +17 -0
- package/dist/detection/detectors/rust.d.ts.map +1 -0
- package/dist/detection/detectors/rust.js +159 -0
- package/dist/detection/detectors/rust.js.map +1 -0
- package/dist/detection/detectors/swift.d.ts +17 -0
- package/dist/detection/detectors/swift.d.ts.map +1 -0
- package/dist/detection/detectors/swift.js +229 -0
- package/dist/detection/detectors/swift.js.map +1 -0
- package/dist/detection/detectors/typescript.d.ts +17 -0
- package/dist/detection/detectors/typescript.d.ts.map +1 -0
- package/dist/detection/detectors/typescript.js +350 -0
- package/dist/detection/detectors/typescript.js.map +1 -0
- package/dist/detection/feature-flag.d.ts +16 -0
- package/dist/detection/feature-flag.d.ts.map +1 -0
- package/dist/detection/feature-flag.js +2 -0
- package/dist/detection/feature-flag.js.map +1 -0
- package/dist/detection/helpers.d.ts +51 -0
- package/dist/detection/helpers.d.ts.map +1 -0
- package/dist/detection/helpers.js +266 -0
- package/dist/detection/helpers.js.map +1 -0
- package/dist/detection/index.d.ts +19 -0
- package/dist/detection/index.d.ts.map +1 -0
- package/dist/detection/index.js +53 -0
- package/dist/detection/index.js.map +1 -0
- package/dist/detection/interface.d.ts +76 -0
- package/dist/detection/interface.d.ts.map +1 -0
- package/dist/detection/interface.js +25 -0
- package/dist/detection/interface.js.map +1 -0
- package/dist/detection/polyglot-analyzer.d.ts +83 -0
- package/dist/detection/polyglot-analyzer.d.ts.map +1 -0
- package/dist/detection/polyglot-analyzer.js +245 -0
- package/dist/detection/polyglot-analyzer.js.map +1 -0
- package/dist/detection/registry.d.ts +31 -0
- package/dist/detection/registry.d.ts.map +1 -0
- package/dist/detection/registry.js +68 -0
- package/dist/detection/registry.js.map +1 -0
- package/dist/detection/yaml-config.d.ts +333 -0
- package/dist/detection/yaml-config.d.ts.map +1 -0
- package/dist/detection/yaml-config.js +215 -0
- package/dist/detection/yaml-config.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/scan-repo.d.ts +67 -0
- package/dist/scan-repo.d.ts.map +1 -0
- package/dist/scan-repo.js +55 -0
- package/dist/scan-repo.js.map +1 -0
- package/dist/scanner.d.ts +18 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +108 -0
- package/dist/scanner.js.map +1 -0
- package/dist/staleness.d.ts +30 -0
- package/dist/staleness.d.ts.map +1 -0
- package/dist/staleness.js +217 -0
- package/dist/staleness.js.map +1 -0
- package/package.json +37 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Top-level orchestrator: walk the repo, detect flags, analyse staleness,
|
|
3
|
+
* return a single result object that consumers can render however they like.
|
|
4
|
+
*/
|
|
5
|
+
import { collectFiles } from './scanner.js';
|
|
6
|
+
import { createDefaultRegistry } from './detection/index.js';
|
|
7
|
+
import { PolyglotAnalyzer } from './detection/polyglot-analyzer.js';
|
|
8
|
+
import { analyzeStaleness } from './staleness.js';
|
|
9
|
+
const NOOP_LOGGER = {
|
|
10
|
+
debug: () => { },
|
|
11
|
+
info: () => { },
|
|
12
|
+
warn: () => { },
|
|
13
|
+
error: () => { },
|
|
14
|
+
};
|
|
15
|
+
export async function scanRepo(opts) {
|
|
16
|
+
const start = performance.now();
|
|
17
|
+
const logger = opts.logger ?? NOOP_LOGGER;
|
|
18
|
+
const threshold = opts.threshold ?? 6;
|
|
19
|
+
const registry = createDefaultRegistry();
|
|
20
|
+
const supportedExtensions = new Set(registry.getSupportedExtensions());
|
|
21
|
+
const analyzer = new PolyglotAnalyzer(registry, logger);
|
|
22
|
+
logger.debug('Collecting files...');
|
|
23
|
+
const files = collectFiles({
|
|
24
|
+
root: opts.cwd,
|
|
25
|
+
supportedExtensions,
|
|
26
|
+
diffRef: opts.diff,
|
|
27
|
+
});
|
|
28
|
+
logger.debug(`Detected ${files.size} candidate files`);
|
|
29
|
+
const filesScanned = files.size;
|
|
30
|
+
const analysisResult = await analyzer.analyzeFiles(files, opts.signal);
|
|
31
|
+
const staleFlags = await analyzeStaleness(analysisResult.totalFlags, { thresholdMonths: threshold, repoRoot: opts.cwd });
|
|
32
|
+
const totalFlags = analysisResult.totalFlags.size;
|
|
33
|
+
const uniqueStaleNames = new Set(staleFlags.map((f) => f.name)).size;
|
|
34
|
+
const healthScore = totalFlags === 0 ? 100 : Math.round(((totalFlags - uniqueStaleNames) / totalFlags) * 100);
|
|
35
|
+
const allFlags = [];
|
|
36
|
+
for (const flags of analysisResult.totalFlags.values()) {
|
|
37
|
+
allFlags.push(...flags);
|
|
38
|
+
}
|
|
39
|
+
const detectedProviders = [
|
|
40
|
+
...new Set(allFlags
|
|
41
|
+
.map((f) => f.provider)
|
|
42
|
+
.filter((p) => p != null && p !== '')),
|
|
43
|
+
];
|
|
44
|
+
return {
|
|
45
|
+
totalFlags,
|
|
46
|
+
filesScanned,
|
|
47
|
+
staleFlags,
|
|
48
|
+
detectedProviders,
|
|
49
|
+
// analysisResult.languages is Map<Language, number> — convert to plain object
|
|
50
|
+
languageBreakdown: Object.fromEntries(analysisResult.languages),
|
|
51
|
+
healthScore,
|
|
52
|
+
scanDuration: Math.round(performance.now() - start),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=scan-repo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan-repo.js","sourceRoot":"","sources":["../src/scan-repo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File system scanner that wraps PolyglotAnalyzer.
|
|
3
|
+
*
|
|
4
|
+
* Reads files from disk, filters by supported extensions,
|
|
5
|
+
* and feeds them to the detection engine.
|
|
6
|
+
*
|
|
7
|
+
* walkDir() → filterFiles() → readFiles() → PolyglotAnalyzer.analyzeFiles()
|
|
8
|
+
*/
|
|
9
|
+
export interface ScanOptions {
|
|
10
|
+
root: string;
|
|
11
|
+
supportedExtensions: Set<string>;
|
|
12
|
+
diffRef?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Collects files to scan, reads their contents, and returns a Map<filePath, content>.
|
|
16
|
+
*/
|
|
17
|
+
export declare function collectFiles(options: ScanOptions): Map<string, string>;
|
|
18
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAwBH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CA4BtE"}
|
package/dist/scanner.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File system scanner that wraps PolyglotAnalyzer.
|
|
3
|
+
*
|
|
4
|
+
* Reads files from disk, filters by supported extensions,
|
|
5
|
+
* and feeds them to the detection engine.
|
|
6
|
+
*
|
|
7
|
+
* walkDir() → filterFiles() → readFiles() → PolyglotAnalyzer.analyzeFiles()
|
|
8
|
+
*/
|
|
9
|
+
import { execFileSync } from 'node:child_process';
|
|
10
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
11
|
+
import { join, extname } from 'node:path';
|
|
12
|
+
const SKIP_DIRS = new Set([
|
|
13
|
+
'node_modules',
|
|
14
|
+
'vendor',
|
|
15
|
+
'.git',
|
|
16
|
+
'dist',
|
|
17
|
+
'build',
|
|
18
|
+
'coverage',
|
|
19
|
+
'__pycache__',
|
|
20
|
+
'.next',
|
|
21
|
+
'.turbo',
|
|
22
|
+
'.cache',
|
|
23
|
+
'.venv',
|
|
24
|
+
'venv',
|
|
25
|
+
'env',
|
|
26
|
+
]);
|
|
27
|
+
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
|
|
28
|
+
/**
|
|
29
|
+
* Collects files to scan, reads their contents, and returns a Map<filePath, content>.
|
|
30
|
+
*/
|
|
31
|
+
export function collectFiles(options) {
|
|
32
|
+
const { root, supportedExtensions, diffRef } = options;
|
|
33
|
+
const files = new Map();
|
|
34
|
+
let filePaths;
|
|
35
|
+
if (diffRef) {
|
|
36
|
+
filePaths = getDiffFiles(diffRef, supportedExtensions);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
filePaths = walkDir(root, supportedExtensions);
|
|
40
|
+
}
|
|
41
|
+
for (const fp of filePaths) {
|
|
42
|
+
try {
|
|
43
|
+
const stat = statSync(fp);
|
|
44
|
+
if (stat.size > MAX_FILE_SIZE) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (stat.size === 0) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
files.set(fp, readFileSync(fp, 'utf-8'));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Skip unreadable files
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return files;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get files changed since a git ref.
|
|
60
|
+
*/
|
|
61
|
+
function getDiffFiles(ref, supportedExtensions) {
|
|
62
|
+
try {
|
|
63
|
+
const output = execFileSync('git', ['diff', ref, '--name-only', '--diff-filter=ACMR'], {
|
|
64
|
+
encoding: 'utf-8',
|
|
65
|
+
timeout: 30000,
|
|
66
|
+
});
|
|
67
|
+
return output
|
|
68
|
+
.split('\n')
|
|
69
|
+
.map((line) => line.trim())
|
|
70
|
+
.filter((line) => line.length > 0)
|
|
71
|
+
.filter((line) => supportedExtensions.has(extname(line)));
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
throw new Error(`Failed to get diff from ref "${ref}". Is this a git repository?`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Recursively walk a directory, returning paths to supported source files.
|
|
79
|
+
*/
|
|
80
|
+
function walkDir(dir, supportedExtensions) {
|
|
81
|
+
const results = [];
|
|
82
|
+
try {
|
|
83
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
if (SKIP_DIRS.has(entry.name)) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (entry.name.startsWith('.')) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const fullPath = join(dir, entry.name);
|
|
92
|
+
if (entry.isDirectory()) {
|
|
93
|
+
results.push(...walkDir(fullPath, supportedExtensions));
|
|
94
|
+
}
|
|
95
|
+
else if (entry.isFile()) {
|
|
96
|
+
const ext = extname(entry.name);
|
|
97
|
+
if (supportedExtensions.has(ext)) {
|
|
98
|
+
results.push(fullPath);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Skip unreadable directories
|
|
105
|
+
}
|
|
106
|
+
return results;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type FeatureFlag } from './detection/feature-flag.js';
|
|
2
|
+
export interface StalenessSignal {
|
|
3
|
+
type: 'age' | 'hardcoded' | 'low-usage';
|
|
4
|
+
description: string;
|
|
5
|
+
}
|
|
6
|
+
export interface StaleFlag {
|
|
7
|
+
name: string;
|
|
8
|
+
filePath: string;
|
|
9
|
+
lineNumber: number;
|
|
10
|
+
language: string;
|
|
11
|
+
provider: string;
|
|
12
|
+
signals: StalenessSignal[];
|
|
13
|
+
/** Human-readable age, e.g. "14 months ago" */
|
|
14
|
+
age?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface StalenessOptions {
|
|
17
|
+
/** Flag lines older than this are considered stale. Default: 6. */
|
|
18
|
+
thresholdMonths: number;
|
|
19
|
+
/** Absolute path to the git repository root. */
|
|
20
|
+
repoRoot: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Analyze a set of detected feature flags for staleness signals.
|
|
24
|
+
*
|
|
25
|
+
* @param flags Map of flag name → all occurrences across the codebase.
|
|
26
|
+
* @param options Staleness configuration (threshold, repo root).
|
|
27
|
+
* @returns Array of flags that have at least one staleness signal.
|
|
28
|
+
*/
|
|
29
|
+
export declare function analyzeStaleness(flags: Map<string, FeatureFlag[]>, options: StalenessOptions): Promise<StaleFlag[]>;
|
|
30
|
+
//# sourceMappingURL=staleness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staleness.d.ts","sourceRoot":"","sources":["../src/staleness.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,6BAA6B,CAAA;AAI9D,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,KAAK,GAAG,WAAW,GAAG,WAAW,CAAA;IACvC,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,eAAe,EAAE,CAAA;IAC1B,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,eAAe,EAAE,MAAM,CAAA;IACvB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;CACjB;AAyKD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,EACjC,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,SAAS,EAAE,CAAC,CAyEtB"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { execFileSync, execSync } from 'child_process';
|
|
2
|
+
// ── Helpers ────────────────────────────────────────────────────────
|
|
3
|
+
/**
|
|
4
|
+
* Returns true when the repository is a shallow clone (git blame timestamps
|
|
5
|
+
* would all point to the checkout commit and be meaningless).
|
|
6
|
+
*/
|
|
7
|
+
function isShallowRepo(repoRoot) {
|
|
8
|
+
try {
|
|
9
|
+
const out = execSync('git rev-parse --is-shallow-repository', {
|
|
10
|
+
cwd: repoRoot,
|
|
11
|
+
encoding: 'utf-8',
|
|
12
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
13
|
+
}).trim();
|
|
14
|
+
return out === 'true';
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// Not a git repo at all – caller will handle.
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parse `git blame --porcelain` output and return a map of
|
|
23
|
+
* 1-based line number → author-time (unix seconds).
|
|
24
|
+
*
|
|
25
|
+
* Porcelain format per-line group:
|
|
26
|
+
* <sha> <orig-line> <final-line> [<group-size>]
|
|
27
|
+
* author ...
|
|
28
|
+
* author-mail ...
|
|
29
|
+
* author-time <unix-ts>
|
|
30
|
+
* ...
|
|
31
|
+
* \t<content line>
|
|
32
|
+
*/
|
|
33
|
+
function parseBlamePortcelain(output) {
|
|
34
|
+
const lineAges = new Map();
|
|
35
|
+
const lines = output.split('\n');
|
|
36
|
+
let currentLine = 0;
|
|
37
|
+
let currentAuthorTime = 0;
|
|
38
|
+
for (const line of lines) {
|
|
39
|
+
// Group header: <40-char sha> <orig> <final> [count]
|
|
40
|
+
const headerMatch = line.match(/^[0-9a-f]{40}\s+\d+\s+(\d+)(?:\s+\d+)?$/);
|
|
41
|
+
if (headerMatch) {
|
|
42
|
+
currentLine = parseInt(headerMatch[1], 10);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
// author-time line
|
|
46
|
+
if (line.startsWith('author-time ')) {
|
|
47
|
+
currentAuthorTime = parseInt(line.slice('author-time '.length), 10);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
// Content line (starts with tab) – marks end of this group's metadata.
|
|
51
|
+
if (line.startsWith('\t')) {
|
|
52
|
+
if (currentLine > 0 && currentAuthorTime > 0) {
|
|
53
|
+
lineAges.set(currentLine, currentAuthorTime);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return lineAges;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Run `git blame --porcelain` for a single file and return line → unix-ts map.
|
|
61
|
+
* Returns null on any failure (untracked file, binary, non-git dir, etc.).
|
|
62
|
+
*/
|
|
63
|
+
function blameFile(filePath, repoRoot) {
|
|
64
|
+
try {
|
|
65
|
+
const out = execFileSync('git', ['blame', '--porcelain', '--', filePath], {
|
|
66
|
+
cwd: repoRoot,
|
|
67
|
+
encoding: 'utf-8',
|
|
68
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
69
|
+
maxBuffer: 10 * 1024 * 1024, // 10 MB – large files
|
|
70
|
+
});
|
|
71
|
+
return parseBlamePortcelain(out);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Untracked, binary, or other git error – skip gracefully.
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Convert a unix timestamp to a human-readable relative age string,
|
|
80
|
+
* e.g. "14 months ago", "3 years ago", "29 days ago".
|
|
81
|
+
*/
|
|
82
|
+
function formatAge(unixSeconds) {
|
|
83
|
+
const nowMs = Date.now();
|
|
84
|
+
const thenMs = unixSeconds * 1000;
|
|
85
|
+
const diffMs = nowMs - thenMs;
|
|
86
|
+
const seconds = Math.floor(diffMs / 1000);
|
|
87
|
+
const minutes = Math.floor(seconds / 60);
|
|
88
|
+
const hours = Math.floor(minutes / 60);
|
|
89
|
+
const days = Math.floor(hours / 24);
|
|
90
|
+
const months = Math.floor(days / 30.44); // average days/month
|
|
91
|
+
const years = Math.floor(days / 365.25);
|
|
92
|
+
if (years >= 1) {
|
|
93
|
+
return `${years} year${years === 1 ? '' : 's'} ago`;
|
|
94
|
+
}
|
|
95
|
+
if (months >= 1) {
|
|
96
|
+
return `${months} month${months === 1 ? '' : 's'} ago`;
|
|
97
|
+
}
|
|
98
|
+
if (days >= 1) {
|
|
99
|
+
return `${days} day${days === 1 ? '' : 's'} ago`;
|
|
100
|
+
}
|
|
101
|
+
return 'less than a day ago';
|
|
102
|
+
}
|
|
103
|
+
// ── Signal detectors ───────────────────────────────────────────────
|
|
104
|
+
/**
|
|
105
|
+
* Check whether a flag's line is older than the threshold.
|
|
106
|
+
* Returns the signal + age string, or null if the signal doesn't fire.
|
|
107
|
+
*/
|
|
108
|
+
function checkAgeSignal(authorTime, thresholdMonths) {
|
|
109
|
+
if (authorTime === undefined) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const thresholdMs = thresholdMonths * 30.44 * 24 * 60 * 60 * 1000;
|
|
113
|
+
const ageMs = Date.now() - authorTime * 1000;
|
|
114
|
+
if (ageMs < thresholdMs) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const age = formatAge(authorTime);
|
|
118
|
+
return {
|
|
119
|
+
signal: {
|
|
120
|
+
type: 'age',
|
|
121
|
+
description: `Flag reference last modified ${age} (threshold: ${thresholdMonths} months)`,
|
|
122
|
+
},
|
|
123
|
+
age,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Check whether a flag appears in only one file.
|
|
128
|
+
*/
|
|
129
|
+
function checkLowUsageSignal(flagName, occurrences) {
|
|
130
|
+
const uniqueFiles = new Set(occurrences.map((o) => o.filePath));
|
|
131
|
+
if (uniqueFiles.size > 1) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
type: 'low-usage',
|
|
136
|
+
description: `Flag "${flagName}" only appears in 1 file — may have been fully rolled out`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Hardcoded signal — placeholder for tree-sitter v2.
|
|
141
|
+
* Always returns null (no signal) for the regex v1 implementation.
|
|
142
|
+
*/
|
|
143
|
+
function checkHardcodedSignal(_flag) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
// ── Main entry point ───────────────────────────────────────────────
|
|
147
|
+
/**
|
|
148
|
+
* Analyze a set of detected feature flags for staleness signals.
|
|
149
|
+
*
|
|
150
|
+
* @param flags Map of flag name → all occurrences across the codebase.
|
|
151
|
+
* @param options Staleness configuration (threshold, repo root).
|
|
152
|
+
* @returns Array of flags that have at least one staleness signal.
|
|
153
|
+
*/
|
|
154
|
+
export async function analyzeStaleness(flags, options) {
|
|
155
|
+
const { thresholdMonths = 6, repoRoot } = options;
|
|
156
|
+
// ── 1. Determine whether we can use git blame at all ──
|
|
157
|
+
const shallow = isShallowRepo(repoRoot);
|
|
158
|
+
// ── 2. Collect unique files that need blaming ──
|
|
159
|
+
const fileBlames = new Map();
|
|
160
|
+
if (!shallow) {
|
|
161
|
+
const filesToBlame = new Set();
|
|
162
|
+
for (const occurrences of flags.values()) {
|
|
163
|
+
for (const flag of occurrences) {
|
|
164
|
+
filesToBlame.add(flag.filePath);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const file of filesToBlame) {
|
|
168
|
+
fileBlames.set(file, blameFile(file, repoRoot));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// ── 3. Evaluate signals for every flag occurrence ──
|
|
172
|
+
const staleFlags = [];
|
|
173
|
+
for (const [flagName, occurrences] of flags) {
|
|
174
|
+
// Low-usage is per-flag-name (not per-occurrence).
|
|
175
|
+
const lowUsageSignal = checkLowUsageSignal(flagName, occurrences);
|
|
176
|
+
for (const flag of occurrences) {
|
|
177
|
+
const signals = [];
|
|
178
|
+
let age;
|
|
179
|
+
// Age signal (git blame)
|
|
180
|
+
if (!shallow) {
|
|
181
|
+
const blame = fileBlames.get(flag.filePath);
|
|
182
|
+
const authorTime = blame?.get(flag.lineNumber);
|
|
183
|
+
const ageResult = checkAgeSignal(authorTime, thresholdMonths);
|
|
184
|
+
if (ageResult) {
|
|
185
|
+
signals.push(ageResult.signal);
|
|
186
|
+
age = ageResult.age;
|
|
187
|
+
}
|
|
188
|
+
else if (authorTime !== undefined) {
|
|
189
|
+
age = formatAge(authorTime);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Low-usage signal
|
|
193
|
+
if (lowUsageSignal) {
|
|
194
|
+
signals.push(lowUsageSignal);
|
|
195
|
+
}
|
|
196
|
+
// Hardcoded signal (v2 placeholder)
|
|
197
|
+
const hardcoded = checkHardcodedSignal(flag);
|
|
198
|
+
if (hardcoded) {
|
|
199
|
+
signals.push(hardcoded);
|
|
200
|
+
}
|
|
201
|
+
// Only include flags that have at least one signal.
|
|
202
|
+
if (signals.length > 0) {
|
|
203
|
+
staleFlags.push({
|
|
204
|
+
name: flag.name,
|
|
205
|
+
filePath: flag.filePath,
|
|
206
|
+
lineNumber: flag.lineNumber,
|
|
207
|
+
language: flag.language,
|
|
208
|
+
provider: flag.provider ?? 'unknown',
|
|
209
|
+
signals,
|
|
210
|
+
age,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return staleFlags;
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=staleness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staleness.js","sourceRoot":"","sources":["../src/staleness.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flagshark/core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Detection engine for FlagShark — finds feature flag references across 13 languages.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/FlagShark/flagshark.git",
|
|
10
|
+
"directory": "packages/core"
|
|
11
|
+
},
|
|
12
|
+
"keywords": ["feature-flags", "polyglot-analysis", "static-analysis"],
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": ["dist/"],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"typecheck": "tsc --noEmit"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"p-limit": "^6.0.0",
|
|
29
|
+
"zod": "^3.23.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^22.0.0",
|
|
33
|
+
"typescript": "^5.7.0",
|
|
34
|
+
"vitest": "^3.0.0"
|
|
35
|
+
},
|
|
36
|
+
"engines": { "node": ">=18.0.0" }
|
|
37
|
+
}
|