@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.
Files changed (3) hide show
  1. package/dist/index.d.ts +136 -31
  2. package/dist/index.js +1598 -1129
  3. 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: () => import_tooling6.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
- resolveProxyConfig: () => import_tooling6.resolveProxyConfig,
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(config, null, 2) + "\n");
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(config, null, 2) + "\n");
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 import_tooling5 = require("@codacy/tooling");
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-catalog.ts
2341
- var DEPENDENCY_CATALOG = /* @__PURE__ */ new Map([
2342
- // --- JavaScript / TypeScript — Frameworks ---
2343
- ["react", { displayName: "React", kind: "framework" }],
2344
- ["react-dom", { displayName: "React DOM", kind: "framework" }],
2345
- ["react-native", { displayName: "React Native", kind: "framework" }],
2346
- ["vue", { displayName: "Vue.js", kind: "framework" }],
2347
- ["nuxt", { displayName: "Nuxt", kind: "framework" }],
2348
- ["@angular/core", { displayName: "Angular", kind: "framework" }],
2349
- ["svelte", { displayName: "Svelte", kind: "framework" }],
2350
- ["@sveltejs/kit", { displayName: "SvelteKit", kind: "framework" }],
2351
- ["next", { displayName: "Next.js", kind: "framework" }],
2352
- ["ember-source", { displayName: "Ember.js", kind: "framework" }],
2353
- ["lit", { displayName: "Lit", kind: "framework" }],
2354
- ["solid-js", { displayName: "Solid", kind: "framework" }],
2355
- ["astro", { displayName: "Astro", kind: "framework" }],
2356
- ["@builder.io/qwik", { displayName: "Qwik", kind: "framework" }],
2357
- ["@builder.io/qwik-city", { displayName: "Qwik", kind: "framework" }],
2358
- ["@remix-run/react", { displayName: "Remix", kind: "framework" }],
2359
- ["gatsby", { displayName: "Gatsby", kind: "framework" }],
2360
- ["preact", { displayName: "Preact", kind: "framework" }],
2361
- ["express", { displayName: "Express", kind: "framework" }],
2362
- ["@nestjs/core", { displayName: "NestJS", kind: "framework" }],
2363
- ["fastify", { displayName: "Fastify", kind: "framework" }],
2364
- ["koa", { displayName: "Koa", kind: "framework" }],
2365
- ["@hapi/hapi", { displayName: "Hapi", kind: "framework" }],
2366
- ["hono", { displayName: "Hono", kind: "framework" }],
2367
- ["electron", { displayName: "Electron", kind: "framework" }],
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 below).
2370
- ["aws-cdk-lib", { displayName: "AWS CDK", kind: "framework" }],
2371
- ["aws-cdk", { displayName: "AWS CDK", kind: "framework" }],
2372
- // --- JavaScript / TypeScript — Libraries ---
2373
- ["lodash", { displayName: "Lodash", kind: "library" }],
2374
- ["underscore", { displayName: "Underscore.js", kind: "library" }],
2375
- ["ramda", { displayName: "Ramda", kind: "library" }],
2376
- ["jest", { displayName: "Jest", kind: "library" }],
2377
- ["vitest", { displayName: "Vitest", kind: "library" }],
2378
- ["@playwright/test", { displayName: "Playwright", kind: "library" }],
2379
- ["playwright", { displayName: "Playwright", kind: "library" }],
2380
- ["cypress", { displayName: "Cypress", kind: "library" }],
2381
- ["mocha", { displayName: "Mocha", kind: "library" }],
2382
- ["ava", { displayName: "AVA", kind: "library" }],
2383
- ["jasmine", { displayName: "Jasmine", kind: "library" }],
2384
- ["jasmine-core", { displayName: "Jasmine", kind: "library" }],
2385
- ["@testing-library/react", { displayName: "Testing Library", kind: "library" }],
2386
- ["@testing-library/vue", { displayName: "Testing Library", kind: "library" }],
2387
- ["@testing-library/dom", { displayName: "Testing Library", kind: "library" }],
2388
- ["tailwindcss", { displayName: "Tailwind CSS", kind: "library" }],
2389
- ["@storybook/react", { displayName: "Storybook", kind: "library" }],
2390
- ["@storybook/vue3", { displayName: "Storybook", kind: "library" }],
2391
- ["@storybook/svelte", { displayName: "Storybook", kind: "library" }],
2392
- ["redux", { displayName: "Redux", kind: "library" }],
2393
- ["@reduxjs/toolkit", { displayName: "Redux Toolkit", kind: "library" }],
2394
- ["mobx", { displayName: "MobX", kind: "library" }],
2395
- ["zustand", { displayName: "Zustand", kind: "library" }],
2396
- ["axios", { displayName: "Axios", kind: "library" }],
2397
- ["date-fns", { displayName: "date-fns", kind: "library" }],
2398
- ["dayjs", { displayName: "Day.js", kind: "library" }],
2399
- ["moment", { displayName: "Moment.js", kind: "library" }],
2400
- ["rxjs", { displayName: "RxJS", kind: "library" }],
2401
- ["zod", { displayName: "Zod", kind: "library" }],
2402
- ["joi", { displayName: "Joi", kind: "library" }],
2403
- ["yup", { displayName: "Yup", kind: "library" }],
2404
- ["prisma", { displayName: "Prisma", kind: "library" }],
2405
- ["@prisma/client", { displayName: "Prisma", kind: "library" }],
2406
- ["typeorm", { displayName: "TypeORM", kind: "library" }],
2407
- ["sequelize", { displayName: "Sequelize", kind: "library" }],
2408
- ["drizzle-orm", { displayName: "Drizzle ORM", kind: "library" }],
2409
- ["mongoose", { displayName: "Mongoose", kind: "library" }],
2410
- ["graphql", { displayName: "GraphQL", kind: "library" }],
2411
- ["@apollo/client", { displayName: "Apollo Client", kind: "library" }],
2412
- ["socket.io", { displayName: "Socket.IO", kind: "library" }],
2413
- ["jquery", { displayName: "jQuery", kind: "library" }],
2414
- ["mongodb", { displayName: "MongoDB", kind: "library" }],
2415
- ["three", { displayName: "Three.js", kind: "library" }],
2416
- ["d3", { displayName: "D3.js", kind: "library" }],
2417
- ["chart.js", { displayName: "Chart.js", kind: "library" }],
2418
- ["sass", { displayName: "Sass", kind: "library" }],
2419
- ["node-sass", { displayName: "Sass", kind: "library" }],
2420
- ["less", { displayName: "Less", kind: "library" }],
2421
- ["styled-components", { displayName: "styled-components", kind: "library" }],
2422
- ["@emotion/react", { displayName: "Emotion", kind: "library" }],
2423
- ["pino", { displayName: "Pino", kind: "library" }],
2424
- ["winston", { displayName: "Winston", kind: "library" }],
2425
- ["i18next", { displayName: "i18next", kind: "library" }],
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", { displayName: "formatjs", kind: "library" }],
2429
- ["vue-i18n", { displayName: "vue-i18n", kind: "library" }],
2430
- ["next-intl", { displayName: "next-intl", kind: "library" }],
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", { displayName: "Flow", kind: "library" }],
2531
+ ["flow-bin", "flow"],
2433
2532
  // Legacy JS libraries with dedicated ESLint plugins.
2434
- ["backbone", { displayName: "Backbone.js", kind: "library" }],
2435
- ["chai", { displayName: "Chai", kind: "library" }],
2436
- ["codeceptjs", { displayName: "CodeceptJS", kind: "library" }],
2437
- ["webdriverio", { displayName: "WebdriverIO", kind: "library" }],
2438
- ["react-relay", { displayName: "Relay", kind: "library" }],
2439
- ["relay-runtime", { displayName: "Relay", kind: "library" }],
2440
- // --- JavaScript / TypeScript — Tools ---
2441
- ["typescript", { displayName: "TypeScript", kind: "tool" }],
2442
- ["eslint", { displayName: "ESLint", kind: "tool" }],
2443
- ["prettier", { displayName: "Prettier", kind: "tool" }],
2444
- ["webpack", { displayName: "Webpack", kind: "tool" }],
2445
- ["vite", { displayName: "Vite", kind: "tool" }],
2446
- ["esbuild", { displayName: "esbuild", kind: "tool" }],
2447
- ["rollup", { displayName: "Rollup", kind: "tool" }],
2448
- ["turbo", { displayName: "Turborepo", kind: "tool" }],
2449
- ["tsup", { displayName: "tsup", kind: "tool" }],
2450
- ["@biomejs/biome", { displayName: "Biome", kind: "tool" }],
2451
- ["stylelint", { displayName: "Stylelint", kind: "tool" }],
2452
- // --- JavaScript / TypeScript — Platforms ---
2453
- ["@lwc/engine-dom", { displayName: "Lightning Web Components", kind: "platform" }],
2454
- ["@salesforce/core", { displayName: "Salesforce", kind: "platform" }],
2455
- ["@shopify/polaris", { displayName: "Shopify Polaris", kind: "platform" }],
2456
- // --- Python — Frameworks ---
2457
- ["django", { displayName: "Django", kind: "framework" }],
2458
- ["flask", { displayName: "Flask", kind: "framework" }],
2459
- ["fastapi", { displayName: "FastAPI", kind: "framework" }],
2460
- ["tornado", { displayName: "Tornado", kind: "framework" }],
2461
- ["pyramid", { displayName: "Pyramid", kind: "framework" }],
2462
- ["starlette", { displayName: "Starlette", kind: "framework" }],
2463
- ["sanic", { displayName: "Sanic", kind: "framework" }],
2464
- ["bottle", { displayName: "Bottle", kind: "framework" }],
2465
- ["streamlit", { displayName: "Streamlit", kind: "framework" }],
2466
- ["gradio", { displayName: "Gradio", kind: "framework" }],
2467
- ["apache-airflow", { displayName: "Airflow", kind: "framework" }],
2468
- // --- Python — Libraries ---
2469
- ["flask-babel", { displayName: "Flask-Babel", kind: "library" }],
2470
- ["pytest", { displayName: "pytest", kind: "library" }],
2471
- ["numpy", { displayName: "NumPy", kind: "library" }],
2472
- ["pandas", { displayName: "pandas", kind: "library" }],
2473
- ["requests", { displayName: "Requests", kind: "library" }],
2474
- ["sqlalchemy", { displayName: "SQLAlchemy", kind: "library" }],
2475
- ["celery", { displayName: "Celery", kind: "library" }],
2476
- ["pydantic", { displayName: "Pydantic", kind: "library" }],
2477
- ["boto3", { displayName: "Boto3 (AWS SDK)", kind: "library" }],
2478
- ["scipy", { displayName: "SciPy", kind: "library" }],
2479
- ["scikit-learn", { displayName: "scikit-learn", kind: "library" }],
2480
- ["tensorflow", { displayName: "TensorFlow", kind: "library" }],
2481
- ["torch", { displayName: "PyTorch", kind: "library" }],
2482
- ["httpx", { displayName: "HTTPX", kind: "library" }],
2483
- ["aiohttp", { displayName: "aiohttp", kind: "library" }],
2484
- ["pillow", { displayName: "Pillow", kind: "library" }],
2485
- ["beautifulsoup4", { displayName: "Beautiful Soup", kind: "library" }],
2486
- ["scrapy", { displayName: "Scrapy", kind: "library" }],
2487
- ["alembic", { displayName: "Alembic", kind: "library" }],
2488
- ["marshmallow", { displayName: "Marshmallow", kind: "library" }],
2489
- ["click", { displayName: "Click", kind: "library" }],
2490
- ["typer", { displayName: "Typer", kind: "library" }],
2491
- ["rich", { displayName: "Rich", kind: "library" }],
2492
- ["polars", { displayName: "Polars", kind: "library" }],
2493
- ["dask", { displayName: "Dask", kind: "library" }],
2494
- ["matplotlib", { displayName: "Matplotlib", kind: "library" }],
2495
- ["seaborn", { displayName: "Seaborn", kind: "library" }],
2496
- ["plotly", { displayName: "Plotly", kind: "library" }],
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", { displayName: "MongoDB", kind: "library" }],
2500
- ["motor", { displayName: "MongoDB", kind: "library" }],
2501
- // --- Python — Tools ---
2502
- ["black", { displayName: "Black", kind: "tool" }],
2503
- ["ruff", { displayName: "Ruff", kind: "tool" }],
2504
- ["mypy", { displayName: "mypy", kind: "tool" }],
2505
- ["pylint", { displayName: "Pylint", kind: "tool" }],
2506
- ["flake8", { displayName: "Flake8", kind: "tool" }],
2507
- ["bandit", { displayName: "Bandit", kind: "tool" }],
2508
- ["isort", { displayName: "isort", kind: "tool" }],
2509
- // --- Ruby — Frameworks ---
2510
- ["rails", { displayName: "Ruby on Rails", kind: "framework" }],
2511
- ["sinatra", { displayName: "Sinatra", kind: "framework" }],
2512
- ["hanami", { displayName: "Hanami", kind: "framework" }],
2513
- ["grape", { displayName: "Grape", kind: "framework" }],
2514
- ["padrino", { displayName: "Padrino", kind: "framework" }],
2515
- // --- Ruby — Libraries ---
2516
- ["rspec", { displayName: "RSpec", kind: "library" }],
2517
- ["rspec-core", { displayName: "RSpec", kind: "library" }],
2518
- ["rspec-rails", { displayName: "RSpec Rails", kind: "library" }],
2519
- ["capybara", { displayName: "Capybara", kind: "library" }],
2520
- ["sidekiq", { displayName: "Sidekiq", kind: "library" }],
2521
- ["devise", { displayName: "Devise", kind: "library" }],
2522
- ["pundit", { displayName: "Pundit", kind: "library" }],
2523
- ["activeadmin", { displayName: "ActiveAdmin", kind: "library" }],
2524
- ["puma", { displayName: "Puma", kind: "library" }],
2525
- ["nokogiri", { displayName: "Nokogiri", kind: "library" }],
2526
- ["faraday", { displayName: "Faraday", kind: "library" }],
2527
- ["httparty", { displayName: "HTTParty", kind: "library" }],
2528
- ["minitest", { displayName: "Minitest", kind: "library" }],
2529
- ["factory_bot", { displayName: "FactoryBot", kind: "library" }],
2530
- ["dry-validation", { displayName: "dry-validation", kind: "library" }],
2531
- // NOTE: `faker` and `rake` are Gemfile-scoped (MANIFEST_SCOPED_CATALOG), not
2532
- // global — they are bare words that also name npm/PyPI packages.
2533
- // --- Ruby — Tools ---
2534
- ["rubocop", { displayName: "RuboCop", kind: "tool" }],
2535
- ["bundler", { displayName: "Bundler", kind: "tool" }],
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", { displayName: "Sorbet", kind: "tool" }],
2538
- ["sorbet-runtime", { displayName: "Sorbet", kind: "tool" }],
2539
- ["sorbet-static", { displayName: "Sorbet", kind: "tool" }],
2540
- ["sorbet-static-and-runtime", { displayName: "Sorbet", kind: "tool" }],
2541
- ["solargraph", { displayName: "Solargraph", kind: "tool" }],
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", { displayName: "Spring Boot", kind: "framework" }],
2544
- ["spring-boot-starter-web", { displayName: "Spring Boot", kind: "framework" }],
2545
- ["spring-core", { displayName: "Spring Framework", kind: "framework" }],
2546
- ["quarkus-core", { displayName: "Quarkus", kind: "framework" }],
2547
- ["micronaut-core", { displayName: "Micronaut", kind: "framework" }],
2548
- ["ktor-server-core", { displayName: "Ktor", kind: "framework" }],
2549
- ["android", { displayName: "Android SDK", kind: "framework" }],
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", { displayName: "JUnit 5", kind: "library" }],
2552
- ["junit", { displayName: "JUnit", kind: "library" }],
2553
- ["junit-jupiter-api", { displayName: "JUnit 5", kind: "library" }],
2554
- ["mockito-core", { displayName: "Mockito", kind: "library" }],
2555
- ["lombok", { displayName: "Lombok", kind: "library" }],
2556
- ["jackson-databind", { displayName: "Jackson", kind: "library" }],
2557
- ["jackson-core", { displayName: "Jackson", kind: "library" }],
2558
- ["hibernate-core", { displayName: "Hibernate", kind: "library" }],
2559
- ["guava", { displayName: "Guava", kind: "library" }],
2560
- ["commons-lang3", { displayName: "Apache Commons Lang", kind: "library" }],
2561
- ["commons-io", { displayName: "Apache Commons IO", kind: "library" }],
2562
- ["slf4j-api", { displayName: "SLF4J", kind: "library" }],
2563
- ["log4j-core", { displayName: "Log4j", kind: "library" }],
2564
- ["logback-classic", { displayName: "Logback", kind: "library" }],
2565
- ["testng", { displayName: "TestNG", kind: "library" }],
2566
- ["assertj-core", { displayName: "AssertJ", kind: "library" }],
2567
- ["gson", { displayName: "Gson", kind: "library" }],
2568
- ["okhttp", { displayName: "OkHttp", kind: "library" }],
2569
- ["retrofit", { displayName: "Retrofit", kind: "library" }],
2570
- ["spock-core", { displayName: "Spock", kind: "library" }],
2571
- ["mongodb-driver-sync", { displayName: "MongoDB", kind: "library" }],
2572
- ["mongodb-driver-core", { displayName: "MongoDB", kind: "library" }],
2573
- ["mongodb-driver-reactivestreams", { displayName: "MongoDB", kind: "library" }],
2574
- ["mongo-java-driver", { displayName: "MongoDB", kind: "library" }],
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 below catch the rest.
2578
- ["grails-core", { displayName: "Grails", kind: "framework" }],
2579
- ["gorm-core", { displayName: "GORM", kind: "library" }],
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 below only
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", { displayName: "Play Framework", kind: "framework" }],
2663
- ["org.playframework:sbt-plugin", { displayName: "Play Framework", kind: "framework" }],
2664
- ["play", { displayName: "Play Framework", kind: "framework" }],
2665
- ["play-server", { displayName: "Play Framework", kind: "framework" }],
2666
- ["play-ws", { displayName: "Play Framework", kind: "framework" }],
2667
- ["play-test", { displayName: "Play Framework", kind: "framework" }],
2668
- ["play-slick", { displayName: "Play Framework", kind: "framework" }],
2669
- ["twirl-api", { displayName: "Play Framework", kind: "framework" }],
2670
- ["scalatestplus-play", { displayName: "Play Framework", kind: "framework" }],
2671
- ["akka-actor", { displayName: "Akka", kind: "framework" }],
2672
- ["spray-can", { displayName: "Spray", kind: "framework" }],
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", { displayName: "ScalaTest", kind: "library" }],
2675
- ["specs2-core", { displayName: "Specs2", kind: "library" }],
2676
- ["cats-core", { displayName: "Cats", kind: "library" }],
2677
- ["circe-core", { displayName: "Circe", kind: "library" }],
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", { displayName: "Slick", kind: "library" }],
2680
- ["slick-hikaricp", { displayName: "Slick", kind: "library" }],
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", { displayName: "jwt-scala", kind: "library" }],
2684
- ["jwt-circe", { displayName: "jwt-scala", kind: "library" }],
2685
- ["jwt-play-json", { displayName: "jwt-scala", kind: "library" }],
2686
- ["jwt-json4s", { displayName: "jwt-scala", kind: "library" }],
2687
- ["jwt-spray-json", { displayName: "jwt-scala", kind: "library" }],
2688
- ["jwt-upickle", { displayName: "jwt-scala", kind: "library" }],
2689
- ["jwt-argonaut", { displayName: "jwt-scala", kind: "library" }],
2690
- ["munit", { displayName: "MUnit", kind: "library" }],
2691
- ["scalacheck", { displayName: "ScalaCheck", kind: "library" }],
2692
- ["scala-logging", { displayName: "scala-logging", kind: "library" }],
2693
- ["chimney", { displayName: "Chimney", kind: "library" }],
2694
- // --- Elixir — Frameworks ---
2695
- ["phoenix", { displayName: "Phoenix", kind: "framework" }],
2696
- ["plug", { displayName: "Plug", kind: "framework" }],
2697
- ["plug_cowboy", { displayName: "Plug", kind: "framework" }],
2698
- // --- Elixir — Libraries ---
2699
- ["ecto", { displayName: "Ecto", kind: "library" }],
2700
- ["ecto_sql", { displayName: "Ecto", kind: "library" }],
2701
- ["phoenix_ecto", { displayName: "Ecto", kind: "library" }],
2702
- // --- Crystal — Frameworks ---
2703
- ["amber", { displayName: "Amber", kind: "framework" }],
2704
- ["lucky", { displayName: "Lucky", kind: "framework" }],
2705
- ["kemal", { displayName: "Kemal", kind: "framework" }],
2706
- // --- Dart — Frameworks ---
2707
- // `flutter` and `angular` appear as plain keys in pubspec.yaml. NOTE: `angular`
2708
- // here is Dart's AngularDart. The npm package `angular` is AngularJS (1.x) — a
2709
- // different framework — so package.json overrides this via MANIFEST_SCOPED_CATALOG.
2710
- ["flutter", { displayName: "Flutter", kind: "framework" }],
2711
- ["flutter_test", { displayName: "Flutter", kind: "framework" }],
2712
- ["angular", { displayName: "Angular", kind: "framework" }],
2713
- // --- Dart — Libraries ---
2714
- // Kept to Dart-specific names with negligible cross-ecosystem collision risk
2715
- // (generic names like "get"/"provider" are intentionally omitted).
2716
- ["riverpod", { displayName: "Riverpod", kind: "library" }],
2717
- ["flutter_bloc", { displayName: "Bloc", kind: "library" }],
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
- { prefix: "grails-", classification: { displayName: "Grails", kind: "framework" } },
2768
- { prefix: "gorm-", classification: { displayName: "GORM", kind: "library" } },
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
- { prefix: "http4s-", classification: { displayName: "http4s", kind: "framework" } },
2773
- { prefix: "akka-", classification: { displayName: "Akka", kind: "framework" } },
2774
- { prefix: "pekko-", classification: { displayName: "Pekko", kind: "framework" } },
2775
- { prefix: "zio-", classification: { displayName: "ZIO", kind: "library" } },
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
- { prefix: "cats-effect", classification: { displayName: "Cats Effect", kind: "library" } },
2778
- { prefix: "doobie-", classification: { displayName: "Doobie", kind: "library" } },
2779
- { prefix: "fs2-", classification: { displayName: "FS2", kind: "library" } },
2780
- { prefix: "circe-", classification: { displayName: "Circe", kind: "library" } },
2781
- { prefix: "pureconfig", classification: { displayName: "PureConfig", kind: "library" } },
2782
- { prefix: "kamon-", classification: { displayName: "Kamon", kind: "library" } },
2783
- { prefix: "flyway-", classification: { displayName: "Flyway", kind: "library" } },
2784
- {
2785
- prefix: "testcontainers-scala",
2786
- classification: { displayName: "Testcontainers", kind: "library" }
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
- prefix: "Microsoft.AspNetCore.Components.WebAssembly",
2792
- classification: { displayName: "Blazor", kind: "framework" }
2793
- },
2794
- {
2795
- prefix: "Microsoft.AspNetCore.",
2796
- classification: { displayName: "ASP.NET Core", kind: "framework" }
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 MANIFEST_SCOPED_CATALOG = /* @__PURE__ */ new Map([
2815
- ["package.json", /* @__PURE__ */ new Map([["angular", { displayName: "AngularJS", kind: "framework" }]])],
2816
- [
2817
- "Gemfile",
2818
- /* @__PURE__ */ new Map([
2819
- ["faker", { displayName: "Faker", kind: "library" }],
2820
- ["rake", { displayName: "Rake", kind: "tool" }]
2821
- ])
2822
- ]
2823
- ]);
2824
- function classifyDependency(name, manifestBasename) {
2825
- if (manifestBasename) {
2826
- const scoped = MANIFEST_SCOPED_CATALOG.get(manifestBasename)?.get(name);
2827
- if (scoped) return scoped;
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
- const exact = DEPENDENCY_CATALOG.get(name);
2830
- if (exact) return exact;
2831
- for (const rule of DEPENDENCY_PREFIX_RULES) {
2832
- if (name.startsWith(rule.prefix)) return rule.classification;
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
- classification: { displayName: "Flow", kind: "library" }
3558
+ entity: "flow"
2844
3559
  },
2845
3560
  {
2846
3561
  name: "cdk.json",
2847
3562
  matches: (f) => pathBasename(f) === "cdk.json",
2848
- classification: { displayName: "AWS CDK", kind: "framework" }
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
- classification: { displayName: "Meteor", kind: "framework" }
3568
+ entity: "meteor"
2854
3569
  },
2855
3570
  {
2856
3571
  name: "sfdx-project.json",
2857
3572
  matches: (f) => pathBasename(f) === "sfdx-project.json",
2858
- classification: { displayName: "Salesforce", kind: "platform" }
3573
+ entity: "salesforce"
2859
3574
  },
2860
3575
  {
2861
3576
  name: "lwc.config.json",
2862
3577
  matches: (f) => pathBasename(f) === "lwc.config.json",
2863
- classification: { displayName: "Lightning Web Components", kind: "platform" }
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
- classification: { displayName: "SuiteScript", kind: "platform" }
3583
+ entity: "suitescript"
2869
3584
  },
2870
3585
  {
2871
3586
  name: ".github/workflows",
2872
3587
  matches: (f) => f.startsWith(".github/workflows/"),
2873
- classification: { displayName: "GitHub Actions", kind: "framework" }
3588
+ entity: "github-actions"
2874
3589
  },
2875
3590
  {
2876
3591
  name: ".circleci",
2877
3592
  matches: (f) => f.startsWith(".circleci/"),
2878
- classification: { displayName: "CircleCI", kind: "framework" }
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
- classification: { displayName: "GitLab CI", kind: "framework" }
3598
+ entity: "gitlab-ci"
2884
3599
  },
2885
3600
  {
2886
3601
  name: "bitbucket-pipelines.yml",
2887
3602
  matches: (f) => f === "bitbucket-pipelines.yml",
2888
- classification: { displayName: "Bitbucket Pipelines", kind: "framework" }
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
- classification: { displayName: "Azure Pipelines", kind: "framework" }
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
- classification: { displayName: "Jenkins", kind: "tool" }
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
- classification: { displayName: "Sorbet", kind: "tool" }
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
- classification: { displayName: "Rake", kind: "tool" }
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
- classification: { displayName: "Play Framework", kind: "framework" }
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
- return deps;
3377
- }
3378
- function parsePackageSwift(content) {
3379
- const deps = [];
3380
- const pkgRegex = /\.package\s*\(\s*url:\s*["'](?:https?:\/\/[^/]+\/[^/]+\/|git@[^:]+:[^/]+\/)([^"'/.]+)/g;
3381
- let match;
3382
- while ((match = pkgRegex.exec(content)) !== null) {
3383
- deps.push({ name: match[1] });
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 deps;
3761
+ return result;
3386
3762
  }
3387
- function parsePubspecYaml(content) {
3388
- const deps = [];
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 parseShardYml(content) {
3409
- const deps = [];
3410
- let inSection = false;
3411
- let depIndent = -1;
3412
- let current;
3413
- const flush = () => {
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
- flush();
3441
- return deps;
3442
- }
3443
- function parseXmlDependencyElements(content, elementRegex, nameAttr) {
3444
- const deps = [];
3445
- const nameRegex = new RegExp(`\\b${nameAttr}\\s*=\\s*["']([^"']+)["']`, "i");
3446
- const versionRegex = /\bversion\s*=\s*["']([^"']+)["']/i;
3447
- let match;
3448
- while ((match = elementRegex.exec(content)) !== null) {
3449
- const attrs = match[1];
3450
- const nameMatch = attrs.match(nameRegex);
3451
- if (!nameMatch) continue;
3452
- const versionMatch = attrs.match(versionRegex);
3453
- deps.push({ name: nameMatch[1].trim(), version: versionMatch?.[1]?.trim() });
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 deps;
3788
+ return [];
3456
3789
  }
3457
- function parseCsproj(content) {
3458
- return parseXmlDependencyElements(
3459
- content,
3460
- /<Package(?:Reference|Version)\s+([^>]+?)\/?>/gi,
3461
- "Include"
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 parsePackagesConfig(content) {
3465
- return parseXmlDependencyElements(content, /<package\s+([^>]+?)\/?>/gi, "id");
3809
+ function getKnownDisplayNames() {
3810
+ return new Set(Object.values(import_tooling6.DEPENDENCY_ENTITIES).map((entity) => entity.displayName));
3466
3811
  }
3467
- var MANIFEST_PARSERS = /* @__PURE__ */ new Map([
3468
- ["package.json", parsePackageJson],
3469
- ["requirements.txt", parseRequirementsTxt],
3470
- ["pyproject.toml", parsePyprojectToml],
3471
- ["Pipfile", parsePipfile],
3472
- ["Gemfile", parseGemfile],
3473
- ["go.mod", parseGoMod],
3474
- ["pom.xml", parsePomXml],
3475
- ["build.gradle", parseBuildGradle],
3476
- ["build.gradle.kts", parseBuildGradle],
3477
- ["libs.versions.toml", parseVersionCatalogToml],
3478
- ["setup.py", parseSetupPy],
3479
- ["build.sbt", parseBuildSbt],
3480
- ["Cargo.toml", parseCargoToml],
3481
- ["composer.json", parseComposerJson],
3482
- ["Podfile", parsePodfile],
3483
- ["Package.swift", parsePackageSwift],
3484
- ["pubspec.yaml", parsePubspecYaml],
3485
- ["mix.exs", parseMixExs],
3486
- ["shard.yml", parseShardYml],
3487
- ["packages.config", parsePackagesConfig],
3488
- ["Directory.Packages.props", parseCsproj]
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 MANIFEST_SUFFIX_PARSERS = /* @__PURE__ */ new Map([
3491
- [".csproj", parseCsproj],
3492
- [".vbproj", parseCsproj],
3493
- [".fsproj", parseCsproj]
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 MANIFEST_PATH_PARSERS = [
3496
- {
3497
- matches: (filePath, basename) => /^requirements[a-zA-Z0-9_.-]*\.(txt|in)$/i.test(basename) || // Any .txt/.in directly inside a `requirements/` directory: base.txt,
3498
- // dev.txt, prod.in — the other common split-layout convention.
3499
- /(^|\/)requirements\/[^/]+\.(txt|in)$/i.test(filePath),
3500
- parser: parseRequirementsTxt
3501
- },
3502
- {
3503
- // sbt build definition: `project/Dependencies.scala`, `project/plugins.sbt`
3504
- // and the meta-build's own `project/project/*`. Matching by directory rather
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 parser = resolveManifestParser(filePath);
3586
- if (!parser) continue;
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 basename = filePath.split("/").pop() ?? filePath;
3594
- const rawDeps = parser(content);
3595
- for (const raw of rawDeps) {
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
- displayName: classification.displayName,
3601
- kind: classification.kind,
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 (basename === "package.json") {
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.displayName)) {
3621
- seen.add(dep.displayName);
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(token) {
7175
- OpenAPI.HEADERS = { "api-token": token };
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(import_tooling5.LANGUAGE_CONFIG)) {
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, apiToken, onRemoteToolsFound, onToolProcessed, onIgnoredFilesFetched) {
7441
- configureApiToken(apiToken);
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.displayName);
7608
- for (const lib of discoveryResult.libraries) detectedKeys.add(lib.displayName);
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
- tools.push(toolEntry);
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 applyAdapterParameterSuggestions(adapter, toolEntry, repositoryRoot, repoFiles) {
7722
- if (!adapter.suggestParameters) return;
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 suggestions = await adapter.suggestParameters(ctx);
7748
- if (!suggestions) return;
7749
- for (const pattern of toolEntry.patterns) {
7750
- const params = suggestions[pattern.patternId];
7751
- if (!params) continue;
7752
- pattern.parameters = { ...pattern.parameters, ...params };
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("suggestParameters failed", {
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
- const ranked = [...defaultFamilyMembers.length > 0 ? defaultFamilyMembers : pool].sort(
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 = filters?.severities.length ? new Map(filters.severities.map((s) => [s.level, s.all])) : void 0;
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.languages.length > 0 && repoLanguages.size > 0) {
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 import_tooling6 = require("@codacy/tooling");
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,