@flagshark/core 2.0.0 → 2.1.1
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 +2 -0
- package/dist/config/schema.d.ts +112 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +63 -0
- 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 +89 -0
- package/dist/output/text.js.map +1 -1
- package/dist/providers/cross-reference.d.ts +36 -2
- package/dist/providers/cross-reference.d.ts.map +1 -1
- package/dist/providers/cross-reference.js +102 -7
- package/dist/providers/cross-reference.js.map +1 -1
- package/dist/providers/interface.d.ts +116 -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 +243 -24
- package/dist/providers/launchdarkly/client.js.map +1 -1
- package/dist/providers/launchdarkly/types.d.ts +318 -0
- package/dist/providers/launchdarkly/types.d.ts.map +1 -1
- package/dist/providers/launchdarkly/types.js +82 -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 +28 -0
- 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 +31 -1
- package/dist/staleness.d.ts.map +1 -1
- package/dist/staleness.js +66 -10
- package/dist/staleness.js.map +1 -1
- package/package.json +2 -1
package/dist/scan-repo.d.ts
CHANGED
|
@@ -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' | 'platform-zero-evaluations' | 'platform-low-evaluations';
|
|
4
4
|
severity: 'error' | 'warning';
|
|
5
5
|
description: string;
|
|
6
6
|
}
|
|
@@ -13,6 +13,25 @@ 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
37
|
/** Flag lines older than this are considered stale. Default: 30. */
|
|
@@ -21,6 +40,17 @@ export interface StalenessOptions {
|
|
|
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.
|
package/dist/staleness.d.ts.map
CHANGED
|
@@ -1 +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;
|
|
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;IAK9B,IAAI,EACA,KAAK,GACL,WAAW,GACX,WAAW,GACX,qBAAqB,GACrB,sBAAsB,GACtB,kBAAkB,GAClB,mBAAmB,GACnB,mBAAmB,GACnB,2BAA2B,GAC3B,0BAA0B,CAAA;IAC9B,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,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;IACZ;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAA;IAEtC;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sEAAsE;IACtE,cAAc,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAA;CAC5D;AAED,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAA;IACrB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAChB,kEAAkE;IAClE,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,0BAA0B,EAAE,cAAc,EAAE,CAAC,CAAA;IAClF;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,GAAG,CACpB,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;AAmKD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,EACjC,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,SAAS,EAAE,CAAC,CAyItB"}
|
package/dist/staleness.js
CHANGED
|
@@ -168,11 +168,18 @@ export async function analyzeStaleness(flags, options) {
|
|
|
168
168
|
for (const [flagName, occurrences] of flags) {
|
|
169
169
|
// Low-usage is per-flag-name (not per-occurrence).
|
|
170
170
|
const lowUsageSignal = checkLowUsageSignal(flagName, occurrences);
|
|
171
|
+
// Pre-check: is this flag marked permanent by ANY platform? The
|
|
172
|
+
// `platform-permanent` cross-reference signal is a CONTROL marker,
|
|
173
|
+
// not a stale signal — it tells us to suppress age + low-usage
|
|
174
|
+
// (those are false positives on kill-switches / operational config).
|
|
175
|
+
// The marker itself never reaches the user-facing signals array.
|
|
176
|
+
const platformSigs = options.platformSignals?.get(flagName);
|
|
177
|
+
const isPermanent = platformSigs?.some((ps) => ps.type === 'platform-permanent') ?? false;
|
|
171
178
|
for (const flag of occurrences) {
|
|
172
179
|
const signals = [];
|
|
173
180
|
let age;
|
|
174
|
-
// Age signal (git blame)
|
|
175
|
-
if (!shallow) {
|
|
181
|
+
// Age signal (git blame) — skipped for permanent flags.
|
|
182
|
+
if (!shallow && !isPermanent) {
|
|
176
183
|
const blame = fileBlames.get(flag.filePath);
|
|
177
184
|
const authorTime = blame?.get(flag.lineNumber);
|
|
178
185
|
const ageResult = checkAgeSignal(authorTime, thresholdDays);
|
|
@@ -184,17 +191,37 @@ export async function analyzeStaleness(flags, options) {
|
|
|
184
191
|
age = formatAge(authorTime);
|
|
185
192
|
}
|
|
186
193
|
}
|
|
187
|
-
|
|
188
|
-
|
|
194
|
+
else if (!shallow && isPermanent) {
|
|
195
|
+
// Still compute the human-readable age for display, even though
|
|
196
|
+
// we suppress the staleness signal. Operators looking at the
|
|
197
|
+
// output for a permanent flag still benefit from knowing how old
|
|
198
|
+
// it is; we just don't yell about it.
|
|
199
|
+
const blame = fileBlames.get(flag.filePath);
|
|
200
|
+
const authorTime = blame?.get(flag.lineNumber);
|
|
201
|
+
if (authorTime !== undefined) {
|
|
202
|
+
age = formatAge(authorTime);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Low-usage signal — skipped for permanent flags.
|
|
206
|
+
if (lowUsageSignal && !isPermanent) {
|
|
189
207
|
signals.push(lowUsageSignal);
|
|
190
208
|
}
|
|
191
209
|
// Hardcoded signal (v2 placeholder — always null for regex v1)
|
|
192
210
|
// checkHardcodedSignal returns null unconditionally; kept for structural symmetry
|
|
193
211
|
// with the other signal detectors. No branch emitted here.
|
|
194
|
-
// Platform signals (from provider API cross-reference)
|
|
195
|
-
|
|
212
|
+
// Platform signals (from provider API cross-reference).
|
|
213
|
+
// `platform-permanent` is dropped at this seam — it was a control
|
|
214
|
+
// signal, not a user-facing one. `missing-in-platform` and
|
|
215
|
+
// `archived-in-platform` still fire even for permanent flags
|
|
216
|
+
// because those represent platform-state-vs-code mismatches the
|
|
217
|
+
// user genuinely needs to know about.
|
|
196
218
|
if (platformSigs) {
|
|
197
219
|
for (const ps of platformSigs) {
|
|
220
|
+
if (ps.type === 'platform-permanent')
|
|
221
|
+
continue;
|
|
222
|
+
// After the platform-permanent filter, the remaining types are
|
|
223
|
+
// missing-in-platform / archived-in-platform — both narrow to
|
|
224
|
+
// error/warning severities. Help the type checker see that.
|
|
198
225
|
signals.push({
|
|
199
226
|
type: ps.type,
|
|
200
227
|
severity: ps.severity,
|
|
@@ -202,9 +229,18 @@ export async function analyzeStaleness(flags, options) {
|
|
|
202
229
|
});
|
|
203
230
|
}
|
|
204
231
|
}
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
232
|
+
// A flag is stale only if it has at least one PRIMARY signal —
|
|
233
|
+
// anything other than `low-usage`. The single-file/low-usage
|
|
234
|
+
// signal is contributing context, not a stale verdict on its own:
|
|
235
|
+
// plenty of legitimate flags ARE single-file (small isolated
|
|
236
|
+
// features, kill switches, A/B toggles for one screen) and
|
|
237
|
+
// surfacing them as stale floods the output with false positives.
|
|
238
|
+
// When a flag IS stale for another reason, low-usage stays in
|
|
239
|
+
// the signals list because "this is single-file → small cleanup
|
|
240
|
+
// scope" is useful diagnostic for the reviewer.
|
|
241
|
+
const hasPrimarySignal = signals.some((s) => s.type !== 'low-usage');
|
|
242
|
+
if (hasPrimarySignal) {
|
|
243
|
+
const stale = {
|
|
208
244
|
name: flag.name,
|
|
209
245
|
filePath: flag.filePath,
|
|
210
246
|
lineNumber: flag.lineNumber,
|
|
@@ -212,7 +248,27 @@ export async function analyzeStaleness(flags, options) {
|
|
|
212
248
|
provider: flag.provider ?? 'unknown',
|
|
213
249
|
signals,
|
|
214
250
|
age,
|
|
215
|
-
}
|
|
251
|
+
};
|
|
252
|
+
// Propagate the detection confidence only when it's non-default,
|
|
253
|
+
// matching the FeatureFlag emission contract (absent = 'high').
|
|
254
|
+
if (flag.confidence && flag.confidence !== 'high') {
|
|
255
|
+
stale.confidence = flag.confidence;
|
|
256
|
+
}
|
|
257
|
+
// Attach platform-side metadata when available. Each field is
|
|
258
|
+
// optional so the StaleFlag shape stays stable for code-only
|
|
259
|
+
// scans (no platforms configured) and JSON consumers that
|
|
260
|
+
// never expected these fields aren't broken by their presence
|
|
261
|
+
// since they're absent unless explicitly populated.
|
|
262
|
+
const meta = options.platformMetadata?.get(flagName);
|
|
263
|
+
if (meta) {
|
|
264
|
+
if (meta.tags && meta.tags.length > 0)
|
|
265
|
+
stale.tags = meta.tags;
|
|
266
|
+
if (meta.maintainer)
|
|
267
|
+
stale.maintainer = meta.maintainer;
|
|
268
|
+
if (meta.status)
|
|
269
|
+
stale.platformStatus = meta.status;
|
|
270
|
+
}
|
|
271
|
+
staleFlags.push(stale);
|
|
216
272
|
}
|
|
217
273
|
}
|
|
218
274
|
}
|
package/dist/staleness.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staleness.js","sourceRoot":"","sources":["../src/staleness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"staleness.js","sourceRoot":"","sources":["../src/staleness.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flagshark/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Detection engine for FlagShark — finds feature flag references across 13 languages.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"generate:queries": "node scripts/generate-inline-queries.mjs",
|
|
26
26
|
"test": "vitest run",
|
|
27
27
|
"test:coverage": "vitest run --coverage",
|
|
28
|
+
"test:live": "vitest run --config vitest.live.config.ts",
|
|
28
29
|
"typecheck": "tsc --noEmit"
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|