> ## Documentation Index
> Fetch the complete documentation index at: https://docs.endorlabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Enable auto detection

> Learn how to automatically detect toolchains used in your repository.

export const Diagram = ({children}) => {
  const [svg, setSvg] = useState('');
  const [error, setError] = useState(null);
  const [mounted, setMounted] = useState(false);
  const [id] = useState(() => `diagram-${Math.random().toString(36).slice(2)}`);
  const DEFAULTS = {
    theme: 'base',
    fontSize: '14px',
    fontFamily: 'inherit',
    primaryColor: '#26D07C',
    primaryTextColor: '#000000',
    primaryBorderColor: '#059669',
    secondaryColor: '#e5e7eb',
    secondaryTextColor: '#000000',
    secondaryBorderColor: '#9ca3af',
    tertiaryColor: '#e5e7eb',
    tertiaryTextColor: '#000000',
    tertiaryBorderColor: '#9ca3af',
    lineColor: '#6b7280',
    background: '#ffffff',
    edgeLabelBackground: '#f9fafb',
    clusterBkg: '#f0fdf4',
    clusterBorder: '#059669',
    nodeTextColor: '#000000',
    endorColor: '#26D07C',
    endorBorder: '#059669',
    managedColor: '#A7F3D0',
    managedBorder: '#059669',
    externalColor: '#e5e7eb',
    externalBorder: '#9ca3af'
  };
  const VAR_LINE_RE = /^(\w+):\s*(.+)$/;
  const TOKEN_RE = /\{\{(\w+)\}\}/g;
  const parseVarsBlock = raw => {
    const vars = {};
    const lines = raw.trim().split('\n');
    const diagramLines = [];
    let inVars = false;
    for (const line of lines) {
      const trimmed = line.trim();
      if (trimmed === '%%vars') {
        inVars = true;
        continue;
      }
      if (inVars && trimmed === '%%') {
        inVars = false;
        continue;
      }
      if (inVars) {
        const m = VAR_LINE_RE.exec(trimmed);
        if (m) vars[m[1]] = m[2];
      } else {
        diagramLines.push(line);
      }
    }
    return {
      vars,
      diagramLines
    };
  };
  const buildInitConfig = merged => ({
    theme: merged.theme,
    themeVariables: {
      fontSize: merged.fontSize,
      fontFamily: merged.fontFamily,
      primaryColor: merged.primaryColor,
      primaryTextColor: merged.primaryTextColor,
      primaryBorderColor: merged.primaryBorderColor,
      secondaryColor: merged.secondaryColor,
      secondaryTextColor: merged.secondaryTextColor,
      secondaryBorderColor: merged.secondaryBorderColor,
      tertiaryColor: merged.tertiaryColor,
      tertiaryTextColor: merged.tertiaryTextColor,
      tertiaryBorderColor: merged.tertiaryBorderColor,
      lineColor: merged.lineColor,
      background: merged.background,
      edgeLabelBackground: merged.edgeLabelBackground,
      clusterBkg: merged.clusterBkg,
      clusterBorder: merged.clusterBorder,
      nodeTextColor: merged.nodeTextColor
    }
  });
  const renderWithMermaid = (mermaid, fullDiagram) => {
    mermaid.initialize({
      startOnLoad: false,
      zoom: {
        enabled: false
      }
    });
    mermaid.render(id, fullDiagram).then(({svg: rendered}) => {
      setSvg(rendered);
      setError(null);
    }).catch(err => setError(err.message));
  };
  useEffect(() => {
    setMounted(true);
  }, []);
  useEffect(() => {
    if (!mounted || !children) return;
    const raw = typeof children === 'string' ? children : String(children);
    const {vars, diagramLines} = parseVarsBlock(raw);
    const merged = {
      ...DEFAULTS,
      ...vars
    };
    const diagram = diagramLines.join('\n').trim().replaceAll(TOKEN_RE, (_, key) => merged[key] || '');
    const fullDiagram = `%%{init: ${JSON.stringify(buildInitConfig(merged))}}%%\n${diagram}`;
    const existing = document.getElementById(id);
    if (existing) existing.remove();
    if (globalThis.mermaid) {
      renderWithMermaid(globalThis.mermaid, fullDiagram);
    } else {
      const script = document.createElement('script');
      script.src = 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js';
      script.onload = () => renderWithMermaid(globalThis.mermaid, fullDiagram);
      script.onerror = () => setError('Failed to load Mermaid');
      document.head.appendChild(script);
    }
  }, [mounted, children, id]);
  if (!mounted) return null;
  if (error) {
    return <pre style={{
      color: '#dc2626',
      background: '#fef2f2',
      padding: '12px',
      borderRadius: '6px',
      fontSize: '13px',
      overflowX: 'auto'
    }}>
        Diagram error: {error}
      </pre>;
  }
  if (!svg) {
    return <div style={{
      height: '200px',
      background: '#f3f4f6',
      borderRadius: '8px',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
      color: '#9ca3af',
      fontSize: '14px'
    }}>
        Loading diagram...
      </div>;
  }
  return <div dangerouslySetInnerHTML={{
    __html: svg
  }} style={{
    overflowX: 'auto',
    padding: '8px 0'
  }} />;
};

