@codacy/analysis-runner 0.17.0 → 0.19.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 +191 -35
  2. package/dist/index.js +1502 -860
  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,
@@ -76,9 +76,11 @@ __export(index_exports, {
76
76
  resolveApiToken: () => resolveApiToken,
77
77
  resolveBaselinePath: () => resolveBaselinePath,
78
78
  resolveConfigPath: () => resolveConfigPath,
79
+ resolveDiscoveryExcludes: () => resolveDiscoveryExcludes,
79
80
  resolveEngineToAdapter: () => resolveEngineToAdapter,
80
81
  resolveHeadCommit: () => resolveHeadCommit,
81
- resolveProxyConfig: () => import_tooling6.resolveProxyConfig,
82
+ resolveProxyConfig: () => import_tooling8.resolveProxyConfig,
83
+ resolveRemoteAuth: () => resolveRemoteAuth,
82
84
  resolveRepositoryRoot: () => resolveRepositoryRoot,
83
85
  resolveResultsApiBaseUrl: () => resolveResultsApiBaseUrl,
84
86
  rotateLogFiles: () => rotateLogFiles,
@@ -515,7 +517,7 @@ function isDependencyManifest(relativePath) {
515
517
  if (DEPENDENCY_MANIFEST_FILES.has(base)) return true;
516
518
  return DEPENDENCY_MANIFEST_SUFFIXES.some((suffix) => base.endsWith(suffix));
517
519
  }
518
- function listRepoFiles(repositoryRoot) {
520
+ function listRepoFiles(repositoryRoot, exclude) {
519
521
  try {
520
522
  const tracked = (0, import_child_process2.execSync)("git ls-files", {
521
523
  cwd: repositoryRoot,
@@ -534,7 +536,8 @@ function listRepoFiles(repositoryRoot) {
534
536
  for (const f of untracked.split("\n")) {
535
537
  if (f) files.add(f);
536
538
  }
537
- return Array.from(files).sort();
539
+ const all = Array.from(files).sort();
540
+ return exclude && exclude.length > 0 ? applyExclusions(all, [...exclude], []) : all;
538
541
  } catch {
539
542
  return [];
540
543
  }
@@ -1652,6 +1655,11 @@ function formatSarif(result) {
1652
1655
  ruleIndex: ruleIndex.get(issue.patternId) ?? 0,
1653
1656
  level: mapSeverityToSarif(issue.severity),
1654
1657
  message: { text: issue.message },
1658
+ // SARIF has no first-class field for a tool's own finding id, so it rides in
1659
+ // the result property bag. `upload` reads it back from here — without it the
1660
+ // SARIF path would silently drop `sourceId` and produce different Codacy
1661
+ // result UUIDs than the JSON path for the same analysis.
1662
+ ...issue.sourceId ? { properties: { sourceId: issue.sourceId } } : {},
1655
1663
  locations: [
1656
1664
  {
1657
1665
  physicalLocation: {
@@ -1716,7 +1724,8 @@ function formatContainer(result) {
1716
1724
  patternId: issue.patternId,
1717
1725
  line: issue.line,
1718
1726
  message: issue.message,
1719
- suggestion: issue.suggestion
1727
+ suggestion: issue.suggestion,
1728
+ sourceId: issue.sourceId
1720
1729
  })
1721
1730
  ).join("\n");
1722
1731
  }
@@ -2323,642 +2332,16 @@ var import_promises4 = __toESM(require("fs/promises"));
2323
2332
  var import_os3 = __toESM(require("os"));
2324
2333
  var import_path8 = __toESM(require("path"));
2325
2334
  var import_child_process4 = require("child_process");
2326
- var import_tooling5 = require("@codacy/tooling");
2335
+ var import_tooling7 = require("@codacy/tooling");
2327
2336
 
2328
- // src/discover.ts
2337
+ // src/discover/index.ts
2329
2338
  var import_promises2 = __toESM(require("fs/promises"));
2330
2339
  var import_path6 = __toESM(require("path"));
2331
2340
 
2332
- // src/discover-catalog.ts
2333
- var DEPENDENCY_CATALOG = /* @__PURE__ */ new Map([
2334
- // --- JavaScript / TypeScript — Frameworks ---
2335
- ["react", { displayName: "React", kind: "framework" }],
2336
- ["react-dom", { displayName: "React DOM", kind: "framework" }],
2337
- ["react-native", { displayName: "React Native", kind: "framework" }],
2338
- ["vue", { displayName: "Vue.js", kind: "framework" }],
2339
- ["nuxt", { displayName: "Nuxt", kind: "framework" }],
2340
- ["@angular/core", { displayName: "Angular", kind: "framework" }],
2341
- ["svelte", { displayName: "Svelte", kind: "framework" }],
2342
- ["@sveltejs/kit", { displayName: "SvelteKit", kind: "framework" }],
2343
- ["next", { displayName: "Next.js", kind: "framework" }],
2344
- ["ember-source", { displayName: "Ember.js", kind: "framework" }],
2345
- ["lit", { displayName: "Lit", kind: "framework" }],
2346
- ["solid-js", { displayName: "Solid", kind: "framework" }],
2347
- ["astro", { displayName: "Astro", kind: "framework" }],
2348
- ["@builder.io/qwik", { displayName: "Qwik", kind: "framework" }],
2349
- ["@builder.io/qwik-city", { displayName: "Qwik", kind: "framework" }],
2350
- ["@remix-run/react", { displayName: "Remix", kind: "framework" }],
2351
- ["gatsby", { displayName: "Gatsby", kind: "framework" }],
2352
- ["preact", { displayName: "Preact", kind: "framework" }],
2353
- ["express", { displayName: "Express", kind: "framework" }],
2354
- ["@nestjs/core", { displayName: "NestJS", kind: "framework" }],
2355
- ["fastify", { displayName: "Fastify", kind: "framework" }],
2356
- ["koa", { displayName: "Koa", kind: "framework" }],
2357
- ["@hapi/hapi", { displayName: "Hapi", kind: "framework" }],
2358
- ["hono", { displayName: "Hono", kind: "framework" }],
2359
- ["electron", { displayName: "Electron", kind: "framework" }],
2360
- // AWS CDK — the v2 umbrella package `aws-cdk-lib`, the CLI package `aws-cdk`,
2361
- // and the v1 `@aws-cdk/*` construct packages (prefix rule below).
2362
- ["aws-cdk-lib", { displayName: "AWS CDK", kind: "framework" }],
2363
- ["aws-cdk", { displayName: "AWS CDK", kind: "framework" }],
2364
- // --- JavaScript / TypeScript — Libraries ---
2365
- ["lodash", { displayName: "Lodash", kind: "library" }],
2366
- ["underscore", { displayName: "Underscore.js", kind: "library" }],
2367
- ["ramda", { displayName: "Ramda", kind: "library" }],
2368
- ["jest", { displayName: "Jest", kind: "library" }],
2369
- ["vitest", { displayName: "Vitest", kind: "library" }],
2370
- ["@playwright/test", { displayName: "Playwright", kind: "library" }],
2371
- ["playwright", { displayName: "Playwright", kind: "library" }],
2372
- ["cypress", { displayName: "Cypress", kind: "library" }],
2373
- ["mocha", { displayName: "Mocha", kind: "library" }],
2374
- ["ava", { displayName: "AVA", kind: "library" }],
2375
- ["jasmine", { displayName: "Jasmine", kind: "library" }],
2376
- ["jasmine-core", { displayName: "Jasmine", kind: "library" }],
2377
- ["@testing-library/react", { displayName: "Testing Library", kind: "library" }],
2378
- ["@testing-library/vue", { displayName: "Testing Library", kind: "library" }],
2379
- ["@testing-library/dom", { displayName: "Testing Library", kind: "library" }],
2380
- ["tailwindcss", { displayName: "Tailwind CSS", kind: "library" }],
2381
- ["@storybook/react", { displayName: "Storybook", kind: "library" }],
2382
- ["@storybook/vue3", { displayName: "Storybook", kind: "library" }],
2383
- ["@storybook/svelte", { displayName: "Storybook", kind: "library" }],
2384
- ["redux", { displayName: "Redux", kind: "library" }],
2385
- ["@reduxjs/toolkit", { displayName: "Redux Toolkit", kind: "library" }],
2386
- ["mobx", { displayName: "MobX", kind: "library" }],
2387
- ["zustand", { displayName: "Zustand", kind: "library" }],
2388
- ["axios", { displayName: "Axios", kind: "library" }],
2389
- ["date-fns", { displayName: "date-fns", kind: "library" }],
2390
- ["dayjs", { displayName: "Day.js", kind: "library" }],
2391
- ["moment", { displayName: "Moment.js", kind: "library" }],
2392
- ["rxjs", { displayName: "RxJS", kind: "library" }],
2393
- ["zod", { displayName: "Zod", kind: "library" }],
2394
- ["joi", { displayName: "Joi", kind: "library" }],
2395
- ["yup", { displayName: "Yup", kind: "library" }],
2396
- ["prisma", { displayName: "Prisma", kind: "library" }],
2397
- ["@prisma/client", { displayName: "Prisma", kind: "library" }],
2398
- ["typeorm", { displayName: "TypeORM", kind: "library" }],
2399
- ["sequelize", { displayName: "Sequelize", kind: "library" }],
2400
- ["drizzle-orm", { displayName: "Drizzle ORM", kind: "library" }],
2401
- ["mongoose", { displayName: "Mongoose", kind: "library" }],
2402
- ["graphql", { displayName: "GraphQL", kind: "library" }],
2403
- ["@apollo/client", { displayName: "Apollo Client", kind: "library" }],
2404
- ["socket.io", { displayName: "Socket.IO", kind: "library" }],
2405
- ["three", { displayName: "Three.js", kind: "library" }],
2406
- ["d3", { displayName: "D3.js", kind: "library" }],
2407
- ["chart.js", { displayName: "Chart.js", kind: "library" }],
2408
- ["sass", { displayName: "Sass", kind: "library" }],
2409
- ["node-sass", { displayName: "Sass", kind: "library" }],
2410
- ["less", { displayName: "Less", kind: "library" }],
2411
- ["styled-components", { displayName: "styled-components", kind: "library" }],
2412
- ["@emotion/react", { displayName: "Emotion", kind: "library" }],
2413
- ["pino", { displayName: "Pino", kind: "library" }],
2414
- ["winston", { displayName: "Winston", kind: "library" }],
2415
- ["i18next", { displayName: "i18next", kind: "library" }],
2416
- // react-intl is the React binding of the FormatJS ecosystem; eslint-plugin-formatjs
2417
- // lints both, so we report the umbrella name "formatjs" (matches the mapping key).
2418
- ["react-intl", { displayName: "formatjs", kind: "library" }],
2419
- ["vue-i18n", { displayName: "vue-i18n", kind: "library" }],
2420
- ["next-intl", { displayName: "next-intl", kind: "library" }],
2421
- // Type checker (eslint-plugin-flowtype). Also detected via a `.flowconfig` file.
2422
- ["flow-bin", { displayName: "Flow", kind: "library" }],
2423
- // Legacy JS libraries with dedicated ESLint plugins.
2424
- ["backbone", { displayName: "Backbone.js", kind: "library" }],
2425
- ["chai", { displayName: "Chai", kind: "library" }],
2426
- ["codeceptjs", { displayName: "CodeceptJS", kind: "library" }],
2427
- ["webdriverio", { displayName: "WebdriverIO", kind: "library" }],
2428
- ["react-relay", { displayName: "Relay", kind: "library" }],
2429
- ["relay-runtime", { displayName: "Relay", kind: "library" }],
2430
- // --- JavaScript / TypeScript — Tools ---
2431
- ["typescript", { displayName: "TypeScript", kind: "tool" }],
2432
- ["eslint", { displayName: "ESLint", kind: "tool" }],
2433
- ["prettier", { displayName: "Prettier", kind: "tool" }],
2434
- ["webpack", { displayName: "Webpack", kind: "tool" }],
2435
- ["vite", { displayName: "Vite", kind: "tool" }],
2436
- ["esbuild", { displayName: "esbuild", kind: "tool" }],
2437
- ["rollup", { displayName: "Rollup", kind: "tool" }],
2438
- ["turbo", { displayName: "Turborepo", kind: "tool" }],
2439
- ["tsup", { displayName: "tsup", kind: "tool" }],
2440
- ["@biomejs/biome", { displayName: "Biome", kind: "tool" }],
2441
- ["stylelint", { displayName: "Stylelint", kind: "tool" }],
2442
- // --- JavaScript / TypeScript — Platforms ---
2443
- ["@lwc/engine-dom", { displayName: "Lightning Web Components", kind: "platform" }],
2444
- ["@salesforce/core", { displayName: "Salesforce", kind: "platform" }],
2445
- ["@shopify/polaris", { displayName: "Shopify Polaris", kind: "platform" }],
2446
- // --- Python — Frameworks ---
2447
- ["django", { displayName: "Django", kind: "framework" }],
2448
- ["flask", { displayName: "Flask", kind: "framework" }],
2449
- ["fastapi", { displayName: "FastAPI", kind: "framework" }],
2450
- ["tornado", { displayName: "Tornado", kind: "framework" }],
2451
- ["pyramid", { displayName: "Pyramid", kind: "framework" }],
2452
- ["starlette", { displayName: "Starlette", kind: "framework" }],
2453
- ["sanic", { displayName: "Sanic", kind: "framework" }],
2454
- ["bottle", { displayName: "Bottle", kind: "framework" }],
2455
- ["streamlit", { displayName: "Streamlit", kind: "framework" }],
2456
- ["gradio", { displayName: "Gradio", kind: "framework" }],
2457
- ["apache-airflow", { displayName: "Airflow", kind: "framework" }],
2458
- // --- Python — Libraries ---
2459
- ["flask-babel", { displayName: "Flask-Babel", kind: "library" }],
2460
- ["pytest", { displayName: "pytest", kind: "library" }],
2461
- ["numpy", { displayName: "NumPy", kind: "library" }],
2462
- ["pandas", { displayName: "pandas", kind: "library" }],
2463
- ["requests", { displayName: "Requests", kind: "library" }],
2464
- ["sqlalchemy", { displayName: "SQLAlchemy", kind: "library" }],
2465
- ["celery", { displayName: "Celery", kind: "library" }],
2466
- ["pydantic", { displayName: "Pydantic", kind: "library" }],
2467
- ["boto3", { displayName: "Boto3 (AWS SDK)", kind: "library" }],
2468
- ["scipy", { displayName: "SciPy", kind: "library" }],
2469
- ["scikit-learn", { displayName: "scikit-learn", kind: "library" }],
2470
- ["tensorflow", { displayName: "TensorFlow", kind: "library" }],
2471
- ["torch", { displayName: "PyTorch", kind: "library" }],
2472
- ["httpx", { displayName: "HTTPX", kind: "library" }],
2473
- ["aiohttp", { displayName: "aiohttp", kind: "library" }],
2474
- ["pillow", { displayName: "Pillow", kind: "library" }],
2475
- ["beautifulsoup4", { displayName: "Beautiful Soup", kind: "library" }],
2476
- ["scrapy", { displayName: "Scrapy", kind: "library" }],
2477
- ["alembic", { displayName: "Alembic", kind: "library" }],
2478
- ["marshmallow", { displayName: "Marshmallow", kind: "library" }],
2479
- ["click", { displayName: "Click", kind: "library" }],
2480
- ["typer", { displayName: "Typer", kind: "library" }],
2481
- ["rich", { displayName: "Rich", kind: "library" }],
2482
- ["polars", { displayName: "Polars", kind: "library" }],
2483
- ["dask", { displayName: "Dask", kind: "library" }],
2484
- ["matplotlib", { displayName: "Matplotlib", kind: "library" }],
2485
- ["seaborn", { displayName: "Seaborn", kind: "library" }],
2486
- ["plotly", { displayName: "Plotly", kind: "library" }],
2487
- // --- Python — Tools ---
2488
- ["black", { displayName: "Black", kind: "tool" }],
2489
- ["ruff", { displayName: "Ruff", kind: "tool" }],
2490
- ["mypy", { displayName: "mypy", kind: "tool" }],
2491
- ["pylint", { displayName: "Pylint", kind: "tool" }],
2492
- ["flake8", { displayName: "Flake8", kind: "tool" }],
2493
- ["bandit", { displayName: "Bandit", kind: "tool" }],
2494
- ["isort", { displayName: "isort", kind: "tool" }],
2495
- // --- Ruby — Frameworks ---
2496
- ["rails", { displayName: "Ruby on Rails", kind: "framework" }],
2497
- ["sinatra", { displayName: "Sinatra", kind: "framework" }],
2498
- ["hanami", { displayName: "Hanami", kind: "framework" }],
2499
- ["grape", { displayName: "Grape", kind: "framework" }],
2500
- ["padrino", { displayName: "Padrino", kind: "framework" }],
2501
- // --- Ruby — Libraries ---
2502
- ["rspec", { displayName: "RSpec", kind: "library" }],
2503
- ["rspec-core", { displayName: "RSpec", kind: "library" }],
2504
- ["rspec-rails", { displayName: "RSpec Rails", kind: "library" }],
2505
- ["capybara", { displayName: "Capybara", kind: "library" }],
2506
- ["sidekiq", { displayName: "Sidekiq", kind: "library" }],
2507
- ["devise", { displayName: "Devise", kind: "library" }],
2508
- ["pundit", { displayName: "Pundit", kind: "library" }],
2509
- ["activeadmin", { displayName: "ActiveAdmin", kind: "library" }],
2510
- ["puma", { displayName: "Puma", kind: "library" }],
2511
- ["nokogiri", { displayName: "Nokogiri", kind: "library" }],
2512
- ["faraday", { displayName: "Faraday", kind: "library" }],
2513
- ["httparty", { displayName: "HTTParty", kind: "library" }],
2514
- ["minitest", { displayName: "Minitest", kind: "library" }],
2515
- ["factory_bot", { displayName: "FactoryBot", kind: "library" }],
2516
- ["dry-validation", { displayName: "dry-validation", kind: "library" }],
2517
- // --- Ruby — Tools ---
2518
- ["rubocop", { displayName: "RuboCop", kind: "tool" }],
2519
- ["bundler", { displayName: "Bundler", kind: "tool" }],
2520
- ["solargraph", { displayName: "Solargraph", kind: "tool" }],
2521
- // --- Java / Kotlin — Frameworks ---
2522
- ["spring-boot-starter", { displayName: "Spring Boot", kind: "framework" }],
2523
- ["spring-boot-starter-web", { displayName: "Spring Boot", kind: "framework" }],
2524
- ["spring-core", { displayName: "Spring Framework", kind: "framework" }],
2525
- ["quarkus-core", { displayName: "Quarkus", kind: "framework" }],
2526
- ["micronaut-core", { displayName: "Micronaut", kind: "framework" }],
2527
- ["ktor-server-core", { displayName: "Ktor", kind: "framework" }],
2528
- ["android", { displayName: "Android SDK", kind: "framework" }],
2529
- // --- Java / Kotlin — Libraries ---
2530
- ["junit-jupiter", { displayName: "JUnit 5", kind: "library" }],
2531
- ["junit", { displayName: "JUnit", kind: "library" }],
2532
- ["junit-jupiter-api", { displayName: "JUnit 5", kind: "library" }],
2533
- ["mockito-core", { displayName: "Mockito", kind: "library" }],
2534
- ["lombok", { displayName: "Lombok", kind: "library" }],
2535
- ["jackson-databind", { displayName: "Jackson", kind: "library" }],
2536
- ["jackson-core", { displayName: "Jackson", kind: "library" }],
2537
- ["hibernate-core", { displayName: "Hibernate", kind: "library" }],
2538
- ["guava", { displayName: "Guava", kind: "library" }],
2539
- ["commons-lang3", { displayName: "Apache Commons Lang", kind: "library" }],
2540
- ["commons-io", { displayName: "Apache Commons IO", kind: "library" }],
2541
- ["slf4j-api", { displayName: "SLF4J", kind: "library" }],
2542
- ["log4j-core", { displayName: "Log4j", kind: "library" }],
2543
- ["logback-classic", { displayName: "Logback", kind: "library" }],
2544
- ["testng", { displayName: "TestNG", kind: "library" }],
2545
- ["assertj-core", { displayName: "AssertJ", kind: "library" }],
2546
- ["gson", { displayName: "Gson", kind: "library" }],
2547
- ["okhttp", { displayName: "OkHttp", kind: "library" }],
2548
- ["retrofit", { displayName: "Retrofit", kind: "library" }],
2549
- ["spock-core", { displayName: "Spock", kind: "library" }],
2550
- // --- Groovy — Frameworks ---
2551
- // Grails/GORM artifact IDs vary (grails-core, grails-web, gorm-hibernate5, …);
2552
- // the `grails-`/`gorm-` prefix rules below catch the rest.
2553
- ["grails-core", { displayName: "Grails", kind: "framework" }],
2554
- ["gorm-core", { displayName: "GORM", kind: "library" }],
2555
- // --- Go — Frameworks ---
2556
- ["github.com/gin-gonic/gin", { displayName: "Gin", kind: "framework" }],
2557
- ["github.com/labstack/echo", { displayName: "Echo", kind: "framework" }],
2558
- ["github.com/gofiber/fiber", { displayName: "Fiber", kind: "framework" }],
2559
- ["github.com/go-chi/chi", { displayName: "Chi", kind: "framework" }],
2560
- ["github.com/gorilla/mux", { displayName: "Gorilla Mux", kind: "framework" }],
2561
- ["github.com/beego/beego", { displayName: "Beego", kind: "framework" }],
2562
- // --- Go — Libraries ---
2563
- ["github.com/stretchr/testify", { displayName: "Testify", kind: "library" }],
2564
- ["github.com/spf13/cobra", { displayName: "Cobra", kind: "library" }],
2565
- ["github.com/spf13/viper", { displayName: "Viper", kind: "library" }],
2566
- ["go.uber.org/zap", { displayName: "Zap", kind: "library" }],
2567
- ["github.com/sirupsen/logrus", { displayName: "Logrus", kind: "library" }],
2568
- ["gorm.io/gorm", { displayName: "GORM", kind: "library" }],
2569
- ["github.com/go-kit/kit", { displayName: "Go Kit", kind: "library" }],
2570
- ["google.golang.org/protobuf", { displayName: "Protocol Buffers", kind: "library" }],
2571
- ["google.golang.org/grpc", { displayName: "gRPC", kind: "library" }],
2572
- ["github.com/jackc/pgx", { displayName: "pgx", kind: "library" }],
2573
- ["github.com/redis/go-redis", { displayName: "go-redis", kind: "library" }],
2574
- ["github.com/aws/aws-sdk-go-v2", { displayName: "AWS SDK for Go", kind: "library" }],
2575
- ["github.com/prometheus/client_golang", { displayName: "Prometheus Client", kind: "library" }],
2576
- // --- Rust — Frameworks ---
2577
- ["actix-web", { displayName: "Actix Web", kind: "framework" }],
2578
- ["axum", { displayName: "Axum", kind: "framework" }],
2579
- ["rocket", { displayName: "Rocket", kind: "framework" }],
2580
- ["warp", { displayName: "Warp", kind: "framework" }],
2581
- ["tauri", { displayName: "Tauri", kind: "framework" }],
2582
- // --- Rust — Libraries ---
2583
- ["tokio", { displayName: "Tokio", kind: "library" }],
2584
- ["serde", { displayName: "Serde", kind: "library" }],
2585
- ["clap", { displayName: "clap", kind: "library" }],
2586
- ["reqwest", { displayName: "reqwest", kind: "library" }],
2587
- ["diesel", { displayName: "Diesel", kind: "library" }],
2588
- ["sqlx", { displayName: "SQLx", kind: "library" }],
2589
- ["tracing", { displayName: "Tracing", kind: "library" }],
2590
- ["anyhow", { displayName: "anyhow", kind: "library" }],
2591
- ["thiserror", { displayName: "thiserror", kind: "library" }],
2592
- ["rayon", { displayName: "Rayon", kind: "library" }],
2593
- // --- PHP — Frameworks ---
2594
- ["laravel/framework", { displayName: "Laravel", kind: "framework" }],
2595
- ["symfony/framework-bundle", { displayName: "Symfony", kind: "framework" }],
2596
- ["symfony/symfony", { displayName: "Symfony", kind: "framework" }],
2597
- ["codeigniter4/framework", { displayName: "CodeIgniter", kind: "framework" }],
2598
- ["slim/slim", { displayName: "Slim", kind: "framework" }],
2599
- ["cakephp/cakephp", { displayName: "CakePHP", kind: "framework" }],
2600
- // WordPress is rarely a real Composer dependency; these are the common
2601
- // "WordPress as a dependency" packages used by Bedrock/Composer-based sites.
2602
- ["johnpbloch/wordpress-core", { displayName: "WordPress", kind: "framework" }],
2603
- ["johnpbloch/wordpress", { displayName: "WordPress", kind: "framework" }],
2604
- ["roots/wordpress", { displayName: "WordPress", kind: "framework" }],
2605
- ["drupal/core", { displayName: "Drupal", kind: "framework" }],
2606
- ["drupal/core-recommended", { displayName: "Drupal", kind: "framework" }],
2607
- ["magento/product-community-edition", { displayName: "Magento", kind: "framework" }],
2608
- ["magento/product-enterprise-edition", { displayName: "Magento", kind: "framework" }],
2609
- ["magento/framework", { displayName: "Magento", kind: "framework" }],
2610
- // --- PHP — Libraries ---
2611
- ["phpunit/phpunit", { displayName: "PHPUnit", kind: "library" }],
2612
- ["guzzlehttp/guzzle", { displayName: "Guzzle", kind: "library" }],
2613
- ["monolog/monolog", { displayName: "Monolog", kind: "library" }],
2614
- ["nesbot/carbon", { displayName: "Carbon", kind: "library" }],
2615
- ["doctrine/orm", { displayName: "Doctrine ORM", kind: "library" }],
2616
- ["doctrine/dbal", { displayName: "Doctrine DBAL", kind: "library" }],
2617
- ["league/flysystem", { displayName: "Flysystem", kind: "library" }],
2618
- ["php-amqplib/php-amqplib", { displayName: "php-amqplib", kind: "library" }],
2619
- ["predis/predis", { displayName: "Predis", kind: "library" }],
2620
- // --- PHP — Tools ---
2621
- ["phpstan/phpstan", { displayName: "PHPStan", kind: "tool" }],
2622
- ["squizlabs/php_codesniffer", { displayName: "PHP_CodeSniffer", kind: "tool" }],
2623
- // --- Swift — Frameworks ---
2624
- ["vapor", { displayName: "Vapor", kind: "framework" }],
2625
- // --- Swift — Libraries ---
2626
- ["Alamofire", { displayName: "Alamofire", kind: "library" }],
2627
- ["SnapKit", { displayName: "SnapKit", kind: "library" }],
2628
- ["Kingfisher", { displayName: "Kingfisher", kind: "library" }],
2629
- ["Moya", { displayName: "Moya", kind: "library" }],
2630
- ["RxSwift", { displayName: "RxSwift", kind: "library" }],
2631
- ["RealmSwift", { displayName: "Realm", kind: "library" }],
2632
- // --- Scala — Frameworks ---
2633
- ["play-server", { displayName: "Play Framework", kind: "framework" }],
2634
- ["akka-actor", { displayName: "Akka", kind: "framework" }],
2635
- ["spray-can", { displayName: "Spray", kind: "framework" }],
2636
- // --- Scala — Libraries ---
2637
- ["scalatest", { displayName: "ScalaTest", kind: "library" }],
2638
- ["specs2-core", { displayName: "Specs2", kind: "library" }],
2639
- ["cats-core", { displayName: "Cats", kind: "library" }],
2640
- ["circe-core", { displayName: "Circe", kind: "library" }],
2641
- // --- Elixir — Frameworks ---
2642
- ["phoenix", { displayName: "Phoenix", kind: "framework" }],
2643
- ["plug", { displayName: "Plug", kind: "framework" }],
2644
- ["plug_cowboy", { displayName: "Plug", kind: "framework" }],
2645
- // --- Elixir — Libraries ---
2646
- ["ecto", { displayName: "Ecto", kind: "library" }],
2647
- ["ecto_sql", { displayName: "Ecto", kind: "library" }],
2648
- ["phoenix_ecto", { displayName: "Ecto", kind: "library" }],
2649
- // --- Crystal — Frameworks ---
2650
- ["amber", { displayName: "Amber", kind: "framework" }],
2651
- ["lucky", { displayName: "Lucky", kind: "framework" }],
2652
- ["kemal", { displayName: "Kemal", kind: "framework" }],
2653
- // --- Dart — Frameworks ---
2654
- // `flutter` and `angular` appear as plain keys in pubspec.yaml. NOTE: `angular`
2655
- // here is Dart's AngularDart. The npm package `angular` is AngularJS (1.x) — a
2656
- // different framework — so package.json overrides this via MANIFEST_SCOPED_CATALOG.
2657
- ["flutter", { displayName: "Flutter", kind: "framework" }],
2658
- ["flutter_test", { displayName: "Flutter", kind: "framework" }],
2659
- ["angular", { displayName: "Angular", kind: "framework" }],
2660
- // --- Dart — Libraries ---
2661
- // Kept to Dart-specific names with negligible cross-ecosystem collision risk
2662
- // (generic names like "get"/"provider" are intentionally omitted).
2663
- ["riverpod", { displayName: "Riverpod", kind: "library" }],
2664
- ["flutter_bloc", { displayName: "Bloc", kind: "library" }],
2665
- ["dio", { displayName: "Dio", kind: "library" }],
2666
- // --- .NET (C# / VB) — Libraries ---
2667
- // NuGet package IDs. Microsoft.* families are also covered by prefix rules below.
2668
- ["EntityFramework", { displayName: "Entity Framework", kind: "library" }],
2669
- ["NUnit", { displayName: "NUnit", kind: "library" }],
2670
- ["Moq", { displayName: "Moq", kind: "library" }],
2671
- ["NSubstitute", { displayName: "NSubstitute", kind: "library" }],
2672
- ["FluentAssertions", { displayName: "FluentAssertions", kind: "library" }],
2673
- ["Shouldly", { displayName: "Shouldly", kind: "library" }],
2674
- ["NLog", { displayName: "NLog", kind: "library" }],
2675
- ["log4net", { displayName: "log4net", kind: "library" }],
2676
- ["Newtonsoft.Json", { displayName: "Json.NET", kind: "library" }],
2677
- ["AutoMapper", { displayName: "AutoMapper", kind: "library" }],
2678
- ["Dapper", { displayName: "Dapper", kind: "library" }],
2679
- ["MediatR", { displayName: "MediatR", kind: "library" }]
2680
- ]);
2681
- var DEPENDENCY_PREFIX_RULES = [
2682
- { prefix: "@angular/", classification: { displayName: "Angular", kind: "framework" } },
2683
- { prefix: "@nestjs/", classification: { displayName: "NestJS", kind: "framework" } },
2684
- { prefix: "@remix-run/", classification: { displayName: "Remix", kind: "framework" } },
2685
- { prefix: "@sveltejs/", classification: { displayName: "SvelteKit", kind: "framework" } },
2686
- { prefix: "@nuxt/", classification: { displayName: "Nuxt", kind: "framework" } },
2687
- { prefix: "@storybook/", classification: { displayName: "Storybook", kind: "library" } },
2688
- {
2689
- prefix: "@testing-library/",
2690
- classification: { displayName: "Testing Library", kind: "library" }
2691
- },
2692
- { prefix: "@tanstack/", classification: { displayName: "TanStack", kind: "library" } },
2693
- { prefix: "@wdio/", classification: { displayName: "WebdriverIO", kind: "library" } },
2694
- // AWS CDK v1 construct libraries are published under the @aws-cdk/ scope.
2695
- { prefix: "@aws-cdk/", classification: { displayName: "AWS CDK", kind: "framework" } },
2696
- { prefix: "@reduxjs/", classification: { displayName: "Redux", kind: "library" } },
2697
- { prefix: "@prisma/", classification: { displayName: "Prisma", kind: "library" } },
2698
- { prefix: "@apollo/", classification: { displayName: "Apollo", kind: "library" } },
2699
- { prefix: "@formatjs/", classification: { displayName: "formatjs", kind: "library" } },
2700
- { prefix: "@lingui/", classification: { displayName: "Lingui", kind: "library" } },
2701
- { prefix: "@emotion/", classification: { displayName: "Emotion", kind: "library" } },
2702
- { prefix: "@mui/", classification: { displayName: "Material UI", kind: "library" } },
2703
- { prefix: "@chakra-ui/", classification: { displayName: "Chakra UI", kind: "library" } },
2704
- {
2705
- prefix: "@lwc/",
2706
- classification: { displayName: "Lightning Web Components", kind: "platform" }
2707
- },
2708
- { prefix: "@salesforce/", classification: { displayName: "Salesforce", kind: "platform" } },
2709
- { prefix: "@shopify/", classification: { displayName: "Shopify", kind: "platform" } },
2710
- { prefix: "@aws-sdk/", classification: { displayName: "AWS SDK", kind: "library" } },
2711
- { prefix: "@google-cloud/", classification: { displayName: "Google Cloud", kind: "library" } },
2712
- { prefix: "@azure/", classification: { displayName: "Azure SDK", kind: "library" } },
2713
- // Groovy — Grails/GORM artifact families (build.gradle artifactIds).
2714
- { prefix: "grails-", classification: { displayName: "Grails", kind: "framework" } },
2715
- { prefix: "gorm-", classification: { displayName: "GORM", kind: "library" } },
2716
- // .NET — Microsoft package families and common test/logging libraries. More
2717
- // specific prefixes (Blazor) must precede broader ones (ASP.NET Core).
2718
- {
2719
- prefix: "Microsoft.AspNetCore.Components.WebAssembly",
2720
- classification: { displayName: "Blazor", kind: "framework" }
2721
- },
2722
- {
2723
- prefix: "Microsoft.AspNetCore.",
2724
- classification: { displayName: "ASP.NET Core", kind: "framework" }
2725
- },
2726
- {
2727
- prefix: "Microsoft.EntityFrameworkCore",
2728
- classification: { displayName: "Entity Framework", kind: "library" }
2729
- },
2730
- {
2731
- prefix: "Microsoft.Azure.Functions",
2732
- classification: { displayName: "Azure Functions", kind: "framework" }
2733
- },
2734
- {
2735
- prefix: "Microsoft.Azure.WebJobs",
2736
- classification: { displayName: "Azure Functions", kind: "framework" }
2737
- },
2738
- { prefix: "xunit", classification: { displayName: "xUnit", kind: "library" } },
2739
- { prefix: "MSTest.", classification: { displayName: "MSTest", kind: "library" } },
2740
- { prefix: "Serilog", classification: { displayName: "Serilog", kind: "library" } }
2741
- ];
2742
- var MANIFEST_SCOPED_CATALOG = /* @__PURE__ */ new Map([
2743
- ["package.json", /* @__PURE__ */ new Map([["angular", { displayName: "AngularJS", kind: "framework" }]])]
2744
- ]);
2745
- function classifyDependency(name, manifestBasename) {
2746
- if (manifestBasename) {
2747
- const scoped = MANIFEST_SCOPED_CATALOG.get(manifestBasename)?.get(name);
2748
- if (scoped) return scoped;
2749
- }
2750
- const exact = DEPENDENCY_CATALOG.get(name);
2751
- if (exact) return exact;
2752
- for (const rule of DEPENDENCY_PREFIX_RULES) {
2753
- if (name.startsWith(rule.prefix)) return rule.classification;
2754
- }
2755
- return void 0;
2756
- }
2757
- function pathBasename(filePath) {
2758
- return filePath.split("/").pop() ?? filePath;
2759
- }
2760
- var FILE_SIGNAL_RULES = [
2761
- {
2762
- name: ".flowconfig",
2763
- matches: (f) => pathBasename(f) === ".flowconfig",
2764
- classification: { displayName: "Flow", kind: "library" }
2765
- },
2766
- {
2767
- name: "cdk.json",
2768
- matches: (f) => pathBasename(f) === "cdk.json",
2769
- classification: { displayName: "AWS CDK", kind: "framework" }
2770
- },
2771
- {
2772
- name: ".meteor/release",
2773
- matches: (f) => f === ".meteor/release" || f.endsWith("/.meteor/release"),
2774
- classification: { displayName: "Meteor", kind: "framework" }
2775
- },
2776
- {
2777
- name: "sfdx-project.json",
2778
- matches: (f) => pathBasename(f) === "sfdx-project.json",
2779
- classification: { displayName: "Salesforce", kind: "platform" }
2780
- },
2781
- {
2782
- name: "lwc.config.json",
2783
- matches: (f) => pathBasename(f) === "lwc.config.json",
2784
- classification: { displayName: "Lightning Web Components", kind: "platform" }
2785
- },
2786
- {
2787
- name: "suitecloud.config.js",
2788
- matches: (f) => pathBasename(f) === "suitecloud.config.js",
2789
- classification: { displayName: "SuiteScript", kind: "platform" }
2790
- },
2791
- {
2792
- name: ".github/workflows",
2793
- matches: (f) => f.startsWith(".github/workflows/"),
2794
- classification: { displayName: "GitHub Actions", kind: "framework" }
2795
- },
2796
- {
2797
- name: ".circleci",
2798
- matches: (f) => f.startsWith(".circleci/"),
2799
- classification: { displayName: "CircleCI", kind: "framework" }
2800
- },
2801
- {
2802
- name: ".gitlab-ci.yml",
2803
- matches: (f) => f === ".gitlab-ci.yml" || f.startsWith(".gitlab/"),
2804
- classification: { displayName: "GitLab CI", kind: "framework" }
2805
- },
2806
- {
2807
- name: "bitbucket-pipelines.yml",
2808
- matches: (f) => f === "bitbucket-pipelines.yml",
2809
- classification: { displayName: "Bitbucket Pipelines", kind: "framework" }
2810
- },
2811
- {
2812
- name: "azure-pipelines.yml",
2813
- matches: (f) => f === "azure-pipelines.yml" || f === "azure-pipelines.yaml",
2814
- classification: { displayName: "Azure Pipelines", kind: "framework" }
2815
- }
2816
- ];
2817
- function detectFileSignals(files) {
2818
- const result = [];
2819
- for (const rule of FILE_SIGNAL_RULES) {
2820
- const hit = files.find((f) => rule.matches(f));
2821
- if (!hit) continue;
2822
- result.push({
2823
- name: rule.name,
2824
- displayName: rule.classification.displayName,
2825
- kind: rule.classification.kind,
2826
- source: "file",
2827
- manifestFile: hit
2828
- });
2829
- }
2830
- return result;
2831
- }
2832
- var PACKAGE_JSON_SIGNAL_DISPLAY_NAMES = ["ESLint Plugin"];
2833
- function isEslintPluginName(name) {
2834
- return /^eslint-plugin-/.test(name) || /^@[^/]+\/eslint-plugin(-.*)?$/.test(name);
2835
- }
2836
- function detectPackageJsonSignals(content) {
2837
- let pkg;
2838
- try {
2839
- pkg = JSON.parse(content);
2840
- } catch {
2841
- return [];
2842
- }
2843
- const name = pkg && typeof pkg.name === "string" ? pkg.name : "";
2844
- if (name && isEslintPluginName(name)) {
2845
- return [
2846
- {
2847
- name,
2848
- displayName: "ESLint Plugin",
2849
- kind: "tool",
2850
- source: "manifest",
2851
- manifestFile: "package.json"
2852
- }
2853
- ];
2854
- }
2855
- return [];
2856
- }
2857
- function getKnownDisplayNames() {
2858
- const names = /* @__PURE__ */ new Set();
2859
- for (const { displayName } of DEPENDENCY_CATALOG.values()) names.add(displayName);
2860
- for (const scoped of MANIFEST_SCOPED_CATALOG.values())
2861
- for (const { displayName } of scoped.values()) names.add(displayName);
2862
- for (const rule of DEPENDENCY_PREFIX_RULES) names.add(rule.classification.displayName);
2863
- for (const rule of FILE_SIGNAL_RULES) names.add(rule.classification.displayName);
2864
- for (const name of PACKAGE_JSON_SIGNAL_DISPLAY_NAMES) names.add(name);
2865
- return names;
2866
- }
2867
- var NOTABLE_ANY_DEPTH = /* @__PURE__ */ new Set([
2868
- "Dockerfile",
2869
- "docker-compose.yml",
2870
- "docker-compose.yaml",
2871
- "compose.yml",
2872
- "compose.yaml",
2873
- "Makefile",
2874
- "Justfile",
2875
- "pom.xml",
2876
- "build.gradle",
2877
- "build.gradle.kts",
2878
- "build.sbt",
2879
- "go.mod",
2880
- "Cargo.toml",
2881
- "Gemfile",
2882
- "Podfile",
2883
- "Package.swift",
2884
- "Jenkinsfile",
2885
- "Vagrantfile",
2886
- "Procfile",
2887
- "pubspec.yaml",
2888
- "mix.exs",
2889
- "shard.yml",
2890
- "packages.config",
2891
- "Directory.Packages.props"
2892
- ]);
2893
- var NOTABLE_ROOT_ONLY = /* @__PURE__ */ new Set([
2894
- ".dockerignore",
2895
- "Taskfile.yml",
2896
- "Rakefile",
2897
- "Gruntfile.js",
2898
- "Gulpfile.js",
2899
- "settings.gradle",
2900
- "settings.gradle.kts",
2901
- "go.sum",
2902
- "Cargo.lock",
2903
- "CLAUDE.md",
2904
- "AGENTS.md",
2905
- "agents.md",
2906
- ".cursorrules",
2907
- "copilot-instructions.md",
2908
- ".travis.yml",
2909
- ".gitlab-ci.yml",
2910
- "azure-pipelines.yml",
2911
- "bitbucket-pipelines.yml",
2912
- "appveyor.yml",
2913
- "package.json",
2914
- "requirements.txt",
2915
- "pyproject.toml",
2916
- "Pipfile",
2917
- "composer.json",
2918
- "tsconfig.json",
2919
- "jsconfig.json",
2920
- "serverless.yml",
2921
- "serverless.yaml",
2922
- "cdk.json",
2923
- "pulumi.yaml",
2924
- "README.md",
2925
- "CHANGELOG.md",
2926
- "CONTRIBUTING.md",
2927
- "LICENSE",
2928
- "renovate.json",
2929
- ".nvmrc",
2930
- ".node-version",
2931
- ".python-version",
2932
- ".ruby-version",
2933
- ".tool-versions"
2934
- ]);
2935
- var NOTABLE_PATH_PREFIXES = [".github/workflows/", ".circleci/", ".gitlab/"];
2936
- var CONFIG_HEURISTIC_PATTERNS = [
2937
- /^\..*rc$/,
2938
- // .eslintrc, .prettierrc, etc.
2939
- /^\..*rc\.(json|js|cjs|mjs|yml|yaml|ts)$/,
2940
- // .eslintrc.json, etc.
2941
- /^.*\.config\.(js|ts|mjs|cjs)$/,
2942
- // vite.config.ts, jest.config.js, etc.
2943
- /^\.env(\..+)?$/
2944
- // .env, .env.local, .env.production
2945
- ];
2946
- function isNotableFile(filePath) {
2947
- const basename = filePath.split("/").pop() ?? filePath;
2948
- const isRoot = !filePath.includes("/");
2949
- if (NOTABLE_ANY_DEPTH.has(basename)) return true;
2950
- if (isRoot && NOTABLE_ROOT_ONLY.has(basename)) return true;
2951
- if (/\.(csproj|vbproj|fsproj|sln)$/i.test(basename)) return true;
2952
- for (const prefix of NOTABLE_PATH_PREFIXES) {
2953
- if (filePath.startsWith(prefix)) return true;
2954
- }
2955
- if (!filePath.includes("/")) {
2956
- for (const pattern of CONFIG_HEURISTIC_PATTERNS) {
2957
- if (pattern.test(basename)) return true;
2958
- }
2959
- }
2960
- return false;
2961
- }
2341
+ // src/discover/classify.ts
2342
+ var import_tooling6 = require("@codacy/tooling");
2343
+
2344
+ // src/discover/ecosystems/npm.ts
2962
2345
  function parsePackageJson(content) {
2963
2346
  try {
2964
2347
  const pkg = JSON.parse(content);
@@ -2976,18 +2359,236 @@ function parsePackageJson(content) {
2976
2359
  return [];
2977
2360
  }
2978
2361
  }
2362
+ var TELLTALES = [
2363
+ // --- Frameworks ---
2364
+ ["react", "react"],
2365
+ ["react-dom", "react-dom"],
2366
+ ["react-native", "react-native"],
2367
+ ["vue", "vue"],
2368
+ ["nuxt", "nuxt"],
2369
+ ["@angular/core", "angular"],
2370
+ ["svelte", "svelte"],
2371
+ ["@sveltejs/kit", "sveltekit"],
2372
+ ["next", "next"],
2373
+ ["ember-source", "ember"],
2374
+ ["lit", "lit"],
2375
+ ["solid-js", "solid"],
2376
+ ["astro", "astro"],
2377
+ ["@builder.io/qwik", "qwik"],
2378
+ ["@builder.io/qwik-city", "qwik"],
2379
+ ["@remix-run/react", "remix"],
2380
+ ["gatsby", "gatsby"],
2381
+ ["preact", "preact"],
2382
+ ["express", "express"],
2383
+ ["@nestjs/core", "nestjs"],
2384
+ ["fastify", "fastify"],
2385
+ ["koa", "koa"],
2386
+ ["@hapi/hapi", "hapi"],
2387
+ ["hono", "hono"],
2388
+ ["electron", "electron"],
2389
+ // AWS CDK — the v2 umbrella package `aws-cdk-lib`, the CLI package `aws-cdk`,
2390
+ // and the v1 `@aws-cdk/*` construct packages (a prefix rule).
2391
+ ["aws-cdk-lib", "aws-cdk"],
2392
+ ["aws-cdk", "aws-cdk"],
2393
+ // The v2 SDK is one flat package; v3 is the scoped `@aws-sdk/*` family, which a
2394
+ // prefix rule below covers. Both mean "this code calls AWS".
2395
+ ["aws-sdk", "aws-sdk"],
2396
+ // --- Libraries ---
2397
+ ["lodash", "lodash"],
2398
+ ["underscore", "underscore"],
2399
+ ["ramda", "ramda"],
2400
+ ["jest", "jest"],
2401
+ ["vitest", "vitest"],
2402
+ ["@playwright/test", "playwright"],
2403
+ ["playwright", "playwright"],
2404
+ ["puppeteer", "puppeteer"],
2405
+ ["puppeteer-core", "puppeteer"],
2406
+ ["cypress", "cypress"],
2407
+ ["mocha", "mocha"],
2408
+ ["ava", "ava"],
2409
+ ["jasmine", "jasmine"],
2410
+ ["jasmine-core", "jasmine"],
2411
+ ["@testing-library/react", "testing-library"],
2412
+ ["@testing-library/vue", "testing-library"],
2413
+ ["@testing-library/dom", "testing-library"],
2414
+ ["tailwindcss", "tailwind-css"],
2415
+ ["@storybook/react", "storybook"],
2416
+ ["@storybook/vue3", "storybook"],
2417
+ ["@storybook/svelte", "storybook"],
2418
+ ["redux", "redux"],
2419
+ ["@reduxjs/toolkit", "redux-toolkit"],
2420
+ ["mobx", "mobx"],
2421
+ ["zustand", "zustand"],
2422
+ ["axios", "axios"],
2423
+ ["date-fns", "date-fns"],
2424
+ ["dayjs", "dayjs"],
2425
+ ["moment", "moment"],
2426
+ ["rxjs", "rxjs"],
2427
+ ["zod", "zod"],
2428
+ ["joi", "joi"],
2429
+ ["yup", "yup"],
2430
+ ["prisma", "prisma"],
2431
+ ["@prisma/client", "prisma"],
2432
+ ["typeorm", "typeorm"],
2433
+ ["sequelize", "sequelize"],
2434
+ ["drizzle-orm", "drizzle-orm"],
2435
+ ["mongoose", "mongoose"],
2436
+ ["graphql", "graphql"],
2437
+ ["@apollo/client", "apollo-client"],
2438
+ ["socket.io", "socketio"],
2439
+ ["jquery", "jquery"],
2440
+ ["mongodb", "mongodb"],
2441
+ ["three", "threejs"],
2442
+ ["d3", "d3"],
2443
+ ["chart.js", "chartjs"],
2444
+ ["sass", "sass"],
2445
+ ["node-sass", "sass"],
2446
+ ["less", "less"],
2447
+ ["styled-components", "styled-components"],
2448
+ ["@emotion/react", "emotion"],
2449
+ ["pino", "pino"],
2450
+ ["winston", "winston"],
2451
+ ["i18next", "i18next"],
2452
+ // react-intl is the React binding of the FormatJS ecosystem; eslint-plugin-formatjs
2453
+ // lints both, so we report the umbrella name "formatjs" (matches the mapping key).
2454
+ ["react-intl", "formatjs"],
2455
+ ["vue-i18n", "vue-i18n"],
2456
+ ["next-intl", "next-intl"],
2457
+ // Type checker (eslint-plugin-flowtype). Also detected via a `.flowconfig` file.
2458
+ ["flow-bin", "flow"],
2459
+ // Legacy JS libraries with dedicated ESLint plugins.
2460
+ ["backbone", "backbone"],
2461
+ ["chai", "chai"],
2462
+ ["codeceptjs", "codeceptjs"],
2463
+ ["webdriverio", "webdriverio"],
2464
+ ["react-relay", "relay"],
2465
+ ["relay-runtime", "relay"],
2466
+ // --- Tools ---
2467
+ ["typescript", "typescript"],
2468
+ ["eslint", "eslint"],
2469
+ ["prettier", "prettier"],
2470
+ ["webpack", "webpack"],
2471
+ ["vite", "vite"],
2472
+ ["esbuild", "esbuild"],
2473
+ ["rollup", "rollup"],
2474
+ ["turbo", "turborepo"],
2475
+ ["tsup", "tsup"],
2476
+ ["@biomejs/biome", "biome"],
2477
+ ["stylelint", "stylelint"],
2478
+ // --- Platforms ---
2479
+ ["@lwc/engine-dom", "lightning-web-components"],
2480
+ ["@salesforce/core", "salesforce"],
2481
+ ["@shopify/polaris", "shopify-polaris"],
2482
+ // The npm `angular` package is AngularJS 1.x — a different framework from both
2483
+ // modern Angular (the `@angular/*` scope) and pub's AngularDart. One global
2484
+ // name-keyed catalog could not hold all three; three entities can.
2485
+ ["angular", "angular-js"]
2486
+ ];
2487
+ var PREFIXES = [
2488
+ ["@angular/", "angular"],
2489
+ ["@nestjs/", "nestjs"],
2490
+ ["@remix-run/", "remix"],
2491
+ ["@sveltejs/", "sveltekit"],
2492
+ ["@nuxt/", "nuxt"],
2493
+ ["@storybook/", "storybook"],
2494
+ ["@testing-library/", "testing-library"],
2495
+ ["@tanstack/", "tanstack"],
2496
+ ["@wdio/", "webdriverio"],
2497
+ // AWS CDK v1 construct libraries are published under the @aws-cdk/ scope.
2498
+ ["@aws-cdk/", "aws-cdk"],
2499
+ ["@reduxjs/", "redux"],
2500
+ ["@prisma/", "prisma"],
2501
+ ["@apollo/", "apollo"],
2502
+ ["@formatjs/", "formatjs"],
2503
+ ["@lingui/", "lingui"],
2504
+ ["@emotion/", "emotion"],
2505
+ ["@mui/", "material-ui"],
2506
+ ["@chakra-ui/", "chakra-ui"],
2507
+ ["@lwc/", "lightning-web-components"],
2508
+ ["@salesforce/", "salesforce"],
2509
+ ["@shopify/", "shopify"],
2510
+ ["@aws-sdk/", "aws-sdk"],
2511
+ ["@google-cloud/", "google-cloud"],
2512
+ ["@azure/", "azure-sdk"]
2513
+ ];
2514
+ var npm = {
2515
+ id: "npm",
2516
+ manifests: [{ basename: "package.json", parser: parsePackageJson }],
2517
+ telltales: TELLTALES,
2518
+ prefixes: PREFIXES
2519
+ };
2520
+
2521
+ // src/discover/ecosystems/pypi.ts
2522
+ function parseRequirementLine(rawLine) {
2523
+ const line = rawLine.trim();
2524
+ if (!line || line.startsWith("#") || line.startsWith("-")) return void 0;
2525
+ const noComment = line.split("#")[0].trim();
2526
+ if (!noComment) return void 0;
2527
+ const match = noComment.match(/^([a-zA-Z0-9_.-]+(?:\[[^\]]*\])?)\s*([<>=!~@;].*)?$/);
2528
+ if (!match) return void 0;
2529
+ const rawName = match[1].replace(/\[.*\]$/, "");
2530
+ return { name: rawName.toLowerCase().replace(/_/g, "-"), version: match[2]?.trim() };
2531
+ }
2979
2532
  function parseRequirementsTxt(content) {
2980
2533
  const deps = [];
2981
2534
  for (const rawLine of content.split("\n")) {
2982
- const line = rawLine.trim();
2983
- if (!line || line.startsWith("#") || line.startsWith("-")) continue;
2984
- const noComment = line.split("#")[0].trim();
2985
- if (!noComment) continue;
2986
- const match = noComment.match(/^([a-zA-Z0-9_.-]+(?:\[[^\]]*\])?)\s*([<>=!~@;].*)?$/);
2987
- if (match) {
2988
- const rawName = match[1].replace(/\[.*\]$/, "");
2989
- const normalized = rawName.toLowerCase().replace(/_/g, "-");
2990
- deps.push({ name: normalized, version: match[2]?.trim() });
2535
+ const dep = parseRequirementLine(rawLine);
2536
+ if (dep) deps.push(dep);
2537
+ }
2538
+ return deps;
2539
+ }
2540
+ function sliceBalanced(content, fromIndex, open, close) {
2541
+ const start = content.indexOf(open, fromIndex);
2542
+ if (start === -1) return void 0;
2543
+ let depth = 0;
2544
+ let quote;
2545
+ for (let i = start; i < content.length; i++) {
2546
+ const ch = content[i];
2547
+ if (quote) {
2548
+ if (ch === "\\") i++;
2549
+ else if (ch === quote) quote = void 0;
2550
+ continue;
2551
+ }
2552
+ if (ch === '"' || ch === "'") {
2553
+ quote = ch;
2554
+ continue;
2555
+ }
2556
+ if (ch === open) depth++;
2557
+ else if (ch === close && --depth === 0) {
2558
+ return { body: content.slice(start + 1, i), endIndex: i };
2559
+ }
2560
+ }
2561
+ return void 0;
2562
+ }
2563
+ function parseSetupPy(content) {
2564
+ const deps = [];
2565
+ const collectQuoted = (block) => {
2566
+ for (const quoted of block.matchAll(/["']([^"']+)["']/g)) {
2567
+ const dep = parseRequirementLine(quoted[1]);
2568
+ if (dep) deps.push(dep);
2569
+ }
2570
+ };
2571
+ const valueStartOf = (key) => {
2572
+ const match = new RegExp(`\\b${key}\\s*=\\s*`).exec(content);
2573
+ return match ? match.index + match[0].length : -1;
2574
+ };
2575
+ for (const key of ["install_requires", "tests_require", "setup_requires"]) {
2576
+ const start = valueStartOf(key);
2577
+ if (start === -1 || content[start] !== "[") continue;
2578
+ const list = sliceBalanced(content, start, "[", "]");
2579
+ if (list) collectQuoted(list.body);
2580
+ }
2581
+ const extrasStart = valueStartOf("extras_require");
2582
+ if (extrasStart !== -1 && content[extrasStart] === "{") {
2583
+ const dict = sliceBalanced(content, extrasStart, "{", "}");
2584
+ if (dict) {
2585
+ let cursor = 0;
2586
+ while (cursor < dict.body.length) {
2587
+ const list = sliceBalanced(dict.body, cursor, "[", "]");
2588
+ if (!list) break;
2589
+ collectQuoted(list.body);
2590
+ cursor = list.endIndex + 1;
2591
+ }
2991
2592
  }
2992
2593
  }
2993
2594
  return deps;
@@ -3048,6 +2649,107 @@ function parsePipfile(content) {
3048
2649
  }
3049
2650
  return deps;
3050
2651
  }
2652
+ var TELLTALES2 = [
2653
+ // --- Frameworks ---
2654
+ ["django", "django"],
2655
+ ["flask", "flask"],
2656
+ ["fastapi", "fastapi"],
2657
+ ["tornado", "tornado"],
2658
+ ["pyramid", "pyramid"],
2659
+ ["starlette", "starlette"],
2660
+ ["sanic", "sanic"],
2661
+ ["bottle", "bottle"],
2662
+ ["streamlit", "streamlit"],
2663
+ ["gradio", "gradio"],
2664
+ ["apache-airflow", "airflow"],
2665
+ // --- Libraries ---
2666
+ ["flask-babel", "flask-babel"],
2667
+ ["pytest", "pytest"],
2668
+ ["numpy", "numpy"],
2669
+ ["pandas", "pandas"],
2670
+ ["requests", "requests"],
2671
+ ["sqlalchemy", "sqlalchemy"],
2672
+ ["celery", "celery"],
2673
+ ["pydantic", "pydantic"],
2674
+ ["boto3", "boto3"],
2675
+ // `botocore` is boto3's transport layer and `aws-lambda-powertools` depends on
2676
+ // it; either declared alone still means "this is AWS code". Lambda runtimes
2677
+ // preinstall boto3, so it is routinely absent from requirements.txt.
2678
+ ["botocore", "boto3"],
2679
+ ["aws-lambda-powertools", "boto3"],
2680
+ ["scipy", "scipy"],
2681
+ ["scikit-learn", "scikit-learn"],
2682
+ ["tensorflow", "tensorflow"],
2683
+ ["torch", "pytorch"],
2684
+ ["httpx", "httpx"],
2685
+ ["aiohttp", "aiohttp"],
2686
+ ["pillow", "pillow"],
2687
+ ["beautifulsoup4", "beautiful-soup"],
2688
+ ["scrapy", "scrapy"],
2689
+ ["alembic", "alembic"],
2690
+ ["marshmallow", "marshmallow"],
2691
+ ["click", "click"],
2692
+ ["typer", "typer"],
2693
+ ["rich", "rich"],
2694
+ ["polars", "polars"],
2695
+ ["dask", "dask"],
2696
+ ["matplotlib", "matplotlib"],
2697
+ ["seaborn", "seaborn"],
2698
+ ["plotly", "plotly"],
2699
+ ["jinja2", "jinja2"],
2700
+ ["mako", "mako"],
2701
+ ["lxml", "lxml"],
2702
+ ["pyyaml", "pyyaml"],
2703
+ ["paramiko", "paramiko"],
2704
+ ["twilio", "twilio"],
2705
+ // Structured-concurrency libraries. Ruff's `ASYNC1xx` rules are written against
2706
+ // trio's checkpoint model and anyio implements the same one, so either gates them.
2707
+ ["trio", "trio"],
2708
+ ["anyio", "anyio"],
2709
+ // The two crypto packages are distinct projects with distinct Bandit checks
2710
+ // (`B413` imports PyCrypto, `B414` PyCryptodome), so they must not be merged.
2711
+ // `pycryptodomex` is the same project published under its `Cryptodome` namespace.
2712
+ ["pycrypto", "pycrypto"],
2713
+ ["pycryptodome", "pycryptodome"],
2714
+ ["pycryptodomex", "pycryptodome"],
2715
+ // `pysnmp` was renamed to `pysnmplib` for part of its history; both are the
2716
+ // library Bandit's `B508`/`B509` weak-SNMP checks apply to.
2717
+ ["pysnmp", "pysnmp"],
2718
+ ["pysnmplib", "pysnmp"],
2719
+ // MongoDB drivers, one entry per ecosystem — they all gate the same
2720
+ // MongoDB-specific patterns (Opengrep's `java.mongodb.`/`python.pymongo.`).
2721
+ ["pymongo", "mongodb"],
2722
+ ["motor", "mongodb"],
2723
+ // --- Tools ---
2724
+ ["black", "black"],
2725
+ ["ruff", "ruff"],
2726
+ ["mypy", "mypy"],
2727
+ ["pylint", "pylint"],
2728
+ ["flake8", "flake8"],
2729
+ ["bandit", "bandit"],
2730
+ ["isort", "isort"]
2731
+ ];
2732
+ var pypi = {
2733
+ id: "pypi",
2734
+ manifests: [
2735
+ { basename: "requirements.txt", parser: parseRequirementsTxt },
2736
+ { basename: "pyproject.toml", parser: parsePyprojectToml },
2737
+ { basename: "Pipfile", parser: parsePipfile },
2738
+ { basename: "setup.py", parser: parseSetupPy },
2739
+ {
2740
+ // The requirements family, by path: real projects split it by environment
2741
+ // (`requirements-dev.txt`, `requirements/base.txt`, `requirements.in` for
2742
+ // pip-tools). Matching only the exact name made every dependency of a split
2743
+ // layout invisible, and with it the gating for Django, Flask, FastAPI,
2744
+ // pytest, pandas and NumPy patterns.
2745
+ matches: (filePath, basename) => /^requirements[a-zA-Z0-9_.-]*\.(txt|in)$/i.test(basename) || /(^|\/)requirements\/[^/]+\.(txt|in)$/i.test(filePath),
2746
+ parser: parseRequirementsTxt
2747
+ }
2748
+ ],
2749
+ telltales: TELLTALES2
2750
+ };
2751
+
2752
+ // src/discover/ecosystems/gem.ts
3051
2753
  function parseGemfile(content) {
3052
2754
  const deps = [];
3053
2755
  const gemRegex = /^\s*gem\s+["']([^"']+)["'](?:\s*,\s*["']([^"']+)["'])?/gm;
@@ -3057,63 +2759,339 @@ function parseGemfile(content) {
3057
2759
  }
3058
2760
  return deps;
3059
2761
  }
3060
- function parseMixExs(content) {
2762
+ var TELLTALES3 = [
2763
+ // --- Frameworks ---
2764
+ ["rails", "ruby-on-rails"],
2765
+ ["sinatra", "sinatra"],
2766
+ ["hanami", "hanami"],
2767
+ ["grape", "grape"],
2768
+ ["padrino", "padrino"],
2769
+ // --- Libraries ---
2770
+ ["rspec", "rspec"],
2771
+ ["rspec-core", "rspec"],
2772
+ ["rspec-rails", "rspec-rails"],
2773
+ ["capybara", "capybara"],
2774
+ ["sidekiq", "sidekiq"],
2775
+ ["devise", "devise"],
2776
+ ["pundit", "pundit"],
2777
+ ["activeadmin", "activeadmin"],
2778
+ ["puma", "puma"],
2779
+ ["nokogiri", "nokogiri"],
2780
+ ["faraday", "faraday"],
2781
+ ["httparty", "httparty"],
2782
+ ["minitest", "minitest"],
2783
+ ["factory_bot", "factorybot"],
2784
+ ["dry-validation", "dry-validation"],
2785
+ // --- Tools ---
2786
+ ["rubocop", "rubocop"],
2787
+ ["bundler", "bundler"],
2788
+ // Sorbet ships as several gems; any of them means the project is Sorbet-typed.
2789
+ ["sorbet", "sorbet"],
2790
+ ["sorbet-runtime", "sorbet"],
2791
+ ["sorbet-static", "sorbet"],
2792
+ ["sorbet-static-and-runtime", "sorbet"],
2793
+ ["solargraph", "solargraph"],
2794
+ // GetText, under both the plain gem and its Rails integration. RuboCop's
2795
+ // `I18n_GetText_*` cops rewrite `_()`-decorated strings, so they only apply to a
2796
+ // project that actually uses GetText rather than Rails' own I18n.
2797
+ ["gettext", "gettext"],
2798
+ ["gettext_i18n_rails", "gettext"],
2799
+ ["rubocop-i18n", "gettext"],
2800
+ // --- Names other registries also publish ---
2801
+ // `faker`, `rake` and `graphql` all exist on npm and PyPI too, and all three
2802
+ // gate RuboCop cops. Reaching them only from a Gemfile is the whole point of
2803
+ // scoping by ecosystem — a JS dependency must not enable Ruby cops.
2804
+ ["faker", "faker"],
2805
+ ["rake", "rake"],
2806
+ ["graphql", "graphql"]
2807
+ ];
2808
+ var gem = {
2809
+ id: "gem",
2810
+ manifests: [{ basename: "Gemfile", parser: parseGemfile }],
2811
+ telltales: TELLTALES3
2812
+ };
2813
+
2814
+ // src/discover/ecosystems/maven.ts
2815
+ function parsePomXml(content) {
3061
2816
  const deps = [];
3062
- const depRegex = /\{:([a-zA-Z_][a-zA-Z0-9_]*)\s*,\s*"([^"]*)"/g;
2817
+ const depRegex = /<dependency>[\s\S]*?<artifactId>([^<]+)<\/artifactId>[\s\S]*?<\/dependency>/g;
3063
2818
  let match;
3064
2819
  while ((match = depRegex.exec(content)) !== null) {
3065
- deps.push({ name: match[1], version: match[2] || void 0 });
2820
+ deps.push({ name: match[1].trim() });
3066
2821
  }
3067
2822
  return deps;
3068
2823
  }
3069
- function parseGoMod(content) {
2824
+ function parseBuildGradle(content) {
3070
2825
  const deps = [];
3071
- const singleRegex = /^require\s+(\S+)\s+(\S+)/gm;
2826
+ const depRegex = /(?:implementation|testImplementation|testCompileOnly|testRuntimeOnly|api|compileOnly|runtimeOnly|kapt|annotationProcessor)\s*(?:\(\s*)?["']([^"':]+):([^"':]+)(?::([^"']+))?["']/g;
3072
2827
  let match;
3073
- while ((match = singleRegex.exec(content)) !== null) {
3074
- if (!match[1].startsWith("(")) {
3075
- deps.push({ name: match[1], version: match[2] });
3076
- }
2828
+ while ((match = depRegex.exec(content)) !== null) {
2829
+ deps.push({ name: match[2].trim(), version: match[3]?.trim() });
3077
2830
  }
3078
- const blockRegex = /require\s*\(([\s\S]*?)\)/g;
3079
- while ((match = blockRegex.exec(content)) !== null) {
3080
- const lines = match[1].split("\n");
3081
- for (const line of lines) {
3082
- const m = line.trim().match(/^(\S+)\s+(\S+)/);
3083
- if (m && !m[1].startsWith("//")) {
3084
- deps.push({ name: m[1], version: m[2] });
3085
- }
2831
+ return deps;
2832
+ }
2833
+ function parseVersionCatalogToml(content) {
2834
+ const deps = [];
2835
+ let inLibraries = false;
2836
+ for (const rawLine of content.split("\n")) {
2837
+ const line = rawLine.trim();
2838
+ if (line === "" || line.startsWith("#")) continue;
2839
+ if (line.startsWith("[")) {
2840
+ inLibraries = /^\[\s*libraries\s*\]/.test(line);
2841
+ continue;
2842
+ }
2843
+ if (!inLibraries) continue;
2844
+ const moduleMatch = line.match(/\bmodule\s*=\s*["'][^"':]+:([^"']+)["']/);
2845
+ const nameMatch = line.match(/\bname\s*=\s*["']([^"']+)["']/);
2846
+ const versionMatch = line.match(/\bversion\s*=\s*["']([^"']+)["']/);
2847
+ if (moduleMatch || nameMatch) {
2848
+ deps.push({
2849
+ name: (moduleMatch?.[1] ?? nameMatch?.[1] ?? "").trim(),
2850
+ version: versionMatch?.[1]?.trim()
2851
+ });
2852
+ continue;
2853
+ }
2854
+ const shorthand = line.match(/^[A-Za-z0-9_.-]+\s*=\s*["'][^"':]+:([^"':]+)(?::([^"']+))?["']/);
2855
+ if (shorthand) {
2856
+ deps.push({ name: shorthand[1].trim(), version: shorthand[2]?.trim() });
3086
2857
  }
3087
2858
  }
3088
2859
  return deps;
3089
2860
  }
3090
- function parsePomXml(content) {
2861
+ function parseBuildSbt(content) {
3091
2862
  const deps = [];
3092
- const depRegex = /<dependency>[\s\S]*?<artifactId>([^<]+)<\/artifactId>[\s\S]*?<\/dependency>/g;
2863
+ const depRegex = /"([^"]+)"\s*%%?\s*"([^"]+)"\s*%(?!%)\s*(?:"([^"]+)")?/g;
3093
2864
  let match;
3094
2865
  while ((match = depRegex.exec(content)) !== null) {
3095
- deps.push({ name: match[1].trim() });
2866
+ deps.push({ name: match[2], version: match[3] });
3096
2867
  }
3097
2868
  return deps;
3098
2869
  }
3099
- function parseBuildGradle(content) {
3100
- const deps = [];
3101
- const depRegex = /(?:implementation|testImplementation|api|compileOnly|runtimeOnly|kapt|annotationProcessor)\s*(?:\(\s*)?["']([^"':]+):([^"':]+)(?::([^"']+))?["']/g;
2870
+ function parseSbtBuildDefinition(content) {
2871
+ const deps = parseBuildSbt(content);
2872
+ const pluginRegex = /addSbtPlugin\s*\(\s*"([^"]+)"\s*%%?\s*"([^"]+)"/g;
3102
2873
  let match;
3103
- while ((match = depRegex.exec(content)) !== null) {
3104
- deps.push({ name: match[2].trim(), version: match[3]?.trim() });
2874
+ while ((match = pluginRegex.exec(content)) !== null) {
2875
+ deps.push({ name: `${match[1]}:${match[2]}` });
3105
2876
  }
3106
2877
  return deps;
3107
2878
  }
3108
- function parseBuildSbt(content) {
2879
+ var TELLTALES4 = [
2880
+ // --- Java / Kotlin — Frameworks ---
2881
+ ["spring-boot-starter", "spring-boot"],
2882
+ ["spring-boot-starter-web", "spring-boot"],
2883
+ ["spring-core", "spring-framework"],
2884
+ ["quarkus-core", "quarkus"],
2885
+ ["micronaut-core", "micronaut"],
2886
+ ["ktor-server-core", "ktor"],
2887
+ ["android", "android-sdk"],
2888
+ // --- Java / Kotlin — Libraries ---
2889
+ ["junit-jupiter", "junit-5"],
2890
+ ["junit", "junit"],
2891
+ ["junit-jupiter-api", "junit-5"],
2892
+ // Spring Boot's test starter is the single most common Java test declaration and
2893
+ // pulls JUnit 5 transitively; without it PMD's JUnit rules are unreachable for
2894
+ // most Spring projects. The `junit-jupiter-`/`junit-platform-` prefixes below
2895
+ // cover `-engine`/`-params`/`-launcher`, which Gradle declares separately.
2896
+ ["spring-boot-starter-test", "junit-5"],
2897
+ ["mockito-core", "mockito"],
2898
+ ["lombok", "lombok"],
2899
+ ["jackson-databind", "jackson"],
2900
+ ["jackson-core", "jackson"],
2901
+ ["hibernate-core", "hibernate"],
2902
+ ["guava", "guava"],
2903
+ ["commons-lang3", "apache-commons-lang"],
2904
+ ["commons-io", "apache-commons-io"],
2905
+ ["commons-logging", "apache-commons-logging"],
2906
+ ["commons-fileupload", "apache-commons-fileupload"],
2907
+ ["commons-fileupload2-core", "apache-commons-fileupload"],
2908
+ // The EJB/Jakarta EE API jars, under every name they have shipped as. PMD's
2909
+ // session-bean naming and static-field rules only mean anything with these on
2910
+ // the classpath.
2911
+ ["jakarta.ejb-api", "jakarta-ee"],
2912
+ ["javax.ejb-api", "jakarta-ee"],
2913
+ ["jakarta.jakartaee-api", "jakarta-ee"],
2914
+ ["jakarta.jakartaee-web-api", "jakarta-ee"],
2915
+ ["javaee-api", "jakarta-ee"],
2916
+ ["javaee-web-api", "jakarta-ee"],
2917
+ ["slf4j-api", "slf4j"],
2918
+ ["log4j-core", "log4j"],
2919
+ ["logback-classic", "logback"],
2920
+ ["testng", "testng"],
2921
+ ["assertj-core", "assertj"],
2922
+ ["gson", "gson"],
2923
+ ["okhttp", "okhttp"],
2924
+ ["retrofit", "retrofit"],
2925
+ ["spock-core", "spock"],
2926
+ ["mongodb-driver-sync", "mongodb"],
2927
+ ["mongodb-driver-core", "mongodb"],
2928
+ ["mongodb-driver-reactivestreams", "mongodb"],
2929
+ ["mongo-java-driver", "mongodb"],
2930
+ // --- Groovy — Frameworks ---
2931
+ // Grails/GORM artifact IDs vary (grails-core, grails-web, gorm-hibernate5, …);
2932
+ // the `grails-`/`gorm-` prefix rules catch the rest.
2933
+ ["grails-core", "grails"],
2934
+ ["gorm-core", "grails-gorm"],
2935
+ // --- Scala — Frameworks ---
2936
+ // Play is declared as an sbt *plugin*, so `group:artifact` (from
2937
+ // `addSbtPlugin`) is the reliable signal; the library artifacts that follow only
2938
+ // appear in some builds. `play-json` is deliberately absent — it is a
2939
+ // standalone JSON library used widely outside Play.
2940
+ ["com.typesafe.play:sbt-plugin", "play-framework"],
2941
+ ["org.playframework:sbt-plugin", "play-framework"],
2942
+ ["play", "play-framework"],
2943
+ ["play-server", "play-framework"],
2944
+ ["play-ws", "play-framework"],
2945
+ ["play-test", "play-framework"],
2946
+ ["play-slick", "play-framework"],
2947
+ ["twirl-api", "play-framework"],
2948
+ ["scalatestplus-play", "play-framework"],
2949
+ ["akka-actor", "akka"],
2950
+ ["spray-can", "spray"],
2951
+ // --- Scala — Libraries ---
2952
+ ["scalatest", "scalatest"],
2953
+ ["specs2-core", "specs2"],
2954
+ ["cats-core", "cats"],
2955
+ ["circe-core", "circe"],
2956
+ // Slick gates Opengrep's `scala.slick.` SQL-injection patterns.
2957
+ ["slick", "slick"],
2958
+ ["slick-hikaricp", "slick"],
2959
+ // jwt-scala publishes one artifact per JSON backend; all gate the same
2960
+ // hardcoded-secret patterns.
2961
+ ["jwt-core", "jwt-scala"],
2962
+ ["jwt-circe", "jwt-scala"],
2963
+ ["jwt-play-json", "jwt-scala"],
2964
+ ["jwt-json4s", "jwt-scala"],
2965
+ ["jwt-spray-json", "jwt-scala"],
2966
+ ["jwt-upickle", "jwt-scala"],
2967
+ ["jwt-argonaut", "jwt-scala"],
2968
+ ["munit", "munit"],
2969
+ ["scalacheck", "scalacheck"],
2970
+ ["scala-logging", "scala-logging"],
2971
+ ["chimney", "chimney"],
2972
+ // --- WebJars — front-end libraries republished to Maven Central ---
2973
+ // A JVM web app serves its JavaScript from a WebJar instead of from npm, so
2974
+ // `"org.webjars" % "jquery"` is real evidence of jQuery in the repo — and jQuery
2975
+ // gates Opengrep's `javascript.jquery.` rules. This is the one place where an
2976
+ // entity legitimately belongs to two registries, and dropping it was the only
2977
+ // recall loss the before/after sweep over local repos turned up (codacy-website).
2978
+ // The parsers emit bare artifact ids, so the `org.webjars` group is not visible
2979
+ // here; nothing else on Maven Central is named after a front-end library, so
2980
+ // matching the artifact alone is safe.
2981
+ ["jquery", "jquery"]
2982
+ ];
2983
+ var PREFIXES2 = [
2984
+ // Kotlin — coroutines ships one artifact per target (`-core`, `-android`,
2985
+ // `-test`, `-jdk8`, `-reactive`, …) and any of them means the project uses
2986
+ // coroutines, which is what detekt's `coroutines` rules gate on.
2987
+ ["kotlinx-coroutines-", "kotlinx-coroutines"],
2988
+ // JUnit 5 ships one artifact per role (`-engine`, `-params`, `-launcher`), and
2989
+ // Gradle builds declare them separately from `junit-jupiter` itself.
2990
+ ["junit-jupiter-", "junit-5"],
2991
+ ["junit-platform-", "junit-5"],
2992
+ // A modern Android project declares `androidx.*`, never the deprecated
2993
+ // `com.google.android:android` jar the exact telltale matches.
2994
+ ["androidx-", "android-sdk"],
2995
+ ["androidx.", "android-sdk"],
2996
+ // Groovy — Grails/GORM artifact families (build.gradle artifactIds).
2997
+ ["grails-", "grails"],
2998
+ ["gorm-", "grails-gorm"],
2999
+ // Scala — artifact families published one module per feature (sbt artifactIds).
3000
+ // Enumerating members would go stale on every release; the prefixes are
3001
+ // distinctive enough not to collide with other ecosystems.
3002
+ ["http4s-", "http4s"],
3003
+ ["akka-", "akka"],
3004
+ ["pekko-", "pekko"],
3005
+ ["zio-", "zio"],
3006
+ // Must precede any broader `cats-` rule: cats-effect is its own project.
3007
+ ["cats-effect", "cats-effect"],
3008
+ ["doobie-", "doobie"],
3009
+ ["fs2-", "fs2"],
3010
+ ["circe-", "circe"],
3011
+ ["pureconfig", "pureconfig"],
3012
+ ["kamon-", "kamon"],
3013
+ ["flyway-", "flyway"],
3014
+ ["testcontainers-scala", "testcontainers"]
3015
+ ];
3016
+ var maven = {
3017
+ id: "maven",
3018
+ manifests: [
3019
+ { basename: "pom.xml", parser: parsePomXml },
3020
+ { basename: "build.gradle", parser: parseBuildGradle },
3021
+ { basename: "build.gradle.kts", parser: parseBuildGradle },
3022
+ { basename: "libs.versions.toml", parser: parseVersionCatalogToml },
3023
+ { basename: "build.sbt", parser: parseBuildSbt },
3024
+ {
3025
+ // sbt build definition: `project/Dependencies.scala`, `project/plugins.sbt`
3026
+ // and the meta-build's own `project/project/*`. Matching by directory rather
3027
+ // than basename is what this rule exists for — the Scala file can be called
3028
+ // anything, and `*.scala` on its own is source code, not a manifest.
3029
+ //
3030
+ // Case-sensitive deliberately: sbt's directory is always lowercase `project`,
3031
+ // while `Project` is an ordinary domain-package name, and reading
3032
+ // `…/domain/Project/Project.scala` as a manifest would be wrong.
3033
+ matches: (filePath) => /(^|\/)project\/[^/]+\.(scala|sbt)$/.test(filePath),
3034
+ parser: parseSbtBuildDefinition
3035
+ }
3036
+ ],
3037
+ telltales: TELLTALES4,
3038
+ prefixes: PREFIXES2
3039
+ };
3040
+
3041
+ // src/discover/ecosystems/golang.ts
3042
+ function parseGoMod(content) {
3109
3043
  const deps = [];
3110
- const depRegex = /"([^"]+)"\s*%%?\s*"([^"]+)"\s*%\s*"([^"]+)"/g;
3044
+ const singleRegex = /^require\s+(\S+)\s+(\S+)/gm;
3111
3045
  let match;
3112
- while ((match = depRegex.exec(content)) !== null) {
3113
- deps.push({ name: match[2], version: match[3] });
3046
+ while ((match = singleRegex.exec(content)) !== null) {
3047
+ if (!match[1].startsWith("(")) {
3048
+ deps.push({ name: match[1], version: match[2] });
3049
+ }
3050
+ }
3051
+ const blockRegex = /require\s*\(([\s\S]*?)\)/g;
3052
+ while ((match = blockRegex.exec(content)) !== null) {
3053
+ const lines = match[1].split("\n");
3054
+ for (const line of lines) {
3055
+ const m = line.trim().match(/^(\S+)\s+(\S+)/);
3056
+ if (m && !m[1].startsWith("//")) {
3057
+ deps.push({ name: m[1], version: m[2] });
3058
+ }
3059
+ }
3114
3060
  }
3115
3061
  return deps;
3116
3062
  }
3063
+ var TELLTALES5 = [
3064
+ // --- Frameworks ---
3065
+ ["github.com/gin-gonic/gin", "gin"],
3066
+ ["github.com/labstack/echo", "echo"],
3067
+ ["github.com/gofiber/fiber", "fiber"],
3068
+ ["github.com/go-chi/chi", "chi"],
3069
+ ["github.com/gorilla/mux", "gorilla-mux"],
3070
+ ["github.com/beego/beego", "beego"],
3071
+ // --- Libraries ---
3072
+ ["github.com/stretchr/testify", "testify"],
3073
+ ["github.com/spf13/cobra", "cobra"],
3074
+ ["github.com/spf13/viper", "viper"],
3075
+ ["go.uber.org/zap", "zap"],
3076
+ ["github.com/sirupsen/logrus", "logrus"],
3077
+ ["gorm.io/gorm", "go-gorm"],
3078
+ ["github.com/go-kit/kit", "go-kit"],
3079
+ ["google.golang.org/protobuf", "protocol-buffers"],
3080
+ ["google.golang.org/grpc", "grpc"],
3081
+ ["github.com/jackc/pgx", "pgx"],
3082
+ ["github.com/redis/go-redis", "go-redis"],
3083
+ // v1 is still widely deployed; both gate Opengrep's AWS Lambda packs.
3084
+ ["github.com/aws/aws-sdk-go", "aws-sdk-go"],
3085
+ ["github.com/aws/aws-sdk-go-v2", "aws-sdk-go"],
3086
+ ["github.com/prometheus/client_golang", "prometheus-client"]
3087
+ ];
3088
+ var golang = {
3089
+ id: "golang",
3090
+ manifests: [{ basename: "go.mod", parser: parseGoMod }],
3091
+ telltales: TELLTALES5
3092
+ };
3093
+
3094
+ // src/discover/ecosystems/cargo.ts
3117
3095
  function parseCargoToml(content) {
3118
3096
  const deps = [];
3119
3097
  let inDepsSection = false;
@@ -3137,6 +3115,32 @@ function parseCargoToml(content) {
3137
3115
  }
3138
3116
  return deps;
3139
3117
  }
3118
+ var TELLTALES6 = [
3119
+ // --- Frameworks ---
3120
+ ["actix-web", "actix-web"],
3121
+ ["axum", "axum"],
3122
+ ["rocket", "rocket"],
3123
+ ["warp", "warp"],
3124
+ ["tauri", "tauri"],
3125
+ // --- Libraries ---
3126
+ ["tokio", "tokio"],
3127
+ ["serde", "serde"],
3128
+ ["clap", "clap"],
3129
+ ["reqwest", "reqwest"],
3130
+ ["diesel", "diesel"],
3131
+ ["sqlx", "sqlx"],
3132
+ ["tracing", "tracing"],
3133
+ ["anyhow", "anyhow"],
3134
+ ["thiserror", "thiserror"],
3135
+ ["rayon", "rayon"]
3136
+ ];
3137
+ var cargo = {
3138
+ id: "cargo",
3139
+ manifests: [{ basename: "Cargo.toml", parser: parseCargoToml }],
3140
+ telltales: TELLTALES6
3141
+ };
3142
+
3143
+ // src/discover/ecosystems/composer.ts
3140
3144
  function parseComposerJson(content) {
3141
3145
  try {
3142
3146
  const pkg = JSON.parse(content);
@@ -3155,6 +3159,182 @@ function parseComposerJson(content) {
3155
3159
  return [];
3156
3160
  }
3157
3161
  }
3162
+ var TELLTALES7 = [
3163
+ // --- Frameworks ---
3164
+ ["laravel/framework", "laravel"],
3165
+ ["symfony/framework-bundle", "symfony"],
3166
+ ["symfony/symfony", "symfony"],
3167
+ ["codeigniter4/framework", "codeigniter"],
3168
+ ["slim/slim", "slim"],
3169
+ ["cakephp/cakephp", "cakephp"],
3170
+ // WordPress is rarely a real Composer dependency; these are the common
3171
+ // "WordPress as a dependency" packages used by Bedrock/Composer-based sites.
3172
+ ["johnpbloch/wordpress-core", "wordpress"],
3173
+ ["johnpbloch/wordpress", "wordpress"],
3174
+ ["roots/wordpress", "wordpress"],
3175
+ ["drupal/core", "drupal"],
3176
+ ["drupal/core-recommended", "drupal"],
3177
+ ["magento/product-community-edition", "magento"],
3178
+ ["magento/product-enterprise-edition", "magento"],
3179
+ ["magento/framework", "magento"],
3180
+ // --- Libraries ---
3181
+ ["phpunit/phpunit", "phpunit"],
3182
+ ["guzzlehttp/guzzle", "guzzle"],
3183
+ ["monolog/monolog", "monolog"],
3184
+ ["nesbot/carbon", "carbon"],
3185
+ ["doctrine/orm", "doctrine-orm"],
3186
+ ["doctrine/dbal", "doctrine-dbal"],
3187
+ ["league/flysystem", "flysystem"],
3188
+ ["php-amqplib/php-amqplib", "php-amqplib"],
3189
+ ["predis/predis", "predis"],
3190
+ // --- Tools ---
3191
+ ["phpstan/phpstan", "phpstan"],
3192
+ ["squizlabs/php_codesniffer", "php-codesniffer"]
3193
+ ];
3194
+ var composer = {
3195
+ id: "composer",
3196
+ manifests: [{ basename: "composer.json", parser: parseComposerJson }],
3197
+ telltales: TELLTALES7
3198
+ };
3199
+
3200
+ // src/discover/ecosystems/nuget.ts
3201
+ function parseXmlDependencyElements(content, elementRegex, nameAttr) {
3202
+ const deps = [];
3203
+ const nameRegex = new RegExp(`\\b${nameAttr}\\s*=\\s*["']([^"']+)["']`, "i");
3204
+ const versionRegex = /\bversion\s*=\s*["']([^"']+)["']/i;
3205
+ let match;
3206
+ while ((match = elementRegex.exec(content)) !== null) {
3207
+ const attrs = match[1];
3208
+ const nameMatch = attrs.match(nameRegex);
3209
+ if (!nameMatch) continue;
3210
+ const versionMatch = attrs.match(versionRegex);
3211
+ deps.push({ name: nameMatch[1].trim(), version: versionMatch?.[1]?.trim() });
3212
+ }
3213
+ return deps;
3214
+ }
3215
+ function parseCsproj(content) {
3216
+ return parseXmlDependencyElements(
3217
+ content,
3218
+ /<Package(?:Reference|Version)\s+([^>]+?)\/?>/gi,
3219
+ "Include"
3220
+ );
3221
+ }
3222
+ function parsePackagesConfig(content) {
3223
+ return parseXmlDependencyElements(content, /<package\s+([^>]+?)\/?>/gi, "id");
3224
+ }
3225
+ var TELLTALES8 = [
3226
+ // --- Libraries ---
3227
+ // NuGet package IDs. Microsoft.* families are covered by the prefix rules.
3228
+ ["EntityFramework", "entity-framework"],
3229
+ ["NUnit", "nunit"],
3230
+ ["Moq", "moq"],
3231
+ ["NSubstitute", "nsubstitute"],
3232
+ ["FluentAssertions", "fluentassertions"],
3233
+ ["Shouldly", "shouldly"],
3234
+ ["NLog", "nlog"],
3235
+ ["log4net", "log4net"],
3236
+ ["Newtonsoft.Json", "json-net"],
3237
+ ["AutoMapper", "automapper"],
3238
+ ["Dapper", "dapper"],
3239
+ ["MediatR", "mediatr"]
3240
+ ];
3241
+ var PREFIXES3 = [
3242
+ // .NET — Microsoft package families and common test/logging libraries. More
3243
+ // specific prefixes (Blazor) must precede broader ones (ASP.NET Core).
3244
+ ["Microsoft.AspNetCore.Components.WebAssembly", "blazor"],
3245
+ ["Microsoft.AspNetCore.", "aspnet-core"],
3246
+ ["Microsoft.EntityFrameworkCore", "entity-framework"],
3247
+ ["Microsoft.Azure.Functions", "azure-functions"],
3248
+ ["Microsoft.Azure.WebJobs", "azure-functions"],
3249
+ ["xunit", "xunit"],
3250
+ ["MSTest.", "mstest"],
3251
+ ["Serilog", "serilog"]
3252
+ ];
3253
+ var nuget = {
3254
+ id: "nuget",
3255
+ manifests: [
3256
+ { basename: "packages.config", parser: parsePackagesConfig },
3257
+ { basename: "Directory.Packages.props", parser: parseCsproj },
3258
+ // Project files carry an arbitrary basename, so they match by suffix. Matched
3259
+ // case-insensitively — .NET project extensions may be upper-cased.
3260
+ { suffix: ".csproj", parser: parseCsproj },
3261
+ { suffix: ".vbproj", parser: parseCsproj },
3262
+ { suffix: ".fsproj", parser: parseCsproj }
3263
+ ],
3264
+ telltales: TELLTALES8,
3265
+ prefixes: PREFIXES3
3266
+ };
3267
+
3268
+ // src/discover/ecosystems/pub.ts
3269
+ function parsePubspecYaml(content) {
3270
+ const deps = [];
3271
+ let inSection = false;
3272
+ let childIndent = -1;
3273
+ for (const rawLine of content.split("\n")) {
3274
+ const trimmed = rawLine.trim();
3275
+ if (trimmed === "" || trimmed.startsWith("#")) continue;
3276
+ const indent = rawLine.length - rawLine.trimStart().length;
3277
+ if (indent === 0) {
3278
+ inSection = /^(dependencies|dev_dependencies|dependency_overrides):/.test(trimmed);
3279
+ childIndent = -1;
3280
+ continue;
3281
+ }
3282
+ if (!inSection) continue;
3283
+ if (childIndent === -1) childIndent = indent;
3284
+ if (indent !== childIndent) continue;
3285
+ const m = trimmed.match(/^([a-zA-Z0-9_.]+)\s*:(?:\s*["']?\^?([0-9][^"'#\s]*))?/);
3286
+ if (m) deps.push({ name: m[1], version: m[2] });
3287
+ }
3288
+ return deps;
3289
+ }
3290
+ var TELLTALES9 = [
3291
+ // --- Frameworks ---
3292
+ // `flutter` and `angular` appear as plain keys in pubspec.yaml. `angular` here is
3293
+ // AngularDart — a different framework from npm's `angular` (AngularJS 1.x) and
3294
+ // from modern Angular — hence the distinct `angular-dart` entity.
3295
+ ["flutter", "flutter"],
3296
+ ["flutter_test", "flutter"],
3297
+ ["angular", "angular-dart"],
3298
+ // --- Libraries ---
3299
+ // Kept to Dart-specific names with negligible cross-ecosystem collision risk
3300
+ // (generic names like "get"/"provider" are intentionally omitted).
3301
+ ["riverpod", "riverpod"],
3302
+ ["flutter_bloc", "bloc"],
3303
+ ["dio", "dio"]
3304
+ ];
3305
+ var pub = {
3306
+ id: "pub",
3307
+ manifests: [{ basename: "pubspec.yaml", parser: parsePubspecYaml }],
3308
+ telltales: TELLTALES9
3309
+ };
3310
+
3311
+ // src/discover/ecosystems/hex.ts
3312
+ function parseMixExs(content) {
3313
+ const deps = [];
3314
+ const depRegex = /\{:([a-zA-Z_][a-zA-Z0-9_]*)\s*,\s*"([^"]*)"/g;
3315
+ let match;
3316
+ while ((match = depRegex.exec(content)) !== null) {
3317
+ deps.push({ name: match[1], version: match[2] || void 0 });
3318
+ }
3319
+ return deps;
3320
+ }
3321
+ var TELLTALES10 = [
3322
+ // --- Frameworks ---
3323
+ ["phoenix", "phoenix"],
3324
+ ["plug", "plug"],
3325
+ ["plug_cowboy", "plug"],
3326
+ // --- Libraries ---
3327
+ ["ecto", "ecto"],
3328
+ ["ecto_sql", "ecto"],
3329
+ ["phoenix_ecto", "ecto"]
3330
+ ];
3331
+ var hex = {
3332
+ id: "hex",
3333
+ manifests: [{ basename: "mix.exs", parser: parseMixExs }],
3334
+ telltales: TELLTALES10
3335
+ };
3336
+
3337
+ // src/discover/ecosystems/swift.ts
3158
3338
  function parsePodfile(content) {
3159
3339
  const deps = [];
3160
3340
  const podRegex = /^\s*pod\s+["']([^"']+)["'](?:\s*,\s*["']([^"']+)["'])?/gm;
@@ -3162,127 +3342,503 @@ function parsePodfile(content) {
3162
3342
  while ((match = podRegex.exec(content)) !== null) {
3163
3343
  deps.push({ name: match[1], version: match[2] });
3164
3344
  }
3165
- return deps;
3345
+ return deps;
3346
+ }
3347
+ function parsePackageSwift(content) {
3348
+ const deps = [];
3349
+ const pkgRegex = /\.package\s*\(\s*url:\s*["'](?:https?:\/\/[^/]+\/[^/]+\/|git@[^:]+:[^/]+\/)([^"'/.]+)/g;
3350
+ let match;
3351
+ while ((match = pkgRegex.exec(content)) !== null) {
3352
+ deps.push({ name: match[1] });
3353
+ }
3354
+ return deps;
3355
+ }
3356
+ var TELLTALES11 = [
3357
+ // --- Frameworks ---
3358
+ ["vapor", "vapor"],
3359
+ // --- Libraries ---
3360
+ ["Alamofire", "alamofire"],
3361
+ ["SnapKit", "snapkit"],
3362
+ ["Kingfisher", "kingfisher"],
3363
+ ["Moya", "moya"],
3364
+ ["RxSwift", "rxswift"],
3365
+ ["RealmSwift", "realm"],
3366
+ // Quick and Nimble ship from separate repos and are declared independently —
3367
+ // Nimble is usable on its own with XCTest — so they are two entities, gating
3368
+ // SwiftLint's `quick_discouraged_*` and `prefer_nimble` rules respectively.
3369
+ // Ids are matched exactly and case-sensitively: `parsePackageSwift` yields the
3370
+ // GitHub repo name and `parsePodfile` the pod name, and both are `Quick`/`Nimble`.
3371
+ ["Quick", "quick"],
3372
+ ["Nimble", "nimble"]
3373
+ ];
3374
+ var swift = {
3375
+ id: "swift",
3376
+ manifests: [
3377
+ { basename: "Package.swift", parser: parsePackageSwift },
3378
+ { basename: "Podfile", parser: parsePodfile }
3379
+ ],
3380
+ telltales: TELLTALES11
3381
+ };
3382
+
3383
+ // src/discover/ecosystems/shards.ts
3384
+ function parseShardYml(content) {
3385
+ const deps = [];
3386
+ let inSection = false;
3387
+ let depIndent = -1;
3388
+ let current;
3389
+ const flush = () => {
3390
+ if (current) deps.push(current);
3391
+ current = void 0;
3392
+ };
3393
+ for (const rawLine of content.split("\n")) {
3394
+ const trimmed = rawLine.trim();
3395
+ if (trimmed === "" || trimmed.startsWith("#")) continue;
3396
+ const indent = rawLine.length - rawLine.trimStart().length;
3397
+ if (indent === 0) {
3398
+ flush();
3399
+ inSection = /^(dependencies|development_dependencies):/.test(trimmed);
3400
+ depIndent = -1;
3401
+ continue;
3402
+ }
3403
+ if (!inSection) continue;
3404
+ if (depIndent === -1) depIndent = indent;
3405
+ if (indent === depIndent) {
3406
+ flush();
3407
+ const m = trimmed.match(/^([a-zA-Z0-9_-]+)\s*:/);
3408
+ current = m ? { name: m[1] } : void 0;
3409
+ continue;
3410
+ }
3411
+ if (indent > depIndent && current) {
3412
+ const v = trimmed.match(/^version\s*:\s*["']?(?:~>|[~^])?\s*([0-9][^"'\s]*)/);
3413
+ if (v) current.version = v[1];
3414
+ }
3415
+ }
3416
+ flush();
3417
+ return deps;
3418
+ }
3419
+ var TELLTALES12 = [
3420
+ // --- Frameworks ---
3421
+ ["amber", "amber"],
3422
+ ["lucky", "lucky"],
3423
+ ["kemal", "kemal"]
3424
+ ];
3425
+ var shards = {
3426
+ id: "shards",
3427
+ manifests: [{ basename: "shard.yml", parser: parseShardYml }],
3428
+ telltales: TELLTALES12
3429
+ };
3430
+
3431
+ // src/discover/ecosystems/index.ts
3432
+ var ECOSYSTEMS = [
3433
+ npm,
3434
+ pypi,
3435
+ gem,
3436
+ maven,
3437
+ golang,
3438
+ cargo,
3439
+ composer,
3440
+ nuget,
3441
+ pub,
3442
+ hex,
3443
+ swift,
3444
+ shards
3445
+ ];
3446
+ var BY_BASENAME = /* @__PURE__ */ new Map();
3447
+ var BY_SUFFIX = [];
3448
+ var BY_PATH = [];
3449
+ for (const ecosystem of ECOSYSTEMS) {
3450
+ for (const rule of ecosystem.manifests) {
3451
+ const resolved = { ecosystem: ecosystem.id, parser: rule.parser };
3452
+ if ("basename" in rule) BY_BASENAME.set(rule.basename, resolved);
3453
+ else if ("suffix" in rule) BY_SUFFIX.push({ suffix: rule.suffix, resolved });
3454
+ else BY_PATH.push({ matches: rule.matches, resolved });
3455
+ }
3456
+ }
3457
+ function getEcosystem(id) {
3458
+ const found = ECOSYSTEMS.find((e) => e.id === id);
3459
+ if (!found) throw new Error(`Unknown dependency ecosystem: ${id}`);
3460
+ return found;
3461
+ }
3462
+ function resolveManifest(filePath) {
3463
+ const basename = filePath.split("/").pop() ?? filePath;
3464
+ const byName = BY_BASENAME.get(basename);
3465
+ if (byName) return byName;
3466
+ const lowerBasename = basename.toLowerCase();
3467
+ for (const { suffix, resolved } of BY_SUFFIX) {
3468
+ if (lowerBasename.endsWith(suffix)) return resolved;
3469
+ }
3470
+ for (const { matches, resolved } of BY_PATH) {
3471
+ if (matches(filePath, basename)) return resolved;
3472
+ }
3473
+ return void 0;
3474
+ }
3475
+
3476
+ // src/discover/signals.ts
3477
+ var import_tooling5 = require("@codacy/tooling");
3478
+ function pathBasename(filePath) {
3479
+ return filePath.split("/").pop() ?? filePath;
3480
+ }
3481
+ var FILE_SIGNAL_RULES = [
3482
+ {
3483
+ name: ".flowconfig",
3484
+ matches: (f) => pathBasename(f) === ".flowconfig",
3485
+ entity: "flow"
3486
+ },
3487
+ {
3488
+ name: "cdk.json",
3489
+ matches: (f) => pathBasename(f) === "cdk.json",
3490
+ entity: "aws-cdk"
3491
+ },
3492
+ {
3493
+ name: ".meteor/release",
3494
+ matches: (f) => f === ".meteor/release" || f.endsWith("/.meteor/release"),
3495
+ entity: "meteor"
3496
+ },
3497
+ {
3498
+ name: "sfdx-project.json",
3499
+ matches: (f) => pathBasename(f) === "sfdx-project.json",
3500
+ entity: "salesforce"
3501
+ },
3502
+ {
3503
+ name: "lwc.config.json",
3504
+ matches: (f) => pathBasename(f) === "lwc.config.json",
3505
+ entity: "lightning-web-components"
3506
+ },
3507
+ {
3508
+ name: "suitecloud.config.js",
3509
+ matches: (f) => pathBasename(f) === "suitecloud.config.js",
3510
+ entity: "suitescript"
3511
+ },
3512
+ {
3513
+ name: ".github/workflows",
3514
+ matches: (f) => f.startsWith(".github/workflows/"),
3515
+ entity: "github-actions"
3516
+ },
3517
+ {
3518
+ name: ".circleci",
3519
+ matches: (f) => f.startsWith(".circleci/"),
3520
+ entity: "circleci"
3521
+ },
3522
+ {
3523
+ name: ".gitlab-ci.yml",
3524
+ matches: (f) => f === ".gitlab-ci.yml" || f.startsWith(".gitlab/"),
3525
+ entity: "gitlab-ci"
3526
+ },
3527
+ {
3528
+ name: "bitbucket-pipelines.yml",
3529
+ matches: (f) => f === "bitbucket-pipelines.yml",
3530
+ entity: "bitbucket-pipelines"
3531
+ },
3532
+ {
3533
+ name: "azure-pipelines.yml",
3534
+ matches: (f) => f === "azure-pipelines.yml" || f === "azure-pipelines.yaml",
3535
+ entity: "azure-pipelines"
3536
+ },
3537
+ {
3538
+ // Jenkins pipelines are Groovy scripts with no manifest at all — the
3539
+ // Jenkinsfile *is* the declaration. Gates CodeNarc's `jenkins`/
3540
+ // `jenkinspipeline` tagged patterns.
3541
+ name: "Jenkinsfile",
3542
+ matches: (f) => {
3543
+ const base = pathBasename(f);
3544
+ return base === "Jenkinsfile" || base.startsWith("Jenkinsfile.");
3545
+ },
3546
+ entity: "jenkins"
3547
+ },
3548
+ {
3549
+ // A `sorbet/` directory (typically `sorbet/config` + RBI files) marks a
3550
+ // Sorbet-typed Ruby project even when the gem is pulled in transitively.
3551
+ name: "sorbet/config",
3552
+ matches: (f) => f === "sorbet/config" || f.endsWith("/sorbet/config"),
3553
+ entity: "sorbet"
3554
+ },
3555
+ {
3556
+ name: "Rakefile",
3557
+ matches: (f) => {
3558
+ const base = pathBasename(f);
3559
+ return base === "Rakefile" || base === "rakefile" || base.endsWith(".rake");
3560
+ },
3561
+ entity: "rake"
3562
+ },
3563
+ {
3564
+ // XCTest ships with Xcode, so no manifest ever declares it and a file signal
3565
+ // is the only producer available. `FooTests.swift` / `FooTest.swift` is the
3566
+ // naming Xcode's templates and SwiftPM's `Tests/` layout both generate, and it
3567
+ // is what SwiftLint's own test rules key on. Matching the file name rather than
3568
+ // a `Tests/` directory also covers CocoaPods/Xcode projects, which have no
3569
+ // fixed directory convention.
3570
+ name: "*Tests.swift",
3571
+ matches: (f) => /Tests?\.swift$/.test(pathBasename(f)),
3572
+ entity: "xctest"
3573
+ },
3574
+ // ── Infrastructure-as-Code ─────────────────────────────────────────────────
3575
+ //
3576
+ // These gate Checkov, whose ~1,300 cloud-security checks are otherwise
3577
+ // universal: they are all default-on and the only other filter is the language
3578
+ // one, which passes on any repo containing a `.json` or `.yaml` file. Terraform
3579
+ // needs no rule here — `.tf` is the `Terraform` language, so `lang:Terraform`
3580
+ // already means "this repo has Terraform". What the language axis cannot
3581
+ // express is which IaC *framework* a YAML/JSON file belongs to, since
3582
+ // Kubernetes manifests, Helm charts, Ansible playbooks, CloudFormation
3583
+ // templates and ARM templates are all YAML or JSON.
3584
+ //
3585
+ // Bias these rules **permissive**, the opposite of the rules above. A Checkov
3586
+ // check declares the resource types it applies to, so a check enabled in a repo
3587
+ // that has none of them is inert — it produces no findings and costs only a line
3588
+ // of config. A false negative is what's expensive: it silently drops hundreds of
3589
+ // checks from a repo that needed them. Where a signal is ambiguous, prefer
3590
+ // matching.
3591
+ {
3592
+ // A Helm chart's templates render to Kubernetes manifests, so a chart is
3593
+ // evidence of Kubernetes as much as of Helm. `Chart.yaml` is the chart
3594
+ // manifest and carries no other meaning at a chart root.
3595
+ name: "Chart.yaml",
3596
+ matches: (f) => {
3597
+ const base = pathBasename(f);
3598
+ return base === "Chart.yaml" || base === "Chart.yml";
3599
+ },
3600
+ entity: "helm"
3601
+ },
3602
+ {
3603
+ // Kustomize is the one Kubernetes layout with a distinctive filename. Bare
3604
+ // manifests declare themselves only in their content (`apiVersion:`/`kind:`),
3605
+ // which a path-only rule cannot see. `kustomization.yaml` is exact; the
3606
+ // directory rule covers the far more common layout — loose manifests under
3607
+ // `k8s/`, `kubernetes/` or `manifests/`, applied with `kubectl apply -f`.
3608
+ //
3609
+ // Gating on `kustomization.yaml` alone dropped 14 default-on Opengrep rules
3610
+ // (privileged-container, secrets-in-config-file, skip-tls-verify-*, …) from
3611
+ // exactly that layout. Per the bias above, a directory name matched wrongly
3612
+ // only enables inert checks, while missing it loses real coverage.
3613
+ name: "k8s manifests",
3614
+ matches: (f) => {
3615
+ const base = pathBasename(f);
3616
+ if (base === "kustomization.yaml" || base === "kustomization.yml") return true;
3617
+ return /(^|\/)(k8s|kubernetes|manifests)\/.+\.ya?ml$/.test(f);
3618
+ },
3619
+ entity: "kubernetes"
3620
+ },
3621
+ {
3622
+ // `ansible.cfg` is unambiguous; the `roles/<name>/tasks/main.yml` layout is
3623
+ // Ansible's own role convention and is the only marker in a playbook repo
3624
+ // that ships no config file.
3625
+ name: "ansible.cfg",
3626
+ matches: (f) => pathBasename(f) === "ansible.cfg" || /(^|\/)roles\/[^/]+\/tasks\/main\.ya?ml$/.test(f),
3627
+ entity: "ansible"
3628
+ },
3629
+ {
3630
+ name: "serverless.yml",
3631
+ matches: (f) => /(^|\/)serverless\.(ya?ml|json|ts|js)$/.test(f),
3632
+ entity: "serverless-framework"
3633
+ },
3634
+ {
3635
+ // Bicep compiles to ARM, so `.bicep` is Azure IaC. Plain ARM templates are
3636
+ // JSON identified by their `$schema`, which needs a content probe.
3637
+ name: "*.bicep",
3638
+ matches: (f) => {
3639
+ const base = pathBasename(f);
3640
+ return base.endsWith(".bicep") || base.endsWith(".bicepparam");
3641
+ },
3642
+ entity: "bicep"
3643
+ },
3644
+ {
3645
+ // A CloudFormation template is identified by `AWSTemplateFormatVersion` in its
3646
+ // body, not by its name, so this covers only the conventional shells: the SAM
3647
+ // CLI's config file, SAM's root `template.yaml`, and a `cloudformation/`
3648
+ // directory. Deliberately loose per the note above — over-matching enables AWS
3649
+ // checks that then find nothing, under-matching drops 456 of them.
3650
+ name: "cloudformation template",
3651
+ matches: (f) => {
3652
+ const base = pathBasename(f);
3653
+ if (base === "samconfig.toml" || base === "samconfig.yaml" || base === "samconfig.yml") {
3654
+ return true;
3655
+ }
3656
+ if (f === "template.yaml" || f === "template.yml") return true;
3657
+ return /(^|\/)cloudformation\//.test(f);
3658
+ },
3659
+ entity: "cloudformation"
3660
+ },
3661
+ {
3662
+ // Play's routes file — `conf/routes`, plus the `conf/*.routes` sub-files a
3663
+ // multi-module app includes from it (Codacy's own website uses
3664
+ // `app/conf/website.routes`, `modules/legacy-api/conf/api.routes`). Nothing
3665
+ // but Play uses this layout, and it is version-independent, unlike the
3666
+ // artifact names. Note `conf/application.conf` is NOT a signal: plain
3667
+ // Typesafe Config uses the same filename without any Play involvement.
3668
+ name: "conf/routes",
3669
+ matches: (f) => /(^|\/)conf\/([^/]+\.)?routes$/.test(f),
3670
+ entity: "play-framework"
3671
+ }
3672
+ ];
3673
+ function detectFileSignals(files) {
3674
+ const result = [];
3675
+ for (const rule of FILE_SIGNAL_RULES) {
3676
+ const hit = files.find((f) => rule.matches(f));
3677
+ if (!hit) continue;
3678
+ const entity = import_tooling5.DEPENDENCY_ENTITIES[rule.entity];
3679
+ result.push({
3680
+ name: rule.name,
3681
+ key: entity.key,
3682
+ displayName: entity.displayName,
3683
+ kind: entity.kind,
3684
+ source: "file",
3685
+ manifestFile: hit
3686
+ });
3687
+ }
3688
+ return result;
3166
3689
  }
3167
- function parsePackageSwift(content) {
3168
- const deps = [];
3169
- const pkgRegex = /\.package\s*\(\s*url:\s*["'](?:https?:\/\/[^/]+\/[^/]+\/|git@[^:]+:[^/]+\/)([^"'/.]+)/g;
3170
- let match;
3171
- while ((match = pkgRegex.exec(content)) !== null) {
3172
- deps.push({ name: match[1] });
3173
- }
3174
- return deps;
3690
+ function isEslintPluginName(name) {
3691
+ return /^eslint-plugin-/.test(name) || /^@[^/]+\/eslint-plugin(-.*)?$/.test(name);
3175
3692
  }
3176
- function parsePubspecYaml(content) {
3177
- const deps = [];
3178
- let inSection = false;
3179
- let childIndent = -1;
3180
- for (const rawLine of content.split("\n")) {
3181
- const trimmed = rawLine.trim();
3182
- if (trimmed === "" || trimmed.startsWith("#")) continue;
3183
- const indent = rawLine.length - rawLine.trimStart().length;
3184
- if (indent === 0) {
3185
- inSection = /^(dependencies|dev_dependencies|dependency_overrides):/.test(trimmed);
3186
- childIndent = -1;
3187
- continue;
3188
- }
3189
- if (!inSection) continue;
3190
- if (childIndent === -1) childIndent = indent;
3191
- if (indent !== childIndent) continue;
3192
- const m = trimmed.match(/^([a-zA-Z0-9_.]+)\s*:(?:\s*["']?\^?([0-9][^"'#\s]*))?/);
3193
- if (m) deps.push({ name: m[1], version: m[2] });
3693
+ function detectPackageJsonSignals(content) {
3694
+ let pkg;
3695
+ try {
3696
+ pkg = JSON.parse(content);
3697
+ } catch {
3698
+ return [];
3194
3699
  }
3195
- return deps;
3196
- }
3197
- function parseShardYml(content) {
3198
- const deps = [];
3199
- let inSection = false;
3200
- let depIndent = -1;
3201
- let current;
3202
- const flush = () => {
3203
- if (current) deps.push(current);
3204
- current = void 0;
3205
- };
3206
- for (const rawLine of content.split("\n")) {
3207
- const trimmed = rawLine.trim();
3208
- if (trimmed === "" || trimmed.startsWith("#")) continue;
3209
- const indent = rawLine.length - rawLine.trimStart().length;
3210
- if (indent === 0) {
3211
- flush();
3212
- inSection = /^(dependencies|development_dependencies):/.test(trimmed);
3213
- depIndent = -1;
3214
- continue;
3215
- }
3216
- if (!inSection) continue;
3217
- if (depIndent === -1) depIndent = indent;
3218
- if (indent === depIndent) {
3219
- flush();
3220
- const m = trimmed.match(/^([a-zA-Z0-9_-]+)\s*:/);
3221
- current = m ? { name: m[1] } : void 0;
3222
- continue;
3223
- }
3224
- if (indent > depIndent && current) {
3225
- const v = trimmed.match(/^version\s*:\s*["']?(?:~>|[~^])?\s*([0-9][^"'\s]*)/);
3226
- if (v) current.version = v[1];
3227
- }
3700
+ const name = pkg && typeof pkg.name === "string" ? pkg.name : "";
3701
+ if (name && isEslintPluginName(name)) {
3702
+ const entity = import_tooling5.DEPENDENCY_ENTITIES["eslint-plugin"];
3703
+ return [
3704
+ {
3705
+ name,
3706
+ key: entity.key,
3707
+ displayName: entity.displayName,
3708
+ kind: entity.kind,
3709
+ source: "manifest",
3710
+ manifestFile: "package.json",
3711
+ ecosystem: "npm"
3712
+ }
3713
+ ];
3228
3714
  }
3229
- flush();
3230
- return deps;
3715
+ return [];
3231
3716
  }
3232
- function parseXmlDependencyElements(content, elementRegex, nameAttr) {
3233
- const deps = [];
3234
- const nameRegex = new RegExp(`\\b${nameAttr}\\s*=\\s*["']([^"']+)["']`, "i");
3235
- const versionRegex = /\bversion\s*=\s*["']([^"']+)["']/i;
3236
- let match;
3237
- while ((match = elementRegex.exec(content)) !== null) {
3238
- const attrs = match[1];
3239
- const nameMatch = attrs.match(nameRegex);
3240
- if (!nameMatch) continue;
3241
- const versionMatch = attrs.match(versionRegex);
3242
- deps.push({ name: nameMatch[1].trim(), version: versionMatch?.[1]?.trim() });
3717
+
3718
+ // src/discover/classify.ts
3719
+ var TELLTALE_INDEX = /* @__PURE__ */ new Map();
3720
+ function telltaleIndex(ecosystem) {
3721
+ let index = TELLTALE_INDEX.get(ecosystem);
3722
+ if (!index) {
3723
+ index = new Map(getEcosystem(ecosystem).telltales);
3724
+ TELLTALE_INDEX.set(ecosystem, index);
3725
+ }
3726
+ return index;
3727
+ }
3728
+ function classifyDependency(name, ecosystem) {
3729
+ const exact = telltaleIndex(ecosystem).get(name);
3730
+ if (exact) return import_tooling6.DEPENDENCY_ENTITIES[exact];
3731
+ for (const [prefix, entity] of getEcosystem(ecosystem).prefixes ?? []) {
3732
+ if (name.startsWith(prefix)) return import_tooling6.DEPENDENCY_ENTITIES[entity];
3243
3733
  }
3244
- return deps;
3245
- }
3246
- function parseCsproj(content) {
3247
- return parseXmlDependencyElements(
3248
- content,
3249
- /<Package(?:Reference|Version)\s+([^>]+?)\/?>/gi,
3250
- "Include"
3251
- );
3734
+ return void 0;
3252
3735
  }
3253
- function parsePackagesConfig(content) {
3254
- return parseXmlDependencyElements(content, /<package\s+([^>]+?)\/?>/gi, "id");
3736
+ function getKnownDisplayNames() {
3737
+ return new Set(Object.values(import_tooling6.DEPENDENCY_ENTITIES).map((entity) => entity.displayName));
3255
3738
  }
3256
- var MANIFEST_PARSERS = /* @__PURE__ */ new Map([
3257
- ["package.json", parsePackageJson],
3258
- ["requirements.txt", parseRequirementsTxt],
3259
- ["pyproject.toml", parsePyprojectToml],
3260
- ["Pipfile", parsePipfile],
3261
- ["Gemfile", parseGemfile],
3262
- ["go.mod", parseGoMod],
3263
- ["pom.xml", parsePomXml],
3264
- ["build.gradle", parseBuildGradle],
3265
- ["build.gradle.kts", parseBuildGradle],
3266
- ["build.sbt", parseBuildSbt],
3267
- ["Cargo.toml", parseCargoToml],
3268
- ["composer.json", parseComposerJson],
3269
- ["Podfile", parsePodfile],
3270
- ["Package.swift", parsePackageSwift],
3271
- ["pubspec.yaml", parsePubspecYaml],
3272
- ["mix.exs", parseMixExs],
3273
- ["shard.yml", parseShardYml],
3274
- ["packages.config", parsePackagesConfig],
3275
- ["Directory.Packages.props", parseCsproj]
3739
+
3740
+ // src/discover/notable-files.ts
3741
+ var NOTABLE_ANY_DEPTH = /* @__PURE__ */ new Set([
3742
+ "Dockerfile",
3743
+ "docker-compose.yml",
3744
+ "docker-compose.yaml",
3745
+ "compose.yml",
3746
+ "compose.yaml",
3747
+ "Makefile",
3748
+ "Justfile",
3749
+ "pom.xml",
3750
+ "build.gradle",
3751
+ "build.gradle.kts",
3752
+ "build.sbt",
3753
+ "go.mod",
3754
+ "Cargo.toml",
3755
+ "Gemfile",
3756
+ "Podfile",
3757
+ "Package.swift",
3758
+ "Jenkinsfile",
3759
+ "Vagrantfile",
3760
+ "Procfile",
3761
+ "pubspec.yaml",
3762
+ "mix.exs",
3763
+ "shard.yml",
3764
+ "packages.config",
3765
+ "Directory.Packages.props"
3276
3766
  ]);
3277
- var MANIFEST_SUFFIX_PARSERS = /* @__PURE__ */ new Map([
3278
- [".csproj", parseCsproj],
3279
- [".vbproj", parseCsproj],
3280
- [".fsproj", parseCsproj]
3767
+ var NOTABLE_ROOT_ONLY = /* @__PURE__ */ new Set([
3768
+ ".dockerignore",
3769
+ "Taskfile.yml",
3770
+ "Rakefile",
3771
+ "Gruntfile.js",
3772
+ "Gulpfile.js",
3773
+ "settings.gradle",
3774
+ "settings.gradle.kts",
3775
+ "go.sum",
3776
+ "Cargo.lock",
3777
+ "CLAUDE.md",
3778
+ "AGENTS.md",
3779
+ "agents.md",
3780
+ ".cursorrules",
3781
+ "copilot-instructions.md",
3782
+ ".travis.yml",
3783
+ ".gitlab-ci.yml",
3784
+ "azure-pipelines.yml",
3785
+ "bitbucket-pipelines.yml",
3786
+ "appveyor.yml",
3787
+ "package.json",
3788
+ "requirements.txt",
3789
+ "pyproject.toml",
3790
+ "Pipfile",
3791
+ "composer.json",
3792
+ "tsconfig.json",
3793
+ "jsconfig.json",
3794
+ "serverless.yml",
3795
+ "serverless.yaml",
3796
+ "cdk.json",
3797
+ "pulumi.yaml",
3798
+ "README.md",
3799
+ "CHANGELOG.md",
3800
+ "CONTRIBUTING.md",
3801
+ "LICENSE",
3802
+ "renovate.json",
3803
+ ".nvmrc",
3804
+ ".node-version",
3805
+ ".python-version",
3806
+ ".ruby-version",
3807
+ ".tool-versions"
3281
3808
  ]);
3809
+ var NOTABLE_PATH_PREFIXES = [".github/workflows/", ".circleci/", ".gitlab/"];
3810
+ var CONFIG_HEURISTIC_PATTERNS = [
3811
+ /^\..*rc$/,
3812
+ // .eslintrc, .prettierrc, etc.
3813
+ /^\..*rc\.(json|js|cjs|mjs|yml|yaml|ts)$/,
3814
+ // .eslintrc.json, etc.
3815
+ /^.*\.config\.(js|ts|mjs|cjs)$/,
3816
+ // vite.config.ts, jest.config.js, etc.
3817
+ /^\.env(\..+)?$/
3818
+ // .env, .env.local, .env.production
3819
+ ];
3820
+ function isNotableFile(filePath) {
3821
+ const basename = filePath.split("/").pop() ?? filePath;
3822
+ const isRoot = !filePath.includes("/");
3823
+ if (NOTABLE_ANY_DEPTH.has(basename)) return true;
3824
+ if (isRoot && NOTABLE_ROOT_ONLY.has(basename)) return true;
3825
+ if (/\.(csproj|vbproj|fsproj|sln)$/i.test(basename)) return true;
3826
+ for (const prefix of NOTABLE_PATH_PREFIXES) {
3827
+ if (filePath.startsWith(prefix)) return true;
3828
+ }
3829
+ if (!filePath.includes("/")) {
3830
+ for (const pattern of CONFIG_HEURISTIC_PATTERNS) {
3831
+ if (pattern.test(basename)) return true;
3832
+ }
3833
+ }
3834
+ return false;
3835
+ }
3282
3836
 
3283
- // src/discover.ts
3837
+ // src/discover/index.ts
3284
3838
  async function discover(repositoryRoot, options) {
3285
- const files = listRepoFiles(repositoryRoot);
3839
+ const unfiltered = listRepoFiles(repositoryRoot);
3840
+ const files = applyExclusions(unfiltered, [...options?.exclude ?? []], []);
3841
+ const excludedFiles = unfiltered.length - files.length;
3286
3842
  const filePatterns = extractFilePatterns(files);
3287
3843
  const notableFiles = findNotableFiles(files);
3288
3844
  const languages = await detectLanguages(repositoryRoot, files);
@@ -3305,7 +3861,10 @@ async function discover(repositoryRoot, options) {
3305
3861
  metadata: {
3306
3862
  repositoryRoot: import_path6.default.resolve(repositoryRoot),
3307
3863
  totalFiles: files.length,
3308
- scannedAt: (/* @__PURE__ */ new Date()).toISOString()
3864
+ scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
3865
+ // Surfaced so a scan narrowed by excludes is never mistaken for a repo
3866
+ // that genuinely lacks those languages.
3867
+ ...excludedFiles > 0 ? { excludedFiles } : {}
3309
3868
  }
3310
3869
  };
3311
3870
  }
@@ -3328,43 +3887,33 @@ function findNotableFiles(files) {
3328
3887
  }
3329
3888
  return Array.from(notable).sort();
3330
3889
  }
3331
- function resolveManifestParser(filePath) {
3332
- const basename = filePath.split("/").pop() ?? filePath;
3333
- const byName = MANIFEST_PARSERS.get(basename);
3334
- if (byName) return byName;
3335
- const lowerBasename = basename.toLowerCase();
3336
- for (const [suffix, parser] of MANIFEST_SUFFIX_PARSERS) {
3337
- if (lowerBasename.endsWith(suffix)) return parser;
3338
- }
3339
- return void 0;
3340
- }
3341
3890
  async function extractDependencies(repositoryRoot, files) {
3342
3891
  const allDeps = [];
3343
3892
  for (const filePath of files) {
3344
- const parser = resolveManifestParser(filePath);
3345
- if (!parser) continue;
3893
+ const manifest = resolveManifest(filePath);
3894
+ if (!manifest) continue;
3346
3895
  let content;
3347
3896
  try {
3348
3897
  content = await import_promises2.default.readFile(import_path6.default.join(repositoryRoot, filePath), "utf-8");
3349
3898
  } catch {
3350
3899
  continue;
3351
3900
  }
3352
- const basename = filePath.split("/").pop() ?? filePath;
3353
- const rawDeps = parser(content);
3354
- for (const raw of rawDeps) {
3355
- const classification = classifyDependency(raw.name, basename);
3356
- if (classification) {
3901
+ for (const raw of manifest.parser(content)) {
3902
+ const entity = classifyDependency(raw.name, manifest.ecosystem);
3903
+ if (entity) {
3357
3904
  allDeps.push({
3358
3905
  name: raw.name,
3359
- displayName: classification.displayName,
3360
- kind: classification.kind,
3906
+ key: entity.key,
3907
+ displayName: entity.displayName,
3908
+ kind: entity.kind,
3361
3909
  source: "manifest",
3362
3910
  manifestFile: filePath,
3363
- version: raw.version
3911
+ version: raw.version,
3912
+ ecosystem: manifest.ecosystem
3364
3913
  });
3365
3914
  }
3366
3915
  }
3367
- if (basename === "package.json") {
3916
+ if (filePath.split("/").pop() === "package.json") {
3368
3917
  for (const signal of detectPackageJsonSignals(content)) {
3369
3918
  allDeps.push({ ...signal, manifestFile: filePath });
3370
3919
  }
@@ -3376,8 +3925,8 @@ function deduplicateDeps(deps) {
3376
3925
  const seen = /* @__PURE__ */ new Set();
3377
3926
  const result = [];
3378
3927
  for (const dep of deps) {
3379
- if (!seen.has(dep.displayName)) {
3380
- seen.add(dep.displayName);
3928
+ if (!seen.has(dep.key)) {
3929
+ seen.add(dep.key);
3381
3930
  result.push(dep);
3382
3931
  }
3383
3932
  }
@@ -3469,6 +4018,28 @@ function applyCodacyYamlExcludes(config, yamlConfig, adapters, logger) {
3469
4018
  function deduplicateStrings(arr) {
3470
4019
  return [...new Set(arr)];
3471
4020
  }
4021
+ async function resolveDiscoveryExcludes(repositoryRoot, adapters, existingConfig) {
4022
+ const yamlConfig = await readCodacyYaml(repositoryRoot);
4023
+ const global = deduplicateStrings([
4024
+ ...existingConfig?.exclude ?? [],
4025
+ ...yamlConfig?.excludePaths ?? []
4026
+ ]);
4027
+ const perTool = /* @__PURE__ */ new Map();
4028
+ const addToolExcludes = (toolId, patterns) => {
4029
+ if (patterns.length === 0) return;
4030
+ perTool.set(toolId, deduplicateStrings([...perTool.get(toolId) ?? [], ...patterns]));
4031
+ };
4032
+ for (const tool of existingConfig?.tools ?? []) {
4033
+ addToolExcludes(tool.toolId, tool.exclude ?? []);
4034
+ }
4035
+ for (const [engineName, engineConfig] of yamlConfig?.engines ?? []) {
4036
+ if (engineConfig.excludePaths.length === 0) continue;
4037
+ const adapterId = resolveEngineToAdapter(engineName, adapters);
4038
+ if (!adapterId) continue;
4039
+ addToolExcludes(adapterId, engineConfig.excludePaths);
4040
+ }
4041
+ return { yamlConfig, global, perTool };
4042
+ }
3472
4043
 
3473
4044
  // src/api/client/core/OpenAPI.ts
3474
4045
  var OpenAPI = {
@@ -6908,8 +7479,14 @@ var ToolsService = class {
6908
7479
 
6909
7480
  // src/api/index.ts
6910
7481
  OpenAPI.BASE = (process.env.CODACY_API_BASE_URL || "https://app.codacy.com").replace(/\/$/, "") + "/api/v3";
6911
- function configureApiToken(token) {
6912
- OpenAPI.HEADERS = { "api-token": token };
7482
+ function configureApiToken(auth) {
7483
+ const resolved = typeof auth === "string" ? { kind: "account-token", token: auth } : auth;
7484
+ if (!resolved || resolved.kind !== "account-token" && resolved.kind !== "repository-token" || !resolved.token) {
7485
+ throw new Error(
7486
+ 'configureApiToken requires a non-empty apiToken string, or a RemoteAuth object ({ kind: "account-token" | "repository-token", token: string }).'
7487
+ );
7488
+ }
7489
+ OpenAPI.HEADERS = resolved.kind === "account-token" ? { "api-token": resolved.token } : { "project-token": resolved.token };
6913
7490
  }
6914
7491
  async function fetchAllPages(fetcher) {
6915
7492
  const results = [];
@@ -7002,6 +7579,10 @@ async function listIgnoredFiles(provider, org, repo) {
7002
7579
  }
7003
7580
 
7004
7581
  // src/init.ts
7582
+ function isAuthFailure(err) {
7583
+ return err instanceof ApiError && (err.status === 401 || err.status === 403);
7584
+ }
7585
+ 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.";
7005
7586
  function deriveApiToolId(tool) {
7006
7587
  return tool.prefix ? tool.prefix.replace(/_$/, "") : tool.shortName;
7007
7588
  }
@@ -7033,7 +7614,7 @@ function buildAdapterByUuid(adapters, apiTools) {
7033
7614
  }
7034
7615
  return result;
7035
7616
  }
7036
- async function detectLanguages(repositoryRoot, precomputedFiles) {
7617
+ async function detectLanguages(repositoryRoot, precomputedFiles, exclude) {
7037
7618
  let files;
7038
7619
  if (precomputedFiles) {
7039
7620
  files = precomputedFiles;
@@ -7049,11 +7630,14 @@ async function detectLanguages(repositoryRoot, precomputedFiles) {
7049
7630
  return [];
7050
7631
  }
7051
7632
  }
7633
+ if (exclude && exclude.length > 0) {
7634
+ files = applyExclusions(files, [...exclude], []);
7635
+ }
7052
7636
  const detected = /* @__PURE__ */ new Set();
7053
7637
  for (const file of files) {
7054
7638
  const basename = import_path8.default.basename(file);
7055
7639
  const ext = import_path8.default.extname(file).toLowerCase();
7056
- for (const [lang, config] of Object.entries(import_tooling5.LANGUAGE_CONFIG)) {
7640
+ for (const [lang, config] of Object.entries(import_tooling7.LANGUAGE_CONFIG)) {
7057
7641
  if (config.extensions.includes(ext)) {
7058
7642
  detected.add(lang);
7059
7643
  } else if (config.files.includes(basename)) {
@@ -7087,16 +7671,18 @@ async function detectLocalConfigFiles(repositoryRoot, adapters) {
7087
7671
  }
7088
7672
  return found;
7089
7673
  }
7090
- async function enrichWithCodacyYaml(config, repositoryRoot, adapters) {
7091
- const yamlConfig = await readCodacyYaml(repositoryRoot);
7092
- if (!yamlConfig) return { config, codacyYamlApplied: false };
7674
+ function enrichWithCodacyYaml(config, adapters, excludes) {
7675
+ if (!excludes.yamlConfig) return { config, codacyYamlApplied: false };
7093
7676
  return {
7094
- config: applyCodacyYamlExcludes(config, yamlConfig, adapters),
7677
+ config: applyCodacyYamlExcludes(config, excludes.yamlConfig, adapters),
7095
7678
  codacyYamlApplied: true
7096
7679
  };
7097
7680
  }
7098
- async function initLocalConfigBase(repositoryRoot, adapters) {
7099
- const languages = await detectLanguages(repositoryRoot);
7681
+ async function initLocalConfigBase(repositoryRoot, adapters, excludes) {
7682
+ const languages = await detectLanguages(
7683
+ repositoryRoot,
7684
+ listRepoFiles(repositoryRoot, excludes.global)
7685
+ );
7100
7686
  const localConfigs = await detectLocalConfigFiles(repositoryRoot, adapters);
7101
7687
  const tools = [];
7102
7688
  for (const [toolId, configPath] of localConfigs.entries()) {
@@ -7109,17 +7695,23 @@ async function initLocalConfigBase(repositoryRoot, adapters) {
7109
7695
  }
7110
7696
  return createCodacyConfig({ source: "local", languages, tools });
7111
7697
  }
7112
- async function initLocalConfig(repositoryRoot, adapters) {
7113
- const baseConfig = await initLocalConfigBase(repositoryRoot, adapters);
7114
- const { config, codacyYamlApplied } = await enrichWithCodacyYaml(
7115
- baseConfig,
7698
+ async function initLocalConfig(repositoryRoot, adapters, options) {
7699
+ const excludes = await resolveDiscoveryExcludes(
7116
7700
  repositoryRoot,
7117
- adapters
7701
+ adapters,
7702
+ options?.existingConfig
7118
7703
  );
7704
+ const baseConfig = await initLocalConfigBase(repositoryRoot, adapters, excludes);
7705
+ const { config, codacyYamlApplied } = enrichWithCodacyYaml(baseConfig, adapters, excludes);
7119
7706
  return { config, stats: { codacyYamlApplied } };
7120
7707
  }
7121
- async function initDefaultConfig(repositoryRoot, adapters) {
7122
- const base = await initLocalConfigBase(repositoryRoot, adapters);
7708
+ async function initDefaultConfig(repositoryRoot, adapters, options) {
7709
+ const excludes = await resolveDiscoveryExcludes(
7710
+ repositoryRoot,
7711
+ adapters,
7712
+ options?.existingConfig
7713
+ );
7714
+ const base = await initLocalConfigBase(repositoryRoot, adapters, excludes);
7123
7715
  const toolsWithLocalConfig = new Set(
7124
7716
  base.tools.filter((t) => t.useLocalConfigurationFile).map((t) => t.toolId)
7125
7717
  );
@@ -7157,24 +7749,27 @@ async function initDefaultConfig(repositoryRoot, adapters) {
7157
7749
  languages: base.metadata.languages,
7158
7750
  tools: updatedTools
7159
7751
  });
7160
- const { config, codacyYamlApplied } = await enrichWithCodacyYaml(
7161
- baseConfig,
7162
- repositoryRoot,
7163
- adapters
7164
- );
7752
+ const { config, codacyYamlApplied } = enrichWithCodacyYaml(baseConfig, adapters, excludes);
7165
7753
  return {
7166
7754
  config,
7167
7755
  stats: { totalApiTools: apiTools.length, addedTools, addedUnsupported, codacyYamlApplied }
7168
7756
  };
7169
7757
  }
7170
- async function initRemoteConfig(repositoryRoot, provider, organization, repositoryName, adapters, apiToken, onRemoteToolsFound, onToolProcessed, onIgnoredFilesFetched) {
7171
- configureApiToken(apiToken);
7758
+ async function initRemoteConfig(repositoryRoot, provider, organization, repositoryName, adapters, auth, onRemoteToolsFound, onToolProcessed, onIgnoredFilesFetched) {
7759
+ configureApiToken(auth);
7760
+ const repositoryLabel = `${provider}/${organization}/${repositoryName}`;
7172
7761
  const repoInfo = await getRepository(provider, organization, repositoryName);
7173
7762
  const remoteTools = await listRepositoryTools(provider, organization, repositoryName);
7174
7763
  let ignoredFilesResult = { files: [], totalFetched: 0, capped: false };
7175
7764
  try {
7176
7765
  ignoredFilesResult = await listIgnoredFiles(provider, organization, repositoryName);
7177
- } catch {
7766
+ } catch (err) {
7767
+ if (isAuthFailure(err)) {
7768
+ throw new Error(
7769
+ `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.`,
7770
+ { cause: err }
7771
+ );
7772
+ }
7178
7773
  }
7179
7774
  onIgnoredFilesFetched?.(ignoredFilesResult.totalFetched, ignoredFilesResult.capped);
7180
7775
  const localConfigs = await detectLocalConfigFiles(repositoryRoot, adapters);
@@ -7183,7 +7778,7 @@ async function initRemoteConfig(repositoryRoot, provider, organization, reposito
7183
7778
  const apiToolByUuid = new Map(allApiTools.map((t) => [t.uuid, t]));
7184
7779
  const matchedAdapterCount = remoteTools.filter((t) => adapterByUuid.has(t.uuid)).length;
7185
7780
  onRemoteToolsFound?.(remoteTools.length, matchedAdapterCount);
7186
- const fetchPatterns = async (uuid) => {
7781
+ const fetchPatterns = async (uuid, toolLabel) => {
7187
7782
  try {
7188
7783
  const configuredPatterns = await listRepositoryToolPatterns(
7189
7784
  provider,
@@ -7198,7 +7793,13 @@ async function initRemoteConfig(repositoryRoot, provider, organization, reposito
7198
7793
  ...params ? { parameters: params } : {}
7199
7794
  };
7200
7795
  });
7201
- } catch {
7796
+ } catch (err) {
7797
+ if (isAuthFailure(err)) {
7798
+ throw new Error(
7799
+ `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.`,
7800
+ { cause: err }
7801
+ );
7802
+ }
7202
7803
  return [];
7203
7804
  }
7204
7805
  };
@@ -7211,7 +7812,7 @@ async function initRemoteConfig(repositoryRoot, provider, organization, reposito
7211
7812
  if (adapter) {
7212
7813
  matchedAdapters++;
7213
7814
  const localConfigPath = localConfigs.get(adapter.id);
7214
- const patterns2 = usesLocalConfig ? [] : await fetchPatterns(remoteTool.uuid);
7815
+ const patterns2 = usesLocalConfig ? [] : await fetchPatterns(remoteTool.uuid, adapter.displayName);
7215
7816
  onToolProcessed?.(adapter.displayName, patterns2.length, usesLocalConfig);
7216
7817
  tools.push({
7217
7818
  toolId: adapter.id,
@@ -7226,7 +7827,7 @@ async function initRemoteConfig(repositoryRoot, provider, organization, reposito
7226
7827
  const apiTool = apiToolByUuid.get(remoteTool.uuid);
7227
7828
  if (!apiTool || !isTrackableTool(apiTool)) continue;
7228
7829
  const toolId = deriveApiToolId(apiTool);
7229
- const patterns = usesLocalConfig ? [] : await fetchPatterns(remoteTool.uuid);
7830
+ const patterns = usesLocalConfig ? [] : await fetchPatterns(remoteTool.uuid, apiTool.name ?? toolId);
7230
7831
  unsupportedTools++;
7231
7832
  onToolProcessed?.(apiTool.name ?? toolId, patterns.length, usesLocalConfig);
7232
7833
  tools.push({
@@ -7247,10 +7848,10 @@ async function initRemoteConfig(repositoryRoot, provider, organization, reposito
7247
7848
  if (ignoredFilesResult.files.length > 0) {
7248
7849
  baseConfig.exclude = [.../* @__PURE__ */ new Set([...baseConfig.exclude ?? [], ...ignoredFilesResult.files])];
7249
7850
  }
7250
- const { config, codacyYamlApplied } = await enrichWithCodacyYaml(
7851
+ const { config, codacyYamlApplied } = enrichWithCodacyYaml(
7251
7852
  baseConfig,
7252
- repositoryRoot,
7253
- adapters
7853
+ adapters,
7854
+ await resolveDiscoveryExcludes(repositoryRoot, adapters)
7254
7855
  );
7255
7856
  return {
7256
7857
  config,
@@ -7324,24 +7925,40 @@ function hasMatchingFiles(files, filePatterns) {
7324
7925
  if (matcher.matchAll) return files.length > 0;
7325
7926
  return files.some((file) => matcher.matches(file, file.split("/").pop() ?? ""));
7326
7927
  }
7327
- async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, onDiscoveryComplete, rawFilterString, loadUnsupportedPatterns) {
7328
- const discoveryResult = await discover(repositoryRoot);
7928
+ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, onDiscoveryComplete, rawFilterString, loadUnsupportedPatterns, options) {
7929
+ const excludes = await resolveDiscoveryExcludes(
7930
+ repositoryRoot,
7931
+ adapters,
7932
+ options?.existingConfig
7933
+ );
7934
+ const discoveryResult = await discover(repositoryRoot, { exclude: excludes.global });
7329
7935
  onDiscoveryComplete?.(discoveryResult);
7330
7936
  const detectedKeys = /* @__PURE__ */ new Set();
7331
- for (const lang of discoveryResult.languages) detectedKeys.add(lang);
7332
- for (const fw of discoveryResult.frameworks) detectedKeys.add(fw.displayName);
7333
- for (const lib of discoveryResult.libraries) detectedKeys.add(lib.displayName);
7937
+ for (const lang of discoveryResult.languages) detectedKeys.add((0, import_tooling7.languageKey)(lang));
7938
+ for (const fw of discoveryResult.frameworks) detectedKeys.add(fw.key);
7939
+ for (const lib of discoveryResult.libraries) detectedKeys.add(lib.key);
7334
7940
  const repoLanguages = new Set(discoveryResult.languages);
7335
7941
  const localConfigs = await detectLocalConfigFiles(repositoryRoot, adapters);
7336
- const repoFiles = listRepoFiles(repositoryRoot);
7942
+ const repoFiles = listRepoFiles(repositoryRoot, excludes.global);
7337
7943
  const descriptorMap = new Map(descriptors.map((d) => [d.id, d]));
7944
+ const perToolFiles = /* @__PURE__ */ new Map();
7945
+ const filesVisibleTo = (toolId) => {
7946
+ const toolExclude = excludes.perTool.get(toolId);
7947
+ if (!toolExclude || toolExclude.length === 0) return repoFiles;
7948
+ let files = perToolFiles.get(toolId);
7949
+ if (!files) {
7950
+ files = applyExclusions(repoFiles, [], [...toolExclude]);
7951
+ perToolFiles.set(toolId, files);
7952
+ }
7953
+ return files;
7954
+ };
7338
7955
  const candidates = [];
7339
7956
  for (const adapter of adapters) {
7340
7957
  if (!adapter.languages.includes("*") && adapter.languages.length > 0 && repoLanguages.size > 0) {
7341
7958
  const hasOverlap = adapter.languages.some((lang) => repoLanguages.has(lang));
7342
7959
  if (!hasOverlap) continue;
7343
7960
  }
7344
- if (!hasMatchingFiles(repoFiles, adapter.filePatterns)) continue;
7961
+ if (!hasMatchingFiles(filesVisibleTo(adapter.id), adapter.filePatterns)) continue;
7345
7962
  candidates.push(adapter);
7346
7963
  }
7347
7964
  const deduped = deduplicateToolFamilies(candidates, localConfigs);
@@ -7374,7 +7991,12 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
7374
7991
  patterns: selectedPatterns.map((p) => ({ patternId: p.id }))
7375
7992
  };
7376
7993
  tools.push(toolEntry);
7377
- await applyAdapterParameterSuggestions(adapter, toolEntry, repositoryRoot, repoFiles);
7994
+ await applyAdapterParameterSuggestions(
7995
+ adapter,
7996
+ toolEntry,
7997
+ repositoryRoot,
7998
+ filesVisibleTo(adapter.id)
7999
+ );
7378
8000
  }
7379
8001
  }
7380
8002
  let selectedUnsupported = 0;
@@ -7389,7 +8011,7 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
7389
8011
  }
7390
8012
  const family = toolGroupKey(desc.id, desc.alternativeGroup);
7391
8013
  if (selectedFamilies.has(family)) continue;
7392
- if (!hasMatchingFiles(repoFiles, desc.filePatterns)) continue;
8014
+ if (!hasMatchingFiles(filesVisibleTo(desc.id), desc.filePatterns)) continue;
7393
8015
  const patternsMetadata = loadUnsupportedPatterns(desc.id);
7394
8016
  if (patternsMetadata.length === 0) continue;
7395
8017
  const selectedPatterns = filterAutoPatterns(
@@ -7414,11 +8036,7 @@ async function initAutoConfig(repositoryRoot, adapters, descriptors, filters, on
7414
8036
  tools,
7415
8037
  autoFilters: rawFilterString || void 0
7416
8038
  });
7417
- const { config, codacyYamlApplied } = await enrichWithCodacyYaml(
7418
- baseConfig,
7419
- repositoryRoot,
7420
- adapters
7421
- );
8039
+ const { config, codacyYamlApplied } = enrichWithCodacyYaml(baseConfig, adapters, excludes);
7422
8040
  return {
7423
8041
  config,
7424
8042
  stats: {
@@ -7663,7 +8281,7 @@ function parseAutoFilters(raw) {
7663
8281
  }
7664
8282
 
7665
8283
  // src/index.ts
7666
- var import_tooling6 = require("@codacy/tooling");
8284
+ var import_tooling8 = require("@codacy/tooling");
7667
8285
 
7668
8286
  // src/credentials.ts
7669
8287
  var import_crypto = __toESM(require("crypto"));
@@ -7728,6 +8346,17 @@ async function resolveApiToken() {
7728
8346
  if (stored) return stored;
7729
8347
  throw new Error("No API token found. Set CODACY_API_TOKEN or run 'codacy-analysis login'.");
7730
8348
  }
8349
+ async function resolveRemoteAuth(repositoryToken) {
8350
+ const projectToken = repositoryToken || process.env.CODACY_PROJECT_TOKEN || void 0;
8351
+ if (projectToken) return { kind: "repository-token", token: projectToken };
8352
+ const apiToken = process.env.CODACY_API_TOKEN || await loadCredentials() || void 0;
8353
+ if (!apiToken) {
8354
+ throw new Error(
8355
+ "No API token found. Provide --repository-token, set CODACY_PROJECT_TOKEN, set CODACY_API_TOKEN, or run 'codacy-analysis login'."
8356
+ );
8357
+ }
8358
+ return { kind: "account-token", token: apiToken };
8359
+ }
7731
8360
 
7732
8361
  // src/git-info.ts
7733
8362
  var import_child_process5 = require("child_process");
@@ -7809,11 +8438,12 @@ function parseSarif(runs) {
7809
8438
  if (!patternId) continue;
7810
8439
  const level = mapSarifLevelToCodacy(result.level);
7811
8440
  const message = result.message?.text ?? "";
8441
+ const sourceId = result.properties?.sourceId;
7812
8442
  for (const loc of result.locations ?? []) {
7813
8443
  const filePath = loc.physicalLocation?.artifactLocation?.uri;
7814
8444
  const line = loc.physicalLocation?.region?.startLine;
7815
8445
  if (!filePath || !line) continue;
7816
- issues.push({ toolId, patternId, filePath, line, message, level });
8446
+ issues.push({ toolId, patternId, filePath, line, message, level, sourceId });
7817
8447
  }
7818
8448
  }
7819
8449
  }
@@ -7827,10 +8457,11 @@ function parseAnalysisResult(result) {
7827
8457
  line: issue.line,
7828
8458
  message: issue.message,
7829
8459
  // JSON carries the authoritative Codacy severity; pass it through unchanged.
7830
- level: issue.severity
8460
+ level: issue.severity,
8461
+ sourceId: issue.sourceId
7831
8462
  }));
7832
8463
  }
7833
- function buildPayloads(issues, resolveShortName) {
8464
+ function buildPayloads(issues, resolveShortName, needsSourceId = () => false) {
7834
8465
  const byTool = /* @__PURE__ */ new Map();
7835
8466
  for (const issue of issues) {
7836
8467
  const list = byTool.get(issue.toolId);
@@ -7839,6 +8470,7 @@ function buildPayloads(issues, resolveShortName) {
7839
8470
  }
7840
8471
  const payloads = [];
7841
8472
  for (const [toolId, toolIssues] of byTool) {
8473
+ const withSourceId = needsSourceId(toolId);
7842
8474
  const byFile = /* @__PURE__ */ new Map();
7843
8475
  for (const issue of toolIssues) {
7844
8476
  const entry = {
@@ -7847,7 +8479,8 @@ function buildPayloads(issues, resolveShortName) {
7847
8479
  filename: issue.filePath,
7848
8480
  message: { text: issue.message },
7849
8481
  level: issue.level,
7850
- location: { LineLocation: { line: issue.line } }
8482
+ location: { LineLocation: { line: issue.line } },
8483
+ ...withSourceId && issue.sourceId ? { sourceId: issue.sourceId } : {}
7851
8484
  }
7852
8485
  };
7853
8486
  const list = byFile.get(issue.filePath);
@@ -7879,6 +8512,13 @@ async function buildShortNameResolver(onProgress) {
7879
8512
  }
7880
8513
  return (toolId) => byName.get(toolId.toLowerCase()) ?? toolId.toLowerCase();
7881
8514
  }
8515
+ function buildSourceIdResolver() {
8516
+ const needsIt = /* @__PURE__ */ new Set();
8517
+ for (const descriptor of getRegisteredDescriptors()) {
8518
+ if (descriptor.needsSourceIdUpload) needsIt.add(descriptor.id.toLowerCase());
8519
+ }
8520
+ return (toolId) => needsIt.has(toolId.toLowerCase());
8521
+ }
7882
8522
  var COORDINATE_PATTERN = /^[A-Za-z0-9._-]+$/;
7883
8523
  function assertValidCoordinate(kind, value) {
7884
8524
  if (!COORDINATE_PATTERN.test(value)) {
@@ -7918,7 +8558,7 @@ async function uploadReport(options) {
7918
8558
  onProgress?.("No issues found in the report; nothing to upload.");
7919
8559
  }
7920
8560
  const resolveShortName = await buildShortNameResolver(onProgress);
7921
- const payloads = buildPayloads(issues, resolveShortName);
8561
+ const payloads = buildPayloads(issues, resolveShortName, buildSourceIdResolver());
7922
8562
  const base = resolveResultsApiBaseUrl();
7923
8563
  const urls = buildUrls(base, auth, commitUuid);
7924
8564
  const headers = { "content-type": "application/json", ...authHeader(auth) };
@@ -8019,9 +8659,11 @@ async function safeReadBody(res) {
8019
8659
  resolveApiToken,
8020
8660
  resolveBaselinePath,
8021
8661
  resolveConfigPath,
8662
+ resolveDiscoveryExcludes,
8022
8663
  resolveEngineToAdapter,
8023
8664
  resolveHeadCommit,
8024
8665
  resolveProxyConfig,
8666
+ resolveRemoteAuth,
8025
8667
  resolveRepositoryRoot,
8026
8668
  resolveResultsApiBaseUrl,
8027
8669
  rotateLogFiles,