@flagshark/core 1.5.0 → 2.1.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/README.md +8 -6
- package/dist/config/schema.d.ts +112 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +64 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/detection/detectors/typescript.d.ts.map +1 -1
- package/dist/detection/detectors/typescript.js +41 -0
- package/dist/detection/detectors/typescript.js.map +1 -1
- package/dist/detection/feature-flag.d.ts +28 -0
- package/dist/detection/feature-flag.d.ts.map +1 -1
- package/dist/detection/helpers.d.ts +16 -0
- package/dist/detection/helpers.d.ts.map +1 -1
- package/dist/detection/helpers.js +117 -6
- package/dist/detection/helpers.js.map +1 -1
- package/dist/detection/import-graph.d.ts +234 -0
- package/dist/detection/import-graph.d.ts.map +1 -0
- package/dist/detection/import-graph.js +641 -0
- package/dist/detection/import-graph.js.map +1 -0
- package/dist/detection/interface.d.ts +57 -0
- package/dist/detection/interface.d.ts.map +1 -1
- package/dist/detection/interface.js.map +1 -1
- package/dist/detection/polyglot-analyzer.d.ts +8 -0
- package/dist/detection/polyglot-analyzer.d.ts.map +1 -1
- package/dist/detection/polyglot-analyzer.js +2 -0
- package/dist/detection/polyglot-analyzer.js.map +1 -1
- package/dist/detection/tree-sitter/engine.d.ts.map +1 -1
- package/dist/detection/tree-sitter/engine.js +62 -15
- package/dist/detection/tree-sitter/engine.js.map +1 -1
- package/dist/detection/tree-sitter/parser-cache.d.ts +20 -0
- package/dist/detection/tree-sitter/parser-cache.d.ts.map +1 -1
- package/dist/detection/tree-sitter/parser-cache.js +32 -1
- package/dist/detection/tree-sitter/parser-cache.js.map +1 -1
- package/dist/output/json.d.ts.map +1 -1
- package/dist/output/json.js +28 -0
- package/dist/output/json.js.map +1 -1
- package/dist/output/markdown.d.ts.map +1 -1
- package/dist/output/markdown.js +47 -1
- package/dist/output/markdown.js.map +1 -1
- package/dist/output/text.d.ts.map +1 -1
- package/dist/output/text.js +85 -0
- package/dist/output/text.js.map +1 -1
- package/dist/providers/cross-reference.d.ts +24 -2
- package/dist/providers/cross-reference.d.ts.map +1 -1
- package/dist/providers/cross-reference.js +74 -7
- package/dist/providers/cross-reference.js.map +1 -1
- package/dist/providers/interface.d.ts +89 -2
- package/dist/providers/interface.d.ts.map +1 -1
- package/dist/providers/launchdarkly/client.d.ts +12 -0
- package/dist/providers/launchdarkly/client.d.ts.map +1 -1
- package/dist/providers/launchdarkly/client.js +163 -23
- package/dist/providers/launchdarkly/client.js.map +1 -1
- package/dist/providers/launchdarkly/types.d.ts +286 -0
- package/dist/providers/launchdarkly/types.d.ts.map +1 -1
- package/dist/providers/launchdarkly/types.js +56 -0
- package/dist/providers/launchdarkly/types.js.map +1 -1
- package/dist/providers/orchestrate.d.ts +34 -2
- package/dist/providers/orchestrate.d.ts.map +1 -1
- package/dist/providers/orchestrate.js +59 -7
- package/dist/providers/orchestrate.js.map +1 -1
- package/dist/scan-repo.d.ts +30 -2
- package/dist/scan-repo.d.ts.map +1 -1
- package/dist/scan-repo.js +290 -4
- package/dist/scan-repo.js.map +1 -1
- package/dist/staleness.d.ts +33 -3
- package/dist/staleness.d.ts.map +1 -1
- package/dist/staleness.js +60 -13
- package/dist/staleness.js.map +1 -1
- package/package.json +1 -1
|
@@ -10,15 +10,47 @@ export interface OrchestratePlatformsOptions {
|
|
|
10
10
|
/** When true, skip cache for this run. */
|
|
11
11
|
noCache?: boolean;
|
|
12
12
|
signal?: AbortSignal;
|
|
13
|
+
/**
|
|
14
|
+
* Staleness threshold in days. Passed through to cross-reference so it
|
|
15
|
+
* can emit `platform-too-old` for flags whose platform-side creationDate
|
|
16
|
+
* exceeds the threshold. When omitted, the platform-too-old signal is
|
|
17
|
+
* never emitted.
|
|
18
|
+
*/
|
|
19
|
+
thresholdDays?: number;
|
|
13
20
|
/**
|
|
14
21
|
* @internal — test seam. When set, used instead of platform.listFlags().
|
|
15
22
|
* Allows tests to bypass network without monkey-patching globalThis.fetch.
|
|
16
23
|
*/
|
|
17
24
|
listFlagsOverride?: (signal?: AbortSignal) => Promise<PlatformFlag[]>;
|
|
18
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Returned alongside platform signals: the names of flags excluded from
|
|
28
|
+
* staleness checks because a platform marked them permanent, indexed by
|
|
29
|
+
* the platform's displayName. Output formatters surface this directly
|
|
30
|
+
* so users see WHY a permanent flag isn't in the stale table.
|
|
31
|
+
*/
|
|
32
|
+
export interface OrchestrateResult {
|
|
33
|
+
signals: Map<string, PlatformSignal[]>;
|
|
34
|
+
permanentByPlatform: Record<string, string[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Per-flag platform-side metadata (tags, maintainer, status). Keyed
|
|
37
|
+
* by detected flag name. Populated only for flags that matched a
|
|
38
|
+
* platform integration AND the platform exposed the data. When
|
|
39
|
+
* multiple platforms report different metadata for the same flag,
|
|
40
|
+
* the LAST platform processed wins — the typical case is one
|
|
41
|
+
* platform per project anyway.
|
|
42
|
+
*/
|
|
43
|
+
metadataByFlag: Map<string, {
|
|
44
|
+
tags?: string[];
|
|
45
|
+
maintainer?: string;
|
|
46
|
+
status?: 'new' | 'active' | 'inactive' | 'launched';
|
|
47
|
+
}>;
|
|
48
|
+
}
|
|
19
49
|
/**
|
|
20
50
|
* Runs each configured platform integration. Logs warnings on individual
|
|
21
|
-
* platform failures and continues. Returns merged signals keyed by flag name
|
|
51
|
+
* platform failures and continues. Returns merged signals keyed by flag name
|
|
52
|
+
* plus a per-platform record of flags marked permanent (suppressed from
|
|
53
|
+
* staleness; surfaced separately so users see what was excluded and why).
|
|
22
54
|
*/
|
|
23
|
-
export declare function orchestratePlatforms(opts: OrchestratePlatformsOptions): Promise<
|
|
55
|
+
export declare function orchestratePlatforms(opts: OrchestratePlatformsOptions): Promise<OrchestrateResult>;
|
|
24
56
|
//# sourceMappingURL=orchestrate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrate.d.ts","sourceRoot":"","sources":["../../src/providers/orchestrate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEjD,MAAM,WAAW,2BAA2B;IAC1C,iFAAiF;IACjF,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;IACpD,4CAA4C;IAC5C,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAA;IACzC,MAAM,EAAE,UAAU,CAAA;IAClB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAA;CACtE;AAED
|
|
1
|
+
{"version":3,"file":"orchestrate.d.ts","sourceRoot":"","sources":["../../src/providers/orchestrate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEjD,MAAM,WAAW,2BAA2B;IAC1C,iFAAiF;IACjF,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;IACpD,4CAA4C;IAC5C,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAA;IACzC,MAAM,EAAE,UAAU,CAAA;IAClB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAA;CACtE;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC,CAAA;IACtC,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IAC7C;;;;;;;OAOG;IACH,cAAc,EAAE,GAAG,CACjB,MAAM,EACN;QAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAA;KAAE,CAC9F,CAAA;CACF;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,2BAA2B,GAChC,OAAO,CAAC,iBAAiB,CAAC,CA8F5B"}
|
|
@@ -3,12 +3,17 @@ import { crossReference, mergePlatformSignals } from './cross-reference.js';
|
|
|
3
3
|
import { computeCacheKey, loadPlatformFlagsCached } from './cache.js';
|
|
4
4
|
/**
|
|
5
5
|
* Runs each configured platform integration. Logs warnings on individual
|
|
6
|
-
* platform failures and continues. Returns merged signals keyed by flag name
|
|
6
|
+
* platform failures and continues. Returns merged signals keyed by flag name
|
|
7
|
+
* plus a per-platform record of flags marked permanent (suppressed from
|
|
8
|
+
* staleness; surfaced separately so users see what was excluded and why).
|
|
7
9
|
*/
|
|
8
10
|
export async function orchestratePlatforms(opts) {
|
|
9
11
|
const out = new Map();
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
const permanentByPlatform = {};
|
|
13
|
+
const metadataByFlag = new Map();
|
|
14
|
+
if (!opts.platformsConfig) {
|
|
15
|
+
return { signals: out, permanentByPlatform, metadataByFlag };
|
|
16
|
+
}
|
|
12
17
|
for (const [name, rawConfig] of Object.entries(opts.platformsConfig)) {
|
|
13
18
|
const def = findPlatform(name);
|
|
14
19
|
if (!def) {
|
|
@@ -21,7 +26,13 @@ export async function orchestratePlatforms(opts) {
|
|
|
21
26
|
continue;
|
|
22
27
|
}
|
|
23
28
|
const tokenEnv = rawConfig.token_env ?? def.defaultTokenEnv;
|
|
24
|
-
|
|
29
|
+
// Trim whitespace before passing to the auth header. A common foot-gun:
|
|
30
|
+
// tokens copied from a UI or sourced via `.env`/`export` end up with a
|
|
31
|
+
// trailing newline or leading space, which fetch propagates verbatim
|
|
32
|
+
// into `Authorization:` and the platform rejects as 401. Trim once
|
|
33
|
+
// here so every platform's createClient sees a clean string.
|
|
34
|
+
const rawToken = process.env[tokenEnv];
|
|
35
|
+
const token = rawToken?.trim() ?? '';
|
|
25
36
|
if (!token) {
|
|
26
37
|
opts.logger.warn(`${def.displayName}: missing ${tokenEnv}; skipping platform integration`);
|
|
27
38
|
continue;
|
|
@@ -32,13 +43,54 @@ export async function orchestratePlatforms(opts) {
|
|
|
32
43
|
const flags = opts.listFlagsOverride
|
|
33
44
|
? await opts.listFlagsOverride(opts.signal)
|
|
34
45
|
: await loadPlatformFlagsCached(client, cacheKey, { noCache: opts.noCache, signal: opts.signal });
|
|
35
|
-
const signals = crossReference(opts.detectedFlags, flags, def.displayName
|
|
46
|
+
const signals = crossReference(opts.detectedFlags, flags, def.displayName, {
|
|
47
|
+
thresholdDays: opts.thresholdDays,
|
|
48
|
+
});
|
|
36
49
|
mergePlatformSignals(out, signals);
|
|
50
|
+
// Track which flags this platform marked permanent so output
|
|
51
|
+
// formatters can show 'N flag(s) excluded as permanent in
|
|
52
|
+
// <Platform>: a, b, c'. Read the signals MAP we just merged in
|
|
53
|
+
// (not `signals` directly — same content, but consistent source).
|
|
54
|
+
const platformPermanent = [];
|
|
55
|
+
for (const [flagName, sigList] of signals) {
|
|
56
|
+
if (sigList.some((s) => s.type === 'platform-permanent')) {
|
|
57
|
+
platformPermanent.push(flagName);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (platformPermanent.length > 0) {
|
|
61
|
+
permanentByPlatform[def.displayName] = platformPermanent.sort();
|
|
62
|
+
}
|
|
63
|
+
// Surface platform-side metadata (tags, maintainer, activity
|
|
64
|
+
// status) for every detected flag that matched this platform.
|
|
65
|
+
// Output formatters consume this to enrich the per-row display
|
|
66
|
+
// without re-querying the platform.
|
|
67
|
+
for (const flag of flags) {
|
|
68
|
+
if (!opts.detectedFlags.has(flag.key))
|
|
69
|
+
continue;
|
|
70
|
+
const hasMetadata = (flag.tags && flag.tags.length > 0) || flag.maintainer || flag.status;
|
|
71
|
+
if (!hasMetadata)
|
|
72
|
+
continue;
|
|
73
|
+
metadataByFlag.set(flag.key, {
|
|
74
|
+
tags: flag.tags && flag.tags.length > 0 ? flag.tags : undefined,
|
|
75
|
+
maintainer: flag.maintainer,
|
|
76
|
+
status: flag.status,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
37
79
|
}
|
|
38
80
|
catch (err) {
|
|
39
|
-
|
|
81
|
+
// 401/403 are by far the most common failure mode here and the cause
|
|
82
|
+
// is almost always confusable token shapes (SDK key vs. API access
|
|
83
|
+
// token, project-scoped token pointed at the wrong project, project
|
|
84
|
+
// KEY vs. project NAME in the YAML). Surface a hint inline so users
|
|
85
|
+
// don't have to guess.
|
|
86
|
+
const message = err.message;
|
|
87
|
+
const isAuthError = /\b(401|403|Unauthorized|Forbidden)\b/i.test(message);
|
|
88
|
+
const hint = isAuthError
|
|
89
|
+
? ` (check token type — API access tokens, not SDK keys, and the project key matches a project the token can read)`
|
|
90
|
+
: '';
|
|
91
|
+
opts.logger.warn(`${def.displayName}: ${message}${hint}. Continuing with code-only signals.`);
|
|
40
92
|
}
|
|
41
93
|
}
|
|
42
|
-
return out;
|
|
94
|
+
return { signals: out, permanentByPlatform, metadataByFlag };
|
|
43
95
|
}
|
|
44
96
|
//# sourceMappingURL=orchestrate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrate.js","sourceRoot":"","sources":["../../src/providers/orchestrate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"orchestrate.js","sourceRoot":"","sources":["../../src/providers/orchestrate.ts"],"names":[],"mappings":"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"}
|
package/dist/scan-repo.d.ts
CHANGED
|
@@ -19,8 +19,8 @@ export interface ScanRepoOptions {
|
|
|
19
19
|
*/
|
|
20
20
|
cwd: string;
|
|
21
21
|
/**
|
|
22
|
-
* Staleness threshold in
|
|
23
|
-
* git-blame age exceeds this value. Default:
|
|
22
|
+
* Staleness threshold in days. A flag is considered stale if its
|
|
23
|
+
* git-blame age exceeds this value. Default: 30.
|
|
24
24
|
*/
|
|
25
25
|
threshold?: number;
|
|
26
26
|
/**
|
|
@@ -89,6 +89,34 @@ export interface ScanRepoResult {
|
|
|
89
89
|
excludedCount?: number;
|
|
90
90
|
/** Relative paths of excluded files. Only populated when collectExcludedPaths is true. */
|
|
91
91
|
excludedPaths?: string[];
|
|
92
|
+
/**
|
|
93
|
+
* Count of files where the detector raised at least one parse error
|
|
94
|
+
* (i.e. the file was scanned but the tree-sitter/regex pass bailed). Mirrors
|
|
95
|
+
* `RepositoryAnalysisResult.parseErrorCount` and is exposed here so output
|
|
96
|
+
* formatters (text, json, markdown) can tell the user up front when a
|
|
97
|
+
* non-trivial slice of their code couldn't be analysed. Optional for
|
|
98
|
+
* backward compatibility with test fixtures and external callers that
|
|
99
|
+
* construct ScanRepoResult by hand; treat absent as `0`.
|
|
100
|
+
*/
|
|
101
|
+
parseErrorCount?: number;
|
|
102
|
+
/**
|
|
103
|
+
* Flag names that were detected in code AND found in a platform but
|
|
104
|
+
* marked as permanent (LD's `temporary: false`, or equivalent on other
|
|
105
|
+
* platforms). These are filtered out of `staleFlags` because they're
|
|
106
|
+
* intentionally long-lived; surfacing them here lets output formatters
|
|
107
|
+
* show users WHY a flag they expected to see in the table isn't there.
|
|
108
|
+
* Empty array (not undefined) when no platforms are configured or no
|
|
109
|
+
* matches were permanent. Per-platform breakdown lives in the
|
|
110
|
+
* `permanentByPlatform` field below.
|
|
111
|
+
*/
|
|
112
|
+
excludedPermanent?: string[];
|
|
113
|
+
/**
|
|
114
|
+
* Per-platform breakdown of `excludedPermanent`. Keyed by the
|
|
115
|
+
* platform's displayName (e.g. 'LaunchDarkly'). Lets the output
|
|
116
|
+
* formatter print 'X flags excluded as permanent in LaunchDarkly'
|
|
117
|
+
* (not just 'in some platform somewhere').
|
|
118
|
+
*/
|
|
119
|
+
permanentByPlatform?: Record<string, string[]>;
|
|
92
120
|
/** Diagnostic — populated only when logger.debug level is active or callers explicitly opt in. */
|
|
93
121
|
effectiveExcludes?: EffectiveRules;
|
|
94
122
|
}
|
package/dist/scan-repo.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan-repo.d.ts","sourceRoot":"","sources":["../src/scan-repo.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"scan-repo.d.ts","sourceRoot":"","sources":["../src/scan-repo.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgBH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAE1D,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;IACnC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;IAClC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;IAClC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAA;IAEX;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IAEb;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAA;IAEpB;;OAEG;IACH,MAAM,CAAC,EAAE,UAAU,CAAA;IAEnB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,OAAO,GAAG,aAAa,CAAA;IAEhC;;;OAGG;IACH,MAAM,CAAC,EAAE,eAAe,CAAA;IAExB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAElB;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IAEtB;;OAEG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAE9B,qDAAqD;IACrD,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAA;IAElB,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAA;IAEpB;;;;OAIG;IACH,UAAU,EAAE,SAAS,EAAE,CAAA;IAEvB;;;OAGG;IACH,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAE3B,0EAA0E;IAC1E,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEzC;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAA;IAEnB,uDAAuD;IACvD,YAAY,EAAE,MAAM,CAAA;IAEpB,uFAAuF;IACvF,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB,0FAA0F;IAC1F,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IAExB;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB;;;;;;;;;OASG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAE5B;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IAE9C,kGAAkG;IAClG,iBAAiB,CAAC,EAAE,cAAc,CAAA;CACnC;AAUD,wBAAsB,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAkK7E"}
|
package/dist/scan-repo.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { collectFiles } from './scanner.js';
|
|
6
6
|
import { createDefaultRegistry, createRegistryWithEngine } from './detection/index.js';
|
|
7
|
+
import { buildImportGraph, isScannedSourceFile, loadTsconfigAliases } from './detection/import-graph.js';
|
|
8
|
+
import { Languages, getImportPattern } from './detection/interface.js';
|
|
7
9
|
import { PolyglotAnalyzer } from './detection/polyglot-analyzer.js';
|
|
8
10
|
import { analyzeStaleness } from './staleness.js';
|
|
9
11
|
import { buildDefaultConfig } from './config/defaults.js';
|
|
@@ -47,15 +49,55 @@ export async function scanRepo(opts) {
|
|
|
47
49
|
});
|
|
48
50
|
logger.debug(`Detected ${files.size} candidate files (excluded ${excludedCount})`);
|
|
49
51
|
const filesScanned = files.size;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
// Wrapper-aware detection: build the TS/JS import graph and "lift" the SDK
|
|
53
|
+
// gate for any file that transitively reaches a known SDK through 1-N hops
|
|
54
|
+
// of relative imports. We do this by appending a single-line comment to
|
|
55
|
+
// the file content that mentions every reachable SDK pattern; the per-
|
|
56
|
+
// provider import gate (a substring check in helpers.ts / engine.ts) then
|
|
57
|
+
// passes for wrapper-consumer files. Appending to the END of content keeps
|
|
58
|
+
// every flag's line number correct.
|
|
59
|
+
//
|
|
60
|
+
// Direct importers already pass the gate, so the appended marker is a
|
|
61
|
+
// no-op for them. Files with no SDK reach are untouched.
|
|
62
|
+
//
|
|
63
|
+
// Why scan-repo.ts and not PolyglotAnalyzer: PolyglotAnalyzer is a generic
|
|
64
|
+
// multi-language coordinator. The graph is TS/JS-only and SDK-aware; that
|
|
65
|
+
// knowledge lives at the orchestrator layer where we also know the registry.
|
|
66
|
+
const tsJsSdkPatterns = collectSdkPatterns(registry);
|
|
67
|
+
const filesForAnalysis = augmentForWrapperDetection(files, tsJsSdkPatterns, logger, opts.cwd);
|
|
68
|
+
const analysisResult = await analyzer.analyzeFiles(filesForAnalysis, opts.signal);
|
|
69
|
+
// B3: user-configured custom detectors (struct-field-access only today).
|
|
70
|
+
// Layered on top of the standard detection so it never reduces recall,
|
|
71
|
+
// only adds matches — typically for codebases whose flag system bypasses
|
|
72
|
+
// the SDK model entirely (e.g. Mattermost's typed Config().FeatureFlags.X).
|
|
73
|
+
// Detected flags are tagged confidence: 'low' so cleanup pipelines route
|
|
74
|
+
// them to manual review rather than auto-merge.
|
|
75
|
+
if (config.custom_detectors && config.custom_detectors.length > 0) {
|
|
76
|
+
applyCustomDetectors(files, config.custom_detectors, analysisResult.totalFlags, logger);
|
|
77
|
+
}
|
|
78
|
+
const { signals: platformSignals, permanentByPlatform, metadataByFlag, } = await orchestratePlatforms({
|
|
52
79
|
platformsConfig: config.platforms,
|
|
53
80
|
detectedFlags: analysisResult.totalFlags,
|
|
54
81
|
logger,
|
|
55
82
|
noCache: opts.noCache,
|
|
56
83
|
signal: opts.signal,
|
|
84
|
+
// Threshold drives the platform-too-old signal in cross-reference.
|
|
85
|
+
// Same threshold the staleness engine uses for code-age, so the two
|
|
86
|
+
// dimensions stay aligned ("if code older than N is stale, so is a
|
|
87
|
+
// platform record older than N").
|
|
88
|
+
thresholdDays: threshold,
|
|
89
|
+
});
|
|
90
|
+
const staleFlags = await analyzeStaleness(analysisResult.totalFlags, {
|
|
91
|
+
thresholdDays: threshold,
|
|
92
|
+
repoRoot: opts.cwd,
|
|
93
|
+
platformSignals,
|
|
94
|
+
platformMetadata: metadataByFlag,
|
|
57
95
|
});
|
|
58
|
-
|
|
96
|
+
// Flatten the per-platform breakdown into a single sorted, deduplicated
|
|
97
|
+
// list so callers that only want "did anything get excluded?" don't
|
|
98
|
+
// have to walk the platform record themselves. The per-platform record
|
|
99
|
+
// is retained for output formatters that want to attribute correctly.
|
|
100
|
+
const excludedPermanent = Array.from(new Set(Object.values(permanentByPlatform).flat())).sort();
|
|
59
101
|
const totalFlags = analysisResult.totalFlags.size;
|
|
60
102
|
const uniqueStaleNames = new Set(staleFlags.map((f) => f.name)).size;
|
|
61
103
|
const healthScore = totalFlags === 0 ? 100 : Math.round(((totalFlags - uniqueStaleNames) / totalFlags) * 100);
|
|
@@ -68,6 +110,30 @@ export async function scanRepo(opts) {
|
|
|
68
110
|
.map((f) => f.provider)
|
|
69
111
|
.filter((p) => p != null && p !== '')),
|
|
70
112
|
];
|
|
113
|
+
const scanDuration = Math.round(performance.now() - start);
|
|
114
|
+
// Structured metric line — one per scan, info-level. Same shape as the
|
|
115
|
+
// SaaS-side piranha_metric events so a single dashboard can join them.
|
|
116
|
+
// CloudWatch Logs Insights query: `filter event="flagshark_scan_complete"`.
|
|
117
|
+
// Off by default at debug-only loggers (e.g. the no-op logger used in
|
|
118
|
+
// tests); info-level loggers (CLI, Action) surface them.
|
|
119
|
+
logger.info('flagshark_scan_complete', {
|
|
120
|
+
event: 'flagshark_scan_complete',
|
|
121
|
+
durationMs: scanDuration,
|
|
122
|
+
filesScanned,
|
|
123
|
+
// Defensive ?? 0 fallbacks: collectFiles always returns excludedCount as
|
|
124
|
+
// a number and PolyglotAnalyzer always populates parseErrorCount, so the
|
|
125
|
+
// RHS of these expressions is unreachable today — kept for type safety
|
|
126
|
+
// against future refactors.
|
|
127
|
+
/* v8 ignore next 2 */
|
|
128
|
+
excludedCount: excludedCount ?? 0,
|
|
129
|
+
parseErrorCount: analysisResult.parseErrorCount ?? 0,
|
|
130
|
+
totalFlags,
|
|
131
|
+
staleFlags: uniqueStaleNames,
|
|
132
|
+
healthScore,
|
|
133
|
+
detectedProviders: detectedProviders.length,
|
|
134
|
+
languages: Object.keys(analysisResult.languages).length,
|
|
135
|
+
detectionEngine: opts.engine ?? 'regex',
|
|
136
|
+
});
|
|
71
137
|
return {
|
|
72
138
|
totalFlags,
|
|
73
139
|
filesScanned,
|
|
@@ -76,10 +142,230 @@ export async function scanRepo(opts) {
|
|
|
76
142
|
// analysisResult.languages is Map<Language, number> — convert to plain object
|
|
77
143
|
languageBreakdown: Object.fromEntries(analysisResult.languages),
|
|
78
144
|
healthScore,
|
|
79
|
-
scanDuration
|
|
145
|
+
scanDuration,
|
|
80
146
|
excludedCount,
|
|
81
147
|
excludedPaths,
|
|
148
|
+
parseErrorCount: analysisResult.parseErrorCount,
|
|
149
|
+
excludedPermanent,
|
|
150
|
+
permanentByPlatform,
|
|
82
151
|
effectiveExcludes: excluder.effectiveRules,
|
|
83
152
|
};
|
|
84
153
|
}
|
|
154
|
+
// -- Wrapper-aware detection helpers ------------------------------------------
|
|
155
|
+
/**
|
|
156
|
+
* Pulls every `importPattern` string registered on the language detectors
|
|
157
|
+
* the import graph supports today (TS/JS + Python). These are the seed
|
|
158
|
+
* packages for the graph — a file is "in SDK scope" iff its imports reach
|
|
159
|
+
* one of these via 1-N hops of relative imports.
|
|
160
|
+
*
|
|
161
|
+
* We pull TS/JS and Python together because the seed list is matched
|
|
162
|
+
* substring-style on each spec and both languages share the same matching
|
|
163
|
+
* surface inside the graph. Python-only seeds (`posthog`, `ldclient`) and
|
|
164
|
+
* TS-only seeds (`@launchdarkly/node-server-sdk`) never collide because
|
|
165
|
+
* their syntactic shapes are different.
|
|
166
|
+
*/
|
|
167
|
+
function collectSdkPatterns(registry) {
|
|
168
|
+
const patterns = new Set();
|
|
169
|
+
for (const lang of [Languages.TypeScript, Languages.JavaScript, Languages.Python]) {
|
|
170
|
+
const detector = registry.getDetector(lang);
|
|
171
|
+
// Defensive skip: the default registry always populates TS/JS/Python
|
|
172
|
+
// detectors. Custom engines could theoretically omit one, hence the guard.
|
|
173
|
+
/* v8 ignore next */
|
|
174
|
+
if (!detector)
|
|
175
|
+
continue;
|
|
176
|
+
for (const provider of detector.getProviders()) {
|
|
177
|
+
const pat = getImportPattern(provider);
|
|
178
|
+
if (pat)
|
|
179
|
+
patterns.add(pat);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return [...patterns];
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Marker comment appended to TS/JS files that transitively reach an SDK. The
|
|
186
|
+
* existing per-provider import gate is a `content.includes(importPat)` check;
|
|
187
|
+
* mentioning the SDK pattern in this comment satisfies the gate without
|
|
188
|
+
* touching the detector interface or shifting any line numbers (the marker
|
|
189
|
+
* goes at end-of-file, after the last line of real code).
|
|
190
|
+
*
|
|
191
|
+
* The marker text is recognisable so an operator inspecting an instrumented
|
|
192
|
+
* file or a debug log can tell the difference between a real import and our
|
|
193
|
+
* post-hoc marker.
|
|
194
|
+
*/
|
|
195
|
+
const WRAPPER_MARKER_PREFIX = '// flagshark-internal: transitively reaches';
|
|
196
|
+
/**
|
|
197
|
+
* Same marker semantics but in Python comment syntax (`#`). When wrapper
|
|
198
|
+
* augmentation extended to .py files (B4), appending a `//` line would
|
|
199
|
+
* make the file unparseable — Python doesn't have `//` comments. The
|
|
200
|
+
* detector's import-gate is a substring `content.includes(importPat)`
|
|
201
|
+
* check that doesn't care WHICH comment syntax wraps the SDK pattern,
|
|
202
|
+
* so language-appropriate markers work transparently.
|
|
203
|
+
*/
|
|
204
|
+
const WRAPPER_MARKER_PREFIX_PYTHON = '# flagshark-internal: transitively reaches';
|
|
205
|
+
/**
|
|
206
|
+
* Returns a new Map<filePath, content> where TS/JS files with transitive SDK
|
|
207
|
+
* reach have a single comment line appended that mentions each reachable SDK
|
|
208
|
+
* pattern. Returns the original Map unchanged if no SDK seeds exist (e.g.
|
|
209
|
+
* registry was constructed without TS/JS detectors).
|
|
210
|
+
*
|
|
211
|
+
* Non-TS/JS files are passed through untouched.
|
|
212
|
+
*/
|
|
213
|
+
function augmentForWrapperDetection(files, tsJsSdkPatterns, logger, cwd) {
|
|
214
|
+
// Defensive early-return: every default registry contributes at least
|
|
215
|
+
// one SDK pattern, so this branch only fires for hand-built empty
|
|
216
|
+
// registries — not reachable from public scan paths.
|
|
217
|
+
/* v8 ignore next */
|
|
218
|
+
if (tsJsSdkPatterns.length === 0)
|
|
219
|
+
return files;
|
|
220
|
+
// Load tsconfig path aliases from the scan root, then pass them to the
|
|
221
|
+
// graph builder. Most TS monorepos use `@/foo`-style aliases; without
|
|
222
|
+
// this, the transitive wrapper detection stops at every aliased
|
|
223
|
+
// boundary and under-counts. Falls back gracefully — `loadTsconfigAliases`
|
|
224
|
+
// returns null when there's no tsconfig or no aliases declared, in which
|
|
225
|
+
// case the graph behaves exactly as before.
|
|
226
|
+
const aliases = loadTsconfigAliases(cwd);
|
|
227
|
+
if (aliases) {
|
|
228
|
+
logger.debug('tsconfig path aliases loaded', {
|
|
229
|
+
baseUrl: aliases.baseUrl,
|
|
230
|
+
aliasCount: aliases.paths.size,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
const graph = buildImportGraph(files, {
|
|
234
|
+
seedSdkPatterns: tsJsSdkPatterns,
|
|
235
|
+
// Polyglot scope — graph now walks TS/JS *and* Python wrappers so a
|
|
236
|
+
// Python consumer file that does `from .feature_flags import is_enabled`
|
|
237
|
+
// (where feature_flags.py imports `posthog`) is in scope. The option
|
|
238
|
+
// name is legacy from when TS/JS was the only surface; the helper
|
|
239
|
+
// returns true for .py files too. See B4 in the bug inventory.
|
|
240
|
+
isTsJs: isScannedSourceFile,
|
|
241
|
+
aliases: aliases ?? undefined,
|
|
242
|
+
});
|
|
243
|
+
logger.debug('Import graph built', graph.stats);
|
|
244
|
+
// No transitive reach (seeds didn't propagate beyond themselves, or no seeds
|
|
245
|
+
// at all) -> return the original map. Avoids a wasted clone on repos that
|
|
246
|
+
// don't use any flag SDK.
|
|
247
|
+
if (graph.stats.inScopeFiles === 0) {
|
|
248
|
+
return files;
|
|
249
|
+
}
|
|
250
|
+
const augmented = new Map();
|
|
251
|
+
let augmentedCount = 0;
|
|
252
|
+
for (const [filePath, content] of files) {
|
|
253
|
+
const sdks = graph.transitiveSdks.get(filePath);
|
|
254
|
+
if (!sdks || sdks.size === 0) {
|
|
255
|
+
augmented.set(filePath, content);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
// Append at end-of-file with a leading newline so we never glue onto a
|
|
259
|
+
// partial last line. Sorting the SDK list keeps the marker deterministic
|
|
260
|
+
// across runs (useful when diffing logs). Use Python comment syntax for
|
|
261
|
+
// .py files; the substring-based import gate doesn't care which prefix
|
|
262
|
+
// wraps the SDK pattern, so language-appropriate markers stay parseable.
|
|
263
|
+
const sdkList = [...sdks].sort().join(' ');
|
|
264
|
+
const prefix = filePath.toLowerCase().endsWith('.py')
|
|
265
|
+
? WRAPPER_MARKER_PREFIX_PYTHON
|
|
266
|
+
: WRAPPER_MARKER_PREFIX;
|
|
267
|
+
augmented.set(filePath, `${content}\n${prefix} ${sdkList}\n`);
|
|
268
|
+
augmentedCount++;
|
|
269
|
+
}
|
|
270
|
+
logger.debug(`Wrapper-aware detection augmented ${augmentedCount} files`);
|
|
271
|
+
return augmented;
|
|
272
|
+
}
|
|
273
|
+
// -- Custom detector application (B3) -----------------------------------------
|
|
274
|
+
/**
|
|
275
|
+
* File extensions per language, for routing custom detectors to the right
|
|
276
|
+
* files. Kept in sync with the language detector registry; we duplicate
|
|
277
|
+
* the table here rather than pulling from the registry because the
|
|
278
|
+
* registry is awareness-of-detector, not awareness-of-extensions, and
|
|
279
|
+
* the registry's API is method-call-shaped (asks "do you support this
|
|
280
|
+
* file" per detector) rather than data-shaped.
|
|
281
|
+
*/
|
|
282
|
+
const LANGUAGE_EXTENSIONS = {
|
|
283
|
+
go: ['.go'],
|
|
284
|
+
python: ['.py', '.pyx', '.pyi'],
|
|
285
|
+
typescript: ['.ts', '.tsx'],
|
|
286
|
+
javascript: ['.js', '.jsx', '.mjs', '.cjs'],
|
|
287
|
+
java: ['.java'],
|
|
288
|
+
kotlin: ['.kt', '.kts'],
|
|
289
|
+
swift: ['.swift'],
|
|
290
|
+
ruby: ['.rb'],
|
|
291
|
+
csharp: ['.cs'],
|
|
292
|
+
php: ['.php'],
|
|
293
|
+
rust: ['.rs'],
|
|
294
|
+
cpp: ['.c', '.cc', '.cpp', '.h', '.hpp'],
|
|
295
|
+
objc: ['.m', '.mm'],
|
|
296
|
+
};
|
|
297
|
+
/**
|
|
298
|
+
* Runs each configured custom detector over the appropriate file subset
|
|
299
|
+
* and adds matches to `totalFlags`. Mutates the map in place — same shape
|
|
300
|
+
* as the polyglot analyzer's output, so downstream staleness + JSON
|
|
301
|
+
* surfacing pick the new flags up without further wiring.
|
|
302
|
+
*
|
|
303
|
+
* Each match becomes a FeatureFlag with `confidence: 'low'` to signal
|
|
304
|
+
* that the detection came from a user-declared regex rather than a
|
|
305
|
+
* disciplined SDK gate. The legacy `'high'`/`'medium'` flags from the
|
|
306
|
+
* standard detector are left untouched.
|
|
307
|
+
*/
|
|
308
|
+
function applyCustomDetectors(files, detectors, totalFlags, logger) {
|
|
309
|
+
for (const detector of detectors) {
|
|
310
|
+
let regex;
|
|
311
|
+
try {
|
|
312
|
+
regex = new RegExp(detector.access_pattern, 'g');
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
logger.warn(`custom_detector regex failed to compile`, {
|
|
316
|
+
access_pattern: detector.access_pattern,
|
|
317
|
+
/* v8 ignore next */
|
|
318
|
+
error: err instanceof Error ? err.message : String(err),
|
|
319
|
+
});
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const allowedExts = LANGUAGE_EXTENSIONS[detector.language] ?? [];
|
|
323
|
+
if (allowedExts.length === 0) {
|
|
324
|
+
logger.warn(`custom_detector skipped — unknown language`, {
|
|
325
|
+
language: detector.language,
|
|
326
|
+
});
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
const providerName = detector.name ?? 'Custom struct-field detector';
|
|
330
|
+
let matchCount = 0;
|
|
331
|
+
for (const [filePath, content] of files) {
|
|
332
|
+
const lower = filePath.toLowerCase();
|
|
333
|
+
if (!allowedExts.some((ext) => lower.endsWith(ext)))
|
|
334
|
+
continue;
|
|
335
|
+
// Walk every match in the file. Each must capture exactly one
|
|
336
|
+
// group (the flag name) — schemas validated at load time, but we
|
|
337
|
+
// defensively skip captureless matches at runtime too.
|
|
338
|
+
regex.lastIndex = 0;
|
|
339
|
+
let m;
|
|
340
|
+
while ((m = regex.exec(content)) !== null) {
|
|
341
|
+
const flagName = m[1];
|
|
342
|
+
/* v8 ignore next */
|
|
343
|
+
if (!flagName)
|
|
344
|
+
continue;
|
|
345
|
+
// Resolve line number from the match offset. Cheap: count
|
|
346
|
+
// newlines up to the match. Files are typically <10k lines so
|
|
347
|
+
// O(n) per match is fine.
|
|
348
|
+
const lineNumber = content.slice(0, m.index).split('\n').length;
|
|
349
|
+
const existing = totalFlags.get(flagName) ?? [];
|
|
350
|
+
existing.push({
|
|
351
|
+
name: flagName,
|
|
352
|
+
filePath,
|
|
353
|
+
lineNumber,
|
|
354
|
+
language: detector.language,
|
|
355
|
+
provider: providerName,
|
|
356
|
+
confidence: 'low',
|
|
357
|
+
});
|
|
358
|
+
totalFlags.set(flagName, existing);
|
|
359
|
+
matchCount++;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (matchCount > 0) {
|
|
363
|
+
logger.debug(`custom_detector matched`, {
|
|
364
|
+
language: detector.language,
|
|
365
|
+
matchCount,
|
|
366
|
+
provider: providerName,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
85
371
|
//# sourceMappingURL=scan-repo.js.map
|
package/dist/scan-repo.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan-repo.js","sourceRoot":"","sources":["../src/scan-repo.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAA;AACtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"scan-repo.js","sourceRoot":"","sources":["../src/scan-repo.ts"],"names":[],"mappings":"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"}
|
package/dist/staleness.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type FeatureFlag } from './detection/feature-flag.js';
|
|
2
2
|
export interface StalenessSignal {
|
|
3
|
-
type: 'age' | 'hardcoded' | 'low-usage' | 'missing-in-platform' | 'archived-in-platform';
|
|
3
|
+
type: 'age' | 'hardcoded' | 'low-usage' | 'missing-in-platform' | 'archived-in-platform' | 'platform-too-old' | 'platform-inactive' | 'platform-launched';
|
|
4
4
|
severity: 'error' | 'warning';
|
|
5
5
|
description: string;
|
|
6
6
|
}
|
|
@@ -13,14 +13,44 @@ export interface StaleFlag {
|
|
|
13
13
|
signals: StalenessSignal[];
|
|
14
14
|
/** Human-readable age, e.g. "14 months ago" */
|
|
15
15
|
age?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Detection-quality tier inherited from the underlying FeatureFlag.
|
|
18
|
+
* See FlagConfidence in detection/feature-flag.ts for what the values
|
|
19
|
+
* mean. Absent = `'high'`. Surfaced here so consumers of the stale-flag
|
|
20
|
+
* list (the JSON output, the GitHub Action's PR comment) can route
|
|
21
|
+
* medium-confidence flags to manual review rather than auto-cleanup.
|
|
22
|
+
*/
|
|
23
|
+
confidence?: 'high' | 'medium' | 'low';
|
|
24
|
+
/**
|
|
25
|
+
* Platform-side metadata propagated through from the cross-reference
|
|
26
|
+
* layer. Populated only when a platform integration is active AND the
|
|
27
|
+
* platform exposed the data. Output formatters surface these
|
|
28
|
+
* alongside each flag so reviewers see who owns it and how the
|
|
29
|
+
* platform classifies it without a context switch.
|
|
30
|
+
*/
|
|
31
|
+
tags?: string[];
|
|
32
|
+
maintainer?: string;
|
|
33
|
+
/** LD's per-environment activity verdict; see PlatformFlag.status. */
|
|
34
|
+
platformStatus?: 'new' | 'active' | 'inactive' | 'launched';
|
|
16
35
|
}
|
|
17
36
|
export interface StalenessOptions {
|
|
18
|
-
/** Flag lines older than this are considered stale. Default:
|
|
19
|
-
|
|
37
|
+
/** Flag lines older than this are considered stale. Default: 30. */
|
|
38
|
+
thresholdDays: number;
|
|
20
39
|
/** Absolute path to the git repository root. */
|
|
21
40
|
repoRoot: string;
|
|
22
41
|
/** Optional: pre-computed platform signals keyed by flag name. */
|
|
23
42
|
platformSignals?: Map<string, import('./providers/interface.js').PlatformSignal[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Optional: per-flag platform metadata surfaced by the orchestrator.
|
|
45
|
+
* Drives the tags / maintainer / platformStatus fields on each
|
|
46
|
+
* emitted StaleFlag. Keyed by detected flag name; values come
|
|
47
|
+
* straight from the matched PlatformFlag.
|
|
48
|
+
*/
|
|
49
|
+
platformMetadata?: Map<string, {
|
|
50
|
+
tags?: string[];
|
|
51
|
+
maintainer?: string;
|
|
52
|
+
status?: 'new' | 'active' | 'inactive' | 'launched';
|
|
53
|
+
}>;
|
|
24
54
|
}
|
|
25
55
|
/**
|
|
26
56
|
* Analyze a set of detected feature flags for staleness signals.
|