# core/analyzers/package.analyzer.ts

> 79 lines of code and 23 definitions.

Tree: GovLab Docs
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/docs#file-docs-core-analyzers-package-analyzer-ts
Source text: https://banes-lab.com/source/docs/core/analyzers/package.analyzer.ts.txt

Listed in [core/analyzers](https://banes-lab.com/api/source/docs/core/analyzers.md), after [core/analyzers/orchestration.analyzer.ts](https://banes-lab.com/source/docs/core/analyzers/orchestration.analyzer.ts.md) and before [core/analyzers/reference.analyzer.ts](https://banes-lab.com/source/docs/core/analyzers/reference.analyzer.ts.md).

## Definitions

- `barrelCount` (lexical_declaration, line 26)
- `stringOr` (lexical_declaration, line 22)
- `scopedDeps` (lexical_declaration, line 56)
- `collectBarrelExports` (lexical_declaration, line 30)
- `isExcluded` (lexical_declaration, line 20)
- `extractPurpose` (lexical_declaration, line 42)
- `inspectPackage` (lexical_declaration, line 62, exported)
- `BARREL_FILE` (lexical_declaration, line 13)
- `PURPOSE_HEADING` (lexical_declaration, line 14)
- `NEXT_HEADING` (lexical_declaration, line 15)
- `LINE_BREAK` (lexical_declaration, line 16)
- `WORD_JOIN` (lexical_declaration, line 17)
- `POSIX_SEPARATOR` (lexical_declaration, line 18)
- `DEFAULT_VERSION` (lexical_declaration, line 19)
- `rootBarrel` (lexical_declaration, line 31)
- `at` (lexical_declaration, line 43)
- `after` (lexical_declaration, line 47)
- `next` (lexical_declaration, line 48)
- `manifest` (lexical_declaration, line 63, exported)
- `readme` (lexical_declaration, line 67, exported)
- `stats` (lexical_declaration, line 68, exported)
- `declared` (lexical_declaration, line 69, exported)
- `dependencies` (lexical_declaration, line 70, exported)

## Contained in

- [core/analyzers](https://banes-lab.com/anatomy/docs/folder-docs-core-analyzers.md)

## Uses

- [core/loaders/base.loader.ts](https://banes-lab.com/source/docs/core/loaders/base.loader.ts.md)
- [core/parsers/export.parser.ts](https://banes-lab.com/source/docs/core/parsers/export.parser.ts.md)
- [core/predicates/record.predicate.ts](https://banes-lab.com/source/docs/core/predicates/record.predicate.ts.md)

## Linked from

- [core/analyzers](https://banes-lab.com/anatomy/docs/folder-docs-core-analyzers.md)
- [core/loaders](https://banes-lab.com/anatomy/docs/folder-docs-core-loaders.md)
- [core/parsers](https://banes-lab.com/anatomy/docs/folder-docs-core-parsers.md)
- [core/predicates](https://banes-lab.com/anatomy/docs/folder-docs-core-predicates.md)

## Source

```typescript
import type { InspectContext, PackageInfo } from "#types/index.types";
import { PACKAGE_FILE, README_FILE } from "#configuration/constants/document.constants";
import { join, relative, sep } from "node:path";
import { readDirSafe, readJsonSafe, readTextSafe } from "#core/loaders/base.loader";
import { PACKAGE_SCOPE } from "#configuration/constants/manifest.constants";
import { ROOT } from "@ssot/paths";
import { collectSourceStats } from "#core/counters/source.counter";
import { excludeMatcher } from "@govlab/quality/config";
import { existsSync } from "node:fs";
import { exportedNames } from "#core/parsers/export.parser";
import { isPlainRecord } from "#core/predicates/record.predicate";

const BARREL_FILE = "index.ts";
const PURPOSE_HEADING = "## What is it";
const NEXT_HEADING = "\n## ";
const LINE_BREAK = "\n";
const WORD_JOIN = " ";
const POSIX_SEPARATOR = "/";
const DEFAULT_VERSION = "0.0.0";
const isExcluded = await excludeMatcher(ROOT);

const stringOr = function stringOr(value: unknown, fallback: string): string {
    return typeof value === "string" ? value : fallback;
};

const barrelCount = function barrelCount(barrel: string): number {
    return existsSync(barrel) ? exportedNames(barrel).size : 0;
};

const collectBarrelExports = function collectBarrelExports(packageDir: string): number {
    const rootBarrel = join(packageDir, BARREL_FILE);
    if (existsSync(rootBarrel)) {
        return barrelCount(rootBarrel);
    }
    return readDirSafe(packageDir)
        .filter((entry) => entry.isDirectory())
        .map((entry) => join(packageDir, entry.name))
        .filter((dir) => !isExcluded(dir))
        .reduce((total, dir) => total + barrelCount(join(dir, BARREL_FILE)), 0);
};

const extractPurpose = function extractPurpose(readme: string | null): string | null {
    const at = readme === null ? -1 : readme.indexOf(PURPOSE_HEADING);
    if (readme === null || at === -1) {
        return null;
    }
    const after = readme.slice(at + PURPOSE_HEADING.length).trimStart();
    const next = after.indexOf(NEXT_HEADING);
    return (next === -1 ? after : after.slice(0, next))
        .trim()
        .split(LINE_BREAK)
        .filter((line) => line.trim().length > 0)
        .join(WORD_JOIN);
};

const scopedDeps = function scopedDeps(dependencies: Readonly<Record<string, unknown>>, wantScoped: boolean): string[] {
    return Object.keys(dependencies)
        .filter((dep) => dep.startsWith(PACKAGE_SCOPE) === wantScoped)
        .toSorted((left, right) => left.localeCompare(right));
};

export const inspectPackage = function inspectPackage(packageDir: string, context: InspectContext): PackageInfo | null {
    const manifest = readJsonSafe(join(packageDir, PACKAGE_FILE));
    if (!isPlainRecord(manifest) || typeof manifest["name"] !== "string") {
        return null;
    }
    const readme = readTextSafe(join(packageDir, README_FILE));
    const stats = collectSourceStats(packageDir, isExcluded);
    const declared = manifest["dependencies"];
    const dependencies = isPlainRecord(declared) ? declared : {};
    return {
        barrelExports: collectBarrelExports(packageDir),
        description: stringOr(manifest["description"], ""),
        externalDeps: scopedDeps(dependencies, false),
        group: context.group,
        hasReadme: readme !== null,
        name: manifest["name"],
        path: relative(context.repoRoot, packageDir).split(sep).join(POSIX_SEPARATOR),
        purpose: extractPurpose(readme),
        siblingDeps: scopedDeps(dependencies, true),
        slug: context.packageName,
        sourceFiles: stats.fileCount,
        sourceLoc: stats.loc,
        version: stringOr(manifest["version"], DEFAULT_VERSION),
    };
};
```