export const YamlTable = ({children, data: propData, content}) => {
  const KV_RE = /^([A-Za-z][A-Za-z0-9_()/#\s-]+?):\s*(.+)$/;
  const INLINE_MD_RE = /(\[([^\]]+)\]\(([^)]+)\))|(`([^`]+)`)|(\*\*([^*]+)\*\*)|(\*([^*]+)\*)/g;
  const YES_RE = /^-yes-$/i;
  const NO_RE = /^-no-$/i;
  const LIMITED_RE = /^-(limited|partial)-$/i;
  const NA_RE = /^-(na|none)-$/i;
  const NA2_RE = /^-na2-$/i;
  const SIMPLE_TAG_RE = /(<br\s*\/?>)|(<p\s*\/?>)|(-note-)|(-warning-)/gi;
  const tryParseKV = trimmed => {
    const m = KV_RE.exec(trimmed);
    return m ? {
      key: m[1],
      value: m[2].trim()
    } : null;
  };
  const registerKey = (key, seenKeys, orderedKeys) => {
    if (!seenKeys.has(key)) {
      orderedKeys.push(key);
      seenKeys.add(key);
    }
  };
  const flushEntry = (currentEntry, entries) => {
    if (Object.keys(currentEntry).length > 0) entries.push(currentEntry);
  };
  const parseDashPrefixed = (lines, entries, orderedKeys, seenKeys) => {
    let currentEntry = {};
    let inEntry = false;
    for (const line of lines) {
      const trimmed = line.trim();
      if (trimmed.startsWith('- ')) {
        if (inEntry) entries.push(currentEntry);
        currentEntry = {};
        inEntry = true;
        const kv = tryParseKV(trimmed.substring(2).trim());
        if (kv) {
          registerKey(kv.key, seenKeys, orderedKeys);
          currentEntry[kv.key] = kv.value;
        }
      } else if (inEntry && trimmed !== '') {
        const kv = tryParseKV(trimmed);
        if (kv) {
          registerKey(kv.key, seenKeys, orderedKeys);
          currentEntry[kv.key] = kv.value;
        }
      }
    }
    flushEntry(currentEntry, entries);
  };
  const parseBlankSeparated = (lines, entries, orderedKeys, seenKeys) => {
    let currentEntry = {};
    let inEntry = false;
    for (const line of lines) {
      const trimmed = line.trim();
      if (trimmed === '') {
        if (inEntry) {
          flushEntry(currentEntry, entries);
          currentEntry = {};
          inEntry = false;
        }
        continue;
      }
      const kv = tryParseKV(trimmed);
      if (!kv) continue;
      const isNewEntry = !line.startsWith(' ') && !line.startsWith('\t');
      if (isNewEntry && inEntry && Object.keys(currentEntry).length > 0) {
        entries.push(currentEntry);
        currentEntry = {};
      }
      registerKey(kv.key, seenKeys, orderedKeys);
      currentEntry[kv.key] = kv.value;
      inEntry = true;
    }
    flushEntry(currentEntry, entries);
  };
  const normalizeEntries = (entries, orderedKeys) => entries.map(entry => {
    const filled = {};
    for (const key of orderedKeys) filled[key] = entry[key] || '';
    return filled;
  });
  const parseYamlTableContent = contentStr => {
    if (!contentStr) return [];
    const entries = [];
    const orderedKeys = [];
    const seenKeys = new Set();
    const lines = contentStr.split('\n');
    if (lines.some(line => line.trim().startsWith('- '))) {
      parseDashPrefixed(lines, entries, orderedKeys, seenKeys);
    } else {
      parseBlankSeparated(lines, entries, orderedKeys, seenKeys);
    }
    return normalizeEntries(entries, orderedKeys);
  };
  const processText = text => {
    if (!text) return text;
    const parts = [];
    let keyIndex = 0;
    let lastIndex = 0;
    let match;
    while ((match = INLINE_MD_RE.exec(text)) !== null) {
      if (match.index > lastIndex) parts.push(text.slice(lastIndex, match.index));
      if (match[1]) {
        parts.push(<a key={keyIndex++} href={match[3]}>{match[2]}</a>);
      } else if (match[4]) {
        parts.push(<code key={keyIndex++}>{match[5]}</code>);
      } else if (match[6]) {
        parts.push(<strong key={keyIndex++}>{match[7]}</strong>);
      } else if (match[8]) {
        parts.push(<em key={keyIndex++}>{match[9]}</em>);
      }
      lastIndex = match.index + match[0].length;
    }
    if (lastIndex < text.length) parts.push(text.slice(lastIndex));
    if (parts.length === 0) return text;
    const keyRef = {
      current: keyIndex
    };
    return expandHtmlTags(parts, keyRef);
  };
  const processBadges = text => {
    if (!text || typeof text !== 'string') return text;
    if (YES_RE.test(text)) return <span className="yt-badge-yes" role="img" aria-label="Supported" title="Supported">✓</span>;
    if (NO_RE.test(text)) return <span className="yt-badge-no" role="img" aria-label="Not supported" title="Not supported">✗</span>;
    if (LIMITED_RE.test(text)) return <span className="yt-badge-limited" role="img" aria-label="Partially supported" title="Partially supported">◐</span>;
    if (NA_RE.test(text) || NA2_RE.test(text)) return <span className="yt-sr-only" title="Not applicable">Not applicable</span>;
    return processText(text);
  };
  const cellClassName = text => {
    if (!text || typeof text !== 'string') return undefined;
    if (NA_RE.test(text)) return 'yt-cell-na';
    if (NA2_RE.test(text)) return 'yt-cell-na2';
    return undefined;
  };
  const expandSimpleTags = (str, keyRef) => {
    const result = [];
    let last = 0;
    SIMPLE_TAG_RE.lastIndex = 0;
    let m;
    while ((m = SIMPLE_TAG_RE.exec(str)) !== null) {
      if (m.index > last) result.push(str.slice(last, m.index));
      if (m[1]) {
        result.push(<br key={keyRef.current++} />);
      } else if (m[2]) {
        result.push(<br key={keyRef.current++} />, <br key={keyRef.current++} />);
      } else if (m[3]) {
        result.push(<span key={keyRef.current++} className="yt-badge-note" style={{
          fontWeight: 600
        }}>Note: </span>);
      } else if (m[4]) {
        result.push(<span key={keyRef.current++} className="yt-badge-warning" style={{
          fontWeight: 600
        }}>Warning: </span>);
      }
      last = m.index + m[0].length;
    }
    if (last < str.length) result.push(str.slice(last));
    return result;
  };
  const expandHtmlTags = (chunks, keyRef) => {
    const out = [];
    for (const chunk of chunks) {
      if (typeof chunk === 'string') {
        out.push(...expandSimpleTags(chunk, keyRef));
      } else {
        out.push(chunk);
      }
    }
    return out;
  };
  const extractText = node => {
    if (node === null || node === undefined) return '';
    if (typeof node === 'string') return node;
    if (typeof node === 'number') return String(node);
    if (typeof node === 'boolean') return '';
    if (Array.isArray(node)) return node.map(extractText).join('');
    if (node && typeof node === 'object' && node.type) {
      const props = node.props || ({});
      if (typeof props.children === 'string') return props.children;
      if (props.children) return extractText(props.children);
      return '';
    }
    return String(node || '');
  };
  const [mounted, setMounted] = useState(false);
  useEffect(() => {
    setMounted(true);
  }, []);
  const data = useMemo(() => {
    if (propData) return propData;
    if (content && typeof content === 'string') return parseYamlTableContent(content);
    if (!children) return [];
    if (typeof children === 'string') return parseYamlTableContent(children);
    const childrenArray = Array.isArray(children) ? children : [children];
    return parseYamlTableContent(childrenArray.map(extractText).join('').trim());
  }, [children, propData, content]);
  const columns = useMemo(() => {
    if (!data || data.length === 0) return [];
    const firstRow = data[0];
    if (!firstRow || typeof firstRow !== 'object') return [];
    return Object.keys(firstRow);
  }, [data]);
  if (!mounted) return null;
  if (!data || data.length === 0) return null;
  const rowKey = row => columns.map(c => row[c] || '').join('|');
  return <table>
      <thead>
        <tr>
          {columns.map(col => <th key={col}>{col.replaceAll('_', ' ')}</th>)}
        </tr>
      </thead>
      <tbody>
        {data.map(row => <tr key={rowKey(row)}>
            {columns.map(col => <td key={col} className={cellClassName(row[col])}>{processBadges(row[col])}</td>)}
          </tr>)}
      </tbody>
    </table>;
};

The system can automatically detect toolchains required for your projects based on the manifest files present in your repository. Auto detection is supported for Java, Python, Go and .NET(C#) projects. Only the Long Term Support (LTS) versions of the toolchains are supported in auto detection. See the [Toolchain support matrix](/scan/scan-profiles/build-tools#toolchain-support-matrix) for a complete list of supported toolchain versions for auto detection.

### How auto detection works

Endor Labs begins auto detection by scanning your project repository to locate manifest files and identify the languages used in your project. Based on the results, it runs language specific detectors to extract version information. Each detector operates independently and follows a consistent process. It reviews the associated manifest or build configuration files to determine the toolchain version. If a file contains multiple version fields, the detector uses a fixed priority order to select the most appropriate one.

After identifying a version, the detector sends the version details to the assigner. The assigner checks the Endor Labs toolchain support matrix to verify if the version is supported for the host operating system and architecture. If it doesn’t find an exact match, it selects the closest supported version based on the major version number. This version will be the toolchain used for your project scan.

For example, when analyzing Java projects, the Java detector checks config files like `pom.xml` or `build.gradle` to find the Java version used in the project.

<Diagram>
  {`
    flowchart TD
    subgraph C["Detectors find versions of detected languages"]
      topSpacer[" "]:::hidden
      L1["language 1 detector"]
      Lang["..."]
      L2["language n detector"]
    end
    A(["Scan Repository"])
    B["Detect Languages"]
    D["Assigners"]
    F{"Exact match"}
    G(["Select closest supported version by matching major version"])
    H(["Select exact supported version"])
    I["Yes"]
    J["No"]
    K["Match version with Endor Labs suported versions"]

    A --> B --> C --> D --> K --> F
    F --> I --> H
    F --> J --> G

    %% Force black border on subgraph
    classDef subgraphStyle stroke:#000000,stroke-width:1px,fill:#00F078;
    class C subgraphStyle
    classDef JavaStyle fill:#D3D3D3;
    class L1,L2,Lang JavaStyle

    class topSpacer hidden
    classDef blueText fill:#3FE1F3,stroke:#000000,color:#000000;
    class I,J,K blueText

    %% NEW: Bigger node style and class
    classDef largeNode fill:#00F078,stroke:#000000,color:#000000;
    class A,B,D,E largeNode
    `}
</Diagram>

### Config files scanned for version detection

The following table lists the config files Endor Labs scans to detect the language and version used in your project.

<YamlTable>
  {`


    - Language: Java
    Build_Tool: Maven
    Config_File: \`pom.xml\`

    - Language: Java
    Build_Tool: Gradle
    Config_File: \`build.gradle\`, \`gradle-wrapper.properties\`

    - Language: .NET
    Build_Tool:
    Config_File: \`global.json\`, \`*.csproj\`, \`Directory.Build.props\`, \`Directory.Packages.props\`

    - Language: Golang
    Build_Tool:
    Config_File: \`go.mod\`

    - Language: Python
    Build_Tool:
    Config_File: \`setup.py\`, \`.python-version\`, \`pyproject.toml\`

    - Language: NodeJS
    Build_Tool:
    Config_File: \`package.json\`, \`.nvmrc\`, \`.node-version\`

    - Language: Yarn
    Build_Tool:
    Config_File: \`package.json\`, \`.yarnrc.yml\`, \`yarnrc\`

    - Language: pnpm
    Build_Tool:
    Config_File: \`package.json\`


    `}
