> ## 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.

# JavaScript/TypeScript

> Learn how to implement Endor Labs in repositories with JavaScript or TypeScript packages.

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>;
};

JavaScript is a high-level, interpreted programming language primarily used for creating interactive and dynamic web content widely used by developers. Endor Labs supports the scanning and monitoring of JavaScript projects.

Using Endor Labs, application security engineers and developers can:

* Scan their software for potential security issues and violations of organizational policy.
* Prioritize vulnerabilities in the context of their applications.
* Understand the relationships between software components in their applications.

## System specifications for deep scan

Before you proceed to run a deep scan, ensure that your system meets the following specification.

| Project Size      | Processor         | Memory |
| ----------------- | ----------------- | ------ |
| Small projects    | 4-core processor  | 16 GB  |
| Mid-size projects | 8-core processor  | 32 GB  |
| Large projects    | 16-core processor | 64 GB  |

### Software prerequisites

* Install the following software before you scan:
  * Yarn: Any version
  * npm: 6.14.18 or higher versions
  * pnpm: 3.0.0 or higher versions
  * Rush: 5.90.0 or higher versions. To enable Rush support, set the environment variable `ENDOR_RUSH_ENABLED=true`.
* Install Bazel version `5.x.x`, `6.x.x`, `7.x.x`, `8.x.x`, or `9.x.x` if your project uses Bazel. Endor Labs supports Bzlmod with Bazel aspects. See [Bazel](/scan/bazel) for more information.
* Make sure your repository includes one or more files with `.js` or `.ts` extension.

To run deep scanning for JavaScript and TypeScript projects make sure you have the following prerequisites installed:

* Install endorctl version 1.7.0 or higher.

* Install Node.js version 4.2.6 or higher to support TypeScript version 4.9.

* Install TypeScript version 4.9 or higher.

* Install `tsserver`. TypeScript includes `tsserver`, so installing the right TypeScript version also installs `tsserver`.

  Install the appropriate TypeScript version based on your Node.js version.

  <YamlTable>
    {`


      - Nodejs_Version: Lower than 12.2
      TypeScript_Version: 4.9 or higher

      - Nodejs_Version: Between 12.2 and 14.17
      TypeScript_Version: 5.0

      - Nodejs_Version: Higher than or equal to 14.17
      TypeScript_Version: Latest

      `}
  </YamlTable>

* Use the following command based on your Node.js version to install typescript:

  <Tabs>
    <Tab title="14.17 or higher">
      ```bash theme={null}
      npm install -g typescript
      ```
    </Tab>

    <Tab title="Between 12.2 and 14.17">
      ```bash theme={null}
      npm install -g typescript@5.0
      ```
    </Tab>

    <Tab title="Lower than 12.2">
      ```bash theme={null}
      npm install -g typescript@4.9
      ```
    </Tab>
  </Tabs>

* Run the following command to verify the `tsserver` installation

  ```bash theme={null}
  which tsserver
  ```

  If you are running the endorctl scan with `--install-build-tools`, you don't need to install `tsserver`. See [Configure build tools](/scan/scan-profiles/build-tools) for more information.

### Build JavaScript projects

You can build your JavaScript projects before running a scan. Building first creates a `package-lock.json`, `yarn.lock`, or `pnpm-lock.yaml` file, which speeds up the scan.

Ensure your repository has `package.json` and run the following command making sure it builds the project successfully.

<Tabs>
  <Tab title="For npm">
    ```bash theme={null}
    npm install
    ```
  </Tab>

  <Tab title="For Yarn">
    ```bash theme={null}
    yarn install
    ```
  </Tab>

  <Tab title="For pnpm">
    ```bash theme={null}
    pnpm install
    ```
  </Tab>
</Tabs>

If the project is not built, endorctl builds the project during the scan and generates `package-lock.json`, `yarn.lock`, or `pnpm-lock.yaml` file. Make sure that npm, Yarn, or pnpm is available on your system. If your repository includes a lock file, endorctl uses the existing file for dependency resolution and does not create it again.

The `npm install` command may fail in a subdirectory if your project has a `package-lock.json` file at the root of the repository but not in sub-packages. See the following example.

```text theme={null}
 .
 ├── package.json
 ├── package-lock.json
 └── sub-package/
     └── package.json

```

You need to instruct endorctl to use the root-level lock file to avoid scan failures in monorepo setups where dependencies are centrally managed at the root.

Set the following environment variable before you run the scan.

```bash theme={null}
export ENDOR_JS_USE_ROOT_DIR_LOCK_FILE=true
```

### Scan Rush monorepos

Rush is a monorepo management tool for JavaScript/TypeScript that works on top of npm, pnpm, or Yarn and manages multiple projects in a single repository using a centralized configuration.

Endor Labs detects Rush repositories using the `rush.json` file at the repository root and scans them with the standard JavaScript workflow. Endor Labs infers the package manager and uses the corresponding lock file for dependency resolution.

Run the following command at the repository root to build the repository before a scan and to ensure the appropriate lock file exists.

```bash theme={null}
rush install
```

To scan Rush monorepos, you must first enable Rush detection.

