@patchstack/connect 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/parsers/index.ts","../src/types.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/parsers/pnpm.ts","../src/parsers/yarn.ts","../src/normalize.ts","../src/client.ts","../src/config.ts"],"sourcesContent":["import { scanLockfile } from './parsers/index.js';\nimport { buildWirePayload } from './normalize.js';\nimport { postManifest } from './client.js';\nimport { persistSiteUuid, resolveConfig } from './config.js';\nimport type { Config, Manifest, StoreManifestResponse } from './types.js';\n\nexport { scanLockfile, detectLockfile } from './parsers/index.js';\nexport { buildWirePayload, compareVersions } from './normalize.js';\nexport { postManifest, buildClaimUrl, buildEndpointUrl, DEFAULT_ENDPOINT } from './client.js';\nexport { persistSiteUuid, resolveConfig, writeConfigFile } from './config.js';\nexport {\n PatchstackError,\n type Config,\n type Ecosystem,\n type Manifest,\n type PackageEntry,\n type StoreManifestResponse,\n} from './types.js';\n\nexport interface ScanAndReportOptions {\n cwd?: string;\n config?: Config;\n}\n\nexport interface ScanAndReportResult {\n manifest: Manifest;\n response: StoreManifestResponse;\n duplicateNames: string[];\n uniqueNames: number;\n totalEntries: number;\n}\n\nexport async function scanAndReport(\n options: ScanAndReportOptions = {},\n): Promise<ScanAndReportResult> {\n const cwd = options.cwd ?? process.cwd();\n const config = options.config ?? (await resolveConfig({ cwd }));\n const manifest = await scanLockfile(cwd);\n const { payload, stats } = buildWirePayload(manifest);\n const response = await postManifest(config, payload);\n\n // First-run convenience: if we didn't have a UUID and the server provisioned\n // one for us, persist it so subsequent runs target the same site.\n if (config.siteUuid === null && response.uuid !== undefined && response.uuid.length > 0) {\n await persistSiteUuid(cwd, response.uuid);\n }\n\n return {\n manifest,\n response,\n duplicateNames: stats.duplicateNames,\n uniqueNames: stats.uniqueNames,\n totalEntries: stats.totalEntries,\n };\n}\n","import { access } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Manifest, type PackageEntry } from '../types.js';\nimport { parseNpmLockfile } from './npm.js';\nimport { walkNodeModules } from './node_modules.js';\nimport { parsePnpmLockfile } from './pnpm.js';\nimport { parseYarnLockfile } from './yarn.js';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy =\n | 'npm-lockfile'\n | 'node-modules-walk'\n | 'pnpm-lockfile'\n | 'yarn-lockfile';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\ninterface LockfileCandidate {\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\n// Checked in priority order: the first candidate present in `cwd` wins.\nconst LOCKFILE_CANDIDATES: readonly LockfileCandidate[] = [\n { filename: 'package-lock.json', strategy: 'npm-lockfile' },\n { filename: 'bun.lock', strategy: 'node-modules-walk' },\n { filename: 'bun.lockb', strategy: 'node-modules-walk' },\n { filename: 'pnpm-lock.yaml', strategy: 'pnpm-lockfile' },\n { filename: 'yarn.lock', strategy: 'yarn-lockfile' },\n];\n\nexport async function detectLockfile(cwd: string): Promise<DetectedLockfile> {\n // Probe every candidate concurrently, then resolve by declaration order so\n // the result is identical to a sequential first-match scan — but bounded by a\n // single I/O round-trip instead of up to one `stat` per candidate.\n const probed = await Promise.all(\n LOCKFILE_CANDIDATES.map(async (candidate) => {\n const filePath = path.join(cwd, candidate.filename);\n return { ...candidate, filePath, present: await exists(filePath) };\n }),\n );\n\n const match = probed.find((candidate) => candidate.present);\n if (match) {\n return {\n ecosystem: 'npm',\n filePath: match.filePath,\n filename: match.filename,\n strategy: match.strategy,\n };\n }\n\n const expected = LOCKFILE_CANDIDATES.map((candidate) => candidate.filename).join(', ');\n throw new PatchstackError(\n `No lockfile found in ${cwd}. Expected one of: ${expected}.`,\n 'LOCKFILE_NOT_FOUND',\n );\n}\n\nexport async function scanLockfile(cwd: string): Promise<Manifest> {\n const detected = await detectLockfile(cwd);\n const packages = await runStrategy(detected, cwd);\n return { ecosystem: detected.ecosystem, packages };\n}\n\nasync function runStrategy(\n detected: DetectedLockfile,\n cwd: string,\n): Promise<PackageEntry[]> {\n switch (detected.strategy) {\n case 'npm-lockfile':\n return parseNpmLockfile(detected.filePath);\n case 'pnpm-lockfile':\n return parsePnpmLockfile(detected.filePath);\n case 'yarn-lockfile':\n return parseYarnLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise<boolean> {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n","export type Ecosystem = 'npm' | 'composer';\n\n/**\n * Which environment a manifest was captured in. The connector reports from real\n * builds (prebuild scan / build hooks), so it defaults to 'production'. Override\n * with PATCHSTACK_ENVIRONMENT=sandbox (or \"environment\" in .patchstackrc.json)\n * for test manifests.\n */\nexport type Environment = 'production' | 'sandbox';\n\nexport interface PackageEntry {\n name: string;\n version: string;\n path?: string;\n direct?: boolean;\n}\n\nexport interface Manifest {\n ecosystem: Ecosystem;\n packages: PackageEntry[];\n}\n\nexport interface Config {\n /**\n * The site UUID. `null` means we don't have one yet — `postManifest` will then\n * post to the bare endpoint, the server will provision a fresh site, and the\n * UUID it returns should be persisted via `persistSiteUuid()`.\n */\n siteUuid: string | null;\n endpoint: string;\n timeoutMs: number;\n /** Environment to report the manifest under. Defaults to 'production'. */\n environment: Environment;\n}\n\nexport interface StoreManifestResponse {\n /** The UUID of the site the manifest was stored against. Always returned. */\n uuid?: string;\n stored: boolean;\n manifest_id?: number;\n checksum?: string;\n reason?: string;\n message?: string;\n error?: string;\n}\n\nexport class PatchstackError extends Error {\n constructor(\n message: string,\n public readonly code:\n | 'CONFIG_MISSING'\n | 'CONFIG_INVALID'\n | 'LOCKFILE_NOT_FOUND'\n | 'LOCKFILE_UNSUPPORTED'\n | 'LOCKFILE_PARSE_ERROR'\n | 'NETWORK_ERROR'\n | 'NETWORK_TIMEOUT'\n | 'SITE_NOT_FOUND'\n | 'VALIDATION_ERROR'\n | 'SERVER_ERROR',\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = 'PatchstackError';\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\ninterface LockfileV2Package {\n name?: string;\n version?: string;\n link?: boolean;\n resolved?: string;\n}\n\ninterface LockfileV2 {\n lockfileVersion: number;\n packages?: Record<string, LockfileV2Package>;\n dependencies?: Record<string, LockfileV1Dependency>;\n}\n\ninterface LockfileV1Dependency {\n version: string;\n dependencies?: Record<string, LockfileV1Dependency>;\n}\n\nexport async function parseNpmLockfile(lockfilePath: string): Promise<PackageEntry[]> {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, 'LOCKFILE_NOT_FOUND', cause);\n }\n\n let parsed: LockfileV2;\n try {\n parsed = JSON.parse(raw) as LockfileV2;\n } catch (cause) {\n throw new PatchstackError(`Lockfile at ${lockfilePath} is not valid JSON`, 'LOCKFILE_PARSE_ERROR', cause);\n }\n\n if (parsed.packages) {\n return extractFromV2(parsed.packages);\n }\n\n if (parsed.dependencies) {\n return extractFromV1(parsed.dependencies);\n }\n\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} has no \"packages\" or \"dependencies\" key`,\n 'LOCKFILE_PARSE_ERROR',\n );\n}\n\nfunction extractFromV2(packages: Record<string, LockfileV2Package>): PackageEntry[] {\n const entries: PackageEntry[] = [];\n\n for (const [pkgPath, pkg] of Object.entries(packages)) {\n if (pkgPath === '') {\n continue;\n }\n if (pkg.link === true) {\n continue;\n }\n if (typeof pkg.version !== 'string' || pkg.version.length === 0) {\n continue;\n }\n\n const name = pkg.name ?? extractNameFromPath(pkgPath);\n if (name === null) {\n continue;\n }\n\n entries.push({\n name,\n version: pkg.version,\n path: pkgPath,\n direct: isDirectV2(pkgPath),\n });\n }\n\n return entries;\n}\n\nfunction extractFromV1(\n deps: Record<string, LockfileV1Dependency>,\n acc: PackageEntry[] = [],\n depth = 0,\n): PackageEntry[] {\n for (const [name, dep] of Object.entries(deps)) {\n if (typeof dep.version === 'string' && dep.version.length > 0) {\n acc.push({ name, version: dep.version, direct: depth === 0 });\n }\n if (dep.dependencies) {\n extractFromV1(dep.dependencies, acc, depth + 1);\n }\n }\n return acc;\n}\n\nfunction extractNameFromPath(pkgPath: string): string | null {\n const segments = pkgPath.split('node_modules' + path.sep === pkgPath ? path.sep : '/');\n const parts = pkgPath.split('/');\n const nmIndex = parts.lastIndexOf('node_modules');\n if (nmIndex === -1 || nmIndex >= parts.length - 1) {\n return segments[segments.length - 1] ?? null;\n }\n const tail = parts.slice(nmIndex + 1);\n if (tail.length === 0) {\n return null;\n }\n const first = tail[0];\n if (first !== undefined && first.startsWith('@') && tail.length >= 2) {\n return `${first}/${tail[1]}`;\n }\n return first ?? null;\n}\n\nfunction isDirectV2(pkgPath: string): boolean {\n const parts = pkgPath.split('/');\n const nmCount = parts.filter((p) => p === 'node_modules').length;\n return nmCount === 1;\n}\n","import { lstat, readFile, readdir, stat } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\nexport async function walkNodeModules(cwd: string): Promise<PackageEntry[]> {\n const root = path.join(cwd, 'node_modules');\n\n try {\n const info = await stat(root);\n if (!info.isDirectory()) {\n throw new PatchstackError(\n `${root} exists but is not a directory.`,\n 'LOCKFILE_NOT_FOUND',\n );\n }\n } catch (cause) {\n if (cause instanceof PatchstackError) {\n throw cause;\n }\n throw new PatchstackError(\n `node_modules/ not found at ${cwd}. Install dependencies first (e.g. \\`bun install\\` or \\`npm install\\`).`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const entries: PackageEntry[] = [];\n await walk(root, entries, 0);\n return entries;\n}\n\nasync function walk(dir: string, acc: PackageEntry[], depth: number): Promise<void> {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n return;\n }\n\n for (const name of names) {\n if (name.startsWith('.')) {\n continue;\n }\n\n const fullPath = path.join(dir, name);\n if (!(await isPlainDirectory(fullPath))) {\n continue;\n }\n\n if (name.startsWith('@')) {\n let subNames: string[];\n try {\n subNames = await readdir(fullPath);\n } catch {\n continue;\n }\n for (const sub of subNames) {\n if (sub.startsWith('.')) {\n continue;\n }\n const scopedDir = path.join(fullPath, sub);\n if (!(await isPlainDirectory(scopedDir))) {\n continue;\n }\n await readPackage(scopedDir, depth, acc);\n await walkNested(scopedDir, acc, depth);\n }\n continue;\n }\n\n await readPackage(fullPath, depth, acc);\n await walkNested(fullPath, acc, depth);\n }\n}\n\nasync function readPackage(\n pkgDir: string,\n depth: number,\n acc: PackageEntry[],\n): Promise<void> {\n let raw: string;\n try {\n raw = await readFile(path.join(pkgDir, 'package.json'), 'utf8');\n } catch {\n return;\n }\n\n let parsed: { name?: unknown; version?: unknown };\n try {\n parsed = JSON.parse(raw) as { name?: unknown; version?: unknown };\n } catch {\n return;\n }\n\n if (typeof parsed.name !== 'string' || parsed.name.length === 0) {\n return;\n }\n if (typeof parsed.version !== 'string' || parsed.version.length === 0) {\n return;\n }\n\n acc.push({\n name: parsed.name,\n version: parsed.version,\n direct: depth === 0,\n });\n}\n\nasync function walkNested(\n pkgDir: string,\n acc: PackageEntry[],\n depth: number,\n): Promise<void> {\n const nested = path.join(pkgDir, 'node_modules');\n if (!(await isPlainDirectory(nested))) {\n return;\n }\n await walk(nested, acc, depth + 1);\n}\n\nasync function isPlainDirectory(dir: string): Promise<boolean> {\n try {\n const info = await lstat(dir);\n return info.isDirectory() && !info.isSymbolicLink();\n } catch {\n return false;\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\n/**\n * Parses pnpm-lock.yaml without pulling in a YAML library. We don't need full\n * YAML semantics — only the `packages:` block (the canonical list of every\n * installed package) and, for direct-dep marking, the dependency sections under\n * `importers:` (v9+) or at the top level (v6-v8 single-project).\n *\n * Supported pnpm-lock key formats:\n * v5: /pkg/1.0.0 /@scope/pkg/1.0.0\n * v6-v8: /pkg@1.0.0 /@scope/pkg@1.0.0 (optional `(peer@x)` suffix)\n * v9: pkg@1.0.0 @scope/pkg@1.0.0 (optional `(peer@x)` suffix, may be quoted)\n */\nexport async function parsePnpmLockfile(lockfilePath: string): Promise<PackageEntry[]> {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(\n `Could not read lockfile at ${lockfilePath}`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const lines = raw.split(/\\r?\\n/);\n const directNames = collectDirectDepNames(lines);\n const packageKeys = collectPackagesBlockKeys(lines);\n\n if (packageKeys.length === 0) {\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} has no \"packages\" entries`,\n 'LOCKFILE_PARSE_ERROR',\n );\n }\n\n const entries: PackageEntry[] = [];\n for (const key of packageKeys) {\n const parsed = parsePackageKey(key);\n if (parsed === null) {\n continue;\n }\n entries.push({\n name: parsed.name,\n version: parsed.version,\n direct: directNames.has(parsed.name),\n });\n }\n\n return entries;\n}\n\ninterface ParsedKey {\n name: string;\n version: string;\n}\n\nexport function parsePackageKey(rawKey: string): ParsedKey | null {\n let k = rawKey.trim();\n if (k.length === 0) {\n return null;\n }\n\n if (\n (k.startsWith(\"'\") && k.endsWith(\"'\")) ||\n (k.startsWith('\"') && k.endsWith('\"'))\n ) {\n k = k.slice(1, -1);\n }\n\n if (k.startsWith('/')) {\n k = k.slice(1);\n }\n\n // Strip peer-dependency suffix used by v6+: `pkg@1.0.0(peer@1.0.0)(other@2)`.\n const parenIdx = k.indexOf('(');\n if (parenIdx >= 0) {\n k = k.slice(0, parenIdx);\n }\n\n // Split off the optional `@scope/` prefix so the remaining \"body\" contains\n // exactly one separator between the bare name and the version. npm forbids\n // both `@` and `/` inside the bare-name portion, so the first occurrence of\n // either character in the body is unambiguously the name/version separator.\n let scopePrefix = '';\n let body = k;\n if (k.startsWith('@')) {\n const firstSlash = k.indexOf('/');\n if (firstSlash <= 0) {\n return null;\n }\n scopePrefix = k.slice(0, firstSlash + 1);\n body = k.slice(firstSlash + 1);\n }\n\n const slashIdx = body.indexOf('/');\n const atIdx = body.indexOf('@');\n\n let sepIdx: number;\n if (slashIdx < 0 && atIdx < 0) {\n return null;\n } else if (slashIdx < 0) {\n sepIdx = atIdx;\n } else if (atIdx < 0) {\n sepIdx = slashIdx;\n } else {\n sepIdx = Math.min(slashIdx, atIdx);\n }\n\n const name = scopePrefix + body.slice(0, sepIdx);\n let version = body.slice(sepIdx + 1);\n\n // v5 peer suffix uses `_`: `1.0.0_react@18.2.0`.\n const underscoreIdx = version.indexOf('_');\n if (underscoreIdx >= 0) {\n version = version.slice(0, underscoreIdx);\n }\n\n if (name.length === 0 || version.length === 0) {\n return null;\n }\n\n return { name, version };\n}\n\nfunction indentOf(line: string): number {\n let i = 0;\n while (i < line.length && line[i] === ' ') {\n i++;\n }\n return i;\n}\n\nfunction isBlankOrComment(line: string): boolean {\n const trimmed = line.trim();\n return trimmed.length === 0 || trimmed.startsWith('#');\n}\n\n/**\n * Collects keys directly nested under the top-level `packages:` block. Stops\n * when another top-level key (e.g. `snapshots:`) appears.\n */\nfunction collectPackagesBlockKeys(lines: string[]): string[] {\n const keys: string[] = [];\n let inBlock = false;\n let childIndent: number | null = null;\n\n for (const line of lines) {\n if (isBlankOrComment(line)) {\n continue;\n }\n\n const indent = indentOf(line);\n\n if (!inBlock) {\n if (indent === 0 && line.trim() === 'packages:') {\n inBlock = true;\n }\n continue;\n }\n\n if (indent === 0) {\n break;\n }\n\n if (childIndent === null) {\n childIndent = indent;\n }\n\n if (indent !== childIndent) {\n continue;\n }\n\n const content = line.slice(indent);\n if (!content.endsWith(':')) {\n continue;\n }\n\n keys.push(content.slice(0, -1));\n }\n\n return keys;\n}\n\n/**\n * Collects direct dependency names. Looks in two places, since the format\n * differs across lockfile versions and workspaces:\n *\n * 1. `importers:` > `<importer>:` > `{dependencies,devDependencies,optionalDependencies}:`\n * (v9 always, v6-v8 in workspaces)\n * 2. Top-level `{dependencies,devDependencies,optionalDependencies}:`\n * (v6-v8 single-project lockfiles)\n *\n * Direct-dep marking is best-effort: a name absent from both is simply left as\n * not-direct, matching the npm parser's behaviour for transitive packages.\n */\nfunction collectDirectDepNames(lines: string[]): Set<string> {\n const names = new Set<string>();\n collectFromImporters(lines, names);\n for (const section of ['dependencies', 'devDependencies', 'optionalDependencies']) {\n collectFromTopLevelSection(lines, section, names);\n }\n return names;\n}\n\nconst DEP_SECTIONS = new Set([\n 'dependencies',\n 'devDependencies',\n 'optionalDependencies',\n]);\n\nfunction collectFromImporters(lines: string[], out: Set<string>): void {\n let inImporters = false;\n let importerIndent: number | null = null;\n let inDepSection = false;\n let depSectionIndent: number | null = null;\n let leafIndent: number | null = null;\n\n for (const line of lines) {\n if (isBlankOrComment(line)) {\n continue;\n }\n const indent = indentOf(line);\n const trimmed = line.trim();\n\n if (!inImporters) {\n if (indent === 0 && trimmed === 'importers:') {\n inImporters = true;\n }\n continue;\n }\n\n if (indent === 0) {\n break;\n }\n\n // Track the indent at which importer entries (e.g. `.:`, `packages/foo:`) sit.\n if (importerIndent === null) {\n importerIndent = indent;\n }\n\n if (indent === importerIndent) {\n inDepSection = false;\n depSectionIndent = null;\n leafIndent = null;\n continue;\n }\n\n if (!inDepSection) {\n const key = stripTrailingColon(trimmed);\n if (key !== null && DEP_SECTIONS.has(key)) {\n inDepSection = true;\n depSectionIndent = indent;\n }\n continue;\n }\n\n if (depSectionIndent !== null && indent <= depSectionIndent) {\n // Reset — we've exited the dep section. Reconsider this line.\n inDepSection = false;\n depSectionIndent = null;\n leafIndent = null;\n const key = stripTrailingColon(trimmed);\n if (key !== null && DEP_SECTIONS.has(key)) {\n inDepSection = true;\n depSectionIndent = indent;\n }\n continue;\n }\n\n if (leafIndent === null) {\n leafIndent = indent;\n }\n\n if (indent !== leafIndent) {\n continue;\n }\n\n const name = extractLeafName(trimmed);\n if (name !== null) {\n out.add(name);\n }\n }\n}\n\nfunction collectFromTopLevelSection(\n lines: string[],\n section: string,\n out: Set<string>,\n): void {\n let inSection = false;\n let leafIndent: number | null = null;\n\n for (const line of lines) {\n if (isBlankOrComment(line)) {\n continue;\n }\n const indent = indentOf(line);\n const trimmed = line.trim();\n\n if (!inSection) {\n if (indent === 0 && trimmed === `${section}:`) {\n inSection = true;\n }\n continue;\n }\n\n if (indent === 0) {\n break;\n }\n\n if (leafIndent === null) {\n leafIndent = indent;\n }\n\n if (indent !== leafIndent) {\n continue;\n }\n\n const name = extractLeafName(trimmed);\n if (name !== null) {\n out.add(name);\n }\n }\n}\n\nfunction stripTrailingColon(s: string): string | null {\n if (!s.endsWith(':')) {\n return null;\n }\n return s.slice(0, -1).trim();\n}\n\n/**\n * Extracts the package name from a dependency-section leaf, handling both the\n * short form (`pkg: 1.0.0`) and the v9 expanded form (`pkg:` followed by\n * `specifier:` / `version:` children). Quoted scoped names like `'@scope/pkg':`\n * are unquoted.\n */\nfunction extractLeafName(trimmed: string): string | null {\n const colonIdx = trimmed.indexOf(':');\n if (colonIdx < 0) {\n return null;\n }\n let name = trimmed.slice(0, colonIdx).trim();\n if (name.length === 0) {\n return null;\n }\n if (\n (name.startsWith(\"'\") && name.endsWith(\"'\")) ||\n (name.startsWith('\"') && name.endsWith('\"'))\n ) {\n name = name.slice(1, -1);\n }\n return name.length > 0 ? name : null;\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\n/**\n * Parses yarn.lock (yarn classic v1 and yarn berry v2+) without a YAML\n * dependency. Both generations share the same block structure — a top-level\n * mapping of comma-separated descriptor lists to a block containing a\n * `version` field — so we walk them with the same scanner and only branch on\n * the `version` syntax (`version \"x\"` for v1, `version: x` for berry).\n *\n * Direct vs transitive can't be derived from yarn.lock alone (yarn does not\n * record an importer manifest the way pnpm v9 does), so we cross-reference\n * the sibling `package.json` when present.\n */\nexport async function parseYarnLockfile(lockfilePath: string): Promise<PackageEntry[]> {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(\n `Could not read lockfile at ${lockfilePath}`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const blocks = parseBlocks(raw);\n if (blocks.length === 0) {\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} contains no package entries`,\n 'LOCKFILE_PARSE_ERROR',\n );\n }\n\n const directNames = await readDirectDepNames(path.dirname(lockfilePath));\n\n const entries: PackageEntry[] = [];\n const seen = new Set<string>();\n for (const block of blocks) {\n if (block.version.length === 0 || block.names.size === 0) {\n continue;\n }\n for (const name of block.names) {\n const dedupKey = `${name}@${block.version}`;\n if (seen.has(dedupKey)) {\n continue;\n }\n seen.add(dedupKey);\n entries.push({\n name,\n version: block.version,\n direct: directNames.has(name),\n });\n }\n }\n\n return entries;\n}\n\ninterface Block {\n names: Set<string>;\n version: string;\n}\n\nfunction parseBlocks(raw: string): Block[] {\n const lines = raw.split(/\\r?\\n/);\n const blocks: Block[] = [];\n let current: Block | null = null;\n\n const finalize = () => {\n if (current !== null && current.version.length > 0 && current.names.size > 0) {\n blocks.push(current);\n }\n current = null;\n };\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed.length === 0 || trimmed.startsWith('#')) {\n continue;\n }\n\n const indent = countLeadingSpaces(line);\n\n if (indent === 0) {\n finalize();\n if (!trimmed.endsWith(':')) {\n continue;\n }\n // `__metadata:` (yarn berry header) has no `@` in any descriptor and\n // produces an empty names set, so it's naturally skipped on finalize.\n const keyLine = trimmed.slice(0, -1);\n const names = new Set<string>();\n for (const spec of splitDescriptors(keyLine)) {\n const name = extractName(spec);\n if (name !== null) {\n names.add(name);\n }\n }\n current = { names, version: '' };\n continue;\n }\n\n if (current === null) {\n continue;\n }\n\n const version = parseVersionField(trimmed);\n if (version !== null) {\n current.version = version;\n }\n }\n\n finalize();\n return blocks;\n}\n\nfunction countLeadingSpaces(line: string): number {\n let i = 0;\n while (i < line.length && line[i] === ' ') {\n i++;\n }\n return i;\n}\n\n/**\n * Splits a yarn descriptor key list on top-level commas. yarn quotes any\n * descriptor that contains characters needing escaping, so we respect quotes\n * while splitting to avoid breaking on commas inside (rare in practice but\n * cheap to handle).\n */\nexport function splitDescriptors(keyLine: string): string[] {\n const parts: string[] = [];\n let current = '';\n let quote: '\"' | \"'\" | null = null;\n\n for (let i = 0; i < keyLine.length; i++) {\n const c = keyLine[i];\n if (quote !== null) {\n current += c;\n if (c === quote) {\n quote = null;\n }\n continue;\n }\n if (c === '\"' || c === \"'\") {\n quote = c;\n current += c;\n continue;\n }\n if (c === ',') {\n const piece = current.trim();\n if (piece.length > 0) {\n parts.push(piece);\n }\n current = '';\n continue;\n }\n current += c;\n }\n const tail = current.trim();\n if (tail.length > 0) {\n parts.push(tail);\n }\n return parts;\n}\n\n/**\n * Extracts the package name from a yarn descriptor like `axios@^1.6.0`,\n * `\"@scope/pkg@^2.1.0\"`, or `\"@scope/pkg@npm:2.1.0\"`. The descriptor's\n * range portion is discarded — we only need the name, since the resolved\n * version comes from the `version` field of the block.\n */\nexport function extractName(rawSpec: string): string | null {\n let s = rawSpec.trim();\n if (s.length === 0) {\n return null;\n }\n if (\n (s.startsWith('\"') && s.endsWith('\"')) ||\n (s.startsWith(\"'\") && s.endsWith(\"'\"))\n ) {\n s = s.slice(1, -1);\n }\n // Position-0 `@` belongs to a scope, so we want the last `@` after it.\n const atIdx = s.lastIndexOf('@');\n if (atIdx <= 0) {\n return null;\n }\n const name = s.slice(0, atIdx);\n return name.length > 0 ? name : null;\n}\n\nfunction parseVersionField(content: string): string | null {\n if (!content.startsWith('version')) {\n return null;\n }\n const after = content.slice('version'.length);\n // yarn v1: `version \"1.2.3\"` (whitespace then quoted)\n // yarn berry: `version: 1.2.3` or `version: \"1.2.3\"`\n const firstChar = after.charAt(0);\n if (firstChar !== ' ' && firstChar !== '\\t' && firstChar !== ':') {\n return null;\n }\n let rest = firstChar === ':' ? after.slice(1) : after;\n rest = rest.trim();\n if (rest.length === 0) {\n return null;\n }\n if (\n (rest.startsWith('\"') && rest.endsWith('\"')) ||\n (rest.startsWith(\"'\") && rest.endsWith(\"'\"))\n ) {\n rest = rest.slice(1, -1);\n }\n return rest.length > 0 ? rest : null;\n}\n\nasync function readDirectDepNames(cwd: string): Promise<Set<string>> {\n const names = new Set<string>();\n let raw: string;\n try {\n raw = await readFile(path.join(cwd, 'package.json'), 'utf8');\n } catch {\n return names;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return names;\n }\n\n if (typeof parsed !== 'object' || parsed === null) {\n return names;\n }\n const obj = parsed as Record<string, unknown>;\n\n for (const field of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) {\n const section = obj[field];\n if (typeof section !== 'object' || section === null) {\n continue;\n }\n for (const name of Object.keys(section)) {\n names.add(name);\n }\n }\n\n return names;\n}\n","import type { Manifest, PackageEntry } from './types.js';\n\nexport interface WirePackage {\n name: string;\n version: string;\n}\n\nexport interface WirePayload {\n ecosystem: Manifest['ecosystem'];\n packages: WirePackage[];\n}\n\nexport interface NormalizeStats {\n uniqueNames: number;\n duplicateNames: string[];\n totalEntries: number;\n}\n\nexport interface NormalizeResult {\n payload: WirePayload;\n stats: NormalizeStats;\n}\n\nexport function buildWirePayload(manifest: Manifest): NormalizeResult {\n const seen = new Map<string, Set<string>>();\n const wirePackages: WirePackage[] = [];\n\n for (const entry of manifest.packages) {\n const versions = seen.get(entry.name);\n if (versions) {\n if (versions.has(entry.version)) {\n continue;\n }\n versions.add(entry.version);\n } else {\n seen.set(entry.name, new Set([entry.version]));\n }\n wirePackages.push({ name: entry.name, version: entry.version });\n }\n\n wirePackages.sort((a, b) => {\n if (a.name === b.name) {\n return compareVersions(a.version, b.version);\n }\n return a.name < b.name ? -1 : 1;\n });\n\n const duplicateNames: string[] = [];\n for (const [name, versions] of seen) {\n if (versions.size > 1) {\n duplicateNames.push(name);\n }\n }\n\n return {\n payload: { ecosystem: manifest.ecosystem, packages: wirePackages },\n stats: {\n uniqueNames: seen.size,\n duplicateNames,\n totalEntries: manifest.packages.length,\n },\n };\n}\n\nexport function compareVersions(a: string, b: string): number {\n if (a === b) {\n return 0;\n }\n\n const [aBase, aPre] = splitPrerelease(a);\n const [bBase, bPre] = splitPrerelease(b);\n\n const baseCmp = compareSegments(aBase.split('.'), bBase.split('.'));\n if (baseCmp !== 0) {\n return baseCmp;\n }\n\n if (aPre === null && bPre === null) {\n return 0;\n }\n if (aPre === null) {\n return 1;\n }\n if (bPre === null) {\n return -1;\n }\n return compareSegments(aPre.split('.'), bPre.split('.'));\n}\n\nfunction splitPrerelease(version: string): [string, string | null] {\n const cleaned = version.replace(/^[v=]+/, '').split('+')[0]!;\n const dashIndex = cleaned.indexOf('-');\n if (dashIndex === -1) {\n return [cleaned, null];\n }\n return [cleaned.slice(0, dashIndex), cleaned.slice(dashIndex + 1)];\n}\n\nfunction compareSegments(a: string[], b: string[]): number {\n const max = Math.max(a.length, b.length);\n for (let i = 0; i < max; i++) {\n const aPart = a[i];\n const bPart = b[i];\n if (aPart === undefined) {\n return -1;\n }\n if (bPart === undefined) {\n return 1;\n }\n const aNum = /^\\d+$/.test(aPart);\n const bNum = /^\\d+$/.test(bPart);\n if (aNum && bNum) {\n const diff = Number(aPart) - Number(bPart);\n if (diff !== 0) {\n return diff < 0 ? -1 : 1;\n }\n continue;\n }\n if (aNum) {\n return -1;\n }\n if (bNum) {\n return 1;\n }\n if (aPart < bPart) {\n return -1;\n }\n if (aPart > bPart) {\n return 1;\n }\n }\n return 0;\n}\n\nexport function findPackageInManifest(\n manifest: Manifest,\n name: string,\n): PackageEntry[] {\n return manifest.packages.filter((p) => p.name === name);\n}\n","import { PatchstackError, type Config, type StoreManifestResponse } from './types.js';\nimport type { WirePayload } from './normalize.js';\n\nexport const DEFAULT_ENDPOINT = 'https://api.patchstack.com/monitor/pulse/manifest';\nexport const DEFAULT_TIMEOUT_MS = 30_000;\n\nexport function buildEndpointUrl(base: string, siteUuid?: string | null): string {\n const trimmed = base.replace(/\\/$/, '');\n return siteUuid !== undefined && siteUuid !== null && siteUuid.length > 0\n ? `${trimmed}/${encodeURIComponent(siteUuid)}`\n : trimmed;\n}\n\n/**\n * Build the claim URL for a site. The claim page lives on the same origin as\n * the API endpoint, at `/monitor/claim?site=<uuid>`. Using the API endpoint's\n * origin (rather than a hard-coded https://api.patchstack.com) means staging,\n * ngrok tunnels and local dev environments all produce a claim URL on the same\n * host the connector is already talking to.\n */\nexport function buildClaimUrl(endpoint: string, siteUuid: string): string {\n const origin = new URL(endpoint).origin;\n return `${origin}/monitor/claim?site=${encodeURIComponent(siteUuid)}`;\n}\n\nexport async function postManifest(\n config: Config,\n payload: WirePayload,\n): Promise<StoreManifestResponse> {\n const url = buildEndpointUrl(config.endpoint, config.siteUuid);\n const timeoutMs = config.timeoutMs;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'User-Agent': '@patchstack/connect',\n },\n body: JSON.stringify({ ...payload, environment: config.environment }),\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (cause) {\n if (isTimeoutError(cause)) {\n throw new PatchstackError(\n `Patchstack request to ${url} timed out after ${timeoutMs}ms. Override with PATCHSTACK_TIMEOUT_MS.`,\n 'NETWORK_TIMEOUT',\n cause,\n );\n }\n throw new PatchstackError(\n `Could not reach Patchstack at ${url}. Check your network connection.`,\n 'NETWORK_ERROR',\n cause,\n );\n }\n\n const text = await response.text();\n let body: StoreManifestResponse | null = null;\n try {\n body = text.length > 0 ? (JSON.parse(text) as StoreManifestResponse) : null;\n } catch {\n body = null;\n }\n\n if (response.status === 404) {\n throw new PatchstackError(\n body?.error ?? 'Site not found. Check that your site UUID is correct and that the app is registered as a Pulse app in your Patchstack dashboard.',\n 'SITE_NOT_FOUND',\n );\n }\n\n if (response.status === 422) {\n throw new PatchstackError(\n body?.message ?? 'Patchstack rejected the manifest payload (validation failed).',\n 'VALIDATION_ERROR',\n );\n }\n\n if (response.status < 200 || response.status >= 300) {\n throw new PatchstackError(\n `Patchstack returned ${response.status}: ${text.slice(0, 200)}`,\n 'SERVER_ERROR',\n );\n }\n\n if (body === null) {\n throw new PatchstackError('Patchstack returned an empty response.', 'SERVER_ERROR');\n }\n\n return body;\n}\n\nfunction isTimeoutError(cause: unknown): boolean {\n if (cause instanceof Error) {\n return cause.name === 'TimeoutError' || cause.name === 'AbortError';\n }\n return false;\n}\n","import { readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Config, type Environment } from './types.js';\nimport { DEFAULT_ENDPOINT, DEFAULT_TIMEOUT_MS } from './client.js';\n\nconst CONFIG_FILENAME = '.patchstackrc.json';\n\nexport const DEFAULT_ENVIRONMENT: Environment = 'production';\n\ninterface ConfigFile {\n siteUuid?: string;\n endpoint?: string;\n timeoutMs?: number;\n environment?: string;\n}\n\nexport interface ResolveConfigOptions {\n cwd: string;\n cliSiteUuid?: string;\n cliEndpoint?: string;\n /**\n * When true, resolveConfig throws CONFIG_MISSING if no site UUID is configured.\n * Defaults to false: callers that can run without a UUID (the first `scan` after\n * `npm install`) just get `siteUuid: null` back and learn the UUID from the\n * server response.\n */\n requireSiteUuid?: boolean;\n}\n\nexport async function resolveConfig(options: ResolveConfigOptions): Promise<Config> {\n const fromFile = await readConfigFile(options.cwd);\n const fromEnv = readEnv();\n\n const siteUuid =\n options.cliSiteUuid ??\n fromEnv.siteUuid ??\n fromFile.siteUuid ??\n null;\n\n const endpoint =\n options.cliEndpoint ??\n fromEnv.endpoint ??\n fromFile.endpoint ??\n DEFAULT_ENDPOINT;\n\n const timeoutMs = fromEnv.timeoutMs ?? fromFile.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const environmentRaw = fromEnv.environment ?? fromFile.environment;\n if (environmentRaw !== undefined && !isEnvironment(environmentRaw)) {\n throw new PatchstackError(\n `Environment must be \"production\" or \"sandbox\"; got \"${environmentRaw}\".`,\n 'CONFIG_INVALID',\n );\n }\n const environment: Environment = environmentRaw ?? DEFAULT_ENVIRONMENT;\n\n if (siteUuid !== null && siteUuid.length > 0 && !isUuid(siteUuid)) {\n throw new PatchstackError(\n `Site UUID \"${siteUuid}\" does not look like a valid UUID.`,\n 'CONFIG_INVALID',\n );\n }\n\n if (options.requireSiteUuid && (siteUuid === null || siteUuid.length === 0)) {\n throw new PatchstackError(\n 'No site UUID configured. Run `patchstack-connect scan` to provision one, or set PATCHSTACK_SITE_UUID.',\n 'CONFIG_MISSING',\n );\n }\n\n return {\n siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid,\n endpoint,\n timeoutMs,\n environment,\n };\n}\n\nexport async function writeConfigFile(cwd: string, config: ConfigFile): Promise<string> {\n const target = path.join(cwd, CONFIG_FILENAME);\n const content = JSON.stringify(config, null, 2) + '\\n';\n await writeFile(target, content, 'utf8');\n return target;\n}\n\n/**\n * Merge a new siteUuid into the existing `.patchstackrc.json` (or create it).\n * Preserves any `endpoint` / `timeoutMs` the user already wrote.\n */\nexport async function persistSiteUuid(cwd: string, siteUuid: string): Promise<string> {\n const existing = await readConfigFile(cwd);\n return writeConfigFile(cwd, { ...existing, siteUuid });\n}\n\nasync function readConfigFile(cwd: string): Promise<ConfigFile> {\n const target = path.join(cwd, CONFIG_FILENAME);\n let raw: string;\n try {\n raw = await readFile(target, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return {};\n }\n throw new PatchstackError(\n `Could not read ${target}: ${(err as Error).message}`,\n 'CONFIG_INVALID',\n err,\n );\n }\n\n try {\n return JSON.parse(raw) as ConfigFile;\n } catch (err) {\n throw new PatchstackError(\n `Config file ${target} contains invalid JSON.`,\n 'CONFIG_INVALID',\n err,\n );\n }\n}\n\nfunction readEnv(): ConfigFile {\n const timeoutRaw = process.env.PATCHSTACK_TIMEOUT_MS;\n let timeoutMs: number | undefined;\n if (timeoutRaw !== undefined && timeoutRaw.length > 0) {\n const parsed = Number(timeoutRaw);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n throw new PatchstackError(\n `PATCHSTACK_TIMEOUT_MS must be a positive number; got \"${timeoutRaw}\".`,\n 'CONFIG_INVALID',\n );\n }\n timeoutMs = parsed;\n }\n const environmentRaw = process.env.PATCHSTACK_ENVIRONMENT;\n return {\n siteUuid: process.env.PATCHSTACK_SITE_UUID ?? undefined,\n endpoint: process.env.PATCHSTACK_ENDPOINT ?? undefined,\n timeoutMs,\n environment:\n environmentRaw !== undefined && environmentRaw.length > 0 ? environmentRaw : undefined,\n };\n}\n\nfunction isUuid(value: string): boolean {\n return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);\n}\n\nfunction isEnvironment(value: string): value is Environment {\n return value === 'production' || value === 'sandbox';\n}\n"],"mappings":"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","names":["import_promises","import_node_path","path","import_promises","import_node_path","path","import_promises","import_promises","import_node_path","path","path","import_promises","import_node_path","path"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/parsers/index.ts","../src/types.ts","../src/parsers/consistency.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/parsers/pnpm.ts","../src/parsers/yarn.ts","../src/normalize.ts","../src/client.ts","../src/config.ts"],"sourcesContent":["import { scanLockfile } from './parsers/index.js';\nimport { buildWirePayload } from './normalize.js';\nimport { postManifest } from './client.js';\nimport { persistSiteUuid, resolveConfig } from './config.js';\nimport type { Config, Manifest, StoreManifestResponse } from './types.js';\n\nexport { scanLockfile, detectLockfile } from './parsers/index.js';\nexport { buildWirePayload, compareVersions } from './normalize.js';\nexport { postManifest, buildClaimUrl, buildEndpointUrl, DEFAULT_ENDPOINT } from './client.js';\nexport { persistSiteUuid, resolveConfig, writeConfigFile } from './config.js';\nexport {\n PatchstackError,\n type Config,\n type Ecosystem,\n type Manifest,\n type PackageEntry,\n type StoreManifestResponse,\n} from './types.js';\n\nexport interface ScanAndReportOptions {\n cwd?: string;\n config?: Config;\n}\n\nexport interface ScanAndReportResult {\n manifest: Manifest;\n response: StoreManifestResponse;\n duplicateNames: string[];\n uniqueNames: number;\n totalEntries: number;\n}\n\nexport async function scanAndReport(\n options: ScanAndReportOptions = {},\n): Promise<ScanAndReportResult> {\n const cwd = options.cwd ?? process.cwd();\n const config = options.config ?? (await resolveConfig({ cwd }));\n const manifest = await scanLockfile(cwd);\n const { payload, stats } = buildWirePayload(manifest);\n const response = await postManifest(config, payload);\n\n // First-run convenience: if we didn't have a UUID and the server provisioned\n // one for us, persist it so subsequent runs target the same site.\n if (config.siteUuid === null && response.uuid !== undefined && response.uuid.length > 0) {\n await persistSiteUuid(cwd, response.uuid);\n }\n\n return {\n manifest,\n response,\n duplicateNames: stats.duplicateNames,\n uniqueNames: stats.uniqueNames,\n totalEntries: stats.totalEntries,\n };\n}\n","import { access } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Manifest, type PackageEntry } from '../types.js';\nimport { missingDependencies, readDeclaredDependencyNames } from './consistency.js';\nimport { parseNpmLockfile } from './npm.js';\nimport { walkNodeModules } from './node_modules.js';\nimport { parsePnpmLockfile } from './pnpm.js';\nimport { parseYarnLockfile } from './yarn.js';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy =\n | 'npm-lockfile'\n | 'node-modules-walk'\n | 'pnpm-lockfile'\n | 'yarn-lockfile';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\ninterface LockfileCandidate {\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\n// Checked in priority order: the first candidate present in `cwd` wins.\nconst LOCKFILE_CANDIDATES: readonly LockfileCandidate[] = [\n { filename: 'package-lock.json', strategy: 'npm-lockfile' },\n { filename: 'bun.lock', strategy: 'node-modules-walk' },\n { filename: 'bun.lockb', strategy: 'node-modules-walk' },\n { filename: 'pnpm-lock.yaml', strategy: 'pnpm-lockfile' },\n { filename: 'yarn.lock', strategy: 'yarn-lockfile' },\n];\n\nexport async function detectLockfile(cwd: string): Promise<DetectedLockfile> {\n // Probe every candidate concurrently, then resolve by declaration order so\n // the result is identical to a sequential first-match scan — but bounded by a\n // single I/O round-trip instead of up to one `stat` per candidate.\n const probed = await Promise.all(\n LOCKFILE_CANDIDATES.map(async (candidate) => {\n const filePath = path.join(cwd, candidate.filename);\n return { ...candidate, filePath, present: await exists(filePath) };\n }),\n );\n\n const match = probed.find((candidate) => candidate.present);\n if (match) {\n return {\n ecosystem: 'npm',\n filePath: match.filePath,\n filename: match.filename,\n strategy: match.strategy,\n };\n }\n\n const expected = LOCKFILE_CANDIDATES.map((candidate) => candidate.filename).join(', ');\n throw new PatchstackError(\n `No lockfile found in ${cwd}. Expected one of: ${expected}.`,\n 'LOCKFILE_NOT_FOUND',\n );\n}\n\nexport async function scanLockfile(cwd: string): Promise<Manifest> {\n const candidates = await presentLockfiles(cwd);\n if (candidates.length === 0) {\n // Preserve the exact historical error for the no-lockfile case.\n await detectLockfile(cwd);\n }\n\n // Validate each source against package.json before trusting it. A lockfile\n // missing declared dependencies is a fossil — e.g. `npm install` planted a\n // package-lock.json once on a bun-managed platform (Lovable), and the native\n // dependency flow never updates it again. Trusting it would freeze the\n // manifest and the build fingerprint while the real dependency set drifts.\n const declared = await readDeclaredDependencyNames(cwd);\n const warnings: string[] = [];\n let firstParsed: { packages: PackageEntry[]; filename: string } | null = null;\n let walkTried = false;\n\n for (const candidate of candidates) {\n let packages: PackageEntry[];\n try {\n packages = await runStrategy(candidate, cwd);\n } catch {\n continue;\n }\n walkTried ||= candidate.strategy === 'node-modules-walk';\n firstParsed ??= { packages, filename: candidate.filename };\n\n const missing = missingDependencies(declared, packages);\n if (missing.length === 0) {\n return manifestWith(packages, warnings);\n }\n warnings.push(staleWarning(candidate.filename, missing));\n }\n\n // Last resort: the installed truth. node_modules reflects what the build\n // actually compiles against, whichever package manager wrote it.\n if (!walkTried) {\n try {\n const packages = await walkNodeModules(cwd);\n const missing = missingDependencies(declared, packages);\n if (missing.length > 0) {\n warnings.push(staleWarning('node_modules/', missing));\n }\n warnings.push('Scanned node_modules/ instead. Delete the stale lockfile to silence this warning.');\n return manifestWith(packages, warnings);\n } catch {\n // fall through to the best lockfile we managed to parse\n }\n }\n\n if (firstParsed === null) {\n const expected = LOCKFILE_CANDIDATES.map((candidate) => candidate.filename).join(', ');\n throw new PatchstackError(\n `No readable lockfile found in ${cwd}. Expected one of: ${expected}.`,\n 'LOCKFILE_NOT_FOUND',\n );\n }\n\n warnings.push(\n `No fully-consistent source found; reporting ${firstParsed.filename}. The manifest may understate the real dependency set.`,\n );\n\n return manifestWith(firstParsed.packages, warnings);\n}\n\nfunction manifestWith(packages: PackageEntry[], warnings: string[]): Manifest {\n return warnings.length > 0\n ? { ecosystem: 'npm', packages, warnings }\n : { ecosystem: 'npm', packages };\n}\n\nfunction staleWarning(source: string, missing: string[]): string {\n const sample = missing.slice(0, 3).join(', ');\n const suffix = missing.length > 3 ? `, +${missing.length - 3} more` : '';\n return `${source} looks stale: package.json declares ${missing.length} dependenc${missing.length === 1 ? 'y' : 'ies'} it does not contain (${sample}${suffix}).`;\n}\n\nasync function presentLockfiles(cwd: string): Promise<DetectedLockfile[]> {\n const probed = await Promise.all(\n LOCKFILE_CANDIDATES.map(async (candidate) => {\n const filePath = path.join(cwd, candidate.filename);\n return { ...candidate, filePath, present: await exists(filePath) };\n }),\n );\n\n return probed\n .filter((candidate) => candidate.present)\n .map(({ filename, strategy, filePath }) => ({ ecosystem: 'npm' as const, filename, strategy, filePath }));\n}\n\nasync function runStrategy(\n detected: DetectedLockfile,\n cwd: string,\n): Promise<PackageEntry[]> {\n switch (detected.strategy) {\n case 'npm-lockfile':\n return parseNpmLockfile(detected.filePath);\n case 'pnpm-lockfile':\n return parsePnpmLockfile(detected.filePath);\n case 'yarn-lockfile':\n return parseYarnLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise<boolean> {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n","export type Ecosystem = 'npm' | 'composer';\n\n/**\n * Which environment a manifest was captured in. The connector reports from real\n * builds (prebuild scan / build hooks), so it defaults to 'production'. Override\n * with PATCHSTACK_ENVIRONMENT=sandbox (or \"environment\" in .patchstackrc.json)\n * for test manifests.\n */\nexport type Environment = 'production' | 'sandbox';\n\nexport interface PackageEntry {\n name: string;\n version: string;\n path?: string;\n direct?: boolean;\n}\n\nexport interface Manifest {\n ecosystem: Ecosystem;\n packages: PackageEntry[];\n /**\n * Human-readable scan diagnostics — set when the preferred lockfile looked\n * stale (missing dependencies declared in package.json) and another source\n * was used, or when no fully-consistent source existed. Never fatal.\n */\n warnings?: string[];\n}\n\nexport interface Config {\n /**\n * The site UUID. `null` means we don't have one yet — `postManifest` will then\n * post to the bare endpoint, the server will provision a fresh site, and the\n * UUID it returns should be persisted via `persistSiteUuid()`.\n */\n siteUuid: string | null;\n endpoint: string;\n timeoutMs: number;\n /** Environment to report the manifest under. Defaults to 'production'. */\n environment: Environment;\n}\n\nexport interface StoreManifestResponse {\n /** The UUID of the site the manifest was stored against. Always returned. */\n uuid?: string;\n stored: boolean;\n manifest_id?: number;\n checksum?: string;\n reason?: string;\n message?: string;\n error?: string;\n}\n\nexport class PatchstackError extends Error {\n constructor(\n message: string,\n public readonly code:\n | 'CONFIG_MISSING'\n | 'CONFIG_INVALID'\n | 'LOCKFILE_NOT_FOUND'\n | 'LOCKFILE_UNSUPPORTED'\n | 'LOCKFILE_PARSE_ERROR'\n | 'NETWORK_ERROR'\n | 'NETWORK_TIMEOUT'\n | 'SITE_NOT_FOUND'\n | 'VALIDATION_ERROR'\n | 'SERVER_ERROR',\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = 'PatchstackError';\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport type { PackageEntry } from '../types.js';\n\n/**\n * Registry-installable dependency names declared in package.json\n * (dependencies + devDependencies). Non-registry specifiers (file:, link:,\n * workspace:, portal:) are excluded — lockfile parsers skip those too, so\n * their absence from a parsed set says nothing about staleness. Returns []\n * when package.json is missing or unreadable (nothing to validate against).\n */\nexport async function readDeclaredDependencyNames(cwd: string): Promise<string[]> {\n let raw: string;\n try {\n raw = await readFile(path.join(cwd, 'package.json'), 'utf8');\n } catch {\n return [];\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return [];\n }\n if (typeof parsed !== 'object' || parsed === null) {\n return [];\n }\n\n const names = new Set<string>();\n for (const group of ['dependencies', 'devDependencies'] as const) {\n const deps = (parsed as Record<string, unknown>)[group];\n if (typeof deps !== 'object' || deps === null) {\n continue;\n }\n for (const [name, spec] of Object.entries(deps as Record<string, unknown>)) {\n if (typeof spec === 'string' && /^(file|link|workspace|portal):/.test(spec)) {\n continue;\n }\n names.add(name);\n }\n }\n\n return [...names];\n}\n\n/**\n * Declared names absent from a parsed package set. A non-empty result means\n * the source predates package.json — the fossil-lockfile failure mode where\n * e.g. `npm install` planted a package-lock.json once and the platform's\n * native package manager (bun on Lovable) never updates it again.\n */\nexport function missingDependencies(declared: string[], packages: PackageEntry[]): string[] {\n const present = new Set(packages.map((entry) => entry.name));\n return declared.filter((name) => !present.has(name));\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\ninterface LockfileV2Package {\n name?: string;\n version?: string;\n link?: boolean;\n resolved?: string;\n}\n\ninterface LockfileV2 {\n lockfileVersion: number;\n packages?: Record<string, LockfileV2Package>;\n dependencies?: Record<string, LockfileV1Dependency>;\n}\n\ninterface LockfileV1Dependency {\n version: string;\n dependencies?: Record<string, LockfileV1Dependency>;\n}\n\nexport async function parseNpmLockfile(lockfilePath: string): Promise<PackageEntry[]> {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, 'LOCKFILE_NOT_FOUND', cause);\n }\n\n let parsed: LockfileV2;\n try {\n parsed = JSON.parse(raw) as LockfileV2;\n } catch (cause) {\n throw new PatchstackError(`Lockfile at ${lockfilePath} is not valid JSON`, 'LOCKFILE_PARSE_ERROR', cause);\n }\n\n if (parsed.packages) {\n return extractFromV2(parsed.packages);\n }\n\n if (parsed.dependencies) {\n return extractFromV1(parsed.dependencies);\n }\n\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} has no \"packages\" or \"dependencies\" key`,\n 'LOCKFILE_PARSE_ERROR',\n );\n}\n\nfunction extractFromV2(packages: Record<string, LockfileV2Package>): PackageEntry[] {\n const entries: PackageEntry[] = [];\n\n for (const [pkgPath, pkg] of Object.entries(packages)) {\n if (pkgPath === '') {\n continue;\n }\n if (pkg.link === true) {\n continue;\n }\n if (typeof pkg.version !== 'string' || pkg.version.length === 0) {\n continue;\n }\n\n const name = pkg.name ?? extractNameFromPath(pkgPath);\n if (name === null) {\n continue;\n }\n\n entries.push({\n name,\n version: pkg.version,\n path: pkgPath,\n direct: isDirectV2(pkgPath),\n });\n }\n\n return entries;\n}\n\nfunction extractFromV1(\n deps: Record<string, LockfileV1Dependency>,\n acc: PackageEntry[] = [],\n depth = 0,\n): PackageEntry[] {\n for (const [name, dep] of Object.entries(deps)) {\n if (typeof dep.version === 'string' && dep.version.length > 0) {\n acc.push({ name, version: dep.version, direct: depth === 0 });\n }\n if (dep.dependencies) {\n extractFromV1(dep.dependencies, acc, depth + 1);\n }\n }\n return acc;\n}\n\nfunction extractNameFromPath(pkgPath: string): string | null {\n const segments = pkgPath.split('node_modules' + path.sep === pkgPath ? path.sep : '/');\n const parts = pkgPath.split('/');\n const nmIndex = parts.lastIndexOf('node_modules');\n if (nmIndex === -1 || nmIndex >= parts.length - 1) {\n return segments[segments.length - 1] ?? null;\n }\n const tail = parts.slice(nmIndex + 1);\n if (tail.length === 0) {\n return null;\n }\n const first = tail[0];\n if (first !== undefined && first.startsWith('@') && tail.length >= 2) {\n return `${first}/${tail[1]}`;\n }\n return first ?? null;\n}\n\nfunction isDirectV2(pkgPath: string): boolean {\n const parts = pkgPath.split('/');\n const nmCount = parts.filter((p) => p === 'node_modules').length;\n return nmCount === 1;\n}\n","import { lstat, readFile, readdir, stat } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\nexport async function walkNodeModules(cwd: string): Promise<PackageEntry[]> {\n const root = path.join(cwd, 'node_modules');\n\n try {\n const info = await stat(root);\n if (!info.isDirectory()) {\n throw new PatchstackError(\n `${root} exists but is not a directory.`,\n 'LOCKFILE_NOT_FOUND',\n );\n }\n } catch (cause) {\n if (cause instanceof PatchstackError) {\n throw cause;\n }\n throw new PatchstackError(\n `node_modules/ not found at ${cwd}. Install dependencies first (e.g. \\`bun install\\` or \\`npm install\\`).`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const entries: PackageEntry[] = [];\n await walk(root, entries, 0);\n return entries;\n}\n\nasync function walk(dir: string, acc: PackageEntry[], depth: number): Promise<void> {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n return;\n }\n\n for (const name of names) {\n if (name.startsWith('.')) {\n continue;\n }\n\n const fullPath = path.join(dir, name);\n if (!(await isPlainDirectory(fullPath))) {\n continue;\n }\n\n if (name.startsWith('@')) {\n let subNames: string[];\n try {\n subNames = await readdir(fullPath);\n } catch {\n continue;\n }\n for (const sub of subNames) {\n if (sub.startsWith('.')) {\n continue;\n }\n const scopedDir = path.join(fullPath, sub);\n if (!(await isPlainDirectory(scopedDir))) {\n continue;\n }\n await readPackage(scopedDir, depth, acc);\n await walkNested(scopedDir, acc, depth);\n }\n continue;\n }\n\n await readPackage(fullPath, depth, acc);\n await walkNested(fullPath, acc, depth);\n }\n}\n\nasync function readPackage(\n pkgDir: string,\n depth: number,\n acc: PackageEntry[],\n): Promise<void> {\n let raw: string;\n try {\n raw = await readFile(path.join(pkgDir, 'package.json'), 'utf8');\n } catch {\n return;\n }\n\n let parsed: { name?: unknown; version?: unknown };\n try {\n parsed = JSON.parse(raw) as { name?: unknown; version?: unknown };\n } catch {\n return;\n }\n\n if (typeof parsed.name !== 'string' || parsed.name.length === 0) {\n return;\n }\n if (typeof parsed.version !== 'string' || parsed.version.length === 0) {\n return;\n }\n\n acc.push({\n name: parsed.name,\n version: parsed.version,\n direct: depth === 0,\n });\n}\n\nasync function walkNested(\n pkgDir: string,\n acc: PackageEntry[],\n depth: number,\n): Promise<void> {\n const nested = path.join(pkgDir, 'node_modules');\n if (!(await isPlainDirectory(nested))) {\n return;\n }\n await walk(nested, acc, depth + 1);\n}\n\nasync function isPlainDirectory(dir: string): Promise<boolean> {\n try {\n const info = await lstat(dir);\n return info.isDirectory() && !info.isSymbolicLink();\n } catch {\n return false;\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\n/**\n * Parses pnpm-lock.yaml without pulling in a YAML library. We don't need full\n * YAML semantics — only the `packages:` block (the canonical list of every\n * installed package) and, for direct-dep marking, the dependency sections under\n * `importers:` (v9+) or at the top level (v6-v8 single-project).\n *\n * Supported pnpm-lock key formats:\n * v5: /pkg/1.0.0 /@scope/pkg/1.0.0\n * v6-v8: /pkg@1.0.0 /@scope/pkg@1.0.0 (optional `(peer@x)` suffix)\n * v9: pkg@1.0.0 @scope/pkg@1.0.0 (optional `(peer@x)` suffix, may be quoted)\n */\nexport async function parsePnpmLockfile(lockfilePath: string): Promise<PackageEntry[]> {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(\n `Could not read lockfile at ${lockfilePath}`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const lines = raw.split(/\\r?\\n/);\n const directNames = collectDirectDepNames(lines);\n const packageKeys = collectPackagesBlockKeys(lines);\n\n if (packageKeys.length === 0) {\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} has no \"packages\" entries`,\n 'LOCKFILE_PARSE_ERROR',\n );\n }\n\n const entries: PackageEntry[] = [];\n for (const key of packageKeys) {\n const parsed = parsePackageKey(key);\n if (parsed === null) {\n continue;\n }\n entries.push({\n name: parsed.name,\n version: parsed.version,\n direct: directNames.has(parsed.name),\n });\n }\n\n return entries;\n}\n\ninterface ParsedKey {\n name: string;\n version: string;\n}\n\nexport function parsePackageKey(rawKey: string): ParsedKey | null {\n let k = rawKey.trim();\n if (k.length === 0) {\n return null;\n }\n\n if (\n (k.startsWith(\"'\") && k.endsWith(\"'\")) ||\n (k.startsWith('\"') && k.endsWith('\"'))\n ) {\n k = k.slice(1, -1);\n }\n\n if (k.startsWith('/')) {\n k = k.slice(1);\n }\n\n // Strip peer-dependency suffix used by v6+: `pkg@1.0.0(peer@1.0.0)(other@2)`.\n const parenIdx = k.indexOf('(');\n if (parenIdx >= 0) {\n k = k.slice(0, parenIdx);\n }\n\n // Split off the optional `@scope/` prefix so the remaining \"body\" contains\n // exactly one separator between the bare name and the version. npm forbids\n // both `@` and `/` inside the bare-name portion, so the first occurrence of\n // either character in the body is unambiguously the name/version separator.\n let scopePrefix = '';\n let body = k;\n if (k.startsWith('@')) {\n const firstSlash = k.indexOf('/');\n if (firstSlash <= 0) {\n return null;\n }\n scopePrefix = k.slice(0, firstSlash + 1);\n body = k.slice(firstSlash + 1);\n }\n\n const slashIdx = body.indexOf('/');\n const atIdx = body.indexOf('@');\n\n let sepIdx: number;\n if (slashIdx < 0 && atIdx < 0) {\n return null;\n } else if (slashIdx < 0) {\n sepIdx = atIdx;\n } else if (atIdx < 0) {\n sepIdx = slashIdx;\n } else {\n sepIdx = Math.min(slashIdx, atIdx);\n }\n\n const name = scopePrefix + body.slice(0, sepIdx);\n let version = body.slice(sepIdx + 1);\n\n // v5 peer suffix uses `_`: `1.0.0_react@18.2.0`.\n const underscoreIdx = version.indexOf('_');\n if (underscoreIdx >= 0) {\n version = version.slice(0, underscoreIdx);\n }\n\n if (name.length === 0 || version.length === 0) {\n return null;\n }\n\n return { name, version };\n}\n\nfunction indentOf(line: string): number {\n let i = 0;\n while (i < line.length && line[i] === ' ') {\n i++;\n }\n return i;\n}\n\nfunction isBlankOrComment(line: string): boolean {\n const trimmed = line.trim();\n return trimmed.length === 0 || trimmed.startsWith('#');\n}\n\n/**\n * Collects keys directly nested under the top-level `packages:` block. Stops\n * when another top-level key (e.g. `snapshots:`) appears.\n */\nfunction collectPackagesBlockKeys(lines: string[]): string[] {\n const keys: string[] = [];\n let inBlock = false;\n let childIndent: number | null = null;\n\n for (const line of lines) {\n if (isBlankOrComment(line)) {\n continue;\n }\n\n const indent = indentOf(line);\n\n if (!inBlock) {\n if (indent === 0 && line.trim() === 'packages:') {\n inBlock = true;\n }\n continue;\n }\n\n if (indent === 0) {\n break;\n }\n\n if (childIndent === null) {\n childIndent = indent;\n }\n\n if (indent !== childIndent) {\n continue;\n }\n\n const content = line.slice(indent);\n if (!content.endsWith(':')) {\n continue;\n }\n\n keys.push(content.slice(0, -1));\n }\n\n return keys;\n}\n\n/**\n * Collects direct dependency names. Looks in two places, since the format\n * differs across lockfile versions and workspaces:\n *\n * 1. `importers:` > `<importer>:` > `{dependencies,devDependencies,optionalDependencies}:`\n * (v9 always, v6-v8 in workspaces)\n * 2. Top-level `{dependencies,devDependencies,optionalDependencies}:`\n * (v6-v8 single-project lockfiles)\n *\n * Direct-dep marking is best-effort: a name absent from both is simply left as\n * not-direct, matching the npm parser's behaviour for transitive packages.\n */\nfunction collectDirectDepNames(lines: string[]): Set<string> {\n const names = new Set<string>();\n collectFromImporters(lines, names);\n for (const section of ['dependencies', 'devDependencies', 'optionalDependencies']) {\n collectFromTopLevelSection(lines, section, names);\n }\n return names;\n}\n\nconst DEP_SECTIONS = new Set([\n 'dependencies',\n 'devDependencies',\n 'optionalDependencies',\n]);\n\nfunction collectFromImporters(lines: string[], out: Set<string>): void {\n let inImporters = false;\n let importerIndent: number | null = null;\n let inDepSection = false;\n let depSectionIndent: number | null = null;\n let leafIndent: number | null = null;\n\n for (const line of lines) {\n if (isBlankOrComment(line)) {\n continue;\n }\n const indent = indentOf(line);\n const trimmed = line.trim();\n\n if (!inImporters) {\n if (indent === 0 && trimmed === 'importers:') {\n inImporters = true;\n }\n continue;\n }\n\n if (indent === 0) {\n break;\n }\n\n // Track the indent at which importer entries (e.g. `.:`, `packages/foo:`) sit.\n if (importerIndent === null) {\n importerIndent = indent;\n }\n\n if (indent === importerIndent) {\n inDepSection = false;\n depSectionIndent = null;\n leafIndent = null;\n continue;\n }\n\n if (!inDepSection) {\n const key = stripTrailingColon(trimmed);\n if (key !== null && DEP_SECTIONS.has(key)) {\n inDepSection = true;\n depSectionIndent = indent;\n }\n continue;\n }\n\n if (depSectionIndent !== null && indent <= depSectionIndent) {\n // Reset — we've exited the dep section. Reconsider this line.\n inDepSection = false;\n depSectionIndent = null;\n leafIndent = null;\n const key = stripTrailingColon(trimmed);\n if (key !== null && DEP_SECTIONS.has(key)) {\n inDepSection = true;\n depSectionIndent = indent;\n }\n continue;\n }\n\n if (leafIndent === null) {\n leafIndent = indent;\n }\n\n if (indent !== leafIndent) {\n continue;\n }\n\n const name = extractLeafName(trimmed);\n if (name !== null) {\n out.add(name);\n }\n }\n}\n\nfunction collectFromTopLevelSection(\n lines: string[],\n section: string,\n out: Set<string>,\n): void {\n let inSection = false;\n let leafIndent: number | null = null;\n\n for (const line of lines) {\n if (isBlankOrComment(line)) {\n continue;\n }\n const indent = indentOf(line);\n const trimmed = line.trim();\n\n if (!inSection) {\n if (indent === 0 && trimmed === `${section}:`) {\n inSection = true;\n }\n continue;\n }\n\n if (indent === 0) {\n break;\n }\n\n if (leafIndent === null) {\n leafIndent = indent;\n }\n\n if (indent !== leafIndent) {\n continue;\n }\n\n const name = extractLeafName(trimmed);\n if (name !== null) {\n out.add(name);\n }\n }\n}\n\nfunction stripTrailingColon(s: string): string | null {\n if (!s.endsWith(':')) {\n return null;\n }\n return s.slice(0, -1).trim();\n}\n\n/**\n * Extracts the package name from a dependency-section leaf, handling both the\n * short form (`pkg: 1.0.0`) and the v9 expanded form (`pkg:` followed by\n * `specifier:` / `version:` children). Quoted scoped names like `'@scope/pkg':`\n * are unquoted.\n */\nfunction extractLeafName(trimmed: string): string | null {\n const colonIdx = trimmed.indexOf(':');\n if (colonIdx < 0) {\n return null;\n }\n let name = trimmed.slice(0, colonIdx).trim();\n if (name.length === 0) {\n return null;\n }\n if (\n (name.startsWith(\"'\") && name.endsWith(\"'\")) ||\n (name.startsWith('\"') && name.endsWith('\"'))\n ) {\n name = name.slice(1, -1);\n }\n return name.length > 0 ? name : null;\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\n/**\n * Parses yarn.lock (yarn classic v1 and yarn berry v2+) without a YAML\n * dependency. Both generations share the same block structure — a top-level\n * mapping of comma-separated descriptor lists to a block containing a\n * `version` field — so we walk them with the same scanner and only branch on\n * the `version` syntax (`version \"x\"` for v1, `version: x` for berry).\n *\n * Direct vs transitive can't be derived from yarn.lock alone (yarn does not\n * record an importer manifest the way pnpm v9 does), so we cross-reference\n * the sibling `package.json` when present.\n */\nexport async function parseYarnLockfile(lockfilePath: string): Promise<PackageEntry[]> {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(\n `Could not read lockfile at ${lockfilePath}`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const blocks = parseBlocks(raw);\n if (blocks.length === 0) {\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} contains no package entries`,\n 'LOCKFILE_PARSE_ERROR',\n );\n }\n\n const directNames = await readDirectDepNames(path.dirname(lockfilePath));\n\n const entries: PackageEntry[] = [];\n const seen = new Set<string>();\n for (const block of blocks) {\n if (block.version.length === 0 || block.names.size === 0) {\n continue;\n }\n for (const name of block.names) {\n const dedupKey = `${name}@${block.version}`;\n if (seen.has(dedupKey)) {\n continue;\n }\n seen.add(dedupKey);\n entries.push({\n name,\n version: block.version,\n direct: directNames.has(name),\n });\n }\n }\n\n return entries;\n}\n\ninterface Block {\n names: Set<string>;\n version: string;\n}\n\nfunction parseBlocks(raw: string): Block[] {\n const lines = raw.split(/\\r?\\n/);\n const blocks: Block[] = [];\n let current: Block | null = null;\n\n const finalize = () => {\n if (current !== null && current.version.length > 0 && current.names.size > 0) {\n blocks.push(current);\n }\n current = null;\n };\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed.length === 0 || trimmed.startsWith('#')) {\n continue;\n }\n\n const indent = countLeadingSpaces(line);\n\n if (indent === 0) {\n finalize();\n if (!trimmed.endsWith(':')) {\n continue;\n }\n // `__metadata:` (yarn berry header) has no `@` in any descriptor and\n // produces an empty names set, so it's naturally skipped on finalize.\n const keyLine = trimmed.slice(0, -1);\n const names = new Set<string>();\n for (const spec of splitDescriptors(keyLine)) {\n const name = extractName(spec);\n if (name !== null) {\n names.add(name);\n }\n }\n current = { names, version: '' };\n continue;\n }\n\n if (current === null) {\n continue;\n }\n\n const version = parseVersionField(trimmed);\n if (version !== null) {\n current.version = version;\n }\n }\n\n finalize();\n return blocks;\n}\n\nfunction countLeadingSpaces(line: string): number {\n let i = 0;\n while (i < line.length && line[i] === ' ') {\n i++;\n }\n return i;\n}\n\n/**\n * Splits a yarn descriptor key list on top-level commas. yarn quotes any\n * descriptor that contains characters needing escaping, so we respect quotes\n * while splitting to avoid breaking on commas inside (rare in practice but\n * cheap to handle).\n */\nexport function splitDescriptors(keyLine: string): string[] {\n const parts: string[] = [];\n let current = '';\n let quote: '\"' | \"'\" | null = null;\n\n for (let i = 0; i < keyLine.length; i++) {\n const c = keyLine[i];\n if (quote !== null) {\n current += c;\n if (c === quote) {\n quote = null;\n }\n continue;\n }\n if (c === '\"' || c === \"'\") {\n quote = c;\n current += c;\n continue;\n }\n if (c === ',') {\n const piece = current.trim();\n if (piece.length > 0) {\n parts.push(piece);\n }\n current = '';\n continue;\n }\n current += c;\n }\n const tail = current.trim();\n if (tail.length > 0) {\n parts.push(tail);\n }\n return parts;\n}\n\n/**\n * Extracts the package name from a yarn descriptor like `axios@^1.6.0`,\n * `\"@scope/pkg@^2.1.0\"`, or `\"@scope/pkg@npm:2.1.0\"`. The descriptor's\n * range portion is discarded — we only need the name, since the resolved\n * version comes from the `version` field of the block.\n */\nexport function extractName(rawSpec: string): string | null {\n let s = rawSpec.trim();\n if (s.length === 0) {\n return null;\n }\n if (\n (s.startsWith('\"') && s.endsWith('\"')) ||\n (s.startsWith(\"'\") && s.endsWith(\"'\"))\n ) {\n s = s.slice(1, -1);\n }\n // Position-0 `@` belongs to a scope, so we want the last `@` after it.\n const atIdx = s.lastIndexOf('@');\n if (atIdx <= 0) {\n return null;\n }\n const name = s.slice(0, atIdx);\n return name.length > 0 ? name : null;\n}\n\nfunction parseVersionField(content: string): string | null {\n if (!content.startsWith('version')) {\n return null;\n }\n const after = content.slice('version'.length);\n // yarn v1: `version \"1.2.3\"` (whitespace then quoted)\n // yarn berry: `version: 1.2.3` or `version: \"1.2.3\"`\n const firstChar = after.charAt(0);\n if (firstChar !== ' ' && firstChar !== '\\t' && firstChar !== ':') {\n return null;\n }\n let rest = firstChar === ':' ? after.slice(1) : after;\n rest = rest.trim();\n if (rest.length === 0) {\n return null;\n }\n if (\n (rest.startsWith('\"') && rest.endsWith('\"')) ||\n (rest.startsWith(\"'\") && rest.endsWith(\"'\"))\n ) {\n rest = rest.slice(1, -1);\n }\n return rest.length > 0 ? rest : null;\n}\n\nasync function readDirectDepNames(cwd: string): Promise<Set<string>> {\n const names = new Set<string>();\n let raw: string;\n try {\n raw = await readFile(path.join(cwd, 'package.json'), 'utf8');\n } catch {\n return names;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return names;\n }\n\n if (typeof parsed !== 'object' || parsed === null) {\n return names;\n }\n const obj = parsed as Record<string, unknown>;\n\n for (const field of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) {\n const section = obj[field];\n if (typeof section !== 'object' || section === null) {\n continue;\n }\n for (const name of Object.keys(section)) {\n names.add(name);\n }\n }\n\n return names;\n}\n","import type { Manifest, PackageEntry } from './types.js';\n\nexport interface WirePackage {\n name: string;\n version: string;\n}\n\nexport interface WirePayload {\n ecosystem: Manifest['ecosystem'];\n packages: WirePackage[];\n}\n\nexport interface NormalizeStats {\n uniqueNames: number;\n duplicateNames: string[];\n totalEntries: number;\n}\n\nexport interface NormalizeResult {\n payload: WirePayload;\n stats: NormalizeStats;\n}\n\nexport function buildWirePayload(manifest: Manifest): NormalizeResult {\n const seen = new Map<string, Set<string>>();\n const wirePackages: WirePackage[] = [];\n\n for (const entry of manifest.packages) {\n const versions = seen.get(entry.name);\n if (versions) {\n if (versions.has(entry.version)) {\n continue;\n }\n versions.add(entry.version);\n } else {\n seen.set(entry.name, new Set([entry.version]));\n }\n wirePackages.push({ name: entry.name, version: entry.version });\n }\n\n wirePackages.sort((a, b) => {\n if (a.name === b.name) {\n return compareVersions(a.version, b.version);\n }\n return a.name < b.name ? -1 : 1;\n });\n\n const duplicateNames: string[] = [];\n for (const [name, versions] of seen) {\n if (versions.size > 1) {\n duplicateNames.push(name);\n }\n }\n\n return {\n payload: { ecosystem: manifest.ecosystem, packages: wirePackages },\n stats: {\n uniqueNames: seen.size,\n duplicateNames,\n totalEntries: manifest.packages.length,\n },\n };\n}\n\nexport function compareVersions(a: string, b: string): number {\n if (a === b) {\n return 0;\n }\n\n const [aBase, aPre] = splitPrerelease(a);\n const [bBase, bPre] = splitPrerelease(b);\n\n const baseCmp = compareSegments(aBase.split('.'), bBase.split('.'));\n if (baseCmp !== 0) {\n return baseCmp;\n }\n\n if (aPre === null && bPre === null) {\n return 0;\n }\n if (aPre === null) {\n return 1;\n }\n if (bPre === null) {\n return -1;\n }\n return compareSegments(aPre.split('.'), bPre.split('.'));\n}\n\nfunction splitPrerelease(version: string): [string, string | null] {\n const cleaned = version.replace(/^[v=]+/, '').split('+')[0]!;\n const dashIndex = cleaned.indexOf('-');\n if (dashIndex === -1) {\n return [cleaned, null];\n }\n return [cleaned.slice(0, dashIndex), cleaned.slice(dashIndex + 1)];\n}\n\nfunction compareSegments(a: string[], b: string[]): number {\n const max = Math.max(a.length, b.length);\n for (let i = 0; i < max; i++) {\n const aPart = a[i];\n const bPart = b[i];\n if (aPart === undefined) {\n return -1;\n }\n if (bPart === undefined) {\n return 1;\n }\n const aNum = /^\\d+$/.test(aPart);\n const bNum = /^\\d+$/.test(bPart);\n if (aNum && bNum) {\n const diff = Number(aPart) - Number(bPart);\n if (diff !== 0) {\n return diff < 0 ? -1 : 1;\n }\n continue;\n }\n if (aNum) {\n return -1;\n }\n if (bNum) {\n return 1;\n }\n if (aPart < bPart) {\n return -1;\n }\n if (aPart > bPart) {\n return 1;\n }\n }\n return 0;\n}\n\nexport function findPackageInManifest(\n manifest: Manifest,\n name: string,\n): PackageEntry[] {\n return manifest.packages.filter((p) => p.name === name);\n}\n","import { PatchstackError, type Config, type StoreManifestResponse } from './types.js';\nimport type { WirePayload } from './normalize.js';\n\nexport const DEFAULT_ENDPOINT = 'https://api.patchstack.com/monitor/pulse/manifest';\nexport const DEFAULT_TIMEOUT_MS = 30_000;\n\nexport function buildEndpointUrl(base: string, siteUuid?: string | null): string {\n const trimmed = base.replace(/\\/$/, '');\n return siteUuid !== undefined && siteUuid !== null && siteUuid.length > 0\n ? `${trimmed}/${encodeURIComponent(siteUuid)}`\n : trimmed;\n}\n\n/**\n * Build the claim URL for a site. The claim page lives on the same origin as\n * the API endpoint, at `/monitor/claim?site=<uuid>`. Using the API endpoint's\n * origin (rather than a hard-coded https://api.patchstack.com) means staging,\n * ngrok tunnels and local dev environments all produce a claim URL on the same\n * host the connector is already talking to.\n */\nexport function buildClaimUrl(endpoint: string, siteUuid: string): string {\n const origin = new URL(endpoint).origin;\n return `${origin}/monitor/claim?site=${encodeURIComponent(siteUuid)}`;\n}\n\nexport async function postManifest(\n config: Config,\n payload: WirePayload,\n): Promise<StoreManifestResponse> {\n const url = buildEndpointUrl(config.endpoint, config.siteUuid);\n const timeoutMs = config.timeoutMs;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'User-Agent': '@patchstack/connect',\n },\n body: JSON.stringify({ ...payload, environment: config.environment }),\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (cause) {\n if (isTimeoutError(cause)) {\n throw new PatchstackError(\n `Patchstack request to ${url} timed out after ${timeoutMs}ms. Override with PATCHSTACK_TIMEOUT_MS.`,\n 'NETWORK_TIMEOUT',\n cause,\n );\n }\n throw new PatchstackError(\n `Could not reach Patchstack at ${url}. Check your network connection.`,\n 'NETWORK_ERROR',\n cause,\n );\n }\n\n const text = await response.text();\n let body: StoreManifestResponse | null = null;\n try {\n body = text.length > 0 ? (JSON.parse(text) as StoreManifestResponse) : null;\n } catch {\n body = null;\n }\n\n if (response.status === 404) {\n throw new PatchstackError(\n body?.error ?? 'Site not found. Check that your site UUID is correct and that the app is registered as a Pulse app in your Patchstack dashboard.',\n 'SITE_NOT_FOUND',\n );\n }\n\n if (response.status === 422) {\n throw new PatchstackError(\n body?.message ?? 'Patchstack rejected the manifest payload (validation failed).',\n 'VALIDATION_ERROR',\n );\n }\n\n if (response.status < 200 || response.status >= 300) {\n throw new PatchstackError(\n `Patchstack returned ${response.status}: ${text.slice(0, 200)}`,\n 'SERVER_ERROR',\n );\n }\n\n if (body === null) {\n throw new PatchstackError('Patchstack returned an empty response.', 'SERVER_ERROR');\n }\n\n return body;\n}\n\nfunction isTimeoutError(cause: unknown): boolean {\n if (cause instanceof Error) {\n return cause.name === 'TimeoutError' || cause.name === 'AbortError';\n }\n return false;\n}\n","import { readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Config, type Environment } from './types.js';\nimport { DEFAULT_ENDPOINT, DEFAULT_TIMEOUT_MS } from './client.js';\n\nconst CONFIG_FILENAME = '.patchstackrc.json';\n\nexport const DEFAULT_ENVIRONMENT: Environment = 'production';\n\ninterface ConfigFile {\n siteUuid?: string;\n endpoint?: string;\n timeoutMs?: number;\n environment?: string;\n}\n\nexport interface ResolveConfigOptions {\n cwd: string;\n cliSiteUuid?: string;\n cliEndpoint?: string;\n /**\n * When true, resolveConfig throws CONFIG_MISSING if no site UUID is configured.\n * Defaults to false: callers that can run without a UUID (the first `scan` after\n * `npm install`) just get `siteUuid: null` back and learn the UUID from the\n * server response.\n */\n requireSiteUuid?: boolean;\n}\n\nexport async function resolveConfig(options: ResolveConfigOptions): Promise<Config> {\n const fromFile = await readConfigFile(options.cwd);\n const fromEnv = readEnv();\n\n const siteUuid =\n options.cliSiteUuid ??\n fromEnv.siteUuid ??\n fromFile.siteUuid ??\n null;\n\n const endpoint =\n options.cliEndpoint ??\n fromEnv.endpoint ??\n fromFile.endpoint ??\n DEFAULT_ENDPOINT;\n\n const timeoutMs = fromEnv.timeoutMs ?? fromFile.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const environmentRaw = fromEnv.environment ?? fromFile.environment;\n if (environmentRaw !== undefined && !isEnvironment(environmentRaw)) {\n throw new PatchstackError(\n `Environment must be \"production\" or \"sandbox\"; got \"${environmentRaw}\".`,\n 'CONFIG_INVALID',\n );\n }\n const environment: Environment = environmentRaw ?? DEFAULT_ENVIRONMENT;\n\n if (siteUuid !== null && siteUuid.length > 0 && !isUuid(siteUuid)) {\n throw new PatchstackError(\n `Site UUID \"${siteUuid}\" does not look like a valid UUID.`,\n 'CONFIG_INVALID',\n );\n }\n\n if (options.requireSiteUuid && (siteUuid === null || siteUuid.length === 0)) {\n throw new PatchstackError(\n 'No site UUID configured. Run `patchstack-connect scan` to provision one, or set PATCHSTACK_SITE_UUID.',\n 'CONFIG_MISSING',\n );\n }\n\n return {\n siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid,\n endpoint,\n timeoutMs,\n environment,\n };\n}\n\nexport async function writeConfigFile(cwd: string, config: ConfigFile): Promise<string> {\n const target = path.join(cwd, CONFIG_FILENAME);\n const content = JSON.stringify(config, null, 2) + '\\n';\n await writeFile(target, content, 'utf8');\n return target;\n}\n\n/**\n * Merge a new siteUuid into the existing `.patchstackrc.json` (or create it).\n * Preserves any `endpoint` / `timeoutMs` the user already wrote.\n */\nexport async function persistSiteUuid(cwd: string, siteUuid: string): Promise<string> {\n const existing = await readConfigFile(cwd);\n return writeConfigFile(cwd, { ...existing, siteUuid });\n}\n\nasync function readConfigFile(cwd: string): Promise<ConfigFile> {\n const target = path.join(cwd, CONFIG_FILENAME);\n let raw: string;\n try {\n raw = await readFile(target, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return {};\n }\n throw new PatchstackError(\n `Could not read ${target}: ${(err as Error).message}`,\n 'CONFIG_INVALID',\n err,\n );\n }\n\n try {\n return JSON.parse(raw) as ConfigFile;\n } catch (err) {\n throw new PatchstackError(\n `Config file ${target} contains invalid JSON.`,\n 'CONFIG_INVALID',\n err,\n );\n }\n}\n\nfunction readEnv(): ConfigFile {\n const timeoutRaw = process.env.PATCHSTACK_TIMEOUT_MS;\n let timeoutMs: number | undefined;\n if (timeoutRaw !== undefined && timeoutRaw.length > 0) {\n const parsed = Number(timeoutRaw);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n throw new PatchstackError(\n `PATCHSTACK_TIMEOUT_MS must be a positive number; got \"${timeoutRaw}\".`,\n 'CONFIG_INVALID',\n );\n }\n timeoutMs = parsed;\n }\n const environmentRaw = process.env.PATCHSTACK_ENVIRONMENT;\n return {\n siteUuid: process.env.PATCHSTACK_SITE_UUID ?? undefined,\n endpoint: process.env.PATCHSTACK_ENDPOINT ?? undefined,\n timeoutMs,\n environment:\n environmentRaw !== undefined && environmentRaw.length > 0 ? environmentRaw : undefined,\n };\n}\n\nfunction isUuid(value: string): boolean {\n return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);\n}\n\nfunction isEnvironment(value: string): value is Environment {\n return value === 'production' || value === 'sandbox';\n}\n"],"mappings":"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","names":["import_promises","import_node_path","path","import_promises","import_node_path","path","import_promises","import_node_path","path","import_promises","import_promises","import_node_path","path","path","import_promises","import_node_path","path"]}
package/dist/index.d.cts CHANGED
@@ -15,6 +15,12 @@ interface PackageEntry {
15
15
  interface Manifest {
16
16
  ecosystem: Ecosystem;
17
17
  packages: PackageEntry[];
18
+ /**
19
+ * Human-readable scan diagnostics — set when the preferred lockfile looked
20
+ * stale (missing dependencies declared in package.json) and another source
21
+ * was used, or when no fully-consistent source existed. Never fatal.
22
+ */
23
+ warnings?: string[];
18
24
  }
19
25
  interface Config {
20
26
  /**
package/dist/index.d.ts CHANGED
@@ -15,6 +15,12 @@ interface PackageEntry {
15
15
  interface Manifest {
16
16
  ecosystem: Ecosystem;
17
17
  packages: PackageEntry[];
18
+ /**
19
+ * Human-readable scan diagnostics — set when the preferred lockfile looked
20
+ * stale (missing dependencies declared in package.json) and another source
21
+ * was used, or when no fully-consistent source existed. Never fatal.
22
+ */
23
+ warnings?: string[];
18
24
  }
19
25
  interface Config {
20
26
  /**
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/parsers/index.ts
2
2
  import { access } from "fs/promises";
3
- import path4 from "path";
3
+ import path5 from "path";
4
4
 
5
5
  // src/types.ts
6
6
  var PatchstackError = class extends Error {
@@ -14,13 +14,52 @@ var PatchstackError = class extends Error {
14
14
  cause;
15
15
  };
16
16
 
17
- // src/parsers/npm.ts
17
+ // src/parsers/consistency.ts
18
18
  import { readFile } from "fs/promises";
19
19
  import path from "path";
20
+ async function readDeclaredDependencyNames(cwd) {
21
+ let raw;
22
+ try {
23
+ raw = await readFile(path.join(cwd, "package.json"), "utf8");
24
+ } catch {
25
+ return [];
26
+ }
27
+ let parsed;
28
+ try {
29
+ parsed = JSON.parse(raw);
30
+ } catch {
31
+ return [];
32
+ }
33
+ if (typeof parsed !== "object" || parsed === null) {
34
+ return [];
35
+ }
36
+ const names = /* @__PURE__ */ new Set();
37
+ for (const group of ["dependencies", "devDependencies"]) {
38
+ const deps = parsed[group];
39
+ if (typeof deps !== "object" || deps === null) {
40
+ continue;
41
+ }
42
+ for (const [name, spec] of Object.entries(deps)) {
43
+ if (typeof spec === "string" && /^(file|link|workspace|portal):/.test(spec)) {
44
+ continue;
45
+ }
46
+ names.add(name);
47
+ }
48
+ }
49
+ return [...names];
50
+ }
51
+ function missingDependencies(declared, packages) {
52
+ const present = new Set(packages.map((entry) => entry.name));
53
+ return declared.filter((name) => !present.has(name));
54
+ }
55
+
56
+ // src/parsers/npm.ts
57
+ import { readFile as readFile2 } from "fs/promises";
58
+ import path2 from "path";
20
59
  async function parseNpmLockfile(lockfilePath) {
21
60
  let raw;
22
61
  try {
23
- raw = await readFile(lockfilePath, "utf8");
62
+ raw = await readFile2(lockfilePath, "utf8");
24
63
  } catch (cause) {
25
64
  throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, "LOCKFILE_NOT_FOUND", cause);
26
65
  }
@@ -78,7 +117,7 @@ function extractFromV1(deps, acc = [], depth = 0) {
78
117
  return acc;
79
118
  }
80
119
  function extractNameFromPath(pkgPath) {
81
- const segments = pkgPath.split("node_modules" + path.sep === pkgPath ? path.sep : "/");
120
+ const segments = pkgPath.split("node_modules" + path2.sep === pkgPath ? path2.sep : "/");
82
121
  const parts = pkgPath.split("/");
83
122
  const nmIndex = parts.lastIndexOf("node_modules");
84
123
  if (nmIndex === -1 || nmIndex >= parts.length - 1) {
@@ -101,10 +140,10 @@ function isDirectV2(pkgPath) {
101
140
  }
102
141
 
103
142
  // src/parsers/node_modules.ts
104
- import { lstat, readFile as readFile2, readdir, stat } from "fs/promises";
105
- import path2 from "path";
143
+ import { lstat, readFile as readFile3, readdir, stat } from "fs/promises";
144
+ import path3 from "path";
106
145
  async function walkNodeModules(cwd) {
107
- const root = path2.join(cwd, "node_modules");
146
+ const root = path3.join(cwd, "node_modules");
108
147
  try {
109
148
  const info = await stat(root);
110
149
  if (!info.isDirectory()) {
@@ -138,7 +177,7 @@ async function walk(dir, acc, depth) {
138
177
  if (name.startsWith(".")) {
139
178
  continue;
140
179
  }
141
- const fullPath = path2.join(dir, name);
180
+ const fullPath = path3.join(dir, name);
142
181
  if (!await isPlainDirectory(fullPath)) {
143
182
  continue;
144
183
  }
@@ -153,7 +192,7 @@ async function walk(dir, acc, depth) {
153
192
  if (sub.startsWith(".")) {
154
193
  continue;
155
194
  }
156
- const scopedDir = path2.join(fullPath, sub);
195
+ const scopedDir = path3.join(fullPath, sub);
157
196
  if (!await isPlainDirectory(scopedDir)) {
158
197
  continue;
159
198
  }
@@ -169,7 +208,7 @@ async function walk(dir, acc, depth) {
169
208
  async function readPackage(pkgDir, depth, acc) {
170
209
  let raw;
171
210
  try {
172
- raw = await readFile2(path2.join(pkgDir, "package.json"), "utf8");
211
+ raw = await readFile3(path3.join(pkgDir, "package.json"), "utf8");
173
212
  } catch {
174
213
  return;
175
214
  }
@@ -192,7 +231,7 @@ async function readPackage(pkgDir, depth, acc) {
192
231
  });
193
232
  }
194
233
  async function walkNested(pkgDir, acc, depth) {
195
- const nested = path2.join(pkgDir, "node_modules");
234
+ const nested = path3.join(pkgDir, "node_modules");
196
235
  if (!await isPlainDirectory(nested)) {
197
236
  return;
198
237
  }
@@ -208,11 +247,11 @@ async function isPlainDirectory(dir) {
208
247
  }
209
248
 
210
249
  // src/parsers/pnpm.ts
211
- import { readFile as readFile3 } from "fs/promises";
250
+ import { readFile as readFile4 } from "fs/promises";
212
251
  async function parsePnpmLockfile(lockfilePath) {
213
252
  let raw;
214
253
  try {
215
- raw = await readFile3(lockfilePath, "utf8");
254
+ raw = await readFile4(lockfilePath, "utf8");
216
255
  } catch (cause) {
217
256
  throw new PatchstackError(
218
257
  `Could not read lockfile at ${lockfilePath}`,
@@ -460,12 +499,12 @@ function extractLeafName(trimmed) {
460
499
  }
461
500
 
462
501
  // src/parsers/yarn.ts
463
- import { readFile as readFile4 } from "fs/promises";
464
- import path3 from "path";
502
+ import { readFile as readFile5 } from "fs/promises";
503
+ import path4 from "path";
465
504
  async function parseYarnLockfile(lockfilePath) {
466
505
  let raw;
467
506
  try {
468
- raw = await readFile4(lockfilePath, "utf8");
507
+ raw = await readFile5(lockfilePath, "utf8");
469
508
  } catch (cause) {
470
509
  throw new PatchstackError(
471
510
  `Could not read lockfile at ${lockfilePath}`,
@@ -480,7 +519,7 @@ async function parseYarnLockfile(lockfilePath) {
480
519
  "LOCKFILE_PARSE_ERROR"
481
520
  );
482
521
  }
483
- const directNames = await readDirectDepNames(path3.dirname(lockfilePath));
522
+ const directNames = await readDirectDepNames(path4.dirname(lockfilePath));
484
523
  const entries = [];
485
524
  const seen = /* @__PURE__ */ new Set();
486
525
  for (const block of blocks) {
@@ -624,7 +663,7 @@ async function readDirectDepNames(cwd) {
624
663
  const names = /* @__PURE__ */ new Set();
625
664
  let raw;
626
665
  try {
627
- raw = await readFile4(path3.join(cwd, "package.json"), "utf8");
666
+ raw = await readFile5(path4.join(cwd, "package.json"), "utf8");
628
667
  } catch {
629
668
  return names;
630
669
  }
@@ -661,7 +700,7 @@ var LOCKFILE_CANDIDATES = [
661
700
  async function detectLockfile(cwd) {
662
701
  const probed = await Promise.all(
663
702
  LOCKFILE_CANDIDATES.map(async (candidate) => {
664
- const filePath = path4.join(cwd, candidate.filename);
703
+ const filePath = path5.join(cwd, candidate.filename);
665
704
  return { ...candidate, filePath, present: await exists(filePath) };
666
705
  })
667
706
  );
@@ -681,9 +720,69 @@ async function detectLockfile(cwd) {
681
720
  );
682
721
  }
683
722
  async function scanLockfile(cwd) {
684
- const detected = await detectLockfile(cwd);
685
- const packages = await runStrategy(detected, cwd);
686
- return { ecosystem: detected.ecosystem, packages };
723
+ const candidates = await presentLockfiles(cwd);
724
+ if (candidates.length === 0) {
725
+ await detectLockfile(cwd);
726
+ }
727
+ const declared = await readDeclaredDependencyNames(cwd);
728
+ const warnings = [];
729
+ let firstParsed = null;
730
+ let walkTried = false;
731
+ for (const candidate of candidates) {
732
+ let packages;
733
+ try {
734
+ packages = await runStrategy(candidate, cwd);
735
+ } catch {
736
+ continue;
737
+ }
738
+ walkTried ||= candidate.strategy === "node-modules-walk";
739
+ firstParsed ??= { packages, filename: candidate.filename };
740
+ const missing = missingDependencies(declared, packages);
741
+ if (missing.length === 0) {
742
+ return manifestWith(packages, warnings);
743
+ }
744
+ warnings.push(staleWarning(candidate.filename, missing));
745
+ }
746
+ if (!walkTried) {
747
+ try {
748
+ const packages = await walkNodeModules(cwd);
749
+ const missing = missingDependencies(declared, packages);
750
+ if (missing.length > 0) {
751
+ warnings.push(staleWarning("node_modules/", missing));
752
+ }
753
+ warnings.push("Scanned node_modules/ instead. Delete the stale lockfile to silence this warning.");
754
+ return manifestWith(packages, warnings);
755
+ } catch {
756
+ }
757
+ }
758
+ if (firstParsed === null) {
759
+ const expected = LOCKFILE_CANDIDATES.map((candidate) => candidate.filename).join(", ");
760
+ throw new PatchstackError(
761
+ `No readable lockfile found in ${cwd}. Expected one of: ${expected}.`,
762
+ "LOCKFILE_NOT_FOUND"
763
+ );
764
+ }
765
+ warnings.push(
766
+ `No fully-consistent source found; reporting ${firstParsed.filename}. The manifest may understate the real dependency set.`
767
+ );
768
+ return manifestWith(firstParsed.packages, warnings);
769
+ }
770
+ function manifestWith(packages, warnings) {
771
+ return warnings.length > 0 ? { ecosystem: "npm", packages, warnings } : { ecosystem: "npm", packages };
772
+ }
773
+ function staleWarning(source, missing) {
774
+ const sample = missing.slice(0, 3).join(", ");
775
+ const suffix = missing.length > 3 ? `, +${missing.length - 3} more` : "";
776
+ return `${source} looks stale: package.json declares ${missing.length} dependenc${missing.length === 1 ? "y" : "ies"} it does not contain (${sample}${suffix}).`;
777
+ }
778
+ async function presentLockfiles(cwd) {
779
+ const probed = await Promise.all(
780
+ LOCKFILE_CANDIDATES.map(async (candidate) => {
781
+ const filePath = path5.join(cwd, candidate.filename);
782
+ return { ...candidate, filePath, present: await exists(filePath) };
783
+ })
784
+ );
785
+ return probed.filter((candidate) => candidate.present).map(({ filename, strategy, filePath }) => ({ ecosystem: "npm", filename, strategy, filePath }));
687
786
  }
688
787
  async function runStrategy(detected, cwd) {
689
788
  switch (detected.strategy) {
@@ -886,8 +985,8 @@ function isTimeoutError(cause) {
886
985
  }
887
986
 
888
987
  // src/config.ts
889
- import { readFile as readFile5, writeFile } from "fs/promises";
890
- import path5 from "path";
988
+ import { readFile as readFile6, writeFile } from "fs/promises";
989
+ import path6 from "path";
891
990
  var CONFIG_FILENAME = ".patchstackrc.json";
892
991
  var DEFAULT_ENVIRONMENT = "production";
893
992
  async function resolveConfig(options) {
@@ -924,7 +1023,7 @@ async function resolveConfig(options) {
924
1023
  };
925
1024
  }
926
1025
  async function writeConfigFile(cwd, config) {
927
- const target = path5.join(cwd, CONFIG_FILENAME);
1026
+ const target = path6.join(cwd, CONFIG_FILENAME);
928
1027
  const content = JSON.stringify(config, null, 2) + "\n";
929
1028
  await writeFile(target, content, "utf8");
930
1029
  return target;
@@ -934,10 +1033,10 @@ async function persistSiteUuid(cwd, siteUuid) {
934
1033
  return writeConfigFile(cwd, { ...existing, siteUuid });
935
1034
  }
936
1035
  async function readConfigFile(cwd) {
937
- const target = path5.join(cwd, CONFIG_FILENAME);
1036
+ const target = path6.join(cwd, CONFIG_FILENAME);
938
1037
  let raw;
939
1038
  try {
940
- raw = await readFile5(target, "utf8");
1039
+ raw = await readFile6(target, "utf8");
941
1040
  } catch (err) {
942
1041
  if (err.code === "ENOENT") {
943
1042
  return {};