</YamlTable>

The following examples illustrate how to define versions in each config file.

<AccordionGroup>
  <Accordion title="Java with Maven">
    Config file: `pom.xml`

    Define the Java version using any one of the following options:

    * Using version fields

      ```bash theme={null}
      <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.release>11</maven.compiler.release>
        <java.version>17</java.version>
      </properties>
      ```

    * Using plugin configuration

      ```bash theme={null}
      <build>
        <plugins>
          <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
            <source>11</source>
            <target>11</target>
            </configuration>
          </plugin>
        </plugins>
      </build>
      ```
  </Accordion>

  <Accordion title="Java with Gradle">
    Config file: `build.gradle`

    Define the Java version using `sourceCompatibility` and `targetCompatibility`.

    ```bash theme={null}
    sourceCompatibility='17'
    targetCompatibility='17'

    ```

    Ensure the Gradle wrapper version is defined in `gradle/wrapper/gradle-wrapper.properties`.

    ```bash theme={null}
    distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
    ```
  </Accordion>

  <Accordion title="Python">
    Specify the Python version in any of the following config files.

    <Accordion title="setup.py">
      Use `python_requires` inside the `setup()` block.

      ```bash theme={null}
      setup(
          ...
          python_requires='>=3.8, <4'
      )
      ```
    </Accordion>

    <Accordion title="pyproject.toml">
      Use `requires-python` to define the Python version range.

      ```toml theme={null}
      [project]
      requires-python = ">=3.8, <4.0"

      [tool.poetry.dependencies]
      python = "3.12.1"
      ```
    </Accordion>

    <Accordion title=".python-version">
      Specify the exact Python version.

      ```bash theme={null}
      3.12.1
      ```
    </Accordion>
  </Accordion>