```bash theme={null}
export ENDOR_RUSH_ENABLED=true
```

Run `endorctl scan` to discover Rush dependencies.

```bash theme={null}
endorctl scan
```

### Configure call graph generation timeout

When generating call graphs for JavaScript/TypeScript projects, endorctl uses `tsserver` to analyze the code. By default, `tsserver` waits 15 seconds for a response before timing out. For large or complex projects, you may need to increase this timeout.

Set the `ENDOR_JS_TSSERVER_TIMEOUT` environment variable to specify the timeout in seconds.

```bash theme={null}
export ENDOR_JS_TSSERVER_TIMEOUT=30
```

Increasing the timeout might be beneficial in the following scenarios:

* Large monorepos with many TypeScript files
* Projects with complex type hierarchies
* Projects with extensive type checking requirements

### Override JavaScript package manager detection

endorctl detects the JavaScript package manager automatically. You can override this detection by setting the `ENDOR_JS_PACKAGE_MANAGER` environment variable to `npm`, `yarn`, `pnpm`, or `lerna`.

For example, to use `npm` as the package manager run the following command.

```bash theme={null}
export ENDOR_JS_PACKAGE_MANAGER=npm
```

This setting forces endorctl to use the specified package manager and overrides all other JavaScript package manager configuration variables.

## Scan Bazel projects

Endor Labs supports Bazel scans for JavaScript and TypeScript with Bazel aspects. See [Bazel](/scan/bazel) for build instructions, supported rules, and scan commands. See [Bazel Aspects](/scan/bazel/bazel-aspects) for more information.

* **[rules\_js](https://github.com/aspect-build/rules_js)** (>= 2.0.0): scan targets such as `js_binary` and `js_library`. Supports Bzlmod. Pass `--use-bazel-aspects`.
* **[rules\_ts](https://github.com/aspect-build/rules_ts)** (>= 1.0.0): scan targets such as `ts_project` and `ts_project_rule`. Supports both the WORKSPACE model and Bzlmod. Pass `--use-bazel-aspects`.

### Scan TypeScript projects with rules\_ts

Ensure that the following prerequisites are in place:

* A Bazel workspace (WORKSPACE or `MODULE.bazel`)
* `rules_ts` >= 1.0.0 in your workspace
* endorctl with `--use-bazel-aspects`

Run the following command to discover TypeScript targets.

```bash theme={null}
bazel query 'kind(ts_project, //...)'
```

Run the following command to scan those targets.

```bash theme={null}
endorctl scan --use-bazel --use-bazel-aspects \
  --bazel-targets-query='kind(ts_project, //...)'
```

<Note>
  For call graph generation on TypeScript sources, endorctl uses `tsserver`. Large repositories may need a higher timeout. See [Configure call graph generation timeout](#configure-call-graph-generation-timeout).
</Note>

## Run a scan

Perform a scan to get visibility into your software composition and resolve dependencies.

```bash theme={null}
endorctl scan
```

### Understand the scan process

Dependency analysis tools analyze the lock file of an npm, yarn, pnpm, or Rush based package and attempt to resolve dependencies. To resolve dependencies from private repositories, Endor Labs reads the `.npmrc` settings from the repository.

Endor Labs surpasses mere manifest file analysis by expertly resolving JavaScript dependencies and identifies:

* Dependencies listed in the manifest file but not used by the application
* Dependencies used by the application but not listed in the manifest file
* Dependencies listed in the manifest as transitive but used directly by the application
* Dependencies categorized as test in the manifest, but used directly by the application

Developers can eliminate false positives, false negatives, and easily identify test dependencies with this analysis. Endor Labs tags dependencies found in source code but not declared in the manifest files as **Phantom**.

Endor Labs also supports npm, Yarn, pnpm, and Rush workspaces out-of-the-box. If your JavaScript frameworks and packages use workspaces, Endor Labs will automatically take the dependencies from the workspace to ensure that the package successfully builds.

The lock file speeds up the scan when it exists in the repository. endorctl skips the build step and uses the existing files for analysis.

### Configure private npm package repositories

Endor Labs supports fetching and scanning dependencies from private npm package registries. Endor Labs will fetch resources from authenticated endpoints and perform the scan, allowing you to view the resolved dependencies and findings. See [npm package manager integrations](/integrations/package-managers/npm-private-package-manager) for more information on configuring private registries.

### Known Limitations

* Endor Labs doesn't currently support local package references
* If a dependency cannot resolve from the lock file, building that specific package may fail. The package may no longer exist in npm, or the `.npmrc` file may not be properly configured. Other packages in the workspace are scanned as usual.

#### Call graph limitations

* The call graph might not include functions passed as arguments to call expressions.
* The call graph might not include functions that return and then execute.
* The call graph might not include functions assigned to a variable based on a runtime value.
* The call graph might not include functions assigned to an array element.

### Troubleshoot errors

* **Unresolved dependency errors**:
  The manifest file `package.json` is not buildable. Try running `npm install`, `yarn install`, `pnpm install`, or `rush install` in the root project to debug this error.
* **Resolved dependency errors**:
  A dependency version does not exist or cannot be found. The package may no longer exist in the repository.
