175 lines · 7.1 KB
import { chunks, MAX_IN_LIST, placeholders } from "../db";
import { repoDb, stmt, type ScopedStatement } from "../scoped-db";
import { packageKey, publishedAdvisoryCounts } from "./advisory-packages";
import { loadRepoById } from "../repos";
import { withStore } from "../git/store";
import { branchSha, refreshRefs } from "../git/refs";
import { manifestKind } from "./manifests";
import type { Dependency, Ecosystem } from "./manifests";
import { notifyDependencyAlerts } from "../notify/security";
import { loadSecuritySettings } from "./settings";

const MAX_TREE_ENTRIES = 20_000;
const MAX_MANIFESTS = 50;
const MAX_MANIFEST_BYTES = 2 * 1024 * 1024;
const MAX_DEPENDENCIES = 5000;
const DEPENDENCY_COLUMNS = 7;
const STATEMENTS_PER_BATCH = 50;
const DEPENDENCY_PAGE = 100;
const MAX_NAME_LENGTH = 214;
const MAX_REQUIREMENT_LENGTH = 100;
const decoder = new TextDecoder();

type Parsed = { path: string; ecosystem: Ecosystem; dependencies: Dependency[] };

/** Rebuilds the dependency graph for a commit from its manifests and lockfiles, within fixed budgets. */
export async function scanDependencies(repoId: string, sha: string): Promise<number> {
  const repo = await loadRepoById(repoId);
  if (!repo || !(await loadSecuritySettings(repo.id)).dependencyGraph) return 0;
  const parsed = await withStore(repo.artifacts_name, async (store) => {
    const commit = await store.commit(sha);
    if (!commit) return null;
    const { entries } = await store.walk(commit.treeSha, MAX_TREE_ENTRIES);
    const manifests = entries
      .filter((entry) => entry.type === "blob" && manifestKind(entry.path))
      .slice(0, MAX_MANIFESTS);
    const out: Parsed[] = [];
    let total = 0;
    for (const entry of manifests) {
      const kind = manifestKind(entry.path);
      const blob = kind ? await store.blob(entry.sha) : null;