</AccordionGroup>

<AccordionGroup>
  <Accordion title="Node.js">
    Specify the NodeJS version in any of the following config files.

    <Accordion title="package.json">
      Use `engines.node`

      ```json theme={null}
      "engines": {
        "node": ">=16.0.0 <19"
      }

      ```
    </Accordion>

    <Accordion title=".nvmrc">
      Specify the exact NodeJS version.

      ```bash theme={null}
      18.17.1
      ```
    </Accordion>

    <Accordion title=".node-version">
      Specify the exact NodeJS version.

      ```bash theme={null}
      18.17.1
      ```
    </Accordion>
  </Accordion>
</AccordionGroup>

<AccordionGroup>
  <Accordion title="Yarn">
    Specify the Yarn version in any of the following config files.

    <Accordion title="package.json">
      Use `engines.yarn`

      ```json theme={null}
      "engines": {
        "yarn": ">=1.22.0 <2.0.0"
      }
      ```
    </Accordion>

    <Accordion title=".yarnrc.yml">
      Use `yarnPath`

      ```bash theme={null}
      yarnPath: ".yarn/releases/yarn-3.2.1.cjs"
      ```
    </Accordion>

    <Accordion title=".yarnrc">
      Use `yarnPath`

      ```bash theme={null}
      yarnPath: ".yarn/releases/yarn-3.2.1.cjs"
      ```
    </Accordion>
  </Accordion>
