@flagshark/core 1.3.1 → 1.5.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 +24 -0
- package/dist/config/excluder.d.ts.map +1 -1
- package/dist/config/excluder.js +1 -0
- package/dist/config/excluder.js.map +1 -1
- package/dist/config/loader.js +1 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.d.ts +25 -12
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +1 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/detection/detectors/cpp.d.ts.map +1 -1
- package/dist/detection/detectors/cpp.js +81 -4
- package/dist/detection/detectors/cpp.js.map +1 -1
- package/dist/detection/detectors/csharp.d.ts.map +1 -1
- package/dist/detection/detectors/csharp.js +5 -0
- package/dist/detection/detectors/csharp.js.map +1 -1
- package/dist/detection/detectors/javascript.d.ts.map +1 -1
- package/dist/detection/detectors/javascript.js +6 -2
- package/dist/detection/detectors/javascript.js.map +1 -1
- package/dist/detection/detectors/php.d.ts.map +1 -1
- package/dist/detection/detectors/php.js +6 -2
- package/dist/detection/detectors/php.js.map +1 -1
- package/dist/detection/detectors/python.d.ts.map +1 -1
- package/dist/detection/detectors/python.js +6 -2
- package/dist/detection/detectors/python.js.map +1 -1
- package/dist/detection/detectors/ruby.d.ts.map +1 -1
- package/dist/detection/detectors/ruby.js +7 -4
- package/dist/detection/detectors/ruby.js.map +1 -1
- package/dist/detection/detectors/typescript.d.ts.map +1 -1
- package/dist/detection/detectors/typescript.js +6 -2
- package/dist/detection/detectors/typescript.js.map +1 -1
- package/dist/detection/feature-flag.d.ts.map +1 -1
- package/dist/detection/helpers.d.ts.map +1 -1
- package/dist/detection/helpers.js +20 -3
- package/dist/detection/helpers.js.map +1 -1
- package/dist/detection/polyglot-analyzer.d.ts.map +1 -1
- package/dist/detection/polyglot-analyzer.js +50 -0
- package/dist/detection/polyglot-analyzer.js.map +1 -1
- package/dist/detection/tree-sitter/const-resolver.d.ts +11 -0
- package/dist/detection/tree-sitter/const-resolver.d.ts.map +1 -1
- package/dist/detection/tree-sitter/const-resolver.js +59 -2
- package/dist/detection/tree-sitter/const-resolver.js.map +1 -1
- package/dist/detection/tree-sitter/engine.d.ts.map +1 -1
- package/dist/detection/tree-sitter/engine.js +15 -6
- package/dist/detection/tree-sitter/engine.js.map +1 -1
- package/dist/detection/tree-sitter/module-url.d.ts +25 -0
- package/dist/detection/tree-sitter/module-url.d.ts.map +1 -0
- package/dist/detection/tree-sitter/module-url.js +38 -0
- package/dist/detection/tree-sitter/module-url.js.map +1 -0
- package/dist/detection/tree-sitter/parser-cache.d.ts +4 -0
- package/dist/detection/tree-sitter/parser-cache.d.ts.map +1 -1
- package/dist/detection/tree-sitter/parser-cache.js +30 -6
- package/dist/detection/tree-sitter/parser-cache.js.map +1 -1
- package/dist/detection/tree-sitter/queries-inline.d.ts +13 -0
- package/dist/detection/tree-sitter/queries-inline.d.ts.map +1 -0
- package/dist/detection/tree-sitter/queries-inline.js +17 -0
- package/dist/detection/tree-sitter/queries-inline.js.map +1 -0
- package/dist/detection/tree-sitter/query-runner.d.ts +14 -1
- package/dist/detection/tree-sitter/query-runner.d.ts.map +1 -1
- package/dist/detection/tree-sitter/query-runner.js +28 -11
- package/dist/detection/tree-sitter/query-runner.js.map +1 -1
- package/dist/detection/yaml-config.js +2 -2
- package/dist/detection/yaml-config.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/output/csv.d.ts +9 -0
- package/dist/output/csv.d.ts.map +1 -0
- package/dist/output/csv.js +31 -0
- package/dist/output/csv.js.map +1 -0
- package/dist/output/index.d.ts +8 -0
- package/dist/output/index.d.ts.map +1 -0
- package/dist/output/index.js +8 -0
- package/dist/output/index.js.map +1 -0
- package/dist/output/json.d.ts +13 -0
- package/dist/output/json.d.ts.map +1 -0
- package/dist/output/json.js +43 -0
- package/dist/output/json.js.map +1 -0
- package/dist/output/markdown.d.ts +23 -0
- package/dist/output/markdown.d.ts.map +1 -0
- package/dist/output/markdown.js +94 -0
- package/dist/output/markdown.js.map +1 -0
- package/dist/output/sarif.d.ts +16 -0
- package/dist/output/sarif.d.ts.map +1 -0
- package/dist/output/sarif.js +104 -0
- package/dist/output/sarif.js.map +1 -0
- package/dist/output/select.d.ts +23 -0
- package/dist/output/select.d.ts.map +1 -0
- package/dist/output/select.js +36 -0
- package/dist/output/select.js.map +1 -0
- package/dist/output/shared.d.ts +13 -0
- package/dist/output/shared.d.ts.map +1 -0
- package/dist/output/shared.js +28 -0
- package/dist/output/shared.js.map +1 -0
- package/dist/output/text.d.ts +11 -0
- package/dist/output/text.d.ts.map +1 -0
- package/dist/output/text.js +104 -0
- package/dist/output/text.js.map +1 -0
- package/dist/providers/cache.d.ts +28 -0
- package/dist/providers/cache.d.ts.map +1 -0
- package/dist/providers/cache.js +84 -0
- package/dist/providers/cache.js.map +1 -0
- package/dist/providers/cross-reference.d.ts +19 -0
- package/dist/providers/cross-reference.d.ts.map +1 -0
- package/dist/providers/cross-reference.js +48 -0
- package/dist/providers/cross-reference.js.map +1 -0
- package/dist/providers/index.d.ts +5 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +4 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/interface.d.ts +37 -0
- package/dist/providers/interface.d.ts.map +1 -0
- package/dist/providers/interface.js +2 -0
- package/dist/providers/interface.js.map +1 -0
- package/dist/providers/launchdarkly/client.d.ts +13 -0
- package/dist/providers/launchdarkly/client.d.ts.map +1 -0
- package/dist/providers/launchdarkly/client.js +44 -0
- package/dist/providers/launchdarkly/client.js.map +1 -0
- package/dist/providers/launchdarkly/definition.d.ts +22 -0
- package/dist/providers/launchdarkly/definition.d.ts.map +1 -0
- package/dist/providers/launchdarkly/definition.js +24 -0
- package/dist/providers/launchdarkly/definition.js.map +1 -0
- package/dist/providers/launchdarkly/errors.d.ts +5 -0
- package/dist/providers/launchdarkly/errors.d.ts.map +1 -0
- package/dist/providers/launchdarkly/errors.js +9 -0
- package/dist/providers/launchdarkly/errors.js.map +1 -0
- package/dist/providers/launchdarkly/types.d.ts +154 -0
- package/dist/providers/launchdarkly/types.d.ts.map +1 -0
- package/dist/providers/launchdarkly/types.js +17 -0
- package/dist/providers/launchdarkly/types.js.map +1 -0
- package/dist/providers/orchestrate.d.ts +24 -0
- package/dist/providers/orchestrate.d.ts.map +1 -0
- package/dist/providers/orchestrate.js +44 -0
- package/dist/providers/orchestrate.js.map +1 -0
- package/dist/providers/registry.d.ts +8 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +15 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/scan-repo.d.ts +2 -0
- package/dist/scan-repo.d.ts.map +1 -1
- package/dist/scan-repo.js +15 -6
- package/dist/scan-repo.js.map +1 -1
- package/dist/staleness.d.ts +4 -1
- package/dist/staleness.d.ts.map +1 -1
- package/dist/staleness.js +15 -11
- package/dist/staleness.js.map +1 -1
- package/package.json +6 -2
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SARIF v2.1.0 output for FlagShark scan results.
|
|
3
|
+
*
|
|
4
|
+
* Consumable directly by `github/codeql-action/upload-sarif` so stale flags
|
|
5
|
+
* appear in the repo's Security → Code Scanning tab — same UX as CodeQL,
|
|
6
|
+
* ESLint with SARIF output, etc.
|
|
7
|
+
*
|
|
8
|
+
* Spec: https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html
|
|
9
|
+
*/
|
|
10
|
+
const RULE_DEFS = {
|
|
11
|
+
'stale-age': {
|
|
12
|
+
id: 'stale-age',
|
|
13
|
+
name: 'Stale by age',
|
|
14
|
+
shortDescription: { text: 'Flag reference older than the configured threshold' },
|
|
15
|
+
helpUri: 'https://github.com/FlagShark/flagshark#how-staleness-works',
|
|
16
|
+
},
|
|
17
|
+
'stale-low-usage': {
|
|
18
|
+
id: 'stale-low-usage',
|
|
19
|
+
name: 'Stale by usage',
|
|
20
|
+
shortDescription: { text: 'Flag appears in only one file across the repo' },
|
|
21
|
+
helpUri: 'https://github.com/FlagShark/flagshark#how-staleness-works',
|
|
22
|
+
},
|
|
23
|
+
'stale-hardcoded': {
|
|
24
|
+
id: 'stale-hardcoded',
|
|
25
|
+
name: 'Stale by hardcoded variation',
|
|
26
|
+
shortDescription: { text: 'Flag call uses a constant default — the flag may be permanently removed upstream' },
|
|
27
|
+
helpUri: 'https://github.com/FlagShark/flagshark#how-staleness-works',
|
|
28
|
+
},
|
|
29
|
+
'flagshark/missing-in-platform': {
|
|
30
|
+
id: 'flagshark/missing-in-platform',
|
|
31
|
+
name: 'Missing in platform',
|
|
32
|
+
shortDescription: { text: 'Flag is referenced in code but does not exist in the feature flag platform' },
|
|
33
|
+
helpUri: 'https://github.com/FlagShark/flagshark#how-staleness-works',
|
|
34
|
+
},
|
|
35
|
+
'flagshark/archived-in-platform': {
|
|
36
|
+
id: 'flagshark/archived-in-platform',
|
|
37
|
+
name: 'Archived in platform',
|
|
38
|
+
shortDescription: { text: 'Flag is referenced in code but has been archived in the feature flag platform' },
|
|
39
|
+
helpUri: 'https://github.com/FlagShark/flagshark#how-staleness-works',
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
function signalTypeToRuleId(signalType) {
|
|
43
|
+
if (signalType === 'age')
|
|
44
|
+
return 'stale-age';
|
|
45
|
+
if (signalType === 'low-usage')
|
|
46
|
+
return 'stale-low-usage';
|
|
47
|
+
if (signalType === 'missing-in-platform')
|
|
48
|
+
return 'flagshark/missing-in-platform';
|
|
49
|
+
if (signalType === 'archived-in-platform')
|
|
50
|
+
return 'flagshark/archived-in-platform';
|
|
51
|
+
return 'stale-hardcoded';
|
|
52
|
+
}
|
|
53
|
+
export function formatSarif(result, options) {
|
|
54
|
+
const results = result.staleFlags.map((flag) => toSarifResult(flag));
|
|
55
|
+
// Collect only the rule IDs that appear in results
|
|
56
|
+
const usedRuleIds = new Set(results.map((r) => r.ruleId));
|
|
57
|
+
const rules = [...usedRuleIds]
|
|
58
|
+
.filter((id) => id in RULE_DEFS)
|
|
59
|
+
.map((id) => RULE_DEFS[id]);
|
|
60
|
+
const envelope = {
|
|
61
|
+
$schema: 'https://json.schemastore.org/sarif-2.1.0.json',
|
|
62
|
+
version: '2.1.0',
|
|
63
|
+
runs: [
|
|
64
|
+
{
|
|
65
|
+
tool: {
|
|
66
|
+
driver: {
|
|
67
|
+
name: 'FlagShark',
|
|
68
|
+
version: options.version,
|
|
69
|
+
informationUri: 'https://github.com/FlagShark/flagshark',
|
|
70
|
+
rules,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
results,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
return JSON.stringify(envelope, null, 2);
|
|
78
|
+
}
|
|
79
|
+
function toSarifResult(flag) {
|
|
80
|
+
const firstSignal = flag.signals[0];
|
|
81
|
+
const ruleId = signalTypeToRuleId(firstSignal ? firstSignal.type : '');
|
|
82
|
+
// Level is based on max severity across all signals
|
|
83
|
+
const level = flag.signals.some((s) => s.severity === 'error') ? 'error' : 'warning';
|
|
84
|
+
return {
|
|
85
|
+
ruleId,
|
|
86
|
+
level,
|
|
87
|
+
message: {
|
|
88
|
+
text: `Flag "${flag.name}" appears stale. ${flag.signals.map((s) => s.description).join('; ')}`,
|
|
89
|
+
},
|
|
90
|
+
locations: [{
|
|
91
|
+
physicalLocation: {
|
|
92
|
+
artifactLocation: { uri: flag.filePath.replace(/^\.\//, '') },
|
|
93
|
+
region: { startLine: flag.lineNumber },
|
|
94
|
+
},
|
|
95
|
+
}],
|
|
96
|
+
properties: {
|
|
97
|
+
flag: flag.name,
|
|
98
|
+
provider: flag.provider,
|
|
99
|
+
language: flag.language,
|
|
100
|
+
age: flag.age ?? '',
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=sarif.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sarif.js","sourceRoot":"","sources":["../../src/output/sarif.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAuBH,MAAM,SAAS,GAAsG;IACnH,WAAW,EAAE;QACX,EAAE,EAAE,WAAW;QACf,IAAI,EAAE,cAAc;QACpB,gBAAgB,EAAE,EAAE,IAAI,EAAE,oDAAoD,EAAE;QAChF,OAAO,EAAE,4DAA4D;KACtE;IACD,iBAAiB,EAAE;QACjB,EAAE,EAAE,iBAAiB;QACrB,IAAI,EAAE,gBAAgB;QACtB,gBAAgB,EAAE,EAAE,IAAI,EAAE,+CAA+C,EAAE;QAC3E,OAAO,EAAE,4DAA4D;KACtE;IACD,iBAAiB,EAAE;QACjB,EAAE,EAAE,iBAAiB;QACrB,IAAI,EAAE,8BAA8B;QACpC,gBAAgB,EAAE,EAAE,IAAI,EAAE,kFAAkF,EAAE;QAC9G,OAAO,EAAE,4DAA4D;KACtE;IACD,+BAA+B,EAAE;QAC/B,EAAE,EAAE,+BAA+B;QACnC,IAAI,EAAE,qBAAqB;QAC3B,gBAAgB,EAAE,EAAE,IAAI,EAAE,4EAA4E,EAAE;QACxG,OAAO,EAAE,4DAA4D;KACtE;IACD,gCAAgC,EAAE;QAChC,EAAE,EAAE,gCAAgC;QACpC,IAAI,EAAE,sBAAsB;QAC5B,gBAAgB,EAAE,EAAE,IAAI,EAAE,+EAA+E,EAAE;QAC3G,OAAO,EAAE,4DAA4D;KACtE;CACF,CAAA;AAED,SAAS,kBAAkB,CAAC,UAAkB;IAC5C,IAAI,UAAU,KAAK,KAAK;QAAE,OAAO,WAAW,CAAA;IAC5C,IAAI,UAAU,KAAK,WAAW;QAAE,OAAO,iBAAiB,CAAA;IACxD,IAAI,UAAU,KAAK,qBAAqB;QAAE,OAAO,+BAA+B,CAAA;IAChF,IAAI,UAAU,KAAK,sBAAsB;QAAE,OAAO,gCAAgC,CAAA;IAClF,OAAO,iBAAiB,CAAA;AAC1B,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAsB,EAAE,OAA2B;IAC7E,MAAM,OAAO,GAAkB,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAA;IAEnF,mDAAmD;IACnD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;IACzD,MAAM,KAAK,GAAG,CAAC,GAAG,WAAW,CAAC;SAC3B,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,SAAS,CAAC;SAC/B,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAA;IAE7B,MAAM,QAAQ,GAAG;QACf,OAAO,EAAE,+CAA+C;QACxD,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE;YACJ;gBACE,IAAI,EAAE;oBACJ,MAAM,EAAE;wBACN,IAAI,EAAE,WAAW;wBACjB,OAAO,EAAE,OAAO,CAAC,OAAO;wBACxB,cAAc,EAAE,wCAAwC;wBACxD,KAAK;qBACN;iBACF;gBACD,OAAO;aACR;SACF;KACF,CAAA;IAED,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;AAC1C,CAAC;AAED,SAAS,aAAa,CAAC,IAAe;IACpC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IACnC,MAAM,MAAM,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAEtE,oDAAoD;IACpD,MAAM,KAAK,GAAwB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAA;IAEzG,OAAO;QACL,MAAM;QACN,KAAK;QACL,OAAO,EAAE;YACP,IAAI,EAAE,SAAS,IAAI,CAAC,IAAI,oBAAoB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;SAChG;QACD,SAAS,EAAE,CAAC;gBACV,gBAAgB,EAAE;oBAChB,gBAAgB,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE;oBAC7D,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE;iBACvC;aACF,CAAC;QACF,UAAU,EAAE;YACV,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,EAAE;SACpB;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format-name dispatcher. Returns a unified callable that all formatters
|
|
3
|
+
* conform to, so CLI/Action callers don't switch on format name themselves.
|
|
4
|
+
*/
|
|
5
|
+
import type { ScanRepoResult } from '../scan-repo.js';
|
|
6
|
+
export type FormatName = 'text' | 'json' | 'markdown' | 'csv' | 'sarif';
|
|
7
|
+
export interface UnifiedFormatOptions {
|
|
8
|
+
/** Tool version (used by JSON + SARIF envelopes). */
|
|
9
|
+
version: string;
|
|
10
|
+
/** Scan mode label (used by markdown). */
|
|
11
|
+
scanMode: 'full' | 'changed';
|
|
12
|
+
/** Verbose flag (used by text). Default false. */
|
|
13
|
+
verbose?: boolean;
|
|
14
|
+
/** Max stale flags rendered in text/markdown. Default: 10 (text), 20 (markdown). */
|
|
15
|
+
maxDisplay?: number;
|
|
16
|
+
/** Link prefix for markdown (Action-context absolute URLs). */
|
|
17
|
+
linkPrefix?: string;
|
|
18
|
+
/** Comment marker (Action only). */
|
|
19
|
+
commentMarker?: string;
|
|
20
|
+
}
|
|
21
|
+
export type Formatter = (result: ScanRepoResult, options: UnifiedFormatOptions) => string;
|
|
22
|
+
export declare function selectFormatter(name: FormatName): Formatter;
|
|
23
|
+
//# sourceMappingURL=select.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../src/output/select.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAQrD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,KAAK,GAAG,OAAO,CAAA;AAEvE,MAAM,WAAW,oBAAoB;IACnC,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAA;IACf,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,kDAAkD;IAClD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,oCAAoC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,oBAAoB,KAAK,MAAM,CAAA;AAKzF,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,CAuB3D"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format-name dispatcher. Returns a unified callable that all formatters
|
|
3
|
+
* conform to, so CLI/Action callers don't switch on format name themselves.
|
|
4
|
+
*/
|
|
5
|
+
import { formatCsv } from './csv.js';
|
|
6
|
+
import { formatJson } from './json.js';
|
|
7
|
+
import { formatMarkdown } from './markdown.js';
|
|
8
|
+
import { formatSarif } from './sarif.js';
|
|
9
|
+
import { formatText } from './text.js';
|
|
10
|
+
const TEXT_DEFAULT_MAX = 10;
|
|
11
|
+
const MARKDOWN_DEFAULT_MAX = 20;
|
|
12
|
+
export function selectFormatter(name) {
|
|
13
|
+
switch (name) {
|
|
14
|
+
case 'text':
|
|
15
|
+
return (result, opts) => formatText(result, {
|
|
16
|
+
verbose: opts.verbose ?? false,
|
|
17
|
+
maxDisplay: opts.maxDisplay ?? TEXT_DEFAULT_MAX,
|
|
18
|
+
});
|
|
19
|
+
case 'json':
|
|
20
|
+
return (result, opts) => formatJson(result, { version: opts.version });
|
|
21
|
+
case 'markdown':
|
|
22
|
+
return (result, opts) => formatMarkdown(result, {
|
|
23
|
+
scanMode: opts.scanMode,
|
|
24
|
+
linkPrefix: opts.linkPrefix,
|
|
25
|
+
commentMarker: opts.commentMarker,
|
|
26
|
+
maxStaleFlags: opts.maxDisplay ?? MARKDOWN_DEFAULT_MAX,
|
|
27
|
+
});
|
|
28
|
+
case 'csv':
|
|
29
|
+
return (result) => formatCsv(result);
|
|
30
|
+
case 'sarif':
|
|
31
|
+
return (result, opts) => formatSarif(result, { version: opts.version });
|
|
32
|
+
default:
|
|
33
|
+
throw new Error(`Unknown format: ${name}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=select.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select.js","sourceRoot":"","sources":["../../src/output/select.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAqBtC,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAC3B,MAAM,oBAAoB,GAAG,EAAE,CAAA;AAE/B,MAAM,UAAU,eAAe,CAAC,IAAgB;IAC9C,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,MAAM;YACT,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE;gBAC1C,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,KAAK;gBAC9B,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,gBAAgB;aAChD,CAAC,CAAA;QACJ,KAAK,MAAM;YACT,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAA;QACxE,KAAK,UAAU;YACb,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,MAAM,EAAE;gBAC9C,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,aAAa,EAAE,IAAI,CAAC,aAAa;gBACjC,aAAa,EAAE,IAAI,CAAC,UAAU,IAAI,oBAAoB;aACvD,CAAC,CAAA;QACJ,KAAK,KAAK;YACR,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;QACtC,KAAK,OAAO;YACV,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAA;QACzE;YACE,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAc,EAAE,CAAC,CAAA;IACxD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { StaleFlag } from '../staleness.js';
|
|
2
|
+
/** Returns the count of unique stale flag names (de-duped across occurrences). */
|
|
3
|
+
export declare function uniqueStaleCount(stale: StaleFlag[]): number;
|
|
4
|
+
/** Map health score to an emoji used in markdown + SARIF + Action summary. */
|
|
5
|
+
export declare function healthEmoji(score: number): string;
|
|
6
|
+
/**
|
|
7
|
+
* SARIF severity level mapping based on number of staleness signals on a flag.
|
|
8
|
+
* 1 signal → 'note'
|
|
9
|
+
* 2 signals → 'warning'
|
|
10
|
+
* 3+ signals → 'error'
|
|
11
|
+
*/
|
|
12
|
+
export declare function sarifLevel(signalCount: number): 'note' | 'warning' | 'error';
|
|
13
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/output/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAEhD,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,CAE3D;AAED,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAI5E"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Returns the count of unique stale flag names (de-duped across occurrences). */
|
|
2
|
+
export function uniqueStaleCount(stale) {
|
|
3
|
+
return new Set(stale.map((f) => f.name)).size;
|
|
4
|
+
}
|
|
5
|
+
/** Map health score to an emoji used in markdown + SARIF + Action summary. */
|
|
6
|
+
export function healthEmoji(score) {
|
|
7
|
+
if (score >= 90)
|
|
8
|
+
return '🟢';
|
|
9
|
+
if (score >= 70)
|
|
10
|
+
return '🟡';
|
|
11
|
+
if (score >= 40)
|
|
12
|
+
return '🟠';
|
|
13
|
+
return '🔴';
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* SARIF severity level mapping based on number of staleness signals on a flag.
|
|
17
|
+
* 1 signal → 'note'
|
|
18
|
+
* 2 signals → 'warning'
|
|
19
|
+
* 3+ signals → 'error'
|
|
20
|
+
*/
|
|
21
|
+
export function sarifLevel(signalCount) {
|
|
22
|
+
if (signalCount >= 3)
|
|
23
|
+
return 'error';
|
|
24
|
+
if (signalCount === 2)
|
|
25
|
+
return 'warning';
|
|
26
|
+
return 'note';
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/output/shared.ts"],"names":[],"mappings":"AAEA,kFAAkF;AAClF,MAAM,UAAU,gBAAgB,CAAC,KAAkB;IACjD,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;AAC/C,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,IAAI,CAAA;IAC5B,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,IAAI,CAAA;IAC5B,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,IAAI,CAAA;IAC5B,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,WAAmB;IAC5C,IAAI,WAAW,IAAI,CAAC;QAAE,OAAO,OAAO,CAAA;IACpC,IAAI,WAAW,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IACvC,OAAO,MAAM,CAAA;AACf,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-readable text output for FlagShark scan results.
|
|
3
|
+
*/
|
|
4
|
+
import type { ScanRepoResult } from '../scan-repo.js';
|
|
5
|
+
export interface TextFormatOptions {
|
|
6
|
+
verbose: boolean;
|
|
7
|
+
/** Max stale flags to show before truncating. Default: 10. Ignored if verbose. */
|
|
8
|
+
maxDisplay: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function formatText(result: ScanRepoResult, options: TextFormatOptions): string;
|
|
11
|
+
//# sourceMappingURL=text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/output/text.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAGrD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAA;CACnB;AAmDD,wBAAgB,UAAU,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAoErF"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-readable text output for FlagShark scan results.
|
|
3
|
+
*/
|
|
4
|
+
function maxSeverity(signals) {
|
|
5
|
+
return signals.some((s) => s.severity === 'error') ? 'error' : 'warning';
|
|
6
|
+
}
|
|
7
|
+
function severityRank(s) {
|
|
8
|
+
return s === 'error' ? 0 : 1;
|
|
9
|
+
}
|
|
10
|
+
/** Pad a string to a fixed width, truncating with ellipsis if necessary. */
|
|
11
|
+
function pad(str, width) {
|
|
12
|
+
if (str.length > width) {
|
|
13
|
+
return str.slice(0, width - 1) + '…';
|
|
14
|
+
}
|
|
15
|
+
return str.padEnd(width);
|
|
16
|
+
}
|
|
17
|
+
function buildTable(flags) {
|
|
18
|
+
const cols = { flag: 16, file: 22, added: 13, signal: 28 };
|
|
19
|
+
const hBorder = (left, mid, right) => `${left}${'─'.repeat(cols.flag + 2)}${mid}${'─'.repeat(cols.file + 2)}${mid}${'─'.repeat(cols.added + 2)}${mid}${'─'.repeat(cols.signal + 2)}${right}`;
|
|
20
|
+
const lines = [];
|
|
21
|
+
lines.push(hBorder('┌', '┬', '┐'));
|
|
22
|
+
lines.push(`│ ${pad('Flag', cols.flag)} │ ${pad('File', cols.file)} │ ${pad('Added', cols.added)} │ ${pad('Signal', cols.signal)} │`);
|
|
23
|
+
lines.push(hBorder('├', '┼', '┤'));
|
|
24
|
+
for (const sf of flags) {
|
|
25
|
+
const fileRef = `${sf.filePath}:${sf.lineNumber}`;
|
|
26
|
+
const signalText = sf.signals
|
|
27
|
+
.map((s) => {
|
|
28
|
+
if (s.type === 'age')
|
|
29
|
+
return 'Age > threshold';
|
|
30
|
+
if (s.type === 'low-usage')
|
|
31
|
+
return 'Single file';
|
|
32
|
+
if (s.type === 'missing-in-platform')
|
|
33
|
+
return 'missing-in-platform';
|
|
34
|
+
if (s.type === 'archived-in-platform')
|
|
35
|
+
return 'archived-in-platform';
|
|
36
|
+
return s.description;
|
|
37
|
+
})
|
|
38
|
+
.join(', ');
|
|
39
|
+
lines.push(`│ ${pad(sf.name, cols.flag)} │ ${pad(fileRef, cols.file)} │ ${pad(sf.age ?? 'unknown', cols.added)} │ ${pad(signalText, cols.signal)} │`);
|
|
40
|
+
}
|
|
41
|
+
lines.push(hBorder('└', '┴', '┘'));
|
|
42
|
+
return lines.join('\n');
|
|
43
|
+
}
|
|
44
|
+
export function formatText(result, options) {
|
|
45
|
+
const lines = [];
|
|
46
|
+
lines.push(`\u{1F988} FlagShark`);
|
|
47
|
+
lines.push('');
|
|
48
|
+
const langCount = Object.keys(result.languageBreakdown).length;
|
|
49
|
+
lines.push(`Scanned ${result.filesScanned} files across ${langCount} language${langCount === 1 ? '' : 's'}`);
|
|
50
|
+
if (result.excludedCount && result.excludedCount > 0) {
|
|
51
|
+
lines.push(`(${result.excludedCount} excluded via .flagsharkignore + excludes)`);
|
|
52
|
+
}
|
|
53
|
+
if (result.totalFlags === 0) {
|
|
54
|
+
lines.push('No feature flags detected.');
|
|
55
|
+
lines.push('');
|
|
56
|
+
lines.push('Supported providers: LaunchDarkly, Unleash, Flipt, Split.io, PostHog, and more.');
|
|
57
|
+
lines.push('Run flagshark scan --help for configuration options.');
|
|
58
|
+
return lines.join('\n');
|
|
59
|
+
}
|
|
60
|
+
if (result.detectedProviders.length > 0) {
|
|
61
|
+
lines.push(`Detected providers: ${result.detectedProviders.join(', ')}`);
|
|
62
|
+
}
|
|
63
|
+
const uniqueStaleNames = new Set(result.staleFlags.map((f) => f.name));
|
|
64
|
+
const staleCount = uniqueStaleNames.size;
|
|
65
|
+
lines.push(`Found ${result.totalFlags} feature flags, ${staleCount} stale`);
|
|
66
|
+
if (staleCount > 0) {
|
|
67
|
+
lines.push('');
|
|
68
|
+
lines.push('Stale flags:');
|
|
69
|
+
const sorted = [...result.staleFlags].sort((a, b) => {
|
|
70
|
+
const sevA = severityRank(maxSeverity(a.signals));
|
|
71
|
+
const sevB = severityRank(maxSeverity(b.signals));
|
|
72
|
+
if (sevA !== sevB)
|
|
73
|
+
return sevA - sevB;
|
|
74
|
+
return 0;
|
|
75
|
+
});
|
|
76
|
+
const displayCount = options.verbose ? staleCount : Math.min(staleCount, options.maxDisplay);
|
|
77
|
+
const displayFlags = sorted.slice(0, displayCount);
|
|
78
|
+
lines.push(buildTable(displayFlags));
|
|
79
|
+
const remaining = staleCount - displayCount;
|
|
80
|
+
if (remaining > 0) {
|
|
81
|
+
lines.push('');
|
|
82
|
+
lines.push(`... and ${remaining} more (use --verbose to see all)`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
lines.push('');
|
|
86
|
+
if (staleCount === 0) {
|
|
87
|
+
lines.push(`Flag Health Score: ${result.healthScore}/100 ✓ All flags look healthy!`);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
lines.push(`Flag Health Score: ${result.healthScore}/100 (${staleCount}/${result.totalFlags} flags are stale)`);
|
|
91
|
+
lines.push('');
|
|
92
|
+
lines.push('Automate cleanup → https://flagshark.com');
|
|
93
|
+
lines.push('Open source CLI → https://github.com/FlagShark/flagshark');
|
|
94
|
+
}
|
|
95
|
+
if (result.excludedPaths && result.excludedPaths.length > 0) {
|
|
96
|
+
lines.push('');
|
|
97
|
+
lines.push(`Excluded files (${result.excludedPaths.length}):`);
|
|
98
|
+
for (const p of result.excludedPaths) {
|
|
99
|
+
lines.push(` ${p}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return lines.join('\n');
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/output/text.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PlatformClient, PlatformFlag } from './interface.js';
|
|
2
|
+
export interface CacheOptions {
|
|
3
|
+
/** Override the cache directory. Default: XDG-spec resolution. */
|
|
4
|
+
cacheDir?: string;
|
|
5
|
+
/** Time-to-live in milliseconds. Default: 24h. */
|
|
6
|
+
ttlMs?: number;
|
|
7
|
+
/** When true, skip the cache entirely. */
|
|
8
|
+
noCache?: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface CacheReadResult {
|
|
11
|
+
fetchedAt: Date;
|
|
12
|
+
flags: PlatformFlag[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Build a stable cache key from platform name + non-secret config + token hash.
|
|
16
|
+
* The raw token is never written to disk.
|
|
17
|
+
*/
|
|
18
|
+
export declare function computeCacheKey(platformName: string, config: unknown, token: string): string;
|
|
19
|
+
/** Returns parsed cache contents, or null on miss / corruption / TTL expiry. */
|
|
20
|
+
export declare function readCache(key: string, opts?: CacheOptions): CacheReadResult | null;
|
|
21
|
+
/** Writes the cache file, creating directories as needed. Silent on error. */
|
|
22
|
+
export declare function writeCache(key: string, flags: PlatformFlag[], opts?: CacheOptions): void;
|
|
23
|
+
/** Reads from cache if fresh; otherwise calls the client and writes the result. */
|
|
24
|
+
export declare function loadPlatformFlagsCached(client: PlatformClient, cacheKey: string, opts?: CacheOptions & {
|
|
25
|
+
signal?: AbortSignal;
|
|
26
|
+
}): Promise<PlatformFlag[]>;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/providers/cache.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAElE,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0CAA0C;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAOD,UAAU,eAAe;IACvB,SAAS,EAAE,IAAI,CAAA;IACf,KAAK,EAAE,YAAY,EAAE,CAAA;CACtB;AAWD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,OAAO,EACf,KAAK,EAAE,MAAM,GACZ,MAAM,CAIR;AAED,gFAAgF;AAChF,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,YAAiB,GAAG,eAAe,GAAG,IAAI,CA4BtF;AAED,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,IAAI,GAAE,YAAiB,GAAG,IAAI,CAgB5F;AAED,mFAAmF;AACnF,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,YAAY,GAAG;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GACjD,OAAO,CAAC,YAAY,EAAE,CAAC,CAQzB"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
6
|
+
function resolveCacheDir(override) {
|
|
7
|
+
if (override)
|
|
8
|
+
return override;
|
|
9
|
+
const xdg = process.env.XDG_CACHE_HOME;
|
|
10
|
+
const base = xdg && xdg.length > 0 ? xdg : join(homedir(), '.cache');
|
|
11
|
+
return join(base, 'flagshark');
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Build a stable cache key from platform name + non-secret config + token hash.
|
|
15
|
+
* The raw token is never written to disk.
|
|
16
|
+
*/
|
|
17
|
+
export function computeCacheKey(platformName, config, token) {
|
|
18
|
+
const tokenHash = createHash('sha256').update(token).digest('hex').slice(0, 8);
|
|
19
|
+
const configHash = createHash('sha256').update(JSON.stringify(config)).digest('hex').slice(0, 8);
|
|
20
|
+
return `v1-${platformName}-${configHash}-${tokenHash}`;
|
|
21
|
+
}
|
|
22
|
+
/** Returns parsed cache contents, or null on miss / corruption / TTL expiry. */
|
|
23
|
+
export function readCache(key, opts = {}) {
|
|
24
|
+
const dir = resolveCacheDir(opts.cacheDir);
|
|
25
|
+
const path = join(dir, `${key}.json`);
|
|
26
|
+
let raw;
|
|
27
|
+
try {
|
|
28
|
+
raw = readFileSync(path, 'utf-8');
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
let parsed;
|
|
34
|
+
try {
|
|
35
|
+
parsed = JSON.parse(raw);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
if (typeof parsed?.fetchedAt !== 'string' || !Array.isArray(parsed.flags)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const fetchedAt = new Date(parsed.fetchedAt);
|
|
44
|
+
const ttl = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
45
|
+
if (Date.now() - fetchedAt.getTime() >= ttl)
|
|
46
|
+
return null;
|
|
47
|
+
const flags = parsed.flags.map((f) => ({
|
|
48
|
+
key: f.key,
|
|
49
|
+
archived: f.archived,
|
|
50
|
+
lastModified: f.lastModified ? new Date(f.lastModified) : null,
|
|
51
|
+
}));
|
|
52
|
+
return { fetchedAt, flags };
|
|
53
|
+
}
|
|
54
|
+
/** Writes the cache file, creating directories as needed. Silent on error. */
|
|
55
|
+
export function writeCache(key, flags, opts = {}) {
|
|
56
|
+
const dir = resolveCacheDir(opts.cacheDir);
|
|
57
|
+
try {
|
|
58
|
+
mkdirSync(dir, { recursive: true });
|
|
59
|
+
const body = {
|
|
60
|
+
fetchedAt: new Date().toISOString(),
|
|
61
|
+
flags: flags.map((f) => ({
|
|
62
|
+
key: f.key,
|
|
63
|
+
archived: f.archived,
|
|
64
|
+
lastModified: f.lastModified ? f.lastModified.toISOString() : null,
|
|
65
|
+
})),
|
|
66
|
+
};
|
|
67
|
+
writeFileSync(join(dir, `${key}.json`), JSON.stringify(body));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Cache write failure is non-fatal — data is still in memory for this run.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Reads from cache if fresh; otherwise calls the client and writes the result. */
|
|
74
|
+
export async function loadPlatformFlagsCached(client, cacheKey, opts = {}) {
|
|
75
|
+
if (!opts.noCache) {
|
|
76
|
+
const cached = readCache(cacheKey, opts);
|
|
77
|
+
if (cached)
|
|
78
|
+
return cached.flags;
|
|
79
|
+
}
|
|
80
|
+
const flags = await client.listFlags({ signal: opts.signal });
|
|
81
|
+
writeCache(cacheKey, flags, opts);
|
|
82
|
+
return flags;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/providers/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FeatureFlag } from '../detection/feature-flag.js';
|
|
2
|
+
import type { PlatformFlag, PlatformSignal } from './interface.js';
|
|
3
|
+
/**
|
|
4
|
+
* Pure function: joins detected flag keys against a platform's flag list,
|
|
5
|
+
* emits PlatformSignals for keys that are missing (error) or archived (warning).
|
|
6
|
+
*
|
|
7
|
+
* Does NOT surface platform flags with no code reference — that's a separate
|
|
8
|
+
* "orphan platform flags" feature, out of scope.
|
|
9
|
+
*/
|
|
10
|
+
export declare function crossReference(detectedFlags: Map<string, FeatureFlag[]>, platformFlags: PlatformFlag[], platformDisplayName: string): Map<string, PlatformSignal[]>;
|
|
11
|
+
/**
|
|
12
|
+
* Merge platform signals from multiple platforms into a single per-flag map.
|
|
13
|
+
* If both LaunchDarkly and Unleash say a flag is missing, the flag's entry
|
|
14
|
+
* gets two signals (one per platform).
|
|
15
|
+
*
|
|
16
|
+
* Source arrays are cloned — subsequent mutations to source don't affect into.
|
|
17
|
+
*/
|
|
18
|
+
export declare function mergePlatformSignals(into: Map<string, PlatformSignal[]>, source: Map<string, PlatformSignal[]>): void;
|
|
19
|
+
//# sourceMappingURL=cross-reference.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cross-reference.d.ts","sourceRoot":"","sources":["../../src/providers/cross-reference.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAElE;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,EACzC,aAAa,EAAE,YAAY,EAAE,EAC7B,mBAAmB,EAAE,MAAM,GAC1B,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC,CAsB/B;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC,EACnC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC,GACpC,IAAI,CASN"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure function: joins detected flag keys against a platform's flag list,
|
|
3
|
+
* emits PlatformSignals for keys that are missing (error) or archived (warning).
|
|
4
|
+
*
|
|
5
|
+
* Does NOT surface platform flags with no code reference — that's a separate
|
|
6
|
+
* "orphan platform flags" feature, out of scope.
|
|
7
|
+
*/
|
|
8
|
+
export function crossReference(detectedFlags, platformFlags, platformDisplayName) {
|
|
9
|
+
const platformByKey = new Map(platformFlags.map((f) => [f.key, f]));
|
|
10
|
+
const out = new Map();
|
|
11
|
+
for (const key of detectedFlags.keys()) {
|
|
12
|
+
const platform = platformByKey.get(key);
|
|
13
|
+
if (!platform) {
|
|
14
|
+
out.set(key, [{
|
|
15
|
+
type: 'missing-in-platform',
|
|
16
|
+
severity: 'error',
|
|
17
|
+
description: `referenced in code but not found in ${platformDisplayName}`,
|
|
18
|
+
}]);
|
|
19
|
+
}
|
|
20
|
+
else if (platform.archived) {
|
|
21
|
+
out.set(key, [{
|
|
22
|
+
type: 'archived-in-platform',
|
|
23
|
+
severity: 'warning',
|
|
24
|
+
description: `archived in ${platformDisplayName}`,
|
|
25
|
+
}]);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Merge platform signals from multiple platforms into a single per-flag map.
|
|
32
|
+
* If both LaunchDarkly and Unleash say a flag is missing, the flag's entry
|
|
33
|
+
* gets two signals (one per platform).
|
|
34
|
+
*
|
|
35
|
+
* Source arrays are cloned — subsequent mutations to source don't affect into.
|
|
36
|
+
*/
|
|
37
|
+
export function mergePlatformSignals(into, source) {
|
|
38
|
+
for (const [key, signals] of source) {
|
|
39
|
+
const existing = into.get(key);
|
|
40
|
+
if (existing) {
|
|
41
|
+
existing.push(...signals);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
into.set(key, [...signals]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=cross-reference.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cross-reference.js","sourceRoot":"","sources":["../../src/providers/cross-reference.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,aAAyC,EACzC,aAA6B,EAC7B,mBAA2B;IAE3B,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IACnE,MAAM,GAAG,GAAG,IAAI,GAAG,EAA4B,CAAA;IAE/C,KAAK,MAAM,GAAG,IAAI,aAAa,CAAC,IAAI,EAAE,EAAE,CAAC;QACvC,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACvC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;oBACZ,IAAI,EAAE,qBAAqB;oBAC3B,QAAQ,EAAE,OAAO;oBACjB,WAAW,EAAE,uCAAuC,mBAAmB,EAAE;iBAC1E,CAAC,CAAC,CAAA;QACL,CAAC;aAAM,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;YAC7B,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;oBACZ,IAAI,EAAE,sBAAsB;oBAC5B,QAAQ,EAAE,SAAS;oBACnB,WAAW,EAAE,eAAe,mBAAmB,EAAE;iBAClD,CAAC,CAAC,CAAA;QACL,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAmC,EACnC,MAAqC;IAErC,KAAK,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,MAAM,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC9B,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAA;QAC3B,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAA;QAC7B,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { PlatformFlag, PlatformClient, PlatformDefinition, PlatformSignal, } from './interface.js';
|
|
2
|
+
export { platformRegistry, findPlatform } from './registry.js';
|
|
3
|
+
export { crossReference, mergePlatformSignals } from './cross-reference.js';
|
|
4
|
+
export { computeCacheKey, readCache, writeCache, loadPlatformFlagsCached, type CacheOptions, } from './cache.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,cAAc,GACf,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC9D,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,EACL,eAAe,EACf,SAAS,EACT,UAAU,EACV,uBAAuB,EACvB,KAAK,YAAY,GAClB,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { platformRegistry, findPlatform } from './registry.js';
|
|
2
|
+
export { crossReference, mergePlatformSignals } from './cross-reference.js';
|
|
3
|
+
export { computeCacheKey, readCache, writeCache, loadPlatformFlagsCached, } from './cache.js';
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC9D,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,EACL,eAAe,EACf,SAAS,EACT,UAAU,EACV,uBAAuB,GAExB,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ZodType } from 'zod';
|
|
2
|
+
/** A flag entry as reported by a flag-management platform's API. */
|
|
3
|
+
export interface PlatformFlag {
|
|
4
|
+
key: string;
|
|
5
|
+
/** Each platform maps its concept (archived/disabled/stale) to this boolean. */
|
|
6
|
+
archived: boolean;
|
|
7
|
+
lastModified: Date | null;
|
|
8
|
+
}
|
|
9
|
+
/** Runtime client for a configured platform. Returned by PlatformDefinition.createClient. */
|
|
10
|
+
export interface PlatformClient {
|
|
11
|
+
/** Registry key, e.g. 'launchdarkly'. */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Human-readable name used in signal descriptions, e.g. 'LaunchDarkly'. */
|
|
14
|
+
displayName: string;
|
|
15
|
+
listFlags(opts?: {
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
}): Promise<PlatformFlag[]>;
|
|
18
|
+
}
|
|
19
|
+
/** Registry entry. Each platform implementation exports exactly one of these. */
|
|
20
|
+
export interface PlatformDefinition<TConfig = unknown> {
|
|
21
|
+
/** YAML key under `platforms:` and registry lookup key. */
|
|
22
|
+
name: string;
|
|
23
|
+
displayName: string;
|
|
24
|
+
/** Env var read for the secret token. User can override via token_env. */
|
|
25
|
+
defaultTokenEnv: string;
|
|
26
|
+
/** Zod schema validating this platform's config block. */
|
|
27
|
+
configSchema: ZodType<TConfig>;
|
|
28
|
+
/** Factory — validated config + resolved token → runtime client. No IO until listFlags() is called. */
|
|
29
|
+
createClient: (config: TConfig, token: string) => PlatformClient;
|
|
30
|
+
}
|
|
31
|
+
/** Signal type emitted by crossReference(). Merged into StaleFlag.signals[] by staleness.ts. */
|
|
32
|
+
export interface PlatformSignal {
|
|
33
|
+
type: 'missing-in-platform' | 'archived-in-platform';
|
|
34
|
+
severity: 'error' | 'warning';
|
|
35
|
+
description: string;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/providers/interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;AAElC,oEAAoE;AACpE,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,gFAAgF;IAChF,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,EAAE,IAAI,GAAG,IAAI,CAAA;CAC1B;AAED,6FAA6F;AAC7F,MAAM,WAAW,cAAc;IAC7B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAA;IACZ,4EAA4E;IAC5E,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAA;CACpE;AAED,iFAAiF;AACjF,MAAM,WAAW,kBAAkB,CAAC,OAAO,GAAG,OAAO;IACnD,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,0EAA0E;IAC1E,eAAe,EAAE,MAAM,CAAA;IACvB,0DAA0D;IAC1D,YAAY,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;IAC9B,uGAAuG;IACvG,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,cAAc,CAAA;CACjE;AAED,gGAAgG;AAChG,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,qBAAqB,GAAG,sBAAsB,CAAA;IACpD,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;CACpB"}
|