/** * Package Validator * * Validates that package.json dependencies match the architecture graph nx derived * from the source, and — critically — that each dependency is declared in the RIGHT * SECTION of package.json: * * - reached from production source → `dependencies` (or `peerDependencies`) * - reached ONLY from test/dev files → `devDependencies` * * WHY the section matters: production images are built with * `pnpm --filter= deploy --prod`, which installs exactly the `dependencies` * closure. A test-support package parked in `dependencies` therefore ships test * machinery (auth-bypass hooks, canned credentials, fake datastores) into the * production container. Before this validator understood `devDependencies`, moving * such a package to its correct home FAILED the build — the tool enforced the * insecure layout. Now `devDependencies` is the required home for test-only deps, * and listing one in `dependencies` is itself a violation. */ import { DepUsage } from './dep-usage-scanner'; /** * How hard to push back when a test-only package sits in `dependencies` * (i.e. inside the production deploy closure). * * - 'error' (default): fails the build. This is the guardrail the security bug asked for. * - 'warn': reports it without failing — the migration setting for a repo that needs a * few releases to clean up its package.json files. * - 'off': skip the check entirely. * * Missing deps, and production imports declared only in `devDependencies`, ALWAYS error: * their fix is unambiguous and the alternative is a broken runtime. */ export type TestOnlyDepMode = 'error' | 'warn' | 'off'; /** * Options for {@link validatePackageJsonDependencies}. Data-only (CLAUDE.md: data * structures are classes, never anonymous object literals). */ export declare class PackageValidatorOptions { testOnlyDepMode: TestOnlyDepMode; constructor(testOnlyDepMode?: TestOnlyDepMode); } /** * Validation result for a single project */ export declare class ProjectValidationResult { project: string; valid: boolean; missingInPackageJson: string[]; extraInPackageJson: string[]; /** Graph deps that are test-only but declared in `dependencies` (production closure). */ testOnlyInProdDependencies: string[]; constructor(project: string, valid: boolean, missingInPackageJson: string[], extraInPackageJson: string[], testOnlyInProdDependencies: string[]); } /** * Overall validation result * * `errors` fail the build. Every error's fix is either ADDITIVE ("add it to package.json") * or a MOVE between sections ("it belongs in devDependencies") — never "delete a * dependency", so no error can push a user toward removing a runtime-required package. * * `warnings` never fail the build. Workspace deps in package.json that the architecture * graph can't reach are reported here, NOT as errors: a transitively-reachable or even * unreachable entry can still be a real runtime dependency (e.g. a peerDependency or a * generated client that nx's import analysis doesn't traverse). Erroring on these is the * "runtime-validity trap" that previously forced a bad package.json edit — so we only warn. */ export declare class ValidationResult { valid: boolean; errors: string[]; warnings: string[]; projectResults: ProjectValidationResult[]; constructor(valid: boolean, errors: string[], warnings: string[], projectResults: ProjectValidationResult[]); } /** * The three package.json sections that matter, kept apart so the validator can say * WHICH one a package belongs in (the old code merged them and lost that information — * and never even read devDependencies). */ export declare class DeclaredDeps { dependencies: string[]; devDependencies: string[]; peerDependencies: string[]; constructor(dependencies: string[], devDependencies: string[], peerDependencies: string[]); /** Every declared package name, deduped and sorted (all three sections). */ all(): string[]; /** Declared somewhere that survives `pnpm deploy --prod`. */ isProductionDeclared(packageName: string): boolean; isDevDeclared(packageName: string): boolean; isDeclared(packageName: string): boolean; } /** * Graph shape produced by graph-sorter (an input contract we never construct here). */ interface GraphEntry { level: number; dependsOn: string[]; } /** * Per-project classification of graph deps against what package.json declares. */ declare class DepClassification { /** Production-reached deps absent from `dependencies`/`peerDependencies`. */ missingInPackageJson: string[]; /** Production-reached deps declared ONLY in `devDependencies` (runtime would break). */ prodDepsOnlyInDev: string[]; /** Test-only deps declared in no section at all. */ missingTestOnlyDeps: string[]; /** Test-only deps sitting in `dependencies` — i.e. shipped to production. */ testOnlyInProdDependencies: string[]; /** Non-workspace (third-party) package.json entries — informational only. */ extraInPackageJson: string[]; /** Workspace entries the graph cannot reach at all — warn-only drift. */ extraWorkspaceDeps: string[]; } declare class SingleProjectValidation { result: ProjectValidationResult; errors: string[]; warnings: string[]; constructor(result: ProjectValidationResult, errors: string[], warnings: string[]); } /** * The per-workspace lookups a single-project validation needs, passed as one object * so method signatures stay readable. */ declare class ValidationContext { graph: Record; projectToPackage: Map; packageToProject: Map; options: PackageValidatorOptions; constructor(graph: Record, projectToPackage: Map, packageToProject: Map, options: PackageValidatorOptions); } export declare class PackageValidator { private readonly scanner; /** * Read the three dependency sections of a project's package.json. * Returns null when there is no package.json (apps often have none) so the caller * can skip the project entirely. */ readDeclaredDeps(workspaceRoot: string, projectRoot: string): DeclaredDeps | null; private namesOf; /** * Build map of project names to their package names * e.g., "core-util" → "@webpieces/core-util" */ buildProjectToPackageMap(workspaceRoot: string, projectsConfig: any): Map; private readPackageName; /** * Compute the transitive closure of a project's dependencies in the graph. * Example: server → [core-meta, http-server]; the closure includes http-server and * everything http-server reaches. * * Used to allow package.json entries for transitive deps (a legitimate pattern: * npm install brings the whole dependency tree, so a consumer may list any reachable * package directly). */ computeTransitiveClosure(projectName: string, graph: Record): Set; /** * Split a project's graph deps into "declared correctly", "missing", and "declared in * the wrong section", using the import scan to decide which section each dep belongs in. */ classifyDeps(declared: DeclaredDeps, usage: DepUsage, entry: GraphEntry, transitiveClosure: Set, context: ValidationContext): DepClassification; /** * The heart of the fix: which SECTION does this dep belong in? * * A dep is test-only when the scan saw it imported by test/dev files and by NO * production file. Anything else — including a dep we never saw imported at all (it * may be loaded reflectively at runtime) — is treated as production, so this can * never push a runtime-required package out of `dependencies`. */ private classifyOneDep; validateSingleProject(projectName: string, entry: GraphEntry, projectRoot: string, declared: DeclaredDeps, usage: DepUsage, context: ValidationContext): SingleProjectValidation; private buildErrors; private buildWarnings; private testOnlyInProdMessage; validate(graph: Record, workspaceRoot: string, options: PackageValidatorOptions): Promise; } /** * Validate that package.json dependencies cover the dependency graph AND that each dep * is declared in the correct section (dependencies vs devDependencies). * * @param graph - Enhanced graph with project dependencies (uses project names) * @param workspaceRoot - Absolute path to workspace root * @param options - Strictness of the "test-only dep in the production closure" check */ export declare function validatePackageJsonDependencies(graph: Record, workspaceRoot: string, options?: PackageValidatorOptions): Promise; export {};