</AccordionGroup>

<AccordionGroup>
  <Accordion title="PNPM">
    Config file: `package.json`

    Specify the pnpm version using `engines.pnpm`

    ```json theme={null}
    "engines": {
      "pnpm": ">=6.0.0"
    }
    ```
  </Accordion>

  <Accordion title=".NET">
    Specify the .NET version using any of the following config files.

    <Accordion title="global.json">
      Use `sdk.version`

      ```json theme={null}
      {
        "sdk": {
          "version": "7.0.203"
        }
      }
      ```
    </Accordion>

    <Accordion title="*.csproj">
      Use `TargetFramework` or `TargetFrameworks`

      ```bash theme={null}
      <TargetFramework>net6.0</TargetFramework>
      ```
    </Accordion>

    <Accordion title="Directory.Build.props">
      Use `TargetFramework` or `TargetFrameworks` in a `PropertyGroup` to define the .NET version for the directory and subdirectories.

      ```xml theme={null}
      <Project>
        <PropertyGroup>
          <TargetFramework>net6.0</TargetFramework>
        </PropertyGroup>
      </Project>
      ```
    </Accordion>

    <Accordion title="Directory.Packages.props">
      Use `TargetFramework` or `TargetFrameworks` in a `PropertyGroup` to define the .NET version when using central package management.

      ```xml theme={null}
      <Project Sdk="Microsoft.NET.Sdk">
        <PropertyGroup>
          <TargetFramework>net6.0</TargetFramework>
        </PropertyGroup>
      </Project>
      ```
    </Accordion>
  </Accordion>
