@codacy/analysis-runner 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +136 -31
- package/dist/index.js +1598 -1129
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -38,7 +38,7 @@ __export(index_exports, {
|
|
|
38
38
|
clearAdapters: () => clearAdapters,
|
|
39
39
|
computePatternSummary: () => computePatternSummary,
|
|
40
40
|
configureApiToken: () => configureApiToken,
|
|
41
|
-
configureProxy: () =>
|
|
41
|
+
configureProxy: () => import_tooling8.configureProxy,
|
|
42
42
|
createCodacyConfig: () => createCodacyConfig,
|
|
43
43
|
createLogger: () => createLogger,
|
|
44
44
|
createNullLogger: () => createNullLogger,
|
|
@@ -73,13 +73,16 @@ __export(index_exports, {
|
|
|
73
73
|
registerAdapter: () => registerAdapter,
|
|
74
74
|
registerLazyAdapter: () => registerLazyAdapter,
|
|
75
75
|
registerUnsupportedTool: () => registerUnsupportedTool,
|
|
76
|
+
relativizeLocalConfigPaths: () => relativizeLocalConfigPaths,
|
|
76
77
|
resolveApiToken: () => resolveApiToken,
|
|
77
78
|
resolveBaselinePath: () => resolveBaselinePath,
|
|
78
79
|
resolveConfigPath: () => resolveConfigPath,
|
|
79
80
|
resolveDiscoveryExcludes: () => resolveDiscoveryExcludes,
|
|
80
81
|
resolveEngineToAdapter: () => resolveEngineToAdapter,
|
|
81
82
|
resolveHeadCommit: () => resolveHeadCommit,
|
|
82
|
-
|
|
83
|
+
resolveLocalConfigPaths: () => resolveLocalConfigPaths,
|
|
84
|
+
resolveProxyConfig: () => import_tooling8.resolveProxyConfig,
|
|
85
|
+
resolveRemoteAuth: () => resolveRemoteAuth,
|
|
83
86
|
resolveRepositoryRoot: () => resolveRepositoryRoot,
|
|
84
87
|
resolveResultsApiBaseUrl: () => resolveResultsApiBaseUrl,
|
|
85
88
|
rotateLogFiles: () => rotateLogFiles,
|
|
@@ -176,7 +179,7 @@ function resolveBaselinePath(repositoryRoot, configFile) {
|
|
|
176
179
|
const stem = import_path.default.basename(configPath, ext);
|
|
177
180
|
return import_path.default.join(dir, `${stem}.baseline${ext}`);
|
|
178
181
|
}
|
|
179
|
-
async function readConfigAtPath(configPath) {
|
|
182
|
+
async function readConfigAtPath(configPath, repositoryRoot) {
|
|
180
183
|
let raw;
|
|
181
184
|
try {
|
|
182
185
|
raw = await import_promises.default.readFile(configPath, "utf-8");
|
|
@@ -191,21 +194,91 @@ async function readConfigAtPath(configPath) {
|
|
|
191
194
|
throw new Error(`Invalid config at ${configPath}: expected a JSON object`);
|
|
192
195
|
}
|
|
193
196
|
const config = parsed;
|
|
197
|
+
assertConfigShape(config, configPath);
|
|
194
198
|
coerceParameterValues(config);
|
|
199
|
+
resolveLocalConfigPaths(config, repositoryRoot);
|
|
195
200
|
return config;
|
|
196
201
|
}
|
|
202
|
+
function assertConfigShape(config, configPath) {
|
|
203
|
+
const fail = (detail) => {
|
|
204
|
+
throw new Error(`Invalid config at ${configPath}: ${detail}`);
|
|
205
|
+
};
|
|
206
|
+
const isStringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
207
|
+
if (config.exclude !== void 0 && !isStringArray(config.exclude))
|
|
208
|
+
fail(`"exclude" must be an array of strings`);
|
|
209
|
+
if (config.includeFiles !== void 0 && !isStringArray(config.includeFiles))
|
|
210
|
+
fail(`"includeFiles" must be an array of strings`);
|
|
211
|
+
if (!Array.isArray(config.tools)) fail(`"tools" must be an array`);
|
|
212
|
+
config.tools.forEach((tool, i) => {
|
|
213
|
+
const at = `tools[${i}]`;
|
|
214
|
+
if (typeof tool !== "object" || tool === null || Array.isArray(tool))
|
|
215
|
+
fail(`${at} must be an object`);
|
|
216
|
+
if (typeof tool.toolId !== "string" || tool.toolId === "")
|
|
217
|
+
fail(`${at}.toolId must be a non-empty string`);
|
|
218
|
+
if (tool.localConfigurationFile !== void 0 && typeof tool.localConfigurationFile !== "string")
|
|
219
|
+
fail(`${at}.localConfigurationFile must be a string ("${tool.toolId}")`);
|
|
220
|
+
if (tool.exclude !== void 0 && !isStringArray(tool.exclude))
|
|
221
|
+
fail(`${at}.exclude must be an array of strings ("${tool.toolId}")`);
|
|
222
|
+
if (tool.extraFilePatterns !== void 0 && !isStringArray(tool.extraFilePatterns))
|
|
223
|
+
fail(`${at}.extraFilePatterns must be an array of strings ("${tool.toolId}")`);
|
|
224
|
+
if (!Array.isArray(tool.patterns)) fail(`${at}.patterns must be an array ("${tool.toolId}")`);
|
|
225
|
+
tool.patterns.forEach((pattern, j) => {
|
|
226
|
+
const patternAt = `${at}.patterns[${j}]`;
|
|
227
|
+
if (typeof pattern !== "object" || pattern === null || Array.isArray(pattern))
|
|
228
|
+
fail(`${patternAt} must be an object`);
|
|
229
|
+
if (typeof pattern.patternId !== "string" || pattern.patternId === "")
|
|
230
|
+
fail(`${patternAt}.patternId must be a non-empty string`);
|
|
231
|
+
if (pattern.parameters === void 0) return;
|
|
232
|
+
if (typeof pattern.parameters !== "object" || pattern.parameters === null || Array.isArray(pattern.parameters))
|
|
233
|
+
fail(`${patternAt}.parameters must be an object ("${pattern.patternId}")`);
|
|
234
|
+
for (const [name, value] of Object.entries(pattern.parameters)) {
|
|
235
|
+
const isPrimitive = typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
236
|
+
if (!isPrimitive)
|
|
237
|
+
fail(
|
|
238
|
+
`${patternAt}.parameters.${name} must be a string, number, or boolean ("${pattern.patternId}")`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
}
|
|
197
244
|
async function readCodacyConfig(repositoryRoot, configFile) {
|
|
198
|
-
return readConfigAtPath(resolveConfigPath(repositoryRoot, configFile));
|
|
245
|
+
return readConfigAtPath(resolveConfigPath(repositoryRoot, configFile), repositoryRoot);
|
|
199
246
|
}
|
|
200
247
|
async function readBaselineConfig(repositoryRoot, configFile) {
|
|
201
|
-
return readConfigAtPath(resolveBaselinePath(repositoryRoot, configFile));
|
|
248
|
+
return readConfigAtPath(resolveBaselinePath(repositoryRoot, configFile), repositoryRoot);
|
|
202
249
|
}
|
|
203
250
|
async function writeBaselineConfig(repositoryRoot, config, configFile) {
|
|
204
251
|
const baselinePath = resolveBaselinePath(repositoryRoot, configFile);
|
|
252
|
+
const serializable = relativizeLocalConfigPaths(config, repositoryRoot);
|
|
205
253
|
await import_promises.default.mkdir(import_path.default.dirname(baselinePath), { recursive: true });
|
|
206
|
-
await import_promises.default.writeFile(baselinePath, JSON.stringify(
|
|
254
|
+
await import_promises.default.writeFile(baselinePath, JSON.stringify(serializable, null, 2) + "\n");
|
|
207
255
|
return baselinePath;
|
|
208
256
|
}
|
|
257
|
+
function toRepoRelativeConfigPath(configFilePath, repositoryRoot) {
|
|
258
|
+
const normalized = configFilePath.replace(/\\/g, "/");
|
|
259
|
+
if (!import_path.default.isAbsolute(configFilePath)) return normalized;
|
|
260
|
+
const relative = import_path.default.relative(repositoryRoot, configFilePath);
|
|
261
|
+
const escapesRoot = relative === ".." || relative.startsWith(`..${import_path.default.sep}`);
|
|
262
|
+
if (!relative || escapesRoot || import_path.default.isAbsolute(relative)) return normalized;
|
|
263
|
+
return relative.replace(/\\/g, "/");
|
|
264
|
+
}
|
|
265
|
+
function relativizeLocalConfigPaths(config, repositoryRoot) {
|
|
266
|
+
const root = import_path.default.resolve(repositoryRoot);
|
|
267
|
+
return {
|
|
268
|
+
...config,
|
|
269
|
+
tools: config.tools.map((tool) => {
|
|
270
|
+
if (!tool.localConfigurationFile) return tool;
|
|
271
|
+
const relative = toRepoRelativeConfigPath(tool.localConfigurationFile, root);
|
|
272
|
+
return relative === tool.localConfigurationFile ? tool : { ...tool, localConfigurationFile: relative };
|
|
273
|
+
})
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function resolveLocalConfigPaths(config, repositoryRoot) {
|
|
277
|
+
for (const tool of config.tools) {
|
|
278
|
+
if (!tool.localConfigurationFile) continue;
|
|
279
|
+
tool.localConfigurationFile = import_path.default.resolve(repositoryRoot, tool.localConfigurationFile);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
209
282
|
function coerceParameterValues(config) {
|
|
210
283
|
for (const tool of config.tools) {
|
|
211
284
|
for (const pattern of tool.patterns) {
|
|
@@ -220,8 +293,9 @@ function coerceParameterValues(config) {
|
|
|
220
293
|
}
|
|
221
294
|
async function writeCodacyConfigFile(repositoryRoot, config, configFile) {
|
|
222
295
|
const configPath = resolveConfigPath(repositoryRoot, configFile);
|
|
296
|
+
const serializable = relativizeLocalConfigPaths(config, repositoryRoot);
|
|
223
297
|
await import_promises.default.mkdir(import_path.default.dirname(configPath), { recursive: true });
|
|
224
|
-
await import_promises.default.writeFile(configPath, JSON.stringify(
|
|
298
|
+
await import_promises.default.writeFile(configPath, JSON.stringify(serializable, null, 2) + "\n");
|
|
225
299
|
return configPath;
|
|
226
300
|
}
|
|
227
301
|
async function writeCodacyConfig(repositoryRoot, config, configFile) {
|
|
@@ -2331,508 +2405,1149 @@ var import_promises4 = __toESM(require("fs/promises"));
|
|
|
2331
2405
|
var import_os3 = __toESM(require("os"));
|
|
2332
2406
|
var import_path8 = __toESM(require("path"));
|
|
2333
2407
|
var import_child_process4 = require("child_process");
|
|
2334
|
-
var
|
|
2408
|
+
var import_tooling7 = require("@codacy/tooling");
|
|
2335
2409
|
|
|
2336
|
-
// src/discover.ts
|
|
2410
|
+
// src/discover/index.ts
|
|
2337
2411
|
var import_promises2 = __toESM(require("fs/promises"));
|
|
2338
2412
|
var import_path6 = __toESM(require("path"));
|
|
2339
2413
|
|
|
2340
|
-
// src/discover
|
|
2341
|
-
var
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
["
|
|
2364
|
-
["
|
|
2365
|
-
["
|
|
2366
|
-
["
|
|
2367
|
-
["
|
|
2414
|
+
// src/discover/classify.ts
|
|
2415
|
+
var import_tooling6 = require("@codacy/tooling");
|
|
2416
|
+
|
|
2417
|
+
// src/discover/ecosystems/npm.ts
|
|
2418
|
+
function parsePackageJson(content) {
|
|
2419
|
+
try {
|
|
2420
|
+
const pkg = JSON.parse(content);
|
|
2421
|
+
const deps = [];
|
|
2422
|
+
for (const section of ["dependencies", "devDependencies", "peerDependencies"]) {
|
|
2423
|
+
const entries2 = pkg[section];
|
|
2424
|
+
if (entries2 && typeof entries2 === "object") {
|
|
2425
|
+
for (const [name, version] of Object.entries(entries2)) {
|
|
2426
|
+
deps.push({ name, version: typeof version === "string" ? version : void 0 });
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
return deps;
|
|
2431
|
+
} catch {
|
|
2432
|
+
return [];
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
var TELLTALES = [
|
|
2436
|
+
// --- Frameworks ---
|
|
2437
|
+
["react", "react"],
|
|
2438
|
+
["react-dom", "react-dom"],
|
|
2439
|
+
["react-native", "react-native"],
|
|
2440
|
+
["vue", "vue"],
|
|
2441
|
+
["nuxt", "nuxt"],
|
|
2442
|
+
["@angular/core", "angular"],
|
|
2443
|
+
["svelte", "svelte"],
|
|
2444
|
+
["@sveltejs/kit", "sveltekit"],
|
|
2445
|
+
["next", "next"],
|
|
2446
|
+
["ember-source", "ember"],
|
|
2447
|
+
["lit", "lit"],
|
|
2448
|
+
["solid-js", "solid"],
|
|
2449
|
+
["astro", "astro"],
|
|
2450
|
+
["@builder.io/qwik", "qwik"],
|
|
2451
|
+
["@builder.io/qwik-city", "qwik"],
|
|
2452
|
+
["@remix-run/react", "remix"],
|
|
2453
|
+
["gatsby", "gatsby"],
|
|
2454
|
+
["preact", "preact"],
|
|
2455
|
+
["express", "express"],
|
|
2456
|
+
["@nestjs/core", "nestjs"],
|
|
2457
|
+
["fastify", "fastify"],
|
|
2458
|
+
["koa", "koa"],
|
|
2459
|
+
["@hapi/hapi", "hapi"],
|
|
2460
|
+
["hono", "hono"],
|
|
2461
|
+
["electron", "electron"],
|
|
2368
2462
|
// AWS CDK — the v2 umbrella package `aws-cdk-lib`, the CLI package `aws-cdk`,
|
|
2369
|
-
// and the v1 `@aws-cdk/*` construct packages (prefix rule
|
|
2370
|
-
["aws-cdk-lib",
|
|
2371
|
-
["aws-cdk",
|
|
2372
|
-
//
|
|
2373
|
-
|
|
2374
|
-
["
|
|
2375
|
-
|
|
2376
|
-
["
|
|
2377
|
-
["
|
|
2378
|
-
["
|
|
2379
|
-
["
|
|
2380
|
-
["
|
|
2381
|
-
["
|
|
2382
|
-
["
|
|
2383
|
-
["
|
|
2384
|
-
["
|
|
2385
|
-
["
|
|
2386
|
-
["
|
|
2387
|
-
["
|
|
2388
|
-
["
|
|
2389
|
-
["
|
|
2390
|
-
["@
|
|
2391
|
-
["@
|
|
2392
|
-
["
|
|
2393
|
-
["
|
|
2394
|
-
["
|
|
2395
|
-
["
|
|
2396
|
-
["
|
|
2397
|
-
["
|
|
2398
|
-
["
|
|
2399
|
-
["
|
|
2400
|
-
["
|
|
2401
|
-
["
|
|
2402
|
-
["
|
|
2403
|
-
["
|
|
2404
|
-
["
|
|
2405
|
-
["
|
|
2406
|
-
["
|
|
2407
|
-
["
|
|
2408
|
-
["
|
|
2409
|
-
["
|
|
2410
|
-
["
|
|
2411
|
-
["
|
|
2412
|
-
["
|
|
2413
|
-
["
|
|
2414
|
-
["
|
|
2415
|
-
["
|
|
2416
|
-
["
|
|
2417
|
-
["
|
|
2418
|
-
["
|
|
2419
|
-
["
|
|
2420
|
-
["
|
|
2421
|
-
["
|
|
2422
|
-
["
|
|
2423
|
-
["
|
|
2424
|
-
["
|
|
2425
|
-
["
|
|
2463
|
+
// and the v1 `@aws-cdk/*` construct packages (a prefix rule).
|
|
2464
|
+
["aws-cdk-lib", "aws-cdk"],
|
|
2465
|
+
["aws-cdk", "aws-cdk"],
|
|
2466
|
+
// The v2 SDK is one flat package; v3 is the scoped `@aws-sdk/*` family, which a
|
|
2467
|
+
// prefix rule below covers. Both mean "this code calls AWS".
|
|
2468
|
+
["aws-sdk", "aws-sdk"],
|
|
2469
|
+
// --- Libraries ---
|
|
2470
|
+
["lodash", "lodash"],
|
|
2471
|
+
["underscore", "underscore"],
|
|
2472
|
+
["ramda", "ramda"],
|
|
2473
|
+
["jest", "jest"],
|
|
2474
|
+
["vitest", "vitest"],
|
|
2475
|
+
["@playwright/test", "playwright"],
|
|
2476
|
+
["playwright", "playwright"],
|
|
2477
|
+
["puppeteer", "puppeteer"],
|
|
2478
|
+
["puppeteer-core", "puppeteer"],
|
|
2479
|
+
["cypress", "cypress"],
|
|
2480
|
+
["mocha", "mocha"],
|
|
2481
|
+
["ava", "ava"],
|
|
2482
|
+
["jasmine", "jasmine"],
|
|
2483
|
+
["jasmine-core", "jasmine"],
|
|
2484
|
+
["@testing-library/react", "testing-library"],
|
|
2485
|
+
["@testing-library/vue", "testing-library"],
|
|
2486
|
+
["@testing-library/dom", "testing-library"],
|
|
2487
|
+
["tailwindcss", "tailwind-css"],
|
|
2488
|
+
["@storybook/react", "storybook"],
|
|
2489
|
+
["@storybook/vue3", "storybook"],
|
|
2490
|
+
["@storybook/svelte", "storybook"],
|
|
2491
|
+
["redux", "redux"],
|
|
2492
|
+
["@reduxjs/toolkit", "redux-toolkit"],
|
|
2493
|
+
["mobx", "mobx"],
|
|
2494
|
+
["zustand", "zustand"],
|
|
2495
|
+
["axios", "axios"],
|
|
2496
|
+
["date-fns", "date-fns"],
|
|
2497
|
+
["dayjs", "dayjs"],
|
|
2498
|
+
["moment", "moment"],
|
|
2499
|
+
["rxjs", "rxjs"],
|
|
2500
|
+
["zod", "zod"],
|
|
2501
|
+
["joi", "joi"],
|
|
2502
|
+
["yup", "yup"],
|
|
2503
|
+
["prisma", "prisma"],
|
|
2504
|
+
["@prisma/client", "prisma"],
|
|
2505
|
+
["typeorm", "typeorm"],
|
|
2506
|
+
["sequelize", "sequelize"],
|
|
2507
|
+
["drizzle-orm", "drizzle-orm"],
|
|
2508
|
+
["mongoose", "mongoose"],
|
|
2509
|
+
["graphql", "graphql"],
|
|
2510
|
+
["@apollo/client", "apollo-client"],
|
|
2511
|
+
["socket.io", "socketio"],
|
|
2512
|
+
["jquery", "jquery"],
|
|
2513
|
+
["mongodb", "mongodb"],
|
|
2514
|
+
["three", "threejs"],
|
|
2515
|
+
["d3", "d3"],
|
|
2516
|
+
["chart.js", "chartjs"],
|
|
2517
|
+
["sass", "sass"],
|
|
2518
|
+
["node-sass", "sass"],
|
|
2519
|
+
["less", "less"],
|
|
2520
|
+
["styled-components", "styled-components"],
|
|
2521
|
+
["@emotion/react", "emotion"],
|
|
2522
|
+
["pino", "pino"],
|
|
2523
|
+
["winston", "winston"],
|
|
2524
|
+
["i18next", "i18next"],
|
|
2426
2525
|
// react-intl is the React binding of the FormatJS ecosystem; eslint-plugin-formatjs
|
|
2427
2526
|
// lints both, so we report the umbrella name "formatjs" (matches the mapping key).
|
|
2428
|
-
["react-intl",
|
|
2429
|
-
["vue-i18n",
|
|
2430
|
-
["next-intl",
|
|
2527
|
+
["react-intl", "formatjs"],
|
|
2528
|
+
["vue-i18n", "vue-i18n"],
|
|
2529
|
+
["next-intl", "next-intl"],
|
|
2431
2530
|
// Type checker (eslint-plugin-flowtype). Also detected via a `.flowconfig` file.
|
|
2432
|
-
["flow-bin",
|
|
2531
|
+
["flow-bin", "flow"],
|
|
2433
2532
|
// Legacy JS libraries with dedicated ESLint plugins.
|
|
2434
|
-
["backbone",
|
|
2435
|
-
["chai",
|
|
2436
|
-
["codeceptjs",
|
|
2437
|
-
["webdriverio",
|
|
2438
|
-
["react-relay",
|
|
2439
|
-
["relay-runtime",
|
|
2440
|
-
// ---
|
|
2441
|
-
["typescript",
|
|
2442
|
-
["eslint",
|
|
2443
|
-
["prettier",
|
|
2444
|
-
["webpack",
|
|
2445
|
-
["vite",
|
|
2446
|
-
["esbuild",
|
|
2447
|
-
["rollup",
|
|
2448
|
-
["turbo",
|
|
2449
|
-
["tsup",
|
|
2450
|
-
["@biomejs/biome",
|
|
2451
|
-
["stylelint",
|
|
2452
|
-
// ---
|
|
2453
|
-
["@lwc/engine-dom",
|
|
2454
|
-
["@salesforce/core",
|
|
2455
|
-
["@shopify/polaris",
|
|
2456
|
-
//
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
["
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
["
|
|
2463
|
-
["
|
|
2464
|
-
["
|
|
2465
|
-
["
|
|
2466
|
-
["
|
|
2467
|
-
["
|
|
2468
|
-
|
|
2469
|
-
["
|
|
2470
|
-
["
|
|
2471
|
-
|
|
2472
|
-
["
|
|
2473
|
-
["
|
|
2474
|
-
["
|
|
2475
|
-
["
|
|
2476
|
-
["
|
|
2477
|
-
["
|
|
2478
|
-
["
|
|
2479
|
-
["
|
|
2480
|
-
["
|
|
2481
|
-
["
|
|
2482
|
-
["
|
|
2483
|
-
["
|
|
2484
|
-
["
|
|
2485
|
-
["
|
|
2486
|
-
["
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
[
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2533
|
+
["backbone", "backbone"],
|
|
2534
|
+
["chai", "chai"],
|
|
2535
|
+
["codeceptjs", "codeceptjs"],
|
|
2536
|
+
["webdriverio", "webdriverio"],
|
|
2537
|
+
["react-relay", "relay"],
|
|
2538
|
+
["relay-runtime", "relay"],
|
|
2539
|
+
// --- Tools ---
|
|
2540
|
+
["typescript", "typescript"],
|
|
2541
|
+
["eslint", "eslint"],
|
|
2542
|
+
["prettier", "prettier"],
|
|
2543
|
+
["webpack", "webpack"],
|
|
2544
|
+
["vite", "vite"],
|
|
2545
|
+
["esbuild", "esbuild"],
|
|
2546
|
+
["rollup", "rollup"],
|
|
2547
|
+
["turbo", "turborepo"],
|
|
2548
|
+
["tsup", "tsup"],
|
|
2549
|
+
["@biomejs/biome", "biome"],
|
|
2550
|
+
["stylelint", "stylelint"],
|
|
2551
|
+
// --- Platforms ---
|
|
2552
|
+
["@lwc/engine-dom", "lightning-web-components"],
|
|
2553
|
+
["@salesforce/core", "salesforce"],
|
|
2554
|
+
["@shopify/polaris", "shopify-polaris"],
|
|
2555
|
+
// The npm `angular` package is AngularJS 1.x — a different framework from both
|
|
2556
|
+
// modern Angular (the `@angular/*` scope) and pub's AngularDart. One global
|
|
2557
|
+
// name-keyed catalog could not hold all three; three entities can.
|
|
2558
|
+
["angular", "angular-js"]
|
|
2559
|
+
];
|
|
2560
|
+
var PREFIXES = [
|
|
2561
|
+
["@angular/", "angular"],
|
|
2562
|
+
["@nestjs/", "nestjs"],
|
|
2563
|
+
["@remix-run/", "remix"],
|
|
2564
|
+
["@sveltejs/", "sveltekit"],
|
|
2565
|
+
["@nuxt/", "nuxt"],
|
|
2566
|
+
["@storybook/", "storybook"],
|
|
2567
|
+
["@testing-library/", "testing-library"],
|
|
2568
|
+
["@tanstack/", "tanstack"],
|
|
2569
|
+
["@wdio/", "webdriverio"],
|
|
2570
|
+
// AWS CDK v1 construct libraries are published under the @aws-cdk/ scope.
|
|
2571
|
+
["@aws-cdk/", "aws-cdk"],
|
|
2572
|
+
["@reduxjs/", "redux"],
|
|
2573
|
+
["@prisma/", "prisma"],
|
|
2574
|
+
["@apollo/", "apollo"],
|
|
2575
|
+
["@formatjs/", "formatjs"],
|
|
2576
|
+
["@lingui/", "lingui"],
|
|
2577
|
+
["@emotion/", "emotion"],
|
|
2578
|
+
["@mui/", "material-ui"],
|
|
2579
|
+
["@chakra-ui/", "chakra-ui"],
|
|
2580
|
+
["@lwc/", "lightning-web-components"],
|
|
2581
|
+
["@salesforce/", "salesforce"],
|
|
2582
|
+
["@shopify/", "shopify"],
|
|
2583
|
+
["@aws-sdk/", "aws-sdk"],
|
|
2584
|
+
["@google-cloud/", "google-cloud"],
|
|
2585
|
+
["@azure/", "azure-sdk"]
|
|
2586
|
+
];
|
|
2587
|
+
var npm = {
|
|
2588
|
+
id: "npm",
|
|
2589
|
+
manifests: [{ basename: "package.json", parser: parsePackageJson }],
|
|
2590
|
+
telltales: TELLTALES,
|
|
2591
|
+
prefixes: PREFIXES
|
|
2592
|
+
};
|
|
2593
|
+
|
|
2594
|
+
// src/discover/ecosystems/pypi.ts
|
|
2595
|
+
function parseRequirementLine(rawLine) {
|
|
2596
|
+
const line = rawLine.trim();
|
|
2597
|
+
if (!line || line.startsWith("#") || line.startsWith("-")) return void 0;
|
|
2598
|
+
const noComment = line.split("#")[0].trim();
|
|
2599
|
+
if (!noComment) return void 0;
|
|
2600
|
+
const match = noComment.match(/^([a-zA-Z0-9_.-]+(?:\[[^\]]*\])?)\s*([<>=!~@;].*)?$/);
|
|
2601
|
+
if (!match) return void 0;
|
|
2602
|
+
const rawName = match[1].replace(/\[.*\]$/, "");
|
|
2603
|
+
return { name: rawName.toLowerCase().replace(/_/g, "-"), version: match[2]?.trim() };
|
|
2604
|
+
}
|
|
2605
|
+
function parseRequirementsTxt(content) {
|
|
2606
|
+
const deps = [];
|
|
2607
|
+
for (const rawLine of content.split("\n")) {
|
|
2608
|
+
const dep = parseRequirementLine(rawLine);
|
|
2609
|
+
if (dep) deps.push(dep);
|
|
2610
|
+
}
|
|
2611
|
+
return deps;
|
|
2612
|
+
}
|
|
2613
|
+
function sliceBalanced(content, fromIndex, open, close) {
|
|
2614
|
+
const start = content.indexOf(open, fromIndex);
|
|
2615
|
+
if (start === -1) return void 0;
|
|
2616
|
+
let depth = 0;
|
|
2617
|
+
let quote;
|
|
2618
|
+
for (let i = start; i < content.length; i++) {
|
|
2619
|
+
const ch = content[i];
|
|
2620
|
+
if (quote) {
|
|
2621
|
+
if (ch === "\\") i++;
|
|
2622
|
+
else if (ch === quote) quote = void 0;
|
|
2623
|
+
continue;
|
|
2624
|
+
}
|
|
2625
|
+
if (ch === '"' || ch === "'") {
|
|
2626
|
+
quote = ch;
|
|
2627
|
+
continue;
|
|
2628
|
+
}
|
|
2629
|
+
if (ch === open) depth++;
|
|
2630
|
+
else if (ch === close && --depth === 0) {
|
|
2631
|
+
return { body: content.slice(start + 1, i), endIndex: i };
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
return void 0;
|
|
2635
|
+
}
|
|
2636
|
+
function parseSetupPy(content) {
|
|
2637
|
+
const deps = [];
|
|
2638
|
+
const collectQuoted = (block) => {
|
|
2639
|
+
for (const quoted of block.matchAll(/["']([^"']+)["']/g)) {
|
|
2640
|
+
const dep = parseRequirementLine(quoted[1]);
|
|
2641
|
+
if (dep) deps.push(dep);
|
|
2642
|
+
}
|
|
2643
|
+
};
|
|
2644
|
+
const valueStartOf = (key) => {
|
|
2645
|
+
const match = new RegExp(`\\b${key}\\s*=\\s*`).exec(content);
|
|
2646
|
+
return match ? match.index + match[0].length : -1;
|
|
2647
|
+
};
|
|
2648
|
+
for (const key of ["install_requires", "tests_require", "setup_requires"]) {
|
|
2649
|
+
const start = valueStartOf(key);
|
|
2650
|
+
if (start === -1 || content[start] !== "[") continue;
|
|
2651
|
+
const list = sliceBalanced(content, start, "[", "]");
|
|
2652
|
+
if (list) collectQuoted(list.body);
|
|
2653
|
+
}
|
|
2654
|
+
const extrasStart = valueStartOf("extras_require");
|
|
2655
|
+
if (extrasStart !== -1 && content[extrasStart] === "{") {
|
|
2656
|
+
const dict = sliceBalanced(content, extrasStart, "{", "}");
|
|
2657
|
+
if (dict) {
|
|
2658
|
+
let cursor = 0;
|
|
2659
|
+
while (cursor < dict.body.length) {
|
|
2660
|
+
const list = sliceBalanced(dict.body, cursor, "[", "]");
|
|
2661
|
+
if (!list) break;
|
|
2662
|
+
collectQuoted(list.body);
|
|
2663
|
+
cursor = list.endIndex + 1;
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
return deps;
|
|
2668
|
+
}
|
|
2669
|
+
function parsePyprojectToml(content) {
|
|
2670
|
+
const deps = [];
|
|
2671
|
+
const projectDepsMatch = content.match(/\[project\][\s\S]*?dependencies\s*=\s*\[([\s\S]*?)\]/);
|
|
2672
|
+
if (projectDepsMatch) {
|
|
2673
|
+
const items = projectDepsMatch[1].match(/"([^"]+)"|'([^']+)'/g) ?? [];
|
|
2674
|
+
for (const item of items) {
|
|
2675
|
+
const raw = item.replace(/["']/g, "");
|
|
2676
|
+
const name = raw.split(/[<>=!~;\s]/)[0].replace(/\[.*\]$/, "");
|
|
2677
|
+
if (name) deps.push({ name: name.toLowerCase().replace(/_/g, "-") });
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
const poetryMatch = content.match(/\[tool\.poetry\.dependencies\]([\s\S]*?)(?:\n\[|\n$)/);
|
|
2681
|
+
if (poetryMatch) {
|
|
2682
|
+
const lines = poetryMatch[1].split("\n");
|
|
2683
|
+
for (const line of lines) {
|
|
2684
|
+
const m = line.match(/^([a-zA-Z0-9_.-]+)\s*=/);
|
|
2685
|
+
if (m && m[1] !== "python") {
|
|
2686
|
+
deps.push({ name: m[1].toLowerCase().replace(/_/g, "-") });
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
const poetryGroupRegex = /\[tool\.poetry\.group\.\w+\.dependencies\]([\s\S]*?)(?:\n\[|\n$)/g;
|
|
2691
|
+
let groupMatch;
|
|
2692
|
+
while ((groupMatch = poetryGroupRegex.exec(content)) !== null) {
|
|
2693
|
+
const lines = groupMatch[1].split("\n");
|
|
2694
|
+
for (const line of lines) {
|
|
2695
|
+
const m = line.match(/^([a-zA-Z0-9_.-]+)\s*=/);
|
|
2696
|
+
if (m) {
|
|
2697
|
+
deps.push({ name: m[1].toLowerCase().replace(/_/g, "-") });
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
return deps;
|
|
2702
|
+
}
|
|
2703
|
+
function parsePipfile(content) {
|
|
2704
|
+
const deps = [];
|
|
2705
|
+
let inDepsSection = false;
|
|
2706
|
+
for (const rawLine of content.split("\n")) {
|
|
2707
|
+
const line = rawLine.trim();
|
|
2708
|
+
if (/^\[(packages|dev-packages)\]/.test(line)) {
|
|
2709
|
+
inDepsSection = true;
|
|
2710
|
+
continue;
|
|
2711
|
+
}
|
|
2712
|
+
if (line.startsWith("[")) {
|
|
2713
|
+
inDepsSection = false;
|
|
2714
|
+
continue;
|
|
2715
|
+
}
|
|
2716
|
+
if (inDepsSection) {
|
|
2717
|
+
const m = line.match(/^([a-zA-Z0-9_.-]+)\s*=/);
|
|
2718
|
+
if (m) {
|
|
2719
|
+
deps.push({ name: m[1].toLowerCase().replace(/_/g, "-") });
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
return deps;
|
|
2724
|
+
}
|
|
2725
|
+
var TELLTALES2 = [
|
|
2726
|
+
// --- Frameworks ---
|
|
2727
|
+
["django", "django"],
|
|
2728
|
+
["flask", "flask"],
|
|
2729
|
+
["fastapi", "fastapi"],
|
|
2730
|
+
["tornado", "tornado"],
|
|
2731
|
+
["pyramid", "pyramid"],
|
|
2732
|
+
["starlette", "starlette"],
|
|
2733
|
+
["sanic", "sanic"],
|
|
2734
|
+
["bottle", "bottle"],
|
|
2735
|
+
["streamlit", "streamlit"],
|
|
2736
|
+
["gradio", "gradio"],
|
|
2737
|
+
["apache-airflow", "airflow"],
|
|
2738
|
+
// --- Libraries ---
|
|
2739
|
+
["flask-babel", "flask-babel"],
|
|
2740
|
+
["pytest", "pytest"],
|
|
2741
|
+
["numpy", "numpy"],
|
|
2742
|
+
["pandas", "pandas"],
|
|
2743
|
+
["requests", "requests"],
|
|
2744
|
+
["sqlalchemy", "sqlalchemy"],
|
|
2745
|
+
["celery", "celery"],
|
|
2746
|
+
["pydantic", "pydantic"],
|
|
2747
|
+
["boto3", "boto3"],
|
|
2748
|
+
// `botocore` is boto3's transport layer and `aws-lambda-powertools` depends on
|
|
2749
|
+
// it; either declared alone still means "this is AWS code". Lambda runtimes
|
|
2750
|
+
// preinstall boto3, so it is routinely absent from requirements.txt.
|
|
2751
|
+
["botocore", "boto3"],
|
|
2752
|
+
["aws-lambda-powertools", "boto3"],
|
|
2753
|
+
["scipy", "scipy"],
|
|
2754
|
+
["scikit-learn", "scikit-learn"],
|
|
2755
|
+
["tensorflow", "tensorflow"],
|
|
2756
|
+
["torch", "pytorch"],
|
|
2757
|
+
["httpx", "httpx"],
|
|
2758
|
+
["aiohttp", "aiohttp"],
|
|
2759
|
+
["pillow", "pillow"],
|
|
2760
|
+
["beautifulsoup4", "beautiful-soup"],
|
|
2761
|
+
["scrapy", "scrapy"],
|
|
2762
|
+
["alembic", "alembic"],
|
|
2763
|
+
["marshmallow", "marshmallow"],
|
|
2764
|
+
["click", "click"],
|
|
2765
|
+
["typer", "typer"],
|
|
2766
|
+
["rich", "rich"],
|
|
2767
|
+
["polars", "polars"],
|
|
2768
|
+
["dask", "dask"],
|
|
2769
|
+
["matplotlib", "matplotlib"],
|
|
2770
|
+
["seaborn", "seaborn"],
|
|
2771
|
+
["plotly", "plotly"],
|
|
2772
|
+
["jinja2", "jinja2"],
|
|
2773
|
+
["mako", "mako"],
|
|
2774
|
+
["lxml", "lxml"],
|
|
2775
|
+
["pyyaml", "pyyaml"],
|
|
2776
|
+
["paramiko", "paramiko"],
|
|
2777
|
+
["twilio", "twilio"],
|
|
2778
|
+
// Structured-concurrency libraries. Ruff's `ASYNC1xx` rules are written against
|
|
2779
|
+
// trio's checkpoint model and anyio implements the same one, so either gates them.
|
|
2780
|
+
["trio", "trio"],
|
|
2781
|
+
["anyio", "anyio"],
|
|
2782
|
+
// The two crypto packages are distinct projects with distinct Bandit checks
|
|
2783
|
+
// (`B413` imports PyCrypto, `B414` PyCryptodome), so they must not be merged.
|
|
2784
|
+
// `pycryptodomex` is the same project published under its `Cryptodome` namespace.
|
|
2785
|
+
["pycrypto", "pycrypto"],
|
|
2786
|
+
["pycryptodome", "pycryptodome"],
|
|
2787
|
+
["pycryptodomex", "pycryptodome"],
|
|
2788
|
+
// `pysnmp` was renamed to `pysnmplib` for part of its history; both are the
|
|
2789
|
+
// library Bandit's `B508`/`B509` weak-SNMP checks apply to.
|
|
2790
|
+
["pysnmp", "pysnmp"],
|
|
2791
|
+
["pysnmplib", "pysnmp"],
|
|
2497
2792
|
// MongoDB drivers, one entry per ecosystem — they all gate the same
|
|
2498
2793
|
// MongoDB-specific patterns (Opengrep's `java.mongodb.`/`python.pymongo.`).
|
|
2499
|
-
["pymongo",
|
|
2500
|
-
["motor",
|
|
2501
|
-
// ---
|
|
2502
|
-
["black",
|
|
2503
|
-
["ruff",
|
|
2504
|
-
["mypy",
|
|
2505
|
-
["pylint",
|
|
2506
|
-
["flake8",
|
|
2507
|
-
["bandit",
|
|
2508
|
-
["isort",
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2794
|
+
["pymongo", "mongodb"],
|
|
2795
|
+
["motor", "mongodb"],
|
|
2796
|
+
// --- Tools ---
|
|
2797
|
+
["black", "black"],
|
|
2798
|
+
["ruff", "ruff"],
|
|
2799
|
+
["mypy", "mypy"],
|
|
2800
|
+
["pylint", "pylint"],
|
|
2801
|
+
["flake8", "flake8"],
|
|
2802
|
+
["bandit", "bandit"],
|
|
2803
|
+
["isort", "isort"]
|
|
2804
|
+
];
|
|
2805
|
+
var pypi = {
|
|
2806
|
+
id: "pypi",
|
|
2807
|
+
manifests: [
|
|
2808
|
+
{ basename: "requirements.txt", parser: parseRequirementsTxt },
|
|
2809
|
+
{ basename: "pyproject.toml", parser: parsePyprojectToml },
|
|
2810
|
+
{ basename: "Pipfile", parser: parsePipfile },
|
|
2811
|
+
{ basename: "setup.py", parser: parseSetupPy },
|
|
2812
|
+
{
|
|
2813
|
+
// The requirements family, by path: real projects split it by environment
|
|
2814
|
+
// (`requirements-dev.txt`, `requirements/base.txt`, `requirements.in` for
|
|
2815
|
+
// pip-tools). Matching only the exact name made every dependency of a split
|
|
2816
|
+
// layout invisible, and with it the gating for Django, Flask, FastAPI,
|
|
2817
|
+
// pytest, pandas and NumPy patterns.
|
|
2818
|
+
matches: (filePath, basename) => /^requirements[a-zA-Z0-9_.-]*\.(txt|in)$/i.test(basename) || /(^|\/)requirements\/[^/]+\.(txt|in)$/i.test(filePath),
|
|
2819
|
+
parser: parseRequirementsTxt
|
|
2820
|
+
}
|
|
2821
|
+
],
|
|
2822
|
+
telltales: TELLTALES2
|
|
2823
|
+
};
|
|
2824
|
+
|
|
2825
|
+
// src/discover/ecosystems/gem.ts
|
|
2826
|
+
function parseGemfile(content) {
|
|
2827
|
+
const deps = [];
|
|
2828
|
+
const gemRegex = /^\s*gem\s+["']([^"']+)["'](?:\s*,\s*["']([^"']+)["'])?/gm;
|
|
2829
|
+
let match;
|
|
2830
|
+
while ((match = gemRegex.exec(content)) !== null) {
|
|
2831
|
+
deps.push({ name: match[1], version: match[2] });
|
|
2832
|
+
}
|
|
2833
|
+
return deps;
|
|
2834
|
+
}
|
|
2835
|
+
var TELLTALES3 = [
|
|
2836
|
+
// --- Frameworks ---
|
|
2837
|
+
["rails", "ruby-on-rails"],
|
|
2838
|
+
["sinatra", "sinatra"],
|
|
2839
|
+
["hanami", "hanami"],
|
|
2840
|
+
["grape", "grape"],
|
|
2841
|
+
["padrino", "padrino"],
|
|
2842
|
+
// --- Libraries ---
|
|
2843
|
+
["rspec", "rspec"],
|
|
2844
|
+
["rspec-core", "rspec"],
|
|
2845
|
+
["rspec-rails", "rspec-rails"],
|
|
2846
|
+
["capybara", "capybara"],
|
|
2847
|
+
["sidekiq", "sidekiq"],
|
|
2848
|
+
["devise", "devise"],
|
|
2849
|
+
["pundit", "pundit"],
|
|
2850
|
+
["activeadmin", "activeadmin"],
|
|
2851
|
+
["puma", "puma"],
|
|
2852
|
+
["nokogiri", "nokogiri"],
|
|
2853
|
+
["faraday", "faraday"],
|
|
2854
|
+
["httparty", "httparty"],
|
|
2855
|
+
["minitest", "minitest"],
|
|
2856
|
+
["factory_bot", "factorybot"],
|
|
2857
|
+
["dry-validation", "dry-validation"],
|
|
2858
|
+
// --- Tools ---
|
|
2859
|
+
["rubocop", "rubocop"],
|
|
2860
|
+
["bundler", "bundler"],
|
|
2536
2861
|
// Sorbet ships as several gems; any of them means the project is Sorbet-typed.
|
|
2537
|
-
["sorbet",
|
|
2538
|
-
["sorbet-runtime",
|
|
2539
|
-
["sorbet-static",
|
|
2540
|
-
["sorbet-static-and-runtime",
|
|
2541
|
-
["solargraph",
|
|
2862
|
+
["sorbet", "sorbet"],
|
|
2863
|
+
["sorbet-runtime", "sorbet"],
|
|
2864
|
+
["sorbet-static", "sorbet"],
|
|
2865
|
+
["sorbet-static-and-runtime", "sorbet"],
|
|
2866
|
+
["solargraph", "solargraph"],
|
|
2867
|
+
// GetText, under both the plain gem and its Rails integration. RuboCop's
|
|
2868
|
+
// `I18n_GetText_*` cops rewrite `_()`-decorated strings, so they only apply to a
|
|
2869
|
+
// project that actually uses GetText rather than Rails' own I18n.
|
|
2870
|
+
["gettext", "gettext"],
|
|
2871
|
+
["gettext_i18n_rails", "gettext"],
|
|
2872
|
+
["rubocop-i18n", "gettext"],
|
|
2873
|
+
// --- Names other registries also publish ---
|
|
2874
|
+
// `faker`, `rake` and `graphql` all exist on npm and PyPI too, and all three
|
|
2875
|
+
// gate RuboCop cops. Reaching them only from a Gemfile is the whole point of
|
|
2876
|
+
// scoping by ecosystem — a JS dependency must not enable Ruby cops.
|
|
2877
|
+
["faker", "faker"],
|
|
2878
|
+
["rake", "rake"],
|
|
2879
|
+
["graphql", "graphql"]
|
|
2880
|
+
];
|
|
2881
|
+
var gem = {
|
|
2882
|
+
id: "gem",
|
|
2883
|
+
manifests: [{ basename: "Gemfile", parser: parseGemfile }],
|
|
2884
|
+
telltales: TELLTALES3
|
|
2885
|
+
};
|
|
2886
|
+
|
|
2887
|
+
// src/discover/ecosystems/maven.ts
|
|
2888
|
+
function parsePomXml(content) {
|
|
2889
|
+
const deps = [];
|
|
2890
|
+
const depRegex = /<dependency>[\s\S]*?<artifactId>([^<]+)<\/artifactId>[\s\S]*?<\/dependency>/g;
|
|
2891
|
+
let match;
|
|
2892
|
+
while ((match = depRegex.exec(content)) !== null) {
|
|
2893
|
+
deps.push({ name: match[1].trim() });
|
|
2894
|
+
}
|
|
2895
|
+
return deps;
|
|
2896
|
+
}
|
|
2897
|
+
function parseBuildGradle(content) {
|
|
2898
|
+
const deps = [];
|
|
2899
|
+
const depRegex = /(?:implementation|testImplementation|testCompileOnly|testRuntimeOnly|api|compileOnly|runtimeOnly|kapt|annotationProcessor)\s*(?:\(\s*)?["']([^"':]+):([^"':]+)(?::([^"']+))?["']/g;
|
|
2900
|
+
let match;
|
|
2901
|
+
while ((match = depRegex.exec(content)) !== null) {
|
|
2902
|
+
deps.push({ name: match[2].trim(), version: match[3]?.trim() });
|
|
2903
|
+
}
|
|
2904
|
+
return deps;
|
|
2905
|
+
}
|
|
2906
|
+
function parseVersionCatalogToml(content) {
|
|
2907
|
+
const deps = [];
|
|
2908
|
+
let inLibraries = false;
|
|
2909
|
+
for (const rawLine of content.split("\n")) {
|
|
2910
|
+
const line = rawLine.trim();
|
|
2911
|
+
if (line === "" || line.startsWith("#")) continue;
|
|
2912
|
+
if (line.startsWith("[")) {
|
|
2913
|
+
inLibraries = /^\[\s*libraries\s*\]/.test(line);
|
|
2914
|
+
continue;
|
|
2915
|
+
}
|
|
2916
|
+
if (!inLibraries) continue;
|
|
2917
|
+
const moduleMatch = line.match(/\bmodule\s*=\s*["'][^"':]+:([^"']+)["']/);
|
|
2918
|
+
const nameMatch = line.match(/\bname\s*=\s*["']([^"']+)["']/);
|
|
2919
|
+
const versionMatch = line.match(/\bversion\s*=\s*["']([^"']+)["']/);
|
|
2920
|
+
if (moduleMatch || nameMatch) {
|
|
2921
|
+
deps.push({
|
|
2922
|
+
name: (moduleMatch?.[1] ?? nameMatch?.[1] ?? "").trim(),
|
|
2923
|
+
version: versionMatch?.[1]?.trim()
|
|
2924
|
+
});
|
|
2925
|
+
continue;
|
|
2926
|
+
}
|
|
2927
|
+
const shorthand = line.match(/^[A-Za-z0-9_.-]+\s*=\s*["'][^"':]+:([^"':]+)(?::([^"']+))?["']/);
|
|
2928
|
+
if (shorthand) {
|
|
2929
|
+
deps.push({ name: shorthand[1].trim(), version: shorthand[2]?.trim() });
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
return deps;
|
|
2933
|
+
}
|
|
2934
|
+
function parseBuildSbt(content) {
|
|
2935
|
+
const deps = [];
|
|
2936
|
+
const depRegex = /"([^"]+)"\s*%%?\s*"([^"]+)"\s*%(?!%)\s*(?:"([^"]+)")?/g;
|
|
2937
|
+
let match;
|
|
2938
|
+
while ((match = depRegex.exec(content)) !== null) {
|
|
2939
|
+
deps.push({ name: match[2], version: match[3] });
|
|
2940
|
+
}
|
|
2941
|
+
return deps;
|
|
2942
|
+
}
|
|
2943
|
+
function parseSbtBuildDefinition(content) {
|
|
2944
|
+
const deps = parseBuildSbt(content);
|
|
2945
|
+
const pluginRegex = /addSbtPlugin\s*\(\s*"([^"]+)"\s*%%?\s*"([^"]+)"/g;
|
|
2946
|
+
let match;
|
|
2947
|
+
while ((match = pluginRegex.exec(content)) !== null) {
|
|
2948
|
+
deps.push({ name: `${match[1]}:${match[2]}` });
|
|
2949
|
+
}
|
|
2950
|
+
return deps;
|
|
2951
|
+
}
|
|
2952
|
+
var TELLTALES4 = [
|
|
2542
2953
|
// --- Java / Kotlin — Frameworks ---
|
|
2543
|
-
["spring-boot-starter",
|
|
2544
|
-
["spring-boot-starter-web",
|
|
2545
|
-
["spring-core",
|
|
2546
|
-
["quarkus-core",
|
|
2547
|
-
["micronaut-core",
|
|
2548
|
-
["ktor-server-core",
|
|
2549
|
-
["android",
|
|
2954
|
+
["spring-boot-starter", "spring-boot"],
|
|
2955
|
+
["spring-boot-starter-web", "spring-boot"],
|
|
2956
|
+
["spring-core", "spring-framework"],
|
|
2957
|
+
["quarkus-core", "quarkus"],
|
|
2958
|
+
["micronaut-core", "micronaut"],
|
|
2959
|
+
["ktor-server-core", "ktor"],
|
|
2960
|
+
["android", "android-sdk"],
|
|
2550
2961
|
// --- Java / Kotlin — Libraries ---
|
|
2551
|
-
["junit-jupiter",
|
|
2552
|
-
["junit",
|
|
2553
|
-
["junit-jupiter-api",
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
["
|
|
2559
|
-
["
|
|
2560
|
-
["
|
|
2561
|
-
["
|
|
2562
|
-
["
|
|
2563
|
-
["
|
|
2564
|
-
["
|
|
2565
|
-
["
|
|
2566
|
-
["
|
|
2567
|
-
["
|
|
2568
|
-
["
|
|
2569
|
-
["
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
["
|
|
2574
|
-
["
|
|
2962
|
+
["junit-jupiter", "junit-5"],
|
|
2963
|
+
["junit", "junit"],
|
|
2964
|
+
["junit-jupiter-api", "junit-5"],
|
|
2965
|
+
// Spring Boot's test starter is the single most common Java test declaration and
|
|
2966
|
+
// pulls JUnit 5 transitively; without it PMD's JUnit rules are unreachable for
|
|
2967
|
+
// most Spring projects. The `junit-jupiter-`/`junit-platform-` prefixes below
|
|
2968
|
+
// cover `-engine`/`-params`/`-launcher`, which Gradle declares separately.
|
|
2969
|
+
["spring-boot-starter-test", "junit-5"],
|
|
2970
|
+
["mockito-core", "mockito"],
|
|
2971
|
+
["lombok", "lombok"],
|
|
2972
|
+
["jackson-databind", "jackson"],
|
|
2973
|
+
["jackson-core", "jackson"],
|
|
2974
|
+
["hibernate-core", "hibernate"],
|
|
2975
|
+
["guava", "guava"],
|
|
2976
|
+
["commons-lang3", "apache-commons-lang"],
|
|
2977
|
+
["commons-io", "apache-commons-io"],
|
|
2978
|
+
["commons-logging", "apache-commons-logging"],
|
|
2979
|
+
["commons-fileupload", "apache-commons-fileupload"],
|
|
2980
|
+
["commons-fileupload2-core", "apache-commons-fileupload"],
|
|
2981
|
+
// The EJB/Jakarta EE API jars, under every name they have shipped as. PMD's
|
|
2982
|
+
// session-bean naming and static-field rules only mean anything with these on
|
|
2983
|
+
// the classpath.
|
|
2984
|
+
["jakarta.ejb-api", "jakarta-ee"],
|
|
2985
|
+
["javax.ejb-api", "jakarta-ee"],
|
|
2986
|
+
["jakarta.jakartaee-api", "jakarta-ee"],
|
|
2987
|
+
["jakarta.jakartaee-web-api", "jakarta-ee"],
|
|
2988
|
+
["javaee-api", "jakarta-ee"],
|
|
2989
|
+
["javaee-web-api", "jakarta-ee"],
|
|
2990
|
+
["slf4j-api", "slf4j"],
|
|
2991
|
+
["log4j-core", "log4j"],
|
|
2992
|
+
["logback-classic", "logback"],
|
|
2993
|
+
["testng", "testng"],
|
|
2994
|
+
["assertj-core", "assertj"],
|
|
2995
|
+
["gson", "gson"],
|
|
2996
|
+
["okhttp", "okhttp"],
|
|
2997
|
+
["retrofit", "retrofit"],
|
|
2998
|
+
["spock-core", "spock"],
|
|
2999
|
+
["mongodb-driver-sync", "mongodb"],
|
|
3000
|
+
["mongodb-driver-core", "mongodb"],
|
|
3001
|
+
["mongodb-driver-reactivestreams", "mongodb"],
|
|
3002
|
+
["mongo-java-driver", "mongodb"],
|
|
2575
3003
|
// --- Groovy — Frameworks ---
|
|
2576
3004
|
// Grails/GORM artifact IDs vary (grails-core, grails-web, gorm-hibernate5, …);
|
|
2577
|
-
// the `grails-`/`gorm-` prefix rules
|
|
2578
|
-
["grails-core",
|
|
2579
|
-
["gorm-core",
|
|
2580
|
-
// --- Go — Frameworks ---
|
|
2581
|
-
["github.com/gin-gonic/gin", { displayName: "Gin", kind: "framework" }],
|
|
2582
|
-
["github.com/labstack/echo", { displayName: "Echo", kind: "framework" }],
|
|
2583
|
-
["github.com/gofiber/fiber", { displayName: "Fiber", kind: "framework" }],
|
|
2584
|
-
["github.com/go-chi/chi", { displayName: "Chi", kind: "framework" }],
|
|
2585
|
-
["github.com/gorilla/mux", { displayName: "Gorilla Mux", kind: "framework" }],
|
|
2586
|
-
["github.com/beego/beego", { displayName: "Beego", kind: "framework" }],
|
|
2587
|
-
// --- Go — Libraries ---
|
|
2588
|
-
["github.com/stretchr/testify", { displayName: "Testify", kind: "library" }],
|
|
2589
|
-
["github.com/spf13/cobra", { displayName: "Cobra", kind: "library" }],
|
|
2590
|
-
["github.com/spf13/viper", { displayName: "Viper", kind: "library" }],
|
|
2591
|
-
["go.uber.org/zap", { displayName: "Zap", kind: "library" }],
|
|
2592
|
-
["github.com/sirupsen/logrus", { displayName: "Logrus", kind: "library" }],
|
|
2593
|
-
["gorm.io/gorm", { displayName: "GORM", kind: "library" }],
|
|
2594
|
-
["github.com/go-kit/kit", { displayName: "Go Kit", kind: "library" }],
|
|
2595
|
-
["google.golang.org/protobuf", { displayName: "Protocol Buffers", kind: "library" }],
|
|
2596
|
-
["google.golang.org/grpc", { displayName: "gRPC", kind: "library" }],
|
|
2597
|
-
["github.com/jackc/pgx", { displayName: "pgx", kind: "library" }],
|
|
2598
|
-
["github.com/redis/go-redis", { displayName: "go-redis", kind: "library" }],
|
|
2599
|
-
["github.com/aws/aws-sdk-go-v2", { displayName: "AWS SDK for Go", kind: "library" }],
|
|
2600
|
-
["github.com/prometheus/client_golang", { displayName: "Prometheus Client", kind: "library" }],
|
|
2601
|
-
// --- Rust — Frameworks ---
|
|
2602
|
-
["actix-web", { displayName: "Actix Web", kind: "framework" }],
|
|
2603
|
-
["axum", { displayName: "Axum", kind: "framework" }],
|
|
2604
|
-
["rocket", { displayName: "Rocket", kind: "framework" }],
|
|
2605
|
-
["warp", { displayName: "Warp", kind: "framework" }],
|
|
2606
|
-
["tauri", { displayName: "Tauri", kind: "framework" }],
|
|
2607
|
-
// --- Rust — Libraries ---
|
|
2608
|
-
["tokio", { displayName: "Tokio", kind: "library" }],
|
|
2609
|
-
["serde", { displayName: "Serde", kind: "library" }],
|
|
2610
|
-
["clap", { displayName: "clap", kind: "library" }],
|
|
2611
|
-
["reqwest", { displayName: "reqwest", kind: "library" }],
|
|
2612
|
-
["diesel", { displayName: "Diesel", kind: "library" }],
|
|
2613
|
-
["sqlx", { displayName: "SQLx", kind: "library" }],
|
|
2614
|
-
["tracing", { displayName: "Tracing", kind: "library" }],
|
|
2615
|
-
["anyhow", { displayName: "anyhow", kind: "library" }],
|
|
2616
|
-
["thiserror", { displayName: "thiserror", kind: "library" }],
|
|
2617
|
-
["rayon", { displayName: "Rayon", kind: "library" }],
|
|
2618
|
-
// --- PHP — Frameworks ---
|
|
2619
|
-
["laravel/framework", { displayName: "Laravel", kind: "framework" }],
|
|
2620
|
-
["symfony/framework-bundle", { displayName: "Symfony", kind: "framework" }],
|
|
2621
|
-
["symfony/symfony", { displayName: "Symfony", kind: "framework" }],
|
|
2622
|
-
["codeigniter4/framework", { displayName: "CodeIgniter", kind: "framework" }],
|
|
2623
|
-
["slim/slim", { displayName: "Slim", kind: "framework" }],
|
|
2624
|
-
["cakephp/cakephp", { displayName: "CakePHP", kind: "framework" }],
|
|
2625
|
-
// WordPress is rarely a real Composer dependency; these are the common
|
|
2626
|
-
// "WordPress as a dependency" packages used by Bedrock/Composer-based sites.
|
|
2627
|
-
["johnpbloch/wordpress-core", { displayName: "WordPress", kind: "framework" }],
|
|
2628
|
-
["johnpbloch/wordpress", { displayName: "WordPress", kind: "framework" }],
|
|
2629
|
-
["roots/wordpress", { displayName: "WordPress", kind: "framework" }],
|
|
2630
|
-
["drupal/core", { displayName: "Drupal", kind: "framework" }],
|
|
2631
|
-
["drupal/core-recommended", { displayName: "Drupal", kind: "framework" }],
|
|
2632
|
-
["magento/product-community-edition", { displayName: "Magento", kind: "framework" }],
|
|
2633
|
-
["magento/product-enterprise-edition", { displayName: "Magento", kind: "framework" }],
|
|
2634
|
-
["magento/framework", { displayName: "Magento", kind: "framework" }],
|
|
2635
|
-
// --- PHP — Libraries ---
|
|
2636
|
-
["phpunit/phpunit", { displayName: "PHPUnit", kind: "library" }],
|
|
2637
|
-
["guzzlehttp/guzzle", { displayName: "Guzzle", kind: "library" }],
|
|
2638
|
-
["monolog/monolog", { displayName: "Monolog", kind: "library" }],
|
|
2639
|
-
["nesbot/carbon", { displayName: "Carbon", kind: "library" }],
|
|
2640
|
-
["doctrine/orm", { displayName: "Doctrine ORM", kind: "library" }],
|
|
2641
|
-
["doctrine/dbal", { displayName: "Doctrine DBAL", kind: "library" }],
|
|
2642
|
-
["league/flysystem", { displayName: "Flysystem", kind: "library" }],
|
|
2643
|
-
["php-amqplib/php-amqplib", { displayName: "php-amqplib", kind: "library" }],
|
|
2644
|
-
["predis/predis", { displayName: "Predis", kind: "library" }],
|
|
2645
|
-
// --- PHP — Tools ---
|
|
2646
|
-
["phpstan/phpstan", { displayName: "PHPStan", kind: "tool" }],
|
|
2647
|
-
["squizlabs/php_codesniffer", { displayName: "PHP_CodeSniffer", kind: "tool" }],
|
|
2648
|
-
// --- Swift — Frameworks ---
|
|
2649
|
-
["vapor", { displayName: "Vapor", kind: "framework" }],
|
|
2650
|
-
// --- Swift — Libraries ---
|
|
2651
|
-
["Alamofire", { displayName: "Alamofire", kind: "library" }],
|
|
2652
|
-
["SnapKit", { displayName: "SnapKit", kind: "library" }],
|
|
2653
|
-
["Kingfisher", { displayName: "Kingfisher", kind: "library" }],
|
|
2654
|
-
["Moya", { displayName: "Moya", kind: "library" }],
|
|
2655
|
-
["RxSwift", { displayName: "RxSwift", kind: "library" }],
|
|
2656
|
-
["RealmSwift", { displayName: "Realm", kind: "library" }],
|
|
3005
|
+
// the `grails-`/`gorm-` prefix rules catch the rest.
|
|
3006
|
+
["grails-core", "grails"],
|
|
3007
|
+
["gorm-core", "grails-gorm"],
|
|
2657
3008
|
// --- Scala — Frameworks ---
|
|
2658
3009
|
// Play is declared as an sbt *plugin*, so `group:artifact` (from
|
|
2659
|
-
// `addSbtPlugin`) is the reliable signal; the library artifacts
|
|
3010
|
+
// `addSbtPlugin`) is the reliable signal; the library artifacts that follow only
|
|
2660
3011
|
// appear in some builds. `play-json` is deliberately absent — it is a
|
|
2661
3012
|
// standalone JSON library used widely outside Play.
|
|
2662
|
-
["com.typesafe.play:sbt-plugin",
|
|
2663
|
-
["org.playframework:sbt-plugin",
|
|
2664
|
-
["play",
|
|
2665
|
-
["play-server",
|
|
2666
|
-
["play-ws",
|
|
2667
|
-
["play-test",
|
|
2668
|
-
["play-slick",
|
|
2669
|
-
["twirl-api",
|
|
2670
|
-
["scalatestplus-play",
|
|
2671
|
-
["akka-actor",
|
|
2672
|
-
["spray-can",
|
|
3013
|
+
["com.typesafe.play:sbt-plugin", "play-framework"],
|
|
3014
|
+
["org.playframework:sbt-plugin", "play-framework"],
|
|
3015
|
+
["play", "play-framework"],
|
|
3016
|
+
["play-server", "play-framework"],
|
|
3017
|
+
["play-ws", "play-framework"],
|
|
3018
|
+
["play-test", "play-framework"],
|
|
3019
|
+
["play-slick", "play-framework"],
|
|
3020
|
+
["twirl-api", "play-framework"],
|
|
3021
|
+
["scalatestplus-play", "play-framework"],
|
|
3022
|
+
["akka-actor", "akka"],
|
|
3023
|
+
["spray-can", "spray"],
|
|
2673
3024
|
// --- Scala — Libraries ---
|
|
2674
|
-
["scalatest",
|
|
2675
|
-
["specs2-core",
|
|
2676
|
-
["cats-core",
|
|
2677
|
-
["circe-core",
|
|
3025
|
+
["scalatest", "scalatest"],
|
|
3026
|
+
["specs2-core", "specs2"],
|
|
3027
|
+
["cats-core", "cats"],
|
|
3028
|
+
["circe-core", "circe"],
|
|
2678
3029
|
// Slick gates Opengrep's `scala.slick.` SQL-injection patterns.
|
|
2679
|
-
["slick",
|
|
2680
|
-
["slick-hikaricp",
|
|
3030
|
+
["slick", "slick"],
|
|
3031
|
+
["slick-hikaricp", "slick"],
|
|
2681
3032
|
// jwt-scala publishes one artifact per JSON backend; all gate the same
|
|
2682
3033
|
// hardcoded-secret patterns.
|
|
2683
|
-
["jwt-core",
|
|
2684
|
-
["jwt-circe",
|
|
2685
|
-
["jwt-play-json",
|
|
2686
|
-
["jwt-json4s",
|
|
2687
|
-
["jwt-spray-json",
|
|
2688
|
-
["jwt-upickle",
|
|
2689
|
-
["jwt-argonaut",
|
|
2690
|
-
["munit",
|
|
2691
|
-
["scalacheck",
|
|
2692
|
-
["scala-logging",
|
|
2693
|
-
["chimney",
|
|
2694
|
-
// ---
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
//
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
//
|
|
2703
|
-
["
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
//
|
|
2707
|
-
//
|
|
2708
|
-
//
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
["
|
|
2713
|
-
|
|
2714
|
-
//
|
|
2715
|
-
//
|
|
2716
|
-
["
|
|
2717
|
-
["
|
|
2718
|
-
["dio", { displayName: "Dio", kind: "library" }],
|
|
2719
|
-
// --- .NET (C# / VB) — Libraries ---
|
|
2720
|
-
// NuGet package IDs. Microsoft.* families are also covered by prefix rules below.
|
|
2721
|
-
["EntityFramework", { displayName: "Entity Framework", kind: "library" }],
|
|
2722
|
-
["NUnit", { displayName: "NUnit", kind: "library" }],
|
|
2723
|
-
["Moq", { displayName: "Moq", kind: "library" }],
|
|
2724
|
-
["NSubstitute", { displayName: "NSubstitute", kind: "library" }],
|
|
2725
|
-
["FluentAssertions", { displayName: "FluentAssertions", kind: "library" }],
|
|
2726
|
-
["Shouldly", { displayName: "Shouldly", kind: "library" }],
|
|
2727
|
-
["NLog", { displayName: "NLog", kind: "library" }],
|
|
2728
|
-
["log4net", { displayName: "log4net", kind: "library" }],
|
|
2729
|
-
["Newtonsoft.Json", { displayName: "Json.NET", kind: "library" }],
|
|
2730
|
-
["AutoMapper", { displayName: "AutoMapper", kind: "library" }],
|
|
2731
|
-
["Dapper", { displayName: "Dapper", kind: "library" }],
|
|
2732
|
-
["MediatR", { displayName: "MediatR", kind: "library" }]
|
|
2733
|
-
]);
|
|
2734
|
-
var DEPENDENCY_PREFIX_RULES = [
|
|
2735
|
-
{ prefix: "@angular/", classification: { displayName: "Angular", kind: "framework" } },
|
|
2736
|
-
{ prefix: "@nestjs/", classification: { displayName: "NestJS", kind: "framework" } },
|
|
2737
|
-
{ prefix: "@remix-run/", classification: { displayName: "Remix", kind: "framework" } },
|
|
2738
|
-
{ prefix: "@sveltejs/", classification: { displayName: "SvelteKit", kind: "framework" } },
|
|
2739
|
-
{ prefix: "@nuxt/", classification: { displayName: "Nuxt", kind: "framework" } },
|
|
2740
|
-
{ prefix: "@storybook/", classification: { displayName: "Storybook", kind: "library" } },
|
|
2741
|
-
{
|
|
2742
|
-
prefix: "@testing-library/",
|
|
2743
|
-
classification: { displayName: "Testing Library", kind: "library" }
|
|
2744
|
-
},
|
|
2745
|
-
{ prefix: "@tanstack/", classification: { displayName: "TanStack", kind: "library" } },
|
|
2746
|
-
{ prefix: "@wdio/", classification: { displayName: "WebdriverIO", kind: "library" } },
|
|
2747
|
-
// AWS CDK v1 construct libraries are published under the @aws-cdk/ scope.
|
|
2748
|
-
{ prefix: "@aws-cdk/", classification: { displayName: "AWS CDK", kind: "framework" } },
|
|
2749
|
-
{ prefix: "@reduxjs/", classification: { displayName: "Redux", kind: "library" } },
|
|
2750
|
-
{ prefix: "@prisma/", classification: { displayName: "Prisma", kind: "library" } },
|
|
2751
|
-
{ prefix: "@apollo/", classification: { displayName: "Apollo", kind: "library" } },
|
|
2752
|
-
{ prefix: "@formatjs/", classification: { displayName: "formatjs", kind: "library" } },
|
|
2753
|
-
{ prefix: "@lingui/", classification: { displayName: "Lingui", kind: "library" } },
|
|
2754
|
-
{ prefix: "@emotion/", classification: { displayName: "Emotion", kind: "library" } },
|
|
2755
|
-
{ prefix: "@mui/", classification: { displayName: "Material UI", kind: "library" } },
|
|
2756
|
-
{ prefix: "@chakra-ui/", classification: { displayName: "Chakra UI", kind: "library" } },
|
|
2757
|
-
{
|
|
2758
|
-
prefix: "@lwc/",
|
|
2759
|
-
classification: { displayName: "Lightning Web Components", kind: "platform" }
|
|
2760
|
-
},
|
|
2761
|
-
{ prefix: "@salesforce/", classification: { displayName: "Salesforce", kind: "platform" } },
|
|
2762
|
-
{ prefix: "@shopify/", classification: { displayName: "Shopify", kind: "platform" } },
|
|
2763
|
-
{ prefix: "@aws-sdk/", classification: { displayName: "AWS SDK", kind: "library" } },
|
|
2764
|
-
{ prefix: "@google-cloud/", classification: { displayName: "Google Cloud", kind: "library" } },
|
|
2765
|
-
{ prefix: "@azure/", classification: { displayName: "Azure SDK", kind: "library" } },
|
|
3034
|
+
["jwt-core", "jwt-scala"],
|
|
3035
|
+
["jwt-circe", "jwt-scala"],
|
|
3036
|
+
["jwt-play-json", "jwt-scala"],
|
|
3037
|
+
["jwt-json4s", "jwt-scala"],
|
|
3038
|
+
["jwt-spray-json", "jwt-scala"],
|
|
3039
|
+
["jwt-upickle", "jwt-scala"],
|
|
3040
|
+
["jwt-argonaut", "jwt-scala"],
|
|
3041
|
+
["munit", "munit"],
|
|
3042
|
+
["scalacheck", "scalacheck"],
|
|
3043
|
+
["scala-logging", "scala-logging"],
|
|
3044
|
+
["chimney", "chimney"],
|
|
3045
|
+
// --- WebJars — front-end libraries republished to Maven Central ---
|
|
3046
|
+
// A JVM web app serves its JavaScript from a WebJar instead of from npm, so
|
|
3047
|
+
// `"org.webjars" % "jquery"` is real evidence of jQuery in the repo — and jQuery
|
|
3048
|
+
// gates Opengrep's `javascript.jquery.` rules. This is the one place where an
|
|
3049
|
+
// entity legitimately belongs to two registries, and dropping it was the only
|
|
3050
|
+
// recall loss the before/after sweep over local repos turned up (codacy-website).
|
|
3051
|
+
// The parsers emit bare artifact ids, so the `org.webjars` group is not visible
|
|
3052
|
+
// here; nothing else on Maven Central is named after a front-end library, so
|
|
3053
|
+
// matching the artifact alone is safe.
|
|
3054
|
+
["jquery", "jquery"]
|
|
3055
|
+
];
|
|
3056
|
+
var PREFIXES2 = [
|
|
3057
|
+
// Kotlin — coroutines ships one artifact per target (`-core`, `-android`,
|
|
3058
|
+
// `-test`, `-jdk8`, `-reactive`, …) and any of them means the project uses
|
|
3059
|
+
// coroutines, which is what detekt's `coroutines` rules gate on.
|
|
3060
|
+
["kotlinx-coroutines-", "kotlinx-coroutines"],
|
|
3061
|
+
// JUnit 5 ships one artifact per role (`-engine`, `-params`, `-launcher`), and
|
|
3062
|
+
// Gradle builds declare them separately from `junit-jupiter` itself.
|
|
3063
|
+
["junit-jupiter-", "junit-5"],
|
|
3064
|
+
["junit-platform-", "junit-5"],
|
|
3065
|
+
// A modern Android project declares `androidx.*`, never the deprecated
|
|
3066
|
+
// `com.google.android:android` jar the exact telltale matches.
|
|
3067
|
+
["androidx-", "android-sdk"],
|
|
3068
|
+
["androidx.", "android-sdk"],
|
|
2766
3069
|
// Groovy — Grails/GORM artifact families (build.gradle artifactIds).
|
|
2767
|
-
|
|
2768
|
-
|
|
3070
|
+
["grails-", "grails"],
|
|
3071
|
+
["gorm-", "grails-gorm"],
|
|
2769
3072
|
// Scala — artifact families published one module per feature (sbt artifactIds).
|
|
2770
3073
|
// Enumerating members would go stale on every release; the prefixes are
|
|
2771
3074
|
// distinctive enough not to collide with other ecosystems.
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
3075
|
+
["http4s-", "http4s"],
|
|
3076
|
+
["akka-", "akka"],
|
|
3077
|
+
["pekko-", "pekko"],
|
|
3078
|
+
["zio-", "zio"],
|
|
2776
3079
|
// Must precede any broader `cats-` rule: cats-effect is its own project.
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
3080
|
+
["cats-effect", "cats-effect"],
|
|
3081
|
+
["doobie-", "doobie"],
|
|
3082
|
+
["fs2-", "fs2"],
|
|
3083
|
+
["circe-", "circe"],
|
|
3084
|
+
["pureconfig", "pureconfig"],
|
|
3085
|
+
["kamon-", "kamon"],
|
|
3086
|
+
["flyway-", "flyway"],
|
|
3087
|
+
["testcontainers-scala", "testcontainers"]
|
|
3088
|
+
];
|
|
3089
|
+
var maven = {
|
|
3090
|
+
id: "maven",
|
|
3091
|
+
manifests: [
|
|
3092
|
+
{ basename: "pom.xml", parser: parsePomXml },
|
|
3093
|
+
{ basename: "build.gradle", parser: parseBuildGradle },
|
|
3094
|
+
{ basename: "build.gradle.kts", parser: parseBuildGradle },
|
|
3095
|
+
{ basename: "libs.versions.toml", parser: parseVersionCatalogToml },
|
|
3096
|
+
{ basename: "build.sbt", parser: parseBuildSbt },
|
|
3097
|
+
{
|
|
3098
|
+
// sbt build definition: `project/Dependencies.scala`, `project/plugins.sbt`
|
|
3099
|
+
// and the meta-build's own `project/project/*`. Matching by directory rather
|
|
3100
|
+
// than basename is what this rule exists for — the Scala file can be called
|
|
3101
|
+
// anything, and `*.scala` on its own is source code, not a manifest.
|
|
3102
|
+
//
|
|
3103
|
+
// Case-sensitive deliberately: sbt's directory is always lowercase `project`,
|
|
3104
|
+
// while `Project` is an ordinary domain-package name, and reading
|
|
3105
|
+
// `…/domain/Project/Project.scala` as a manifest would be wrong.
|
|
3106
|
+
matches: (filePath) => /(^|\/)project\/[^/]+\.(scala|sbt)$/.test(filePath),
|
|
3107
|
+
parser: parseSbtBuildDefinition
|
|
3108
|
+
}
|
|
3109
|
+
],
|
|
3110
|
+
telltales: TELLTALES4,
|
|
3111
|
+
prefixes: PREFIXES2
|
|
3112
|
+
};
|
|
3113
|
+
|
|
3114
|
+
// src/discover/ecosystems/golang.ts
|
|
3115
|
+
function parseGoMod(content) {
|
|
3116
|
+
const deps = [];
|
|
3117
|
+
const singleRegex = /^require\s+(\S+)\s+(\S+)/gm;
|
|
3118
|
+
let match;
|
|
3119
|
+
while ((match = singleRegex.exec(content)) !== null) {
|
|
3120
|
+
if (!match[1].startsWith("(")) {
|
|
3121
|
+
deps.push({ name: match[1], version: match[2] });
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
const blockRegex = /require\s*\(([\s\S]*?)\)/g;
|
|
3125
|
+
while ((match = blockRegex.exec(content)) !== null) {
|
|
3126
|
+
const lines = match[1].split("\n");
|
|
3127
|
+
for (const line of lines) {
|
|
3128
|
+
const m = line.trim().match(/^(\S+)\s+(\S+)/);
|
|
3129
|
+
if (m && !m[1].startsWith("//")) {
|
|
3130
|
+
deps.push({ name: m[1], version: m[2] });
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
return deps;
|
|
3135
|
+
}
|
|
3136
|
+
var TELLTALES5 = [
|
|
3137
|
+
// --- Frameworks ---
|
|
3138
|
+
["github.com/gin-gonic/gin", "gin"],
|
|
3139
|
+
["github.com/labstack/echo", "echo"],
|
|
3140
|
+
["github.com/gofiber/fiber", "fiber"],
|
|
3141
|
+
["github.com/go-chi/chi", "chi"],
|
|
3142
|
+
["github.com/gorilla/mux", "gorilla-mux"],
|
|
3143
|
+
["github.com/beego/beego", "beego"],
|
|
3144
|
+
// --- Libraries ---
|
|
3145
|
+
["github.com/stretchr/testify", "testify"],
|
|
3146
|
+
["github.com/spf13/cobra", "cobra"],
|
|
3147
|
+
["github.com/spf13/viper", "viper"],
|
|
3148
|
+
["go.uber.org/zap", "zap"],
|
|
3149
|
+
["github.com/sirupsen/logrus", "logrus"],
|
|
3150
|
+
["gorm.io/gorm", "go-gorm"],
|
|
3151
|
+
["github.com/go-kit/kit", "go-kit"],
|
|
3152
|
+
["google.golang.org/protobuf", "protocol-buffers"],
|
|
3153
|
+
["google.golang.org/grpc", "grpc"],
|
|
3154
|
+
["github.com/jackc/pgx", "pgx"],
|
|
3155
|
+
["github.com/redis/go-redis", "go-redis"],
|
|
3156
|
+
// v1 is still widely deployed; both gate Opengrep's AWS Lambda packs.
|
|
3157
|
+
["github.com/aws/aws-sdk-go", "aws-sdk-go"],
|
|
3158
|
+
["github.com/aws/aws-sdk-go-v2", "aws-sdk-go"],
|
|
3159
|
+
["github.com/prometheus/client_golang", "prometheus-client"]
|
|
3160
|
+
];
|
|
3161
|
+
var golang = {
|
|
3162
|
+
id: "golang",
|
|
3163
|
+
manifests: [{ basename: "go.mod", parser: parseGoMod }],
|
|
3164
|
+
telltales: TELLTALES5
|
|
3165
|
+
};
|
|
3166
|
+
|
|
3167
|
+
// src/discover/ecosystems/cargo.ts
|
|
3168
|
+
function parseCargoToml(content) {
|
|
3169
|
+
const deps = [];
|
|
3170
|
+
let inDepsSection = false;
|
|
3171
|
+
for (const rawLine of content.split("\n")) {
|
|
3172
|
+
const line = rawLine.trim();
|
|
3173
|
+
if (/^\[((?:dev-|build-)?dependencies)\]/.test(line)) {
|
|
3174
|
+
inDepsSection = true;
|
|
3175
|
+
continue;
|
|
3176
|
+
}
|
|
3177
|
+
if (line.startsWith("[") && !line.includes("dependencies.")) {
|
|
3178
|
+
inDepsSection = false;
|
|
3179
|
+
continue;
|
|
3180
|
+
}
|
|
3181
|
+
if (inDepsSection) {
|
|
3182
|
+
const m = line.match(/^([a-zA-Z0-9_-]+)\s*=/);
|
|
3183
|
+
if (m) {
|
|
3184
|
+
const versionMatch = line.match(/["']([^"']+)["']/);
|
|
3185
|
+
deps.push({ name: m[1], version: versionMatch?.[1] });
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
return deps;
|
|
3190
|
+
}
|
|
3191
|
+
var TELLTALES6 = [
|
|
3192
|
+
// --- Frameworks ---
|
|
3193
|
+
["actix-web", "actix-web"],
|
|
3194
|
+
["axum", "axum"],
|
|
3195
|
+
["rocket", "rocket"],
|
|
3196
|
+
["warp", "warp"],
|
|
3197
|
+
["tauri", "tauri"],
|
|
3198
|
+
// --- Libraries ---
|
|
3199
|
+
["tokio", "tokio"],
|
|
3200
|
+
["serde", "serde"],
|
|
3201
|
+
["clap", "clap"],
|
|
3202
|
+
["reqwest", "reqwest"],
|
|
3203
|
+
["diesel", "diesel"],
|
|
3204
|
+
["sqlx", "sqlx"],
|
|
3205
|
+
["tracing", "tracing"],
|
|
3206
|
+
["anyhow", "anyhow"],
|
|
3207
|
+
["thiserror", "thiserror"],
|
|
3208
|
+
["rayon", "rayon"]
|
|
3209
|
+
];
|
|
3210
|
+
var cargo = {
|
|
3211
|
+
id: "cargo",
|
|
3212
|
+
manifests: [{ basename: "Cargo.toml", parser: parseCargoToml }],
|
|
3213
|
+
telltales: TELLTALES6
|
|
3214
|
+
};
|
|
3215
|
+
|
|
3216
|
+
// src/discover/ecosystems/composer.ts
|
|
3217
|
+
function parseComposerJson(content) {
|
|
3218
|
+
try {
|
|
3219
|
+
const pkg = JSON.parse(content);
|
|
3220
|
+
const deps = [];
|
|
3221
|
+
for (const section of ["require", "require-dev"]) {
|
|
3222
|
+
const entries2 = pkg[section];
|
|
3223
|
+
if (entries2 && typeof entries2 === "object") {
|
|
3224
|
+
for (const [name, version] of Object.entries(entries2)) {
|
|
3225
|
+
if (name === "php" || name.startsWith("ext-")) continue;
|
|
3226
|
+
deps.push({ name, version: typeof version === "string" ? version : void 0 });
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
return deps;
|
|
3231
|
+
} catch {
|
|
3232
|
+
return [];
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
var TELLTALES7 = [
|
|
3236
|
+
// --- Frameworks ---
|
|
3237
|
+
["laravel/framework", "laravel"],
|
|
3238
|
+
["symfony/framework-bundle", "symfony"],
|
|
3239
|
+
["symfony/symfony", "symfony"],
|
|
3240
|
+
["codeigniter4/framework", "codeigniter"],
|
|
3241
|
+
["slim/slim", "slim"],
|
|
3242
|
+
["cakephp/cakephp", "cakephp"],
|
|
3243
|
+
// WordPress is rarely a real Composer dependency; these are the common
|
|
3244
|
+
// "WordPress as a dependency" packages used by Bedrock/Composer-based sites.
|
|
3245
|
+
["johnpbloch/wordpress-core", "wordpress"],
|
|
3246
|
+
["johnpbloch/wordpress", "wordpress"],
|
|
3247
|
+
["roots/wordpress", "wordpress"],
|
|
3248
|
+
["drupal/core", "drupal"],
|
|
3249
|
+
["drupal/core-recommended", "drupal"],
|
|
3250
|
+
["magento/product-community-edition", "magento"],
|
|
3251
|
+
["magento/product-enterprise-edition", "magento"],
|
|
3252
|
+
["magento/framework", "magento"],
|
|
3253
|
+
// --- Libraries ---
|
|
3254
|
+
["phpunit/phpunit", "phpunit"],
|
|
3255
|
+
["guzzlehttp/guzzle", "guzzle"],
|
|
3256
|
+
["monolog/monolog", "monolog"],
|
|
3257
|
+
["nesbot/carbon", "carbon"],
|
|
3258
|
+
["doctrine/orm", "doctrine-orm"],
|
|
3259
|
+
["doctrine/dbal", "doctrine-dbal"],
|
|
3260
|
+
["league/flysystem", "flysystem"],
|
|
3261
|
+
["php-amqplib/php-amqplib", "php-amqplib"],
|
|
3262
|
+
["predis/predis", "predis"],
|
|
3263
|
+
// --- Tools ---
|
|
3264
|
+
["phpstan/phpstan", "phpstan"],
|
|
3265
|
+
["squizlabs/php_codesniffer", "php-codesniffer"]
|
|
3266
|
+
];
|
|
3267
|
+
var composer = {
|
|
3268
|
+
id: "composer",
|
|
3269
|
+
manifests: [{ basename: "composer.json", parser: parseComposerJson }],
|
|
3270
|
+
telltales: TELLTALES7
|
|
3271
|
+
};
|
|
3272
|
+
|
|
3273
|
+
// src/discover/ecosystems/nuget.ts
|
|
3274
|
+
function parseXmlDependencyElements(content, elementRegex, nameAttr) {
|
|
3275
|
+
const deps = [];
|
|
3276
|
+
const nameRegex = new RegExp(`\\b${nameAttr}\\s*=\\s*["']([^"']+)["']`, "i");
|
|
3277
|
+
const versionRegex = /\bversion\s*=\s*["']([^"']+)["']/i;
|
|
3278
|
+
let match;
|
|
3279
|
+
while ((match = elementRegex.exec(content)) !== null) {
|
|
3280
|
+
const attrs = match[1];
|
|
3281
|
+
const nameMatch = attrs.match(nameRegex);
|
|
3282
|
+
if (!nameMatch) continue;
|
|
3283
|
+
const versionMatch = attrs.match(versionRegex);
|
|
3284
|
+
deps.push({ name: nameMatch[1].trim(), version: versionMatch?.[1]?.trim() });
|
|
3285
|
+
}
|
|
3286
|
+
return deps;
|
|
3287
|
+
}
|
|
3288
|
+
function parseCsproj(content) {
|
|
3289
|
+
return parseXmlDependencyElements(
|
|
3290
|
+
content,
|
|
3291
|
+
/<Package(?:Reference|Version)\s+([^>]+?)\/?>/gi,
|
|
3292
|
+
"Include"
|
|
3293
|
+
);
|
|
3294
|
+
}
|
|
3295
|
+
function parsePackagesConfig(content) {
|
|
3296
|
+
return parseXmlDependencyElements(content, /<package\s+([^>]+?)\/?>/gi, "id");
|
|
3297
|
+
}
|
|
3298
|
+
var TELLTALES8 = [
|
|
3299
|
+
// --- Libraries ---
|
|
3300
|
+
// NuGet package IDs. Microsoft.* families are covered by the prefix rules.
|
|
3301
|
+
["EntityFramework", "entity-framework"],
|
|
3302
|
+
["NUnit", "nunit"],
|
|
3303
|
+
["Moq", "moq"],
|
|
3304
|
+
["NSubstitute", "nsubstitute"],
|
|
3305
|
+
["FluentAssertions", "fluentassertions"],
|
|
3306
|
+
["Shouldly", "shouldly"],
|
|
3307
|
+
["NLog", "nlog"],
|
|
3308
|
+
["log4net", "log4net"],
|
|
3309
|
+
["Newtonsoft.Json", "json-net"],
|
|
3310
|
+
["AutoMapper", "automapper"],
|
|
3311
|
+
["Dapper", "dapper"],
|
|
3312
|
+
["MediatR", "mediatr"]
|
|
3313
|
+
];
|
|
3314
|
+
var PREFIXES3 = [
|
|
2788
3315
|
// .NET — Microsoft package families and common test/logging libraries. More
|
|
2789
3316
|
// specific prefixes (Blazor) must precede broader ones (ASP.NET Core).
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
{
|
|
2799
|
-
prefix: "Microsoft.EntityFrameworkCore",
|
|
2800
|
-
classification: { displayName: "Entity Framework", kind: "library" }
|
|
2801
|
-
},
|
|
2802
|
-
{
|
|
2803
|
-
prefix: "Microsoft.Azure.Functions",
|
|
2804
|
-
classification: { displayName: "Azure Functions", kind: "framework" }
|
|
2805
|
-
},
|
|
2806
|
-
{
|
|
2807
|
-
prefix: "Microsoft.Azure.WebJobs",
|
|
2808
|
-
classification: { displayName: "Azure Functions", kind: "framework" }
|
|
2809
|
-
},
|
|
2810
|
-
{ prefix: "xunit", classification: { displayName: "xUnit", kind: "library" } },
|
|
2811
|
-
{ prefix: "MSTest.", classification: { displayName: "MSTest", kind: "library" } },
|
|
2812
|
-
{ prefix: "Serilog", classification: { displayName: "Serilog", kind: "library" } }
|
|
3317
|
+
["Microsoft.AspNetCore.Components.WebAssembly", "blazor"],
|
|
3318
|
+
["Microsoft.AspNetCore.", "aspnet-core"],
|
|
3319
|
+
["Microsoft.EntityFrameworkCore", "entity-framework"],
|
|
3320
|
+
["Microsoft.Azure.Functions", "azure-functions"],
|
|
3321
|
+
["Microsoft.Azure.WebJobs", "azure-functions"],
|
|
3322
|
+
["xunit", "xunit"],
|
|
3323
|
+
["MSTest.", "mstest"],
|
|
3324
|
+
["Serilog", "serilog"]
|
|
2813
3325
|
];
|
|
2814
|
-
var
|
|
2815
|
-
|
|
2816
|
-
[
|
|
2817
|
-
"
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
3326
|
+
var nuget = {
|
|
3327
|
+
id: "nuget",
|
|
3328
|
+
manifests: [
|
|
3329
|
+
{ basename: "packages.config", parser: parsePackagesConfig },
|
|
3330
|
+
{ basename: "Directory.Packages.props", parser: parseCsproj },
|
|
3331
|
+
// Project files carry an arbitrary basename, so they match by suffix. Matched
|
|
3332
|
+
// case-insensitively — .NET project extensions may be upper-cased.
|
|
3333
|
+
{ suffix: ".csproj", parser: parseCsproj },
|
|
3334
|
+
{ suffix: ".vbproj", parser: parseCsproj },
|
|
3335
|
+
{ suffix: ".fsproj", parser: parseCsproj }
|
|
3336
|
+
],
|
|
3337
|
+
telltales: TELLTALES8,
|
|
3338
|
+
prefixes: PREFIXES3
|
|
3339
|
+
};
|
|
3340
|
+
|
|
3341
|
+
// src/discover/ecosystems/pub.ts
|
|
3342
|
+
function parsePubspecYaml(content) {
|
|
3343
|
+
const deps = [];
|
|
3344
|
+
let inSection = false;
|
|
3345
|
+
let childIndent = -1;
|
|
3346
|
+
for (const rawLine of content.split("\n")) {
|
|
3347
|
+
const trimmed = rawLine.trim();
|
|
3348
|
+
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
3349
|
+
const indent = rawLine.length - rawLine.trimStart().length;
|
|
3350
|
+
if (indent === 0) {
|
|
3351
|
+
inSection = /^(dependencies|dev_dependencies|dependency_overrides):/.test(trimmed);
|
|
3352
|
+
childIndent = -1;
|
|
3353
|
+
continue;
|
|
3354
|
+
}
|
|
3355
|
+
if (!inSection) continue;
|
|
3356
|
+
if (childIndent === -1) childIndent = indent;
|
|
3357
|
+
if (indent !== childIndent) continue;
|
|
3358
|
+
const m = trimmed.match(/^([a-zA-Z0-9_.]+)\s*:(?:\s*["']?\^?([0-9][^"'#\s]*))?/);
|
|
3359
|
+
if (m) deps.push({ name: m[1], version: m[2] });
|
|
2828
3360
|
}
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
3361
|
+
return deps;
|
|
3362
|
+
}
|
|
3363
|
+
var TELLTALES9 = [
|
|
3364
|
+
// --- Frameworks ---
|
|
3365
|
+
// `flutter` and `angular` appear as plain keys in pubspec.yaml. `angular` here is
|
|
3366
|
+
// AngularDart — a different framework from npm's `angular` (AngularJS 1.x) and
|
|
3367
|
+
// from modern Angular — hence the distinct `angular-dart` entity.
|
|
3368
|
+
["flutter", "flutter"],
|
|
3369
|
+
["flutter_test", "flutter"],
|
|
3370
|
+
["angular", "angular-dart"],
|
|
3371
|
+
// --- Libraries ---
|
|
3372
|
+
// Kept to Dart-specific names with negligible cross-ecosystem collision risk
|
|
3373
|
+
// (generic names like "get"/"provider" are intentionally omitted).
|
|
3374
|
+
["riverpod", "riverpod"],
|
|
3375
|
+
["flutter_bloc", "bloc"],
|
|
3376
|
+
["dio", "dio"]
|
|
3377
|
+
];
|
|
3378
|
+
var pub = {
|
|
3379
|
+
id: "pub",
|
|
3380
|
+
manifests: [{ basename: "pubspec.yaml", parser: parsePubspecYaml }],
|
|
3381
|
+
telltales: TELLTALES9
|
|
3382
|
+
};
|
|
3383
|
+
|
|
3384
|
+
// src/discover/ecosystems/hex.ts
|
|
3385
|
+
function parseMixExs(content) {
|
|
3386
|
+
const deps = [];
|
|
3387
|
+
const depRegex = /\{:([a-zA-Z_][a-zA-Z0-9_]*)\s*,\s*"([^"]*)"/g;
|
|
3388
|
+
let match;
|
|
3389
|
+
while ((match = depRegex.exec(content)) !== null) {
|
|
3390
|
+
deps.push({ name: match[1], version: match[2] || void 0 });
|
|
3391
|
+
}
|
|
3392
|
+
return deps;
|
|
3393
|
+
}
|
|
3394
|
+
var TELLTALES10 = [
|
|
3395
|
+
// --- Frameworks ---
|
|
3396
|
+
["phoenix", "phoenix"],
|
|
3397
|
+
["plug", "plug"],
|
|
3398
|
+
["plug_cowboy", "plug"],
|
|
3399
|
+
// --- Libraries ---
|
|
3400
|
+
["ecto", "ecto"],
|
|
3401
|
+
["ecto_sql", "ecto"],
|
|
3402
|
+
["phoenix_ecto", "ecto"]
|
|
3403
|
+
];
|
|
3404
|
+
var hex = {
|
|
3405
|
+
id: "hex",
|
|
3406
|
+
manifests: [{ basename: "mix.exs", parser: parseMixExs }],
|
|
3407
|
+
telltales: TELLTALES10
|
|
3408
|
+
};
|
|
3409
|
+
|
|
3410
|
+
// src/discover/ecosystems/swift.ts
|
|
3411
|
+
function parsePodfile(content) {
|
|
3412
|
+
const deps = [];
|
|
3413
|
+
const podRegex = /^\s*pod\s+["']([^"']+)["'](?:\s*,\s*["']([^"']+)["'])?/gm;
|
|
3414
|
+
let match;
|
|
3415
|
+
while ((match = podRegex.exec(content)) !== null) {
|
|
3416
|
+
deps.push({ name: match[1], version: match[2] });
|
|
3417
|
+
}
|
|
3418
|
+
return deps;
|
|
3419
|
+
}
|
|
3420
|
+
function parsePackageSwift(content) {
|
|
3421
|
+
const deps = [];
|
|
3422
|
+
const pkgRegex = /\.package\s*\(\s*url:\s*["'](?:https?:\/\/[^/]+\/[^/]+\/|git@[^:]+:[^/]+\/)([^"'/.]+)/g;
|
|
3423
|
+
let match;
|
|
3424
|
+
while ((match = pkgRegex.exec(content)) !== null) {
|
|
3425
|
+
deps.push({ name: match[1] });
|
|
3426
|
+
}
|
|
3427
|
+
return deps;
|
|
3428
|
+
}
|
|
3429
|
+
var TELLTALES11 = [
|
|
3430
|
+
// --- Frameworks ---
|
|
3431
|
+
["vapor", "vapor"],
|
|
3432
|
+
// --- Libraries ---
|
|
3433
|
+
["Alamofire", "alamofire"],
|
|
3434
|
+
["SnapKit", "snapkit"],
|
|
3435
|
+
["Kingfisher", "kingfisher"],
|
|
3436
|
+
["Moya", "moya"],
|
|
3437
|
+
["RxSwift", "rxswift"],
|
|
3438
|
+
["RealmSwift", "realm"],
|
|
3439
|
+
// Quick and Nimble ship from separate repos and are declared independently —
|
|
3440
|
+
// Nimble is usable on its own with XCTest — so they are two entities, gating
|
|
3441
|
+
// SwiftLint's `quick_discouraged_*` and `prefer_nimble` rules respectively.
|
|
3442
|
+
// Ids are matched exactly and case-sensitively: `parsePackageSwift` yields the
|
|
3443
|
+
// GitHub repo name and `parsePodfile` the pod name, and both are `Quick`/`Nimble`.
|
|
3444
|
+
["Quick", "quick"],
|
|
3445
|
+
["Nimble", "nimble"]
|
|
3446
|
+
];
|
|
3447
|
+
var swift = {
|
|
3448
|
+
id: "swift",
|
|
3449
|
+
manifests: [
|
|
3450
|
+
{ basename: "Package.swift", parser: parsePackageSwift },
|
|
3451
|
+
{ basename: "Podfile", parser: parsePodfile }
|
|
3452
|
+
],
|
|
3453
|
+
telltales: TELLTALES11
|
|
3454
|
+
};
|
|
3455
|
+
|
|
3456
|
+
// src/discover/ecosystems/shards.ts
|
|
3457
|
+
function parseShardYml(content) {
|
|
3458
|
+
const deps = [];
|
|
3459
|
+
let inSection = false;
|
|
3460
|
+
let depIndent = -1;
|
|
3461
|
+
let current;
|
|
3462
|
+
const flush = () => {
|
|
3463
|
+
if (current) deps.push(current);
|
|
3464
|
+
current = void 0;
|
|
3465
|
+
};
|
|
3466
|
+
for (const rawLine of content.split("\n")) {
|
|
3467
|
+
const trimmed = rawLine.trim();
|
|
3468
|
+
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
3469
|
+
const indent = rawLine.length - rawLine.trimStart().length;
|
|
3470
|
+
if (indent === 0) {
|
|
3471
|
+
flush();
|
|
3472
|
+
inSection = /^(dependencies|development_dependencies):/.test(trimmed);
|
|
3473
|
+
depIndent = -1;
|
|
3474
|
+
continue;
|
|
3475
|
+
}
|
|
3476
|
+
if (!inSection) continue;
|
|
3477
|
+
if (depIndent === -1) depIndent = indent;
|
|
3478
|
+
if (indent === depIndent) {
|
|
3479
|
+
flush();
|
|
3480
|
+
const m = trimmed.match(/^([a-zA-Z0-9_-]+)\s*:/);
|
|
3481
|
+
current = m ? { name: m[1] } : void 0;
|
|
3482
|
+
continue;
|
|
3483
|
+
}
|
|
3484
|
+
if (indent > depIndent && current) {
|
|
3485
|
+
const v = trimmed.match(/^version\s*:\s*["']?(?:~>|[~^])?\s*([0-9][^"'\s]*)/);
|
|
3486
|
+
if (v) current.version = v[1];
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
flush();
|
|
3490
|
+
return deps;
|
|
3491
|
+
}
|
|
3492
|
+
var TELLTALES12 = [
|
|
3493
|
+
// --- Frameworks ---
|
|
3494
|
+
["amber", "amber"],
|
|
3495
|
+
["lucky", "lucky"],
|
|
3496
|
+
["kemal", "kemal"]
|
|
3497
|
+
];
|
|
3498
|
+
var shards = {
|
|
3499
|
+
id: "shards",
|
|
3500
|
+
manifests: [{ basename: "shard.yml", parser: parseShardYml }],
|
|
3501
|
+
telltales: TELLTALES12
|
|
3502
|
+
};
|
|
3503
|
+
|
|
3504
|
+
// src/discover/ecosystems/index.ts
|
|
3505
|
+
var ECOSYSTEMS = [
|
|
3506
|
+
npm,
|
|
3507
|
+
pypi,
|
|
3508
|
+
gem,
|
|
3509
|
+
maven,
|
|
3510
|
+
golang,
|
|
3511
|
+
cargo,
|
|
3512
|
+
composer,
|
|
3513
|
+
nuget,
|
|
3514
|
+
pub,
|
|
3515
|
+
hex,
|
|
3516
|
+
swift,
|
|
3517
|
+
shards
|
|
3518
|
+
];
|
|
3519
|
+
var BY_BASENAME = /* @__PURE__ */ new Map();
|
|
3520
|
+
var BY_SUFFIX = [];
|
|
3521
|
+
var BY_PATH = [];
|
|
3522
|
+
for (const ecosystem of ECOSYSTEMS) {
|
|
3523
|
+
for (const rule of ecosystem.manifests) {
|
|
3524
|
+
const resolved = { ecosystem: ecosystem.id, parser: rule.parser };
|
|
3525
|
+
if ("basename" in rule) BY_BASENAME.set(rule.basename, resolved);
|
|
3526
|
+
else if ("suffix" in rule) BY_SUFFIX.push({ suffix: rule.suffix, resolved });
|
|
3527
|
+
else BY_PATH.push({ matches: rule.matches, resolved });
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
function getEcosystem(id) {
|
|
3531
|
+
const found = ECOSYSTEMS.find((e) => e.id === id);
|
|
3532
|
+
if (!found) throw new Error(`Unknown dependency ecosystem: ${id}`);
|
|
3533
|
+
return found;
|
|
3534
|
+
}
|
|
3535
|
+
function resolveManifest(filePath) {
|
|
3536
|
+
const basename = filePath.split("/").pop() ?? filePath;
|
|
3537
|
+
const byName = BY_BASENAME.get(basename);
|
|
3538
|
+
if (byName) return byName;
|
|
3539
|
+
const lowerBasename = basename.toLowerCase();
|
|
3540
|
+
for (const { suffix, resolved } of BY_SUFFIX) {
|
|
3541
|
+
if (lowerBasename.endsWith(suffix)) return resolved;
|
|
3542
|
+
}
|
|
3543
|
+
for (const { matches, resolved } of BY_PATH) {
|
|
3544
|
+
if (matches(filePath, basename)) return resolved;
|
|
2833
3545
|
}
|
|
2834
3546
|
return void 0;
|
|
2835
3547
|
}
|
|
3548
|
+
|
|
3549
|
+
// src/discover/signals.ts
|
|
3550
|
+
var import_tooling5 = require("@codacy/tooling");
|
|
2836
3551
|
function pathBasename(filePath) {
|
|
2837
3552
|
return filePath.split("/").pop() ?? filePath;
|
|
2838
3553
|
}
|
|
@@ -2840,57 +3555,57 @@ var FILE_SIGNAL_RULES = [
|
|
|
2840
3555
|
{
|
|
2841
3556
|
name: ".flowconfig",
|
|
2842
3557
|
matches: (f) => pathBasename(f) === ".flowconfig",
|
|
2843
|
-
|
|
3558
|
+
entity: "flow"
|
|
2844
3559
|
},
|
|
2845
3560
|
{
|
|
2846
3561
|
name: "cdk.json",
|
|
2847
3562
|
matches: (f) => pathBasename(f) === "cdk.json",
|
|
2848
|
-
|
|
3563
|
+
entity: "aws-cdk"
|
|
2849
3564
|
},
|
|
2850
3565
|
{
|
|
2851
3566
|
name: ".meteor/release",
|
|
2852
3567
|
matches: (f) => f === ".meteor/release" || f.endsWith("/.meteor/release"),
|
|
2853
|
-
|
|
3568
|
+
entity: "meteor"
|
|
2854
3569
|
},
|
|
2855
3570
|
{
|
|
2856
3571
|
name: "sfdx-project.json",
|
|
2857
3572
|
matches: (f) => pathBasename(f) === "sfdx-project.json",
|
|
2858
|
-
|
|
3573
|
+
entity: "salesforce"
|
|
2859
3574
|
},
|
|
2860
3575
|
{
|
|
2861
3576
|
name: "lwc.config.json",
|
|
2862
3577
|
matches: (f) => pathBasename(f) === "lwc.config.json",
|
|
2863
|
-
|
|
3578
|
+
entity: "lightning-web-components"
|
|
2864
3579
|
},
|
|
2865
3580
|
{
|
|
2866
3581
|
name: "suitecloud.config.js",
|
|
2867
3582
|
matches: (f) => pathBasename(f) === "suitecloud.config.js",
|
|
2868
|
-
|
|
3583
|
+
entity: "suitescript"
|
|
2869
3584
|
},
|
|
2870
3585
|
{
|
|
2871
3586
|
name: ".github/workflows",
|
|
2872
3587
|
matches: (f) => f.startsWith(".github/workflows/"),
|
|
2873
|
-
|
|
3588
|
+
entity: "github-actions"
|
|
2874
3589
|
},
|
|
2875
3590
|
{
|
|
2876
3591
|
name: ".circleci",
|
|
2877
3592
|
matches: (f) => f.startsWith(".circleci/"),
|
|
2878
|
-
|
|
3593
|
+
entity: "circleci"
|
|
2879
3594
|
},
|
|
2880
3595
|
{
|
|
2881
3596
|
name: ".gitlab-ci.yml",
|
|
2882
3597
|
matches: (f) => f === ".gitlab-ci.yml" || f.startsWith(".gitlab/"),
|
|
2883
|
-
|
|
3598
|
+
entity: "gitlab-ci"
|
|
2884
3599
|
},
|
|
2885
3600
|
{
|
|
2886
3601
|
name: "bitbucket-pipelines.yml",
|
|
2887
3602
|
matches: (f) => f === "bitbucket-pipelines.yml",
|
|
2888
|
-
|
|
3603
|
+
entity: "bitbucket-pipelines"
|
|
2889
3604
|
},
|
|
2890
3605
|
{
|
|
2891
3606
|
name: "azure-pipelines.yml",
|
|
2892
3607
|
matches: (f) => f === "azure-pipelines.yml" || f === "azure-pipelines.yaml",
|
|
2893
|
-
|
|
3608
|
+
entity: "azure-pipelines"
|
|
2894
3609
|
},
|
|
2895
3610
|
{
|
|
2896
3611
|
// Jenkins pipelines are Groovy scripts with no manifest at all — the
|
|
@@ -2901,14 +3616,14 @@ var FILE_SIGNAL_RULES = [
|
|
|
2901
3616
|
const base = pathBasename(f);
|
|
2902
3617
|
return base === "Jenkinsfile" || base.startsWith("Jenkinsfile.");
|
|
2903
3618
|
},
|
|
2904
|
-
|
|
3619
|
+
entity: "jenkins"
|
|
2905
3620
|
},
|
|
2906
3621
|
{
|
|
2907
3622
|
// A `sorbet/` directory (typically `sorbet/config` + RBI files) marks a
|
|
2908
3623
|
// Sorbet-typed Ruby project even when the gem is pulled in transitively.
|
|
2909
3624
|
name: "sorbet/config",
|
|
2910
3625
|
matches: (f) => f === "sorbet/config" || f.endsWith("/sorbet/config"),
|
|
2911
|
-
|
|
3626
|
+
entity: "sorbet"
|
|
2912
3627
|
},
|
|
2913
3628
|
{
|
|
2914
3629
|
name: "Rakefile",
|
|
@@ -2916,7 +3631,105 @@ var FILE_SIGNAL_RULES = [
|
|
|
2916
3631
|
const base = pathBasename(f);
|
|
2917
3632
|
return base === "Rakefile" || base === "rakefile" || base.endsWith(".rake");
|
|
2918
3633
|
},
|
|
2919
|
-
|
|
3634
|
+
entity: "rake"
|
|
3635
|
+
},
|
|
3636
|
+
{
|
|
3637
|
+
// XCTest ships with Xcode, so no manifest ever declares it and a file signal
|
|
3638
|
+
// is the only producer available. `FooTests.swift` / `FooTest.swift` is the
|
|
3639
|
+
// naming Xcode's templates and SwiftPM's `Tests/` layout both generate, and it
|
|
3640
|
+
// is what SwiftLint's own test rules key on. Matching the file name rather than
|
|
3641
|
+
// a `Tests/` directory also covers CocoaPods/Xcode projects, which have no
|
|
3642
|
+
// fixed directory convention.
|
|
3643
|
+
name: "*Tests.swift",
|
|
3644
|
+
matches: (f) => /Tests?\.swift$/.test(pathBasename(f)),
|
|
3645
|
+
entity: "xctest"
|
|
3646
|
+
},
|
|
3647
|
+
// ── Infrastructure-as-Code ─────────────────────────────────────────────────
|
|
3648
|
+
//
|
|
3649
|
+
// These gate Checkov, whose ~1,300 cloud-security checks are otherwise
|
|
3650
|
+
// universal: they are all default-on and the only other filter is the language
|
|
3651
|
+
// one, which passes on any repo containing a `.json` or `.yaml` file. Terraform
|
|
3652
|
+
// needs no rule here — `.tf` is the `Terraform` language, so `lang:Terraform`
|
|
3653
|
+
// already means "this repo has Terraform". What the language axis cannot
|
|
3654
|
+
// express is which IaC *framework* a YAML/JSON file belongs to, since
|
|
3655
|
+
// Kubernetes manifests, Helm charts, Ansible playbooks, CloudFormation
|
|
3656
|
+
// templates and ARM templates are all YAML or JSON.
|
|
3657
|
+
//
|
|
3658
|
+
// Bias these rules **permissive**, the opposite of the rules above. A Checkov
|
|
3659
|
+
// check declares the resource types it applies to, so a check enabled in a repo
|
|
3660
|
+
// that has none of them is inert — it produces no findings and costs only a line
|
|
3661
|
+
// of config. A false negative is what's expensive: it silently drops hundreds of
|
|
3662
|
+
// checks from a repo that needed them. Where a signal is ambiguous, prefer
|
|
3663
|
+
// matching.
|
|
3664
|
+
{
|
|
3665
|
+
// A Helm chart's templates render to Kubernetes manifests, so a chart is
|
|
3666
|
+
// evidence of Kubernetes as much as of Helm. `Chart.yaml` is the chart
|
|
3667
|
+
// manifest and carries no other meaning at a chart root.
|
|
3668
|
+
name: "Chart.yaml",
|
|
3669
|
+
matches: (f) => {
|
|
3670
|
+
const base = pathBasename(f);
|
|
3671
|
+
return base === "Chart.yaml" || base === "Chart.yml";
|
|
3672
|
+
},
|
|
3673
|
+
entity: "helm"
|
|
3674
|
+
},
|
|
3675
|
+
{
|
|
3676
|
+
// Kustomize is the one Kubernetes layout with a distinctive filename. Bare
|
|
3677
|
+
// manifests declare themselves only in their content (`apiVersion:`/`kind:`),
|
|
3678
|
+
// which a path-only rule cannot see. `kustomization.yaml` is exact; the
|
|
3679
|
+
// directory rule covers the far more common layout — loose manifests under
|
|
3680
|
+
// `k8s/`, `kubernetes/` or `manifests/`, applied with `kubectl apply -f`.
|
|
3681
|
+
//
|
|
3682
|
+
// Gating on `kustomization.yaml` alone dropped 14 default-on Opengrep rules
|
|
3683
|
+
// (privileged-container, secrets-in-config-file, skip-tls-verify-*, …) from
|
|
3684
|
+
// exactly that layout. Per the bias above, a directory name matched wrongly
|
|
3685
|
+
// only enables inert checks, while missing it loses real coverage.
|
|
3686
|
+
name: "k8s manifests",
|
|
3687
|
+
matches: (f) => {
|
|
3688
|
+
const base = pathBasename(f);
|
|
3689
|
+
if (base === "kustomization.yaml" || base === "kustomization.yml") return true;
|
|
3690
|
+
return /(^|\/)(k8s|kubernetes|manifests)\/.+\.ya?ml$/.test(f);
|
|
3691
|
+
},
|
|
3692
|
+
entity: "kubernetes"
|
|
3693
|
+
},
|
|
3694
|
+
{
|
|
3695
|
+
// `ansible.cfg` is unambiguous; the `roles/<name>/tasks/main.yml` layout is
|
|
3696
|
+
// Ansible's own role convention and is the only marker in a playbook repo
|
|
3697
|
+
// that ships no config file.
|
|
3698
|
+
name: "ansible.cfg",
|
|
3699
|
+
matches: (f) => pathBasename(f) === "ansible.cfg" || /(^|\/)roles\/[^/]+\/tasks\/main\.ya?ml$/.test(f),
|
|
3700
|
+
entity: "ansible"
|
|
3701
|
+
},
|
|
3702
|
+
{
|
|
3703
|
+
name: "serverless.yml",
|
|
3704
|
+
matches: (f) => /(^|\/)serverless\.(ya?ml|json|ts|js)$/.test(f),
|
|
3705
|
+
entity: "serverless-framework"
|
|
3706
|
+
},
|
|
3707
|
+
{
|
|
3708
|
+
// Bicep compiles to ARM, so `.bicep` is Azure IaC. Plain ARM templates are
|
|
3709
|
+
// JSON identified by their `$schema`, which needs a content probe.
|
|
3710
|
+
name: "*.bicep",
|
|
3711
|
+
matches: (f) => {
|
|
3712
|
+
const base = pathBasename(f);
|
|
3713
|
+
return base.endsWith(".bicep") || base.endsWith(".bicepparam");
|
|
3714
|
+
},
|
|
3715
|
+
entity: "bicep"
|
|
3716
|
+
},
|
|
3717
|
+
{
|
|
3718
|
+
// A CloudFormation template is identified by `AWSTemplateFormatVersion` in its
|
|
3719
|
+
// body, not by its name, so this covers only the conventional shells: the SAM
|
|
3720
|
+
// CLI's config file, SAM's root `template.yaml`, and a `cloudformation/`
|
|
3721
|
+
// directory. Deliberately loose per the note above — over-matching enables AWS
|
|
3722
|
+
// checks that then find nothing, under-matching drops 456 of them.
|
|
3723
|
+
name: "cloudformation template",
|
|
3724
|
+
matches: (f) => {
|
|
3725
|
+
const base = pathBasename(f);
|
|
3726
|
+
if (base === "samconfig.toml" || base === "samconfig.yaml" || base === "samconfig.yml") {
|
|
3727
|
+
return true;
|
|
3728
|
+
}
|
|
3729
|
+
if (f === "template.yaml" || f === "template.yml") return true;
|
|
3730
|
+
return /(^|\/)cloudformation\//.test(f);
|
|
3731
|
+
},
|
|
3732
|
+
entity: "cloudformation"
|
|
2920
3733
|
},
|
|
2921
3734
|
{
|
|
2922
3735
|
// Play's routes file — `conf/routes`, plus the `conf/*.routes` sub-files a
|
|
@@ -2926,594 +3739,175 @@ var FILE_SIGNAL_RULES = [
|
|
|
2926
3739
|
// artifact names. Note `conf/application.conf` is NOT a signal: plain
|
|
2927
3740
|
// Typesafe Config uses the same filename without any Play involvement.
|
|
2928
3741
|
name: "conf/routes",
|
|
2929
|
-
matches: (f) => /(^|\/)conf\/([^/]+\.)?routes$/.test(f),
|
|
2930
|
-
|
|
2931
|
-
}
|
|
2932
|
-
];
|
|
2933
|
-
function detectFileSignals(files) {
|
|
2934
|
-
const result = [];
|
|
2935
|
-
for (const rule of FILE_SIGNAL_RULES) {
|
|
2936
|
-
const hit = files.find((f) => rule.matches(f));
|
|
2937
|
-
if (!hit) continue;
|
|
2938
|
-
result.push({
|
|
2939
|
-
name: rule.name,
|
|
2940
|
-
displayName: rule.classification.displayName,
|
|
2941
|
-
kind: rule.classification.kind,
|
|
2942
|
-
source: "file",
|
|
2943
|
-
manifestFile: hit
|
|
2944
|
-
});
|
|
2945
|
-
}
|
|
2946
|
-
return result;
|
|
2947
|
-
}
|
|
2948
|
-
var PACKAGE_JSON_SIGNAL_DISPLAY_NAMES = ["ESLint Plugin"];
|
|
2949
|
-
function isEslintPluginName(name) {
|
|
2950
|
-
return /^eslint-plugin-/.test(name) || /^@[^/]+\/eslint-plugin(-.*)?$/.test(name);
|
|
2951
|
-
}
|
|
2952
|
-
function detectPackageJsonSignals(content) {
|
|
2953
|
-
let pkg;
|
|
2954
|
-
try {
|
|
2955
|
-
pkg = JSON.parse(content);
|
|
2956
|
-
} catch {
|
|
2957
|
-
return [];
|
|
2958
|
-
}
|
|
2959
|
-
const name = pkg && typeof pkg.name === "string" ? pkg.name : "";
|
|
2960
|
-
if (name && isEslintPluginName(name)) {
|
|
2961
|
-
return [
|
|
2962
|
-
{
|
|
2963
|
-
name,
|
|
2964
|
-
displayName: "ESLint Plugin",
|
|
2965
|
-
kind: "tool",
|
|
2966
|
-
source: "manifest",
|
|
2967
|
-
manifestFile: "package.json"
|
|
2968
|
-
}
|
|
2969
|
-
];
|
|
2970
|
-
}
|
|
2971
|
-
return [];
|
|
2972
|
-
}
|
|
2973
|
-
function getKnownDisplayNames() {
|
|
2974
|
-
const names = /* @__PURE__ */ new Set();
|
|
2975
|
-
for (const { displayName } of DEPENDENCY_CATALOG.values()) names.add(displayName);
|
|
2976
|
-
for (const scoped of MANIFEST_SCOPED_CATALOG.values())
|
|
2977
|
-
for (const { displayName } of scoped.values()) names.add(displayName);
|
|
2978
|
-
for (const rule of DEPENDENCY_PREFIX_RULES) names.add(rule.classification.displayName);
|
|
2979
|
-
for (const rule of FILE_SIGNAL_RULES) names.add(rule.classification.displayName);
|
|
2980
|
-
for (const name of PACKAGE_JSON_SIGNAL_DISPLAY_NAMES) names.add(name);
|
|
2981
|
-
return names;
|
|
2982
|
-
}
|
|
2983
|
-
var NOTABLE_ANY_DEPTH = /* @__PURE__ */ new Set([
|
|
2984
|
-
"Dockerfile",
|
|
2985
|
-
"docker-compose.yml",
|
|
2986
|
-
"docker-compose.yaml",
|
|
2987
|
-
"compose.yml",
|
|
2988
|
-
"compose.yaml",
|
|
2989
|
-
"Makefile",
|
|
2990
|
-
"Justfile",
|
|
2991
|
-
"pom.xml",
|
|
2992
|
-
"build.gradle",
|
|
2993
|
-
"build.gradle.kts",
|
|
2994
|
-
"build.sbt",
|
|
2995
|
-
"go.mod",
|
|
2996
|
-
"Cargo.toml",
|
|
2997
|
-
"Gemfile",
|
|
2998
|
-
"Podfile",
|
|
2999
|
-
"Package.swift",
|
|
3000
|
-
"Jenkinsfile",
|
|
3001
|
-
"Vagrantfile",
|
|
3002
|
-
"Procfile",
|
|
3003
|
-
"pubspec.yaml",
|
|
3004
|
-
"mix.exs",
|
|
3005
|
-
"shard.yml",
|
|
3006
|
-
"packages.config",
|
|
3007
|
-
"Directory.Packages.props"
|
|
3008
|
-
]);
|
|
3009
|
-
var NOTABLE_ROOT_ONLY = /* @__PURE__ */ new Set([
|
|
3010
|
-
".dockerignore",
|
|
3011
|
-
"Taskfile.yml",
|
|
3012
|
-
"Rakefile",
|
|
3013
|
-
"Gruntfile.js",
|
|
3014
|
-
"Gulpfile.js",
|
|
3015
|
-
"settings.gradle",
|
|
3016
|
-
"settings.gradle.kts",
|
|
3017
|
-
"go.sum",
|
|
3018
|
-
"Cargo.lock",
|
|
3019
|
-
"CLAUDE.md",
|
|
3020
|
-
"AGENTS.md",
|
|
3021
|
-
"agents.md",
|
|
3022
|
-
".cursorrules",
|
|
3023
|
-
"copilot-instructions.md",
|
|
3024
|
-
".travis.yml",
|
|
3025
|
-
".gitlab-ci.yml",
|
|
3026
|
-
"azure-pipelines.yml",
|
|
3027
|
-
"bitbucket-pipelines.yml",
|
|
3028
|
-
"appveyor.yml",
|
|
3029
|
-
"package.json",
|
|
3030
|
-
"requirements.txt",
|
|
3031
|
-
"pyproject.toml",
|
|
3032
|
-
"Pipfile",
|
|
3033
|
-
"composer.json",
|
|
3034
|
-
"tsconfig.json",
|
|
3035
|
-
"jsconfig.json",
|
|
3036
|
-
"serverless.yml",
|
|
3037
|
-
"serverless.yaml",
|
|
3038
|
-
"cdk.json",
|
|
3039
|
-
"pulumi.yaml",
|
|
3040
|
-
"README.md",
|
|
3041
|
-
"CHANGELOG.md",
|
|
3042
|
-
"CONTRIBUTING.md",
|
|
3043
|
-
"LICENSE",
|
|
3044
|
-
"renovate.json",
|
|
3045
|
-
".nvmrc",
|
|
3046
|
-
".node-version",
|
|
3047
|
-
".python-version",
|
|
3048
|
-
".ruby-version",
|
|
3049
|
-
".tool-versions"
|
|
3050
|
-
]);
|
|
3051
|
-
var NOTABLE_PATH_PREFIXES = [".github/workflows/", ".circleci/", ".gitlab/"];
|
|
3052
|
-
var CONFIG_HEURISTIC_PATTERNS = [
|
|
3053
|
-
/^\..*rc$/,
|
|
3054
|
-
// .eslintrc, .prettierrc, etc.
|
|
3055
|
-
/^\..*rc\.(json|js|cjs|mjs|yml|yaml|ts)$/,
|
|
3056
|
-
// .eslintrc.json, etc.
|
|
3057
|
-
/^.*\.config\.(js|ts|mjs|cjs)$/,
|
|
3058
|
-
// vite.config.ts, jest.config.js, etc.
|
|
3059
|
-
/^\.env(\..+)?$/
|
|
3060
|
-
// .env, .env.local, .env.production
|
|
3061
|
-
];
|
|
3062
|
-
function isNotableFile(filePath) {
|
|
3063
|
-
const basename = filePath.split("/").pop() ?? filePath;
|
|
3064
|
-
const isRoot = !filePath.includes("/");
|
|
3065
|
-
if (NOTABLE_ANY_DEPTH.has(basename)) return true;
|
|
3066
|
-
if (isRoot && NOTABLE_ROOT_ONLY.has(basename)) return true;
|
|
3067
|
-
if (/\.(csproj|vbproj|fsproj|sln)$/i.test(basename)) return true;
|
|
3068
|
-
for (const prefix of NOTABLE_PATH_PREFIXES) {
|
|
3069
|
-
if (filePath.startsWith(prefix)) return true;
|
|
3070
|
-
}
|
|
3071
|
-
if (!filePath.includes("/")) {
|
|
3072
|
-
for (const pattern of CONFIG_HEURISTIC_PATTERNS) {
|
|
3073
|
-
if (pattern.test(basename)) return true;
|
|
3074
|
-
}
|
|
3075
|
-
}
|
|
3076
|
-
return false;
|
|
3077
|
-
}
|
|
3078
|
-
function parsePackageJson(content) {
|
|
3079
|
-
try {
|
|
3080
|
-
const pkg = JSON.parse(content);
|
|
3081
|
-
const deps = [];
|
|
3082
|
-
for (const section of ["dependencies", "devDependencies", "peerDependencies"]) {
|
|
3083
|
-
const entries2 = pkg[section];
|
|
3084
|
-
if (entries2 && typeof entries2 === "object") {
|
|
3085
|
-
for (const [name, version] of Object.entries(entries2)) {
|
|
3086
|
-
deps.push({ name, version: typeof version === "string" ? version : void 0 });
|
|
3087
|
-
}
|
|
3088
|
-
}
|
|
3089
|
-
}
|
|
3090
|
-
return deps;
|
|
3091
|
-
} catch {
|
|
3092
|
-
return [];
|
|
3093
|
-
}
|
|
3094
|
-
}
|
|
3095
|
-
function parseRequirementLine(rawLine) {
|
|
3096
|
-
const line = rawLine.trim();
|
|
3097
|
-
if (!line || line.startsWith("#") || line.startsWith("-")) return void 0;
|
|
3098
|
-
const noComment = line.split("#")[0].trim();
|
|
3099
|
-
if (!noComment) return void 0;
|
|
3100
|
-
const match = noComment.match(/^([a-zA-Z0-9_.-]+(?:\[[^\]]*\])?)\s*([<>=!~@;].*)?$/);
|
|
3101
|
-
if (!match) return void 0;
|
|
3102
|
-
const rawName = match[1].replace(/\[.*\]$/, "");
|
|
3103
|
-
return { name: rawName.toLowerCase().replace(/_/g, "-"), version: match[2]?.trim() };
|
|
3104
|
-
}
|
|
3105
|
-
function parseRequirementsTxt(content) {
|
|
3106
|
-
const deps = [];
|
|
3107
|
-
for (const rawLine of content.split("\n")) {
|
|
3108
|
-
const dep = parseRequirementLine(rawLine);
|
|
3109
|
-
if (dep) deps.push(dep);
|
|
3110
|
-
}
|
|
3111
|
-
return deps;
|
|
3112
|
-
}
|
|
3113
|
-
function sliceBalanced(content, fromIndex, open, close) {
|
|
3114
|
-
const start = content.indexOf(open, fromIndex);
|
|
3115
|
-
if (start === -1) return void 0;
|
|
3116
|
-
let depth = 0;
|
|
3117
|
-
let quote;
|
|
3118
|
-
for (let i = start; i < content.length; i++) {
|
|
3119
|
-
const ch = content[i];
|
|
3120
|
-
if (quote) {
|
|
3121
|
-
if (ch === "\\") i++;
|
|
3122
|
-
else if (ch === quote) quote = void 0;
|
|
3123
|
-
continue;
|
|
3124
|
-
}
|
|
3125
|
-
if (ch === '"' || ch === "'") {
|
|
3126
|
-
quote = ch;
|
|
3127
|
-
continue;
|
|
3128
|
-
}
|
|
3129
|
-
if (ch === open) depth++;
|
|
3130
|
-
else if (ch === close && --depth === 0) {
|
|
3131
|
-
return { body: content.slice(start + 1, i), endIndex: i };
|
|
3132
|
-
}
|
|
3133
|
-
}
|
|
3134
|
-
return void 0;
|
|
3135
|
-
}
|
|
3136
|
-
function parseSetupPy(content) {
|
|
3137
|
-
const deps = [];
|
|
3138
|
-
const collectQuoted = (block) => {
|
|
3139
|
-
for (const quoted of block.matchAll(/["']([^"']+)["']/g)) {
|
|
3140
|
-
const dep = parseRequirementLine(quoted[1]);
|
|
3141
|
-
if (dep) deps.push(dep);
|
|
3142
|
-
}
|
|
3143
|
-
};
|
|
3144
|
-
const valueStartOf = (key) => {
|
|
3145
|
-
const match = new RegExp(`\\b${key}\\s*=\\s*`).exec(content);
|
|
3146
|
-
return match ? match.index + match[0].length : -1;
|
|
3147
|
-
};
|
|
3148
|
-
for (const key of ["install_requires", "tests_require", "setup_requires"]) {
|
|
3149
|
-
const start = valueStartOf(key);
|
|
3150
|
-
if (start === -1 || content[start] !== "[") continue;
|
|
3151
|
-
const list = sliceBalanced(content, start, "[", "]");
|
|
3152
|
-
if (list) collectQuoted(list.body);
|
|
3153
|
-
}
|
|
3154
|
-
const extrasStart = valueStartOf("extras_require");
|
|
3155
|
-
if (extrasStart !== -1 && content[extrasStart] === "{") {
|
|
3156
|
-
const dict = sliceBalanced(content, extrasStart, "{", "}");
|
|
3157
|
-
if (dict) {
|
|
3158
|
-
let cursor = 0;
|
|
3159
|
-
while (cursor < dict.body.length) {
|
|
3160
|
-
const list = sliceBalanced(dict.body, cursor, "[", "]");
|
|
3161
|
-
if (!list) break;
|
|
3162
|
-
collectQuoted(list.body);
|
|
3163
|
-
cursor = list.endIndex + 1;
|
|
3164
|
-
}
|
|
3165
|
-
}
|
|
3166
|
-
}
|
|
3167
|
-
return deps;
|
|
3168
|
-
}
|
|
3169
|
-
function parsePyprojectToml(content) {
|
|
3170
|
-
const deps = [];
|
|
3171
|
-
const projectDepsMatch = content.match(/\[project\][\s\S]*?dependencies\s*=\s*\[([\s\S]*?)\]/);
|
|
3172
|
-
if (projectDepsMatch) {
|
|
3173
|
-
const items = projectDepsMatch[1].match(/"([^"]+)"|'([^']+)'/g) ?? [];
|
|
3174
|
-
for (const item of items) {
|
|
3175
|
-
const raw = item.replace(/["']/g, "");
|
|
3176
|
-
const name = raw.split(/[<>=!~;\s]/)[0].replace(/\[.*\]$/, "");
|
|
3177
|
-
if (name) deps.push({ name: name.toLowerCase().replace(/_/g, "-") });
|
|
3178
|
-
}
|
|
3179
|
-
}
|
|
3180
|
-
const poetryMatch = content.match(/\[tool\.poetry\.dependencies\]([\s\S]*?)(?:\n\[|\n$)/);
|
|
3181
|
-
if (poetryMatch) {
|
|
3182
|
-
const lines = poetryMatch[1].split("\n");
|
|
3183
|
-
for (const line of lines) {
|
|
3184
|
-
const m = line.match(/^([a-zA-Z0-9_.-]+)\s*=/);
|
|
3185
|
-
if (m && m[1] !== "python") {
|
|
3186
|
-
deps.push({ name: m[1].toLowerCase().replace(/_/g, "-") });
|
|
3187
|
-
}
|
|
3188
|
-
}
|
|
3189
|
-
}
|
|
3190
|
-
const poetryGroupRegex = /\[tool\.poetry\.group\.\w+\.dependencies\]([\s\S]*?)(?:\n\[|\n$)/g;
|
|
3191
|
-
let groupMatch;
|
|
3192
|
-
while ((groupMatch = poetryGroupRegex.exec(content)) !== null) {
|
|
3193
|
-
const lines = groupMatch[1].split("\n");
|
|
3194
|
-
for (const line of lines) {
|
|
3195
|
-
const m = line.match(/^([a-zA-Z0-9_.-]+)\s*=/);
|
|
3196
|
-
if (m) {
|
|
3197
|
-
deps.push({ name: m[1].toLowerCase().replace(/_/g, "-") });
|
|
3198
|
-
}
|
|
3199
|
-
}
|
|
3200
|
-
}
|
|
3201
|
-
return deps;
|
|
3202
|
-
}
|
|
3203
|
-
function parsePipfile(content) {
|
|
3204
|
-
const deps = [];
|
|
3205
|
-
let inDepsSection = false;
|
|
3206
|
-
for (const rawLine of content.split("\n")) {
|
|
3207
|
-
const line = rawLine.trim();
|
|
3208
|
-
if (/^\[(packages|dev-packages)\]/.test(line)) {
|
|
3209
|
-
inDepsSection = true;
|
|
3210
|
-
continue;
|
|
3211
|
-
}
|
|
3212
|
-
if (line.startsWith("[")) {
|
|
3213
|
-
inDepsSection = false;
|
|
3214
|
-
continue;
|
|
3215
|
-
}
|
|
3216
|
-
if (inDepsSection) {
|
|
3217
|
-
const m = line.match(/^([a-zA-Z0-9_.-]+)\s*=/);
|
|
3218
|
-
if (m) {
|
|
3219
|
-
deps.push({ name: m[1].toLowerCase().replace(/_/g, "-") });
|
|
3220
|
-
}
|
|
3221
|
-
}
|
|
3222
|
-
}
|
|
3223
|
-
return deps;
|
|
3224
|
-
}
|
|
3225
|
-
function parseGemfile(content) {
|
|
3226
|
-
const deps = [];
|
|
3227
|
-
const gemRegex = /^\s*gem\s+["']([^"']+)["'](?:\s*,\s*["']([^"']+)["'])?/gm;
|
|
3228
|
-
let match;
|
|
3229
|
-
while ((match = gemRegex.exec(content)) !== null) {
|
|
3230
|
-
deps.push({ name: match[1], version: match[2] });
|
|
3231
|
-
}
|
|
3232
|
-
return deps;
|
|
3233
|
-
}
|
|
3234
|
-
function parseMixExs(content) {
|
|
3235
|
-
const deps = [];
|
|
3236
|
-
const depRegex = /\{:([a-zA-Z_][a-zA-Z0-9_]*)\s*,\s*"([^"]*)"/g;
|
|
3237
|
-
let match;
|
|
3238
|
-
while ((match = depRegex.exec(content)) !== null) {
|
|
3239
|
-
deps.push({ name: match[1], version: match[2] || void 0 });
|
|
3240
|
-
}
|
|
3241
|
-
return deps;
|
|
3242
|
-
}
|
|
3243
|
-
function parseGoMod(content) {
|
|
3244
|
-
const deps = [];
|
|
3245
|
-
const singleRegex = /^require\s+(\S+)\s+(\S+)/gm;
|
|
3246
|
-
let match;
|
|
3247
|
-
while ((match = singleRegex.exec(content)) !== null) {
|
|
3248
|
-
if (!match[1].startsWith("(")) {
|
|
3249
|
-
deps.push({ name: match[1], version: match[2] });
|
|
3250
|
-
}
|
|
3251
|
-
}
|
|
3252
|
-
const blockRegex = /require\s*\(([\s\S]*?)\)/g;
|
|
3253
|
-
while ((match = blockRegex.exec(content)) !== null) {
|
|
3254
|
-
const lines = match[1].split("\n");
|
|
3255
|
-
for (const line of lines) {
|
|
3256
|
-
const m = line.trim().match(/^(\S+)\s+(\S+)/);
|
|
3257
|
-
if (m && !m[1].startsWith("//")) {
|
|
3258
|
-
deps.push({ name: m[1], version: m[2] });
|
|
3259
|
-
}
|
|
3260
|
-
}
|
|
3261
|
-
}
|
|
3262
|
-
return deps;
|
|
3263
|
-
}
|
|
3264
|
-
function parsePomXml(content) {
|
|
3265
|
-
const deps = [];
|
|
3266
|
-
const depRegex = /<dependency>[\s\S]*?<artifactId>([^<]+)<\/artifactId>[\s\S]*?<\/dependency>/g;
|
|
3267
|
-
let match;
|
|
3268
|
-
while ((match = depRegex.exec(content)) !== null) {
|
|
3269
|
-
deps.push({ name: match[1].trim() });
|
|
3270
|
-
}
|
|
3271
|
-
return deps;
|
|
3272
|
-
}
|
|
3273
|
-
function parseBuildGradle(content) {
|
|
3274
|
-
const deps = [];
|
|
3275
|
-
const depRegex = /(?:implementation|testImplementation|api|compileOnly|runtimeOnly|kapt|annotationProcessor)\s*(?:\(\s*)?["']([^"':]+):([^"':]+)(?::([^"']+))?["']/g;
|
|
3276
|
-
let match;
|
|
3277
|
-
while ((match = depRegex.exec(content)) !== null) {
|
|
3278
|
-
deps.push({ name: match[2].trim(), version: match[3]?.trim() });
|
|
3279
|
-
}
|
|
3280
|
-
return deps;
|
|
3281
|
-
}
|
|
3282
|
-
function parseVersionCatalogToml(content) {
|
|
3283
|
-
const deps = [];
|
|
3284
|
-
let inLibraries = false;
|
|
3285
|
-
for (const rawLine of content.split("\n")) {
|
|
3286
|
-
const line = rawLine.trim();
|
|
3287
|
-
if (line === "" || line.startsWith("#")) continue;
|
|
3288
|
-
if (line.startsWith("[")) {
|
|
3289
|
-
inLibraries = /^\[\s*libraries\s*\]/.test(line);
|
|
3290
|
-
continue;
|
|
3291
|
-
}
|
|
3292
|
-
if (!inLibraries) continue;
|
|
3293
|
-
const moduleMatch = line.match(/\bmodule\s*=\s*["'][^"':]+:([^"']+)["']/);
|
|
3294
|
-
const nameMatch = line.match(/\bname\s*=\s*["']([^"']+)["']/);
|
|
3295
|
-
const versionMatch = line.match(/\bversion\s*=\s*["']([^"']+)["']/);
|
|
3296
|
-
if (moduleMatch || nameMatch) {
|
|
3297
|
-
deps.push({
|
|
3298
|
-
name: (moduleMatch?.[1] ?? nameMatch?.[1] ?? "").trim(),
|
|
3299
|
-
version: versionMatch?.[1]?.trim()
|
|
3300
|
-
});
|
|
3301
|
-
continue;
|
|
3302
|
-
}
|
|
3303
|
-
const shorthand = line.match(/^[A-Za-z0-9_.-]+\s*=\s*["'][^"':]+:([^"':]+)(?::([^"']+))?["']/);
|
|
3304
|
-
if (shorthand) {
|
|
3305
|
-
deps.push({ name: shorthand[1].trim(), version: shorthand[2]?.trim() });
|
|
3306
|
-
}
|
|
3307
|
-
}
|
|
3308
|
-
return deps;
|
|
3309
|
-
}
|
|
3310
|
-
function parseBuildSbt(content) {
|
|
3311
|
-
const deps = [];
|
|
3312
|
-
const depRegex = /"([^"]+)"\s*%%?\s*"([^"]+)"\s*%(?!%)\s*(?:"([^"]+)")?/g;
|
|
3313
|
-
let match;
|
|
3314
|
-
while ((match = depRegex.exec(content)) !== null) {
|
|
3315
|
-
deps.push({ name: match[2], version: match[3] });
|
|
3316
|
-
}
|
|
3317
|
-
return deps;
|
|
3318
|
-
}
|
|
3319
|
-
function parseSbtBuildDefinition(content) {
|
|
3320
|
-
const deps = parseBuildSbt(content);
|
|
3321
|
-
const pluginRegex = /addSbtPlugin\s*\(\s*"([^"]+)"\s*%%?\s*"([^"]+)"/g;
|
|
3322
|
-
let match;
|
|
3323
|
-
while ((match = pluginRegex.exec(content)) !== null) {
|
|
3324
|
-
deps.push({ name: `${match[1]}:${match[2]}` });
|
|
3325
|
-
}
|
|
3326
|
-
return deps;
|
|
3327
|
-
}
|
|
3328
|
-
function parseCargoToml(content) {
|
|
3329
|
-
const deps = [];
|
|
3330
|
-
let inDepsSection = false;
|
|
3331
|
-
for (const rawLine of content.split("\n")) {
|
|
3332
|
-
const line = rawLine.trim();
|
|
3333
|
-
if (/^\[((?:dev-|build-)?dependencies)\]/.test(line)) {
|
|
3334
|
-
inDepsSection = true;
|
|
3335
|
-
continue;
|
|
3336
|
-
}
|
|
3337
|
-
if (line.startsWith("[") && !line.includes("dependencies.")) {
|
|
3338
|
-
inDepsSection = false;
|
|
3339
|
-
continue;
|
|
3340
|
-
}
|
|
3341
|
-
if (inDepsSection) {
|
|
3342
|
-
const m = line.match(/^([a-zA-Z0-9_-]+)\s*=/);
|
|
3343
|
-
if (m) {
|
|
3344
|
-
const versionMatch = line.match(/["']([^"']+)["']/);
|
|
3345
|
-
deps.push({ name: m[1], version: versionMatch?.[1] });
|
|
3346
|
-
}
|
|
3347
|
-
}
|
|
3348
|
-
}
|
|
3349
|
-
return deps;
|
|
3350
|
-
}
|
|
3351
|
-
function parseComposerJson(content) {
|
|
3352
|
-
try {
|
|
3353
|
-
const pkg = JSON.parse(content);
|
|
3354
|
-
const deps = [];
|
|
3355
|
-
for (const section of ["require", "require-dev"]) {
|
|
3356
|
-
const entries2 = pkg[section];
|
|
3357
|
-
if (entries2 && typeof entries2 === "object") {
|
|
3358
|
-
for (const [name, version] of Object.entries(entries2)) {
|
|
3359
|
-
if (name === "php" || name.startsWith("ext-")) continue;
|
|
3360
|
-
deps.push({ name, version: typeof version === "string" ? version : void 0 });
|
|
3361
|
-
}
|
|
3362
|
-
}
|
|
3363
|
-
}
|
|
3364
|
-
return deps;
|
|
3365
|
-
} catch {
|
|
3366
|
-
return [];
|
|
3367
|
-
}
|
|
3368
|
-
}
|
|
3369
|
-
function parsePodfile(content) {
|
|
3370
|
-
const deps = [];
|
|
3371
|
-
const podRegex = /^\s*pod\s+["']([^"']+)["'](?:\s*,\s*["']([^"']+)["'])?/gm;
|
|
3372
|
-
let match;
|
|
3373
|
-
while ((match = podRegex.exec(content)) !== null) {
|
|
3374
|
-
deps.push({ name: match[1], version: match[2] });
|
|
3742
|
+
matches: (f) => /(^|\/)conf\/([^/]+\.)?routes$/.test(f),
|
|
3743
|
+
entity: "play-framework"
|
|
3375
3744
|
}
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
const
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3745
|
+
];
|
|
3746
|
+
function detectFileSignals(files) {
|
|
3747
|
+
const result = [];
|
|
3748
|
+
for (const rule of FILE_SIGNAL_RULES) {
|
|
3749
|
+
const hit = files.find((f) => rule.matches(f));
|
|
3750
|
+
if (!hit) continue;
|
|
3751
|
+
const entity = import_tooling5.DEPENDENCY_ENTITIES[rule.entity];
|
|
3752
|
+
result.push({
|
|
3753
|
+
name: rule.name,
|
|
3754
|
+
key: entity.key,
|
|
3755
|
+
displayName: entity.displayName,
|
|
3756
|
+
kind: entity.kind,
|
|
3757
|
+
source: "file",
|
|
3758
|
+
manifestFile: hit
|
|
3759
|
+
});
|
|
3384
3760
|
}
|
|
3385
|
-
return
|
|
3761
|
+
return result;
|
|
3386
3762
|
}
|
|
3387
|
-
function
|
|
3388
|
-
|
|
3389
|
-
let inSection = false;
|
|
3390
|
-
let childIndent = -1;
|
|
3391
|
-
for (const rawLine of content.split("\n")) {
|
|
3392
|
-
const trimmed = rawLine.trim();
|
|
3393
|
-
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
3394
|
-
const indent = rawLine.length - rawLine.trimStart().length;
|
|
3395
|
-
if (indent === 0) {
|
|
3396
|
-
inSection = /^(dependencies|dev_dependencies|dependency_overrides):/.test(trimmed);
|
|
3397
|
-
childIndent = -1;
|
|
3398
|
-
continue;
|
|
3399
|
-
}
|
|
3400
|
-
if (!inSection) continue;
|
|
3401
|
-
if (childIndent === -1) childIndent = indent;
|
|
3402
|
-
if (indent !== childIndent) continue;
|
|
3403
|
-
const m = trimmed.match(/^([a-zA-Z0-9_.]+)\s*:(?:\s*["']?\^?([0-9][^"'#\s]*))?/);
|
|
3404
|
-
if (m) deps.push({ name: m[1], version: m[2] });
|
|
3405
|
-
}
|
|
3406
|
-
return deps;
|
|
3763
|
+
function isEslintPluginName(name) {
|
|
3764
|
+
return /^eslint-plugin-/.test(name) || /^@[^/]+\/eslint-plugin(-.*)?$/.test(name);
|
|
3407
3765
|
}
|
|
3408
|
-
function
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
if (current) deps.push(current);
|
|
3415
|
-
current = void 0;
|
|
3416
|
-
};
|
|
3417
|
-
for (const rawLine of content.split("\n")) {
|
|
3418
|
-
const trimmed = rawLine.trim();
|
|
3419
|
-
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
3420
|
-
const indent = rawLine.length - rawLine.trimStart().length;
|
|
3421
|
-
if (indent === 0) {
|
|
3422
|
-
flush();
|
|
3423
|
-
inSection = /^(dependencies|development_dependencies):/.test(trimmed);
|
|
3424
|
-
depIndent = -1;
|
|
3425
|
-
continue;
|
|
3426
|
-
}
|
|
3427
|
-
if (!inSection) continue;
|
|
3428
|
-
if (depIndent === -1) depIndent = indent;
|
|
3429
|
-
if (indent === depIndent) {
|
|
3430
|
-
flush();
|
|
3431
|
-
const m = trimmed.match(/^([a-zA-Z0-9_-]+)\s*:/);
|
|
3432
|
-
current = m ? { name: m[1] } : void 0;
|
|
3433
|
-
continue;
|
|
3434
|
-
}
|
|
3435
|
-
if (indent > depIndent && current) {
|
|
3436
|
-
const v = trimmed.match(/^version\s*:\s*["']?(?:~>|[~^])?\s*([0-9][^"'\s]*)/);
|
|
3437
|
-
if (v) current.version = v[1];
|
|
3438
|
-
}
|
|
3766
|
+
function detectPackageJsonSignals(content) {
|
|
3767
|
+
let pkg;
|
|
3768
|
+
try {
|
|
3769
|
+
pkg = JSON.parse(content);
|
|
3770
|
+
} catch {
|
|
3771
|
+
return [];
|
|
3439
3772
|
}
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3773
|
+
const name = pkg && typeof pkg.name === "string" ? pkg.name : "";
|
|
3774
|
+
if (name && isEslintPluginName(name)) {
|
|
3775
|
+
const entity = import_tooling5.DEPENDENCY_ENTITIES["eslint-plugin"];
|
|
3776
|
+
return [
|
|
3777
|
+
{
|
|
3778
|
+
name,
|
|
3779
|
+
key: entity.key,
|
|
3780
|
+
displayName: entity.displayName,
|
|
3781
|
+
kind: entity.kind,
|
|
3782
|
+
source: "manifest",
|
|
3783
|
+
manifestFile: "package.json",
|
|
3784
|
+
ecosystem: "npm"
|
|
3785
|
+
}
|
|
3786
|
+
];
|
|
3454
3787
|
}
|
|
3455
|
-
return
|
|
3788
|
+
return [];
|
|
3456
3789
|
}
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
)
|
|
3790
|
+
|
|
3791
|
+
// src/discover/classify.ts
|
|
3792
|
+
var TELLTALE_INDEX = /* @__PURE__ */ new Map();
|
|
3793
|
+
function telltaleIndex(ecosystem) {
|
|
3794
|
+
let index = TELLTALE_INDEX.get(ecosystem);
|
|
3795
|
+
if (!index) {
|
|
3796
|
+
index = new Map(getEcosystem(ecosystem).telltales);
|
|
3797
|
+
TELLTALE_INDEX.set(ecosystem, index);
|
|
3798
|
+
}
|
|
3799
|
+
return index;
|
|
3800
|
+
}
|
|
3801
|
+
function classifyDependency(name, ecosystem) {
|
|
3802
|
+
const exact = telltaleIndex(ecosystem).get(name);
|
|
3803
|
+
if (exact) return import_tooling6.DEPENDENCY_ENTITIES[exact];
|
|
3804
|
+
for (const [prefix, entity] of getEcosystem(ecosystem).prefixes ?? []) {
|
|
3805
|
+
if (name.startsWith(prefix)) return import_tooling6.DEPENDENCY_ENTITIES[entity];
|
|
3806
|
+
}
|
|
3807
|
+
return void 0;
|
|
3463
3808
|
}
|
|
3464
|
-
function
|
|
3465
|
-
return
|
|
3809
|
+
function getKnownDisplayNames() {
|
|
3810
|
+
return new Set(Object.values(import_tooling6.DEPENDENCY_ENTITIES).map((entity) => entity.displayName));
|
|
3466
3811
|
}
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3812
|
+
|
|
3813
|
+
// src/discover/notable-files.ts
|
|
3814
|
+
var NOTABLE_ANY_DEPTH = /* @__PURE__ */ new Set([
|
|
3815
|
+
"Dockerfile",
|
|
3816
|
+
"docker-compose.yml",
|
|
3817
|
+
"docker-compose.yaml",
|
|
3818
|
+
"compose.yml",
|
|
3819
|
+
"compose.yaml",
|
|
3820
|
+
"Makefile",
|
|
3821
|
+
"Justfile",
|
|
3822
|
+
"pom.xml",
|
|
3823
|
+
"build.gradle",
|
|
3824
|
+
"build.gradle.kts",
|
|
3825
|
+
"build.sbt",
|
|
3826
|
+
"go.mod",
|
|
3827
|
+
"Cargo.toml",
|
|
3828
|
+
"Gemfile",
|
|
3829
|
+
"Podfile",
|
|
3830
|
+
"Package.swift",
|
|
3831
|
+
"Jenkinsfile",
|
|
3832
|
+
"Vagrantfile",
|
|
3833
|
+
"Procfile",
|
|
3834
|
+
"pubspec.yaml",
|
|
3835
|
+
"mix.exs",
|
|
3836
|
+
"shard.yml",
|
|
3837
|
+
"packages.config",
|
|
3838
|
+
"Directory.Packages.props"
|
|
3489
3839
|
]);
|
|
3490
|
-
var
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3840
|
+
var NOTABLE_ROOT_ONLY = /* @__PURE__ */ new Set([
|
|
3841
|
+
".dockerignore",
|
|
3842
|
+
"Taskfile.yml",
|
|
3843
|
+
"Rakefile",
|
|
3844
|
+
"Gruntfile.js",
|
|
3845
|
+
"Gulpfile.js",
|
|
3846
|
+
"settings.gradle",
|
|
3847
|
+
"settings.gradle.kts",
|
|
3848
|
+
"go.sum",
|
|
3849
|
+
"Cargo.lock",
|
|
3850
|
+
"CLAUDE.md",
|
|
3851
|
+
"AGENTS.md",
|
|
3852
|
+
"agents.md",
|
|
3853
|
+
".cursorrules",
|
|
3854
|
+
"copilot-instructions.md",
|
|
3855
|
+
".travis.yml",
|
|
3856
|
+
".gitlab-ci.yml",
|
|
3857
|
+
"azure-pipelines.yml",
|
|
3858
|
+
"bitbucket-pipelines.yml",
|
|
3859
|
+
"appveyor.yml",
|
|
3860
|
+
"package.json",
|
|
3861
|
+
"requirements.txt",
|
|
3862
|
+
"pyproject.toml",
|
|
3863
|
+
"Pipfile",
|
|
3864
|
+
"composer.json",
|
|
3865
|
+
"tsconfig.json",
|
|
3866
|
+
"jsconfig.json",
|
|
3867
|
+
"serverless.yml",
|
|
3868
|
+
"serverless.yaml",
|
|
3869
|
+
"cdk.json",
|
|
3870
|
+
"pulumi.yaml",
|
|
3871
|
+
"README.md",
|
|
3872
|
+
"CHANGELOG.md",
|
|
3873
|
+
"CONTRIBUTING.md",
|
|
3874
|
+
"LICENSE",
|
|
3875
|
+
"renovate.json",
|
|
3876
|
+
".nvmrc",
|
|
3877
|
+
".node-version",
|
|
3878
|
+
".python-version",
|
|
3879
|
+
".ruby-version",
|
|
3880
|
+
".tool-versions"
|
|
3494
3881
|
]);
|
|
3495
|
-
var
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
// than basename is what this rule exists for — the Scala file can be called
|
|
3506
|
-
// anything, and `*.scala` on its own is source code, not a manifest.
|
|
3507
|
-
//
|
|
3508
|
-
// Case-sensitive deliberately: sbt's directory is always lowercase `project`,
|
|
3509
|
-
// while `Project` is an ordinary domain-package name, and reading
|
|
3510
|
-
// `…/domain/Project/Project.scala` as a manifest would be wrong.
|
|
3511
|
-
matches: (filePath) => /(^|\/)project\/[^/]+\.(scala|sbt)$/.test(filePath),
|
|
3512
|
-
parser: parseSbtBuildDefinition
|
|
3513
|
-
}
|
|
3882
|
+
var NOTABLE_PATH_PREFIXES = [".github/workflows/", ".circleci/", ".gitlab/"];
|
|
3883
|
+
var CONFIG_HEURISTIC_PATTERNS = [
|
|
3884
|
+
/^\..*rc$/,
|
|
3885
|
+
// .eslintrc, .prettierrc, etc.
|
|
3886
|
+
/^\..*rc\.(json|js|cjs|mjs|yml|yaml|ts)$/,
|
|
3887
|
+
// .eslintrc.json, etc.
|
|
3888
|
+
/^.*\.config\.(js|ts|mjs|cjs)$/,
|
|
3889
|
+
// vite.config.ts, jest.config.js, etc.
|
|
3890
|
+
/^\.env(\..+)?$/
|
|
3891
|
+
// .env, .env.local, .env.production
|
|
3514
3892
|
];
|
|
3893
|
+
function isNotableFile(filePath) {
|
|
3894
|
+
const basename = filePath.split("/").pop() ?? filePath;
|
|
3895
|
+
const isRoot = !filePath.includes("/");
|
|
3896
|
+
if (NOTABLE_ANY_DEPTH.has(basename)) return true;
|
|
3897
|
+
if (isRoot && NOTABLE_ROOT_ONLY.has(basename)) return true;
|
|
3898
|
+
if (/\.(csproj|vbproj|fsproj|sln)$/i.test(basename)) return true;
|
|
3899
|
+
for (const prefix of NOTABLE_PATH_PREFIXES) {
|
|
3900
|
+
if (filePath.startsWith(prefix)) return true;
|
|
3901
|
+
}
|
|
3902
|
+
if (!filePath.includes("/")) {
|
|
3903
|
+
for (const pattern of CONFIG_HEURISTIC_PATTERNS) {
|
|
3904
|
+
if (pattern.test(basename)) return true;
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
return false;
|
|
3908
|
+
}
|
|
3515
3909
|
|
|
3516
|
-
// src/discover.ts
|
|
3910
|
+
// src/discover/index.ts
|
|
3517
3911
|
async function discover(repositoryRoot, options) {
|
|
3518
3912
|
const unfiltered = listRepoFiles(repositoryRoot);
|
|
3519
3913
|
const files = applyExclusions(unfiltered, [...options?.exclude ?? []], []);
|
|
@@ -3566,46 +3960,33 @@ function findNotableFiles(files) {
|
|
|
3566
3960
|
}
|
|
3567
3961
|
return Array.from(notable).sort();
|
|
3568
3962
|
}
|
|
3569
|
-
function resolveManifestParser(filePath) {
|
|
3570
|
-
const basename = filePath.split("/").pop() ?? filePath;
|
|
3571
|
-
const byName = MANIFEST_PARSERS.get(basename);
|
|
3572
|
-
if (byName) return byName;
|
|
3573
|
-
const lowerBasename = basename.toLowerCase();
|
|
3574
|
-
for (const [suffix, parser] of MANIFEST_SUFFIX_PARSERS) {
|
|
3575
|
-
if (lowerBasename.endsWith(suffix)) return parser;
|
|
3576
|
-
}
|
|
3577
|
-
for (const rule of MANIFEST_PATH_PARSERS) {
|
|
3578
|
-
if (rule.matches(filePath, basename)) return rule.parser;
|
|
3579
|
-
}
|
|
3580
|
-
return void 0;
|
|
3581
|
-
}
|
|
3582
3963
|
async function extractDependencies(repositoryRoot, files) {
|
|
3583
3964
|
const allDeps = [];
|
|
3584
3965
|
for (const filePath of files) {
|
|
3585
|
-
const
|
|
3586
|
-
if (!
|
|
3966
|
+
const manifest = resolveManifest(filePath);
|
|
3967
|
+
if (!manifest) continue;
|
|
3587
3968
|
let content;
|
|
3588
3969
|
try {
|
|
3589
3970
|
content = await import_promises2.default.readFile(import_path6.default.join(repositoryRoot, filePath), "utf-8");
|
|
3590
3971
|
} catch {
|
|
3591
3972
|
continue;
|
|
3592
3973
|
}
|
|
3593
|
-
const
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
const classification = classifyDependency(raw.name, basename);
|
|
3597
|
-
if (classification) {
|
|
3974
|
+
for (const raw of manifest.parser(content)) {
|
|
3975
|
+
const entity = classifyDependency(raw.name, manifest.ecosystem);
|
|
3976
|
+
if (entity) {
|
|
3598
3977
|
allDeps.push({
|
|
3599
3978
|
name: raw.name,
|
|
3600
|
-
|
|
3601
|
-
|
|
3979
|
+
key: entity.key,
|
|
3980
|
+
displayName: entity.displayName,
|
|
3981
|
+
kind: entity.kind,
|
|
3602
3982
|
source: "manifest",
|
|
3603
3983
|
manifestFile: filePath,
|
|
3604
|
-
version: raw.version
|
|
3984
|
+
version: raw.version,
|
|
3985
|
+
ecosystem: manifest.ecosystem
|
|
3605
3986
|
});
|
|
3606
3987
|
}
|
|
3607
3988
|
}
|
|
3608
|
-
if (
|
|
3989
|
+
if (filePath.split("/").pop() === "package.json") {
|
|
3609
3990
|
for (const signal of detectPackageJsonSignals(content)) {
|
|
3610
3991
|
allDeps.push({ ...signal, manifestFile: filePath });
|
|
3611
3992
|
}
|
|
@@ -3617,8 +3998,8 @@ function deduplicateDeps(deps) {
|
|
|
3617
3998
|
const seen = /* @__PURE__ */ new Set();
|
|
3618
3999
|
const result = [];
|
|
3619
4000
|
for (const dep of deps) {
|
|
3620
|
-
if (!seen.has(dep.
|
|
3621
|
-
seen.add(dep.
|
|
4001
|
+
if (!seen.has(dep.key)) {
|
|
4002
|
+
seen.add(dep.key);
|
|
3622
4003
|
result.push(dep);
|
|
3623
4004
|
}
|
|
3624
4005
|
}
|
|
@@ -7171,8 +7552,14 @@ var ToolsService = class {
|
|
|
7171
7552
|
|
|
7172
7553
|
// src/api/index.ts
|
|
7173
7554
|
OpenAPI.BASE = (process.env.CODACY_API_BASE_URL || "https://app.codacy.com").replace(/\/$/, "") + "/api/v3";
|
|
7174
|
-
function configureApiToken(
|
|
7175
|
-
|
|
7555
|
+
function configureApiToken(auth) {
|
|
7556
|
+
const resolved = typeof auth === "string" ? { kind: "account-token", token: auth } : auth;
|
|
7557
|
+
if (!resolved || resolved.kind !== "account-token" && resolved.kind !== "repository-token" || !resolved.token) {
|
|
7558
|
+
throw new Error(
|
|
7559
|
+
'configureApiToken requires a non-empty apiToken string, or a RemoteAuth object ({ kind: "account-token" | "repository-token", token: string }).'
|
|
7560
|
+
);
|
|
7561
|
+
}
|
|
7562
|
+
OpenAPI.HEADERS = resolved.kind === "account-token" ? { "api-token": resolved.token } : { "project-token": resolved.token };
|
|
7176
7563
|
}
|
|
7177
7564
|
async function fetchAllPages(fetcher) {
|
|
7178
7565
|
const results = [];
|
|
@@ -7265,6 +7652,10 @@ async function listIgnoredFiles(provider, org, repo) {
|
|
|
7265
7652
|
}
|
|
7266
7653
|
|
|
7267
7654
|
// src/init.ts
|
|
7655
|
+
function isAuthFailure(err) {
|
|
7656
|
+
return err instanceof ApiError && (err.status === 401 || err.status === 403);
|
|
7657
|
+
}
|
|
7658
|
+
var AUTH_FAILURE_HINT = "The token was accepted for earlier reads but rejected here: if it is a repository token, check that it belongs to this repository; if it is an account token, check that the account has access to it.";
|
|
7268
7659
|
function deriveApiToolId(tool) {
|
|
7269
7660
|
return tool.prefix ? tool.prefix.replace(/_$/, "") : tool.shortName;
|
|
7270
7661
|
}
|
|
@@ -7319,7 +7710,7 @@ async function detectLanguages(repositoryRoot, precomputedFiles, exclude) {
|
|
|
7319
7710
|
for (const file of files) {
|
|
7320
7711
|
const basename = import_path8.default.basename(file);
|
|
7321
7712
|
const ext = import_path8.default.extname(file).toLowerCase();
|
|
7322
|
-
for (const [lang, config] of Object.entries(
|
|
7713
|
+
for (const [lang, config] of Object.entries(import_tooling7.LANGUAGE_CONFIG)) {
|
|
7323
7714
|
if (config.extensions.includes(ext)) {
|
|
7324
7715
|
detected.add(lang);
|
|
7325
7716
|
} else if (config.files.includes(basename)) {
|
|
@@ -7437,14 +7828,21 @@ async function initDefaultConfig(repositoryRoot, adapters, options) {
|
|
|
7437
7828
|
stats: { totalApiTools: apiTools.length, addedTools, addedUnsupported, codacyYamlApplied }
|
|
7438
7829
|
};
|
|
7439
7830
|
}
|
|
7440
|
-
async function initRemoteConfig(repositoryRoot, provider, organization, repositoryName, adapters,
|
|
7441
|
-
configureApiToken(
|
|
7831
|
+
async function initRemoteConfig(repositoryRoot, provider, organization, repositoryName, adapters, auth, onRemoteToolsFound, onToolProcessed, onIgnoredFilesFetched) {
|
|
7832
|
+
configureApiToken(auth);
|
|
7833
|
+
const repositoryLabel = `${provider}/${organization}/${repositoryName}`;
|
|
7442
7834
|
const repoInfo = await getRepository(provider, organization, repositoryName);
|
|
7443
7835
|
const remoteTools = await listRepositoryTools(provider, organization, repositoryName);
|
|
7444
7836
|
let ignoredFilesResult = { files: [], totalFetched: 0, capped: false };
|
|
7445
7837
|
try {
|
|
7446
7838
|
ignoredFilesResult = await listIgnoredFiles(provider, organization, repositoryName);
|
|
7447
|
-
} catch {
|
|
7839
|
+
} catch (err) {
|
|
7840
|
+
if (isAuthFailure(err)) {
|
|
7841
|
+
throw new Error(
|
|
7842
|
+
`Failed to fetch cloud-ignored files for ${repositoryLabel}: ${err.status} ${err.statusText}. ${AUTH_FAILURE_HINT} Aborting: continuing would silently drop this repository's cloud excludes from the generated config.`,
|
|
7843
|
+
{ cause: err }
|
|
7844
|
+
);
|
|
7845
|
+
}
|
|
7448
7846
|
}
|
|
7449
7847
|
onIgnoredFilesFetched?.(ignoredFilesResult.totalFetched, ignoredFilesResult.capped);
|
|
7450
7848
|
const localConfigs = await detectLocalConfigFiles(repositoryRoot, adapters);
|
|
@@ -7453,7 +7851,7 @@ async function initRemoteConfig(repositoryRoot, provider, organization, reposito
|
|
|
7453
7851
|
const apiToolByUuid = new Map(allApiTools.map((t) => [t.uuid, t]));
|
|
7454
7852
|
const matchedAdapterCount = remoteTools.filter((t) => adapterByUuid.has(t.uuid)).length;
|
|
7455
7853
|
onRemoteToolsFound?.(remoteTools.length, matchedAdapterCount);
|
|
7456
|
-
const fetchPatterns = async (uuid) => {
|
|
7854
|
+
const fetchPatterns = async (uuid, toolLabel) => {
|
|
7457
7855
|
try {
|
|
7458
7856
|
const configuredPatterns = await listRepositoryToolPatterns(
|
|
7459
7857
|
provider,
|
|
@@ -7468,7 +7866,13 @@ async function initRemoteConfig(repositoryRoot, provider, organization, reposito
|
|
|
7468
7866
|
...params ? { parameters: params } : {}
|
|
7469
7867
|
};
|
|
7470
7868
|
});
|
|
7471
|
-
} catch {
|
|
7869
|
+
} catch (err) {
|
|
7870
|
+
if (isAuthFailure(err)) {
|
|
7871
|
+
throw new Error(
|
|
7872
|
+
`Failed to fetch enabled patterns for ${toolLabel} in ${repositoryLabel}: ${err.status} ${err.statusText}. ${AUTH_FAILURE_HINT} Aborting: continuing would write a configuration with no enabled patterns.`,
|
|
7873
|
+
{ cause: err }
|
|
7874
|
+
);
|
|
7875
|
+
}
|
|
7472
7876
|
return [];
|
|
7473
7877
|
}
|
|
7474
7878
|
};
|
|
@@ -7481,7 +7885,7 @@ async function initRemoteConfig(repositoryRoot, provider, organization, reposito
|
|
|
7481
7885
|
if (adapter) {
|
|
7482
7886
|
matchedAdapters++;
|
|
7483
7887
|
const localConfigPath = localConfigs.get(adapter.id);
|
|
7484
|
-
const patterns2 = usesLocalConfig ? [] : await fetchPatterns(remoteTool.uuid);
|
|
7888
|
+
const patterns2 = usesLocalConfig ? [] : await fetchPatterns(remoteTool.uuid, adapter.displayName);
|
|
7485
7889
|
onToolProcessed?.(adapter.displayName, patterns2.length, usesLocalConfig);
|
|
7486
7890
|
tools.push({
|
|
7487
7891
|
toolId: adapter.id,
|
|
@@ -7496,7 +7900,7 @@ async function initRemoteConfig(repositoryRoot, provider, organization, reposito
|
|
|
7496
7900
|
const apiTool = apiToolByUuid.get(remoteTool.uuid);
|
|
7497
7901
|
if (!apiTool || !isTrackableTool(apiTool)) continue;
|
|
7498
7902
|
const toolId = deriveApiToolId(apiTool);
|
|
7499
|
-
const patterns = usesLocalConfig ? [] : await fetchPatterns(remoteTool.uuid);
|
|
7903
|
+
const patterns = usesLocalConfig ? [] : await fetchPatterns(remoteTool.uuid, apiTool.name ?? toolId);
|
|
7500
7904
|
unsupportedTools++;
|
|
7501
7905
|
onToolProcessed?.(apiTool.name ?? toolId, patterns.length, usesLocalConfig);
|
|
7502
7906
|
tools.push({
|
|
@@ -7603,9 +8007,9 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
|
|
|
7603
8007
|
const discoveryResult = await discover(repositoryRoot, { exclude: excludes.global });
|
|
7604
8008
|
onDiscoveryComplete?.(discoveryResult);
|
|
7605
8009
|
const detectedKeys = /* @__PURE__ */ new Set();
|
|
7606
|
-
for (const lang of discoveryResult.languages) detectedKeys.add(lang);
|
|
7607
|
-
for (const fw of discoveryResult.frameworks) detectedKeys.add(fw.
|
|
7608
|
-
for (const lib of discoveryResult.libraries) detectedKeys.add(lib.
|
|
8010
|
+
for (const lang of discoveryResult.languages) detectedKeys.add((0, import_tooling7.languageKey)(lang));
|
|
8011
|
+
for (const fw of discoveryResult.frameworks) detectedKeys.add(fw.key);
|
|
8012
|
+
for (const lib of discoveryResult.libraries) detectedKeys.add(lib.key);
|
|
7609
8013
|
const repoLanguages = new Set(discoveryResult.languages);
|
|
7610
8014
|
const localConfigs = await detectLocalConfigFiles(repositoryRoot, adapters);
|
|
7611
8015
|
const repoFiles = listRepoFiles(repositoryRoot, excludes.global);
|
|
@@ -7659,13 +8063,15 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
|
|
|
7659
8063
|
toolId: adapter.id,
|
|
7660
8064
|
patterns: selectedPatterns.map((p) => ({ patternId: p.id }))
|
|
7661
8065
|
};
|
|
7662
|
-
|
|
7663
|
-
await applyAdapterParameterSuggestions(
|
|
8066
|
+
await applyAdapterConfigSuggestions(
|
|
7664
8067
|
adapter,
|
|
7665
8068
|
toolEntry,
|
|
7666
8069
|
repositoryRoot,
|
|
7667
|
-
filesVisibleTo(adapter.id)
|
|
8070
|
+
filesVisibleTo(adapter.id),
|
|
8071
|
+
repoLanguages,
|
|
8072
|
+
filters
|
|
7668
8073
|
);
|
|
8074
|
+
if (toolEntry.patterns.length > 0) tools.push(toolEntry);
|
|
7669
8075
|
}
|
|
7670
8076
|
}
|
|
7671
8077
|
let selectedUnsupported = 0;
|
|
@@ -7718,8 +8124,8 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
|
|
|
7718
8124
|
}
|
|
7719
8125
|
};
|
|
7720
8126
|
}
|
|
7721
|
-
async function
|
|
7722
|
-
if (!adapter.
|
|
8127
|
+
async function applyAdapterConfigSuggestions(adapter, toolEntry, repositoryRoot, repoFiles, repoLanguages, filters) {
|
|
8128
|
+
if (!adapter.suggestConfiguration) return;
|
|
7723
8129
|
const logger = {
|
|
7724
8130
|
debug() {
|
|
7725
8131
|
},
|
|
@@ -7744,15 +8150,38 @@ async function applyAdapterParameterSuggestions(adapter, toolEntry, repositoryRo
|
|
|
7744
8150
|
logger,
|
|
7745
8151
|
runner: "local"
|
|
7746
8152
|
};
|
|
7747
|
-
const
|
|
7748
|
-
if (!
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7752
|
-
|
|
8153
|
+
const suggestion = await adapter.suggestConfiguration(ctx);
|
|
8154
|
+
if (!suggestion) return;
|
|
8155
|
+
const staticSelection = toolEntry.patterns;
|
|
8156
|
+
if (suggestion.disablePatterns?.length) {
|
|
8157
|
+
const drop = new Set(suggestion.disablePatterns);
|
|
8158
|
+
toolEntry.patterns = toolEntry.patterns.filter((p) => !drop.has(p.patternId));
|
|
8159
|
+
}
|
|
8160
|
+
if (suggestion.enablePatterns?.length) {
|
|
8161
|
+
const { severityFilter, categoryFilter } = buildFilterLookups(filters);
|
|
8162
|
+
const metadata = new Map((adapter.patternsMetadata ?? []).map((p) => [p.id, p]));
|
|
8163
|
+
const present = new Set(toolEntry.patterns.map((p) => p.patternId));
|
|
8164
|
+
for (const patternId of suggestion.enablePatterns) {
|
|
8165
|
+
if (present.has(patternId)) continue;
|
|
8166
|
+
const pattern = metadata.get(patternId);
|
|
8167
|
+
if (!pattern) continue;
|
|
8168
|
+
if (!passesSelectionGates(pattern, repoLanguages, severityFilter, categoryFilter)) continue;
|
|
8169
|
+
toolEntry.patterns.push({ patternId });
|
|
8170
|
+
present.add(patternId);
|
|
8171
|
+
}
|
|
8172
|
+
}
|
|
8173
|
+
if (toolEntry.patterns.length === 0) {
|
|
8174
|
+
toolEntry.patterns = staticSelection;
|
|
8175
|
+
}
|
|
8176
|
+
if (suggestion.parameters) {
|
|
8177
|
+
for (const pattern of toolEntry.patterns) {
|
|
8178
|
+
const params = suggestion.parameters[pattern.patternId];
|
|
8179
|
+
if (!params) continue;
|
|
8180
|
+
pattern.parameters = { ...pattern.parameters, ...params };
|
|
8181
|
+
}
|
|
7753
8182
|
}
|
|
7754
8183
|
} catch (err) {
|
|
7755
|
-
logger.error("
|
|
8184
|
+
logger.error("suggestConfiguration failed", {
|
|
7756
8185
|
error: err instanceof Error ? err.message : String(err)
|
|
7757
8186
|
});
|
|
7758
8187
|
}
|
|
@@ -7778,6 +8207,13 @@ function deduplicateToolFamilies(candidates, localConfigs) {
|
|
|
7778
8207
|
continue;
|
|
7779
8208
|
}
|
|
7780
8209
|
const withConfig = group.filter((a) => localConfigs.has(a.id));
|
|
8210
|
+
const configuredFamilies = groupByToolFamily(withConfig);
|
|
8211
|
+
if (configuredFamilies.size > 1) {
|
|
8212
|
+
for (const members of configuredFamilies.values()) {
|
|
8213
|
+
result.push(members.length === 1 ? members[0] : highestVersion(members));
|
|
8214
|
+
}
|
|
8215
|
+
continue;
|
|
8216
|
+
}
|
|
7781
8217
|
const pool = withConfig.length > 0 ? withConfig : group;
|
|
7782
8218
|
if (pool.length === 1) {
|
|
7783
8219
|
result.push(pool[0]);
|
|
@@ -7792,17 +8228,27 @@ function deduplicateToolFamilies(candidates, localConfigs) {
|
|
|
7792
8228
|
group.filter((a) => a.alternativeGroupDefault).map((a) => toolFamily(a.id))
|
|
7793
8229
|
);
|
|
7794
8230
|
const defaultFamilyMembers = defaultFamilies.size === 1 ? pool.filter((a) => defaultFamilies.has(toolFamily(a.id))) : [];
|
|
7795
|
-
|
|
7796
|
-
(a, b) => {
|
|
7797
|
-
const majorA = parseInt(a.preferredVersion.split(".")[0], 10) || 0;
|
|
7798
|
-
const majorB = parseInt(b.preferredVersion.split(".")[0], 10) || 0;
|
|
7799
|
-
return majorB - majorA;
|
|
7800
|
-
}
|
|
7801
|
-
);
|
|
7802
|
-
result.push(ranked[0]);
|
|
8231
|
+
result.push(highestVersion(defaultFamilyMembers.length > 0 ? defaultFamilyMembers : pool));
|
|
7803
8232
|
}
|
|
7804
8233
|
return result;
|
|
7805
8234
|
}
|
|
8235
|
+
function groupByToolFamily(adapters) {
|
|
8236
|
+
const byFamily = /* @__PURE__ */ new Map();
|
|
8237
|
+
for (const adapter of adapters) {
|
|
8238
|
+
const family = toolFamily(adapter.id);
|
|
8239
|
+
const members = byFamily.get(family) ?? [];
|
|
8240
|
+
members.push(adapter);
|
|
8241
|
+
byFamily.set(family, members);
|
|
8242
|
+
}
|
|
8243
|
+
return byFamily;
|
|
8244
|
+
}
|
|
8245
|
+
function highestVersion(adapters) {
|
|
8246
|
+
return [...adapters].sort((a, b) => {
|
|
8247
|
+
const majorA = parseInt(a.preferredVersion.split(".")[0], 10) || 0;
|
|
8248
|
+
const majorB = parseInt(b.preferredVersion.split(".")[0], 10) || 0;
|
|
8249
|
+
return majorB - majorA;
|
|
8250
|
+
})[0];
|
|
8251
|
+
}
|
|
7806
8252
|
function filterAutoPatterns(patterns, mappings, detectedKeys, repoLanguages, filters) {
|
|
7807
8253
|
const allMappedPrefixes = [];
|
|
7808
8254
|
const prefixToKeys = /* @__PURE__ */ new Map();
|
|
@@ -7828,8 +8274,7 @@ function filterAutoPatterns(patterns, mappings, detectedKeys, repoLanguages, fil
|
|
|
7828
8274
|
}
|
|
7829
8275
|
}
|
|
7830
8276
|
}
|
|
7831
|
-
const severityFilter
|
|
7832
|
-
const categoryFilter = filters?.categories.length ? new Map(filters.categories.map((c) => [c.category, c.all])) : void 0;
|
|
8277
|
+
const { severityFilter, categoryFilter } = buildFilterLookups(filters);
|
|
7833
8278
|
const result = [];
|
|
7834
8279
|
for (const pattern of patterns) {
|
|
7835
8280
|
const matchedKeys = findMappedKeys(
|
|
@@ -7849,15 +8294,25 @@ function filterAutoPatterns(patterns, mappings, detectedKeys, repoLanguages, fil
|
|
|
7849
8294
|
}
|
|
7850
8295
|
}
|
|
7851
8296
|
}
|
|
7852
|
-
if (pattern
|
|
7853
|
-
if (!pattern.languages.some((lang) => repoLanguages.has(lang))) continue;
|
|
7854
|
-
}
|
|
7855
|
-
if (severityFilter && !severityFilter.has(pattern.severityLevel)) continue;
|
|
7856
|
-
if (categoryFilter && !categoryFilter.has(pattern.category)) continue;
|
|
8297
|
+
if (!passesSelectionGates(pattern, repoLanguages, severityFilter, categoryFilter)) continue;
|
|
7857
8298
|
result.push(pattern);
|
|
7858
8299
|
}
|
|
7859
8300
|
return result;
|
|
7860
8301
|
}
|
|
8302
|
+
function buildFilterLookups(filters) {
|
|
8303
|
+
return {
|
|
8304
|
+
severityFilter: filters?.severities.length ? new Map(filters.severities.map((s) => [s.level, s.all])) : void 0,
|
|
8305
|
+
categoryFilter: filters?.categories.length ? new Map(filters.categories.map((c) => [c.category, c.all])) : void 0
|
|
8306
|
+
};
|
|
8307
|
+
}
|
|
8308
|
+
function passesSelectionGates(pattern, repoLanguages, severityFilter, categoryFilter) {
|
|
8309
|
+
if (pattern.languages.length > 0 && repoLanguages.size > 0) {
|
|
8310
|
+
if (!pattern.languages.some((lang) => repoLanguages.has(lang))) return false;
|
|
8311
|
+
}
|
|
8312
|
+
if (severityFilter && !severityFilter.has(pattern.severityLevel)) return false;
|
|
8313
|
+
if (categoryFilter && !categoryFilter.has(pattern.category)) return false;
|
|
8314
|
+
return true;
|
|
8315
|
+
}
|
|
7861
8316
|
function findMappedKeys(patternId, patternTags, allPrefixes, prefixToKeys, allTags, tagToKeys) {
|
|
7862
8317
|
const matched = /* @__PURE__ */ new Set();
|
|
7863
8318
|
let longestPrefix = "";
|
|
@@ -7950,7 +8405,7 @@ function parseAutoFilters(raw) {
|
|
|
7950
8405
|
}
|
|
7951
8406
|
|
|
7952
8407
|
// src/index.ts
|
|
7953
|
-
var
|
|
8408
|
+
var import_tooling8 = require("@codacy/tooling");
|
|
7954
8409
|
|
|
7955
8410
|
// src/credentials.ts
|
|
7956
8411
|
var import_crypto = __toESM(require("crypto"));
|
|
@@ -8015,6 +8470,17 @@ async function resolveApiToken() {
|
|
|
8015
8470
|
if (stored) return stored;
|
|
8016
8471
|
throw new Error("No API token found. Set CODACY_API_TOKEN or run 'codacy-analysis login'.");
|
|
8017
8472
|
}
|
|
8473
|
+
async function resolveRemoteAuth(repositoryToken) {
|
|
8474
|
+
const projectToken = repositoryToken || process.env.CODACY_PROJECT_TOKEN || void 0;
|
|
8475
|
+
if (projectToken) return { kind: "repository-token", token: projectToken };
|
|
8476
|
+
const apiToken = process.env.CODACY_API_TOKEN || await loadCredentials() || void 0;
|
|
8477
|
+
if (!apiToken) {
|
|
8478
|
+
throw new Error(
|
|
8479
|
+
"No API token found. Provide --repository-token, set CODACY_PROJECT_TOKEN, set CODACY_API_TOKEN, or run 'codacy-analysis login'."
|
|
8480
|
+
);
|
|
8481
|
+
}
|
|
8482
|
+
return { kind: "account-token", token: apiToken };
|
|
8483
|
+
}
|
|
8018
8484
|
|
|
8019
8485
|
// src/git-info.ts
|
|
8020
8486
|
var import_child_process5 = require("child_process");
|
|
@@ -8314,13 +8780,16 @@ async function safeReadBody(res) {
|
|
|
8314
8780
|
registerAdapter,
|
|
8315
8781
|
registerLazyAdapter,
|
|
8316
8782
|
registerUnsupportedTool,
|
|
8783
|
+
relativizeLocalConfigPaths,
|
|
8317
8784
|
resolveApiToken,
|
|
8318
8785
|
resolveBaselinePath,
|
|
8319
8786
|
resolveConfigPath,
|
|
8320
8787
|
resolveDiscoveryExcludes,
|
|
8321
8788
|
resolveEngineToAdapter,
|
|
8322
8789
|
resolveHeadCommit,
|
|
8790
|
+
resolveLocalConfigPaths,
|
|
8323
8791
|
resolveProxyConfig,
|
|
8792
|
+
resolveRemoteAuth,
|
|
8324
8793
|
resolveRepositoryRoot,
|
|
8325
8794
|
resolveResultsApiBaseUrl,
|
|
8326
8795
|
rotateLogFiles,
|