@vibgrate/cli 2026.708.3 → 2026.709.2
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/DOCS.md +451 -295
- package/README.md +26 -19
- package/dist/baseline-VGDQ33ZE.js +6 -0
- package/dist/{baseline-AUC3HAXW.js.map → baseline-VGDQ33ZE.js.map} +1 -1
- package/dist/{chunk-2DJ3UXG7.js → chunk-3TCE6IGZ.js} +721 -437
- package/dist/chunk-3TCE6IGZ.js.map +1 -0
- package/dist/chunk-FU4MOQTC.js +3735 -0
- package/dist/chunk-FU4MOQTC.js.map +1 -0
- package/dist/{chunk-QUELGOCR.js → chunk-PIWHQSAR.js} +4 -4
- package/dist/{chunk-QUELGOCR.js.map → chunk-PIWHQSAR.js.map} +1 -1
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +845 -532
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +2 -2
- package/package.json +4 -3
- package/skills/vg/SKILL.md +26 -15
- package/dist/baseline-AUC3HAXW.js +0 -6
- package/dist/chunk-2DJ3UXG7.js.map +0 -1
- package/dist/chunk-JXGVMXYE.js +0 -31
- package/dist/chunk-JXGVMXYE.js.map +0 -1
|
@@ -0,0 +1,3735 @@
|
|
|
1
|
+
import { runCoreScan, writeJsonFile, compactUiPurpose } from './chunk-PIWHQSAR.js';
|
|
2
|
+
import { findPackageJsonFiles, readJsonFile, findFiles, readTextFile, pathExists } from './chunk-GI6W53LM.js';
|
|
3
|
+
import * as path3 from 'path';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import * as semver from 'semver';
|
|
7
|
+
import * as fs from 'fs/promises';
|
|
8
|
+
|
|
9
|
+
var NATIVE_MODULE_PACKAGES = /* @__PURE__ */ new Set([
|
|
10
|
+
// Image / media processing
|
|
11
|
+
"sharp",
|
|
12
|
+
// libvips native bindings
|
|
13
|
+
"canvas",
|
|
14
|
+
// node-canvas (Cairo native)
|
|
15
|
+
"jimp",
|
|
16
|
+
// pure JS but optionally uses native
|
|
17
|
+
"@napi-rs/image",
|
|
18
|
+
// NAPI-RS image processing
|
|
19
|
+
"imagemagick",
|
|
20
|
+
// ImageMagick bindings
|
|
21
|
+
"gm",
|
|
22
|
+
// GraphicsMagick
|
|
23
|
+
"fluent-ffmpeg",
|
|
24
|
+
// FFmpeg bindings
|
|
25
|
+
"ffmpeg-static",
|
|
26
|
+
// bundled FFmpeg binary
|
|
27
|
+
"puppeteer",
|
|
28
|
+
// ships Chromium binary
|
|
29
|
+
"playwright",
|
|
30
|
+
// ships browser binaries
|
|
31
|
+
"playwright-core",
|
|
32
|
+
// browser binaries
|
|
33
|
+
// Cryptography / security
|
|
34
|
+
"bcrypt",
|
|
35
|
+
// native bcrypt
|
|
36
|
+
"argon2",
|
|
37
|
+
// native argon2 hashing
|
|
38
|
+
"sodium-native",
|
|
39
|
+
// libsodium bindings
|
|
40
|
+
"libsodium-wrappers",
|
|
41
|
+
// libsodium WASM/native
|
|
42
|
+
"node-forge",
|
|
43
|
+
// mostly JS but linked to native in some envs
|
|
44
|
+
"ssh2",
|
|
45
|
+
// native SSH bindings (optional)
|
|
46
|
+
"keytar",
|
|
47
|
+
// OS keychain native bindings
|
|
48
|
+
// Database drivers
|
|
49
|
+
"better-sqlite3",
|
|
50
|
+
// native SQLite
|
|
51
|
+
"sqlite3",
|
|
52
|
+
// native SQLite
|
|
53
|
+
"pg-native",
|
|
54
|
+
// PostgreSQL native bindings
|
|
55
|
+
"oracledb",
|
|
56
|
+
// Oracle native client
|
|
57
|
+
"odbc",
|
|
58
|
+
// native ODBC
|
|
59
|
+
"ibm_db",
|
|
60
|
+
// IBM DB2 native
|
|
61
|
+
"couchbase",
|
|
62
|
+
// native Couchbase SDK
|
|
63
|
+
"rocksdb",
|
|
64
|
+
// native RocksDB store
|
|
65
|
+
"leveldown",
|
|
66
|
+
// native LevelDB
|
|
67
|
+
"lmdb",
|
|
68
|
+
// native LMDB bindings
|
|
69
|
+
// Compilation & build tools
|
|
70
|
+
"node-gyp",
|
|
71
|
+
// native build tool itself
|
|
72
|
+
"node-pre-gyp",
|
|
73
|
+
// native binary distribution
|
|
74
|
+
"@mapbox/node-pre-gyp",
|
|
75
|
+
// native binary distribution
|
|
76
|
+
"prebuild",
|
|
77
|
+
// prebuilt native bindings
|
|
78
|
+
"prebuild-install",
|
|
79
|
+
// prebuilt native installer
|
|
80
|
+
"esbuild",
|
|
81
|
+
// platform-specific Go binary
|
|
82
|
+
"@swc/core",
|
|
83
|
+
// platform-specific Rust binary
|
|
84
|
+
"@rspack/core",
|
|
85
|
+
// platform-specific Rust binary
|
|
86
|
+
"@biomejs/biome",
|
|
87
|
+
// platform-specific Rust binary
|
|
88
|
+
"node-sass",
|
|
89
|
+
// deprecated, native libsass
|
|
90
|
+
"sass-embedded",
|
|
91
|
+
// Dart Sass embedded binary
|
|
92
|
+
"turbo",
|
|
93
|
+
// Turborepo Go/Rust binary
|
|
94
|
+
"@vercel/nft",
|
|
95
|
+
// native file tracing (optional)
|
|
96
|
+
// System / hardware access
|
|
97
|
+
"fsevents",
|
|
98
|
+
// macOS-only file watching
|
|
99
|
+
"cpu-features",
|
|
100
|
+
// CPU instruction detection
|
|
101
|
+
"deasync",
|
|
102
|
+
// native event loop control
|
|
103
|
+
"usb",
|
|
104
|
+
// USB device access
|
|
105
|
+
"serialport",
|
|
106
|
+
// serial port access
|
|
107
|
+
"node-hid",
|
|
108
|
+
// HID device access
|
|
109
|
+
"i2c-bus",
|
|
110
|
+
// I2C bus access
|
|
111
|
+
"spi-device",
|
|
112
|
+
// SPI bus access
|
|
113
|
+
"node-bluetooth",
|
|
114
|
+
// Bluetooth
|
|
115
|
+
"mdns",
|
|
116
|
+
// mDNS/Bonjour
|
|
117
|
+
// Compression
|
|
118
|
+
"snappy",
|
|
119
|
+
// native Snappy compression
|
|
120
|
+
"zstd-napi",
|
|
121
|
+
// native Zstandard
|
|
122
|
+
"lz4",
|
|
123
|
+
// native LZ4
|
|
124
|
+
"brotli",
|
|
125
|
+
// native Brotli (older, node has built-in)
|
|
126
|
+
// Regex / text
|
|
127
|
+
"re2",
|
|
128
|
+
// native RE2 regex engine
|
|
129
|
+
"oniguruma",
|
|
130
|
+
// native Oniguruma regex
|
|
131
|
+
"vscode-oniguruma",
|
|
132
|
+
// native Oniguruma (VS Code)
|
|
133
|
+
"tree-sitter",
|
|
134
|
+
// native parser generator
|
|
135
|
+
"node-tree-sitter",
|
|
136
|
+
// native Tree-sitter
|
|
137
|
+
// XML / HTML
|
|
138
|
+
"libxmljs",
|
|
139
|
+
// native libxml2
|
|
140
|
+
"libxmljs2",
|
|
141
|
+
// native libxml2
|
|
142
|
+
"node-expat",
|
|
143
|
+
// native Expat XML parser
|
|
144
|
+
"htmlparser2",
|
|
145
|
+
// mostly JS, optional native
|
|
146
|
+
// Networking / IPC
|
|
147
|
+
"@grpc/grpc-js",
|
|
148
|
+
// gRPC (JS but has native dep for HTTP/2)
|
|
149
|
+
"grpc",
|
|
150
|
+
// deprecated native gRPC
|
|
151
|
+
"zeromq",
|
|
152
|
+
// native ZeroMQ
|
|
153
|
+
"nanomsg",
|
|
154
|
+
// native nanomsg
|
|
155
|
+
"unix-dgram",
|
|
156
|
+
// native Unix sockets
|
|
157
|
+
// Observability
|
|
158
|
+
"dtrace-provider",
|
|
159
|
+
// native DTrace
|
|
160
|
+
"v8-profiler-next",
|
|
161
|
+
// native V8 profiler
|
|
162
|
+
"heapdump",
|
|
163
|
+
// native V8 heap dump
|
|
164
|
+
// Misc
|
|
165
|
+
"farmhash",
|
|
166
|
+
// native FarmHash
|
|
167
|
+
"xxhash",
|
|
168
|
+
// native xxHash
|
|
169
|
+
"xxhash-addon",
|
|
170
|
+
// native xxHash
|
|
171
|
+
"iconv",
|
|
172
|
+
// native iconv
|
|
173
|
+
"ref-napi",
|
|
174
|
+
// native FFI
|
|
175
|
+
"ffi-napi",
|
|
176
|
+
// native FFI
|
|
177
|
+
"node-pty",
|
|
178
|
+
// native pseudo-terminal
|
|
179
|
+
"robotjs",
|
|
180
|
+
// native desktop automation
|
|
181
|
+
"electron",
|
|
182
|
+
// ships Chromium + Node binary
|
|
183
|
+
"xdg-open",
|
|
184
|
+
// OS-specific
|
|
185
|
+
"windows-process-tree"
|
|
186
|
+
// Windows-only
|
|
187
|
+
]);
|
|
188
|
+
var OS_PATTERNS = [
|
|
189
|
+
{ pattern: /\bcmd\.exe\b|\.bat\b|\.cmd\b/i, label: "windows-scripts" },
|
|
190
|
+
{ pattern: /\bpowershell\b|\bpwsh\b/i, label: "powershell" },
|
|
191
|
+
{ pattern: /\bbash\b|#!\/bin\/bash/i, label: "bash-scripts" },
|
|
192
|
+
{ pattern: /\\\\/g, label: "backslash-paths" }
|
|
193
|
+
];
|
|
194
|
+
async function scanPlatformMatrix(rootDir, cache) {
|
|
195
|
+
const result = {
|
|
196
|
+
dotnetTargetFrameworks: [],
|
|
197
|
+
nativeModules: [],
|
|
198
|
+
osAssumptions: [],
|
|
199
|
+
dockerBaseImages: [],
|
|
200
|
+
nodeVersionFiles: []
|
|
201
|
+
};
|
|
202
|
+
const pkgFiles = cache ? await cache.findPackageJsonFiles(rootDir) : await findPackageJsonFiles(rootDir);
|
|
203
|
+
const allDeps = /* @__PURE__ */ new Set();
|
|
204
|
+
const osAssumptions = /* @__PURE__ */ new Set();
|
|
205
|
+
for (const pjPath of pkgFiles) {
|
|
206
|
+
try {
|
|
207
|
+
const pj = cache ? await cache.readJsonFile(pjPath) : await readJsonFile(pjPath);
|
|
208
|
+
if (pj.engines?.node && !result.nodeEngines) result.nodeEngines = pj.engines.node;
|
|
209
|
+
if (pj.engines?.npm && !result.npmEngines) result.npmEngines = pj.engines.npm;
|
|
210
|
+
if (pj.engines?.pnpm && !result.pnpmEngines) {
|
|
211
|
+
result.pnpmEngines = pj.engines.pnpm;
|
|
212
|
+
}
|
|
213
|
+
for (const section of ["dependencies", "devDependencies", "optionalDependencies"]) {
|
|
214
|
+
const deps = pj[section];
|
|
215
|
+
if (deps) {
|
|
216
|
+
for (const name of Object.keys(deps)) {
|
|
217
|
+
allDeps.add(name);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const scripts = pj.scripts;
|
|
222
|
+
if (scripts && typeof scripts === "object") {
|
|
223
|
+
for (const val of Object.values(scripts)) {
|
|
224
|
+
if (typeof val !== "string") continue;
|
|
225
|
+
const firstToken = val.split(/\s/)[0] ?? "";
|
|
226
|
+
for (const { pattern, label } of OS_PATTERNS) {
|
|
227
|
+
if (pattern.test(firstToken)) {
|
|
228
|
+
osAssumptions.add(label);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
} catch {
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
for (const dep of allDeps) {
|
|
237
|
+
if (NATIVE_MODULE_PACKAGES.has(dep)) {
|
|
238
|
+
result.nativeModules.push(dep);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
result.nativeModules.sort();
|
|
242
|
+
result.osAssumptions = [...osAssumptions].sort();
|
|
243
|
+
const csprojFiles = cache ? await cache.findFiles(rootDir, (name) => name.endsWith(".csproj") || name.endsWith(".vbproj")) : await findFiles(rootDir, (name) => name.endsWith(".csproj") || name.endsWith(".vbproj"));
|
|
244
|
+
const tfms = /* @__PURE__ */ new Set();
|
|
245
|
+
for (const csprojPath of csprojFiles) {
|
|
246
|
+
try {
|
|
247
|
+
const xml = cache ? await cache.readTextFile(csprojPath) : await readTextFile(csprojPath);
|
|
248
|
+
const tfMatch = xml.match(/<TargetFramework>(.*?)<\/TargetFramework>/);
|
|
249
|
+
if (tfMatch?.[1]) tfms.add(tfMatch[1]);
|
|
250
|
+
const tfsMatch = xml.match(/<TargetFrameworks>(.*?)<\/TargetFrameworks>/);
|
|
251
|
+
if (tfsMatch?.[1]) {
|
|
252
|
+
for (const tfm of tfsMatch[1].split(";")) {
|
|
253
|
+
if (tfm.trim()) tfms.add(tfm.trim());
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
result.dotnetTargetFrameworks = [...tfms].sort();
|
|
260
|
+
const dockerfiles = cache ? await cache.findFiles(
|
|
261
|
+
rootDir,
|
|
262
|
+
(name) => name === "Dockerfile" || name.startsWith("Dockerfile.")
|
|
263
|
+
) : await findFiles(
|
|
264
|
+
rootDir,
|
|
265
|
+
(name) => name === "Dockerfile" || name.startsWith("Dockerfile.")
|
|
266
|
+
);
|
|
267
|
+
const baseImages = /* @__PURE__ */ new Set();
|
|
268
|
+
for (const df of dockerfiles) {
|
|
269
|
+
try {
|
|
270
|
+
const content = cache ? await cache.readTextFile(df) : await readTextFile(df);
|
|
271
|
+
for (const line of content.split("\n")) {
|
|
272
|
+
const trimmed = line.trim();
|
|
273
|
+
if (/^FROM\s+/i.test(trimmed)) {
|
|
274
|
+
const parts = trimmed.split(/\s+/);
|
|
275
|
+
if (parts[1] && !parts[1].startsWith("--")) {
|
|
276
|
+
baseImages.add(parts[1]);
|
|
277
|
+
} else if (parts[1]?.startsWith("--")) {
|
|
278
|
+
const imageIdx = parts[1].includes("=") ? 2 : 3;
|
|
279
|
+
if (parts[imageIdx]) baseImages.add(parts[imageIdx]);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
} catch {
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
result.dockerBaseImages = [...baseImages].sort();
|
|
287
|
+
for (const file of [".nvmrc", ".node-version", ".tool-versions"]) {
|
|
288
|
+
const exists = cache ? await cache.pathExists(path3.join(rootDir, file)) : await pathExists(path3.join(rootDir, file));
|
|
289
|
+
if (exists) {
|
|
290
|
+
result.nodeVersionFiles.push(file);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/reporting/scanners/dependency-risk.ts
|
|
297
|
+
var DEPRECATED_PACKAGES = /* @__PURE__ */ new Set([
|
|
298
|
+
"request",
|
|
299
|
+
"node-sass",
|
|
300
|
+
"tslint",
|
|
301
|
+
"istanbul",
|
|
302
|
+
"popper.js",
|
|
303
|
+
"Left-pad",
|
|
304
|
+
"left-pad",
|
|
305
|
+
"bower",
|
|
306
|
+
"grunt",
|
|
307
|
+
"gulp",
|
|
308
|
+
"coffee-script",
|
|
309
|
+
"coffeescript",
|
|
310
|
+
"merge",
|
|
311
|
+
"nomnom",
|
|
312
|
+
"optimist",
|
|
313
|
+
"natives",
|
|
314
|
+
"querystring",
|
|
315
|
+
"domain-browser",
|
|
316
|
+
"sys",
|
|
317
|
+
"punycode"
|
|
318
|
+
]);
|
|
319
|
+
var NATIVE_MODULE_PACKAGES2 = /* @__PURE__ */ new Set([
|
|
320
|
+
"sharp",
|
|
321
|
+
"canvas",
|
|
322
|
+
"bcrypt",
|
|
323
|
+
"node-gyp",
|
|
324
|
+
"fsevents",
|
|
325
|
+
"better-sqlite3",
|
|
326
|
+
"sqlite3",
|
|
327
|
+
"leveldown",
|
|
328
|
+
"sodium-native",
|
|
329
|
+
"node-sass",
|
|
330
|
+
"argon2",
|
|
331
|
+
"usb",
|
|
332
|
+
"serialport",
|
|
333
|
+
"re2",
|
|
334
|
+
"libxmljs",
|
|
335
|
+
"libxmljs2",
|
|
336
|
+
"cpu-features",
|
|
337
|
+
"deasync",
|
|
338
|
+
"farmhash",
|
|
339
|
+
"grpc",
|
|
340
|
+
"@grpc/grpc-js"
|
|
341
|
+
]);
|
|
342
|
+
function scanDependencyRisk(projects) {
|
|
343
|
+
const deprecated = /* @__PURE__ */ new Set();
|
|
344
|
+
const nativeModules = /* @__PURE__ */ new Set();
|
|
345
|
+
let totalDeps = 0;
|
|
346
|
+
for (const project of projects) {
|
|
347
|
+
for (const dep of project.dependencies) {
|
|
348
|
+
totalDeps++;
|
|
349
|
+
if (DEPRECATED_PACKAGES.has(dep.package)) {
|
|
350
|
+
deprecated.add(dep.package);
|
|
351
|
+
}
|
|
352
|
+
if (NATIVE_MODULE_PACKAGES2.has(dep.package)) {
|
|
353
|
+
nativeModules.add(dep.package);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
deprecatedPackages: [...deprecated].sort(),
|
|
359
|
+
nativeModulePackages: [...nativeModules].sort(),
|
|
360
|
+
totalDependencies: totalDeps
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
function parsePnpmLock(content) {
|
|
364
|
+
const entries = [];
|
|
365
|
+
const regex = /^\s+\/?(@?[^@\s][^@\s]*?)@(\d+\.\d+\.\d+[^:\s]*)\s*:/gm;
|
|
366
|
+
let match;
|
|
367
|
+
while ((match = regex.exec(content)) !== null) {
|
|
368
|
+
if (match[1] && match[2]) {
|
|
369
|
+
entries.push({ name: match[1], version: match[2] });
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return entries;
|
|
373
|
+
}
|
|
374
|
+
function parseNpmLock(content) {
|
|
375
|
+
const entries = [];
|
|
376
|
+
try {
|
|
377
|
+
const lock = JSON.parse(content);
|
|
378
|
+
if (lock.packages && typeof lock.packages === "object") {
|
|
379
|
+
for (const [key, value] of Object.entries(lock.packages)) {
|
|
380
|
+
if (key === "") continue;
|
|
381
|
+
const v = value;
|
|
382
|
+
if (v.version) {
|
|
383
|
+
const name = key.replace(/^node_modules\//, "").replace(/.*node_modules\//, "");
|
|
384
|
+
entries.push({ name, version: v.version });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
} else if (lock.dependencies && typeof lock.dependencies === "object") {
|
|
388
|
+
let walkDeps2 = function(deps) {
|
|
389
|
+
for (const [name, data] of Object.entries(deps)) {
|
|
390
|
+
if (data.version) entries.push({ name, version: data.version });
|
|
391
|
+
if (data.dependencies) walkDeps2(data.dependencies);
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
var walkDeps = walkDeps2;
|
|
395
|
+
walkDeps2(lock.dependencies);
|
|
396
|
+
}
|
|
397
|
+
} catch {
|
|
398
|
+
}
|
|
399
|
+
return entries;
|
|
400
|
+
}
|
|
401
|
+
function parseYarnLock(content) {
|
|
402
|
+
const entries = [];
|
|
403
|
+
const regex = /^"?(@?[^\s"@]+)@[^:]+:\s*\n\s+version\s+"([^"]+)"/gm;
|
|
404
|
+
let match;
|
|
405
|
+
while ((match = regex.exec(content)) !== null) {
|
|
406
|
+
if (match[1] && match[2]) {
|
|
407
|
+
entries.push({ name: match[1], version: match[2] });
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return entries;
|
|
411
|
+
}
|
|
412
|
+
async function scanDependencyGraph(rootDir, cache) {
|
|
413
|
+
const result = {
|
|
414
|
+
lockfileType: null,
|
|
415
|
+
totalUnique: 0,
|
|
416
|
+
totalInstalled: 0,
|
|
417
|
+
duplicatedPackages: [],
|
|
418
|
+
phantomDependencies: []
|
|
419
|
+
};
|
|
420
|
+
let entries = [];
|
|
421
|
+
const pnpmLock = path3.join(rootDir, "pnpm-lock.yaml");
|
|
422
|
+
const npmLock = path3.join(rootDir, "package-lock.json");
|
|
423
|
+
const yarnLock = path3.join(rootDir, "yarn.lock");
|
|
424
|
+
const _pathExists = cache ? (p) => cache.pathExists(p) : pathExists;
|
|
425
|
+
const _readTextFile = cache ? (p) => cache.readTextFile(p) : readTextFile;
|
|
426
|
+
if (await _pathExists(pnpmLock)) {
|
|
427
|
+
result.lockfileType = "pnpm";
|
|
428
|
+
const content = await _readTextFile(pnpmLock);
|
|
429
|
+
entries = parsePnpmLock(content);
|
|
430
|
+
} else if (await _pathExists(npmLock)) {
|
|
431
|
+
result.lockfileType = "npm";
|
|
432
|
+
const content = await _readTextFile(npmLock);
|
|
433
|
+
entries = parseNpmLock(content);
|
|
434
|
+
} else if (await _pathExists(yarnLock)) {
|
|
435
|
+
result.lockfileType = "yarn";
|
|
436
|
+
const content = await _readTextFile(yarnLock);
|
|
437
|
+
entries = parseYarnLock(content);
|
|
438
|
+
}
|
|
439
|
+
if (entries.length === 0) return result;
|
|
440
|
+
const versionMap = /* @__PURE__ */ new Map();
|
|
441
|
+
for (const entry of entries) {
|
|
442
|
+
const existing = versionMap.get(entry.name);
|
|
443
|
+
if (existing) {
|
|
444
|
+
existing.add(entry.version);
|
|
445
|
+
} else {
|
|
446
|
+
versionMap.set(entry.name, /* @__PURE__ */ new Set([entry.version]));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
result.totalInstalled = entries.length;
|
|
450
|
+
result.totalUnique = versionMap.size;
|
|
451
|
+
const duplicated = [];
|
|
452
|
+
for (const [name, versions] of versionMap) {
|
|
453
|
+
if (versions.size > 1) {
|
|
454
|
+
duplicated.push({
|
|
455
|
+
name,
|
|
456
|
+
versions: [...versions].sort(),
|
|
457
|
+
consumers: versions.size
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
duplicated.sort((a, b) => b.versions.length - a.versions.length || a.name.localeCompare(b.name));
|
|
462
|
+
result.duplicatedPackages = duplicated;
|
|
463
|
+
const lockedNames = new Set(versionMap.keys());
|
|
464
|
+
const pkgFiles = cache ? await cache.findPackageJsonFiles(rootDir) : await findPackageJsonFiles(rootDir);
|
|
465
|
+
const phantoms = /* @__PURE__ */ new Set();
|
|
466
|
+
const phantomDetails = [];
|
|
467
|
+
for (const pjPath of pkgFiles) {
|
|
468
|
+
try {
|
|
469
|
+
const pj = cache ? await cache.readJsonFile(pjPath) : await readJsonFile(pjPath);
|
|
470
|
+
const relPath = path3.relative(rootDir, pjPath);
|
|
471
|
+
for (const section of ["dependencies", "devDependencies"]) {
|
|
472
|
+
const deps = pj[section];
|
|
473
|
+
if (!deps) continue;
|
|
474
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
475
|
+
const ver = typeof version === "string" ? version : "";
|
|
476
|
+
if (!lockedNames.has(name) && !ver.startsWith("workspace:")) {
|
|
477
|
+
phantoms.add(name);
|
|
478
|
+
phantomDetails.push({ package: name, spec: ver, sourcePath: relPath });
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
} catch {
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
result.phantomDependencies = [...phantoms].sort();
|
|
486
|
+
result.phantomDependencyDetails = phantomDetails.sort((a, b) => a.sourcePath.localeCompare(b.sourcePath) || a.package.localeCompare(b.package));
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// src/reporting/scanners/tooling-inventory.ts
|
|
491
|
+
var CATEGORIES = {
|
|
492
|
+
frontend: {
|
|
493
|
+
"react": "React",
|
|
494
|
+
"react-dom": "React DOM",
|
|
495
|
+
"vue": "Vue",
|
|
496
|
+
"@angular/core": "Angular",
|
|
497
|
+
"svelte": "Svelte",
|
|
498
|
+
"solid-js": "Solid",
|
|
499
|
+
"qwik": "Qwik",
|
|
500
|
+
"htmx.org": "htmx",
|
|
501
|
+
"preact": "Preact",
|
|
502
|
+
"lit": "Lit",
|
|
503
|
+
"alpinejs": "Alpine.js",
|
|
504
|
+
"stimulus": "Stimulus",
|
|
505
|
+
"petite-vue": "petite-vue",
|
|
506
|
+
"mithril": "Mithril",
|
|
507
|
+
"inferno": "Inferno",
|
|
508
|
+
"hyperapp": "Hyperapp",
|
|
509
|
+
"marko": "Marko",
|
|
510
|
+
"@builder.io/qwik": "Qwik (Builder)",
|
|
511
|
+
"million": "Million",
|
|
512
|
+
"ember-source": "Ember"
|
|
513
|
+
},
|
|
514
|
+
metaFrameworks: {
|
|
515
|
+
"next": "Next.js",
|
|
516
|
+
"nuxt": "Nuxt",
|
|
517
|
+
"@analogjs/platform": "Analog",
|
|
518
|
+
"astro": "Astro",
|
|
519
|
+
"@remix-run/react": "Remix",
|
|
520
|
+
"gatsby": "Gatsby",
|
|
521
|
+
"@sveltejs/kit": "SvelteKit",
|
|
522
|
+
"@tanstack/start": "TanStack Start",
|
|
523
|
+
"@adonisjs/core": "AdonisJS",
|
|
524
|
+
"@redwoodjs/core": "RedwoodJS",
|
|
525
|
+
"blitz": "Blitz.js",
|
|
526
|
+
"@solidjs/start": "SolidStart",
|
|
527
|
+
"fresh": "Fresh (Deno)"
|
|
528
|
+
},
|
|
529
|
+
bundlers: {
|
|
530
|
+
"vite": "Vite",
|
|
531
|
+
"webpack": "webpack",
|
|
532
|
+
"rollup": "Rollup",
|
|
533
|
+
"esbuild": "esbuild",
|
|
534
|
+
"parcel": "Parcel",
|
|
535
|
+
"turbo": "Turbo",
|
|
536
|
+
"tsup": "tsup",
|
|
537
|
+
"unbuild": "unbuild",
|
|
538
|
+
"@swc/core": "SWC",
|
|
539
|
+
"bun": "Bun",
|
|
540
|
+
"pkg": "pkg",
|
|
541
|
+
"ncc": "ncc",
|
|
542
|
+
"@vercel/ncc": "Vercel ncc",
|
|
543
|
+
"microbundle": "microbundle",
|
|
544
|
+
"tsc-watch": "tsc-watch",
|
|
545
|
+
"ts-node": "ts-node",
|
|
546
|
+
"tsx": "tsx",
|
|
547
|
+
"jiti": "jiti",
|
|
548
|
+
"@rspack/core": "Rspack",
|
|
549
|
+
"farm": "Farm"
|
|
550
|
+
},
|
|
551
|
+
css: {
|
|
552
|
+
"tailwindcss": "Tailwind CSS",
|
|
553
|
+
"@mui/material": "MUI",
|
|
554
|
+
"vuetify": "Vuetify",
|
|
555
|
+
"bootstrap": "Bootstrap",
|
|
556
|
+
"styled-components": "styled-components",
|
|
557
|
+
"@emotion/react": "Emotion",
|
|
558
|
+
"@chakra-ui/react": "Chakra UI",
|
|
559
|
+
"sass": "Sass",
|
|
560
|
+
"@mantine/core": "Mantine",
|
|
561
|
+
"antd": "Ant Design",
|
|
562
|
+
"@radix-ui/react-slot": "Radix UI",
|
|
563
|
+
"@headlessui/react": "Headless UI",
|
|
564
|
+
"daisyui": "DaisyUI",
|
|
565
|
+
"@shadcn/ui": "shadcn/ui",
|
|
566
|
+
"primereact": "PrimeReact",
|
|
567
|
+
"primevue": "PrimeVue",
|
|
568
|
+
"@nextui-org/react": "NextUI",
|
|
569
|
+
"@ariakit/react": "Ariakit",
|
|
570
|
+
"windstitch": "Windstitch",
|
|
571
|
+
"vanilla-extract": "vanilla-extract",
|
|
572
|
+
"@vanilla-extract/css": "vanilla-extract",
|
|
573
|
+
"linaria": "Linaria",
|
|
574
|
+
"stylex": "StyleX",
|
|
575
|
+
"@stylexjs/stylex": "StyleX",
|
|
576
|
+
"unocss": "UnoCSS",
|
|
577
|
+
"postcss": "PostCSS",
|
|
578
|
+
"autoprefixer": "Autoprefixer",
|
|
579
|
+
"lightningcss": "Lightning CSS",
|
|
580
|
+
"less": "Less",
|
|
581
|
+
"stylus": "Stylus",
|
|
582
|
+
"open-props": "Open Props",
|
|
583
|
+
"@ark-ui/react": "Ark UI"
|
|
584
|
+
},
|
|
585
|
+
backend: {
|
|
586
|
+
"express": "Express",
|
|
587
|
+
"fastify": "Fastify",
|
|
588
|
+
"@nestjs/core": "NestJS",
|
|
589
|
+
"hono": "Hono",
|
|
590
|
+
"koa": "Koa",
|
|
591
|
+
"@hapi/hapi": "Hapi",
|
|
592
|
+
"restify": "Restify",
|
|
593
|
+
"elysia": "Elysia",
|
|
594
|
+
"@elysiajs/eden": "Elysia Eden",
|
|
595
|
+
"moleculer": "Moleculer",
|
|
596
|
+
"@feathersjs/feathers": "Feathers",
|
|
597
|
+
"sails": "Sails",
|
|
598
|
+
"micro": "Micro",
|
|
599
|
+
"polka": "Polka",
|
|
600
|
+
"h3": "h3",
|
|
601
|
+
"nitro": "Nitro",
|
|
602
|
+
"@trpc/server": "tRPC Server",
|
|
603
|
+
"@trpc/client": "tRPC Client",
|
|
604
|
+
"middy": "Middy (Lambda)",
|
|
605
|
+
"serverless": "Serverless Framework",
|
|
606
|
+
"aws-lambda": "AWS Lambda",
|
|
607
|
+
"@aws-sdk/client-lambda": "AWS Lambda SDK",
|
|
608
|
+
"@cloudflare/workers-types": "Cloudflare Workers",
|
|
609
|
+
"wrangler": "Wrangler"
|
|
610
|
+
},
|
|
611
|
+
orm: {
|
|
612
|
+
"prisma": "Prisma",
|
|
613
|
+
"@prisma/client": "Prisma Client",
|
|
614
|
+
"drizzle-orm": "Drizzle",
|
|
615
|
+
"typeorm": "TypeORM",
|
|
616
|
+
"sequelize": "Sequelize",
|
|
617
|
+
"knex": "Knex",
|
|
618
|
+
"pg": "pg (PostgreSQL)",
|
|
619
|
+
"mysql2": "mysql2",
|
|
620
|
+
"mongodb": "MongoDB",
|
|
621
|
+
"ioredis": "ioredis",
|
|
622
|
+
"redis": "Redis",
|
|
623
|
+
"better-sqlite3": "better-sqlite3",
|
|
624
|
+
"@mikro-orm/core": "MikroORM",
|
|
625
|
+
"mongoose": "Mongoose",
|
|
626
|
+
"mssql": "mssql",
|
|
627
|
+
"kysely": "Kysely",
|
|
628
|
+
"objection": "Objection.js",
|
|
629
|
+
"@planetscale/database": "PlanetScale",
|
|
630
|
+
"@neondatabase/serverless": "Neon",
|
|
631
|
+
"@libsql/client": "libSQL (Turso)",
|
|
632
|
+
"@electric-sql/pglite": "PGlite",
|
|
633
|
+
"sql.js": "sql.js",
|
|
634
|
+
"oracledb": "Oracle DB",
|
|
635
|
+
"cassandra-driver": "Cassandra",
|
|
636
|
+
"neo4j-driver": "Neo4j",
|
|
637
|
+
"@upstash/redis": "Upstash Redis",
|
|
638
|
+
"@upstash/kafka": "Upstash Kafka",
|
|
639
|
+
"dynamoose": "Dynamoose",
|
|
640
|
+
"fauna": "Fauna",
|
|
641
|
+
"faunadb": "FaunaDB",
|
|
642
|
+
"@clickhouse/client": "ClickHouse",
|
|
643
|
+
"influx": "InfluxDB",
|
|
644
|
+
"slonik": "Slonik",
|
|
645
|
+
"massive": "Massive.js"
|
|
646
|
+
},
|
|
647
|
+
testing: {
|
|
648
|
+
"vitest": "Vitest",
|
|
649
|
+
"jest": "Jest",
|
|
650
|
+
"mocha": "Mocha",
|
|
651
|
+
"@playwright/test": "Playwright",
|
|
652
|
+
"cypress": "Cypress",
|
|
653
|
+
"@testing-library/react": "Testing Library (React)",
|
|
654
|
+
"@testing-library/vue": "Testing Library (Vue)",
|
|
655
|
+
"@testing-library/svelte": "Testing Library (Svelte)",
|
|
656
|
+
"@testing-library/jest-dom": "Testing Library DOM",
|
|
657
|
+
"@testing-library/user-event": "Testing Library User Event",
|
|
658
|
+
"chai": "Chai",
|
|
659
|
+
"ava": "AVA",
|
|
660
|
+
"tap": "node-tap",
|
|
661
|
+
"supertest": "Supertest",
|
|
662
|
+
"storybook": "Storybook",
|
|
663
|
+
"@storybook/react": "Storybook (React)",
|
|
664
|
+
"@storybook/vue3": "Storybook (Vue)",
|
|
665
|
+
"msw": "Mock Service Worker",
|
|
666
|
+
"nock": "Nock",
|
|
667
|
+
"sinon": "Sinon",
|
|
668
|
+
"faker": "Faker",
|
|
669
|
+
"@faker-js/faker": "Faker.js",
|
|
670
|
+
"testcontainers": "Testcontainers",
|
|
671
|
+
"pact": "Pact",
|
|
672
|
+
"@pact-foundation/pact": "Pact",
|
|
673
|
+
"k6": "k6",
|
|
674
|
+
"artillery": "Artillery",
|
|
675
|
+
"autocannon": "Autocannon",
|
|
676
|
+
"puppeteer": "Puppeteer",
|
|
677
|
+
"webdriverio": "WebdriverIO",
|
|
678
|
+
"nightwatch": "Nightwatch",
|
|
679
|
+
"detox": "Detox (Mobile)",
|
|
680
|
+
"jest-image-snapshot": "Image Snapshot",
|
|
681
|
+
"happy-dom": "happy-dom",
|
|
682
|
+
"jsdom": "jsdom",
|
|
683
|
+
"c8": "c8 (coverage)",
|
|
684
|
+
"nyc": "nyc (coverage)",
|
|
685
|
+
"@vitest/coverage-v8": "Vitest Coverage"
|
|
686
|
+
},
|
|
687
|
+
lintFormat: {
|
|
688
|
+
"eslint": "ESLint",
|
|
689
|
+
"prettier": "Prettier",
|
|
690
|
+
"stylelint": "Stylelint",
|
|
691
|
+
"@biomejs/biome": "Biome",
|
|
692
|
+
"oxlint": "oxlint",
|
|
693
|
+
"tslint": "TSLint",
|
|
694
|
+
"@typescript-eslint/parser": "typescript-eslint",
|
|
695
|
+
"eslint-config-next": "ESLint Next.js",
|
|
696
|
+
"eslint-config-prettier": "ESLint Prettier",
|
|
697
|
+
"eslint-plugin-react": "ESLint React",
|
|
698
|
+
"eslint-plugin-vue": "ESLint Vue",
|
|
699
|
+
"husky": "Husky",
|
|
700
|
+
"lint-staged": "lint-staged",
|
|
701
|
+
"commitlint": "commitlint",
|
|
702
|
+
"@commitlint/cli": "commitlint",
|
|
703
|
+
"lefthook": "Lefthook",
|
|
704
|
+
"cspell": "CSpell",
|
|
705
|
+
"markdownlint": "markdownlint",
|
|
706
|
+
"depcheck": "depcheck",
|
|
707
|
+
"knip": "Knip",
|
|
708
|
+
"madge": "Madge",
|
|
709
|
+
"publint": "publint",
|
|
710
|
+
"arethetypeswrong": "Are the Types Wrong",
|
|
711
|
+
"sort-package-json": "sort-package-json"
|
|
712
|
+
},
|
|
713
|
+
apiMessaging: {
|
|
714
|
+
"graphql": "GraphQL",
|
|
715
|
+
"@grpc/grpc-js": "gRPC",
|
|
716
|
+
"@trpc/server": "tRPC",
|
|
717
|
+
"@connectrpc/connect": "Connect",
|
|
718
|
+
"socket.io": "Socket.IO",
|
|
719
|
+
"ws": "WebSocket (ws)",
|
|
720
|
+
"@apollo/server": "Apollo Server",
|
|
721
|
+
"@apollo/client": "Apollo Client",
|
|
722
|
+
"urql": "URQL",
|
|
723
|
+
"graphql-yoga": "GraphQL Yoga",
|
|
724
|
+
"mercurius": "Mercurius",
|
|
725
|
+
"type-graphql": "TypeGraphQL",
|
|
726
|
+
"nexus": "Nexus",
|
|
727
|
+
"pothos": "Pothos",
|
|
728
|
+
"@graphql-codegen/cli": "GraphQL Codegen",
|
|
729
|
+
"kafkajs": "Kafka.js",
|
|
730
|
+
"amqplib": "AMQP (RabbitMQ)",
|
|
731
|
+
"bullmq": "BullMQ",
|
|
732
|
+
"bull": "Bull",
|
|
733
|
+
"bee-queue": "Bee Queue",
|
|
734
|
+
"agenda": "Agenda",
|
|
735
|
+
"pg-boss": "pg-boss",
|
|
736
|
+
"@temporalio/client": "Temporal",
|
|
737
|
+
"inngest": "Inngest",
|
|
738
|
+
"trigger.dev": "Trigger.dev",
|
|
739
|
+
"nats": "NATS",
|
|
740
|
+
"mqtt": "MQTT",
|
|
741
|
+
"zeromq": "ZeroMQ",
|
|
742
|
+
"sse-channel": "SSE Channel"
|
|
743
|
+
},
|
|
744
|
+
observability: {
|
|
745
|
+
"@sentry/node": "Sentry (Node)",
|
|
746
|
+
"@sentry/browser": "Sentry (Browser)",
|
|
747
|
+
"@sentry/react": "Sentry (React)",
|
|
748
|
+
"@sentry/nextjs": "Sentry (Next.js)",
|
|
749
|
+
"@opentelemetry/api": "OpenTelemetry API",
|
|
750
|
+
"@opentelemetry/sdk-node": "OpenTelemetry SDK",
|
|
751
|
+
"@opentelemetry/auto-instrumentations-node": "OpenTelemetry Auto",
|
|
752
|
+
"applicationinsights": "Application Insights",
|
|
753
|
+
"pino": "Pino",
|
|
754
|
+
"winston": "Winston",
|
|
755
|
+
"dd-trace": "Datadog",
|
|
756
|
+
"newrelic": "New Relic",
|
|
757
|
+
"bunyan": "Bunyan",
|
|
758
|
+
"@axiomhq/pino": "Axiom (Pino)",
|
|
759
|
+
"loglevel": "loglevel",
|
|
760
|
+
"consola": "consola",
|
|
761
|
+
"@logtail/node": "Logtail",
|
|
762
|
+
"elastic-apm-node": "Elastic APM",
|
|
763
|
+
"prom-client": "Prometheus Client",
|
|
764
|
+
"@google-cloud/trace-agent": "GCP Trace",
|
|
765
|
+
"@google-cloud/logging": "GCP Logging",
|
|
766
|
+
"lightstep-tracer": "Lightstep",
|
|
767
|
+
"roarr": "Roarr",
|
|
768
|
+
"cls-hooked": "CLS Hooked",
|
|
769
|
+
"@baselime/node-opentelemetry": "Baselime",
|
|
770
|
+
"highlight.run": "Highlight",
|
|
771
|
+
"posthog-node": "PostHog",
|
|
772
|
+
"@amplitude/node": "Amplitude",
|
|
773
|
+
"mixpanel": "Mixpanel",
|
|
774
|
+
"@segment/analytics-node": "Segment"
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
function scanToolingInventory(projects) {
|
|
778
|
+
const result = {
|
|
779
|
+
frontend: [],
|
|
780
|
+
metaFrameworks: [],
|
|
781
|
+
bundlers: [],
|
|
782
|
+
css: [],
|
|
783
|
+
backend: [],
|
|
784
|
+
orm: [],
|
|
785
|
+
testing: [],
|
|
786
|
+
lintFormat: [],
|
|
787
|
+
apiMessaging: [],
|
|
788
|
+
observability: []
|
|
789
|
+
};
|
|
790
|
+
const packageVersions = /* @__PURE__ */ new Map();
|
|
791
|
+
for (const project of projects) {
|
|
792
|
+
for (const dep of project.dependencies) {
|
|
793
|
+
if (!packageVersions.has(dep.package)) {
|
|
794
|
+
packageVersions.set(dep.package, dep.resolvedVersion);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
for (const [category, packages] of Object.entries(CATEGORIES)) {
|
|
799
|
+
const items = [];
|
|
800
|
+
for (const [pkg, displayName] of Object.entries(packages)) {
|
|
801
|
+
if (packageVersions.has(pkg)) {
|
|
802
|
+
items.push({
|
|
803
|
+
name: displayName,
|
|
804
|
+
package: pkg,
|
|
805
|
+
version: packageVersions.get(pkg) ?? null
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
items.sort((a, b) => a.name.localeCompare(b.name));
|
|
810
|
+
result[category] = items;
|
|
811
|
+
}
|
|
812
|
+
return result;
|
|
813
|
+
}
|
|
814
|
+
var CI_FILES = {
|
|
815
|
+
".github/workflows": "github-actions",
|
|
816
|
+
".gitlab-ci.yml": "gitlab-ci",
|
|
817
|
+
"azure-pipelines.yml": "azure-devops",
|
|
818
|
+
"bitbucket-pipelines.yml": "bitbucket-pipelines",
|
|
819
|
+
"Jenkinsfile": "jenkins",
|
|
820
|
+
".circleci/config.yml": "circleci",
|
|
821
|
+
".travis.yml": "travis-ci"
|
|
822
|
+
};
|
|
823
|
+
var RELEASE_PACKAGES = /* @__PURE__ */ new Set([
|
|
824
|
+
"semantic-release",
|
|
825
|
+
"@changesets/cli",
|
|
826
|
+
"standard-version",
|
|
827
|
+
"release-it",
|
|
828
|
+
"auto",
|
|
829
|
+
"lerna"
|
|
830
|
+
]);
|
|
831
|
+
var RELEASE_FILES = {
|
|
832
|
+
".changeset": "changesets",
|
|
833
|
+
".releaserc": "semantic-release",
|
|
834
|
+
".releaserc.json": "semantic-release",
|
|
835
|
+
".releaserc.yml": "semantic-release",
|
|
836
|
+
"release.config.js": "semantic-release",
|
|
837
|
+
"release.config.cjs": "semantic-release",
|
|
838
|
+
"GitVersion.yml": "gitversion"
|
|
839
|
+
};
|
|
840
|
+
var MONOREPO_FILES = {
|
|
841
|
+
"pnpm-workspace.yaml": "pnpm-workspaces",
|
|
842
|
+
"lerna.json": "lerna",
|
|
843
|
+
"nx.json": "nx",
|
|
844
|
+
"turbo.json": "turbo",
|
|
845
|
+
"rush.json": "rush"
|
|
846
|
+
};
|
|
847
|
+
var IAC_EXTENSIONS = {
|
|
848
|
+
".tf": "terraform",
|
|
849
|
+
".bicep": "bicep"
|
|
850
|
+
};
|
|
851
|
+
async function scanBuildDeploy(rootDir, cache) {
|
|
852
|
+
const result = {
|
|
853
|
+
ci: [],
|
|
854
|
+
ciWorkflowCount: 0,
|
|
855
|
+
docker: { dockerfileCount: 0, baseImages: [] },
|
|
856
|
+
iac: [],
|
|
857
|
+
releaseTooling: [],
|
|
858
|
+
packageManagers: [],
|
|
859
|
+
monorepoTools: []
|
|
860
|
+
};
|
|
861
|
+
const _pathExists = cache ? (p) => cache.pathExists(p) : pathExists;
|
|
862
|
+
const _findFiles = cache ? (dir, pred) => cache.findFiles(dir, pred) : findFiles;
|
|
863
|
+
const _readTextFile = cache ? (p) => cache.readTextFile(p) : readTextFile;
|
|
864
|
+
const ciSystems = /* @__PURE__ */ new Set();
|
|
865
|
+
for (const [file, system] of Object.entries(CI_FILES)) {
|
|
866
|
+
const fullPath = path3.join(rootDir, file);
|
|
867
|
+
if (await _pathExists(fullPath)) {
|
|
868
|
+
ciSystems.add(system);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
const ghWorkflowDir = path3.join(rootDir, ".github", "workflows");
|
|
872
|
+
if (await _pathExists(ghWorkflowDir)) {
|
|
873
|
+
try {
|
|
874
|
+
if (cache) {
|
|
875
|
+
const entries = await cache.walkDir(rootDir);
|
|
876
|
+
const ghPrefix = path3.relative(rootDir, ghWorkflowDir) + path3.sep;
|
|
877
|
+
result.ciWorkflowCount = entries.filter(
|
|
878
|
+
(e) => e.isFile && e.relPath.startsWith(ghPrefix) && (e.name.endsWith(".yml") || e.name.endsWith(".yaml"))
|
|
879
|
+
).length;
|
|
880
|
+
} else {
|
|
881
|
+
const files = await _findFiles(
|
|
882
|
+
ghWorkflowDir,
|
|
883
|
+
(name) => name.endsWith(".yml") || name.endsWith(".yaml")
|
|
884
|
+
);
|
|
885
|
+
result.ciWorkflowCount = files.length;
|
|
886
|
+
}
|
|
887
|
+
} catch {
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
result.ci = [...ciSystems].sort();
|
|
891
|
+
const dockerfiles = await _findFiles(
|
|
892
|
+
rootDir,
|
|
893
|
+
(name) => name === "Dockerfile" || name.startsWith("Dockerfile.")
|
|
894
|
+
);
|
|
895
|
+
result.docker.dockerfileCount = dockerfiles.length;
|
|
896
|
+
const baseImages = /* @__PURE__ */ new Set();
|
|
897
|
+
for (const df of dockerfiles) {
|
|
898
|
+
try {
|
|
899
|
+
const content = await _readTextFile(df);
|
|
900
|
+
for (const line of content.split("\n")) {
|
|
901
|
+
const trimmed = line.trim();
|
|
902
|
+
if (/^FROM\s+/i.test(trimmed)) {
|
|
903
|
+
const parts = trimmed.split(/\s+/);
|
|
904
|
+
let imageIdx = 1;
|
|
905
|
+
if (parts[1]?.startsWith("--")) {
|
|
906
|
+
imageIdx = parts[1].includes("=") ? 2 : 3;
|
|
907
|
+
}
|
|
908
|
+
if (parts[imageIdx]) {
|
|
909
|
+
baseImages.add(parts[imageIdx]);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
} catch {
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
result.docker.baseImages = [...baseImages].sort();
|
|
917
|
+
const iacSystems = /* @__PURE__ */ new Set();
|
|
918
|
+
for (const [ext, system] of Object.entries(IAC_EXTENSIONS)) {
|
|
919
|
+
const files = await _findFiles(rootDir, (name) => name.endsWith(ext));
|
|
920
|
+
if (files.length > 0) iacSystems.add(system);
|
|
921
|
+
}
|
|
922
|
+
const cfnFiles = await _findFiles(
|
|
923
|
+
rootDir,
|
|
924
|
+
(name) => name.endsWith(".cfn.json") || name.endsWith(".cfn.yaml")
|
|
925
|
+
);
|
|
926
|
+
if (cfnFiles.length > 0) iacSystems.add("cloudformation");
|
|
927
|
+
if (await _pathExists(path3.join(rootDir, "Pulumi.yaml"))) iacSystems.add("pulumi");
|
|
928
|
+
result.iac = [...iacSystems].sort();
|
|
929
|
+
const releaseTools = /* @__PURE__ */ new Set();
|
|
930
|
+
for (const [file, tool] of Object.entries(RELEASE_FILES)) {
|
|
931
|
+
if (await _pathExists(path3.join(rootDir, file))) releaseTools.add(tool);
|
|
932
|
+
}
|
|
933
|
+
const pkgFiles = cache ? await cache.findPackageJsonFiles(rootDir) : await findPackageJsonFiles(rootDir);
|
|
934
|
+
for (const pjPath of pkgFiles) {
|
|
935
|
+
try {
|
|
936
|
+
const pj = cache ? await cache.readJsonFile(pjPath) : await readJsonFile(pjPath);
|
|
937
|
+
for (const section of ["dependencies", "devDependencies"]) {
|
|
938
|
+
const deps = pj[section];
|
|
939
|
+
if (!deps) continue;
|
|
940
|
+
for (const name of Object.keys(deps)) {
|
|
941
|
+
if (RELEASE_PACKAGES.has(name)) releaseTools.add(name);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
} catch {
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
result.releaseTooling = [...releaseTools].sort();
|
|
948
|
+
const lockfileMap = {
|
|
949
|
+
"pnpm-lock.yaml": "pnpm",
|
|
950
|
+
"package-lock.json": "npm",
|
|
951
|
+
"yarn.lock": "yarn",
|
|
952
|
+
"bun.lockb": "bun"
|
|
953
|
+
};
|
|
954
|
+
const managers = /* @__PURE__ */ new Set();
|
|
955
|
+
for (const [file, manager] of Object.entries(lockfileMap)) {
|
|
956
|
+
if (await _pathExists(path3.join(rootDir, file))) managers.add(manager);
|
|
957
|
+
}
|
|
958
|
+
let rootPkg = null;
|
|
959
|
+
const rootPkgPath = path3.join(rootDir, "package.json");
|
|
960
|
+
if (await _pathExists(rootPkgPath)) {
|
|
961
|
+
try {
|
|
962
|
+
rootPkg = cache ? await cache.readJsonFile(rootPkgPath) : await readJsonFile(rootPkgPath);
|
|
963
|
+
} catch {
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
if (rootPkg?.packageManager) {
|
|
967
|
+
const pm = rootPkg.packageManager.split("@")[0]?.toLowerCase();
|
|
968
|
+
if (pm && ["pnpm", "yarn", "npm", "bun"].includes(pm)) {
|
|
969
|
+
managers.add(pm);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
result.packageManagers = [...managers].sort();
|
|
973
|
+
const monoTools = /* @__PURE__ */ new Set();
|
|
974
|
+
for (const [file, tool] of Object.entries(MONOREPO_FILES)) {
|
|
975
|
+
if (await _pathExists(path3.join(rootDir, file))) monoTools.add(tool);
|
|
976
|
+
}
|
|
977
|
+
if (rootPkg?.workspaces) {
|
|
978
|
+
monoTools.add(managers.has("yarn") ? "yarn-workspaces" : "npm-workspaces");
|
|
979
|
+
}
|
|
980
|
+
result.monorepoTools = [...monoTools].sort();
|
|
981
|
+
return result;
|
|
982
|
+
}
|
|
983
|
+
async function scanTsModernity(rootDir, cache) {
|
|
984
|
+
const result = {
|
|
985
|
+
typescriptVersion: null,
|
|
986
|
+
strict: null,
|
|
987
|
+
noImplicitAny: null,
|
|
988
|
+
strictNullChecks: null,
|
|
989
|
+
module: null,
|
|
990
|
+
moduleResolution: null,
|
|
991
|
+
target: null,
|
|
992
|
+
moduleType: null,
|
|
993
|
+
exportsField: false
|
|
994
|
+
};
|
|
995
|
+
const pkgFiles = cache ? await cache.findPackageJsonFiles(rootDir) : await findPackageJsonFiles(rootDir);
|
|
996
|
+
let hasEsm = false;
|
|
997
|
+
let hasCjs = false;
|
|
998
|
+
for (const pjPath of pkgFiles) {
|
|
999
|
+
try {
|
|
1000
|
+
const pj = cache ? await cache.readJsonFile(pjPath) : await readJsonFile(pjPath);
|
|
1001
|
+
if (!result.typescriptVersion) {
|
|
1002
|
+
const tsVer = pj.devDependencies?.["typescript"] ?? pj.dependencies?.["typescript"];
|
|
1003
|
+
if (tsVer) {
|
|
1004
|
+
result.typescriptVersion = tsVer.replace(/^[\^~>=<\s]+/, "");
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
const typeField = pj.type;
|
|
1008
|
+
if (typeField === "module") hasEsm = true;
|
|
1009
|
+
else if (typeField === "commonjs") hasCjs = true;
|
|
1010
|
+
else if (!typeField) hasCjs = true;
|
|
1011
|
+
if (pj.exports) {
|
|
1012
|
+
result.exportsField = true;
|
|
1013
|
+
}
|
|
1014
|
+
} catch {
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
if (hasEsm && hasCjs) result.moduleType = "mixed";
|
|
1018
|
+
else if (hasEsm) result.moduleType = "esm";
|
|
1019
|
+
else if (hasCjs) result.moduleType = "cjs";
|
|
1020
|
+
let tsConfigPath = path3.join(rootDir, "tsconfig.json");
|
|
1021
|
+
const tsConfigExists = cache ? await cache.pathExists(tsConfigPath) : await pathExists(tsConfigPath);
|
|
1022
|
+
if (!tsConfigExists) {
|
|
1023
|
+
const tsConfigs = cache ? await cache.findFiles(rootDir, (name) => name === "tsconfig.json") : await findFiles(rootDir, (name) => name === "tsconfig.json");
|
|
1024
|
+
if (tsConfigs.length > 0) {
|
|
1025
|
+
tsConfigPath = tsConfigs[0];
|
|
1026
|
+
} else {
|
|
1027
|
+
return result;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
try {
|
|
1031
|
+
const raw = cache ? await cache.readTextFile(tsConfigPath) : await readTextFile(tsConfigPath);
|
|
1032
|
+
const stripped = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/,(\s*[}\]])/g, "$1");
|
|
1033
|
+
const tsConfig = JSON.parse(stripped);
|
|
1034
|
+
const co = tsConfig?.compilerOptions;
|
|
1035
|
+
if (co) {
|
|
1036
|
+
if (typeof co.strict === "boolean") result.strict = co.strict;
|
|
1037
|
+
if (typeof co.noImplicitAny === "boolean") result.noImplicitAny = co.noImplicitAny;
|
|
1038
|
+
if (typeof co.strictNullChecks === "boolean") result.strictNullChecks = co.strictNullChecks;
|
|
1039
|
+
if (typeof co.module === "string") result.module = co.module;
|
|
1040
|
+
if (typeof co.moduleResolution === "string") result.moduleResolution = co.moduleResolution;
|
|
1041
|
+
if (typeof co.target === "string") result.target = co.target;
|
|
1042
|
+
}
|
|
1043
|
+
} catch {
|
|
1044
|
+
}
|
|
1045
|
+
return result;
|
|
1046
|
+
}
|
|
1047
|
+
var DEPRECATED_PACKAGES2 = /* @__PURE__ */ new Set([
|
|
1048
|
+
"request",
|
|
1049
|
+
"request-promise",
|
|
1050
|
+
"request-promise-native",
|
|
1051
|
+
"moment",
|
|
1052
|
+
"node-sass",
|
|
1053
|
+
"tslint",
|
|
1054
|
+
"aws-sdk",
|
|
1055
|
+
"babel-core",
|
|
1056
|
+
"babel-preset-env",
|
|
1057
|
+
"babel-preset-react",
|
|
1058
|
+
"babel-loader",
|
|
1059
|
+
"core-js",
|
|
1060
|
+
"istanbul",
|
|
1061
|
+
"istanbul-instrumenter-loader",
|
|
1062
|
+
"left-pad",
|
|
1063
|
+
"popper.js",
|
|
1064
|
+
"create-react-class",
|
|
1065
|
+
"react-addons-css-transition-group",
|
|
1066
|
+
"react-addons-test-utils",
|
|
1067
|
+
"@types/express-serve-static-core",
|
|
1068
|
+
"enzyme",
|
|
1069
|
+
"enzyme-adapter-react-16",
|
|
1070
|
+
"enzyme-adapter-react-17",
|
|
1071
|
+
"react-hot-loader",
|
|
1072
|
+
"react-loadable",
|
|
1073
|
+
"react-router-dom-v5-compat",
|
|
1074
|
+
"redux-thunk",
|
|
1075
|
+
"redux-saga",
|
|
1076
|
+
"recompose",
|
|
1077
|
+
"classnames",
|
|
1078
|
+
"glamor",
|
|
1079
|
+
"radium",
|
|
1080
|
+
"material-ui",
|
|
1081
|
+
"@material-ui/core",
|
|
1082
|
+
"bower",
|
|
1083
|
+
"grunt",
|
|
1084
|
+
"gulp",
|
|
1085
|
+
"browserify",
|
|
1086
|
+
"coffee-script",
|
|
1087
|
+
"coffeescript",
|
|
1088
|
+
"jade",
|
|
1089
|
+
"nomnom",
|
|
1090
|
+
"optimist",
|
|
1091
|
+
"minimist",
|
|
1092
|
+
"colors",
|
|
1093
|
+
"faker",
|
|
1094
|
+
"event-stream",
|
|
1095
|
+
"ua-parser-js",
|
|
1096
|
+
"caniuse-db",
|
|
1097
|
+
"circular-json",
|
|
1098
|
+
"mkdirp",
|
|
1099
|
+
"rimraf",
|
|
1100
|
+
"glob",
|
|
1101
|
+
"swig",
|
|
1102
|
+
"dustjs-linkedin",
|
|
1103
|
+
"hogan.js",
|
|
1104
|
+
"passport-local-mongoose",
|
|
1105
|
+
"@angular/http",
|
|
1106
|
+
"rxjs-compat",
|
|
1107
|
+
"protractor",
|
|
1108
|
+
"karma",
|
|
1109
|
+
"karma-jasmine",
|
|
1110
|
+
"jasmine"
|
|
1111
|
+
]);
|
|
1112
|
+
var LEGACY_POLYFILLS = /* @__PURE__ */ new Set([
|
|
1113
|
+
"node-fetch",
|
|
1114
|
+
"cross-fetch",
|
|
1115
|
+
"isomorphic-fetch",
|
|
1116
|
+
"whatwg-fetch",
|
|
1117
|
+
"abort-controller",
|
|
1118
|
+
"form-data",
|
|
1119
|
+
"formdata-polyfill",
|
|
1120
|
+
"web-streams-polyfill",
|
|
1121
|
+
"whatwg-url",
|
|
1122
|
+
"url-parse",
|
|
1123
|
+
"domexception",
|
|
1124
|
+
"abortcontroller-polyfill",
|
|
1125
|
+
"querystring",
|
|
1126
|
+
"string_decoder",
|
|
1127
|
+
"buffer",
|
|
1128
|
+
"events",
|
|
1129
|
+
"path-browserify",
|
|
1130
|
+
"stream-browserify",
|
|
1131
|
+
"stream-http",
|
|
1132
|
+
"https-browserify",
|
|
1133
|
+
"os-browserify",
|
|
1134
|
+
"crypto-browserify",
|
|
1135
|
+
"assert",
|
|
1136
|
+
"util",
|
|
1137
|
+
"process",
|
|
1138
|
+
"timers-browserify",
|
|
1139
|
+
"tty-browserify",
|
|
1140
|
+
"vm-browserify",
|
|
1141
|
+
"domain-browser",
|
|
1142
|
+
"punycode",
|
|
1143
|
+
"readable-stream",
|
|
1144
|
+
"es6-promise",
|
|
1145
|
+
"promise-polyfill",
|
|
1146
|
+
"es6-symbol",
|
|
1147
|
+
"es6-map",
|
|
1148
|
+
"es6-set",
|
|
1149
|
+
"es6-weak-map",
|
|
1150
|
+
"es6-iterator",
|
|
1151
|
+
"object-assign",
|
|
1152
|
+
"object.assign",
|
|
1153
|
+
"array.prototype.find",
|
|
1154
|
+
"array.prototype.findindex",
|
|
1155
|
+
"array.prototype.flat",
|
|
1156
|
+
"array.prototype.flatmap",
|
|
1157
|
+
"array-includes",
|
|
1158
|
+
"string.prototype.startswith",
|
|
1159
|
+
"string.prototype.endswith",
|
|
1160
|
+
"string.prototype.includes",
|
|
1161
|
+
"string.prototype.padstart",
|
|
1162
|
+
"string.prototype.padend",
|
|
1163
|
+
"string.prototype.matchall",
|
|
1164
|
+
"string.prototype.replaceall",
|
|
1165
|
+
"string.prototype.trimstart",
|
|
1166
|
+
"string.prototype.trimend",
|
|
1167
|
+
"string.prototype.at",
|
|
1168
|
+
"object.entries",
|
|
1169
|
+
"object.values",
|
|
1170
|
+
"object.fromentries",
|
|
1171
|
+
"globalthis",
|
|
1172
|
+
"symbol-observable",
|
|
1173
|
+
"setimmediate",
|
|
1174
|
+
"regenerator-runtime",
|
|
1175
|
+
"@babel/polyfill",
|
|
1176
|
+
"whatwg-encoding",
|
|
1177
|
+
"text-encoding",
|
|
1178
|
+
"encoding",
|
|
1179
|
+
"unorm",
|
|
1180
|
+
"number.isnan",
|
|
1181
|
+
"is-nan",
|
|
1182
|
+
"has-symbols",
|
|
1183
|
+
"has",
|
|
1184
|
+
"hasown",
|
|
1185
|
+
"safe-buffer",
|
|
1186
|
+
"safer-buffer"
|
|
1187
|
+
]);
|
|
1188
|
+
var BREAKING_SIGNAL_PHRASES = [
|
|
1189
|
+
"BREAKING",
|
|
1190
|
+
"Breaking Change",
|
|
1191
|
+
"Removed",
|
|
1192
|
+
"Deprecated",
|
|
1193
|
+
"Migration",
|
|
1194
|
+
"Rename",
|
|
1195
|
+
"Drop support"
|
|
1196
|
+
];
|
|
1197
|
+
var PACKAGE_PLAYBOOKS = {
|
|
1198
|
+
vue: {
|
|
1199
|
+
impactedFeatures: ["Options API-heavy components", "Deprecated hooks", "Template filters", "$listeners / $attrs merge behavior", "Mixin-heavy architecture"],
|
|
1200
|
+
usagePatterns: [/\bexport\s+default\s*\{/, /\bmixins\s*:/, /\bfilters\s*:/, /\$listeners\b/, /beforeDestroy\b/, /destroyed\b/],
|
|
1201
|
+
automation: "manual"
|
|
1202
|
+
},
|
|
1203
|
+
"react-router-dom": {
|
|
1204
|
+
impactedFeatures: ["Switch/Route v5 patterns", "history prop mutation flows", "withRouter HOC usage"],
|
|
1205
|
+
usagePatterns: [/\bSwitch\b/, /\bwithRouter\b/, /\bhistory\./, /<Route\s+component=/],
|
|
1206
|
+
automation: "deterministic-recipe"
|
|
1207
|
+
},
|
|
1208
|
+
eslint: {
|
|
1209
|
+
impactedFeatures: ["Flat config migration", ".eslintrc plugin resolution", "legacy parser/plugin options"],
|
|
1210
|
+
usagePatterns: [/\.eslintrc/, /eslintConfig/, /module\.exports\s*=\s*\[/, /extends\s*:/],
|
|
1211
|
+
automation: "deterministic-recipe"
|
|
1212
|
+
},
|
|
1213
|
+
typescript: {
|
|
1214
|
+
impactedFeatures: ["Stricter type checks", "tsconfig option removals", "moduleResolution defaults changes"],
|
|
1215
|
+
usagePatterns: [/tsconfig\.json/, /strictNullChecks/, /noImplicitAny/, /moduleResolution/],
|
|
1216
|
+
automation: "manual"
|
|
1217
|
+
},
|
|
1218
|
+
"@angular/core": {
|
|
1219
|
+
impactedFeatures: ["NgModule bootstrap assumptions", "Standalone component migration", "Deprecated lifecycle signatures"],
|
|
1220
|
+
usagePatterns: [/@NgModule\b/, /ngOnInit\(/, /providers\s*:/],
|
|
1221
|
+
automation: "codemod-available",
|
|
1222
|
+
codemod: "ng update"
|
|
1223
|
+
}
|
|
1224
|
+
};
|
|
1225
|
+
function detectDecision(majorItems, manualHotspots, codemodItems) {
|
|
1226
|
+
if (majorItems === 0) return "do-nothing";
|
|
1227
|
+
if (codemodItems > 0 && manualHotspots === 0) return "codemod-available";
|
|
1228
|
+
if (manualHotspots > 0) return "manual-hotspots";
|
|
1229
|
+
if (majorItems <= 2) return "upgrade-safely-now";
|
|
1230
|
+
return "plan-major-upgrade";
|
|
1231
|
+
}
|
|
1232
|
+
function normalizeMajor(version) {
|
|
1233
|
+
if (!version) return null;
|
|
1234
|
+
const parsed = semver.coerce(version);
|
|
1235
|
+
return parsed?.major ?? null;
|
|
1236
|
+
}
|
|
1237
|
+
function resolveCurrentVersion(dep) {
|
|
1238
|
+
if (dep.resolvedVersion && semver.valid(semver.coerce(dep.resolvedVersion))) return semver.coerce(dep.resolvedVersion)?.version ?? null;
|
|
1239
|
+
const min = semver.minVersion(dep.currentSpec);
|
|
1240
|
+
return min?.version ?? null;
|
|
1241
|
+
}
|
|
1242
|
+
function listInterimMajorTargets(current, target) {
|
|
1243
|
+
if (target <= current + 1) return [];
|
|
1244
|
+
const out = [];
|
|
1245
|
+
for (let m = current + 1; m < target; m++) out.push(`${m}.x`);
|
|
1246
|
+
return out;
|
|
1247
|
+
}
|
|
1248
|
+
async function buildProjectUsageIndex(project, rootDir, fileCache, candidatePackages) {
|
|
1249
|
+
const index = /* @__PURE__ */ new Map();
|
|
1250
|
+
for (const pkg of candidatePackages) index.set(pkg, { importSites: 0, filesTouched: 0, patternHits: 0 });
|
|
1251
|
+
const entries = await fileCache.walkDir(rootDir);
|
|
1252
|
+
const projectPrefix = project.path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1253
|
+
const projectEntries = entries.filter((e) => e.isFile && e.relPath.replace(/\\/g, "/").startsWith(projectPrefix));
|
|
1254
|
+
const codeEntries = projectEntries.filter((e) => /\.(ts|tsx|js|jsx|vue|mjs|cjs|json)$/.test(e.name));
|
|
1255
|
+
for (const file of codeEntries) {
|
|
1256
|
+
let content = "";
|
|
1257
|
+
try {
|
|
1258
|
+
content = await fileCache.readTextFile(path3.join(rootDir, file.relPath));
|
|
1259
|
+
} catch {
|
|
1260
|
+
continue;
|
|
1261
|
+
}
|
|
1262
|
+
for (const pkg of candidatePackages) {
|
|
1263
|
+
const current = index.get(pkg);
|
|
1264
|
+
if (!current) continue;
|
|
1265
|
+
const escaped = pkg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1266
|
+
const importRegex = new RegExp(`(?:from\\s+['"]${escaped}['"]|require\\(\\s*['"]${escaped}['"]\\s*\\)|import\\(\\s*['"]${escaped}['"]\\s*\\))`, "g");
|
|
1267
|
+
const importMatches = content.match(importRegex) ?? [];
|
|
1268
|
+
if (importMatches.length > 0) {
|
|
1269
|
+
current.importSites += importMatches.length;
|
|
1270
|
+
current.filesTouched += 1;
|
|
1271
|
+
}
|
|
1272
|
+
const playbook = PACKAGE_PLAYBOOKS[pkg];
|
|
1273
|
+
if (playbook) {
|
|
1274
|
+
for (const pattern of playbook.usagePatterns) {
|
|
1275
|
+
const matches = content.match(new RegExp(pattern.source, "g"));
|
|
1276
|
+
if (matches) current.patternHits += matches.length;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
return index;
|
|
1282
|
+
}
|
|
1283
|
+
async function scanBreakingChangeExposure(projects, rootDir, fileCache) {
|
|
1284
|
+
const deprecated = /* @__PURE__ */ new Set();
|
|
1285
|
+
const legacyPolyfills = /* @__PURE__ */ new Set();
|
|
1286
|
+
let peerConflictsDetected = false;
|
|
1287
|
+
for (const project of projects) {
|
|
1288
|
+
for (const dep of project.dependencies) {
|
|
1289
|
+
if (DEPRECATED_PACKAGES2.has(dep.package)) deprecated.add(dep.package);
|
|
1290
|
+
if (LEGACY_POLYFILLS.has(dep.package)) legacyPolyfills.add(dep.package);
|
|
1291
|
+
if (dep.section === "peerDependencies" && dep.majorsBehind !== null && dep.majorsBehind >= 2) peerConflictsDetected = true;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
let score = 0;
|
|
1295
|
+
score += Math.min(deprecated.size * 10, 40);
|
|
1296
|
+
score += Math.min(legacyPolyfills.size * 5, 30);
|
|
1297
|
+
score += peerConflictsDetected ? 20 : 0;
|
|
1298
|
+
score = Math.min(score, 100);
|
|
1299
|
+
const projectIntelligence = [];
|
|
1300
|
+
const solutionRollup = /* @__PURE__ */ new Map();
|
|
1301
|
+
let majorPackageCount = 0;
|
|
1302
|
+
let manualHotspots = 0;
|
|
1303
|
+
let codemodCandidates = 0;
|
|
1304
|
+
for (const project of projects) {
|
|
1305
|
+
const majorDeps = project.dependencies.filter((d) => (d.majorsBehind ?? 0) >= 1 && d.latestStable);
|
|
1306
|
+
if (majorDeps.length === 0) {
|
|
1307
|
+
projectIntelligence.push({
|
|
1308
|
+
project: project.name,
|
|
1309
|
+
projectPath: project.path,
|
|
1310
|
+
packages: [],
|
|
1311
|
+
recommendation: "do-nothing"
|
|
1312
|
+
});
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
const usageIndex = await buildProjectUsageIndex(project, rootDir, fileCache, majorDeps.map((d) => d.package));
|
|
1316
|
+
const packages = majorDeps.map((dep) => {
|
|
1317
|
+
const currentVersion = resolveCurrentVersion(dep);
|
|
1318
|
+
const targetVersion = dep.latestStable;
|
|
1319
|
+
const currentMajor = normalizeMajor(currentVersion);
|
|
1320
|
+
const targetMajor = normalizeMajor(targetVersion);
|
|
1321
|
+
const interimMajors = currentMajor !== null && targetMajor !== null ? listInterimMajorTargets(currentMajor, targetMajor) : [];
|
|
1322
|
+
const usage = usageIndex.get(dep.package) ?? { importSites: 0, filesTouched: 0, patternHits: 0 };
|
|
1323
|
+
const fileCount = Math.max(1, project.fileCount ?? 1);
|
|
1324
|
+
const touchedPercent = Math.min(100, Math.round((usage.filesTouched + usage.patternHits) / fileCount * 100));
|
|
1325
|
+
const playbook = PACKAGE_PLAYBOOKS[dep.package];
|
|
1326
|
+
const impactedFeatures = playbook?.impactedFeatures ?? ["Public API usage patterns", "Configuration surface", "Runtime compatibility expectations"];
|
|
1327
|
+
const automatable = playbook?.automation ?? (usage.patternHits > 0 ? "manual" : "deterministic-recipe");
|
|
1328
|
+
majorPackageCount++;
|
|
1329
|
+
if (automatable === "manual") manualHotspots++;
|
|
1330
|
+
if (automatable === "codemod-available") codemodCandidates++;
|
|
1331
|
+
return {
|
|
1332
|
+
package: dep.package,
|
|
1333
|
+
currentVersion,
|
|
1334
|
+
targetVersion,
|
|
1335
|
+
majorJumpCount: dep.majorsBehind ?? 0,
|
|
1336
|
+
interimMajors,
|
|
1337
|
+
releaseNoteSources: ["GitHub Releases", "Repository tags", "CHANGELOG.md"],
|
|
1338
|
+
parsedSignals: [...BREAKING_SIGNAL_PHRASES],
|
|
1339
|
+
impactedFeatures,
|
|
1340
|
+
usage: {
|
|
1341
|
+
importSites: usage.importSites,
|
|
1342
|
+
filesTouchedEstimate: usage.filesTouched,
|
|
1343
|
+
functionsTouchedEstimate: usage.patternHits,
|
|
1344
|
+
touchedPercent
|
|
1345
|
+
},
|
|
1346
|
+
automatable,
|
|
1347
|
+
codemod: playbook?.codemod
|
|
1348
|
+
};
|
|
1349
|
+
});
|
|
1350
|
+
const rec = detectDecision(packages.length, packages.filter((p) => p.automatable === "manual").length, packages.filter((p) => p.automatable === "codemod-available").length);
|
|
1351
|
+
projectIntelligence.push({
|
|
1352
|
+
project: project.name,
|
|
1353
|
+
projectPath: project.path,
|
|
1354
|
+
packages,
|
|
1355
|
+
recommendation: rec
|
|
1356
|
+
});
|
|
1357
|
+
if (project.solutionId) {
|
|
1358
|
+
const key = project.solutionId;
|
|
1359
|
+
const existing = solutionRollup.get(key) ?? {
|
|
1360
|
+
solutionId: key,
|
|
1361
|
+
solutionName: project.solutionName ?? key,
|
|
1362
|
+
projectCount: 0,
|
|
1363
|
+
majorPackages: 0,
|
|
1364
|
+
recommendation: "do-nothing"
|
|
1365
|
+
};
|
|
1366
|
+
existing.projectCount += 1;
|
|
1367
|
+
existing.majorPackages += packages.length;
|
|
1368
|
+
existing.recommendation = detectDecision(existing.majorPackages, manualHotspots, codemodCandidates);
|
|
1369
|
+
solutionRollup.set(key, existing);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
const overallRecommendation = detectDecision(majorPackageCount, manualHotspots, codemodCandidates);
|
|
1373
|
+
return {
|
|
1374
|
+
deprecatedPackages: [...deprecated].sort(),
|
|
1375
|
+
legacyPolyfills: [...legacyPolyfills].sort(),
|
|
1376
|
+
peerConflictsDetected,
|
|
1377
|
+
exposureScore: score,
|
|
1378
|
+
projectIntelligence,
|
|
1379
|
+
solutionIntelligence: [...solutionRollup.values()],
|
|
1380
|
+
overallRecommendation
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
1384
|
+
"node_modules",
|
|
1385
|
+
".git",
|
|
1386
|
+
".wrangler",
|
|
1387
|
+
".next",
|
|
1388
|
+
"dist",
|
|
1389
|
+
"build",
|
|
1390
|
+
"out",
|
|
1391
|
+
".turbo",
|
|
1392
|
+
".cache",
|
|
1393
|
+
"coverage",
|
|
1394
|
+
"bin",
|
|
1395
|
+
"obj",
|
|
1396
|
+
".vs",
|
|
1397
|
+
"TestResults",
|
|
1398
|
+
".nuxt",
|
|
1399
|
+
".output",
|
|
1400
|
+
".svelte-kit"
|
|
1401
|
+
]);
|
|
1402
|
+
var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1403
|
+
".map",
|
|
1404
|
+
".lock",
|
|
1405
|
+
".png",
|
|
1406
|
+
".jpg",
|
|
1407
|
+
".jpeg",
|
|
1408
|
+
".gif",
|
|
1409
|
+
".ico",
|
|
1410
|
+
".svg",
|
|
1411
|
+
".woff",
|
|
1412
|
+
".woff2",
|
|
1413
|
+
".ttf",
|
|
1414
|
+
".otf",
|
|
1415
|
+
".eot",
|
|
1416
|
+
".mp4",
|
|
1417
|
+
".webm"
|
|
1418
|
+
]);
|
|
1419
|
+
async function scanFileHotspots(rootDir, cache) {
|
|
1420
|
+
const extensionCounts = {};
|
|
1421
|
+
const allFiles = [];
|
|
1422
|
+
let maxDepth = 0;
|
|
1423
|
+
if (cache) {
|
|
1424
|
+
const entries = await cache.walkDir(rootDir);
|
|
1425
|
+
for (const entry of entries) {
|
|
1426
|
+
if (!entry.isFile) continue;
|
|
1427
|
+
const ext = path3.extname(entry.name).toLowerCase();
|
|
1428
|
+
if (SKIP_EXTENSIONS.has(ext)) continue;
|
|
1429
|
+
const depth = entry.relPath.split(path3.sep).length - 1;
|
|
1430
|
+
if (depth > maxDepth) maxDepth = depth;
|
|
1431
|
+
extensionCounts[ext] = (extensionCounts[ext] ?? 0) + 1;
|
|
1432
|
+
try {
|
|
1433
|
+
const stat2 = await fs.stat(entry.absPath);
|
|
1434
|
+
allFiles.push({
|
|
1435
|
+
path: entry.relPath,
|
|
1436
|
+
bytes: stat2.size
|
|
1437
|
+
});
|
|
1438
|
+
} catch {
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
} else {
|
|
1442
|
+
async function walk(dir, depth) {
|
|
1443
|
+
if (depth > maxDepth) maxDepth = depth;
|
|
1444
|
+
let entries;
|
|
1445
|
+
try {
|
|
1446
|
+
const dirents = await fs.readdir(dir, { withFileTypes: true });
|
|
1447
|
+
entries = dirents.map((d) => ({
|
|
1448
|
+
name: d.name,
|
|
1449
|
+
isDirectory: d.isDirectory(),
|
|
1450
|
+
isFile: d.isFile()
|
|
1451
|
+
}));
|
|
1452
|
+
} catch {
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
for (const e of entries) {
|
|
1456
|
+
if (e.isDirectory) {
|
|
1457
|
+
if (SKIP_DIRS.has(e.name)) continue;
|
|
1458
|
+
await walk(path3.join(dir, e.name), depth + 1);
|
|
1459
|
+
} else if (e.isFile) {
|
|
1460
|
+
const ext = path3.extname(e.name).toLowerCase();
|
|
1461
|
+
if (SKIP_EXTENSIONS.has(ext)) continue;
|
|
1462
|
+
extensionCounts[ext] = (extensionCounts[ext] ?? 0) + 1;
|
|
1463
|
+
try {
|
|
1464
|
+
const stat2 = await fs.stat(path3.join(dir, e.name));
|
|
1465
|
+
allFiles.push({
|
|
1466
|
+
path: path3.relative(rootDir, path3.join(dir, e.name)),
|
|
1467
|
+
bytes: stat2.size
|
|
1468
|
+
});
|
|
1469
|
+
} catch {
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
await walk(rootDir, 0);
|
|
1475
|
+
}
|
|
1476
|
+
allFiles.sort((a, b) => b.bytes - a.bytes);
|
|
1477
|
+
const largestFiles = allFiles.slice(0, 20);
|
|
1478
|
+
return {
|
|
1479
|
+
fileCountByExtension: extensionCounts,
|
|
1480
|
+
largestFiles,
|
|
1481
|
+
totalFiles: allFiles.length,
|
|
1482
|
+
maxDirectoryDepth: maxDepth,
|
|
1483
|
+
mostUsedPackages: []
|
|
1484
|
+
// Filled in by caller with project data
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
var LOCKFILES = {
|
|
1488
|
+
"pnpm-lock.yaml": "pnpm",
|
|
1489
|
+
"package-lock.json": "npm",
|
|
1490
|
+
"yarn.lock": "yarn",
|
|
1491
|
+
"bun.lockb": "bun",
|
|
1492
|
+
"packages.lock.json": "nuget"
|
|
1493
|
+
};
|
|
1494
|
+
async function scanSecurityPosture(rootDir, cache) {
|
|
1495
|
+
const result = {
|
|
1496
|
+
lockfilePresent: false,
|
|
1497
|
+
multipleLockfileTypes: false,
|
|
1498
|
+
gitignoreCoversEnv: false,
|
|
1499
|
+
gitignoreCoversNodeModules: false,
|
|
1500
|
+
envFilesTracked: false,
|
|
1501
|
+
lockfileTypes: []
|
|
1502
|
+
};
|
|
1503
|
+
const _pathExists = cache ? (p) => cache.pathExists(p) : pathExists;
|
|
1504
|
+
const _readTextFile = cache ? (p) => cache.readTextFile(p) : readTextFile;
|
|
1505
|
+
const foundLockfiles = [];
|
|
1506
|
+
for (const [file, type] of Object.entries(LOCKFILES)) {
|
|
1507
|
+
if (await _pathExists(path3.join(rootDir, file))) {
|
|
1508
|
+
foundLockfiles.push(type);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
result.lockfilePresent = foundLockfiles.length > 0;
|
|
1512
|
+
result.multipleLockfileTypes = foundLockfiles.length > 1;
|
|
1513
|
+
result.lockfileTypes = foundLockfiles.sort();
|
|
1514
|
+
const gitignorePath = path3.join(rootDir, ".gitignore");
|
|
1515
|
+
if (await _pathExists(gitignorePath)) {
|
|
1516
|
+
try {
|
|
1517
|
+
const content = await _readTextFile(gitignorePath);
|
|
1518
|
+
const lines = content.split("\n").map((l) => l.trim());
|
|
1519
|
+
result.gitignoreCoversEnv = lines.some(
|
|
1520
|
+
(line) => line === ".env" || line === ".env*" || line === ".env.*" || line === ".env.local" || line === "*.env"
|
|
1521
|
+
);
|
|
1522
|
+
result.gitignoreCoversNodeModules = lines.some(
|
|
1523
|
+
(line) => line === "node_modules" || line === "node_modules/" || line === "/node_modules"
|
|
1524
|
+
);
|
|
1525
|
+
} catch {
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
for (const envFile of [".env", ".env.local", ".env.development", ".env.production"]) {
|
|
1529
|
+
if (await _pathExists(path3.join(rootDir, envFile))) {
|
|
1530
|
+
if (!result.gitignoreCoversEnv) {
|
|
1531
|
+
result.envFilesTracked = true;
|
|
1532
|
+
break;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
return result;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// src/reporting/scanners/service-dependencies.ts
|
|
1540
|
+
var SERVICE_CATEGORIES = {
|
|
1541
|
+
payment: {
|
|
1542
|
+
"stripe": "Stripe",
|
|
1543
|
+
"@stripe/stripe-js": "Stripe.js",
|
|
1544
|
+
"@stripe/react-stripe-js": "Stripe React",
|
|
1545
|
+
"braintree": "Braintree",
|
|
1546
|
+
"braintree-web": "Braintree Web",
|
|
1547
|
+
"@paypal/checkout-server-sdk": "PayPal",
|
|
1548
|
+
"@paypal/react-paypal-js": "PayPal React",
|
|
1549
|
+
"@paypal/paypal-js": "PayPal.js",
|
|
1550
|
+
"square": "Square",
|
|
1551
|
+
"razorpay": "Razorpay",
|
|
1552
|
+
"adyen-api-library": "Adyen",
|
|
1553
|
+
"@adyen/api-library": "Adyen",
|
|
1554
|
+
"@adyen/adyen-web": "Adyen Web",
|
|
1555
|
+
"mollie-api-node": "Mollie",
|
|
1556
|
+
"@lemonsqueezy/lemonsqueezy.js": "Lemon Squeezy",
|
|
1557
|
+
"paddle-sdk": "Paddle",
|
|
1558
|
+
"@paddle/paddle-node-sdk": "Paddle",
|
|
1559
|
+
"coinbase-commerce-node": "Coinbase Commerce",
|
|
1560
|
+
"gocardless-nodejs": "GoCardless",
|
|
1561
|
+
"klarna-checkout": "Klarna",
|
|
1562
|
+
"@recurly/recurly-js": "Recurly",
|
|
1563
|
+
"chargebee": "Chargebee"
|
|
1564
|
+
},
|
|
1565
|
+
auth: {
|
|
1566
|
+
"@auth0/auth0-spa-js": "Auth0 SPA",
|
|
1567
|
+
"@auth0/auth0-react": "Auth0 React",
|
|
1568
|
+
"@auth0/nextjs-auth0": "Auth0 Next.js",
|
|
1569
|
+
"auth0": "Auth0",
|
|
1570
|
+
"@clerk/clerk-js": "Clerk.js",
|
|
1571
|
+
"@clerk/clerk-sdk-node": "Clerk (Node)",
|
|
1572
|
+
"@clerk/nextjs": "Clerk (Next.js)",
|
|
1573
|
+
"@clerk/react-router": "Clerk (React Router)",
|
|
1574
|
+
"@clerk/tanstack-react-start": "Clerk (TanStack Start)",
|
|
1575
|
+
"@clerk/expo": "Clerk (Expo)",
|
|
1576
|
+
"@clerk/astro": "Clerk (Astro)",
|
|
1577
|
+
"@clerk/chrome-extension": "Clerk (Chrome Extension)",
|
|
1578
|
+
"@clerk/express": "Clerk (Express)",
|
|
1579
|
+
"@clerk/fastify": "Clerk (Fastify)",
|
|
1580
|
+
"@clerk/hono": "Clerk (Hono)",
|
|
1581
|
+
"@clerk/nuxt": "Clerk (Nuxt)",
|
|
1582
|
+
"@clerk/react": "Clerk (React)",
|
|
1583
|
+
"@clerk/vue": "Clerk (Vue)",
|
|
1584
|
+
"@clerk/clerk-react": "Clerk (React)",
|
|
1585
|
+
"firebase-admin": "Firebase Admin",
|
|
1586
|
+
"firebase": "Firebase",
|
|
1587
|
+
"@firebase/auth": "Firebase Auth",
|
|
1588
|
+
"passport": "Passport.js",
|
|
1589
|
+
"passport-local": "Passport Local",
|
|
1590
|
+
"passport-google-oauth20": "Passport Google",
|
|
1591
|
+
"passport-github2": "Passport GitHub",
|
|
1592
|
+
"next-auth": "NextAuth",
|
|
1593
|
+
"@auth/core": "Auth.js",
|
|
1594
|
+
"@supabase/supabase-js": "Supabase",
|
|
1595
|
+
"@supabase/auth-helpers-nextjs": "Supabase Auth (Next.js)",
|
|
1596
|
+
"@supabase/ssr": "Supabase SSR",
|
|
1597
|
+
"jsonwebtoken": "JWT",
|
|
1598
|
+
"jose": "JOSE",
|
|
1599
|
+
"@okta/okta-auth-js": "Okta",
|
|
1600
|
+
"@okta/okta-react": "Okta React",
|
|
1601
|
+
"oidc-client-ts": "OIDC Client",
|
|
1602
|
+
"@casl/ability": "CASL",
|
|
1603
|
+
"casbin": "Casbin",
|
|
1604
|
+
"keycloak-js": "Keycloak",
|
|
1605
|
+
"@keycloak/keycloak-admin-client": "Keycloak Admin",
|
|
1606
|
+
"lucia": "Lucia Auth",
|
|
1607
|
+
"@lucia-auth/adapter-prisma": "Lucia (Prisma)",
|
|
1608
|
+
"arctic": "Arctic (OAuth)",
|
|
1609
|
+
"better-auth": "Better Auth",
|
|
1610
|
+
"grant": "Grant (OAuth)",
|
|
1611
|
+
"@workos-inc/node": "WorkOS",
|
|
1612
|
+
"stytch": "Stytch",
|
|
1613
|
+
"frontegg": "Frontegg",
|
|
1614
|
+
"@descope/node-sdk": "Descope",
|
|
1615
|
+
"kinde-auth-nextjs": "Kinde"
|
|
1616
|
+
},
|
|
1617
|
+
email: {
|
|
1618
|
+
"@sendgrid/mail": "SendGrid",
|
|
1619
|
+
"@sendgrid/client": "SendGrid Client",
|
|
1620
|
+
"nodemailer": "Nodemailer",
|
|
1621
|
+
"twilio": "Twilio",
|
|
1622
|
+
"@aws-sdk/client-ses": "AWS SES",
|
|
1623
|
+
"@aws-sdk/client-sesv2": "AWS SES v2",
|
|
1624
|
+
"postmark": "Postmark",
|
|
1625
|
+
"mailgun.js": "Mailgun",
|
|
1626
|
+
"resend": "Resend",
|
|
1627
|
+
"@react-email/components": "React Email",
|
|
1628
|
+
"react-email": "React Email",
|
|
1629
|
+
"mjml": "MJML",
|
|
1630
|
+
"email-templates": "Email Templates",
|
|
1631
|
+
"@mailchimp/mailchimp_marketing": "Mailchimp",
|
|
1632
|
+
"@mailchimp/mailchimp_transactional": "Mailchimp Transactional",
|
|
1633
|
+
"plunk-node": "Plunk",
|
|
1634
|
+
"brevo": "Brevo (Sendinblue)",
|
|
1635
|
+
"sib-api-v3-sdk": "Brevo v3",
|
|
1636
|
+
"@customer.io/node": "Customer.io",
|
|
1637
|
+
"sparkpost": "SparkPost",
|
|
1638
|
+
"mandrill-api": "Mandrill",
|
|
1639
|
+
"mail-listener5": "Mail Listener",
|
|
1640
|
+
"@azure/communication-email": "Azure Email"
|
|
1641
|
+
},
|
|
1642
|
+
cloud: {
|
|
1643
|
+
// AWS
|
|
1644
|
+
"aws-sdk": "AWS SDK v2",
|
|
1645
|
+
"@aws-sdk/client-s3": "AWS S3",
|
|
1646
|
+
"@aws-sdk/client-lambda": "AWS Lambda",
|
|
1647
|
+
"@aws-sdk/client-dynamodb": "AWS DynamoDB",
|
|
1648
|
+
"@aws-sdk/client-ec2": "AWS EC2",
|
|
1649
|
+
"@aws-sdk/client-ecs": "AWS ECS",
|
|
1650
|
+
"@aws-sdk/client-ecr": "AWS ECR",
|
|
1651
|
+
"@aws-sdk/client-cloudformation": "AWS CloudFormation",
|
|
1652
|
+
"@aws-sdk/client-cloudwatch": "AWS CloudWatch",
|
|
1653
|
+
"@aws-sdk/client-cloudfront": "AWS CloudFront",
|
|
1654
|
+
"@aws-sdk/client-iam": "AWS IAM",
|
|
1655
|
+
"@aws-sdk/client-secrets-manager": "AWS Secrets Manager",
|
|
1656
|
+
"@aws-sdk/client-ssm": "AWS SSM",
|
|
1657
|
+
"@aws-sdk/client-sts": "AWS STS",
|
|
1658
|
+
"@aws-sdk/client-cognito-identity-provider": "AWS Cognito",
|
|
1659
|
+
"@aws-sdk/client-route-53": "AWS Route 53",
|
|
1660
|
+
"@aws-sdk/client-step-functions": "AWS Step Functions",
|
|
1661
|
+
"@aws-sdk/client-eventbridge": "AWS EventBridge",
|
|
1662
|
+
"@aws-sdk/client-kinesis": "AWS Kinesis",
|
|
1663
|
+
"@aws-sdk/client-kms": "AWS KMS",
|
|
1664
|
+
"@aws-sdk/client-athena": "AWS Athena",
|
|
1665
|
+
"@aws-sdk/client-bedrock-runtime": "AWS Bedrock",
|
|
1666
|
+
"@aws-sdk/lib-dynamodb": "AWS DynamoDB Doc",
|
|
1667
|
+
// Azure
|
|
1668
|
+
"@azure/storage-blob": "Azure Blob",
|
|
1669
|
+
"@azure/identity": "Azure Identity",
|
|
1670
|
+
"@azure/cosmos": "Azure Cosmos DB",
|
|
1671
|
+
"@azure/keyvault-secrets": "Azure Key Vault",
|
|
1672
|
+
"@azure/service-bus": "Azure Service Bus",
|
|
1673
|
+
"@azure/event-hubs": "Azure Event Hubs",
|
|
1674
|
+
"@azure/functions": "Azure Functions",
|
|
1675
|
+
"@azure/ai-form-recognizer": "Azure Form Recognizer",
|
|
1676
|
+
"@azure/openai": "Azure OpenAI",
|
|
1677
|
+
"@azure/app-configuration": "Azure App Config",
|
|
1678
|
+
"@azure/monitor-opentelemetry": "Azure Monitor OTel",
|
|
1679
|
+
"@azure/container-registry": "Azure Container Registry",
|
|
1680
|
+
// GCP
|
|
1681
|
+
"@google-cloud/storage": "GCP Storage",
|
|
1682
|
+
"@google-cloud/functions-framework": "GCP Functions",
|
|
1683
|
+
"@google-cloud/bigquery": "GCP BigQuery",
|
|
1684
|
+
"@google-cloud/firestore": "GCP Firestore",
|
|
1685
|
+
"@google-cloud/pubsub": "GCP Pub/Sub",
|
|
1686
|
+
"@google-cloud/secret-manager": "GCP Secret Manager",
|
|
1687
|
+
"@google-cloud/tasks": "GCP Cloud Tasks",
|
|
1688
|
+
"@google-cloud/run": "GCP Cloud Run",
|
|
1689
|
+
"@google-cloud/logging": "GCP Logging",
|
|
1690
|
+
"@google-cloud/translate": "GCP Translate",
|
|
1691
|
+
"@google-cloud/vertexai": "GCP Vertex AI",
|
|
1692
|
+
// Multi-cloud / PaaS
|
|
1693
|
+
"@vercel/sdk": "Vercel",
|
|
1694
|
+
"@vercel/kv": "Vercel KV",
|
|
1695
|
+
"@vercel/blob": "Vercel Blob",
|
|
1696
|
+
"@vercel/postgres": "Vercel Postgres",
|
|
1697
|
+
"@vercel/edge-config": "Vercel Edge Config",
|
|
1698
|
+
"@netlify/functions": "Netlify Functions",
|
|
1699
|
+
"@cloudflare/workers-types": "Cloudflare Workers",
|
|
1700
|
+
"wrangler": "Wrangler",
|
|
1701
|
+
"@railway/cli": "Railway",
|
|
1702
|
+
"@fly/apps": "Fly.io",
|
|
1703
|
+
"heroku-client": "Heroku",
|
|
1704
|
+
"@pulumi/pulumi": "Pulumi",
|
|
1705
|
+
"@pulumi/aws": "Pulumi AWS",
|
|
1706
|
+
"cdktf": "CDK for Terraform",
|
|
1707
|
+
"aws-cdk-lib": "AWS CDK",
|
|
1708
|
+
"sst": "SST"
|
|
1709
|
+
},
|
|
1710
|
+
databases: {
|
|
1711
|
+
"pg": "PostgreSQL",
|
|
1712
|
+
"postgres": "Postgres.js",
|
|
1713
|
+
"mysql2": "MySQL",
|
|
1714
|
+
"mysql": "MySQL (legacy)",
|
|
1715
|
+
"mongodb": "MongoDB",
|
|
1716
|
+
"ioredis": "Redis (ioredis)",
|
|
1717
|
+
"redis": "Redis",
|
|
1718
|
+
"better-sqlite3": "SQLite",
|
|
1719
|
+
"sql.js": "sql.js",
|
|
1720
|
+
"@prisma/client": "Prisma",
|
|
1721
|
+
"drizzle-orm": "Drizzle",
|
|
1722
|
+
"typeorm": "TypeORM",
|
|
1723
|
+
"sequelize": "Sequelize",
|
|
1724
|
+
"knex": "Knex",
|
|
1725
|
+
"kysely": "Kysely",
|
|
1726
|
+
"objection": "Objection.js",
|
|
1727
|
+
"slonik": "Slonik",
|
|
1728
|
+
"massive": "Massive.js",
|
|
1729
|
+
"mongoose": "Mongoose",
|
|
1730
|
+
"@mikro-orm/core": "MikroORM",
|
|
1731
|
+
"mssql": "SQL Server",
|
|
1732
|
+
"oracledb": "Oracle",
|
|
1733
|
+
"cassandra-driver": "Cassandra",
|
|
1734
|
+
"neo4j-driver": "Neo4j",
|
|
1735
|
+
"arangojs": "ArangoDB",
|
|
1736
|
+
"@clickhouse/client": "ClickHouse",
|
|
1737
|
+
"influx": "InfluxDB",
|
|
1738
|
+
"@libsql/client": "libSQL (Turso)",
|
|
1739
|
+
"@neondatabase/serverless": "Neon",
|
|
1740
|
+
"@planetscale/database": "PlanetScale",
|
|
1741
|
+
"@electric-sql/pglite": "PGlite",
|
|
1742
|
+
"@upstash/redis": "Upstash Redis",
|
|
1743
|
+
"@upstash/kafka": "Upstash Kafka",
|
|
1744
|
+
"@upstash/qstash": "Upstash QStash",
|
|
1745
|
+
"@upstash/vector": "Upstash Vector",
|
|
1746
|
+
"dynamoose": "Dynamoose",
|
|
1747
|
+
"fauna": "Fauna",
|
|
1748
|
+
"faunadb": "FaunaDB",
|
|
1749
|
+
"@supabase/postgrest-js": "Supabase PostgREST",
|
|
1750
|
+
"couchbase": "Couchbase",
|
|
1751
|
+
"couchdb-nano": "CouchDB",
|
|
1752
|
+
"duckdb": "DuckDB",
|
|
1753
|
+
"@tidbcloud/serverless": "TiDB Serverless"
|
|
1754
|
+
},
|
|
1755
|
+
messaging: {
|
|
1756
|
+
"@aws-sdk/client-sqs": "AWS SQS",
|
|
1757
|
+
"@aws-sdk/client-sns": "AWS SNS",
|
|
1758
|
+
"amqplib": "RabbitMQ",
|
|
1759
|
+
"kafkajs": "Kafka",
|
|
1760
|
+
"bullmq": "BullMQ",
|
|
1761
|
+
"bull": "Bull",
|
|
1762
|
+
"@google-cloud/pubsub": "GCP Pub/Sub",
|
|
1763
|
+
"bee-queue": "Bee Queue",
|
|
1764
|
+
"pg-boss": "pg-boss",
|
|
1765
|
+
"agenda": "Agenda",
|
|
1766
|
+
"@azure/service-bus": "Azure Service Bus",
|
|
1767
|
+
"@azure/event-hubs": "Azure Event Hubs",
|
|
1768
|
+
"nats": "NATS",
|
|
1769
|
+
"mqtt": "MQTT",
|
|
1770
|
+
"zeromq": "ZeroMQ",
|
|
1771
|
+
"@temporalio/client": "Temporal Client",
|
|
1772
|
+
"@temporalio/worker": "Temporal Worker",
|
|
1773
|
+
"inngest": "Inngest",
|
|
1774
|
+
"@trigger.dev/sdk": "Trigger.dev",
|
|
1775
|
+
"@defer/client": "Defer",
|
|
1776
|
+
"graphile-worker": "Graphile Worker",
|
|
1777
|
+
"@quirrel/quirrel": "Quirrel",
|
|
1778
|
+
"rhea": "AMQP 1.0 (rhea)",
|
|
1779
|
+
"stompit": "STOMP",
|
|
1780
|
+
"@eventstore/db-client": "EventStoreDB",
|
|
1781
|
+
"@aws-sdk/client-eventbridge": "AWS EventBridge",
|
|
1782
|
+
"@aws-sdk/client-kinesis": "AWS Kinesis",
|
|
1783
|
+
"@aws-sdk/client-firehose": "AWS Firehose"
|
|
1784
|
+
},
|
|
1785
|
+
observability: {
|
|
1786
|
+
"@sentry/node": "Sentry (Node)",
|
|
1787
|
+
"@sentry/browser": "Sentry (Browser)",
|
|
1788
|
+
"@sentry/react": "Sentry (React)",
|
|
1789
|
+
"@sentry/nextjs": "Sentry (Next.js)",
|
|
1790
|
+
"@sentry/vue": "Sentry (Vue)",
|
|
1791
|
+
"@sentry/angular": "Sentry (Angular)",
|
|
1792
|
+
"@opentelemetry/api": "OpenTelemetry API",
|
|
1793
|
+
"@opentelemetry/sdk-node": "OpenTelemetry SDK",
|
|
1794
|
+
"@opentelemetry/auto-instrumentations-node": "OpenTelemetry Auto",
|
|
1795
|
+
"applicationinsights": "App Insights",
|
|
1796
|
+
"dd-trace": "Datadog",
|
|
1797
|
+
"@datadog/browser-rum": "Datadog RUM",
|
|
1798
|
+
"newrelic": "New Relic",
|
|
1799
|
+
"@newrelic/browser-agent": "New Relic Browser",
|
|
1800
|
+
"pino": "Pino",
|
|
1801
|
+
"winston": "Winston",
|
|
1802
|
+
"bunyan": "Bunyan",
|
|
1803
|
+
"consola": "Consola",
|
|
1804
|
+
"loglevel": "loglevel",
|
|
1805
|
+
"roarr": "Roarr",
|
|
1806
|
+
"prom-client": "Prometheus",
|
|
1807
|
+
"elastic-apm-node": "Elastic APM",
|
|
1808
|
+
"@logtail/node": "Logtail",
|
|
1809
|
+
"@axiomhq/pino": "Axiom (Pino)",
|
|
1810
|
+
"@axiomhq/winston": "Axiom (Winston)",
|
|
1811
|
+
"@baselime/node-opentelemetry": "Baselime",
|
|
1812
|
+
"highlight.run": "Highlight",
|
|
1813
|
+
"@google-cloud/trace-agent": "GCP Trace",
|
|
1814
|
+
"@google-cloud/logging": "GCP Logging",
|
|
1815
|
+
"@azure/monitor-opentelemetry": "Azure Monitor",
|
|
1816
|
+
"lightstep-tracer": "Lightstep",
|
|
1817
|
+
"honeycomb-beeline": "Honeycomb",
|
|
1818
|
+
"@honeycombio/opentelemetry-node": "Honeycomb OTel",
|
|
1819
|
+
"posthog-node": "PostHog",
|
|
1820
|
+
"@amplitude/node": "Amplitude",
|
|
1821
|
+
"mixpanel": "Mixpanel",
|
|
1822
|
+
"@segment/analytics-node": "Segment",
|
|
1823
|
+
"analytics-node": "Segment (legacy)",
|
|
1824
|
+
"@rudderstack/rudder-sdk-node": "RudderStack",
|
|
1825
|
+
"heap-api": "Heap",
|
|
1826
|
+
"@fullstory/browser": "FullStory",
|
|
1827
|
+
"@logrocket/react": "LogRocket",
|
|
1828
|
+
"logrocket": "LogRocket",
|
|
1829
|
+
"hotjar-js": "Hotjar",
|
|
1830
|
+
"@clarity-ai/clarity": "Microsoft Clarity"
|
|
1831
|
+
},
|
|
1832
|
+
crm: {
|
|
1833
|
+
"xero-node": "Xero",
|
|
1834
|
+
"hubspot-api-client": "HubSpot",
|
|
1835
|
+
"@hubspot/api-client": "HubSpot",
|
|
1836
|
+
"@slack/web-api": "Slack Web API",
|
|
1837
|
+
"@slack/bolt": "Slack Bolt",
|
|
1838
|
+
"@slack/events-api": "Slack Events",
|
|
1839
|
+
"discord.js": "Discord",
|
|
1840
|
+
"intercom-client": "Intercom",
|
|
1841
|
+
"salesforce-sdk": "Salesforce",
|
|
1842
|
+
"jsforce": "JSforce (Salesforce)",
|
|
1843
|
+
"@zendesk/sell-client": "Zendesk",
|
|
1844
|
+
"node-zendesk": "Zendesk",
|
|
1845
|
+
"freshdesk-api": "Freshdesk",
|
|
1846
|
+
"pipedrive": "Pipedrive",
|
|
1847
|
+
"airtable": "Airtable",
|
|
1848
|
+
"@notionhq/client": "Notion",
|
|
1849
|
+
"@linear/sdk": "Linear",
|
|
1850
|
+
"jira-client": "Jira",
|
|
1851
|
+
"jira.js": "Jira.js",
|
|
1852
|
+
"@octokit/rest": "GitHub (Octokit)",
|
|
1853
|
+
"@octokit/core": "GitHub (Octokit Core)",
|
|
1854
|
+
"gitlab": "GitLab",
|
|
1855
|
+
"@gitbeaker/rest": "GitLab (Gitbeaker)",
|
|
1856
|
+
"clickup.js": "ClickUp",
|
|
1857
|
+
"asana": "Asana",
|
|
1858
|
+
"monday-sdk-js": "Monday.com",
|
|
1859
|
+
"telegram-bot-api": "Telegram",
|
|
1860
|
+
"telegraf": "Telegraf (Telegram)",
|
|
1861
|
+
"@microsoft/microsoft-graph-client": "Microsoft Graph",
|
|
1862
|
+
"googleapis": "Google APIs",
|
|
1863
|
+
"@google-cloud/aiplatform": "Google AI Platform"
|
|
1864
|
+
},
|
|
1865
|
+
storage: {
|
|
1866
|
+
"@aws-sdk/client-s3": "AWS S3",
|
|
1867
|
+
"@aws-sdk/s3-request-presigner": "AWS S3 Presigner",
|
|
1868
|
+
"@azure/storage-blob": "Azure Blob",
|
|
1869
|
+
"@azure/storage-queue": "Azure Storage Queue",
|
|
1870
|
+
"@azure/storage-file-share": "Azure File Share",
|
|
1871
|
+
"@google-cloud/storage": "GCP Storage",
|
|
1872
|
+
"minio": "MinIO",
|
|
1873
|
+
"@supabase/storage-js": "Supabase Storage",
|
|
1874
|
+
"cloudinary": "Cloudinary",
|
|
1875
|
+
"uploadthing": "UploadThing",
|
|
1876
|
+
"@uploadthing/react": "UploadThing React",
|
|
1877
|
+
"multer": "Multer",
|
|
1878
|
+
"multer-s3": "Multer S3",
|
|
1879
|
+
"formidable": "Formidable",
|
|
1880
|
+
"busboy": "Busboy",
|
|
1881
|
+
"@vercel/blob": "Vercel Blob",
|
|
1882
|
+
"imagekit": "ImageKit",
|
|
1883
|
+
"sharp": "Sharp (image)",
|
|
1884
|
+
"jimp": "Jimp (image)",
|
|
1885
|
+
"fluent-ffmpeg": "FFmpeg",
|
|
1886
|
+
"@google-cloud/vision": "GCP Vision",
|
|
1887
|
+
"@aws-sdk/client-rekognition": "AWS Rekognition",
|
|
1888
|
+
"file-type": "file-type",
|
|
1889
|
+
"@backblaze-b2/sdk": "Backblaze B2",
|
|
1890
|
+
"wasabi-sdk": "Wasabi"
|
|
1891
|
+
},
|
|
1892
|
+
search: {
|
|
1893
|
+
"@elastic/elasticsearch": "Elasticsearch",
|
|
1894
|
+
"algoliasearch": "Algolia",
|
|
1895
|
+
"@algolia/client-search": "Algolia Client",
|
|
1896
|
+
"react-instantsearch": "Algolia InstantSearch",
|
|
1897
|
+
"meilisearch": "Meilisearch",
|
|
1898
|
+
"typesense": "Typesense",
|
|
1899
|
+
"@opensearch-project/opensearch": "OpenSearch",
|
|
1900
|
+
"solr-client": "Solr",
|
|
1901
|
+
"lunr": "Lunr",
|
|
1902
|
+
"flexsearch": "FlexSearch",
|
|
1903
|
+
"fuse.js": "Fuse.js",
|
|
1904
|
+
"minisearch": "MiniSearch",
|
|
1905
|
+
"orama": "Orama",
|
|
1906
|
+
"@orama/orama": "Orama",
|
|
1907
|
+
"@trieve/trieve-ts-sdk": "Trieve",
|
|
1908
|
+
"pagefind": "Pagefind",
|
|
1909
|
+
// Vector / AI search
|
|
1910
|
+
"@pinecone-database/pinecone": "Pinecone",
|
|
1911
|
+
"chromadb": "Chroma",
|
|
1912
|
+
"@qdrant/js-client-rest": "Qdrant",
|
|
1913
|
+
"weaviate-ts-client": "Weaviate",
|
|
1914
|
+
"@zilliz/milvus2-sdk-node": "Milvus",
|
|
1915
|
+
"openai": "OpenAI",
|
|
1916
|
+
"@anthropic-ai/sdk": "Anthropic",
|
|
1917
|
+
"@langchain/core": "LangChain",
|
|
1918
|
+
"langchain": "LangChain",
|
|
1919
|
+
"@google/generative-ai": "Google Gemini",
|
|
1920
|
+
"cohere-ai": "Cohere",
|
|
1921
|
+
"replicate": "Replicate",
|
|
1922
|
+
"@huggingface/inference": "Hugging Face",
|
|
1923
|
+
"ai": "Vercel AI SDK",
|
|
1924
|
+
"@ai-sdk/openai": "AI SDK (OpenAI)"
|
|
1925
|
+
}
|
|
1926
|
+
};
|
|
1927
|
+
function scanServiceDependencies(projects) {
|
|
1928
|
+
const packageVersions = /* @__PURE__ */ new Map();
|
|
1929
|
+
for (const project of projects) {
|
|
1930
|
+
for (const dep of project.dependencies) {
|
|
1931
|
+
if (!packageVersions.has(dep.package)) {
|
|
1932
|
+
packageVersions.set(dep.package, dep.resolvedVersion);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
const result = {
|
|
1937
|
+
payment: [],
|
|
1938
|
+
auth: [],
|
|
1939
|
+
email: [],
|
|
1940
|
+
cloud: [],
|
|
1941
|
+
databases: [],
|
|
1942
|
+
messaging: [],
|
|
1943
|
+
observability: [],
|
|
1944
|
+
crm: [],
|
|
1945
|
+
storage: [],
|
|
1946
|
+
search: []
|
|
1947
|
+
};
|
|
1948
|
+
for (const [category, packages] of Object.entries(SERVICE_CATEGORIES)) {
|
|
1949
|
+
const items = [];
|
|
1950
|
+
for (const [pkg, displayName] of Object.entries(packages)) {
|
|
1951
|
+
if (packageVersions.has(pkg)) {
|
|
1952
|
+
items.push({
|
|
1953
|
+
name: displayName,
|
|
1954
|
+
package: pkg,
|
|
1955
|
+
version: packageVersions.get(pkg) ?? null
|
|
1956
|
+
});
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
items.sort((a, b) => a.name.localeCompare(b.name));
|
|
1960
|
+
result[category] = items;
|
|
1961
|
+
}
|
|
1962
|
+
return result;
|
|
1963
|
+
}
|
|
1964
|
+
var ARCHETYPE_SIGNALS = [
|
|
1965
|
+
// Meta-frameworks (highest priority — they imply routing patterns)
|
|
1966
|
+
{ packages: ["next", "@next/core"], archetype: "nextjs", weight: 10 },
|
|
1967
|
+
{ packages: ["@remix-run/react", "@remix-run/node", "@remix-run/dev"], archetype: "remix", weight: 10 },
|
|
1968
|
+
{ packages: ["@sveltejs/kit"], archetype: "sveltekit", weight: 10 },
|
|
1969
|
+
{ packages: ["nuxt"], archetype: "nuxt", weight: 10 },
|
|
1970
|
+
// Backend frameworks
|
|
1971
|
+
{ packages: ["@nestjs/core", "@nestjs/common"], archetype: "nestjs", weight: 9 },
|
|
1972
|
+
{ packages: ["fastify"], archetype: "fastify", weight: 8 },
|
|
1973
|
+
{ packages: ["hono"], archetype: "hono", weight: 8 },
|
|
1974
|
+
{ packages: ["koa"], archetype: "koa", weight: 8 },
|
|
1975
|
+
{ packages: ["express"], archetype: "express", weight: 7 },
|
|
1976
|
+
// Serverless
|
|
1977
|
+
{ packages: ["serverless", "aws-lambda", "@aws-sdk/client-lambda", "middy", "@cloudflare/workers-types"], archetype: "serverless", weight: 6 },
|
|
1978
|
+
// CLI
|
|
1979
|
+
{ packages: ["commander", "yargs", "meow", "cac", "clipanion", "oclif"], archetype: "cli", weight: 5 }
|
|
1980
|
+
];
|
|
1981
|
+
function detectArchetype(projects) {
|
|
1982
|
+
const allPackages = /* @__PURE__ */ new Set();
|
|
1983
|
+
for (const p of projects) {
|
|
1984
|
+
for (const d of p.dependencies) {
|
|
1985
|
+
allPackages.add(d.package);
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
if (projects.length > 2) {
|
|
1989
|
+
return { archetype: "monorepo", confidence: 0.8 };
|
|
1990
|
+
}
|
|
1991
|
+
let bestArchetype = "unknown";
|
|
1992
|
+
let bestScore = 0;
|
|
1993
|
+
for (const signal of ARCHETYPE_SIGNALS) {
|
|
1994
|
+
const matched = signal.packages.filter((p) => allPackages.has(p)).length;
|
|
1995
|
+
if (matched > 0) {
|
|
1996
|
+
const score = matched * signal.weight;
|
|
1997
|
+
if (score > bestScore) {
|
|
1998
|
+
bestScore = score;
|
|
1999
|
+
bestArchetype = signal.archetype;
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
if (bestArchetype === "unknown") {
|
|
2004
|
+
bestArchetype = "library";
|
|
2005
|
+
bestScore = 3;
|
|
2006
|
+
}
|
|
2007
|
+
const confidence = Math.min(bestScore / 15, 1);
|
|
2008
|
+
return { archetype: bestArchetype, confidence: Math.round(confidence * 100) / 100 };
|
|
2009
|
+
}
|
|
2010
|
+
var PATH_RULES = [
|
|
2011
|
+
// ── Testing (high precision) ──
|
|
2012
|
+
{ pattern: /\/__tests__\//, layer: "testing", confidence: 0.95, signal: "__tests__ directory" },
|
|
2013
|
+
{ pattern: /\.test\.[jt]sx?$/, layer: "testing", confidence: 0.95, signal: ".test.* file" },
|
|
2014
|
+
{ pattern: /\.spec\.[jt]sx?$/, layer: "testing", confidence: 0.95, signal: ".spec.* file" },
|
|
2015
|
+
{ pattern: /\/test\//, layer: "testing", confidence: 0.85, signal: "test/ directory" },
|
|
2016
|
+
{ pattern: /\/tests\//, layer: "testing", confidence: 0.85, signal: "tests/ directory" },
|
|
2017
|
+
{ pattern: /\/__mocks__\//, layer: "testing", confidence: 0.9, signal: "__mocks__ directory" },
|
|
2018
|
+
{ pattern: /\/fixtures\//, layer: "testing", confidence: 0.8, signal: "fixtures/ directory" },
|
|
2019
|
+
// ── Config/Infrastructure (high precision) ──
|
|
2020
|
+
{ pattern: /\/config\.[jt]sx?$/, layer: "config", confidence: 0.85, signal: "config.* file" },
|
|
2021
|
+
{ pattern: /\/config\//, layer: "config", confidence: 0.8, signal: "config/ directory" },
|
|
2022
|
+
{ pattern: /\.config\.[jt]sx?$/, layer: "config", confidence: 0.9, signal: ".config.* file" },
|
|
2023
|
+
{ pattern: /\/env\.[jt]sx?$/, layer: "config", confidence: 0.85, signal: "env.* file" },
|
|
2024
|
+
{ pattern: /\/bootstrap\.[jt]sx?$/, layer: "config", confidence: 0.85, signal: "bootstrap file" },
|
|
2025
|
+
{ pattern: /\/setup\.[jt]sx?$/, layer: "config", confidence: 0.8, signal: "setup file" },
|
|
2026
|
+
// ── Next.js (archetype-specific) ──
|
|
2027
|
+
{ pattern: /(^|\/)app\/.*\/route\.[jt]sx?$/, layer: "routing", confidence: 0.95, signal: "Next.js App Router route", archetypes: ["nextjs"] },
|
|
2028
|
+
{ pattern: /(^|\/)pages\/api\//, layer: "routing", confidence: 0.95, signal: "Next.js Pages API route", archetypes: ["nextjs"] },
|
|
2029
|
+
{ pattern: /(^|\/)app\/.*page\.[jt]sx?$/, layer: "presentation", confidence: 0.9, signal: "Next.js page component", archetypes: ["nextjs"] },
|
|
2030
|
+
{ pattern: /(^|\/)app\/.*layout\.[jt]sx?$/, layer: "presentation", confidence: 0.9, signal: "Next.js layout component", archetypes: ["nextjs"] },
|
|
2031
|
+
{ pattern: /(^|\/)app\/.*loading\.[jt]sx?$/, layer: "presentation", confidence: 0.85, signal: "Next.js loading component", archetypes: ["nextjs"] },
|
|
2032
|
+
{ pattern: /(^|\/)app\/.*error\.[jt]sx?$/, layer: "presentation", confidence: 0.85, signal: "Next.js error component", archetypes: ["nextjs"] },
|
|
2033
|
+
{ pattern: /(^|\/)middleware\.[jt]sx?$/, layer: "middleware", confidence: 0.9, signal: "Next.js middleware", archetypes: ["nextjs"] },
|
|
2034
|
+
// ── Remix (archetype-specific) ──
|
|
2035
|
+
{ pattern: /\/app\/routes\//, layer: "routing", confidence: 0.95, signal: "Remix route file", archetypes: ["remix"] },
|
|
2036
|
+
{ pattern: /\/app\/root\.[jt]sx?$/, layer: "presentation", confidence: 0.9, signal: "Remix root", archetypes: ["remix"] },
|
|
2037
|
+
// ── SvelteKit (archetype-specific) ──
|
|
2038
|
+
{ pattern: /\/src\/routes\/.*\+server\.[jt]s$/, layer: "routing", confidence: 0.95, signal: "SvelteKit API route", archetypes: ["sveltekit"] },
|
|
2039
|
+
{ pattern: /\/src\/routes\/.*\+page\.svelte$/, layer: "presentation", confidence: 0.9, signal: "SvelteKit page", archetypes: ["sveltekit"] },
|
|
2040
|
+
{ pattern: /\/src\/routes\/.*\+layout\.svelte$/, layer: "presentation", confidence: 0.9, signal: "SvelteKit layout", archetypes: ["sveltekit"] },
|
|
2041
|
+
{ pattern: /\/src\/hooks\.server\.[jt]s$/, layer: "middleware", confidence: 0.9, signal: "SvelteKit server hooks", archetypes: ["sveltekit"] },
|
|
2042
|
+
// ── Nuxt (archetype-specific) ──
|
|
2043
|
+
{ pattern: /\/server\/api\//, layer: "routing", confidence: 0.95, signal: "Nuxt server API", archetypes: ["nuxt"] },
|
|
2044
|
+
{ pattern: /\/server\/routes\//, layer: "routing", confidence: 0.95, signal: "Nuxt server route", archetypes: ["nuxt"] },
|
|
2045
|
+
{ pattern: /\/server\/middleware\//, layer: "middleware", confidence: 0.95, signal: "Nuxt server middleware", archetypes: ["nuxt"] },
|
|
2046
|
+
{ pattern: /\/pages\//, layer: "presentation", confidence: 0.85, signal: "Nuxt pages directory", archetypes: ["nuxt"] },
|
|
2047
|
+
// ── NestJS (archetype-specific) ──
|
|
2048
|
+
{ pattern: /\.controller\.[jt]sx?$/, layer: "routing", confidence: 0.95, signal: "NestJS controller", archetypes: ["nestjs"] },
|
|
2049
|
+
{ pattern: /\.service\.[jt]sx?$/, layer: "services", confidence: 0.95, signal: "NestJS service", archetypes: ["nestjs"] },
|
|
2050
|
+
{ pattern: /\.module\.[jt]sx?$/, layer: "config", confidence: 0.9, signal: "NestJS module", archetypes: ["nestjs"] },
|
|
2051
|
+
{ pattern: /\.guard\.[jt]sx?$/, layer: "middleware", confidence: 0.9, signal: "NestJS guard", archetypes: ["nestjs"] },
|
|
2052
|
+
{ pattern: /\.interceptor\.[jt]sx?$/, layer: "middleware", confidence: 0.9, signal: "NestJS interceptor", archetypes: ["nestjs"] },
|
|
2053
|
+
{ pattern: /\.pipe\.[jt]sx?$/, layer: "middleware", confidence: 0.85, signal: "NestJS pipe", archetypes: ["nestjs"] },
|
|
2054
|
+
{ pattern: /\.middleware\.[jt]sx?$/, layer: "middleware", confidence: 0.9, signal: "NestJS middleware", archetypes: ["nestjs"] },
|
|
2055
|
+
{ pattern: /\.entity\.[jt]sx?$/, layer: "domain", confidence: 0.9, signal: "NestJS entity", archetypes: ["nestjs"] },
|
|
2056
|
+
{ pattern: /\.dto\.[jt]sx?$/, layer: "domain", confidence: 0.85, signal: "NestJS DTO", archetypes: ["nestjs"] },
|
|
2057
|
+
{ pattern: /\.repository\.[jt]sx?$/, layer: "data-access", confidence: 0.9, signal: "NestJS repository", archetypes: ["nestjs"] },
|
|
2058
|
+
// ── Generic routing patterns ──
|
|
2059
|
+
{ pattern: /\/routes\//, layer: "routing", confidence: 0.8, signal: "routes/ directory" },
|
|
2060
|
+
{ pattern: /\/router\//, layer: "routing", confidence: 0.8, signal: "router/ directory" },
|
|
2061
|
+
{ pattern: /\/controllers\//, layer: "routing", confidence: 0.8, signal: "controllers/ directory" },
|
|
2062
|
+
{ pattern: /\/handlers\//, layer: "routing", confidence: 0.75, signal: "handlers/ directory" },
|
|
2063
|
+
{ pattern: /\/api\//, layer: "routing", confidence: 0.7, signal: "api/ directory" },
|
|
2064
|
+
{ pattern: /\/endpoints\//, layer: "routing", confidence: 0.8, signal: "endpoints/ directory" },
|
|
2065
|
+
// ── Middleware ──
|
|
2066
|
+
{ pattern: /\/middleware\//, layer: "middleware", confidence: 0.85, signal: "middleware/ directory" },
|
|
2067
|
+
{ pattern: /\/middlewares\//, layer: "middleware", confidence: 0.85, signal: "middlewares/ directory" },
|
|
2068
|
+
{ pattern: /\/hooks\//, layer: "middleware", confidence: 0.6, signal: "hooks/ directory" },
|
|
2069
|
+
{ pattern: /\/plugins\//, layer: "middleware", confidence: 0.6, signal: "plugins/ directory" },
|
|
2070
|
+
{ pattern: /\/guards\//, layer: "middleware", confidence: 0.85, signal: "guards/ directory" },
|
|
2071
|
+
{ pattern: /\/interceptors\//, layer: "middleware", confidence: 0.85, signal: "interceptors/ directory" },
|
|
2072
|
+
// ── Services / application layer ──
|
|
2073
|
+
{ pattern: /\/services\//, layer: "services", confidence: 0.85, signal: "services/ directory" },
|
|
2074
|
+
{ pattern: /\/service\//, layer: "services", confidence: 0.8, signal: "service/ directory" },
|
|
2075
|
+
{ pattern: /\/usecases\//, layer: "services", confidence: 0.85, signal: "usecases/ directory" },
|
|
2076
|
+
{ pattern: /\/use-cases\//, layer: "services", confidence: 0.85, signal: "use-cases/ directory" },
|
|
2077
|
+
{ pattern: /\/application\//, layer: "services", confidence: 0.7, signal: "application/ directory" },
|
|
2078
|
+
{ pattern: /\/actions\//, layer: "services", confidence: 0.65, signal: "actions/ directory" },
|
|
2079
|
+
// ── Domain / models ──
|
|
2080
|
+
{ pattern: /\/domain\//, layer: "domain", confidence: 0.85, signal: "domain/ directory" },
|
|
2081
|
+
{ pattern: /\/models\//, layer: "domain", confidence: 0.8, signal: "models/ directory" },
|
|
2082
|
+
{ pattern: /\/entities\//, layer: "domain", confidence: 0.85, signal: "entities/ directory" },
|
|
2083
|
+
{ pattern: /\/types\//, layer: "domain", confidence: 0.7, signal: "types/ directory" },
|
|
2084
|
+
{ pattern: /\/schemas\//, layer: "domain", confidence: 0.7, signal: "schemas/ directory" },
|
|
2085
|
+
{ pattern: /\/validators\//, layer: "domain", confidence: 0.7, signal: "validators/ directory" },
|
|
2086
|
+
// ── Data access ──
|
|
2087
|
+
{ pattern: /\/repositories\//, layer: "data-access", confidence: 0.9, signal: "repositories/ directory" },
|
|
2088
|
+
{ pattern: /\/repository\//, layer: "data-access", confidence: 0.85, signal: "repository/ directory" },
|
|
2089
|
+
{ pattern: /\/dao\//, layer: "data-access", confidence: 0.9, signal: "dao/ directory" },
|
|
2090
|
+
{ pattern: /\/db\//, layer: "data-access", confidence: 0.8, signal: "db/ directory" },
|
|
2091
|
+
{ pattern: /\/database\//, layer: "data-access", confidence: 0.8, signal: "database/ directory" },
|
|
2092
|
+
{ pattern: /\/persistence\//, layer: "data-access", confidence: 0.85, signal: "persistence/ directory" },
|
|
2093
|
+
{ pattern: /\/migrations\//, layer: "data-access", confidence: 0.9, signal: "migrations/ directory" },
|
|
2094
|
+
{ pattern: /\/seeds\//, layer: "data-access", confidence: 0.85, signal: "seeds/ directory" },
|
|
2095
|
+
{ pattern: /\/prisma\//, layer: "data-access", confidence: 0.85, signal: "prisma/ directory" },
|
|
2096
|
+
{ pattern: /\/drizzle\//, layer: "data-access", confidence: 0.85, signal: "drizzle/ directory" },
|
|
2097
|
+
// ── Infrastructure ──
|
|
2098
|
+
{ pattern: /\/infra\//, layer: "infrastructure", confidence: 0.85, signal: "infra/ directory" },
|
|
2099
|
+
{ pattern: /\/infrastructure\//, layer: "infrastructure", confidence: 0.85, signal: "infrastructure/ directory" },
|
|
2100
|
+
{ pattern: /\/adapters\//, layer: "infrastructure", confidence: 0.8, signal: "adapters/ directory" },
|
|
2101
|
+
{ pattern: /\/clients\//, layer: "infrastructure", confidence: 0.75, signal: "clients/ directory" },
|
|
2102
|
+
{ pattern: /\/integrations\//, layer: "infrastructure", confidence: 0.8, signal: "integrations/ directory" },
|
|
2103
|
+
{ pattern: /\/external\//, layer: "infrastructure", confidence: 0.75, signal: "external/ directory" },
|
|
2104
|
+
{ pattern: /\/queue\//, layer: "infrastructure", confidence: 0.8, signal: "queue/ directory" },
|
|
2105
|
+
{ pattern: /\/jobs\//, layer: "infrastructure", confidence: 0.75, signal: "jobs/ directory" },
|
|
2106
|
+
{ pattern: /\/workers\//, layer: "infrastructure", confidence: 0.75, signal: "workers/ directory" },
|
|
2107
|
+
{ pattern: /\/cron\//, layer: "infrastructure", confidence: 0.8, signal: "cron/ directory" },
|
|
2108
|
+
// ── Presentation (UI layer) ──
|
|
2109
|
+
{ pattern: /\/components\//, layer: "presentation", confidence: 0.85, signal: "components/ directory" },
|
|
2110
|
+
{ pattern: /\/views\//, layer: "presentation", confidence: 0.85, signal: "views/ directory" },
|
|
2111
|
+
{ pattern: /\/pages\//, layer: "presentation", confidence: 0.8, signal: "pages/ directory" },
|
|
2112
|
+
{ pattern: /\/layouts\//, layer: "presentation", confidence: 0.85, signal: "layouts/ directory" },
|
|
2113
|
+
{ pattern: /\/templates\//, layer: "presentation", confidence: 0.8, signal: "templates/ directory" },
|
|
2114
|
+
{ pattern: /\/widgets\//, layer: "presentation", confidence: 0.8, signal: "widgets/ directory" },
|
|
2115
|
+
{ pattern: /\/ui\//, layer: "presentation", confidence: 0.75, signal: "ui/ directory" },
|
|
2116
|
+
// ── Shared / utils ──
|
|
2117
|
+
{ pattern: /\/utils\//, layer: "shared", confidence: 0.7, signal: "utils/ directory" },
|
|
2118
|
+
{ pattern: /\/helpers\//, layer: "shared", confidence: 0.7, signal: "helpers/ directory" },
|
|
2119
|
+
{ pattern: /\/lib\//, layer: "shared", confidence: 0.6, signal: "lib/ directory" },
|
|
2120
|
+
{ pattern: /\/common\//, layer: "shared", confidence: 0.65, signal: "common/ directory" },
|
|
2121
|
+
{ pattern: /\/shared\//, layer: "shared", confidence: 0.75, signal: "shared/ directory" },
|
|
2122
|
+
{ pattern: /\/constants\//, layer: "shared", confidence: 0.7, signal: "constants/ directory" },
|
|
2123
|
+
// ── CLI-specific (command layer → routing) ──
|
|
2124
|
+
{ pattern: /\/commands\//, layer: "routing", confidence: 0.8, signal: "commands/ directory", archetypes: ["cli"] },
|
|
2125
|
+
{ pattern: /\/formatters\//, layer: "presentation", confidence: 0.8, signal: "formatters/ directory", archetypes: ["cli"] },
|
|
2126
|
+
{ pattern: /\/scanners\//, layer: "services", confidence: 0.8, signal: "scanners/ directory", archetypes: ["cli"] },
|
|
2127
|
+
{ pattern: /\/scoring\//, layer: "domain", confidence: 0.8, signal: "scoring/ directory", archetypes: ["cli"] },
|
|
2128
|
+
// ── Serverless-specific ──
|
|
2129
|
+
{ pattern: /\/functions\//, layer: "routing", confidence: 0.8, signal: "functions/ directory", archetypes: ["serverless"] },
|
|
2130
|
+
{ pattern: /\/lambdas\//, layer: "routing", confidence: 0.85, signal: "lambdas/ directory", archetypes: ["serverless"] },
|
|
2131
|
+
{ pattern: /\/layers\//, layer: "shared", confidence: 0.7, signal: "Lambda layers/ directory", archetypes: ["serverless"] }
|
|
2132
|
+
];
|
|
2133
|
+
var SUFFIX_RULES = [
|
|
2134
|
+
{ suffix: ".controller", layer: "routing", confidence: 0.85, signal: "controller suffix" },
|
|
2135
|
+
{ suffix: ".route", layer: "routing", confidence: 0.85, signal: "route suffix" },
|
|
2136
|
+
{ suffix: ".router", layer: "routing", confidence: 0.85, signal: "router suffix" },
|
|
2137
|
+
{ suffix: ".handler", layer: "routing", confidence: 0.8, signal: "handler suffix" },
|
|
2138
|
+
{ suffix: ".middleware", layer: "middleware", confidence: 0.85, signal: "middleware suffix" },
|
|
2139
|
+
{ suffix: ".guard", layer: "middleware", confidence: 0.85, signal: "guard suffix" },
|
|
2140
|
+
{ suffix: ".interceptor", layer: "middleware", confidence: 0.85, signal: "interceptor suffix" },
|
|
2141
|
+
{ suffix: ".service", layer: "services", confidence: 0.85, signal: "service suffix" },
|
|
2142
|
+
{ suffix: ".usecase", layer: "services", confidence: 0.85, signal: "usecase suffix" },
|
|
2143
|
+
{ suffix: ".model", layer: "domain", confidence: 0.8, signal: "model suffix" },
|
|
2144
|
+
{ suffix: ".entity", layer: "domain", confidence: 0.85, signal: "entity suffix" },
|
|
2145
|
+
{ suffix: ".dto", layer: "domain", confidence: 0.8, signal: "DTO suffix" },
|
|
2146
|
+
{ suffix: ".schema", layer: "domain", confidence: 0.75, signal: "schema suffix" },
|
|
2147
|
+
{ suffix: ".validator", layer: "domain", confidence: 0.75, signal: "validator suffix" },
|
|
2148
|
+
{ suffix: ".repository", layer: "data-access", confidence: 0.9, signal: "repository suffix" },
|
|
2149
|
+
{ suffix: ".repo", layer: "data-access", confidence: 0.85, signal: "repo suffix" },
|
|
2150
|
+
{ suffix: ".dao", layer: "data-access", confidence: 0.9, signal: "dao suffix" },
|
|
2151
|
+
{ suffix: ".migration", layer: "data-access", confidence: 0.85, signal: "migration suffix" },
|
|
2152
|
+
{ suffix: ".adapter", layer: "infrastructure", confidence: 0.8, signal: "adapter suffix" },
|
|
2153
|
+
{ suffix: ".client", layer: "infrastructure", confidence: 0.75, signal: "client suffix" },
|
|
2154
|
+
{ suffix: ".provider", layer: "infrastructure", confidence: 0.7, signal: "provider suffix" },
|
|
2155
|
+
{ suffix: ".config", layer: "config", confidence: 0.8, signal: "config suffix" },
|
|
2156
|
+
{ suffix: ".component", layer: "presentation", confidence: 0.8, signal: "component suffix" },
|
|
2157
|
+
{ suffix: ".page", layer: "presentation", confidence: 0.85, signal: "page suffix" },
|
|
2158
|
+
{ suffix: ".view", layer: "presentation", confidence: 0.8, signal: "view suffix" },
|
|
2159
|
+
{ suffix: ".layout", layer: "presentation", confidence: 0.85, signal: "layout suffix" },
|
|
2160
|
+
{ suffix: ".util", layer: "shared", confidence: 0.7, signal: "util suffix" },
|
|
2161
|
+
{ suffix: ".helper", layer: "shared", confidence: 0.7, signal: "helper suffix" },
|
|
2162
|
+
{ suffix: ".constant", layer: "shared", confidence: 0.7, signal: "constant suffix" }
|
|
2163
|
+
];
|
|
2164
|
+
var PACKAGE_LAYER_MAP = {
|
|
2165
|
+
// Routing/controllers
|
|
2166
|
+
"express": "routing",
|
|
2167
|
+
"fastify": "routing",
|
|
2168
|
+
"@nestjs/core": "routing",
|
|
2169
|
+
"hono": "routing",
|
|
2170
|
+
"koa": "routing",
|
|
2171
|
+
"koa-router": "routing",
|
|
2172
|
+
"@hapi/hapi": "routing",
|
|
2173
|
+
"h3": "routing",
|
|
2174
|
+
// Middleware
|
|
2175
|
+
"cors": "middleware",
|
|
2176
|
+
"helmet": "middleware",
|
|
2177
|
+
"passport": "middleware",
|
|
2178
|
+
"express-rate-limit": "middleware",
|
|
2179
|
+
"cookie-parser": "middleware",
|
|
2180
|
+
"body-parser": "middleware",
|
|
2181
|
+
"multer": "middleware",
|
|
2182
|
+
"morgan": "middleware",
|
|
2183
|
+
"compression": "middleware",
|
|
2184
|
+
"express-session": "middleware",
|
|
2185
|
+
// Services / application
|
|
2186
|
+
"bullmq": "services",
|
|
2187
|
+
"bull": "services",
|
|
2188
|
+
"agenda": "services",
|
|
2189
|
+
"pg-boss": "services",
|
|
2190
|
+
"inngest": "services",
|
|
2191
|
+
// Domain / validation
|
|
2192
|
+
"zod": "domain",
|
|
2193
|
+
"joi": "domain",
|
|
2194
|
+
"yup": "domain",
|
|
2195
|
+
"class-validator": "domain",
|
|
2196
|
+
"class-transformer": "domain",
|
|
2197
|
+
"superstruct": "domain",
|
|
2198
|
+
"valibot": "domain",
|
|
2199
|
+
// Data access / ORM
|
|
2200
|
+
"prisma": "data-access",
|
|
2201
|
+
"@prisma/client": "data-access",
|
|
2202
|
+
"drizzle-orm": "data-access",
|
|
2203
|
+
"typeorm": "data-access",
|
|
2204
|
+
"sequelize": "data-access",
|
|
2205
|
+
"knex": "data-access",
|
|
2206
|
+
"pg": "data-access",
|
|
2207
|
+
"mysql2": "data-access",
|
|
2208
|
+
"mongodb": "data-access",
|
|
2209
|
+
"mongoose": "data-access",
|
|
2210
|
+
"ioredis": "data-access",
|
|
2211
|
+
"redis": "data-access",
|
|
2212
|
+
"better-sqlite3": "data-access",
|
|
2213
|
+
"kysely": "data-access",
|
|
2214
|
+
"@mikro-orm/core": "data-access",
|
|
2215
|
+
// Infrastructure
|
|
2216
|
+
"@aws-sdk/client-s3": "infrastructure",
|
|
2217
|
+
"@aws-sdk/client-sqs": "infrastructure",
|
|
2218
|
+
"@aws-sdk/client-sns": "infrastructure",
|
|
2219
|
+
"@aws-sdk/client-ses": "infrastructure",
|
|
2220
|
+
"@aws-sdk/client-lambda": "infrastructure",
|
|
2221
|
+
"@google-cloud/storage": "infrastructure",
|
|
2222
|
+
"@azure/storage-blob": "infrastructure",
|
|
2223
|
+
"nodemailer": "infrastructure",
|
|
2224
|
+
"@sendgrid/mail": "infrastructure",
|
|
2225
|
+
"stripe": "infrastructure",
|
|
2226
|
+
"kafkajs": "infrastructure",
|
|
2227
|
+
"amqplib": "infrastructure",
|
|
2228
|
+
// Presentation
|
|
2229
|
+
"react": "presentation",
|
|
2230
|
+
"react-dom": "presentation",
|
|
2231
|
+
"vue": "presentation",
|
|
2232
|
+
"@angular/core": "presentation",
|
|
2233
|
+
"svelte": "presentation",
|
|
2234
|
+
// Shared
|
|
2235
|
+
"lodash": "shared",
|
|
2236
|
+
"dayjs": "shared",
|
|
2237
|
+
"date-fns": "shared",
|
|
2238
|
+
"uuid": "shared",
|
|
2239
|
+
"nanoid": "shared",
|
|
2240
|
+
// Testing
|
|
2241
|
+
"vitest": "testing",
|
|
2242
|
+
"jest": "testing",
|
|
2243
|
+
"mocha": "testing",
|
|
2244
|
+
"@playwright/test": "testing",
|
|
2245
|
+
"cypress": "testing",
|
|
2246
|
+
"supertest": "testing",
|
|
2247
|
+
// Observability → infrastructure
|
|
2248
|
+
"@sentry/node": "infrastructure",
|
|
2249
|
+
"@opentelemetry/api": "infrastructure",
|
|
2250
|
+
"pino": "infrastructure",
|
|
2251
|
+
"winston": "infrastructure",
|
|
2252
|
+
"dd-trace": "infrastructure"
|
|
2253
|
+
};
|
|
2254
|
+
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs", ".cts", ".cjs", ".svelte", ".vue"]);
|
|
2255
|
+
var IGNORE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".wrangler", "dist", "build", ".next", ".nuxt", ".output", ".svelte-kit", "coverage", ".vibgrate"]);
|
|
2256
|
+
async function walkSourceFiles(rootDir, cache) {
|
|
2257
|
+
if (cache) {
|
|
2258
|
+
const entries = await cache.walkDir(rootDir);
|
|
2259
|
+
return entries.filter((e) => {
|
|
2260
|
+
if (!e.isFile) return false;
|
|
2261
|
+
const name = path3.basename(e.absPath);
|
|
2262
|
+
if (name.startsWith(".") && name !== ".") return false;
|
|
2263
|
+
const ext = path3.extname(name);
|
|
2264
|
+
return SOURCE_EXTENSIONS.has(ext);
|
|
2265
|
+
}).map((e) => e.relPath);
|
|
2266
|
+
}
|
|
2267
|
+
const files = [];
|
|
2268
|
+
async function walk(dir) {
|
|
2269
|
+
let entries;
|
|
2270
|
+
try {
|
|
2271
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
2272
|
+
} catch {
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
for (const entry of entries) {
|
|
2276
|
+
if (entry.name.startsWith(".") && entry.name !== ".") continue;
|
|
2277
|
+
const fullPath = path3.join(dir, entry.name);
|
|
2278
|
+
if (entry.isDirectory()) {
|
|
2279
|
+
if (!IGNORE_DIRS.has(entry.name)) {
|
|
2280
|
+
await walk(fullPath);
|
|
2281
|
+
}
|
|
2282
|
+
} else if (entry.isFile()) {
|
|
2283
|
+
const ext = path3.extname(entry.name);
|
|
2284
|
+
if (SOURCE_EXTENSIONS.has(ext)) {
|
|
2285
|
+
files.push(path3.relative(rootDir, fullPath));
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
await walk(rootDir);
|
|
2291
|
+
return files;
|
|
2292
|
+
}
|
|
2293
|
+
function classifyFile(filePath, archetype) {
|
|
2294
|
+
const normalised = filePath.replace(/\\/g, "/");
|
|
2295
|
+
let bestMatch = null;
|
|
2296
|
+
for (const rule of PATH_RULES) {
|
|
2297
|
+
if (rule.archetypes && rule.archetypes.length > 0 && !rule.archetypes.includes(archetype)) {
|
|
2298
|
+
continue;
|
|
2299
|
+
}
|
|
2300
|
+
if (rule.pattern.test(normalised)) {
|
|
2301
|
+
const boost = rule.archetypes ? 0.05 : 0;
|
|
2302
|
+
const adjustedConfidence = Math.min(rule.confidence + boost, 1);
|
|
2303
|
+
if (!bestMatch || adjustedConfidence > bestMatch.confidence) {
|
|
2304
|
+
bestMatch = { layer: rule.layer, confidence: adjustedConfidence, signal: rule.signal };
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
if (!bestMatch || bestMatch.confidence < 0.7) {
|
|
2309
|
+
const baseName = path3.basename(filePath, path3.extname(filePath));
|
|
2310
|
+
const cleanBase = baseName.replace(/\.(test|spec)$/, "");
|
|
2311
|
+
for (const rule of SUFFIX_RULES) {
|
|
2312
|
+
if (cleanBase.endsWith(rule.suffix)) {
|
|
2313
|
+
if (!bestMatch || rule.confidence > bestMatch.confidence) {
|
|
2314
|
+
bestMatch = { layer: rule.layer, confidence: rule.confidence, signal: rule.signal };
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
if (bestMatch) {
|
|
2320
|
+
return {
|
|
2321
|
+
filePath,
|
|
2322
|
+
layer: bestMatch.layer,
|
|
2323
|
+
confidence: bestMatch.confidence,
|
|
2324
|
+
signals: [bestMatch.signal]
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
return null;
|
|
2328
|
+
}
|
|
2329
|
+
function computeLayerDrift(packages) {
|
|
2330
|
+
if (packages.length === 0) {
|
|
2331
|
+
return { score: 0, riskLevel: "none" };
|
|
2332
|
+
}
|
|
2333
|
+
let current = 0;
|
|
2334
|
+
let oneBehind = 0;
|
|
2335
|
+
let twoPlusBehind = 0;
|
|
2336
|
+
for (const pkg of packages) {
|
|
2337
|
+
if (pkg.majorsBehind === null) ; else if (pkg.majorsBehind === 0) {
|
|
2338
|
+
current++;
|
|
2339
|
+
} else if (pkg.majorsBehind === 1) {
|
|
2340
|
+
oneBehind++;
|
|
2341
|
+
} else {
|
|
2342
|
+
twoPlusBehind++;
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
const known = current + oneBehind + twoPlusBehind;
|
|
2346
|
+
if (known === 0) return { score: 0, riskLevel: "none" };
|
|
2347
|
+
const currentPct = current / known;
|
|
2348
|
+
const onePct = oneBehind / known;
|
|
2349
|
+
const twoPct = twoPlusBehind / known;
|
|
2350
|
+
const health = Math.round(Math.max(0, Math.min(100, currentPct * 100 - onePct * 10 - twoPct * 40)));
|
|
2351
|
+
const score = 100 - health;
|
|
2352
|
+
const riskLevel = score <= 30 ? "low" : score <= 60 ? "moderate" : "high";
|
|
2353
|
+
return { score, riskLevel };
|
|
2354
|
+
}
|
|
2355
|
+
function mapToolingToLayers(tooling, services, depsByLayer) {
|
|
2356
|
+
const layerTooling = /* @__PURE__ */ new Map();
|
|
2357
|
+
const layerServices = /* @__PURE__ */ new Map();
|
|
2358
|
+
const pkgLayerLookup = /* @__PURE__ */ new Map();
|
|
2359
|
+
for (const [layer, packages] of depsByLayer) {
|
|
2360
|
+
for (const pkg of packages) {
|
|
2361
|
+
pkgLayerLookup.set(pkg, layer);
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
if (tooling) {
|
|
2365
|
+
for (const [, items] of Object.entries(tooling)) {
|
|
2366
|
+
for (const item of items) {
|
|
2367
|
+
const layer = pkgLayerLookup.get(item.package) ?? PACKAGE_LAYER_MAP[item.package] ?? "shared";
|
|
2368
|
+
if (!layerTooling.has(layer)) layerTooling.set(layer, []);
|
|
2369
|
+
const existing = layerTooling.get(layer);
|
|
2370
|
+
if (!existing.some((t) => t.package === item.package)) {
|
|
2371
|
+
existing.push(item);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
if (services) {
|
|
2377
|
+
for (const [, items] of Object.entries(services)) {
|
|
2378
|
+
for (const item of items) {
|
|
2379
|
+
const layer = pkgLayerLookup.get(item.package) ?? PACKAGE_LAYER_MAP[item.package] ?? "infrastructure";
|
|
2380
|
+
if (!layerServices.has(layer)) layerServices.set(layer, []);
|
|
2381
|
+
const existing = layerServices.get(layer);
|
|
2382
|
+
if (!existing.some((s) => s.package === item.package)) {
|
|
2383
|
+
existing.push(item);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
return { layerTooling, layerServices };
|
|
2389
|
+
}
|
|
2390
|
+
function generateLayerFlowMermaid(layers) {
|
|
2391
|
+
const labels = {
|
|
2392
|
+
presentation: "Presentation",
|
|
2393
|
+
routing: "Routing",
|
|
2394
|
+
middleware: "Middleware",
|
|
2395
|
+
services: "Services",
|
|
2396
|
+
domain: "Domain",
|
|
2397
|
+
"data-access": "Data Access",
|
|
2398
|
+
infrastructure: "Infrastructure",
|
|
2399
|
+
config: "Config",
|
|
2400
|
+
shared: "Shared",
|
|
2401
|
+
testing: "Testing"
|
|
2402
|
+
};
|
|
2403
|
+
if (layers.length === 0) {
|
|
2404
|
+
return 'flowchart TD\n APP["Project"]';
|
|
2405
|
+
}
|
|
2406
|
+
const ordered = [...layers];
|
|
2407
|
+
const lines = ["flowchart TD"];
|
|
2408
|
+
for (let i = 0; i < ordered.length; i++) {
|
|
2409
|
+
const layer = ordered[i];
|
|
2410
|
+
lines.push(` L${i}["${labels[layer]}"]`);
|
|
2411
|
+
if (i > 0) lines.push(` L${i - 1} --> L${i}`);
|
|
2412
|
+
}
|
|
2413
|
+
return lines.join("\n");
|
|
2414
|
+
}
|
|
2415
|
+
async function buildProjectArchitectureMermaid(rootDir, project, archetype, cache) {
|
|
2416
|
+
const projectRoot = path3.resolve(rootDir, project.path || ".");
|
|
2417
|
+
const allFiles = await walkSourceFiles(projectRoot, cache);
|
|
2418
|
+
const layerSet = /* @__PURE__ */ new Set();
|
|
2419
|
+
for (const rel of allFiles) {
|
|
2420
|
+
const classification = classifyFile(rel, archetype);
|
|
2421
|
+
if (classification) {
|
|
2422
|
+
layerSet.add(classification.layer);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
const layerOrder = [
|
|
2426
|
+
"presentation",
|
|
2427
|
+
"routing",
|
|
2428
|
+
"middleware",
|
|
2429
|
+
"services",
|
|
2430
|
+
"domain",
|
|
2431
|
+
"data-access",
|
|
2432
|
+
"infrastructure",
|
|
2433
|
+
"config",
|
|
2434
|
+
"shared",
|
|
2435
|
+
"testing"
|
|
2436
|
+
];
|
|
2437
|
+
const orderedLayers = layerOrder.filter((l) => layerSet.has(l));
|
|
2438
|
+
return generateLayerFlowMermaid(orderedLayers);
|
|
2439
|
+
}
|
|
2440
|
+
async function scanProjectArchitecture(rootDir, project, cache) {
|
|
2441
|
+
const projectRoot = path3.resolve(rootDir, project.path || ".");
|
|
2442
|
+
return scanArchitecture(projectRoot, [project], void 0, void 0, cache);
|
|
2443
|
+
}
|
|
2444
|
+
function aggregateSolutionArchitecture(projectResults) {
|
|
2445
|
+
if (projectResults.length === 0) {
|
|
2446
|
+
return { archetype: "unknown", archetypeConfidence: 0, layers: [], totalClassified: 0, unclassified: 0 };
|
|
2447
|
+
}
|
|
2448
|
+
if (projectResults.length === 1) {
|
|
2449
|
+
return projectResults[0];
|
|
2450
|
+
}
|
|
2451
|
+
let bestArchetype = "unknown";
|
|
2452
|
+
let bestConfidence = 0;
|
|
2453
|
+
for (const r of projectResults) {
|
|
2454
|
+
if (r.archetypeConfidence > bestConfidence || r.archetypeConfidence === bestConfidence && r.archetype !== "monorepo") {
|
|
2455
|
+
bestArchetype = r.archetype;
|
|
2456
|
+
bestConfidence = r.archetypeConfidence;
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
const layerMap = /* @__PURE__ */ new Map();
|
|
2460
|
+
for (const r of projectResults) {
|
|
2461
|
+
for (const ls of r.layers) {
|
|
2462
|
+
const existing = layerMap.get(ls.layer);
|
|
2463
|
+
if (!existing) {
|
|
2464
|
+
layerMap.set(ls.layer, { ...ls, packages: [...ls.packages], techStack: [...ls.techStack], services: [...ls.services] });
|
|
2465
|
+
} else {
|
|
2466
|
+
existing.fileCount += ls.fileCount;
|
|
2467
|
+
const pkgNames = new Set(existing.packages.map((p) => p.name));
|
|
2468
|
+
for (const pkg of ls.packages) {
|
|
2469
|
+
if (!pkgNames.has(pkg.name)) {
|
|
2470
|
+
existing.packages.push(pkg);
|
|
2471
|
+
pkgNames.add(pkg.name);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
const techPkgs = new Set(existing.techStack.map((t) => t.package));
|
|
2475
|
+
for (const t of ls.techStack) {
|
|
2476
|
+
if (!techPkgs.has(t.package)) {
|
|
2477
|
+
existing.techStack.push(t);
|
|
2478
|
+
techPkgs.add(t.package);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
const svcPkgs = new Set(existing.services.map((s) => s.package));
|
|
2482
|
+
for (const s of ls.services) {
|
|
2483
|
+
if (!svcPkgs.has(s.package)) {
|
|
2484
|
+
existing.services.push(s);
|
|
2485
|
+
svcPkgs.add(s.package);
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
const { score, riskLevel } = computeLayerDrift(existing.packages);
|
|
2489
|
+
existing.driftScore = score;
|
|
2490
|
+
existing.riskLevel = riskLevel;
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
const LAYER_ORDER = {
|
|
2495
|
+
"presentation": 0,
|
|
2496
|
+
"routing": 1,
|
|
2497
|
+
"middleware": 2,
|
|
2498
|
+
"services": 3,
|
|
2499
|
+
"domain": 4,
|
|
2500
|
+
"data-access": 5,
|
|
2501
|
+
"infrastructure": 6,
|
|
2502
|
+
"config": 7,
|
|
2503
|
+
"shared": 8,
|
|
2504
|
+
"testing": 9
|
|
2505
|
+
};
|
|
2506
|
+
const layers = [...layerMap.values()].sort((a, b) => (LAYER_ORDER[a.layer] ?? 99) - (LAYER_ORDER[b.layer] ?? 99));
|
|
2507
|
+
const totalClassified = projectResults.reduce((s, r) => s + r.totalClassified, 0);
|
|
2508
|
+
const unclassified = projectResults.reduce((s, r) => s + r.unclassified, 0);
|
|
2509
|
+
return { archetype: bestArchetype, archetypeConfidence: bestConfidence, layers, totalClassified, unclassified };
|
|
2510
|
+
}
|
|
2511
|
+
async function scanArchitecture(rootDir, projects, tooling, services, cache) {
|
|
2512
|
+
const { archetype, confidence: archetypeConfidence } = detectArchetype(projects);
|
|
2513
|
+
const sourceFiles = await walkSourceFiles(rootDir, cache);
|
|
2514
|
+
const classifications = [];
|
|
2515
|
+
let unclassified = 0;
|
|
2516
|
+
for (const file of sourceFiles) {
|
|
2517
|
+
const classification = classifyFile(file, archetype);
|
|
2518
|
+
if (classification) {
|
|
2519
|
+
classifications.push(classification);
|
|
2520
|
+
} else {
|
|
2521
|
+
unclassified++;
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
const allDeps = /* @__PURE__ */ new Map();
|
|
2525
|
+
for (const p of projects) {
|
|
2526
|
+
for (const d of p.dependencies) {
|
|
2527
|
+
if (!allDeps.has(d.package)) {
|
|
2528
|
+
allDeps.set(d.package, d);
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
const depsByLayer = /* @__PURE__ */ new Map();
|
|
2533
|
+
for (const [pkg] of allDeps) {
|
|
2534
|
+
const layer = PACKAGE_LAYER_MAP[pkg];
|
|
2535
|
+
if (layer) {
|
|
2536
|
+
if (!depsByLayer.has(layer)) depsByLayer.set(layer, /* @__PURE__ */ new Set());
|
|
2537
|
+
depsByLayer.get(layer).add(pkg);
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
const { layerTooling, layerServices } = mapToolingToLayers(tooling, services, depsByLayer);
|
|
2541
|
+
const ALL_LAYERS = [
|
|
2542
|
+
"routing",
|
|
2543
|
+
"middleware",
|
|
2544
|
+
"services",
|
|
2545
|
+
"domain",
|
|
2546
|
+
"data-access",
|
|
2547
|
+
"infrastructure",
|
|
2548
|
+
"presentation",
|
|
2549
|
+
"config",
|
|
2550
|
+
"testing",
|
|
2551
|
+
"shared"
|
|
2552
|
+
];
|
|
2553
|
+
const layerFileCounts = /* @__PURE__ */ new Map();
|
|
2554
|
+
for (const c of classifications) {
|
|
2555
|
+
layerFileCounts.set(c.layer, (layerFileCounts.get(c.layer) ?? 0) + 1);
|
|
2556
|
+
}
|
|
2557
|
+
const layers = [];
|
|
2558
|
+
for (const layer of ALL_LAYERS) {
|
|
2559
|
+
const fileCount = layerFileCounts.get(layer) ?? 0;
|
|
2560
|
+
const layerPkgs = depsByLayer.get(layer) ?? /* @__PURE__ */ new Set();
|
|
2561
|
+
const tech = layerTooling.get(layer) ?? [];
|
|
2562
|
+
const svc = layerServices.get(layer) ?? [];
|
|
2563
|
+
if (fileCount === 0 && layerPkgs.size === 0 && tech.length === 0 && svc.length === 0) {
|
|
2564
|
+
continue;
|
|
2565
|
+
}
|
|
2566
|
+
const packages = [];
|
|
2567
|
+
for (const pkg of layerPkgs) {
|
|
2568
|
+
const dep = allDeps.get(pkg);
|
|
2569
|
+
if (dep) {
|
|
2570
|
+
packages.push({
|
|
2571
|
+
name: dep.package,
|
|
2572
|
+
version: dep.resolvedVersion,
|
|
2573
|
+
latestStable: dep.latestStable,
|
|
2574
|
+
majorsBehind: dep.majorsBehind,
|
|
2575
|
+
drift: dep.drift
|
|
2576
|
+
});
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
const { score, riskLevel } = computeLayerDrift(packages);
|
|
2580
|
+
layers.push({
|
|
2581
|
+
layer,
|
|
2582
|
+
fileCount,
|
|
2583
|
+
driftScore: score,
|
|
2584
|
+
riskLevel,
|
|
2585
|
+
techStack: tech,
|
|
2586
|
+
services: svc,
|
|
2587
|
+
packages
|
|
2588
|
+
});
|
|
2589
|
+
}
|
|
2590
|
+
const LAYER_ORDER = {
|
|
2591
|
+
"presentation": 0,
|
|
2592
|
+
"routing": 1,
|
|
2593
|
+
"middleware": 2,
|
|
2594
|
+
"services": 3,
|
|
2595
|
+
"domain": 4,
|
|
2596
|
+
"data-access": 5,
|
|
2597
|
+
"infrastructure": 6,
|
|
2598
|
+
"config": 7,
|
|
2599
|
+
"shared": 8,
|
|
2600
|
+
"testing": 9
|
|
2601
|
+
};
|
|
2602
|
+
layers.sort((a, b) => (LAYER_ORDER[a.layer] ?? 99) - (LAYER_ORDER[b.layer] ?? 99));
|
|
2603
|
+
return {
|
|
2604
|
+
archetype,
|
|
2605
|
+
archetypeConfidence,
|
|
2606
|
+
layers,
|
|
2607
|
+
totalClassified: classifications.length,
|
|
2608
|
+
unclassified
|
|
2609
|
+
};
|
|
2610
|
+
}
|
|
2611
|
+
var ts;
|
|
2612
|
+
var tsLoading = null;
|
|
2613
|
+
async function ensureTypeScript() {
|
|
2614
|
+
if (ts) return;
|
|
2615
|
+
tsLoading ??= import('typescript').then((m) => {
|
|
2616
|
+
const mod = m;
|
|
2617
|
+
ts = mod.default ?? m;
|
|
2618
|
+
});
|
|
2619
|
+
await tsLoading;
|
|
2620
|
+
}
|
|
2621
|
+
var SOURCE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
2622
|
+
var DEFAULT_RESULT = {
|
|
2623
|
+
filesAnalyzed: 0,
|
|
2624
|
+
functionsAnalyzed: 0,
|
|
2625
|
+
avgCyclomaticComplexity: 0,
|
|
2626
|
+
avgFunctionLength: 0,
|
|
2627
|
+
maxNestingDepth: 0,
|
|
2628
|
+
godFiles: [],
|
|
2629
|
+
circularDependencies: 0,
|
|
2630
|
+
deadCodePercent: 0
|
|
2631
|
+
};
|
|
2632
|
+
async function scanCodeQuality(rootDir, cache) {
|
|
2633
|
+
const filePaths = await findSourceFiles(rootDir, cache);
|
|
2634
|
+
if (filePaths.length === 0) return { ...DEFAULT_RESULT };
|
|
2635
|
+
await ensureTypeScript();
|
|
2636
|
+
let totalFunctions = 0;
|
|
2637
|
+
let totalComplexity = 0;
|
|
2638
|
+
let totalFunctionLength = 0;
|
|
2639
|
+
let maxNestingDepth = 0;
|
|
2640
|
+
let deadFunctions = 0;
|
|
2641
|
+
const godFiles = [];
|
|
2642
|
+
const depGraph = /* @__PURE__ */ new Map();
|
|
2643
|
+
for (const filePath of filePaths) {
|
|
2644
|
+
let raw = "";
|
|
2645
|
+
try {
|
|
2646
|
+
raw = cache ? await cache.readTextFile(filePath) : await readTextFile(filePath);
|
|
2647
|
+
} catch {
|
|
2648
|
+
continue;
|
|
2649
|
+
}
|
|
2650
|
+
if (!raw.trim()) continue;
|
|
2651
|
+
const rel = normalizeModuleId(path3.relative(rootDir, filePath));
|
|
2652
|
+
const source = ts.createSourceFile(filePath, raw, ts.ScriptTarget.Latest, true);
|
|
2653
|
+
const imports = collectLocalImports(source, path3.dirname(filePath), rootDir);
|
|
2654
|
+
depGraph.set(rel, imports);
|
|
2655
|
+
const fileMetrics = computeFileMetrics(source, raw);
|
|
2656
|
+
totalFunctions += fileMetrics.functionsAnalyzed;
|
|
2657
|
+
totalComplexity += fileMetrics.totalComplexity;
|
|
2658
|
+
totalFunctionLength += fileMetrics.totalFunctionLength;
|
|
2659
|
+
maxNestingDepth = Math.max(maxNestingDepth, fileMetrics.maxNestingDepth);
|
|
2660
|
+
deadFunctions += fileMetrics.deadFunctionCount;
|
|
2661
|
+
const fileAvgComplexity = fileMetrics.functionsAnalyzed > 0 ? fileMetrics.totalComplexity / fileMetrics.functionsAnalyzed : 0;
|
|
2662
|
+
if (fileMetrics.lines >= 450 || fileMetrics.functionsAnalyzed >= 25 || fileMetrics.functionsAnalyzed >= 10 && fileAvgComplexity >= 8) {
|
|
2663
|
+
godFiles.push({
|
|
2664
|
+
path: rel,
|
|
2665
|
+
lines: fileMetrics.lines,
|
|
2666
|
+
functionCount: fileMetrics.functionsAnalyzed,
|
|
2667
|
+
averageComplexity: round2(fileAvgComplexity)
|
|
2668
|
+
});
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
const circularDependencies = countCircularDependencyChains(depGraph);
|
|
2672
|
+
const deadCodePercent = totalFunctions > 0 ? deadFunctions / totalFunctions * 100 : 0;
|
|
2673
|
+
return {
|
|
2674
|
+
filesAnalyzed: depGraph.size,
|
|
2675
|
+
functionsAnalyzed: totalFunctions,
|
|
2676
|
+
avgCyclomaticComplexity: totalFunctions > 0 ? round2(totalComplexity / totalFunctions) : 0,
|
|
2677
|
+
avgFunctionLength: totalFunctions > 0 ? round2(totalFunctionLength / totalFunctions) : 0,
|
|
2678
|
+
maxNestingDepth,
|
|
2679
|
+
godFiles: godFiles.sort((a, b) => b.lines - a.lines || b.functionCount - a.functionCount).slice(0, 10),
|
|
2680
|
+
circularDependencies,
|
|
2681
|
+
deadCodePercent: round2(deadCodePercent)
|
|
2682
|
+
};
|
|
2683
|
+
}
|
|
2684
|
+
async function findSourceFiles(rootDir, cache) {
|
|
2685
|
+
if (cache) {
|
|
2686
|
+
const entries = await cache.walkDir(rootDir);
|
|
2687
|
+
return entries.filter((entry) => entry.isFile && SOURCE_EXTENSIONS2.has(path3.extname(entry.name).toLowerCase())).map((entry) => entry.absPath);
|
|
2688
|
+
}
|
|
2689
|
+
const files = await findFiles(rootDir, (name) => SOURCE_EXTENSIONS2.has(path3.extname(name).toLowerCase()));
|
|
2690
|
+
return files;
|
|
2691
|
+
}
|
|
2692
|
+
function collectLocalImports(source, fileDir, rootDir) {
|
|
2693
|
+
const deps = /* @__PURE__ */ new Set();
|
|
2694
|
+
const visit = (node) => {
|
|
2695
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
2696
|
+
const target = resolveLocalImport(node.moduleSpecifier.text, fileDir, rootDir);
|
|
2697
|
+
if (target) deps.add(target);
|
|
2698
|
+
}
|
|
2699
|
+
if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword && node.arguments[0] && ts.isStringLiteral(node.arguments[0])) {
|
|
2700
|
+
const target = resolveLocalImport(node.arguments[0].text, fileDir, rootDir);
|
|
2701
|
+
if (target) deps.add(target);
|
|
2702
|
+
}
|
|
2703
|
+
visitEach(node, visit);
|
|
2704
|
+
};
|
|
2705
|
+
visit(source);
|
|
2706
|
+
return [...deps];
|
|
2707
|
+
}
|
|
2708
|
+
function resolveLocalImport(specifier, fileDir, rootDir) {
|
|
2709
|
+
if (!specifier.startsWith(".")) return null;
|
|
2710
|
+
const rawTarget = path3.resolve(fileDir, specifier);
|
|
2711
|
+
const normalized = path3.relative(rootDir, rawTarget).replace(/\\/g, "/");
|
|
2712
|
+
if (!normalized || normalized.startsWith("..")) return null;
|
|
2713
|
+
return normalizeModuleId(normalized);
|
|
2714
|
+
}
|
|
2715
|
+
function normalizeModuleId(relPath) {
|
|
2716
|
+
return relPath.replace(/\\/g, "/").replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "").replace(/\/index$/, "");
|
|
2717
|
+
}
|
|
2718
|
+
function computeFileMetrics(source, raw) {
|
|
2719
|
+
let functionsAnalyzed = 0;
|
|
2720
|
+
let totalComplexity = 0;
|
|
2721
|
+
let totalFunctionLength = 0;
|
|
2722
|
+
let maxNestingDepth = 0;
|
|
2723
|
+
let deadFunctionCount = 0;
|
|
2724
|
+
const functionDecls = [];
|
|
2725
|
+
const visit = (node) => {
|
|
2726
|
+
if (isFunctionLike(node)) {
|
|
2727
|
+
const complexity = computeCyclomatic(node);
|
|
2728
|
+
const lineLength = computeNodeLineLength(source, node);
|
|
2729
|
+
const nestingDepth = computeMaxNestingDepth(node);
|
|
2730
|
+
functionsAnalyzed++;
|
|
2731
|
+
totalComplexity += complexity;
|
|
2732
|
+
totalFunctionLength += lineLength;
|
|
2733
|
+
maxNestingDepth = Math.max(maxNestingDepth, nestingDepth);
|
|
2734
|
+
}
|
|
2735
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
2736
|
+
functionDecls.push(node);
|
|
2737
|
+
}
|
|
2738
|
+
visitEach(node, visit);
|
|
2739
|
+
};
|
|
2740
|
+
visit(source);
|
|
2741
|
+
const functionBodies = raw;
|
|
2742
|
+
for (const fn of functionDecls) {
|
|
2743
|
+
if (isExported(fn)) continue;
|
|
2744
|
+
const name = fn.name?.text;
|
|
2745
|
+
if (!name) continue;
|
|
2746
|
+
const refs = countWholeWord(functionBodies, name);
|
|
2747
|
+
if (refs <= 1) deadFunctionCount++;
|
|
2748
|
+
}
|
|
2749
|
+
return {
|
|
2750
|
+
lines: raw.split(/\r?\n/).length,
|
|
2751
|
+
functionsAnalyzed,
|
|
2752
|
+
totalComplexity,
|
|
2753
|
+
totalFunctionLength,
|
|
2754
|
+
maxNestingDepth,
|
|
2755
|
+
deadFunctionCount
|
|
2756
|
+
};
|
|
2757
|
+
}
|
|
2758
|
+
function computeCyclomatic(fn) {
|
|
2759
|
+
let complexity = 1;
|
|
2760
|
+
const visit = (node) => {
|
|
2761
|
+
switch (node.kind) {
|
|
2762
|
+
case ts.SyntaxKind.IfStatement:
|
|
2763
|
+
case ts.SyntaxKind.ForStatement:
|
|
2764
|
+
case ts.SyntaxKind.ForOfStatement:
|
|
2765
|
+
case ts.SyntaxKind.ForInStatement:
|
|
2766
|
+
case ts.SyntaxKind.WhileStatement:
|
|
2767
|
+
case ts.SyntaxKind.DoStatement:
|
|
2768
|
+
case ts.SyntaxKind.CaseClause:
|
|
2769
|
+
case ts.SyntaxKind.CatchClause:
|
|
2770
|
+
case ts.SyntaxKind.ConditionalExpression:
|
|
2771
|
+
complexity++;
|
|
2772
|
+
break;
|
|
2773
|
+
case ts.SyntaxKind.BinaryExpression: {
|
|
2774
|
+
const be = node;
|
|
2775
|
+
if (be.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || be.operatorToken.kind === ts.SyntaxKind.BarBarToken || be.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
|
|
2776
|
+
complexity++;
|
|
2777
|
+
}
|
|
2778
|
+
break;
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
visitEach(node, visit);
|
|
2782
|
+
};
|
|
2783
|
+
visit(fn);
|
|
2784
|
+
return complexity;
|
|
2785
|
+
}
|
|
2786
|
+
function computeMaxNestingDepth(node) {
|
|
2787
|
+
let maxDepth = 0;
|
|
2788
|
+
const walk = (current, depth) => {
|
|
2789
|
+
const nextDepth = isNestingNode(current) ? depth + 1 : depth;
|
|
2790
|
+
maxDepth = Math.max(maxDepth, nextDepth);
|
|
2791
|
+
visitEach(current, (child) => walk(child, nextDepth));
|
|
2792
|
+
};
|
|
2793
|
+
walk(node, 0);
|
|
2794
|
+
return Math.max(0, maxDepth - 1);
|
|
2795
|
+
}
|
|
2796
|
+
function countCircularDependencyChains(graph) {
|
|
2797
|
+
let cycles = 0;
|
|
2798
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2799
|
+
const inStack = /* @__PURE__ */ new Set();
|
|
2800
|
+
const dfs = (node) => {
|
|
2801
|
+
visited.add(node);
|
|
2802
|
+
inStack.add(node);
|
|
2803
|
+
const deps = graph.get(node) ?? [];
|
|
2804
|
+
for (const dep of deps) {
|
|
2805
|
+
if (!graph.has(dep)) continue;
|
|
2806
|
+
if (!visited.has(dep)) {
|
|
2807
|
+
dfs(dep);
|
|
2808
|
+
} else if (inStack.has(dep)) {
|
|
2809
|
+
cycles++;
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
inStack.delete(node);
|
|
2813
|
+
};
|
|
2814
|
+
for (const node of graph.keys()) {
|
|
2815
|
+
if (!visited.has(node)) dfs(node);
|
|
2816
|
+
}
|
|
2817
|
+
return cycles;
|
|
2818
|
+
}
|
|
2819
|
+
function computeNodeLineLength(source, node) {
|
|
2820
|
+
const start = source.getLineAndCharacterOfPosition(node.getStart(source)).line;
|
|
2821
|
+
const end = source.getLineAndCharacterOfPosition(node.getEnd()).line;
|
|
2822
|
+
return end - start + 1;
|
|
2823
|
+
}
|
|
2824
|
+
function isFunctionLike(node) {
|
|
2825
|
+
return ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isConstructorDeclaration(node);
|
|
2826
|
+
}
|
|
2827
|
+
function isNestingNode(node) {
|
|
2828
|
+
return node.kind === ts.SyntaxKind.IfStatement || node.kind === ts.SyntaxKind.ForStatement || node.kind === ts.SyntaxKind.ForOfStatement || node.kind === ts.SyntaxKind.ForInStatement || node.kind === ts.SyntaxKind.WhileStatement || node.kind === ts.SyntaxKind.DoStatement || node.kind === ts.SyntaxKind.SwitchStatement || node.kind === ts.SyntaxKind.TryStatement || node.kind === ts.SyntaxKind.CatchClause;
|
|
2829
|
+
}
|
|
2830
|
+
function isExported(node) {
|
|
2831
|
+
if (!ts.canHaveModifiers(node)) return false;
|
|
2832
|
+
const modifiers = ts.getModifiers(node);
|
|
2833
|
+
return !!modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
2834
|
+
}
|
|
2835
|
+
function countWholeWord(input, word) {
|
|
2836
|
+
const re = new RegExp(`\\b${escapeRegExp(word)}\\b`, "g");
|
|
2837
|
+
return input.match(re)?.length ?? 0;
|
|
2838
|
+
}
|
|
2839
|
+
function escapeRegExp(input) {
|
|
2840
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2841
|
+
}
|
|
2842
|
+
function round2(n) {
|
|
2843
|
+
return Math.round(n * 100) / 100;
|
|
2844
|
+
}
|
|
2845
|
+
function visitEach(node, cb) {
|
|
2846
|
+
node.forEachChild(cb);
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
// src/reporting/scanners/ui-purpose.ts
|
|
2850
|
+
var UI_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2851
|
+
".tsx",
|
|
2852
|
+
".jsx",
|
|
2853
|
+
".ts",
|
|
2854
|
+
".js",
|
|
2855
|
+
".vue",
|
|
2856
|
+
".svelte",
|
|
2857
|
+
".html",
|
|
2858
|
+
".mdx",
|
|
2859
|
+
".json",
|
|
2860
|
+
".yml",
|
|
2861
|
+
".yaml"
|
|
2862
|
+
]);
|
|
2863
|
+
var HIGH_SIGNAL_DEPENDENCIES = /* @__PURE__ */ new Set([
|
|
2864
|
+
"stripe",
|
|
2865
|
+
"posthog-js",
|
|
2866
|
+
"posthog-node",
|
|
2867
|
+
"@sentry/node",
|
|
2868
|
+
"@sentry/browser",
|
|
2869
|
+
"@sentry/react",
|
|
2870
|
+
"next-auth",
|
|
2871
|
+
"firebase",
|
|
2872
|
+
"auth0",
|
|
2873
|
+
"@auth0/auth0-react",
|
|
2874
|
+
"@supabase/supabase-js",
|
|
2875
|
+
"supabase",
|
|
2876
|
+
"clerk",
|
|
2877
|
+
"@clerk/clerk-js",
|
|
2878
|
+
"@clerk/react"
|
|
2879
|
+
]);
|
|
2880
|
+
var GENERIC_LOW_SIGNAL = /* @__PURE__ */ new Set(["welcome", "home", "click here", "learn more", "submit", "cancel"]);
|
|
2881
|
+
async function scanUiPurpose(rootDir, fileCache, maxItems = 300) {
|
|
2882
|
+
const entries = await fileCache.walkDir(rootDir);
|
|
2883
|
+
const files = entries.filter((e) => e.isFile);
|
|
2884
|
+
const packageJsonEntries = files.filter(
|
|
2885
|
+
(e) => e.relPath.endsWith("package.json") && !e.relPath.includes("node_modules")
|
|
2886
|
+
);
|
|
2887
|
+
const allPkgJsons = [];
|
|
2888
|
+
for (const entry of packageJsonEntries) {
|
|
2889
|
+
try {
|
|
2890
|
+
allPkgJsons.push(JSON.parse(await fileCache.readTextFile(entry.absPath)));
|
|
2891
|
+
} catch {
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
const frameworks = detectFrameworksFromAll(allPkgJsons);
|
|
2895
|
+
const items = [];
|
|
2896
|
+
for (const entry of files) {
|
|
2897
|
+
const ext = extension(entry.relPath);
|
|
2898
|
+
if (!UI_EXTENSIONS.has(ext)) continue;
|
|
2899
|
+
if (!isLikelyUiPath(entry.relPath)) {
|
|
2900
|
+
const routeHints2 = extractRouteHints(entry.relPath, frameworks);
|
|
2901
|
+
if (routeHints2.length > 0) {
|
|
2902
|
+
items.push(...routeHints2.map((r) => ({ ...r, file: entry.relPath })));
|
|
2903
|
+
}
|
|
2904
|
+
continue;
|
|
2905
|
+
}
|
|
2906
|
+
const routeHints = extractRouteHints(entry.relPath, frameworks);
|
|
2907
|
+
if (routeHints.length > 0) {
|
|
2908
|
+
items.push(...routeHints.map((r) => ({ ...r, file: entry.relPath })));
|
|
2909
|
+
}
|
|
2910
|
+
const src = await fileCache.readTextFile(entry.absPath);
|
|
2911
|
+
if (!src || src.length > 512e3) continue;
|
|
2912
|
+
const strings = extractUiStrings(src);
|
|
2913
|
+
for (const text of strings) {
|
|
2914
|
+
const kind = classifyString(text);
|
|
2915
|
+
const weight = scoreString(kind, text);
|
|
2916
|
+
items.push({ kind, value: text, file: entry.relPath, weight });
|
|
2917
|
+
}
|
|
2918
|
+
if (/(featureFlag|FEATURE_FLAG|launchDarkly|isFeatureEnabled|flags\.)/.test(src)) {
|
|
2919
|
+
items.push({ kind: "feature_flag", value: `flags in ${entry.relPath}`, file: entry.relPath, weight: 2 });
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
for (const pkg of allPkgJsons) {
|
|
2923
|
+
const deps = getDependencies(pkg);
|
|
2924
|
+
for (const [name, version] of deps) {
|
|
2925
|
+
if (HIGH_SIGNAL_DEPENDENCIES.has(name)) {
|
|
2926
|
+
items.push({ kind: "dependency", value: `${name}@${version}`, file: "package.json", weight: 4 });
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
const deduped = dedupeByKindValue(items).sort((a, b) => b.weight - a.weight || a.value.localeCompare(b.value));
|
|
2931
|
+
const cappedItems = deduped.slice(0, maxItems);
|
|
2932
|
+
const unknownSignals = buildUnknowns(cappedItems);
|
|
2933
|
+
return {
|
|
2934
|
+
enabled: true,
|
|
2935
|
+
detectedFrameworks: frameworks,
|
|
2936
|
+
evidenceCount: deduped.length,
|
|
2937
|
+
capped: deduped.length > cappedItems.length,
|
|
2938
|
+
topEvidence: cappedItems,
|
|
2939
|
+
unknownSignals
|
|
2940
|
+
};
|
|
2941
|
+
}
|
|
2942
|
+
function extension(relPath) {
|
|
2943
|
+
const i = relPath.lastIndexOf(".");
|
|
2944
|
+
return i === -1 ? "" : relPath.slice(i).toLowerCase();
|
|
2945
|
+
}
|
|
2946
|
+
function isLikelyUiPath(relPath) {
|
|
2947
|
+
const lower = relPath.toLowerCase();
|
|
2948
|
+
return lower.startsWith("pages/") || lower.includes("/pages/") || lower.startsWith("app/") || lower.includes("/app/") || lower.startsWith("components/") || lower.includes("/components/") || lower.startsWith("ui/") || lower.includes("/ui/") || lower.startsWith("views/") || lower.includes("/views/") || lower.includes("/routes") || lower.startsWith("routes") || lower.includes("/router") || lower.startsWith("router") || lower.startsWith("locales/") || lower.includes("/locales/") || lower.startsWith("i18n/") || lower.includes("/i18n/") || lower.endsWith(".html") || lower.endsWith(".mdx");
|
|
2949
|
+
}
|
|
2950
|
+
function detectFrameworksFromAll(pkgJsons) {
|
|
2951
|
+
const hits = /* @__PURE__ */ new Set();
|
|
2952
|
+
for (const pkgJson of pkgJsons) {
|
|
2953
|
+
const deps = {
|
|
2954
|
+
...asRecord(pkgJson.dependencies),
|
|
2955
|
+
...asRecord(pkgJson.devDependencies)
|
|
2956
|
+
};
|
|
2957
|
+
if (deps.next) hits.add("nextjs");
|
|
2958
|
+
if (deps.nuxt || deps.nuxt3) hits.add("nuxt");
|
|
2959
|
+
if (deps.react) hits.add("react");
|
|
2960
|
+
if (deps.vue) hits.add("vue");
|
|
2961
|
+
if (deps.svelte) hits.add("svelte");
|
|
2962
|
+
if (deps["@angular/core"]) hits.add("angular");
|
|
2963
|
+
}
|
|
2964
|
+
return Array.from(hits);
|
|
2965
|
+
}
|
|
2966
|
+
function extractRouteHints(relPath, frameworks) {
|
|
2967
|
+
const items = [];
|
|
2968
|
+
if (frameworks.includes("nextjs")) {
|
|
2969
|
+
const m = relPath.match(/^(?:src\/)?(pages|app)\/(.+)\.(tsx|jsx|ts|js)$/);
|
|
2970
|
+
if (m) {
|
|
2971
|
+
let route = "/" + m[2].replace(/(^|\/)page$/i, "").replace(/(^|\/)route$/i, "").replace(/index$/i, "").replace(/\[(?:\.\.\.)?(.+?)\]/g, ":$1").replace(/\/+/g, "/");
|
|
2972
|
+
if (route !== "/" && route.endsWith("/")) route = route.slice(0, -1);
|
|
2973
|
+
items.push({ kind: "route", value: route || "/", weight: 5 });
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
if (/(^|\/)(routes?|router)\.(ts|js|json)$/.test(relPath) || relPath.includes("/router/")) {
|
|
2977
|
+
items.push({ kind: "route", value: `route config: ${relPath}`, weight: 3 });
|
|
2978
|
+
}
|
|
2979
|
+
return items;
|
|
2980
|
+
}
|
|
2981
|
+
function extractUiStrings(src) {
|
|
2982
|
+
const out = [];
|
|
2983
|
+
const textNodeRegex = />\s*([A-Za-z0-9][^<>]{2,120}?)\s*</g;
|
|
2984
|
+
for (const m of src.matchAll(textNodeRegex)) {
|
|
2985
|
+
const s = normaliseText(m[1] ?? "");
|
|
2986
|
+
if (isUsefulString(s)) out.push(s);
|
|
2987
|
+
}
|
|
2988
|
+
const titleRegex = /<title>\s*([^<]{2,120})\s*<\/title>|title\s*[:=]\s*["'`](.{2,120}?)["'`]/g;
|
|
2989
|
+
for (const m of src.matchAll(titleRegex)) {
|
|
2990
|
+
const s = normaliseText((m[1] ?? m[2] ?? "").trim());
|
|
2991
|
+
if (isUsefulString(s)) out.push(s);
|
|
2992
|
+
}
|
|
2993
|
+
const attrRegex = /(?:aria-label|label|placeholder|alt)\s*=\s*["'`](.{2,120}?)["'`]/g;
|
|
2994
|
+
for (const m of src.matchAll(attrRegex)) {
|
|
2995
|
+
const s = normaliseText(m[1] ?? "");
|
|
2996
|
+
if (isUsefulString(s)) out.push(s);
|
|
2997
|
+
}
|
|
2998
|
+
const jsonValueRegex = /:\s*["'`](.{2,140}?)["'`]\s*[,\n]/g;
|
|
2999
|
+
for (const m of src.matchAll(jsonValueRegex)) {
|
|
3000
|
+
const s = normaliseText(m[1] ?? "");
|
|
3001
|
+
if (isUsefulString(s)) out.push(s);
|
|
3002
|
+
}
|
|
3003
|
+
return out;
|
|
3004
|
+
}
|
|
3005
|
+
function classifyString(s) {
|
|
3006
|
+
const lower = s.toLowerCase();
|
|
3007
|
+
if (/(pricing|plan|billing|subscription|trial|credit)/.test(lower)) return "copy";
|
|
3008
|
+
if (/(sign in|sign up|log in|register|invite|workspace|organization|sso|oauth)/.test(lower)) return "copy";
|
|
3009
|
+
if (/(dashboard|reports|settings|integrations|users|roles|permissions)/.test(lower)) return "heading";
|
|
3010
|
+
if (/(get started|start|scan|generate|export|run|deploy|upgrade|analy[sz]e)/.test(lower)) return "cta";
|
|
3011
|
+
if (/(overview|features|about|documentation|docs)/.test(lower)) return "title";
|
|
3012
|
+
if (/(menu|navigation|sidebar|breadcrumb)/.test(lower)) return "nav";
|
|
3013
|
+
return "copy";
|
|
3014
|
+
}
|
|
3015
|
+
function scoreString(kind, value) {
|
|
3016
|
+
const lower = value.toLowerCase();
|
|
3017
|
+
if (GENERIC_LOW_SIGNAL.has(lower)) return 0;
|
|
3018
|
+
let score = 1;
|
|
3019
|
+
if (kind === "route") score += 4;
|
|
3020
|
+
if (kind === "nav") score += 3;
|
|
3021
|
+
if (kind === "title") score += 2;
|
|
3022
|
+
if (kind === "heading") score += 2;
|
|
3023
|
+
if (kind === "cta") score += 2;
|
|
3024
|
+
if (/(pricing|billing|subscription|auth|security|integration)/.test(lower)) score += 2;
|
|
3025
|
+
if (/(dashboard|report|scan|workspace|project|repository)/.test(lower)) score += 1;
|
|
3026
|
+
return score;
|
|
3027
|
+
}
|
|
3028
|
+
function dedupeByKindValue(items) {
|
|
3029
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3030
|
+
for (const item of items) {
|
|
3031
|
+
const key = `${item.kind}::${item.value.toLowerCase()}`;
|
|
3032
|
+
const prev = seen.get(key);
|
|
3033
|
+
if (!prev || item.weight > prev.weight) {
|
|
3034
|
+
seen.set(key, item);
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
return Array.from(seen.values()).filter((i) => i.weight > 0);
|
|
3038
|
+
}
|
|
3039
|
+
function buildUnknowns(items) {
|
|
3040
|
+
const unknowns = [];
|
|
3041
|
+
const hasPricing = items.some((i) => /pricing|billing|subscription|trial|credit/i.test(i.value));
|
|
3042
|
+
const hasAuth = items.some((i) => /sign in|sign up|login|auth|sso|oauth|invite/i.test(i.value));
|
|
3043
|
+
const hasIntegrations = items.some((i) => /integration|webhook|api key|connector/i.test(i.value));
|
|
3044
|
+
const hasRoutes = items.some((i) => i.kind === "route");
|
|
3045
|
+
if (!hasPricing) unknowns.push("No pricing or billing evidence found.");
|
|
3046
|
+
if (!hasAuth) unknowns.push("No authentication or user access flow evidence found.");
|
|
3047
|
+
if (!hasIntegrations) unknowns.push("No integrations/connectors evidence found.");
|
|
3048
|
+
if (!hasRoutes) unknowns.push("No route structure evidence found.");
|
|
3049
|
+
return unknowns;
|
|
3050
|
+
}
|
|
3051
|
+
function asRecord(value) {
|
|
3052
|
+
if (!value || typeof value !== "object") return {};
|
|
3053
|
+
return value;
|
|
3054
|
+
}
|
|
3055
|
+
function getDependencies(pkgJson) {
|
|
3056
|
+
return Object.entries(asRecord(pkgJson.dependencies));
|
|
3057
|
+
}
|
|
3058
|
+
function normaliseText(s) {
|
|
3059
|
+
return s.replace(/\s+/g, " ").trim();
|
|
3060
|
+
}
|
|
3061
|
+
function isUsefulString(s) {
|
|
3062
|
+
if (!s || s.length < 3 || s.length > 160) return false;
|
|
3063
|
+
if (/^[0-9_./-]+$/.test(s)) return false;
|
|
3064
|
+
if (/^(true|false|null|undefined)$/i.test(s)) return false;
|
|
3065
|
+
if (/[<>{}]/.test(s)) return false;
|
|
3066
|
+
if (/function\s*\(|=>|console\.|import\s+|export\s+/.test(s)) return false;
|
|
3067
|
+
return true;
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
// src/reporting/data/standards-index.ts
|
|
3071
|
+
var standardsIndex = [{ "slug": "adonet-4-8", "name": "ADO.NET 4.8 Specification", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "aicpa-soc-2-tsc", "name": "AICPA SOC 2 (Trust Services Criteria 2017, rev. 2022)", "category": "compliance", "domains": ["security", "governance", "privacy"], "frameworks": ["soc2"], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": "https://www.aicpa-cima.com/resources/landing/system-and-organization-controls-soc-suite-of-services" }, { "slug": "amqp-1-0", "name": "AMQP 1.0 (OASIS)", "category": "messaging-protocol", "domains": ["integration"], "frameworks": [], "projectTypes": ["api", "infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "archimate-3-2", "name": "Archimate 3.2", "category": "modeling", "domains": ["architecture"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "argo-cd-appspec-v1", "name": "Argo CD AppSpec v1", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "aria-1-3", "name": "W3C ARIA 1.3", "category": "accessibility", "domains": ["accessibility", "frontend"], "frameworks": [], "projectTypes": ["web-app", "mobile"], "complianceRelevant": true, "officialUrl": null }, { "slug": "asyncapi-2-6", "name": "AsyncAPI 2.6", "category": "api-spec", "domains": ["api"], "frameworks": [], "projectTypes": ["api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "avro-1-11", "name": "Apache Avro 1.11", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "aws-well-architected-2023", "name": "AWS Well-Architected Framework 2023", "category": "cloud-architecture", "domains": ["cloud", "architecture"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "azure-waf-2024", "name": "Azure Well-Architected Framework 2024", "category": "cloud-architecture", "domains": ["cloud", "architecture"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "backstage-catalog-1-3", "name": "Backstage Software Catalog 1.3", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "bgp-4", "name": "BGP-4 (RFC 4271)", "category": "network-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "bian-v10", "name": "BIAN Service Landscape v10", "category": "cloud-architecture", "domains": ["cloud", "architecture"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "bpmn-2-0-2", "name": "BPMN 2.0.2", "category": "modeling", "domains": ["architecture"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "bsimm-13", "name": "BSIMM13", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "c11", "name": "ISO/IEC 9899:2011 (C11)", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "c18", "name": "ISO/IEC 9899:2018 (C18)", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "capec-3-9", "name": "CAPEC v3.9", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "capnproto-0-9", "name": "Cap\u2019n Proto 0.9", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "cbl-mariner-2-0", "name": "CBL-Mariner OS Spec 2.0", "category": "cloud-architecture", "domains": ["cloud", "architecture"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "cbor-rfc-8949", "name": "CBOR (RFC 8949)", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "ccpa", "name": "CCPA (AB 375)", "category": "compliance", "domains": ["security", "governance"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "cis-kubernetes-1-7", "name": "CIS Benchmarks Kubernetes v1.7", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "cloudevents-1-0", "name": "CloudEvents 1.0", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "cmmi-v2-0", "name": "CMMI v2.0", "category": "software-process", "domains": ["process"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "cmmn-1-1", "name": "CMMN 1.1", "category": "modeling", "domains": ["architecture"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "cncf-cloud-native-v1", "name": "CNCF Cloud-Native Definition v1", "category": "cloud-architecture", "domains": ["cloud", "architecture"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "commonmark-0-30", "name": "CommonMark 0.30", "category": "documentation", "domains": ["documentation"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "cpp-14", "name": "ISO/IEC 14882:2014 (C++14)", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "cpp-17", "name": "ISO/IEC 14882:2017 (C++17)", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "cpp-20", "name": "ISO/IEC 14882:2020 (C++20)", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "cpp-23", "name": "ISO/IEC 14882:2023 (C++23)", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "csharp-11", "name": "ECMA-334 6th (C# 11)", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "csharp-2-0", "name": "ISO/IEC 23270:2006 (C# 2.0)", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "csv-rfc-4180", "name": "CSV (RFC 4180)", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "cwe-4-11", "name": "MITRE CWE 4.11", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "cwt-rfc-8392", "name": "CBOR Web Token (RFC 8392)", "category": "identity-auth", "domains": ["security", "identity"], "frameworks": [], "projectTypes": ["api", "web-app"], "complianceRelevant": true, "officialUrl": null }, { "slug": "cyclonedx-1-6", "name": "CycloneDX 1.6 (SBOM)", "category": "security", "domains": ["security"], "frameworks": ["nist-csf"], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "dicom-2024a", "name": "DICOM PS3.0 2024a", "category": "messaging-protocol", "domains": ["integration"], "frameworks": [], "projectTypes": ["api", "infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "did-core-1-0", "name": "W3C DID Core 1.0", "category": "identity-auth", "domains": ["security", "identity"], "frameworks": [], "projectTypes": ["api", "web-app"], "complianceRelevant": true, "officialUrl": null }, { "slug": "dmn-1-4", "name": "DMN 1.4", "category": "modeling", "domains": ["architecture"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "dns-rfc-1035", "name": "DNS (RFC 1035)", "category": "network-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "docker-compose-v3", "name": "Docker Compose v3", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "dtd-1-2", "name": "DTD 1.2", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "elixir-1-17", "name": "Elixir 1.17", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "envoy-xds-v3", "name": "Envoy xDS v3", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "erlang-26", "name": "Erlang/OTP 26", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "eu-ai-act-2024", "name": "EU AI Act Proposal 2024", "category": "ai-governance", "domains": ["ai", "governance"], "frameworks": [], "projectTypes": ["ml", "any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "fedramp-moderate", "name": "FedRAMP Moderate Rev 5", "category": "compliance", "domains": ["security", "governance"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "fido2-webauthn-l2", "name": "FIDO2 WebAuthn Level 2", "category": "identity-auth", "domains": ["security", "identity"], "frameworks": [], "projectTypes": ["api", "web-app"], "complianceRelevant": true, "officialUrl": null }, { "slug": "fix-5-0-sp2", "name": "FIX Protocol 5.0 SP2", "category": "messaging-protocol", "domains": ["integration"], "frameworks": [], "projectTypes": ["api", "infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "flatbuffers-23-5", "name": "FlatBuffers 23.5", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "ftps-rfc-4217", "name": "FTPS (RFC 4217)", "category": "network-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "gcp-architecture-2023", "name": "Google Cloud Architecture Framework 2023", "category": "cloud-architecture", "domains": ["cloud", "architecture"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "gdpr-eu-2016-679", "name": "GDPR (EU 2016/679)", "category": "compliance", "domains": ["security", "governance"], "frameworks": ["gdpr"], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "git-spec", "name": "Git Spec (Distributed VC)", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "gitops-principles-v1", "name": "GitOps Principles v1", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "go-1-22", "name": "Go 1.22 Spec", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "graphql-2023", "name": "GraphQL July-2023 Spec", "category": "api-spec", "domains": ["api"], "frameworks": [], "projectTypes": ["api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "grpc-v1", "name": "gRPC Protocol v1", "category": "api-spec", "domains": ["api"], "frameworks": [], "projectTypes": ["api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "haskell-2010", "name": "Haskell 2010 Report", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "helm-chart-3-10", "name": "Helm Chart Spec v3.10", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "hipaa-security-rule", "name": "HIPAA Security Rule", "category": "compliance", "domains": ["security", "governance"], "frameworks": ["hipaa"], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "hl7-fhir-r5", "name": "HL7 FHIR R5", "category": "messaging-protocol", "domains": ["integration"], "frameworks": [], "projectTypes": ["api", "infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "http-1-0", "name": "HTTP/1.0 (RFC 1945)", "category": "web-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["web-app", "api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "http-1-1", "name": "HTTP/1.1 (RFC 7230\u20137235)", "category": "web-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["web-app", "api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "http-2", "name": "HTTP/2 (RFC 7540)", "category": "web-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["web-app", "api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "http-3", "name": "HTTP/3 (RFC 9114)", "category": "web-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["web-app", "api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "http-3-rfc-9114", "name": "HTTP/3 (RFC 9114)", "category": "web-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["web-app", "api"], "complianceRelevant": false, "officialUrl": "https://www.rfc-editor.org/rfc/rfc9114" }, { "slug": "iec-61850-ed2-1", "name": "IEC 61850 Edition 2.1", "category": "messaging-protocol", "domains": ["integration"], "frameworks": [], "projectTypes": ["api", "infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "ieee-1012-2016", "name": "IEEE 1012-2016 (Verification & Validation)", "category": "software-process", "domains": ["process"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "ieee-11073-20702", "name": "IEEE 11073-20702", "category": "messaging-protocol", "domains": ["integration"], "frameworks": [], "projectTypes": ["api", "infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "ieee-14764-2006", "name": "IEEE 14764-2006 (Software Maintenance)", "category": "software-process", "domains": ["process"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "ieee-2410-2019", "name": "IEEE 2410-2019 (Bio-Open ID)", "category": "biometric", "domains": ["security", "identity"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "ieee-7001-2021", "name": "IEEE 7001-2021 (AI Transparency)", "category": "ai-governance", "domains": ["ai", "governance"], "frameworks": [], "projectTypes": ["ml", "any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "ieee-7002-2022", "name": "IEEE 7002-2022 (AI Privacy Data)", "category": "ai-governance", "domains": ["ai", "governance"], "frameworks": [], "projectTypes": ["ml", "any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "ieee-730-2014", "name": "IEEE 730-2014 (Software Quality)", "category": "software-process", "domains": ["process"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "ieee-802-11ax", "name": "IEEE 802.11ax (Wi-Fi 6)", "category": "network-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "ieee-802-15-4-2020", "name": "IEEE 802.15.4-2020 (IoT WPAN)", "category": "network-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "ieee-802-1q-2018", "name": "IEEE 802.1Q-2018 (VLAN)", "category": "network-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "ieee-802-3-2022", "name": "IEEE 802.3-2022 (Ethernet)", "category": "network-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "ieee-829-2008", "name": "IEEE 829-2008 (Test Docs)", "category": "software-process", "domains": ["process"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "incits-459-2011", "name": "ANSI INCITS 459-2011 (JSON)", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "ipv4", "name": "IPv4 (RFC 791)", "category": "network-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "ipv6", "name": "IPv6 (RFC 8200)", "category": "network-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-12207-2017", "name": "ISO/IEC 12207:2017", "category": "software-process", "domains": ["process"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-15288-2015", "name": "ISO/IEC 15288:2015", "category": "software-process", "domains": ["process"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-15408-1-2022", "name": "ISO/IEC 15408-1:2022 (Common Criteria)", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-15504", "name": "ISO/IEC 15504 (SPICE)", "category": "software-process", "domains": ["process"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-15926-2", "name": "ISO 15926 Part 2", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-18013-5-2021", "name": "ISO/IEC 18013-5:2021 (Mobile DL)", "category": "identity-auth", "domains": ["security", "identity"], "frameworks": [], "projectTypes": ["api", "web-app"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-19770-1-2017", "name": "ISO/IEC 19770-1:2017 (IT Asset)", "category": "quality-management", "domains": ["process", "governance"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-19794-5-2011", "name": "ISO/IEC 19794-5:2011 (Face Images)", "category": "biometric", "domains": ["security", "identity"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-20022", "name": "ISO 20022 (Financial Messaging)", "category": "messaging-protocol", "domains": ["integration"], "frameworks": [], "projectTypes": ["api", "infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-20922-2016", "name": "ISO/IEC 20922:2016 (AMQP 1.0)", "category": "messaging-protocol", "domains": ["integration"], "frameworks": [], "projectTypes": ["api", "infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-21502-2020", "name": "ISO 21502:2020 (Project Management)", "category": "project-management", "domains": ["process"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-21504-2022", "name": "ISO 21504:2022 (Portfolio Management)", "category": "project-management", "domains": ["process"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-21511-2018", "name": "ISO 21511:2018 (Work Breakdown Structure)", "category": "project-management", "domains": ["process"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-22989-2022", "name": "ISO/IEC 22989:2022 (AI Concepts)", "category": "ai-governance", "domains": ["ai", "governance"], "frameworks": [], "projectTypes": ["ml", "any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-23053-2022", "name": "ISO/IEC 23053:2022 (AI Lifecycle)", "category": "ai-governance", "domains": ["ai", "governance"], "frameworks": [], "projectTypes": ["ml", "any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-23659-2024", "name": "ISO/IEC 23659:2024 (AI Risk Mgmt)", "category": "ai-governance", "domains": ["ai", "governance"], "frameworks": [], "projectTypes": ["ml", "any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-2382", "name": "ISO/IEC 2382 (IT Vocabulary)", "category": "software-process", "domains": ["process"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-24772-2-2023", "name": "ISO/IEC TR 24772-2:2023 (Safer Programming)", "category": "software-process", "domains": ["process"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-25010-2023", "name": "ISO/IEC 25010:2023 (Quality Model)", "category": "software-process", "domains": ["process"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-27001-2022", "name": "ISO/IEC 27001:2022", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-27002-2022", "name": "ISO/IEC 27002:2022", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-27017-2015", "name": "ISO/IEC 27017:2015 (Cloud Controls)", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-27018-2019", "name": "ISO/IEC 27018:2019 (Cloud PII)", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-27701-2019", "name": "ISO/IEC 27701:2019 (Privacy)", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-29119-1-2022", "name": "ISO/IEC 29119-1:2022 (Software Testing)", "category": "software-process", "domains": ["process"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-29134-2017", "name": "ISO/IEC 29134:2017 (PIA)", "category": "compliance", "domains": ["security", "governance"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-29147-2018", "name": "ISO/IEC 29147:2018 (Vuln Disclosure)", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-30107-3-2017", "name": "ISO/IEC 30107-3:2017 (PAD)", "category": "biometric", "domains": ["security", "identity"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-30111-2019", "name": "ISO/IEC 30111:2019 (Vulnerability Handling)", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-38500-2015", "name": "ISO/IEC 38500:2015 (IT Governance)", "category": "quality-management", "domains": ["process", "governance"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-38505-1-2017", "name": "ISO/IEC 38505-1:2017 (Data Governance)", "category": "compliance", "domains": ["security", "governance"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-40500-2012", "name": "ISO/IEC 40500:2012 (WCAG 2.0)", "category": "accessibility", "domains": ["accessibility", "frontend"], "frameworks": [], "projectTypes": ["web-app", "mobile"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-7816-4-2020", "name": "ISO/IEC 7816-4:2020 (Smart Cards)", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-8601-2019", "name": "ISO 8601:2019 (Date/Time)", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-9001-2015", "name": "ISO 9001:2015", "category": "quality-management", "domains": ["process", "governance"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-9126", "name": "ISO 9126 (Quality Model \u2013 Superseded)", "category": "quality-management", "domains": ["process", "governance"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "iso-9798-3-2014", "name": "ISO/IEC 9798-3:2014", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "iso-iec-27001-2022", "name": "ISO/IEC 27001:2022", "category": "compliance", "domains": ["security", "governance"], "frameworks": ["gdpr", "hipaa", "iso-27001", "soc2"], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": "https://www.iso.org/standard/27001" }, { "slug": "iso-iec-27002-2022", "name": "ISO/IEC 27002:2022", "category": "compliance", "domains": ["security"], "frameworks": ["iso-27001"], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": "https://www.iso.org/standard/75652.html" }, { "slug": "iso-iec-42001-2023", "name": "ISO/IEC 42001:2023 (AI management system)", "category": "ai-governance", "domains": ["ai", "governance"], "frameworks": [], "projectTypes": ["ml", "any"], "complianceRelevant": true, "officialUrl": "https://www.iso.org/standard/81230.html" }, { "slug": "istio-api-1-21", "name": "Istio API v1.21", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "jaeger-trace-v1", "name": "Jaeger Trace Spec v1", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "java-se-17", "name": "Java SE 17 (JSR 392)", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "java-se-8", "name": "Java SE 8 (JSR 337)", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "javascript-es2024", "name": "ECMA-262 2024 (ES2024)", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "javascript-es6", "name": "ECMA-262 2015 (ES6)", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "jdbc-4-3", "name": "JDBC 4.3", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "json-api-1-1", "name": "JSON:API 1.1", "category": "api-spec", "domains": ["api"], "frameworks": [], "projectTypes": ["api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "json-rfc-8259", "name": "JSON (RFC 8259)", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "json-rpc-2-0", "name": "JSON-RPC 2.0", "category": "api-spec", "domains": ["api"], "frameworks": [], "projectTypes": ["api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "json-schema-2020-12", "name": "JSON Schema 2020-12", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "jws-rfc-7515", "name": "RFC 7515 (JWS)", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "jwt-rfc-7519", "name": "JWT (RFC 7519)", "category": "identity-auth", "domains": ["security", "identity"], "frameworks": [], "projectTypes": ["api", "web-app"], "complianceRelevant": true, "officialUrl": null }, { "slug": "kafka-protocol-3-7", "name": "Kafka Protocol 3.7", "category": "messaging-protocol", "domains": ["integration"], "frameworks": [], "projectTypes": ["api", "infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "kotlin-2-0", "name": "Kotlin 2.0 Spec", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "kubernetes-1-28", "name": "Kubernetes API v1.28", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "matter-1-3", "name": "Matter 1.3", "category": "network-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "messagepack-2-3", "name": "MessagePack 2.3", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "mitre-attack-v14", "name": "MITRE ATT&CK v14", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "modbus-tcp-1-1b", "name": "Modbus TCP 1.1b", "category": "messaging-protocol", "domains": ["integration"], "frameworks": [], "projectTypes": ["api", "infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "mqtt-5-0", "name": "MQTT 5.0 (OASIS)", "category": "messaging-protocol", "domains": ["integration"], "frameworks": [], "projectTypes": ["api", "infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "nats-2-9", "name": "NATS Protocol 2.9", "category": "messaging-protocol", "domains": ["integration"], "frameworks": [], "projectTypes": ["api", "infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "nfs-4-1", "name": "NFS 4.1 (RFC 8881)", "category": "network-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "nist-800-171-r3", "name": "NIST SP 800-171 Rev 3", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "nist-800-53-r5", "name": "NIST SP 800-53 Rev 5", "category": "security", "domains": ["security"], "frameworks": ["nist-csf"], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "nist-ai-rmf-1-0", "name": "NIST AI RMF 1.0", "category": "ai-governance", "domains": ["ai", "governance"], "frameworks": [], "projectTypes": ["ml", "any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "nist-ccra", "name": "NIST Cloud Computing Ref Arch SP 500-292", "category": "cloud-architecture", "domains": ["cloud", "architecture"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "nist-csf-2-0", "name": "NIST Cybersecurity Framework (CSF) 2.0", "category": "security", "domains": ["security", "governance"], "frameworks": ["nist-csf"], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": "https://www.nist.gov/cyberframework" }, { "slug": "oas-json-schema-2020-12", "name": "OpenAPI JSON Schema Dialect 2020-12", "category": "api-spec", "domains": ["api"], "frameworks": [], "projectTypes": ["api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "oauth-1-0a", "name": "OAuth 1.0a (RFC 5849)", "category": "identity-auth", "domains": ["security", "identity"], "frameworks": [], "projectTypes": ["api", "web-app"], "complianceRelevant": true, "officialUrl": null }, { "slug": "oauth-2-0", "name": "OAuth 2.0 (RFC 6749)", "category": "identity-auth", "domains": ["security", "identity"], "frameworks": [], "projectTypes": ["api", "web-app"], "complianceRelevant": true, "officialUrl": null }, { "slug": "oauth-2-1", "name": "OAuth 2.1 (Draft)", "category": "identity-auth", "domains": ["security", "identity"], "frameworks": ["iso-27001", "soc2"], "projectTypes": ["api", "web-app"], "complianceRelevant": true, "officialUrl": null }, { "slug": "ocaml-5-2", "name": "OCaml 5.2 Spec", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "oci-image-1-1", "name": "OCI Image Spec 1.1", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "oci-runtime-1-1", "name": "OCI Runtime Spec 1.1", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "odata-4-01", "name": "OData 4.01", "category": "api-spec", "domains": ["api"], "frameworks": [], "projectTypes": ["api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "odbc-3-8", "name": "ODBC 3.8", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "odf-1-3", "name": "OpenDocument 1.3 (ISO/IEC 26300-1)", "category": "document-format", "domains": ["data"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "omg-mda-1-1", "name": "OMG MDA Guide v1.1", "category": "cloud-architecture", "domains": ["cloud", "architecture"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "ooxml-ecma-376-4", "name": "Office Open XML (ECMA-376 4th)", "category": "document-format", "domains": ["data"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "opc-ua-1-05", "name": "OPC UA 1.05", "category": "messaging-protocol", "domains": ["integration"], "frameworks": [], "projectTypes": ["api", "infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "openapi-2-0", "name": "OpenAPI Specification 2.0 (Swagger)", "category": "api-spec", "domains": ["api"], "frameworks": [], "projectTypes": ["api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "openapi-3-0", "name": "OpenAPI Specification 3.0", "category": "api-spec", "domains": ["api"], "frameworks": [], "projectTypes": ["api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "openapi-3-1", "name": "OpenAPI Specification 3.1", "category": "api-spec", "domains": ["api"], "frameworks": [], "projectTypes": ["api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "openapi-specification-3-1-0", "name": "OpenAPI Specification 3.1.0", "category": "api-spec", "domains": ["api"], "frameworks": [], "projectTypes": ["api"], "complianceRelevant": false, "officialUrl": "https://spec.openapis.org/oas/v3.1.0" }, { "slug": "openflow-1-5", "name": "OpenFlow 1.5", "category": "network-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "openid-connect-1-0", "name": "OpenID Connect 1.0", "category": "identity-auth", "domains": ["security", "identity"], "frameworks": [], "projectTypes": ["api", "web-app"], "complianceRelevant": true, "officialUrl": null }, { "slug": "openssf-scorecard-4-10", "name": "OpenSSF Scorecard 4.10", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "opentelemetry-1-0-0", "name": "OpenTelemetry 1.0.0", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "opentracing-1-3", "name": "OpenTracing 1.3", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "orc-1-8", "name": "Apache ORC 1.8", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "owasp-api-top-10-2023", "name": "OWASP API Security Top 10 (2023)", "category": "security", "domains": ["security"], "frameworks": ["pci-dss", "soc2"], "projectTypes": ["api"], "complianceRelevant": true, "officialUrl": "https://owasp.org/API-Security/editions/2023/en/0x11-t10/" }, { "slug": "owasp-asvs-4-0", "name": "OWASP ASVS 4.0", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "owasp-asvs-5-0", "name": "OWASP Application Security Verification Standard (ASVS) 5.0", "category": "security", "domains": ["security"], "frameworks": ["hipaa", "iso-27001", "pci-dss", "soc2"], "projectTypes": ["web-app", "api"], "complianceRelevant": true, "officialUrl": "https://owasp.org/www-project-application-security-verification-standard/" }, { "slug": "owasp-top10-2023", "name": "OWASP Top 10 2023", "category": "security", "domains": ["security"], "frameworks": ["pci-dss"], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "parquet-2-0", "name": "Apache Parquet 2.0", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "pci-dss-4-0", "name": "PCI-DSS 4.0", "category": "compliance", "domains": ["security", "governance"], "frameworks": ["pci-dss"], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "pdf-1-7", "name": "PDF 1.7 (ISO 32000-1)", "category": "document-format", "domains": ["data"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "pdfa-4", "name": "PDF/A-4 (ISO 19005-4:2020)", "category": "document-format", "domains": ["data"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "pep-8", "name": "Python PEP 8 (Style Guide)", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "perl-5-38", "name": "Perl 5.38 Syntax", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "php-8-3", "name": "PHP 8.3 Spec", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "pkcs12-v1-1", "name": "PKCS #12 v1.1", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "pkcs7-rfc-5652", "name": "PKCS #7 / CMS (RFC 5652)", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "pmi-pmbok-7", "name": "PMI PMBOK 7th Edition", "category": "project-management", "domains": ["process"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "prince2-7", "name": "PRINCE2 7", "category": "project-management", "domains": ["process"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "prolog-1995", "name": "ISO/IEC 13211-1:1995 (Prolog)", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "prometheus-expo-0-0-4", "name": "Prometheus Exposition Format 0.0.4", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "protobuf-v3", "name": "Protocol Buffers v3", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "python-3-12", "name": "Python 3.12 (PEP 693)", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "restructuredtext-1-0", "name": "reStructuredText 1.0", "category": "documentation", "domains": ["documentation"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "rfc-2119", "name": "RFC 2119 (Keyword Conventions)", "category": "documentation", "domains": ["documentation"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "rfc-3339", "name": "RFC 3339 (Date/Time Format)", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "rfc-5280", "name": "RFC 5280 (PKIX)", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "rfc-6962", "name": "RFC 6962 (Cert Transparency)", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "rfc-7636", "name": "RFC 7636 (PKCE)", "category": "identity-auth", "domains": ["security", "identity"], "frameworks": [], "projectTypes": ["api", "web-app"], "complianceRelevant": true, "officialUrl": null }, { "slug": "rfc-9081", "name": "RFC 9081 (IPFS HTTP Gateway)", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "rfc-9193", "name": "RFC 9193 (SFrame Media Encryption)", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "ruby-3-1", "name": "ISO/IEC 30170:2022 (Ruby 3.1)", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "rust-2024", "name": "Rust 1.78 Edition 2024", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "safe-6-0", "name": "SAFe 6.0", "category": "project-management", "domains": ["process"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "saml-2-0", "name": "SAML 2.0 (OASIS)", "category": "identity-auth", "domains": ["security", "identity"], "frameworks": [], "projectTypes": ["api", "web-app"], "complianceRelevant": true, "officialUrl": null }, { "slug": "sbvr-1-5", "name": "SBVR 1.5", "category": "modeling", "domains": ["architecture"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "scala-3-4", "name": "Scala 3.4 Reference", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "scim-2-0", "name": "SCIM 2.0 (RFC 7644)", "category": "identity-auth", "domains": ["security", "identity"], "frameworks": [], "projectTypes": ["api", "web-app"], "complianceRelevant": true, "officialUrl": null }, { "slug": "section-508-2017", "name": "Section 508 Refresh (2017)", "category": "accessibility", "domains": ["accessibility", "frontend"], "frameworks": [], "projectTypes": ["web-app", "mobile"], "complianceRelevant": true, "officialUrl": null }, { "slug": "semver-2-0-0", "name": "SemVer 2.0.0", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "sftp-rfc-9134", "name": "SFTP (RFC 9134)", "category": "network-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "slsa-v1", "name": "SLSA v1 (Supply-Chain Levels)", "category": "security", "domains": ["security"], "frameworks": ["iso-27001", "nist-csf"], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "smb-3-1-1", "name": "SMB 3.1.1", "category": "network-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "snmp-v3", "name": "SNMP v3 (RFC 3411-3418)", "category": "network-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "soap-1-2", "name": "SOAP 1.2", "category": "api-spec", "domains": ["api"], "frameworks": [], "projectTypes": ["api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "spdx-3-0", "name": "SPDX 3.0", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "spinnaker-spec", "name": "Spinnaker Deployment Spec", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "sql-1986", "name": "ANSI SQL-86", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "sql-1987", "name": "ISO/IEC 9075:1987 (SQL-87)", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "sql-1989", "name": "ANSI SQL-89", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "sql-1992", "name": "ISO/IEC 9075:1992 (SQL-92)", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "sql-1999", "name": "ISO/IEC 9075:1999 (SQL:1999)", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "sql-2003", "name": "ISO/IEC 9075:2003 (SQL:2003)", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "sql-2006", "name": "ISO/IEC 9075:2006 (SQL:2006)", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "sql-2008", "name": "ISO/IEC 9075:2008 (SQL:2008)", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "sql-2011", "name": "ISO/IEC 9075:2011 (SQL:2011)", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "sql-2016", "name": "ISO/IEC 9075:2016 (SQL:2016)", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "sql-2019", "name": "ISO/IEC 9075:2019 (SQL:2019)", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "sql-2023", "name": "SQL:2023 (ISO/IEC 9075:2023)", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": "https://www.iso.org/standard/76583.html" }, { "slug": "sql-foundation-2016", "name": "SQL Foundation Part 2:2016", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "sql-json-2019", "name": "SQL/JSON Part 15:2019", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "sql-object-2023", "name": "SQL/Object Language Part 10:2023", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "sql-psm-2016", "name": "SQL/PSM Part 4:2016", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "sql-xml-2011", "name": "SQL/XML Part 14:2011", "category": "database-sql", "domains": ["data"], "frameworks": [], "projectTypes": ["data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "ssh-2-0", "name": "SSH 2.0 (RFC 4251)", "category": "network-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "stomp-1-2", "name": "STOMP 1.2", "category": "messaging-protocol", "domains": ["integration"], "frameworks": [], "projectTypes": ["api", "infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "swagger-1-2", "name": "Swagger 1.2", "category": "api-spec", "domains": ["api"], "frameworks": [], "projectTypes": ["api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "swift-5-9", "name": "Swift 5.9 Language Guide", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "sysml-1-6", "name": "SysML 1.6", "category": "modeling", "domains": ["architecture"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "terraform-hcl-2-0", "name": "Terraform HCL 2.0", "category": "devops", "domains": ["devops"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "thrift-0-16", "name": "Apache Thrift 0.16", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "tls-1-0", "name": "TLS 1.0 (RFC 2246)", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "tls-1-1", "name": "TLS 1.1 (RFC 4346)", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "tls-1-2", "name": "TLS 1.2 (RFC 5246)", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "tls-1-3", "name": "TLS 1.3 (RFC 8446)", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "togaf-10", "name": "TOGAF 10", "category": "cloud-architecture", "domains": ["cloud", "architecture"], "frameworks": [], "projectTypes": ["infra", "any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "typescript-5-4", "name": "TypeScript 5.4 Spec", "category": "programming-language", "domains": ["language"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "uk-gdpr-2021", "name": "UK GDPR 2021", "category": "compliance", "domains": ["security", "governance"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "uml-2-5-1", "name": "UML 2.5.1", "category": "modeling", "domains": ["architecture"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": false, "officialUrl": null }, { "slug": "unicode-15-1", "name": "Unicode 15.1", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "utf8-rfc-3629", "name": "UTF-8 (RFC 3629)", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "vc-data-2-0", "name": "VC Data Model 2.0", "category": "identity-auth", "domains": ["security", "identity"], "frameworks": [], "projectTypes": ["api", "web-app"], "complianceRelevant": true, "officialUrl": null }, { "slug": "wcag-2-2", "name": "W3C WCAG 2.2", "category": "accessibility", "domains": ["accessibility", "frontend"], "frameworks": [], "projectTypes": ["web-app", "mobile"], "complianceRelevant": true, "officialUrl": null }, { "slug": "websocket-rfc-6455", "name": "WebSocket (RFC 6455)", "category": "web-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["web-app", "api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "ws-security-1-2", "name": "WS-Security 1.2", "category": "api-spec", "domains": ["api"], "frameworks": [], "projectTypes": ["api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "wsdl-2-0", "name": "WSDL 2.0", "category": "api-spec", "domains": ["api"], "frameworks": [], "projectTypes": ["api"], "complianceRelevant": false, "officialUrl": null }, { "slug": "x509-2017", "name": "ISO/IEC 9594-8:2017 (X.509)", "category": "security", "domains": ["security"], "frameworks": [], "projectTypes": ["any"], "complianceRelevant": true, "officialUrl": null }, { "slug": "xml-1-0", "name": "XML 1.0 (Fifth Edition)", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "xml-namespaces-1-1", "name": "XML Namespaces 1.1", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "xsd-1-0", "name": "XML Schema Definition 1.0", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "xsd-1-1", "name": "XML Schema Definition 1.1", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "xslt-1-0", "name": "XSLT 1.0", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "xslt-2-0", "name": "XSLT 2.0", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "xslt-3-0", "name": "XSLT 3.0", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "yaml-1-2", "name": "YAML 1.2", "category": "data-format", "domains": ["data"], "frameworks": [], "projectTypes": ["api", "data"], "complianceRelevant": false, "officialUrl": null }, { "slug": "zigbee-3-0", "name": "Zigbee 3.0", "category": "network-protocol", "domains": ["networking"], "frameworks": [], "projectTypes": ["infra"], "complianceRelevant": false, "officialUrl": null }, { "slug": "zmtp-3-1", "name": "ZeroMQ ZMTP 3.1", "category": "messaging-protocol", "domains": ["integration"], "frameworks": [], "projectTypes": ["api", "infra"], "complianceRelevant": false, "officialUrl": null }];
|
|
3072
|
+
var complianceFrameworks = [{ "id": "iso-27001", "name": "ISO/IEC 27001:2022", "memberStandards": ["iso-iec-27001-2022", "iso-iec-27002-2022", "owasp-asvs-5-0", "oauth-2-1", "slsa-v1"], "controls": [{ "id": "A.5.15", "name": "Access control", "domain": "access" }, { "id": "A.5.16", "name": "Identity management", "domain": "identity" }, { "id": "A.8.5", "name": "Secure authentication", "domain": "identity" }, { "id": "A.8.8", "name": "Management of technical vulnerabilities", "domain": "supply-chain" }, { "id": "A.8.23", "name": "Web filtering / network controls", "domain": "transport" }, { "id": "A.8.24", "name": "Use of cryptography", "domain": "crypto" }, { "id": "A.8.15", "name": "Logging", "domain": "logging" }, { "id": "A.8.28", "name": "Secure coding", "domain": "appsec" }] }, { "id": "soc2", "name": "SOC 2 (Trust Services Criteria)", "memberStandards": ["aicpa-soc-2-tsc", "iso-iec-27001-2022", "owasp-asvs-5-0", "owasp-api-top-10-2023", "oauth-2-1"], "controls": [{ "id": "CC6.1", "name": "Logical access security", "domain": "identity" }, { "id": "CC6.3", "name": "Role-based access / least privilege", "domain": "access" }, { "id": "CC6.6", "name": "Boundary protection / abuse control", "domain": "transport" }, { "id": "CC6.7", "name": "Encryption in transit and at rest", "domain": "crypto" }, { "id": "CC7.1", "name": "Vulnerability detection", "domain": "supply-chain" }, { "id": "CC7.2", "name": "Security event monitoring", "domain": "logging" }, { "id": "CC8.1", "name": "Change management / secure SDLC", "domain": "appsec" }] }, { "id": "pci-dss", "name": "PCI DSS 4.0", "memberStandards": ["pci-dss-4-0", "owasp-asvs-5-0", "owasp-api-top-10-2023", "owasp-top10-2023"], "controls": [{ "id": "Req.3", "name": "Protect stored account data", "domain": "crypto" }, { "id": "Req.4", "name": "Protect data in transit", "domain": "transport" }, { "id": "Req.6", "name": "Secure systems and software", "domain": "appsec" }, { "id": "Req.8", "name": "Identify users and authenticate access", "domain": "identity" }, { "id": "Req.10", "name": "Log and monitor access", "domain": "logging" }] }, { "id": "nist-csf", "name": "NIST Cybersecurity Framework 2.0", "memberStandards": ["nist-csf-2-0", "nist-800-53-r5", "slsa-v1", "cyclonedx-1-6"], "controls": [{ "id": "GV", "name": "Govern", "domain": "governance" }, { "id": "ID.RA", "name": "Risk assessment", "domain": "supply-chain" }, { "id": "PR.AA", "name": "Identity management & access control", "domain": "identity" }, { "id": "PR.DS", "name": "Data security", "domain": "crypto" }, { "id": "DE.CM", "name": "Continuous monitoring", "domain": "logging" }] }, { "id": "hipaa", "name": "HIPAA Security Rule", "memberStandards": ["hipaa-security-rule", "iso-iec-27001-2022", "owasp-asvs-5-0"], "controls": [{ "id": "164.312(a)", "name": "Access control", "domain": "access" }, { "id": "164.312(e)", "name": "Transmission security", "domain": "transport" }, { "id": "164.312(b)", "name": "Audit controls", "domain": "logging" }] }, { "id": "gdpr", "name": "GDPR (EU 2016/679)", "memberStandards": ["gdpr-eu-2016-679", "iso-iec-27001-2022"], "controls": [{ "id": "Art.32", "name": "Security of processing", "domain": "crypto" }, { "id": "Art.30", "name": "Records of processing activities", "domain": "governance" }, { "id": "Art.17", "name": "Right to erasure", "domain": "privacy" }] }];
|
|
3073
|
+
|
|
3074
|
+
// src/reporting/scanners/standards-mapper.ts
|
|
3075
|
+
var WEB_FRAMEWORK_RE = new RegExp(
|
|
3076
|
+
"\\b(" + [
|
|
3077
|
+
// JS/TS UI
|
|
3078
|
+
"react",
|
|
3079
|
+
"preact",
|
|
3080
|
+
"next(?:\\.?js)?",
|
|
3081
|
+
"remix",
|
|
3082
|
+
"gatsby",
|
|
3083
|
+
"vue",
|
|
3084
|
+
"nuxt",
|
|
3085
|
+
"angular",
|
|
3086
|
+
"svelte(?:kit)?",
|
|
3087
|
+
"solid(?:js|start)?",
|
|
3088
|
+
"qwik",
|
|
3089
|
+
"astro",
|
|
3090
|
+
"ember",
|
|
3091
|
+
"backbone",
|
|
3092
|
+
"alpine",
|
|
3093
|
+
"htmx",
|
|
3094
|
+
"hotwire",
|
|
3095
|
+
"turbo",
|
|
3096
|
+
"stimulus",
|
|
3097
|
+
"inertia",
|
|
3098
|
+
// .NET UI
|
|
3099
|
+
"blazor",
|
|
3100
|
+
"razor",
|
|
3101
|
+
"maui",
|
|
3102
|
+
"wpf",
|
|
3103
|
+
"winforms",
|
|
3104
|
+
"xamarin",
|
|
3105
|
+
"uno",
|
|
3106
|
+
// Java/Kotlin/Scala UI
|
|
3107
|
+
"jsf",
|
|
3108
|
+
"thymeleaf",
|
|
3109
|
+
"vaadin",
|
|
3110
|
+
"wicket",
|
|
3111
|
+
"gwt",
|
|
3112
|
+
"compose(?: multiplatform)?",
|
|
3113
|
+
// PHP UI
|
|
3114
|
+
"livewire",
|
|
3115
|
+
"filament",
|
|
3116
|
+
"blade",
|
|
3117
|
+
// Mobile / cross-platform UI
|
|
3118
|
+
"flutter",
|
|
3119
|
+
"swiftui",
|
|
3120
|
+
"jetpack",
|
|
3121
|
+
"react native"
|
|
3122
|
+
].join("|") + ")\\b"
|
|
3123
|
+
);
|
|
3124
|
+
var API_FRAMEWORK_RE = new RegExp(
|
|
3125
|
+
"\\b(" + [
|
|
3126
|
+
// Node/TS
|
|
3127
|
+
"express",
|
|
3128
|
+
"fastify",
|
|
3129
|
+
"nest(?:js)?",
|
|
3130
|
+
"koa",
|
|
3131
|
+
"hapi",
|
|
3132
|
+
"hono",
|
|
3133
|
+
"adonis",
|
|
3134
|
+
"sails",
|
|
3135
|
+
"loopback",
|
|
3136
|
+
"restify",
|
|
3137
|
+
"feathers",
|
|
3138
|
+
"h3",
|
|
3139
|
+
"nitro",
|
|
3140
|
+
"trpc",
|
|
3141
|
+
// Python
|
|
3142
|
+
"django",
|
|
3143
|
+
"flask",
|
|
3144
|
+
"fastapi",
|
|
3145
|
+
"starlette",
|
|
3146
|
+
"tornado",
|
|
3147
|
+
"pyramid",
|
|
3148
|
+
"sanic",
|
|
3149
|
+
"aiohttp",
|
|
3150
|
+
"bottle",
|
|
3151
|
+
"falcon",
|
|
3152
|
+
"cherrypy",
|
|
3153
|
+
"quart",
|
|
3154
|
+
"litestar",
|
|
3155
|
+
"connexion",
|
|
3156
|
+
// Java / Kotlin / Scala
|
|
3157
|
+
"spring(?:boot)?",
|
|
3158
|
+
"quarkus",
|
|
3159
|
+
"micronaut",
|
|
3160
|
+
"vert\\.?x",
|
|
3161
|
+
"dropwizard",
|
|
3162
|
+
"jersey",
|
|
3163
|
+
"jax-?rs",
|
|
3164
|
+
"struts",
|
|
3165
|
+
"javalin",
|
|
3166
|
+
"helidon",
|
|
3167
|
+
"jhipster",
|
|
3168
|
+
"ktor",
|
|
3169
|
+
"http4s",
|
|
3170
|
+
"finatra",
|
|
3171
|
+
"scalatra",
|
|
3172
|
+
"lagom",
|
|
3173
|
+
"play",
|
|
3174
|
+
"akka(?: ?http)?",
|
|
3175
|
+
"spark java",
|
|
3176
|
+
"ratpack",
|
|
3177
|
+
// .NET
|
|
3178
|
+
"asp\\.?net(?: ?core)?",
|
|
3179
|
+
"signalr",
|
|
3180
|
+
"nancy",
|
|
3181
|
+
"servicestack",
|
|
3182
|
+
"carter",
|
|
3183
|
+
"minimal ?api",
|
|
3184
|
+
"web ?api",
|
|
3185
|
+
"mvc",
|
|
3186
|
+
// Go
|
|
3187
|
+
"gin",
|
|
3188
|
+
"echo",
|
|
3189
|
+
"fiber",
|
|
3190
|
+
"chi",
|
|
3191
|
+
"gorilla",
|
|
3192
|
+
"beego",
|
|
3193
|
+
"revel",
|
|
3194
|
+
"buffalo",
|
|
3195
|
+
"iris",
|
|
3196
|
+
"gqlgen",
|
|
3197
|
+
"mux",
|
|
3198
|
+
"fasthttp",
|
|
3199
|
+
// Ruby
|
|
3200
|
+
"rails",
|
|
3201
|
+
"sinatra",
|
|
3202
|
+
"hanami",
|
|
3203
|
+
"grape",
|
|
3204
|
+
"padrino",
|
|
3205
|
+
"roda",
|
|
3206
|
+
"rack",
|
|
3207
|
+
// PHP
|
|
3208
|
+
"laravel",
|
|
3209
|
+
"symfony",
|
|
3210
|
+
"codeigniter",
|
|
3211
|
+
"slim",
|
|
3212
|
+
"lumen",
|
|
3213
|
+
"yii",
|
|
3214
|
+
"cakephp",
|
|
3215
|
+
"phalcon",
|
|
3216
|
+
"laminas",
|
|
3217
|
+
"zend",
|
|
3218
|
+
"fuel",
|
|
3219
|
+
// Rust
|
|
3220
|
+
"actix",
|
|
3221
|
+
"axum",
|
|
3222
|
+
"rocket",
|
|
3223
|
+
"warp",
|
|
3224
|
+
"tide",
|
|
3225
|
+
"poem",
|
|
3226
|
+
"salvo",
|
|
3227
|
+
"hyper",
|
|
3228
|
+
// Elixir
|
|
3229
|
+
"phoenix",
|
|
3230
|
+
"plug",
|
|
3231
|
+
"cowboy",
|
|
3232
|
+
// Swift / Dart
|
|
3233
|
+
"vapor",
|
|
3234
|
+
"kitura",
|
|
3235
|
+
"hummingbird",
|
|
3236
|
+
"perfect",
|
|
3237
|
+
"shelf",
|
|
3238
|
+
"aqueduct",
|
|
3239
|
+
"conduit"
|
|
3240
|
+
].join("|") + ")\\b"
|
|
3241
|
+
);
|
|
3242
|
+
var API_DEP_STEMS = [
|
|
3243
|
+
"springframework",
|
|
3244
|
+
"spring-boot",
|
|
3245
|
+
"springdoc",
|
|
3246
|
+
"webflux",
|
|
3247
|
+
"webmvc",
|
|
3248
|
+
"quarkus",
|
|
3249
|
+
"micronaut",
|
|
3250
|
+
"dropwizard",
|
|
3251
|
+
"jersey",
|
|
3252
|
+
"resteasy",
|
|
3253
|
+
"javalin",
|
|
3254
|
+
"helidon",
|
|
3255
|
+
"vertx",
|
|
3256
|
+
"ratpack",
|
|
3257
|
+
"restlet",
|
|
3258
|
+
"django",
|
|
3259
|
+
"fastapi",
|
|
3260
|
+
"starlette",
|
|
3261
|
+
"tornado",
|
|
3262
|
+
"pyramid",
|
|
3263
|
+
"sanic",
|
|
3264
|
+
"aiohttp",
|
|
3265
|
+
"litestar",
|
|
3266
|
+
"falcon-",
|
|
3267
|
+
"flask",
|
|
3268
|
+
"express",
|
|
3269
|
+
"fastify",
|
|
3270
|
+
"nestjs",
|
|
3271
|
+
"@nestjs",
|
|
3272
|
+
"adonis",
|
|
3273
|
+
"loopback",
|
|
3274
|
+
"feathers",
|
|
3275
|
+
"restify",
|
|
3276
|
+
"aspnetcore",
|
|
3277
|
+
"asp.net",
|
|
3278
|
+
"servicestack",
|
|
3279
|
+
"signalr",
|
|
3280
|
+
"gin-gonic",
|
|
3281
|
+
"gorilla/mux",
|
|
3282
|
+
"gofiber",
|
|
3283
|
+
"beego",
|
|
3284
|
+
"buffalo",
|
|
3285
|
+
"gqlgen",
|
|
3286
|
+
"laravel",
|
|
3287
|
+
"symfony",
|
|
3288
|
+
"codeigniter",
|
|
3289
|
+
"cakephp",
|
|
3290
|
+
"phalcon",
|
|
3291
|
+
"laminas",
|
|
3292
|
+
"lumen",
|
|
3293
|
+
"sinatra",
|
|
3294
|
+
"hanami",
|
|
3295
|
+
"grape",
|
|
3296
|
+
"actix",
|
|
3297
|
+
"axum",
|
|
3298
|
+
"rocket",
|
|
3299
|
+
"salvo",
|
|
3300
|
+
"warp-",
|
|
3301
|
+
"phoenix",
|
|
3302
|
+
"cowboy",
|
|
3303
|
+
"vapor",
|
|
3304
|
+
"kitura",
|
|
3305
|
+
"hummingbird",
|
|
3306
|
+
"perfect-",
|
|
3307
|
+
"aqueduct",
|
|
3308
|
+
"conduit",
|
|
3309
|
+
"rails",
|
|
3310
|
+
"railties",
|
|
3311
|
+
"actionpack"
|
|
3312
|
+
];
|
|
3313
|
+
var WEB_DEP_STEMS = [
|
|
3314
|
+
"react",
|
|
3315
|
+
"preact",
|
|
3316
|
+
"next",
|
|
3317
|
+
"nuxt",
|
|
3318
|
+
"remix",
|
|
3319
|
+
"gatsby",
|
|
3320
|
+
"vue",
|
|
3321
|
+
"angular",
|
|
3322
|
+
"@angular",
|
|
3323
|
+
"svelte",
|
|
3324
|
+
"solid-js",
|
|
3325
|
+
"qwik",
|
|
3326
|
+
"astro",
|
|
3327
|
+
"ember",
|
|
3328
|
+
"backbone",
|
|
3329
|
+
"blazor",
|
|
3330
|
+
"razor",
|
|
3331
|
+
"vaadin",
|
|
3332
|
+
"thymeleaf",
|
|
3333
|
+
"wicket",
|
|
3334
|
+
"livewire",
|
|
3335
|
+
"filament",
|
|
3336
|
+
"inertia",
|
|
3337
|
+
"flutter",
|
|
3338
|
+
"jetpack",
|
|
3339
|
+
"swiftui"
|
|
3340
|
+
];
|
|
3341
|
+
function matchesStem(haystack, stems) {
|
|
3342
|
+
return stems.some((s) => haystack.includes(s));
|
|
3343
|
+
}
|
|
3344
|
+
var ML_RE = /\b(tensorflow|pytorch|torch|keras|jax|sklearn|scikit-?learn|pandas|numpy|scipy|xgboost|lightgbm|catboost|mxnet|spacy|transformers|huggingface|langchain|llama|onnx|mlflow|kubeflow|sagemaker|spark ?ml|mllib)\b/;
|
|
3345
|
+
var INFRA_RE = /\b(terraform|pulumi|kubernetes|\bk8s\b|helm|ansible|cdktf|aws cdk|\bcdk\b|bicep|crossplane|serverless framework|\bsst\b|cloudformation|packer|nomad)\b/;
|
|
3346
|
+
function archetypeCategory(archetype) {
|
|
3347
|
+
switch (archetype) {
|
|
3348
|
+
case "nextjs":
|
|
3349
|
+
case "remix":
|
|
3350
|
+
case "sveltekit":
|
|
3351
|
+
case "nuxt":
|
|
3352
|
+
return { category: "web-app", signal: `archetype:${archetype}` };
|
|
3353
|
+
case "nestjs":
|
|
3354
|
+
case "express":
|
|
3355
|
+
case "fastify":
|
|
3356
|
+
case "hono":
|
|
3357
|
+
case "koa":
|
|
3358
|
+
case "serverless":
|
|
3359
|
+
return { category: "api", signal: `archetype:${archetype}` };
|
|
3360
|
+
case "library":
|
|
3361
|
+
return { category: "library", signal: "archetype:library" };
|
|
3362
|
+
case "cli":
|
|
3363
|
+
return { category: "cli", signal: "archetype:cli" };
|
|
3364
|
+
default:
|
|
3365
|
+
return null;
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
function inferProjectPurpose(project, uiPurpose) {
|
|
3369
|
+
const signals = [];
|
|
3370
|
+
const votes = {};
|
|
3371
|
+
const vote = (category, weight, signal) => {
|
|
3372
|
+
votes[category] = (votes[category] ?? 0) + weight;
|
|
3373
|
+
signals.push(signal);
|
|
3374
|
+
};
|
|
3375
|
+
if (project.type === "node" || project.type === "typescript") {
|
|
3376
|
+
const arch = archetypeCategory(project.architecture?.archetype);
|
|
3377
|
+
if (arch) vote(arch.category, 3, arch.signal);
|
|
3378
|
+
}
|
|
3379
|
+
const fwNames = (project.frameworks ?? []).map((f) => f.name.toLowerCase());
|
|
3380
|
+
const uiFw = (uiPurpose?.detectedFrameworks ?? []).map((f) => f.toLowerCase());
|
|
3381
|
+
const fwText = [...fwNames, ...uiFw].join(" ");
|
|
3382
|
+
if (WEB_FRAMEWORK_RE.test(fwText)) vote("web-app", 2, "web-framework");
|
|
3383
|
+
if (API_FRAMEWORK_RE.test(fwText)) vote("api", 2, "api-framework");
|
|
3384
|
+
const depText = (project.dependencies ?? []).map((d) => d.package.toLowerCase()).join(" ");
|
|
3385
|
+
if (matchesStem(depText, WEB_DEP_STEMS)) vote("web-app", 1, "web-dependency");
|
|
3386
|
+
if (matchesStem(depText, API_DEP_STEMS)) vote("api", 1, "api-dependency");
|
|
3387
|
+
if (uiPurpose && (uiPurpose.routes?.length || uiPurpose.samples?.length)) {
|
|
3388
|
+
vote("web-app", 1, "ui-evidence");
|
|
3389
|
+
}
|
|
3390
|
+
const haystack = `${fwText} ${depText}`;
|
|
3391
|
+
if (ML_RE.test(haystack)) vote("ml", 2, "ml-libs");
|
|
3392
|
+
if (INFRA_RE.test(haystack)) vote("infra", 2, "infra-tooling");
|
|
3393
|
+
const entries = Object.entries(votes).sort((a, b) => b[1] - a[1]);
|
|
3394
|
+
if (entries.length === 0) {
|
|
3395
|
+
return { project: project.name, category: "any", confidence: 0.3, signals: ["no-strong-signal"] };
|
|
3396
|
+
}
|
|
3397
|
+
const [topCategory, topWeight] = entries[0];
|
|
3398
|
+
const total = entries.reduce((s, [, w]) => s + w, 0);
|
|
3399
|
+
const confidence = Math.min(0.95, 0.4 + topWeight / total * 0.55);
|
|
3400
|
+
return {
|
|
3401
|
+
project: project.name,
|
|
3402
|
+
category: topCategory,
|
|
3403
|
+
confidence: Number(confidence.toFixed(2)),
|
|
3404
|
+
signals: [...new Set(signals)]
|
|
3405
|
+
};
|
|
3406
|
+
}
|
|
3407
|
+
function appliesToPurpose(entry, category) {
|
|
3408
|
+
if (!entry.projectTypes || entry.projectTypes.length === 0) return false;
|
|
3409
|
+
return entry.projectTypes.includes("any") || entry.projectTypes.includes(category);
|
|
3410
|
+
}
|
|
3411
|
+
var AFFINITY = {
|
|
3412
|
+
api: { categories: ["security", "api-spec"], domains: ["api", "security"] },
|
|
3413
|
+
"web-app": { categories: ["security", "accessibility"], domains: ["security", "accessibility", "frontend"] },
|
|
3414
|
+
library: { categories: ["software-process", "programming-language"], domains: ["process", "language"] },
|
|
3415
|
+
cli: { categories: ["software-process"], domains: ["process"] },
|
|
3416
|
+
data: { categories: ["database-sql", "data-format"], domains: ["data"] },
|
|
3417
|
+
ml: { categories: ["ai-governance"], domains: ["ai"] },
|
|
3418
|
+
infra: { categories: ["devops", "cloud-architecture"], domains: ["devops", "cloud"] }
|
|
3419
|
+
};
|
|
3420
|
+
function rankStandardsForCategory(category) {
|
|
3421
|
+
const affinity = AFFINITY[category] ?? { categories: [], domains: [] };
|
|
3422
|
+
const ranked = standardsIndex.filter((e) => appliesToPurpose(e, category)).map((e) => {
|
|
3423
|
+
const tight = e.projectTypes.includes(category) && !e.projectTypes.includes("any");
|
|
3424
|
+
const catIdx = affinity.categories.indexOf(e.category);
|
|
3425
|
+
const categoryAffinity = catIdx >= 0 ? (affinity.categories.length - catIdx) * 2 : 0;
|
|
3426
|
+
const domainAffinity = e.domains.some((d) => affinity.domains.includes(d)) ? 1 : 0;
|
|
3427
|
+
const score = categoryAffinity + domainAffinity + (e.complianceRelevant ? 2 : 0) + (tight ? 1 : 0);
|
|
3428
|
+
return { e, score, tight };
|
|
3429
|
+
}).sort((a, b) => b.score - a.score || a.e.slug.localeCompare(b.e.slug));
|
|
3430
|
+
return ranked.map(({ e, score, tight }) => ({
|
|
3431
|
+
score,
|
|
3432
|
+
rec: {
|
|
3433
|
+
slug: e.slug,
|
|
3434
|
+
name: e.name,
|
|
3435
|
+
category: e.category,
|
|
3436
|
+
reason: tight ? `Applies specifically to ${category} projects` : e.complianceRelevant ? "Compliance-relevant standard applicable to this stack" : "Generally applicable standard",
|
|
3437
|
+
matchedProjectTypes: [category],
|
|
3438
|
+
frameworks: e.frameworks,
|
|
3439
|
+
complianceRelevant: e.complianceRelevant,
|
|
3440
|
+
officialUrl: e.officialUrl
|
|
3441
|
+
}
|
|
3442
|
+
}));
|
|
3443
|
+
}
|
|
3444
|
+
function recommendStandards(projects, extended, options = {}) {
|
|
3445
|
+
const perCategoryLimit = options.perCategoryLimit ?? 10;
|
|
3446
|
+
const totalLimit = options.totalLimit ?? 40;
|
|
3447
|
+
const fallbackUi = extended?.uiPurpose ? {
|
|
3448
|
+
samples: [],
|
|
3449
|
+
routes: [],
|
|
3450
|
+
detectedFrameworks: extended.uiPurpose.detectedFrameworks ?? []
|
|
3451
|
+
} : void 0;
|
|
3452
|
+
const projectPurposes = projects.map((p) => inferProjectPurpose(p, p.uiPurpose ?? fallbackUi));
|
|
3453
|
+
const categories = [...new Set(projectPurposes.map((p) => p.category))];
|
|
3454
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
3455
|
+
const scoreBySlug = /* @__PURE__ */ new Map();
|
|
3456
|
+
for (const category of categories) {
|
|
3457
|
+
const ranked = rankStandardsForCategory(category).slice(0, perCategoryLimit);
|
|
3458
|
+
for (const { rec, score } of ranked) {
|
|
3459
|
+
const existing = bySlug.get(rec.slug);
|
|
3460
|
+
if (existing) {
|
|
3461
|
+
if (!existing.matchedProjectTypes.includes(category)) existing.matchedProjectTypes.push(category);
|
|
3462
|
+
scoreBySlug.set(rec.slug, Math.max(scoreBySlug.get(rec.slug) ?? 0, score));
|
|
3463
|
+
} else {
|
|
3464
|
+
bySlug.set(rec.slug, { ...rec });
|
|
3465
|
+
scoreBySlug.set(rec.slug, score);
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
const recommended = [...bySlug.values()].sort(
|
|
3470
|
+
(a, b) => (scoreBySlug.get(b.slug) ?? 0) - (scoreBySlug.get(a.slug) ?? 0) || b.matchedProjectTypes.length - a.matchedProjectTypes.length || a.slug.localeCompare(b.slug)
|
|
3471
|
+
).slice(0, totalLimit);
|
|
3472
|
+
const recommendedSlugs = new Set(recommended.map((r) => r.slug));
|
|
3473
|
+
const frameworks = complianceFrameworks.map((fw) => ({
|
|
3474
|
+
id: fw.id,
|
|
3475
|
+
name: fw.name,
|
|
3476
|
+
recommendedMembers: fw.memberStandards.filter((s) => recommendedSlugs.has(s)).length,
|
|
3477
|
+
totalMembers: fw.memberStandards.length
|
|
3478
|
+
})).filter((f) => f.recommendedMembers > 0).sort((a, b) => b.recommendedMembers - a.recommendedMembers);
|
|
3479
|
+
return { projectPurposes, recommended, frameworks };
|
|
3480
|
+
}
|
|
3481
|
+
|
|
3482
|
+
// src/reporting/advanced-analysis.ts
|
|
3483
|
+
async function runAdvancedAnalysis(ctx) {
|
|
3484
|
+
const { rootDir, opts, scanners, maxPrivacyMode, allProjects, solutions, projectsByPath, fileCache, progress, extended } = ctx;
|
|
3485
|
+
if (scanners === false) {
|
|
3486
|
+
extended.standards = recommendStandards(allProjects, extended);
|
|
3487
|
+
return;
|
|
3488
|
+
}
|
|
3489
|
+
const scannerPolicy = {
|
|
3490
|
+
platformMatrix: !maxPrivacyMode,
|
|
3491
|
+
serviceDependencies: !maxPrivacyMode,
|
|
3492
|
+
breakingChangeExposure: !maxPrivacyMode,
|
|
3493
|
+
buildDeploy: !maxPrivacyMode,
|
|
3494
|
+
tsModernity: !maxPrivacyMode,
|
|
3495
|
+
fileHotspots: !maxPrivacyMode,
|
|
3496
|
+
architecture: !maxPrivacyMode,
|
|
3497
|
+
codeQuality: !maxPrivacyMode};
|
|
3498
|
+
const advancedSteps = [
|
|
3499
|
+
...scannerPolicy.platformMatrix && scanners?.platformMatrix?.enabled !== false ? [{ id: "platform", label: "Platform matrix" }] : [],
|
|
3500
|
+
...scanners?.toolingInventory?.enabled !== false ? [{ id: "tooling", label: "Tooling inventory" }] : [],
|
|
3501
|
+
...scannerPolicy.serviceDependencies && scanners?.serviceDependencies?.enabled !== false ? [{ id: "services", label: "Service dependencies" }] : [],
|
|
3502
|
+
...scannerPolicy.breakingChangeExposure && scanners?.breakingChangeExposure?.enabled !== false ? [{ id: "breaking", label: "Breaking change exposure" }] : [],
|
|
3503
|
+
...scanners?.securityPosture?.enabled !== false ? [{ id: "security", label: "Security posture" }] : [],
|
|
3504
|
+
...scannerPolicy.buildDeploy && scanners?.buildDeploy?.enabled !== false ? [{ id: "build", label: "Build & deploy analysis" }] : [],
|
|
3505
|
+
...scannerPolicy.tsModernity && scanners?.tsModernity?.enabled !== false ? [{ id: "ts", label: "TypeScript modernity" }] : [],
|
|
3506
|
+
...scannerPolicy.fileHotspots && scanners?.fileHotspots?.enabled !== false ? [{ id: "hotspots", label: "File hotspots" }] : [],
|
|
3507
|
+
...scanners?.dependencyGraph?.enabled !== false ? [{ id: "depgraph", label: "Dependency graph" }] : [],
|
|
3508
|
+
...scanners?.dependencyRisk?.enabled !== false ? [{ id: "deprisk", label: "Dependency risk" }] : [],
|
|
3509
|
+
...scannerPolicy.architecture && scanners?.architecture?.enabled !== false ? [{ id: "architecture", label: "Architecture layers" }] : [],
|
|
3510
|
+
...scannerPolicy.codeQuality && scanners?.codeQuality?.enabled !== false ? [{ id: "codequality", label: "Code quality metrics" }] : [],
|
|
3511
|
+
...!maxPrivacyMode && (opts.uiPurpose || scanners?.uiPurpose?.enabled === true) ? [{ id: "uipurpose", label: "UI purpose evidence" }] : []
|
|
3512
|
+
];
|
|
3513
|
+
for (const step of advancedSteps) {
|
|
3514
|
+
progress.insertStepBefore("drift", step);
|
|
3515
|
+
}
|
|
3516
|
+
const scannerTasks = [];
|
|
3517
|
+
if (scannerPolicy.platformMatrix && scanners?.platformMatrix?.enabled !== false) {
|
|
3518
|
+
progress.startStep("platform");
|
|
3519
|
+
scannerTasks.push(
|
|
3520
|
+
scanPlatformMatrix(rootDir, fileCache).then((result) => {
|
|
3521
|
+
extended.platformMatrix = result;
|
|
3522
|
+
const nativeCount = result.nativeModules.length;
|
|
3523
|
+
const dockerCount = result.dockerBaseImages.length;
|
|
3524
|
+
const parts = [];
|
|
3525
|
+
if (nativeCount > 0) parts.push(`${nativeCount} native`);
|
|
3526
|
+
if (dockerCount > 0) parts.push(`${dockerCount} docker`);
|
|
3527
|
+
progress.completeStep("platform", parts.join(", ") || "clean", nativeCount + dockerCount);
|
|
3528
|
+
})
|
|
3529
|
+
);
|
|
3530
|
+
}
|
|
3531
|
+
if (scanners?.toolingInventory?.enabled !== false) {
|
|
3532
|
+
progress.startStep("tooling");
|
|
3533
|
+
scannerTasks.push(
|
|
3534
|
+
Promise.resolve().then(() => {
|
|
3535
|
+
extended.toolingInventory = scanToolingInventory(allProjects);
|
|
3536
|
+
const toolCount = Object.values(extended.toolingInventory).reduce((sum, arr) => sum + arr.length, 0);
|
|
3537
|
+
progress.completeStep("tooling", `${toolCount} tool${toolCount !== 1 ? "s" : ""} mapped`, toolCount);
|
|
3538
|
+
})
|
|
3539
|
+
);
|
|
3540
|
+
}
|
|
3541
|
+
if (scannerPolicy.serviceDependencies && scanners?.serviceDependencies?.enabled !== false) {
|
|
3542
|
+
progress.startStep("services");
|
|
3543
|
+
scannerTasks.push(
|
|
3544
|
+
Promise.resolve().then(() => {
|
|
3545
|
+
extended.serviceDependencies = scanServiceDependencies(allProjects);
|
|
3546
|
+
const svcCount = Object.values(extended.serviceDependencies).reduce((sum, arr) => sum + arr.length, 0);
|
|
3547
|
+
progress.completeStep("services", `${svcCount} service${svcCount !== 1 ? "s" : ""} detected`, svcCount);
|
|
3548
|
+
})
|
|
3549
|
+
);
|
|
3550
|
+
}
|
|
3551
|
+
if (scannerPolicy.breakingChangeExposure && scanners?.breakingChangeExposure?.enabled !== false) {
|
|
3552
|
+
progress.startStep("breaking");
|
|
3553
|
+
scannerTasks.push(
|
|
3554
|
+
Promise.resolve().then(async () => {
|
|
3555
|
+
extended.breakingChangeExposure = await scanBreakingChangeExposure(allProjects, rootDir, fileCache);
|
|
3556
|
+
const bc = extended.breakingChangeExposure;
|
|
3557
|
+
const bcTotal = bc.deprecatedPackages.length + bc.legacyPolyfills.length;
|
|
3558
|
+
progress.completeStep(
|
|
3559
|
+
"breaking",
|
|
3560
|
+
bcTotal > 0 ? `${bc.deprecatedPackages.length} deprecated, ${bc.legacyPolyfills.length} polyfills` : "none found",
|
|
3561
|
+
bcTotal
|
|
3562
|
+
);
|
|
3563
|
+
})
|
|
3564
|
+
);
|
|
3565
|
+
}
|
|
3566
|
+
if (scanners?.securityPosture?.enabled !== false) {
|
|
3567
|
+
progress.startStep("security");
|
|
3568
|
+
scannerTasks.push(
|
|
3569
|
+
scanSecurityPosture(rootDir, fileCache).then((result) => {
|
|
3570
|
+
extended.securityPosture = result;
|
|
3571
|
+
const secDetail = result.lockfilePresent ? `lockfile \u2714${result.gitignoreCoversEnv ? " \xB7 .env \u2714" : " \xB7 .env \u2716"}` : "no lockfile";
|
|
3572
|
+
progress.completeStep("security", secDetail);
|
|
3573
|
+
})
|
|
3574
|
+
);
|
|
3575
|
+
}
|
|
3576
|
+
if (scannerPolicy.buildDeploy && scanners?.buildDeploy?.enabled !== false) {
|
|
3577
|
+
progress.startStep("build");
|
|
3578
|
+
scannerTasks.push(
|
|
3579
|
+
scanBuildDeploy(rootDir, fileCache).then((result) => {
|
|
3580
|
+
extended.buildDeploy = result;
|
|
3581
|
+
const bdParts = [];
|
|
3582
|
+
if (result.ci.length > 0) bdParts.push(result.ci.join(", "));
|
|
3583
|
+
if (result.docker.dockerfileCount > 0) bdParts.push(`${result.docker.dockerfileCount} Dockerfile${result.docker.dockerfileCount !== 1 ? "s" : ""}`);
|
|
3584
|
+
progress.completeStep("build", bdParts.join(" \xB7 ") || "none detected");
|
|
3585
|
+
})
|
|
3586
|
+
);
|
|
3587
|
+
}
|
|
3588
|
+
if (scannerPolicy.tsModernity && scanners?.tsModernity?.enabled !== false) {
|
|
3589
|
+
progress.startStep("ts");
|
|
3590
|
+
scannerTasks.push(
|
|
3591
|
+
scanTsModernity(rootDir, fileCache).then((result) => {
|
|
3592
|
+
extended.tsModernity = result;
|
|
3593
|
+
const tsParts = [];
|
|
3594
|
+
if (result.typescriptVersion) tsParts.push(`v${result.typescriptVersion}`);
|
|
3595
|
+
if (result.strict === true) tsParts.push("strict");
|
|
3596
|
+
if (result.moduleType) tsParts.push(result.moduleType.toUpperCase());
|
|
3597
|
+
progress.completeStep("ts", tsParts.join(" \xB7 ") || "no tsconfig");
|
|
3598
|
+
})
|
|
3599
|
+
);
|
|
3600
|
+
}
|
|
3601
|
+
if (scannerPolicy.fileHotspots && scanners?.fileHotspots?.enabled !== false) {
|
|
3602
|
+
progress.startStep("hotspots");
|
|
3603
|
+
scannerTasks.push(
|
|
3604
|
+
scanFileHotspots(rootDir, fileCache).then((result) => {
|
|
3605
|
+
extended.fileHotspots = result;
|
|
3606
|
+
progress.completeStep("hotspots", `${result.totalFiles} files`, result.totalFiles);
|
|
3607
|
+
})
|
|
3608
|
+
);
|
|
3609
|
+
}
|
|
3610
|
+
if (scanners?.dependencyGraph?.enabled !== false) {
|
|
3611
|
+
progress.startStep("depgraph");
|
|
3612
|
+
scannerTasks.push(
|
|
3613
|
+
scanDependencyGraph(rootDir, fileCache).then((result) => {
|
|
3614
|
+
extended.dependencyGraph = result;
|
|
3615
|
+
const dgDetail = result.lockfileType ? `${result.lockfileType} \xB7 ${result.totalUnique} unique` : "no lockfile";
|
|
3616
|
+
progress.completeStep("depgraph", dgDetail, result.totalUnique);
|
|
3617
|
+
})
|
|
3618
|
+
);
|
|
3619
|
+
}
|
|
3620
|
+
if (scannerPolicy.codeQuality && scanners?.codeQuality?.enabled !== false) {
|
|
3621
|
+
progress.startStep("codequality");
|
|
3622
|
+
scannerTasks.push(
|
|
3623
|
+
scanCodeQuality(rootDir, fileCache).then((result) => {
|
|
3624
|
+
extended.codeQuality = result;
|
|
3625
|
+
const cqParts = [];
|
|
3626
|
+
cqParts.push(`${result.filesAnalyzed} files`);
|
|
3627
|
+
cqParts.push(`${result.functionsAnalyzed} functions`);
|
|
3628
|
+
if (result.circularDependencies > 0) cqParts.push(`${result.circularDependencies} cycles`);
|
|
3629
|
+
progress.completeStep("codequality", cqParts.join(" \xB7 "), result.functionsAnalyzed);
|
|
3630
|
+
})
|
|
3631
|
+
);
|
|
3632
|
+
}
|
|
3633
|
+
if (scanners?.dependencyRisk?.enabled !== false) {
|
|
3634
|
+
progress.startStep("deprisk");
|
|
3635
|
+
scannerTasks.push(
|
|
3636
|
+
Promise.resolve().then(() => {
|
|
3637
|
+
extended.dependencyRisk = scanDependencyRisk(allProjects);
|
|
3638
|
+
const dr = extended.dependencyRisk;
|
|
3639
|
+
const drParts = [];
|
|
3640
|
+
if (dr.deprecatedPackages.length > 0) drParts.push(`${dr.deprecatedPackages.length} deprecated`);
|
|
3641
|
+
if (dr.nativeModulePackages.length > 0) drParts.push(`${dr.nativeModulePackages.length} native`);
|
|
3642
|
+
progress.completeStep("deprisk", drParts.join(", ") || "low risk");
|
|
3643
|
+
})
|
|
3644
|
+
);
|
|
3645
|
+
}
|
|
3646
|
+
await Promise.all(scannerTasks);
|
|
3647
|
+
if (!maxPrivacyMode && scanners?.uiPurpose?.enabled !== false) {
|
|
3648
|
+
progress.startStep("uipurpose");
|
|
3649
|
+
extended.uiPurpose = await scanUiPurpose(rootDir, fileCache);
|
|
3650
|
+
const up = extended.uiPurpose;
|
|
3651
|
+
const summary = [`${up.topEvidence.length} evidence`, ...up.capped ? ["capped"] : []].join(" \xB7 ");
|
|
3652
|
+
progress.completeStep("uipurpose", summary, up.topEvidence.length);
|
|
3653
|
+
await Promise.all(allProjects.map(async (project) => {
|
|
3654
|
+
const projectDir = path3.join(rootDir, project.path);
|
|
3655
|
+
const projectResult = await scanUiPurpose(projectDir, fileCache, 150);
|
|
3656
|
+
if (projectResult.topEvidence.length > 0) {
|
|
3657
|
+
project.uiPurpose = compactUiPurpose(projectResult);
|
|
3658
|
+
}
|
|
3659
|
+
}));
|
|
3660
|
+
}
|
|
3661
|
+
if (scannerPolicy.architecture && scanners?.architecture?.enabled !== false) {
|
|
3662
|
+
progress.startStep("architecture");
|
|
3663
|
+
extended.architecture = await scanArchitecture(
|
|
3664
|
+
rootDir,
|
|
3665
|
+
allProjects,
|
|
3666
|
+
extended.toolingInventory,
|
|
3667
|
+
extended.serviceDependencies,
|
|
3668
|
+
fileCache
|
|
3669
|
+
);
|
|
3670
|
+
const arch = extended.architecture;
|
|
3671
|
+
const layerCount = arch.layers.filter((l) => l.fileCount > 0).length;
|
|
3672
|
+
await Promise.all(allProjects.map(async (project) => {
|
|
3673
|
+
project.architectureMermaid = await buildProjectArchitectureMermaid(
|
|
3674
|
+
rootDir,
|
|
3675
|
+
project,
|
|
3676
|
+
arch.archetype,
|
|
3677
|
+
fileCache
|
|
3678
|
+
);
|
|
3679
|
+
project.architecture = await scanProjectArchitecture(rootDir, project, fileCache);
|
|
3680
|
+
}));
|
|
3681
|
+
for (const solution of solutions) {
|
|
3682
|
+
const memberProjects = solution.projectPaths.map((pp) => projectsByPath.get(pp) ?? projectsByPath.get(path3.dirname(pp).replace(/\\/g, "/"))).filter((p) => Boolean(p));
|
|
3683
|
+
const memberArchResults = memberProjects.map((p) => p.architecture).filter((a) => Boolean(a));
|
|
3684
|
+
if (memberArchResults.length > 0) {
|
|
3685
|
+
solution.architecture = aggregateSolutionArchitecture(memberArchResults);
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3688
|
+
progress.completeStep(
|
|
3689
|
+
"architecture",
|
|
3690
|
+
`${arch.archetype} \xB7 ${layerCount} layer${layerCount !== 1 ? "s" : ""} \xB7 ${arch.totalClassified} files`,
|
|
3691
|
+
layerCount
|
|
3692
|
+
);
|
|
3693
|
+
}
|
|
3694
|
+
extended.standards = recommendStandards(allProjects, extended);
|
|
3695
|
+
let advancedFiles = 0;
|
|
3696
|
+
if (extended.fileHotspots) advancedFiles += extended.fileHotspots.totalFiles;
|
|
3697
|
+
if (extended.securityPosture) advancedFiles += 1;
|
|
3698
|
+
if (extended.tsModernity?.typescriptVersion) advancedFiles += 1;
|
|
3699
|
+
if (extended.dependencyGraph?.lockfileType) advancedFiles += 1;
|
|
3700
|
+
if (extended.buildDeploy) {
|
|
3701
|
+
advancedFiles += extended.buildDeploy.docker.dockerfileCount;
|
|
3702
|
+
advancedFiles += extended.buildDeploy.ci.length;
|
|
3703
|
+
}
|
|
3704
|
+
if (extended.codeQuality) advancedFiles += extended.codeQuality.filesAnalyzed;
|
|
3705
|
+
if (extended.uiPurpose) advancedFiles += extended.uiPurpose.topEvidence.length;
|
|
3706
|
+
if (advancedFiles > 0) ctx.addFilesScanned(advancedFiles);
|
|
3707
|
+
}
|
|
3708
|
+
var advancedScanHook = runAdvancedAnalysis;
|
|
3709
|
+
|
|
3710
|
+
// src/reporting/advanced-hook.ts
|
|
3711
|
+
async function loadAdvancedScanHook() {
|
|
3712
|
+
return advancedScanHook;
|
|
3713
|
+
}
|
|
3714
|
+
|
|
3715
|
+
// src/reporting/commands/baseline.ts
|
|
3716
|
+
async function runBaseline(rootDir) {
|
|
3717
|
+
console.log(chalk.dim("Creating baseline..."));
|
|
3718
|
+
const advanced = await loadAdvancedScanHook();
|
|
3719
|
+
const artifact = await runCoreScan(rootDir, {
|
|
3720
|
+
format: "text",
|
|
3721
|
+
concurrency: 8
|
|
3722
|
+
}, advanced);
|
|
3723
|
+
const baselinePath = path3.join(rootDir, ".vibgrate", "baseline.json");
|
|
3724
|
+
await writeJsonFile(baselinePath, artifact);
|
|
3725
|
+
console.log(chalk.green("\u2714") + ` Baseline saved to ${chalk.bold(".vibgrate/baseline.json")}`);
|
|
3726
|
+
console.log(chalk.dim(` Baseline score: ${artifact.drift.score}/100`));
|
|
3727
|
+
}
|
|
3728
|
+
var baselineCommand = new Command("baseline").description("Create a drift baseline snapshot").argument("[path]", "Path to baseline", ".").action(async (targetPath) => {
|
|
3729
|
+
const rootDir = path3.resolve(targetPath);
|
|
3730
|
+
await runBaseline(rootDir);
|
|
3731
|
+
});
|
|
3732
|
+
|
|
3733
|
+
export { baselineCommand, loadAdvancedScanHook, runBaseline };
|
|
3734
|
+
//# sourceMappingURL=chunk-FU4MOQTC.js.map
|
|
3735
|
+
//# sourceMappingURL=chunk-FU4MOQTC.js.map
|