</AccordionGroup>

<Accordion title="Golang">
  Config file: `go.mod`

  Use the `go` directive.

  ```Go theme={null}
  module github.com/example/project

  go 1.21
  ```
</Accordion>

<Note>
  Auto detection is best-effort and works only if your project’s config files are correctly configured.
</Note>

### Enable auto detection for endorctl scans

To enable auto detection for endorctl scans, run:

```bash theme={null}
endorctl scan --install-build-tools --enable-build-tools-version-detection
```

<Warning>
  Enabling these options downloads the necessary build toolchains during each scan. This works well for one-time scans but may cause scan failures in CI environments due to intermittent network issues.
</Warning>

### Enable auto detection in GitHub App

When using the GitHub App, you can enable auto detection either by a project or enable it for all projects in a tenant.

* To enable the auto detection by a project, update the project's `meta.annotations` with `"ENDOR_SCAN_ENABLE_BUILD_TOOLS_VERSION_DETECTION":"true"`.

  ```bash theme={null}
  meta:
    annotations: {"ENDOR_SCAN_ENABLE_BUILD_TOOLS_VERSION_DETECTION":"true"}
  ```

  ```bash theme={null}
    endorctl api update -r Project --uuid=<project-uuid> -i
  ```

* To enable auto detection across all projects in a tenant, update the system config's `meta.annotations` with `"ENDOR_SCAN_ENABLE_BUILD_TOOLS_VERSION_DETECTION":"true"`.

  ```bash theme={null}
  meta:
    annotations: {"ENDOR_SCAN_ENABLE_BUILD_TOOLS_VERSION_DETECTION":"true"}
  ```

  ```bash theme={null}
  endorctl api update -r SystemConfig --uuid=<system-config-uuid> -i
  ```

The updates are applied during the next scheduled scan or whenever you perform a manual re-scan.
