diff --git a/README.md b/README.md index 52ff071..9c094ce 100644 --- a/README.md +++ b/README.md @@ -114,10 +114,10 @@ That's the entire payload. No source code, no environment variables, no file pat ## Supported lockfiles - ✅ `package-lock.json` (npm v6 / v2 / v3) — parsed directly +- ✅ `pnpm-lock.yaml` (pnpm v5 / v6 / v7 / v8 / v9) — parsed directly - ✅ `bun.lockb` (binary) — package list resolved by walking `node_modules/` - ✅ `bun.lock` (text) — same fallback; direct parsing coming - ❌ `yarn.lock` — coming soon -- ❌ `pnpm-lock.yaml` — coming soon If both a Bun lockfile and `node_modules/` are present, the connector walks `node_modules/` to enumerate the installed packages. Run `bun install` (or `npm install`) before scanning so the directory is populated. diff --git a/dist/cli.js b/dist/cli.js index 38f5fc0..0342fa5 100755 --- a/dist/cli.js +++ b/dist/cli.js @@ -209,6 +209,258 @@ async function isPlainDirectory(dir) { } } +// src/parsers/pnpm.ts +import { readFile as readFile3 } from "fs/promises"; +async function parsePnpmLockfile(lockfilePath) { + let raw; + try { + raw = await readFile3(lockfilePath, "utf8"); + } catch (cause) { + throw new PatchstackError( + `Could not read lockfile at ${lockfilePath}`, + "LOCKFILE_NOT_FOUND", + cause + ); + } + const lines = raw.split(/\r?\n/); + const directNames = collectDirectDepNames(lines); + const packageKeys = collectPackagesBlockKeys(lines); + if (packageKeys.length === 0) { + throw new PatchstackError( + `Lockfile at ${lockfilePath} has no "packages" entries`, + "LOCKFILE_PARSE_ERROR" + ); + } + const entries = []; + for (const key of packageKeys) { + const parsed = parsePackageKey(key); + if (parsed === null) { + continue; + } + entries.push({ + name: parsed.name, + version: parsed.version, + direct: directNames.has(parsed.name) + }); + } + return entries; +} +function parsePackageKey(rawKey) { + let k = rawKey.trim(); + if (k.length === 0) { + return null; + } + if (k.startsWith("'") && k.endsWith("'") || k.startsWith('"') && k.endsWith('"')) { + k = k.slice(1, -1); + } + if (k.startsWith("/")) { + k = k.slice(1); + } + const parenIdx = k.indexOf("("); + if (parenIdx >= 0) { + k = k.slice(0, parenIdx); + } + let scopePrefix = ""; + let body = k; + if (k.startsWith("@")) { + const firstSlash = k.indexOf("/"); + if (firstSlash <= 0) { + return null; + } + scopePrefix = k.slice(0, firstSlash + 1); + body = k.slice(firstSlash + 1); + } + const slashIdx = body.indexOf("/"); + const atIdx = body.indexOf("@"); + let sepIdx; + if (slashIdx < 0 && atIdx < 0) { + return null; + } else if (slashIdx < 0) { + sepIdx = atIdx; + } else if (atIdx < 0) { + sepIdx = slashIdx; + } else { + sepIdx = Math.min(slashIdx, atIdx); + } + const name = scopePrefix + body.slice(0, sepIdx); + let version = body.slice(sepIdx + 1); + const underscoreIdx = version.indexOf("_"); + if (underscoreIdx >= 0) { + version = version.slice(0, underscoreIdx); + } + if (name.length === 0 || version.length === 0) { + return null; + } + return { name, version }; +} +function indentOf(line) { + let i = 0; + while (i < line.length && line[i] === " ") { + i++; + } + return i; +} +function isBlankOrComment(line) { + const trimmed = line.trim(); + return trimmed.length === 0 || trimmed.startsWith("#"); +} +function collectPackagesBlockKeys(lines) { + const keys = []; + let inBlock = false; + let childIndent = null; + for (const line of lines) { + if (isBlankOrComment(line)) { + continue; + } + const indent = indentOf(line); + if (!inBlock) { + if (indent === 0 && line.trim() === "packages:") { + inBlock = true; + } + continue; + } + if (indent === 0) { + break; + } + if (childIndent === null) { + childIndent = indent; + } + if (indent !== childIndent) { + continue; + } + const content = line.slice(indent); + if (!content.endsWith(":")) { + continue; + } + keys.push(content.slice(0, -1)); + } + return keys; +} +function collectDirectDepNames(lines) { + const names = /* @__PURE__ */ new Set(); + collectFromImporters(lines, names); + for (const section of ["dependencies", "devDependencies", "optionalDependencies"]) { + collectFromTopLevelSection(lines, section, names); + } + return names; +} +var DEP_SECTIONS = /* @__PURE__ */ new Set([ + "dependencies", + "devDependencies", + "optionalDependencies" +]); +function collectFromImporters(lines, out) { + let inImporters = false; + let importerIndent = null; + let inDepSection = false; + let depSectionIndent = null; + let leafIndent = null; + for (const line of lines) { + if (isBlankOrComment(line)) { + continue; + } + const indent = indentOf(line); + const trimmed = line.trim(); + if (!inImporters) { + if (indent === 0 && trimmed === "importers:") { + inImporters = true; + } + continue; + } + if (indent === 0) { + break; + } + if (importerIndent === null) { + importerIndent = indent; + } + if (indent === importerIndent) { + inDepSection = false; + depSectionIndent = null; + leafIndent = null; + continue; + } + if (!inDepSection) { + const key = stripTrailingColon(trimmed); + if (key !== null && DEP_SECTIONS.has(key)) { + inDepSection = true; + depSectionIndent = indent; + } + continue; + } + if (depSectionIndent !== null && indent <= depSectionIndent) { + inDepSection = false; + depSectionIndent = null; + leafIndent = null; + const key = stripTrailingColon(trimmed); + if (key !== null && DEP_SECTIONS.has(key)) { + inDepSection = true; + depSectionIndent = indent; + } + continue; + } + if (leafIndent === null) { + leafIndent = indent; + } + if (indent !== leafIndent) { + continue; + } + const name = extractLeafName(trimmed); + if (name !== null) { + out.add(name); + } + } +} +function collectFromTopLevelSection(lines, section, out) { + let inSection = false; + let leafIndent = null; + for (const line of lines) { + if (isBlankOrComment(line)) { + continue; + } + const indent = indentOf(line); + const trimmed = line.trim(); + if (!inSection) { + if (indent === 0 && trimmed === `${section}:`) { + inSection = true; + } + continue; + } + if (indent === 0) { + break; + } + if (leafIndent === null) { + leafIndent = indent; + } + if (indent !== leafIndent) { + continue; + } + const name = extractLeafName(trimmed); + if (name !== null) { + out.add(name); + } + } +} +function stripTrailingColon(s) { + if (!s.endsWith(":")) { + return null; + } + return s.slice(0, -1).trim(); +} +function extractLeafName(trimmed) { + const colonIdx = trimmed.indexOf(":"); + if (colonIdx < 0) { + return null; + } + let name = trimmed.slice(0, colonIdx).trim(); + if (name.length === 0) { + return null; + } + if (name.startsWith("'") && name.endsWith("'") || name.startsWith('"') && name.endsWith('"')) { + name = name.slice(1, -1); + } + return name.length > 0 ? name : null; +} + // src/parsers/index.ts async function detectLockfile(cwd) { const npmLock = path3.join(cwd, "package-lock.json"); @@ -238,6 +490,15 @@ async function detectLockfile(cwd) { strategy: "node-modules-walk" }; } + const pnpmLock = path3.join(cwd, "pnpm-lock.yaml"); + if (await exists(pnpmLock)) { + return { + ecosystem: "npm", + filePath: pnpmLock, + filename: "pnpm-lock.yaml", + strategy: "pnpm-lockfile" + }; + } const yarnLock = path3.join(cwd, "yarn.lock"); if (await exists(yarnLock)) { throw new PatchstackError( @@ -245,13 +506,6 @@ async function detectLockfile(cwd) { "LOCKFILE_UNSUPPORTED" ); } - const pnpmLock = path3.join(cwd, "pnpm-lock.yaml"); - if (await exists(pnpmLock)) { - throw new PatchstackError( - "pnpm-lock.yaml detected but not yet supported. Open an issue at github.com/patchstack/connect to request support.", - "LOCKFILE_UNSUPPORTED" - ); - } throw new PatchstackError( `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`, "LOCKFILE_NOT_FOUND" @@ -266,6 +520,8 @@ async function runStrategy(detected, cwd) { switch (detected.strategy) { case "npm-lockfile": return parseNpmLockfile(detected.filePath); + case "pnpm-lockfile": + return parsePnpmLockfile(detected.filePath); case "node-modules-walk": return walkNodeModules(cwd); } @@ -459,7 +715,7 @@ function isTimeoutError(cause) { } // src/config.ts -import { readFile as readFile3, writeFile } from "fs/promises"; +import { readFile as readFile4, writeFile } from "fs/promises"; import path4 from "path"; var CONFIG_FILENAME = ".patchstackrc.json"; async function resolveConfig(options) { @@ -500,7 +756,7 @@ async function readConfigFile(cwd) { const target = path4.join(cwd, CONFIG_FILENAME); let raw; try { - raw = await readFile3(target, "utf8"); + raw = await readFile4(target, "utf8"); } catch (err) { if (err.code === "ENOENT") { return {}; diff --git a/dist/cli.js.map b/dist/cli.js.map index 34d15b8..c332efb 100644 --- a/dist/cli.js.map +++ b/dist/cli.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/parsers/index.ts","../src/types.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/normalize.ts","../src/client.ts","../src/config.ts","../src/cli.ts"],"sourcesContent":["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';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy = 'npm-lockfile' | 'node-modules-walk';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\nexport async function detectLockfile(cwd: string): Promise {\n const npmLock = path.join(cwd, 'package-lock.json');\n if (await exists(npmLock)) {\n return {\n ecosystem: 'npm',\n filePath: npmLock,\n filename: 'package-lock.json',\n strategy: 'npm-lockfile',\n };\n }\n\n const bunLock = path.join(cwd, 'bun.lock');\n if (await exists(bunLock)) {\n return {\n ecosystem: 'npm',\n filePath: bunLock,\n filename: 'bun.lock',\n strategy: 'node-modules-walk',\n };\n }\n\n const bunLockB = path.join(cwd, 'bun.lockb');\n if (await exists(bunLockB)) {\n return {\n ecosystem: 'npm',\n filePath: bunLockB,\n filename: 'bun.lockb',\n strategy: 'node-modules-walk',\n };\n }\n\n const yarnLock = path.join(cwd, 'yarn.lock');\n if (await exists(yarnLock)) {\n throw new PatchstackError(\n 'yarn.lock detected but not yet supported. Run `npm install` to generate a package-lock.json, or open an issue at github.com/patchstack/connect.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n const pnpmLock = path.join(cwd, 'pnpm-lock.yaml');\n if (await exists(pnpmLock)) {\n throw new PatchstackError(\n 'pnpm-lock.yaml detected but not yet supported. Open an issue at github.com/patchstack/connect to request support.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n throw new PatchstackError(\n `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`,\n 'LOCKFILE_NOT_FOUND',\n );\n}\n\nexport async function scanLockfile(cwd: string): Promise {\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 {\n switch (detected.strategy) {\n case 'npm-lockfile':\n return parseNpmLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n","export type Ecosystem = 'npm' | 'composer';\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}\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;\n dependencies?: Record;\n}\n\ninterface LockfileV1Dependency {\n version: string;\n dependencies?: Record;\n}\n\nexport async function parseNpmLockfile(lockfilePath: string): Promise {\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): 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,\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 {\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 {\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 {\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 {\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 {\n try {\n const info = await lstat(dir);\n return info.isDirectory() && !info.isSymbolicLink();\n } catch {\n return false;\n }\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>();\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=`. 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 {\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),\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 } from './types.js';\nimport { DEFAULT_ENDPOINT, DEFAULT_TIMEOUT_MS } from './client.js';\n\nconst CONFIG_FILENAME = '.patchstackrc.json';\n\ninterface ConfigFile {\n siteUuid?: string;\n endpoint?: string;\n timeoutMs?: number;\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 {\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 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 };\n}\n\nexport async function writeConfigFile(cwd: string, config: ConfigFile): Promise {\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 {\n const existing = await readConfigFile(cwd);\n return writeConfigFile(cwd, { ...existing, siteUuid });\n}\n\nasync function readConfigFile(cwd: string): Promise {\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 return {\n siteUuid: process.env.PATCHSTACK_SITE_UUID ?? undefined,\n endpoint: process.env.PATCHSTACK_ENDPOINT ?? undefined,\n timeoutMs,\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","import { scanLockfile } from './parsers/index.js';\nimport { buildWirePayload } from './normalize.js';\nimport { buildClaimUrl, postManifest } from './client.js';\nimport { persistSiteUuid, resolveConfig, writeConfigFile } from './config.js';\nimport { PatchstackError } from './types.js';\n\nconst HELP = `@patchstack/connect — scan your lockfile and report packages to Patchstack.\n\nUsage:\n patchstack-connect scan [options] Scan lockfile and POST to Patchstack.\n If no UUID is configured, the server\n provisions one and we persist it.\n patchstack-connect init Optional: pre-seed .patchstackrc.json\n with an existing site UUID\n patchstack-connect status [options] Show current configuration\n patchstack-connect help Print this message\n\nOptions (for scan and status):\n --site-uuid Override the configured site UUID\n --endpoint Override the API endpoint\n --dry-run (scan only) Show the payload without posting\n\nEnvironment:\n PATCHSTACK_SITE_UUID Site UUID\n PATCHSTACK_ENDPOINT API endpoint (default: https://api.patchstack.com/monitor/pulse/manifest)\n PATCHSTACK_TIMEOUT_MS Request timeout in ms (default: 30000)\n\nPrecedence: CLI flag > environment variable > .patchstackrc.json.\n\nExamples:\n npx @patchstack/connect scan\n npx @patchstack/connect scan --dry-run\n npx @patchstack/connect init 550e8400-e29b-41d4-a716-446655440000\n npx @patchstack/connect scan --site-uuid 550e8400-...-446655440000\n`;\n\nconst VALUE_FLAGS = new Set(['site-uuid', 'endpoint']);\n\ninterface ParsedArgs {\n command: string | null;\n positional: string[];\n flags: Map;\n}\n\nfunction parseArgs(argv: string[]): ParsedArgs {\n const args = argv.slice(2);\n const positional: string[] = [];\n const flags = new Map();\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n if (!arg.startsWith('--')) {\n positional.push(arg);\n continue;\n }\n const stripped = arg.slice(2);\n const eqIdx = stripped.indexOf('=');\n if (eqIdx !== -1) {\n flags.set(stripped.slice(0, eqIdx), stripped.slice(eqIdx + 1));\n continue;\n }\n const next = args[i + 1];\n if (VALUE_FLAGS.has(stripped) && next !== undefined && !next.startsWith('--')) {\n flags.set(stripped, next);\n i++;\n } else {\n flags.set(stripped, true);\n }\n }\n\n return {\n command: positional.shift() ?? null,\n positional,\n flags,\n };\n}\n\nfunction getStringFlag(flags: Map, name: string): string | undefined {\n const value = flags.get(name);\n return typeof value === 'string' ? value : undefined;\n}\n\nasync function runInit(args: ParsedArgs): Promise {\n const uuid = args.positional[0];\n if (!uuid) {\n console.error('Error: site UUID is required.\\n');\n console.error('Usage: patchstack-connect init ');\n return 1;\n }\n if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(uuid)) {\n console.error(`Error: \"${uuid}\" does not look like a valid UUID.`);\n return 1;\n }\n\n const target = await writeConfigFile(process.cwd(), { siteUuid: uuid });\n console.log(`Wrote ${target}`);\n console.log('');\n console.log('Next: run `npx @patchstack/connect scan` to send your first manifest.');\n return 0;\n}\n\nasync function runScan(args: ParsedArgs): Promise {\n const dryRun = args.flags.get('dry-run') === true;\n const config = await resolveConfig({\n cwd: process.cwd(),\n cliSiteUuid: getStringFlag(args.flags, 'site-uuid'),\n cliEndpoint: getStringFlag(args.flags, 'endpoint'),\n });\n const manifest = await scanLockfile(process.cwd());\n const { payload, stats } = buildWirePayload(manifest);\n\n console.log(\n `Found ${payload.packages.length} unique package versions across ${stats.uniqueNames} package names in ${manifest.ecosystem} lockfile.`,\n );\n if (stats.duplicateNames.length > 0) {\n console.log(`${stats.duplicateNames.length} package(s) appear at multiple versions:`);\n if (stats.duplicateNames.length <= 10) {\n console.log(` ${stats.duplicateNames.join(', ')}`);\n }\n }\n\n if (dryRun) {\n console.log('');\n if (config.siteUuid === null) {\n console.log('--dry-run: no site UUID configured. A real run would provision one.');\n } else {\n console.log(`--dry-run: not posting to Patchstack (site UUID ${config.siteUuid}).`);\n }\n console.log('Payload preview:');\n const preview = JSON.stringify(payload, null, 2).split('\\n');\n console.log(preview.slice(0, Math.min(preview.length, 30)).join('\\n'));\n if (preview.length > 30) {\n console.log(` ... (${preview.length - 30} more lines)`);\n }\n return 0;\n }\n\n const provisioning = config.siteUuid === null;\n if (provisioning) {\n console.log('No site UUID configured — provisioning a new Patchstack site from this manifest…');\n }\n\n const response = await postManifest(config, payload);\n\n // The server always returns the UUID. If we didn't have one, persist it so\n // every subsequent scan targets the same site.\n if (provisioning && response.uuid !== undefined && response.uuid.length > 0) {\n const target = await persistSiteUuid(process.cwd(), response.uuid);\n console.log(`Provisioned site ${response.uuid}. Saved UUID to ${target}.`);\n }\n\n if (response.stored) {\n console.log(`Stored manifest #${response.manifest_id} (checksum ${response.checksum}).`);\n } else if (response.reason === 'duplicate') {\n console.log('Manifest unchanged since last scan — nothing to store.');\n } else {\n console.log(`Server response: ${response.message ?? JSON.stringify(response)}`);\n }\n\n // On the first scan (provisioning), surface the claim URL so the user can\n // attach this site to their Patchstack account. `npx @patchstack/connect status`\n // re-displays it any time.\n if (provisioning && response.uuid !== undefined && response.uuid.length > 0) {\n console.log('');\n console.log('Claim this site to view vulnerability reports in your Patchstack dashboard:');\n console.log(` ${buildClaimUrl(config.endpoint, response.uuid)}`);\n }\n\n return 0;\n}\n\nasync function runStatus(args: ParsedArgs): Promise {\n const config = await resolveConfig({\n cwd: process.cwd(),\n cliSiteUuid: getStringFlag(args.flags, 'site-uuid'),\n cliEndpoint: getStringFlag(args.flags, 'endpoint'),\n });\n console.log(`Site UUID: ${config.siteUuid ?? '(none yet — the next `scan` will provision one)'}`);\n console.log(`Endpoint: ${config.endpoint}`);\n console.log(`Timeout: ${config.timeoutMs}ms`);\n if (config.siteUuid !== null) {\n console.log(`Claim URL: ${buildClaimUrl(config.endpoint, config.siteUuid)}`);\n }\n return 0;\n}\n\nasync function main(): Promise {\n const args = parseArgs(process.argv);\n\n if (args.flags.has('help') || args.command === 'help' || args.command === null) {\n console.log(HELP);\n return 0;\n }\n\n switch (args.command) {\n case 'init':\n return runInit(args);\n case 'scan':\n return runScan(args);\n case 'status':\n return runStatus(args);\n default:\n console.error(`Unknown command: ${args.command}\\n`);\n console.error(HELP);\n return 1;\n }\n}\n\nmain()\n .then((code) => process.exit(code))\n .catch((err: unknown) => {\n if (err instanceof PatchstackError) {\n console.error(`Error (${err.code}): ${err.message}`);\n process.exit(1);\n }\n console.error('Unexpected error:', err);\n process.exit(2);\n });\n"],"mappings":"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","names":["path","readFile","path","path","readFile","path","readFile","path","path","readFile"]} \ No newline at end of file +{"version":3,"sources":["../src/parsers/index.ts","../src/types.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/parsers/pnpm.ts","../src/normalize.ts","../src/client.ts","../src/config.ts","../src/cli.ts"],"sourcesContent":["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';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy = 'npm-lockfile' | 'node-modules-walk' | 'pnpm-lockfile';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\nexport async function detectLockfile(cwd: string): Promise {\n const npmLock = path.join(cwd, 'package-lock.json');\n if (await exists(npmLock)) {\n return {\n ecosystem: 'npm',\n filePath: npmLock,\n filename: 'package-lock.json',\n strategy: 'npm-lockfile',\n };\n }\n\n const bunLock = path.join(cwd, 'bun.lock');\n if (await exists(bunLock)) {\n return {\n ecosystem: 'npm',\n filePath: bunLock,\n filename: 'bun.lock',\n strategy: 'node-modules-walk',\n };\n }\n\n const bunLockB = path.join(cwd, 'bun.lockb');\n if (await exists(bunLockB)) {\n return {\n ecosystem: 'npm',\n filePath: bunLockB,\n filename: 'bun.lockb',\n strategy: 'node-modules-walk',\n };\n }\n\n const pnpmLock = path.join(cwd, 'pnpm-lock.yaml');\n if (await exists(pnpmLock)) {\n return {\n ecosystem: 'npm',\n filePath: pnpmLock,\n filename: 'pnpm-lock.yaml',\n strategy: 'pnpm-lockfile',\n };\n }\n\n const yarnLock = path.join(cwd, 'yarn.lock');\n if (await exists(yarnLock)) {\n throw new PatchstackError(\n 'yarn.lock detected but not yet supported. Run `npm install` to generate a package-lock.json, or open an issue at github.com/patchstack/connect.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n throw new PatchstackError(\n `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`,\n 'LOCKFILE_NOT_FOUND',\n );\n}\n\nexport async function scanLockfile(cwd: string): Promise {\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 {\n switch (detected.strategy) {\n case 'npm-lockfile':\n return parseNpmLockfile(detected.filePath);\n case 'pnpm-lockfile':\n return parsePnpmLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n","export type Ecosystem = 'npm' | 'composer';\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}\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;\n dependencies?: Record;\n}\n\ninterface LockfileV1Dependency {\n version: string;\n dependencies?: Record;\n}\n\nexport async function parseNpmLockfile(lockfilePath: string): Promise {\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): 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,\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 {\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 {\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 {\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 {\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 {\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 {\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:` > `:` > `{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 {\n const names = new Set();\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): 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,\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 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>();\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=`. 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 {\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),\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 } from './types.js';\nimport { DEFAULT_ENDPOINT, DEFAULT_TIMEOUT_MS } from './client.js';\n\nconst CONFIG_FILENAME = '.patchstackrc.json';\n\ninterface ConfigFile {\n siteUuid?: string;\n endpoint?: string;\n timeoutMs?: number;\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 {\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 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 };\n}\n\nexport async function writeConfigFile(cwd: string, config: ConfigFile): Promise {\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 {\n const existing = await readConfigFile(cwd);\n return writeConfigFile(cwd, { ...existing, siteUuid });\n}\n\nasync function readConfigFile(cwd: string): Promise {\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 return {\n siteUuid: process.env.PATCHSTACK_SITE_UUID ?? undefined,\n endpoint: process.env.PATCHSTACK_ENDPOINT ?? undefined,\n timeoutMs,\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","import { scanLockfile } from './parsers/index.js';\nimport { buildWirePayload } from './normalize.js';\nimport { buildClaimUrl, postManifest } from './client.js';\nimport { persistSiteUuid, resolveConfig, writeConfigFile } from './config.js';\nimport { PatchstackError } from './types.js';\n\nconst HELP = `@patchstack/connect — scan your lockfile and report packages to Patchstack.\n\nUsage:\n patchstack-connect scan [options] Scan lockfile and POST to Patchstack.\n If no UUID is configured, the server\n provisions one and we persist it.\n patchstack-connect init Optional: pre-seed .patchstackrc.json\n with an existing site UUID\n patchstack-connect status [options] Show current configuration\n patchstack-connect help Print this message\n\nOptions (for scan and status):\n --site-uuid Override the configured site UUID\n --endpoint Override the API endpoint\n --dry-run (scan only) Show the payload without posting\n\nEnvironment:\n PATCHSTACK_SITE_UUID Site UUID\n PATCHSTACK_ENDPOINT API endpoint (default: https://api.patchstack.com/monitor/pulse/manifest)\n PATCHSTACK_TIMEOUT_MS Request timeout in ms (default: 30000)\n\nPrecedence: CLI flag > environment variable > .patchstackrc.json.\n\nExamples:\n npx @patchstack/connect scan\n npx @patchstack/connect scan --dry-run\n npx @patchstack/connect init 550e8400-e29b-41d4-a716-446655440000\n npx @patchstack/connect scan --site-uuid 550e8400-...-446655440000\n`;\n\nconst VALUE_FLAGS = new Set(['site-uuid', 'endpoint']);\n\ninterface ParsedArgs {\n command: string | null;\n positional: string[];\n flags: Map;\n}\n\nfunction parseArgs(argv: string[]): ParsedArgs {\n const args = argv.slice(2);\n const positional: string[] = [];\n const flags = new Map();\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n if (!arg.startsWith('--')) {\n positional.push(arg);\n continue;\n }\n const stripped = arg.slice(2);\n const eqIdx = stripped.indexOf('=');\n if (eqIdx !== -1) {\n flags.set(stripped.slice(0, eqIdx), stripped.slice(eqIdx + 1));\n continue;\n }\n const next = args[i + 1];\n if (VALUE_FLAGS.has(stripped) && next !== undefined && !next.startsWith('--')) {\n flags.set(stripped, next);\n i++;\n } else {\n flags.set(stripped, true);\n }\n }\n\n return {\n command: positional.shift() ?? null,\n positional,\n flags,\n };\n}\n\nfunction getStringFlag(flags: Map, name: string): string | undefined {\n const value = flags.get(name);\n return typeof value === 'string' ? value : undefined;\n}\n\nasync function runInit(args: ParsedArgs): Promise {\n const uuid = args.positional[0];\n if (!uuid) {\n console.error('Error: site UUID is required.\\n');\n console.error('Usage: patchstack-connect init ');\n return 1;\n }\n if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(uuid)) {\n console.error(`Error: \"${uuid}\" does not look like a valid UUID.`);\n return 1;\n }\n\n const target = await writeConfigFile(process.cwd(), { siteUuid: uuid });\n console.log(`Wrote ${target}`);\n console.log('');\n console.log('Next: run `npx @patchstack/connect scan` to send your first manifest.');\n return 0;\n}\n\nasync function runScan(args: ParsedArgs): Promise {\n const dryRun = args.flags.get('dry-run') === true;\n const config = await resolveConfig({\n cwd: process.cwd(),\n cliSiteUuid: getStringFlag(args.flags, 'site-uuid'),\n cliEndpoint: getStringFlag(args.flags, 'endpoint'),\n });\n const manifest = await scanLockfile(process.cwd());\n const { payload, stats } = buildWirePayload(manifest);\n\n console.log(\n `Found ${payload.packages.length} unique package versions across ${stats.uniqueNames} package names in ${manifest.ecosystem} lockfile.`,\n );\n if (stats.duplicateNames.length > 0) {\n console.log(`${stats.duplicateNames.length} package(s) appear at multiple versions:`);\n if (stats.duplicateNames.length <= 10) {\n console.log(` ${stats.duplicateNames.join(', ')}`);\n }\n }\n\n if (dryRun) {\n console.log('');\n if (config.siteUuid === null) {\n console.log('--dry-run: no site UUID configured. A real run would provision one.');\n } else {\n console.log(`--dry-run: not posting to Patchstack (site UUID ${config.siteUuid}).`);\n }\n console.log('Payload preview:');\n const preview = JSON.stringify(payload, null, 2).split('\\n');\n console.log(preview.slice(0, Math.min(preview.length, 30)).join('\\n'));\n if (preview.length > 30) {\n console.log(` ... (${preview.length - 30} more lines)`);\n }\n return 0;\n }\n\n const provisioning = config.siteUuid === null;\n if (provisioning) {\n console.log('No site UUID configured — provisioning a new Patchstack site from this manifest…');\n }\n\n const response = await postManifest(config, payload);\n\n // The server always returns the UUID. If we didn't have one, persist it so\n // every subsequent scan targets the same site.\n if (provisioning && response.uuid !== undefined && response.uuid.length > 0) {\n const target = await persistSiteUuid(process.cwd(), response.uuid);\n console.log(`Provisioned site ${response.uuid}. Saved UUID to ${target}.`);\n }\n\n if (response.stored) {\n console.log(`Stored manifest #${response.manifest_id} (checksum ${response.checksum}).`);\n } else if (response.reason === 'duplicate') {\n console.log('Manifest unchanged since last scan — nothing to store.');\n } else {\n console.log(`Server response: ${response.message ?? JSON.stringify(response)}`);\n }\n\n // On the first scan (provisioning), surface the claim URL so the user can\n // attach this site to their Patchstack account. `npx @patchstack/connect status`\n // re-displays it any time.\n if (provisioning && response.uuid !== undefined && response.uuid.length > 0) {\n console.log('');\n console.log('Claim this site to view vulnerability reports in your Patchstack dashboard:');\n console.log(` ${buildClaimUrl(config.endpoint, response.uuid)}`);\n }\n\n return 0;\n}\n\nasync function runStatus(args: ParsedArgs): Promise {\n const config = await resolveConfig({\n cwd: process.cwd(),\n cliSiteUuid: getStringFlag(args.flags, 'site-uuid'),\n cliEndpoint: getStringFlag(args.flags, 'endpoint'),\n });\n console.log(`Site UUID: ${config.siteUuid ?? '(none yet — the next `scan` will provision one)'}`);\n console.log(`Endpoint: ${config.endpoint}`);\n console.log(`Timeout: ${config.timeoutMs}ms`);\n if (config.siteUuid !== null) {\n console.log(`Claim URL: ${buildClaimUrl(config.endpoint, config.siteUuid)}`);\n }\n return 0;\n}\n\nasync function main(): Promise {\n const args = parseArgs(process.argv);\n\n if (args.flags.has('help') || args.command === 'help' || args.command === null) {\n console.log(HELP);\n return 0;\n }\n\n switch (args.command) {\n case 'init':\n return runInit(args);\n case 'scan':\n return runScan(args);\n case 'status':\n return runStatus(args);\n default:\n console.error(`Unknown command: ${args.command}\\n`);\n console.error(HELP);\n return 1;\n }\n}\n\nmain()\n .then((code) => process.exit(code))\n .catch((err: unknown) => {\n if (err instanceof PatchstackError) {\n console.error(`Error (${err.code}): ${err.message}`);\n process.exit(1);\n }\n console.error('Unexpected error:', err);\n process.exit(2);\n });\n"],"mappings":"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","names":["path","readFile","path","path","readFile","readFile","readFile","path","readFile","path","path","readFile"]} \ No newline at end of file diff --git a/dist/index.cjs b/dist/index.cjs index cf6e14a..201ef3c 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -47,7 +47,7 @@ __export(src_exports, { module.exports = __toCommonJS(src_exports); // src/parsers/index.ts -var import_promises3 = require("fs/promises"); +var import_promises4 = require("fs/promises"); var import_node_path3 = __toESM(require("path"), 1); // src/types.ts @@ -255,6 +255,258 @@ async function isPlainDirectory(dir) { } } +// src/parsers/pnpm.ts +var import_promises3 = require("fs/promises"); +async function parsePnpmLockfile(lockfilePath) { + let raw; + try { + raw = await (0, import_promises3.readFile)(lockfilePath, "utf8"); + } catch (cause) { + throw new PatchstackError( + `Could not read lockfile at ${lockfilePath}`, + "LOCKFILE_NOT_FOUND", + cause + ); + } + const lines = raw.split(/\r?\n/); + const directNames = collectDirectDepNames(lines); + const packageKeys = collectPackagesBlockKeys(lines); + if (packageKeys.length === 0) { + throw new PatchstackError( + `Lockfile at ${lockfilePath} has no "packages" entries`, + "LOCKFILE_PARSE_ERROR" + ); + } + const entries = []; + for (const key of packageKeys) { + const parsed = parsePackageKey(key); + if (parsed === null) { + continue; + } + entries.push({ + name: parsed.name, + version: parsed.version, + direct: directNames.has(parsed.name) + }); + } + return entries; +} +function parsePackageKey(rawKey) { + let k = rawKey.trim(); + if (k.length === 0) { + return null; + } + if (k.startsWith("'") && k.endsWith("'") || k.startsWith('"') && k.endsWith('"')) { + k = k.slice(1, -1); + } + if (k.startsWith("/")) { + k = k.slice(1); + } + const parenIdx = k.indexOf("("); + if (parenIdx >= 0) { + k = k.slice(0, parenIdx); + } + let scopePrefix = ""; + let body = k; + if (k.startsWith("@")) { + const firstSlash = k.indexOf("/"); + if (firstSlash <= 0) { + return null; + } + scopePrefix = k.slice(0, firstSlash + 1); + body = k.slice(firstSlash + 1); + } + const slashIdx = body.indexOf("/"); + const atIdx = body.indexOf("@"); + let sepIdx; + if (slashIdx < 0 && atIdx < 0) { + return null; + } else if (slashIdx < 0) { + sepIdx = atIdx; + } else if (atIdx < 0) { + sepIdx = slashIdx; + } else { + sepIdx = Math.min(slashIdx, atIdx); + } + const name = scopePrefix + body.slice(0, sepIdx); + let version = body.slice(sepIdx + 1); + const underscoreIdx = version.indexOf("_"); + if (underscoreIdx >= 0) { + version = version.slice(0, underscoreIdx); + } + if (name.length === 0 || version.length === 0) { + return null; + } + return { name, version }; +} +function indentOf(line) { + let i = 0; + while (i < line.length && line[i] === " ") { + i++; + } + return i; +} +function isBlankOrComment(line) { + const trimmed = line.trim(); + return trimmed.length === 0 || trimmed.startsWith("#"); +} +function collectPackagesBlockKeys(lines) { + const keys = []; + let inBlock = false; + let childIndent = null; + for (const line of lines) { + if (isBlankOrComment(line)) { + continue; + } + const indent = indentOf(line); + if (!inBlock) { + if (indent === 0 && line.trim() === "packages:") { + inBlock = true; + } + continue; + } + if (indent === 0) { + break; + } + if (childIndent === null) { + childIndent = indent; + } + if (indent !== childIndent) { + continue; + } + const content = line.slice(indent); + if (!content.endsWith(":")) { + continue; + } + keys.push(content.slice(0, -1)); + } + return keys; +} +function collectDirectDepNames(lines) { + const names = /* @__PURE__ */ new Set(); + collectFromImporters(lines, names); + for (const section of ["dependencies", "devDependencies", "optionalDependencies"]) { + collectFromTopLevelSection(lines, section, names); + } + return names; +} +var DEP_SECTIONS = /* @__PURE__ */ new Set([ + "dependencies", + "devDependencies", + "optionalDependencies" +]); +function collectFromImporters(lines, out) { + let inImporters = false; + let importerIndent = null; + let inDepSection = false; + let depSectionIndent = null; + let leafIndent = null; + for (const line of lines) { + if (isBlankOrComment(line)) { + continue; + } + const indent = indentOf(line); + const trimmed = line.trim(); + if (!inImporters) { + if (indent === 0 && trimmed === "importers:") { + inImporters = true; + } + continue; + } + if (indent === 0) { + break; + } + if (importerIndent === null) { + importerIndent = indent; + } + if (indent === importerIndent) { + inDepSection = false; + depSectionIndent = null; + leafIndent = null; + continue; + } + if (!inDepSection) { + const key = stripTrailingColon(trimmed); + if (key !== null && DEP_SECTIONS.has(key)) { + inDepSection = true; + depSectionIndent = indent; + } + continue; + } + if (depSectionIndent !== null && indent <= depSectionIndent) { + inDepSection = false; + depSectionIndent = null; + leafIndent = null; + const key = stripTrailingColon(trimmed); + if (key !== null && DEP_SECTIONS.has(key)) { + inDepSection = true; + depSectionIndent = indent; + } + continue; + } + if (leafIndent === null) { + leafIndent = indent; + } + if (indent !== leafIndent) { + continue; + } + const name = extractLeafName(trimmed); + if (name !== null) { + out.add(name); + } + } +} +function collectFromTopLevelSection(lines, section, out) { + let inSection = false; + let leafIndent = null; + for (const line of lines) { + if (isBlankOrComment(line)) { + continue; + } + const indent = indentOf(line); + const trimmed = line.trim(); + if (!inSection) { + if (indent === 0 && trimmed === `${section}:`) { + inSection = true; + } + continue; + } + if (indent === 0) { + break; + } + if (leafIndent === null) { + leafIndent = indent; + } + if (indent !== leafIndent) { + continue; + } + const name = extractLeafName(trimmed); + if (name !== null) { + out.add(name); + } + } +} +function stripTrailingColon(s) { + if (!s.endsWith(":")) { + return null; + } + return s.slice(0, -1).trim(); +} +function extractLeafName(trimmed) { + const colonIdx = trimmed.indexOf(":"); + if (colonIdx < 0) { + return null; + } + let name = trimmed.slice(0, colonIdx).trim(); + if (name.length === 0) { + return null; + } + if (name.startsWith("'") && name.endsWith("'") || name.startsWith('"') && name.endsWith('"')) { + name = name.slice(1, -1); + } + return name.length > 0 ? name : null; +} + // src/parsers/index.ts async function detectLockfile(cwd) { const npmLock = import_node_path3.default.join(cwd, "package-lock.json"); @@ -284,6 +536,15 @@ async function detectLockfile(cwd) { strategy: "node-modules-walk" }; } + const pnpmLock = import_node_path3.default.join(cwd, "pnpm-lock.yaml"); + if (await exists(pnpmLock)) { + return { + ecosystem: "npm", + filePath: pnpmLock, + filename: "pnpm-lock.yaml", + strategy: "pnpm-lockfile" + }; + } const yarnLock = import_node_path3.default.join(cwd, "yarn.lock"); if (await exists(yarnLock)) { throw new PatchstackError( @@ -291,13 +552,6 @@ async function detectLockfile(cwd) { "LOCKFILE_UNSUPPORTED" ); } - const pnpmLock = import_node_path3.default.join(cwd, "pnpm-lock.yaml"); - if (await exists(pnpmLock)) { - throw new PatchstackError( - "pnpm-lock.yaml detected but not yet supported. Open an issue at github.com/patchstack/connect to request support.", - "LOCKFILE_UNSUPPORTED" - ); - } throw new PatchstackError( `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`, "LOCKFILE_NOT_FOUND" @@ -312,13 +566,15 @@ async function runStrategy(detected, cwd) { switch (detected.strategy) { case "npm-lockfile": return parseNpmLockfile(detected.filePath); + case "pnpm-lockfile": + return parsePnpmLockfile(detected.filePath); case "node-modules-walk": return walkNodeModules(cwd); } } async function exists(filePath) { try { - await (0, import_promises3.access)(filePath); + await (0, import_promises4.access)(filePath); return true; } catch { return false; @@ -505,7 +761,7 @@ function isTimeoutError(cause) { } // src/config.ts -var import_promises4 = require("fs/promises"); +var import_promises5 = require("fs/promises"); var import_node_path4 = __toESM(require("path"), 1); var CONFIG_FILENAME = ".patchstackrc.json"; async function resolveConfig(options) { @@ -535,7 +791,7 @@ async function resolveConfig(options) { async function writeConfigFile(cwd, config) { const target = import_node_path4.default.join(cwd, CONFIG_FILENAME); const content = JSON.stringify(config, null, 2) + "\n"; - await (0, import_promises4.writeFile)(target, content, "utf8"); + await (0, import_promises5.writeFile)(target, content, "utf8"); return target; } async function persistSiteUuid(cwd, siteUuid) { @@ -546,7 +802,7 @@ async function readConfigFile(cwd) { const target = import_node_path4.default.join(cwd, CONFIG_FILENAME); let raw; try { - raw = await (0, import_promises4.readFile)(target, "utf8"); + raw = await (0, import_promises5.readFile)(target, "utf8"); } catch (err) { if (err.code === "ENOENT") { return {}; diff --git a/dist/index.cjs.map b/dist/index.cjs.map index 7ae8720..fdfad18 100644 --- a/dist/index.cjs.map +++ b/dist/index.cjs.map @@ -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/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 {\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';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy = 'npm-lockfile' | 'node-modules-walk';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\nexport async function detectLockfile(cwd: string): Promise {\n const npmLock = path.join(cwd, 'package-lock.json');\n if (await exists(npmLock)) {\n return {\n ecosystem: 'npm',\n filePath: npmLock,\n filename: 'package-lock.json',\n strategy: 'npm-lockfile',\n };\n }\n\n const bunLock = path.join(cwd, 'bun.lock');\n if (await exists(bunLock)) {\n return {\n ecosystem: 'npm',\n filePath: bunLock,\n filename: 'bun.lock',\n strategy: 'node-modules-walk',\n };\n }\n\n const bunLockB = path.join(cwd, 'bun.lockb');\n if (await exists(bunLockB)) {\n return {\n ecosystem: 'npm',\n filePath: bunLockB,\n filename: 'bun.lockb',\n strategy: 'node-modules-walk',\n };\n }\n\n const yarnLock = path.join(cwd, 'yarn.lock');\n if (await exists(yarnLock)) {\n throw new PatchstackError(\n 'yarn.lock detected but not yet supported. Run `npm install` to generate a package-lock.json, or open an issue at github.com/patchstack/connect.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n const pnpmLock = path.join(cwd, 'pnpm-lock.yaml');\n if (await exists(pnpmLock)) {\n throw new PatchstackError(\n 'pnpm-lock.yaml detected but not yet supported. Open an issue at github.com/patchstack/connect to request support.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n throw new PatchstackError(\n `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`,\n 'LOCKFILE_NOT_FOUND',\n );\n}\n\nexport async function scanLockfile(cwd: string): Promise {\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 {\n switch (detected.strategy) {\n case 'npm-lockfile':\n return parseNpmLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n","export type Ecosystem = 'npm' | 'composer';\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}\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;\n dependencies?: Record;\n}\n\ninterface LockfileV1Dependency {\n version: string;\n dependencies?: Record;\n}\n\nexport async function parseNpmLockfile(lockfilePath: string): Promise {\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): 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,\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 {\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 {\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 {\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 {\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 {\n try {\n const info = await lstat(dir);\n return info.isDirectory() && !info.isSymbolicLink();\n } catch {\n return false;\n }\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>();\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=`. 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 {\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),\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 } from './types.js';\nimport { DEFAULT_ENDPOINT, DEFAULT_TIMEOUT_MS } from './client.js';\n\nconst CONFIG_FILENAME = '.patchstackrc.json';\n\ninterface ConfigFile {\n siteUuid?: string;\n endpoint?: string;\n timeoutMs?: number;\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 {\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 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 };\n}\n\nexport async function writeConfigFile(cwd: string, config: ConfigFile): Promise {\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 {\n const existing = await readConfigFile(cwd);\n return writeConfigFile(cwd, { ...existing, siteUuid });\n}\n\nasync function readConfigFile(cwd: string): Promise {\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 return {\n siteUuid: process.env.PATCHSTACK_SITE_UUID ?? undefined,\n endpoint: process.env.PATCHSTACK_ENDPOINT ?? undefined,\n timeoutMs,\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"],"mappings":"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","names":["import_promises","import_node_path","path","import_promises","import_node_path","path","path","import_promises","import_node_path","path"]} \ No newline at end of file +{"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/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 {\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';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy = 'npm-lockfile' | 'node-modules-walk' | 'pnpm-lockfile';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\nexport async function detectLockfile(cwd: string): Promise {\n const npmLock = path.join(cwd, 'package-lock.json');\n if (await exists(npmLock)) {\n return {\n ecosystem: 'npm',\n filePath: npmLock,\n filename: 'package-lock.json',\n strategy: 'npm-lockfile',\n };\n }\n\n const bunLock = path.join(cwd, 'bun.lock');\n if (await exists(bunLock)) {\n return {\n ecosystem: 'npm',\n filePath: bunLock,\n filename: 'bun.lock',\n strategy: 'node-modules-walk',\n };\n }\n\n const bunLockB = path.join(cwd, 'bun.lockb');\n if (await exists(bunLockB)) {\n return {\n ecosystem: 'npm',\n filePath: bunLockB,\n filename: 'bun.lockb',\n strategy: 'node-modules-walk',\n };\n }\n\n const pnpmLock = path.join(cwd, 'pnpm-lock.yaml');\n if (await exists(pnpmLock)) {\n return {\n ecosystem: 'npm',\n filePath: pnpmLock,\n filename: 'pnpm-lock.yaml',\n strategy: 'pnpm-lockfile',\n };\n }\n\n const yarnLock = path.join(cwd, 'yarn.lock');\n if (await exists(yarnLock)) {\n throw new PatchstackError(\n 'yarn.lock detected but not yet supported. Run `npm install` to generate a package-lock.json, or open an issue at github.com/patchstack/connect.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n throw new PatchstackError(\n `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`,\n 'LOCKFILE_NOT_FOUND',\n );\n}\n\nexport async function scanLockfile(cwd: string): Promise {\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 {\n switch (detected.strategy) {\n case 'npm-lockfile':\n return parseNpmLockfile(detected.filePath);\n case 'pnpm-lockfile':\n return parsePnpmLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n","export type Ecosystem = 'npm' | 'composer';\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}\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;\n dependencies?: Record;\n}\n\ninterface LockfileV1Dependency {\n version: string;\n dependencies?: Record;\n}\n\nexport async function parseNpmLockfile(lockfilePath: string): Promise {\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): 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,\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 {\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 {\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 {\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 {\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 {\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 {\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:` > `:` > `{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 {\n const names = new Set();\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): 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,\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 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>();\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=`. 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 {\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),\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 } from './types.js';\nimport { DEFAULT_ENDPOINT, DEFAULT_TIMEOUT_MS } from './client.js';\n\nconst CONFIG_FILENAME = '.patchstackrc.json';\n\ninterface ConfigFile {\n siteUuid?: string;\n endpoint?: string;\n timeoutMs?: number;\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 {\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 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 };\n}\n\nexport async function writeConfigFile(cwd: string, config: ConfigFile): Promise {\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 {\n const existing = await readConfigFile(cwd);\n return writeConfigFile(cwd, { ...existing, siteUuid });\n}\n\nasync function readConfigFile(cwd: string): Promise {\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 return {\n siteUuid: process.env.PATCHSTACK_SITE_UUID ?? undefined,\n endpoint: process.env.PATCHSTACK_ENDPOINT ?? undefined,\n timeoutMs,\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"],"mappings":"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","names":["import_promises","import_node_path","path","import_promises","import_node_path","path","import_promises","path","import_promises","import_node_path","path"]} \ No newline at end of file diff --git a/dist/index.d.cts b/dist/index.d.cts index b5ad5b6..b57a198 100644 --- a/dist/index.d.cts +++ b/dist/index.d.cts @@ -36,7 +36,7 @@ declare class PatchstackError extends Error { } type LockfileFilename = 'package-lock.json' | 'bun.lock' | 'bun.lockb' | 'yarn.lock' | 'pnpm-lock.yaml'; -type DetectionStrategy = 'npm-lockfile' | 'node-modules-walk'; +type DetectionStrategy = 'npm-lockfile' | 'node-modules-walk' | 'pnpm-lockfile'; interface DetectedLockfile { ecosystem: 'npm'; filePath: string; diff --git a/dist/index.d.ts b/dist/index.d.ts index b5ad5b6..b57a198 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -36,7 +36,7 @@ declare class PatchstackError extends Error { } type LockfileFilename = 'package-lock.json' | 'bun.lock' | 'bun.lockb' | 'yarn.lock' | 'pnpm-lock.yaml'; -type DetectionStrategy = 'npm-lockfile' | 'node-modules-walk'; +type DetectionStrategy = 'npm-lockfile' | 'node-modules-walk' | 'pnpm-lockfile'; interface DetectedLockfile { ecosystem: 'npm'; filePath: string; diff --git a/dist/index.js b/dist/index.js index 7253be4..3f22477 100644 --- a/dist/index.js +++ b/dist/index.js @@ -207,6 +207,258 @@ async function isPlainDirectory(dir) { } } +// src/parsers/pnpm.ts +import { readFile as readFile3 } from "fs/promises"; +async function parsePnpmLockfile(lockfilePath) { + let raw; + try { + raw = await readFile3(lockfilePath, "utf8"); + } catch (cause) { + throw new PatchstackError( + `Could not read lockfile at ${lockfilePath}`, + "LOCKFILE_NOT_FOUND", + cause + ); + } + const lines = raw.split(/\r?\n/); + const directNames = collectDirectDepNames(lines); + const packageKeys = collectPackagesBlockKeys(lines); + if (packageKeys.length === 0) { + throw new PatchstackError( + `Lockfile at ${lockfilePath} has no "packages" entries`, + "LOCKFILE_PARSE_ERROR" + ); + } + const entries = []; + for (const key of packageKeys) { + const parsed = parsePackageKey(key); + if (parsed === null) { + continue; + } + entries.push({ + name: parsed.name, + version: parsed.version, + direct: directNames.has(parsed.name) + }); + } + return entries; +} +function parsePackageKey(rawKey) { + let k = rawKey.trim(); + if (k.length === 0) { + return null; + } + if (k.startsWith("'") && k.endsWith("'") || k.startsWith('"') && k.endsWith('"')) { + k = k.slice(1, -1); + } + if (k.startsWith("/")) { + k = k.slice(1); + } + const parenIdx = k.indexOf("("); + if (parenIdx >= 0) { + k = k.slice(0, parenIdx); + } + let scopePrefix = ""; + let body = k; + if (k.startsWith("@")) { + const firstSlash = k.indexOf("/"); + if (firstSlash <= 0) { + return null; + } + scopePrefix = k.slice(0, firstSlash + 1); + body = k.slice(firstSlash + 1); + } + const slashIdx = body.indexOf("/"); + const atIdx = body.indexOf("@"); + let sepIdx; + if (slashIdx < 0 && atIdx < 0) { + return null; + } else if (slashIdx < 0) { + sepIdx = atIdx; + } else if (atIdx < 0) { + sepIdx = slashIdx; + } else { + sepIdx = Math.min(slashIdx, atIdx); + } + const name = scopePrefix + body.slice(0, sepIdx); + let version = body.slice(sepIdx + 1); + const underscoreIdx = version.indexOf("_"); + if (underscoreIdx >= 0) { + version = version.slice(0, underscoreIdx); + } + if (name.length === 0 || version.length === 0) { + return null; + } + return { name, version }; +} +function indentOf(line) { + let i = 0; + while (i < line.length && line[i] === " ") { + i++; + } + return i; +} +function isBlankOrComment(line) { + const trimmed = line.trim(); + return trimmed.length === 0 || trimmed.startsWith("#"); +} +function collectPackagesBlockKeys(lines) { + const keys = []; + let inBlock = false; + let childIndent = null; + for (const line of lines) { + if (isBlankOrComment(line)) { + continue; + } + const indent = indentOf(line); + if (!inBlock) { + if (indent === 0 && line.trim() === "packages:") { + inBlock = true; + } + continue; + } + if (indent === 0) { + break; + } + if (childIndent === null) { + childIndent = indent; + } + if (indent !== childIndent) { + continue; + } + const content = line.slice(indent); + if (!content.endsWith(":")) { + continue; + } + keys.push(content.slice(0, -1)); + } + return keys; +} +function collectDirectDepNames(lines) { + const names = /* @__PURE__ */ new Set(); + collectFromImporters(lines, names); + for (const section of ["dependencies", "devDependencies", "optionalDependencies"]) { + collectFromTopLevelSection(lines, section, names); + } + return names; +} +var DEP_SECTIONS = /* @__PURE__ */ new Set([ + "dependencies", + "devDependencies", + "optionalDependencies" +]); +function collectFromImporters(lines, out) { + let inImporters = false; + let importerIndent = null; + let inDepSection = false; + let depSectionIndent = null; + let leafIndent = null; + for (const line of lines) { + if (isBlankOrComment(line)) { + continue; + } + const indent = indentOf(line); + const trimmed = line.trim(); + if (!inImporters) { + if (indent === 0 && trimmed === "importers:") { + inImporters = true; + } + continue; + } + if (indent === 0) { + break; + } + if (importerIndent === null) { + importerIndent = indent; + } + if (indent === importerIndent) { + inDepSection = false; + depSectionIndent = null; + leafIndent = null; + continue; + } + if (!inDepSection) { + const key = stripTrailingColon(trimmed); + if (key !== null && DEP_SECTIONS.has(key)) { + inDepSection = true; + depSectionIndent = indent; + } + continue; + } + if (depSectionIndent !== null && indent <= depSectionIndent) { + inDepSection = false; + depSectionIndent = null; + leafIndent = null; + const key = stripTrailingColon(trimmed); + if (key !== null && DEP_SECTIONS.has(key)) { + inDepSection = true; + depSectionIndent = indent; + } + continue; + } + if (leafIndent === null) { + leafIndent = indent; + } + if (indent !== leafIndent) { + continue; + } + const name = extractLeafName(trimmed); + if (name !== null) { + out.add(name); + } + } +} +function collectFromTopLevelSection(lines, section, out) { + let inSection = false; + let leafIndent = null; + for (const line of lines) { + if (isBlankOrComment(line)) { + continue; + } + const indent = indentOf(line); + const trimmed = line.trim(); + if (!inSection) { + if (indent === 0 && trimmed === `${section}:`) { + inSection = true; + } + continue; + } + if (indent === 0) { + break; + } + if (leafIndent === null) { + leafIndent = indent; + } + if (indent !== leafIndent) { + continue; + } + const name = extractLeafName(trimmed); + if (name !== null) { + out.add(name); + } + } +} +function stripTrailingColon(s) { + if (!s.endsWith(":")) { + return null; + } + return s.slice(0, -1).trim(); +} +function extractLeafName(trimmed) { + const colonIdx = trimmed.indexOf(":"); + if (colonIdx < 0) { + return null; + } + let name = trimmed.slice(0, colonIdx).trim(); + if (name.length === 0) { + return null; + } + if (name.startsWith("'") && name.endsWith("'") || name.startsWith('"') && name.endsWith('"')) { + name = name.slice(1, -1); + } + return name.length > 0 ? name : null; +} + // src/parsers/index.ts async function detectLockfile(cwd) { const npmLock = path3.join(cwd, "package-lock.json"); @@ -236,6 +488,15 @@ async function detectLockfile(cwd) { strategy: "node-modules-walk" }; } + const pnpmLock = path3.join(cwd, "pnpm-lock.yaml"); + if (await exists(pnpmLock)) { + return { + ecosystem: "npm", + filePath: pnpmLock, + filename: "pnpm-lock.yaml", + strategy: "pnpm-lockfile" + }; + } const yarnLock = path3.join(cwd, "yarn.lock"); if (await exists(yarnLock)) { throw new PatchstackError( @@ -243,13 +504,6 @@ async function detectLockfile(cwd) { "LOCKFILE_UNSUPPORTED" ); } - const pnpmLock = path3.join(cwd, "pnpm-lock.yaml"); - if (await exists(pnpmLock)) { - throw new PatchstackError( - "pnpm-lock.yaml detected but not yet supported. Open an issue at github.com/patchstack/connect to request support.", - "LOCKFILE_UNSUPPORTED" - ); - } throw new PatchstackError( `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`, "LOCKFILE_NOT_FOUND" @@ -264,6 +518,8 @@ async function runStrategy(detected, cwd) { switch (detected.strategy) { case "npm-lockfile": return parseNpmLockfile(detected.filePath); + case "pnpm-lockfile": + return parsePnpmLockfile(detected.filePath); case "node-modules-walk": return walkNodeModules(cwd); } @@ -457,7 +713,7 @@ function isTimeoutError(cause) { } // src/config.ts -import { readFile as readFile3, writeFile } from "fs/promises"; +import { readFile as readFile4, writeFile } from "fs/promises"; import path4 from "path"; var CONFIG_FILENAME = ".patchstackrc.json"; async function resolveConfig(options) { @@ -498,7 +754,7 @@ async function readConfigFile(cwd) { const target = path4.join(cwd, CONFIG_FILENAME); let raw; try { - raw = await readFile3(target, "utf8"); + raw = await readFile4(target, "utf8"); } catch (err) { if (err.code === "ENOENT") { return {}; diff --git a/dist/index.js.map b/dist/index.js.map index 19a8503..efd7948 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/parsers/index.ts","../src/types.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/normalize.ts","../src/client.ts","../src/config.ts","../src/index.ts"],"sourcesContent":["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';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy = 'npm-lockfile' | 'node-modules-walk';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\nexport async function detectLockfile(cwd: string): Promise {\n const npmLock = path.join(cwd, 'package-lock.json');\n if (await exists(npmLock)) {\n return {\n ecosystem: 'npm',\n filePath: npmLock,\n filename: 'package-lock.json',\n strategy: 'npm-lockfile',\n };\n }\n\n const bunLock = path.join(cwd, 'bun.lock');\n if (await exists(bunLock)) {\n return {\n ecosystem: 'npm',\n filePath: bunLock,\n filename: 'bun.lock',\n strategy: 'node-modules-walk',\n };\n }\n\n const bunLockB = path.join(cwd, 'bun.lockb');\n if (await exists(bunLockB)) {\n return {\n ecosystem: 'npm',\n filePath: bunLockB,\n filename: 'bun.lockb',\n strategy: 'node-modules-walk',\n };\n }\n\n const yarnLock = path.join(cwd, 'yarn.lock');\n if (await exists(yarnLock)) {\n throw new PatchstackError(\n 'yarn.lock detected but not yet supported. Run `npm install` to generate a package-lock.json, or open an issue at github.com/patchstack/connect.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n const pnpmLock = path.join(cwd, 'pnpm-lock.yaml');\n if (await exists(pnpmLock)) {\n throw new PatchstackError(\n 'pnpm-lock.yaml detected but not yet supported. Open an issue at github.com/patchstack/connect to request support.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n throw new PatchstackError(\n `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`,\n 'LOCKFILE_NOT_FOUND',\n );\n}\n\nexport async function scanLockfile(cwd: string): Promise {\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 {\n switch (detected.strategy) {\n case 'npm-lockfile':\n return parseNpmLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n","export type Ecosystem = 'npm' | 'composer';\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}\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;\n dependencies?: Record;\n}\n\ninterface LockfileV1Dependency {\n version: string;\n dependencies?: Record;\n}\n\nexport async function parseNpmLockfile(lockfilePath: string): Promise {\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): 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,\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 {\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 {\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 {\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 {\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 {\n try {\n const info = await lstat(dir);\n return info.isDirectory() && !info.isSymbolicLink();\n } catch {\n return false;\n }\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>();\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=`. 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 {\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),\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 } from './types.js';\nimport { DEFAULT_ENDPOINT, DEFAULT_TIMEOUT_MS } from './client.js';\n\nconst CONFIG_FILENAME = '.patchstackrc.json';\n\ninterface ConfigFile {\n siteUuid?: string;\n endpoint?: string;\n timeoutMs?: number;\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 {\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 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 };\n}\n\nexport async function writeConfigFile(cwd: string, config: ConfigFile): Promise {\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 {\n const existing = await readConfigFile(cwd);\n return writeConfigFile(cwd, { ...existing, siteUuid });\n}\n\nasync function readConfigFile(cwd: string): Promise {\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 return {\n siteUuid: process.env.PATCHSTACK_SITE_UUID ?? undefined,\n endpoint: process.env.PATCHSTACK_ENDPOINT ?? undefined,\n timeoutMs,\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","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 {\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"],"mappings":"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","names":["path","readFile","path","path","readFile","path","readFile","path","path","readFile"]} \ No newline at end of file +{"version":3,"sources":["../src/parsers/index.ts","../src/types.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/parsers/pnpm.ts","../src/normalize.ts","../src/client.ts","../src/config.ts","../src/index.ts"],"sourcesContent":["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';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy = 'npm-lockfile' | 'node-modules-walk' | 'pnpm-lockfile';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\nexport async function detectLockfile(cwd: string): Promise {\n const npmLock = path.join(cwd, 'package-lock.json');\n if (await exists(npmLock)) {\n return {\n ecosystem: 'npm',\n filePath: npmLock,\n filename: 'package-lock.json',\n strategy: 'npm-lockfile',\n };\n }\n\n const bunLock = path.join(cwd, 'bun.lock');\n if (await exists(bunLock)) {\n return {\n ecosystem: 'npm',\n filePath: bunLock,\n filename: 'bun.lock',\n strategy: 'node-modules-walk',\n };\n }\n\n const bunLockB = path.join(cwd, 'bun.lockb');\n if (await exists(bunLockB)) {\n return {\n ecosystem: 'npm',\n filePath: bunLockB,\n filename: 'bun.lockb',\n strategy: 'node-modules-walk',\n };\n }\n\n const pnpmLock = path.join(cwd, 'pnpm-lock.yaml');\n if (await exists(pnpmLock)) {\n return {\n ecosystem: 'npm',\n filePath: pnpmLock,\n filename: 'pnpm-lock.yaml',\n strategy: 'pnpm-lockfile',\n };\n }\n\n const yarnLock = path.join(cwd, 'yarn.lock');\n if (await exists(yarnLock)) {\n throw new PatchstackError(\n 'yarn.lock detected but not yet supported. Run `npm install` to generate a package-lock.json, or open an issue at github.com/patchstack/connect.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n throw new PatchstackError(\n `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`,\n 'LOCKFILE_NOT_FOUND',\n );\n}\n\nexport async function scanLockfile(cwd: string): Promise {\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 {\n switch (detected.strategy) {\n case 'npm-lockfile':\n return parseNpmLockfile(detected.filePath);\n case 'pnpm-lockfile':\n return parsePnpmLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n","export type Ecosystem = 'npm' | 'composer';\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}\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;\n dependencies?: Record;\n}\n\ninterface LockfileV1Dependency {\n version: string;\n dependencies?: Record;\n}\n\nexport async function parseNpmLockfile(lockfilePath: string): Promise {\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): 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,\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 {\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 {\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 {\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 {\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 {\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 {\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:` > `:` > `{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 {\n const names = new Set();\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): 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,\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 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>();\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=`. 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 {\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),\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 } from './types.js';\nimport { DEFAULT_ENDPOINT, DEFAULT_TIMEOUT_MS } from './client.js';\n\nconst CONFIG_FILENAME = '.patchstackrc.json';\n\ninterface ConfigFile {\n siteUuid?: string;\n endpoint?: string;\n timeoutMs?: number;\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 {\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 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 };\n}\n\nexport async function writeConfigFile(cwd: string, config: ConfigFile): Promise {\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 {\n const existing = await readConfigFile(cwd);\n return writeConfigFile(cwd, { ...existing, siteUuid });\n}\n\nasync function readConfigFile(cwd: string): Promise {\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 return {\n siteUuid: process.env.PATCHSTACK_SITE_UUID ?? undefined,\n endpoint: process.env.PATCHSTACK_ENDPOINT ?? undefined,\n timeoutMs,\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","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 {\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"],"mappings":";AAAA,SAAS,cAAc;AACvB,OAAOA,WAAU;;;ACmCV,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACzC,YACE,SACgB,MAWA,OAChB;AACA,UAAM,OAAO;AAbG;AAWA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EAfkB;AAAA,EAWA;AAKpB;;;ACvDA,SAAS,gBAAgB;AACzB,OAAO,UAAU;AAqBjB,eAAsB,iBAAiB,cAA+C;AACpF,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,SAAS,cAAc,MAAM;AAAA,EAC3C,SAAS,OAAO;AACd,UAAM,IAAI,gBAAgB,8BAA8B,YAAY,IAAI,sBAAsB,KAAK;AAAA,EACrG;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,OAAO;AACd,UAAM,IAAI,gBAAgB,eAAe,YAAY,sBAAsB,wBAAwB,KAAK;AAAA,EAC1G;AAEA,MAAI,OAAO,UAAU;AACnB,WAAO,cAAc,OAAO,QAAQ;AAAA,EACtC;AAEA,MAAI,OAAO,cAAc;AACvB,WAAO,cAAc,OAAO,YAAY;AAAA,EAC1C;AAEA,QAAM,IAAI;AAAA,IACR,eAAe,YAAY;AAAA,IAC3B;AAAA,EACF;AACF;AAEA,SAAS,cAAc,UAA6D;AAClF,QAAM,UAA0B,CAAC;AAEjC,aAAW,CAAC,SAAS,GAAG,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACrD,QAAI,YAAY,IAAI;AAClB;AAAA,IACF;AACA,QAAI,IAAI,SAAS,MAAM;AACrB;AAAA,IACF;AACA,QAAI,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,WAAW,GAAG;AAC/D;AAAA,IACF;AAEA,UAAM,OAAO,IAAI,QAAQ,oBAAoB,OAAO;AACpD,QAAI,SAAS,MAAM;AACjB;AAAA,IACF;AAEA,YAAQ,KAAK;AAAA,MACX;AAAA,MACA,SAAS,IAAI;AAAA,MACb,MAAM;AAAA,MACN,QAAQ,WAAW,OAAO;AAAA,IAC5B,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAEA,SAAS,cACP,MACA,MAAsB,CAAC,GACvB,QAAQ,GACQ;AAChB,aAAW,CAAC,MAAM,GAAG,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC9C,QAAI,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,SAAS,GAAG;AAC7D,UAAI,KAAK,EAAE,MAAM,SAAS,IAAI,SAAS,QAAQ,UAAU,EAAE,CAAC;AAAA,IAC9D;AACA,QAAI,IAAI,cAAc;AACpB,oBAAc,IAAI,cAAc,KAAK,QAAQ,CAAC;AAAA,IAChD;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,SAAgC;AAC3D,QAAM,WAAW,QAAQ,MAAM,iBAAiB,KAAK,QAAQ,UAAU,KAAK,MAAM,GAAG;AACrF,QAAM,QAAQ,QAAQ,MAAM,GAAG;AAC/B,QAAM,UAAU,MAAM,YAAY,cAAc;AAChD,MAAI,YAAY,MAAM,WAAW,MAAM,SAAS,GAAG;AACjD,WAAO,SAAS,SAAS,SAAS,CAAC,KAAK;AAAA,EAC1C;AACA,QAAM,OAAO,MAAM,MAAM,UAAU,CAAC;AACpC,MAAI,KAAK,WAAW,GAAG;AACrB,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,KAAK,CAAC;AACpB,MAAI,UAAU,UAAa,MAAM,WAAW,GAAG,KAAK,KAAK,UAAU,GAAG;AACpE,WAAO,GAAG,KAAK,IAAI,KAAK,CAAC,CAAC;AAAA,EAC5B;AACA,SAAO,SAAS;AAClB;AAEA,SAAS,WAAW,SAA0B;AAC5C,QAAM,QAAQ,QAAQ,MAAM,GAAG;AAC/B,QAAM,UAAU,MAAM,OAAO,CAAC,MAAM,MAAM,cAAc,EAAE;AAC1D,SAAO,YAAY;AACrB;;;ACvHA,SAAS,OAAO,YAAAC,WAAU,SAAS,YAAY;AAC/C,OAAOC,WAAU;AAGjB,eAAsB,gBAAgB,KAAsC;AAC1E,QAAM,OAAOC,MAAK,KAAK,KAAK,cAAc;AAE1C,MAAI;AACF,UAAM,OAAO,MAAM,KAAK,IAAI;AAC5B,QAAI,CAAC,KAAK,YAAY,GAAG;AACvB,YAAM,IAAI;AAAA,QACR,GAAG,IAAI;AAAA,QACP;AAAA,MACF;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,QAAI,iBAAiB,iBAAiB;AACpC,YAAM;AAAA,IACR;AACA,UAAM,IAAI;AAAA,MACR,8BAA8B,GAAG;AAAA,MACjC;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAA0B,CAAC;AACjC,QAAM,KAAK,MAAM,SAAS,CAAC;AAC3B,SAAO;AACT;AAEA,eAAe,KAAK,KAAa,KAAqB,OAA8B;AAClF,MAAI;AACJ,MAAI;AACF,YAAQ,MAAM,QAAQ,GAAG;AAAA,EAC3B,QAAQ;AACN;AAAA,EACF;AAEA,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,WAAW,GAAG,GAAG;AACxB;AAAA,IACF;AAEA,UAAM,WAAWA,MAAK,KAAK,KAAK,IAAI;AACpC,QAAI,CAAE,MAAM,iBAAiB,QAAQ,GAAI;AACvC;AAAA,IACF;AAEA,QAAI,KAAK,WAAW,GAAG,GAAG;AACxB,UAAI;AACJ,UAAI;AACF,mBAAW,MAAM,QAAQ,QAAQ;AAAA,MACnC,QAAQ;AACN;AAAA,MACF;AACA,iBAAW,OAAO,UAAU;AAC1B,YAAI,IAAI,WAAW,GAAG,GAAG;AACvB;AAAA,QACF;AACA,cAAM,YAAYA,MAAK,KAAK,UAAU,GAAG;AACzC,YAAI,CAAE,MAAM,iBAAiB,SAAS,GAAI;AACxC;AAAA,QACF;AACA,cAAM,YAAY,WAAW,OAAO,GAAG;AACvC,cAAM,WAAW,WAAW,KAAK,KAAK;AAAA,MACxC;AACA;AAAA,IACF;AAEA,UAAM,YAAY,UAAU,OAAO,GAAG;AACtC,UAAM,WAAW,UAAU,KAAK,KAAK;AAAA,EACvC;AACF;AAEA,eAAe,YACb,QACA,OACA,KACe;AACf,MAAI;AACJ,MAAI;AACF,UAAM,MAAMC,UAASD,MAAK,KAAK,QAAQ,cAAc,GAAG,MAAM;AAAA,EAChE,QAAQ;AACN;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,QAAQ;AACN;AAAA,EACF;AAEA,MAAI,OAAO,OAAO,SAAS,YAAY,OAAO,KAAK,WAAW,GAAG;AAC/D;AAAA,EACF;AACA,MAAI,OAAO,OAAO,YAAY,YAAY,OAAO,QAAQ,WAAW,GAAG;AACrE;AAAA,EACF;AAEA,MAAI,KAAK;AAAA,IACP,MAAM,OAAO;AAAA,IACb,SAAS,OAAO;AAAA,IAChB,QAAQ,UAAU;AAAA,EACpB,CAAC;AACH;AAEA,eAAe,WACb,QACA,KACA,OACe;AACf,QAAM,SAASA,MAAK,KAAK,QAAQ,cAAc;AAC/C,MAAI,CAAE,MAAM,iBAAiB,MAAM,GAAI;AACrC;AAAA,EACF;AACA,QAAM,KAAK,QAAQ,KAAK,QAAQ,CAAC;AACnC;AAEA,eAAe,iBAAiB,KAA+B;AAC7D,MAAI;AACF,UAAM,OAAO,MAAM,MAAM,GAAG;AAC5B,WAAO,KAAK,YAAY,KAAK,CAAC,KAAK,eAAe;AAAA,EACpD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AC/HA,SAAS,YAAAE,iBAAgB;AAczB,eAAsB,kBAAkB,cAA+C;AACrF,MAAI;AACJ,MAAI;AACF,UAAM,MAAMC,UAAS,cAAc,MAAM;AAAA,EAC3C,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,8BAA8B,YAAY;AAAA,MAC1C;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAAQ,IAAI,MAAM,OAAO;AAC/B,QAAM,cAAc,sBAAsB,KAAK;AAC/C,QAAM,cAAc,yBAAyB,KAAK;AAElD,MAAI,YAAY,WAAW,GAAG;AAC5B,UAAM,IAAI;AAAA,MACR,eAAe,YAAY;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAA0B,CAAC;AACjC,aAAW,OAAO,aAAa;AAC7B,UAAM,SAAS,gBAAgB,GAAG;AAClC,QAAI,WAAW,MAAM;AACnB;AAAA,IACF;AACA,YAAQ,KAAK;AAAA,MACX,MAAM,OAAO;AAAA,MACb,SAAS,OAAO;AAAA,MAChB,QAAQ,YAAY,IAAI,OAAO,IAAI;AAAA,IACrC,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAOO,SAAS,gBAAgB,QAAkC;AAChE,MAAI,IAAI,OAAO,KAAK;AACpB,MAAI,EAAE,WAAW,GAAG;AAClB,WAAO;AAAA,EACT;AAEA,MACG,EAAE,WAAW,GAAG,KAAK,EAAE,SAAS,GAAG,KACnC,EAAE,WAAW,GAAG,KAAK,EAAE,SAAS,GAAG,GACpC;AACA,QAAI,EAAE,MAAM,GAAG,EAAE;AAAA,EACnB;AAEA,MAAI,EAAE,WAAW,GAAG,GAAG;AACrB,QAAI,EAAE,MAAM,CAAC;AAAA,EACf;AAGA,QAAM,WAAW,EAAE,QAAQ,GAAG;AAC9B,MAAI,YAAY,GAAG;AACjB,QAAI,EAAE,MAAM,GAAG,QAAQ;AAAA,EACzB;AAMA,MAAI,cAAc;AAClB,MAAI,OAAO;AACX,MAAI,EAAE,WAAW,GAAG,GAAG;AACrB,UAAM,aAAa,EAAE,QAAQ,GAAG;AAChC,QAAI,cAAc,GAAG;AACnB,aAAO;AAAA,IACT;AACA,kBAAc,EAAE,MAAM,GAAG,aAAa,CAAC;AACvC,WAAO,EAAE,MAAM,aAAa,CAAC;AAAA,EAC/B;AAEA,QAAM,WAAW,KAAK,QAAQ,GAAG;AACjC,QAAM,QAAQ,KAAK,QAAQ,GAAG;AAE9B,MAAI;AACJ,MAAI,WAAW,KAAK,QAAQ,GAAG;AAC7B,WAAO;AAAA,EACT,WAAW,WAAW,GAAG;AACvB,aAAS;AAAA,EACX,WAAW,QAAQ,GAAG;AACpB,aAAS;AAAA,EACX,OAAO;AACL,aAAS,KAAK,IAAI,UAAU,KAAK;AAAA,EACnC;AAEA,QAAM,OAAO,cAAc,KAAK,MAAM,GAAG,MAAM;AAC/C,MAAI,UAAU,KAAK,MAAM,SAAS,CAAC;AAGnC,QAAM,gBAAgB,QAAQ,QAAQ,GAAG;AACzC,MAAI,iBAAiB,GAAG;AACtB,cAAU,QAAQ,MAAM,GAAG,aAAa;AAAA,EAC1C;AAEA,MAAI,KAAK,WAAW,KAAK,QAAQ,WAAW,GAAG;AAC7C,WAAO;AAAA,EACT;AAEA,SAAO,EAAE,MAAM,QAAQ;AACzB;AAEA,SAAS,SAAS,MAAsB;AACtC,MAAI,IAAI;AACR,SAAO,IAAI,KAAK,UAAU,KAAK,CAAC,MAAM,KAAK;AACzC;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,MAAuB;AAC/C,QAAM,UAAU,KAAK,KAAK;AAC1B,SAAO,QAAQ,WAAW,KAAK,QAAQ,WAAW,GAAG;AACvD;AAMA,SAAS,yBAAyB,OAA2B;AAC3D,QAAM,OAAiB,CAAC;AACxB,MAAI,UAAU;AACd,MAAI,cAA6B;AAEjC,aAAW,QAAQ,OAAO;AACxB,QAAI,iBAAiB,IAAI,GAAG;AAC1B;AAAA,IACF;AAEA,UAAM,SAAS,SAAS,IAAI;AAE5B,QAAI,CAAC,SAAS;AACZ,UAAI,WAAW,KAAK,KAAK,KAAK,MAAM,aAAa;AAC/C,kBAAU;AAAA,MACZ;AACA;AAAA,IACF;AAEA,QAAI,WAAW,GAAG;AAChB;AAAA,IACF;AAEA,QAAI,gBAAgB,MAAM;AACxB,oBAAc;AAAA,IAChB;AAEA,QAAI,WAAW,aAAa;AAC1B;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,MAAM,MAAM;AACjC,QAAI,CAAC,QAAQ,SAAS,GAAG,GAAG;AAC1B;AAAA,IACF;AAEA,SAAK,KAAK,QAAQ,MAAM,GAAG,EAAE,CAAC;AAAA,EAChC;AAEA,SAAO;AACT;AAcA,SAAS,sBAAsB,OAA8B;AAC3D,QAAM,QAAQ,oBAAI,IAAY;AAC9B,uBAAqB,OAAO,KAAK;AACjC,aAAW,WAAW,CAAC,gBAAgB,mBAAmB,sBAAsB,GAAG;AACjF,+BAA2B,OAAO,SAAS,KAAK;AAAA,EAClD;AACA,SAAO;AACT;AAEA,IAAM,eAAe,oBAAI,IAAI;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,qBAAqB,OAAiB,KAAwB;AACrE,MAAI,cAAc;AAClB,MAAI,iBAAgC;AACpC,MAAI,eAAe;AACnB,MAAI,mBAAkC;AACtC,MAAI,aAA4B;AAEhC,aAAW,QAAQ,OAAO;AACxB,QAAI,iBAAiB,IAAI,GAAG;AAC1B;AAAA,IACF;AACA,UAAM,SAAS,SAAS,IAAI;AAC5B,UAAM,UAAU,KAAK,KAAK;AAE1B,QAAI,CAAC,aAAa;AAChB,UAAI,WAAW,KAAK,YAAY,cAAc;AAC5C,sBAAc;AAAA,MAChB;AACA;AAAA,IACF;AAEA,QAAI,WAAW,GAAG;AAChB;AAAA,IACF;AAGA,QAAI,mBAAmB,MAAM;AAC3B,uBAAiB;AAAA,IACnB;AAEA,QAAI,WAAW,gBAAgB;AAC7B,qBAAe;AACf,yBAAmB;AACnB,mBAAa;AACb;AAAA,IACF;AAEA,QAAI,CAAC,cAAc;AACjB,YAAM,MAAM,mBAAmB,OAAO;AACtC,UAAI,QAAQ,QAAQ,aAAa,IAAI,GAAG,GAAG;AACzC,uBAAe;AACf,2BAAmB;AAAA,MACrB;AACA;AAAA,IACF;AAEA,QAAI,qBAAqB,QAAQ,UAAU,kBAAkB;AAE3D,qBAAe;AACf,yBAAmB;AACnB,mBAAa;AACb,YAAM,MAAM,mBAAmB,OAAO;AACtC,UAAI,QAAQ,QAAQ,aAAa,IAAI,GAAG,GAAG;AACzC,uBAAe;AACf,2BAAmB;AAAA,MACrB;AACA;AAAA,IACF;AAEA,QAAI,eAAe,MAAM;AACvB,mBAAa;AAAA,IACf;AAEA,QAAI,WAAW,YAAY;AACzB;AAAA,IACF;AAEA,UAAM,OAAO,gBAAgB,OAAO;AACpC,QAAI,SAAS,MAAM;AACjB,UAAI,IAAI,IAAI;AAAA,IACd;AAAA,EACF;AACF;AAEA,SAAS,2BACP,OACA,SACA,KACM;AACN,MAAI,YAAY;AAChB,MAAI,aAA4B;AAEhC,aAAW,QAAQ,OAAO;AACxB,QAAI,iBAAiB,IAAI,GAAG;AAC1B;AAAA,IACF;AACA,UAAM,SAAS,SAAS,IAAI;AAC5B,UAAM,UAAU,KAAK,KAAK;AAE1B,QAAI,CAAC,WAAW;AACd,UAAI,WAAW,KAAK,YAAY,GAAG,OAAO,KAAK;AAC7C,oBAAY;AAAA,MACd;AACA;AAAA,IACF;AAEA,QAAI,WAAW,GAAG;AAChB;AAAA,IACF;AAEA,QAAI,eAAe,MAAM;AACvB,mBAAa;AAAA,IACf;AAEA,QAAI,WAAW,YAAY;AACzB;AAAA,IACF;AAEA,UAAM,OAAO,gBAAgB,OAAO;AACpC,QAAI,SAAS,MAAM;AACjB,UAAI,IAAI,IAAI;AAAA,IACd;AAAA,EACF;AACF;AAEA,SAAS,mBAAmB,GAA0B;AACpD,MAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AACpB,WAAO;AAAA,EACT;AACA,SAAO,EAAE,MAAM,GAAG,EAAE,EAAE,KAAK;AAC7B;AAQA,SAAS,gBAAgB,SAAgC;AACvD,QAAM,WAAW,QAAQ,QAAQ,GAAG;AACpC,MAAI,WAAW,GAAG;AAChB,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,MAAM,GAAG,QAAQ,EAAE,KAAK;AAC3C,MAAI,KAAK,WAAW,GAAG;AACrB,WAAO;AAAA,EACT;AACA,MACG,KAAK,WAAW,GAAG,KAAK,KAAK,SAAS,GAAG,KACzC,KAAK,WAAW,GAAG,KAAK,KAAK,SAAS,GAAG,GAC1C;AACA,WAAO,KAAK,MAAM,GAAG,EAAE;AAAA,EACzB;AACA,SAAO,KAAK,SAAS,IAAI,OAAO;AAClC;;;AJ7UA,eAAsB,eAAe,KAAwC;AAC3E,QAAM,UAAUC,MAAK,KAAK,KAAK,mBAAmB;AAClD,MAAI,MAAM,OAAO,OAAO,GAAG;AACzB,WAAO;AAAA,MACL,WAAW;AAAA,MACX,UAAU;AAAA,MACV,UAAU;AAAA,MACV,UAAU;AAAA,IACZ;AAAA,EACF;AAEA,QAAM,UAAUA,MAAK,KAAK,KAAK,UAAU;AACzC,MAAI,MAAM,OAAO,OAAO,GAAG;AACzB,WAAO;AAAA,MACL,WAAW;AAAA,MACX,UAAU;AAAA,MACV,UAAU;AAAA,MACV,UAAU;AAAA,IACZ;AAAA,EACF;AAEA,QAAM,WAAWA,MAAK,KAAK,KAAK,WAAW;AAC3C,MAAI,MAAM,OAAO,QAAQ,GAAG;AAC1B,WAAO;AAAA,MACL,WAAW;AAAA,MACX,UAAU;AAAA,MACV,UAAU;AAAA,MACV,UAAU;AAAA,IACZ;AAAA,EACF;AAEA,QAAM,WAAWA,MAAK,KAAK,KAAK,gBAAgB;AAChD,MAAI,MAAM,OAAO,QAAQ,GAAG;AAC1B,WAAO;AAAA,MACL,WAAW;AAAA,MACX,UAAU;AAAA,MACV,UAAU;AAAA,MACV,UAAU;AAAA,IACZ;AAAA,EACF;AAEA,QAAM,WAAWA,MAAK,KAAK,KAAK,WAAW;AAC3C,MAAI,MAAM,OAAO,QAAQ,GAAG;AAC1B,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,IAAI;AAAA,IACR,wBAAwB,GAAG;AAAA,IAC3B;AAAA,EACF;AACF;AAEA,eAAsB,aAAa,KAAgC;AACjE,QAAM,WAAW,MAAM,eAAe,GAAG;AACzC,QAAM,WAAW,MAAM,YAAY,UAAU,GAAG;AAChD,SAAO,EAAE,WAAW,SAAS,WAAW,SAAS;AACnD;AAEA,eAAe,YACb,UACA,KACyB;AACzB,UAAQ,SAAS,UAAU;AAAA,IACzB,KAAK;AACH,aAAO,iBAAiB,SAAS,QAAQ;AAAA,IAC3C,KAAK;AACH,aAAO,kBAAkB,SAAS,QAAQ;AAAA,IAC5C,KAAK;AACH,aAAO,gBAAgB,GAAG;AAAA,EAC9B;AACF;AAEA,eAAe,OAAO,UAAoC;AACxD,MAAI;AACF,UAAM,OAAO,QAAQ;AACrB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AKlFO,SAAS,iBAAiB,UAAqC;AACpE,QAAM,OAAO,oBAAI,IAAyB;AAC1C,QAAM,eAA8B,CAAC;AAErC,aAAW,SAAS,SAAS,UAAU;AACrC,UAAM,WAAW,KAAK,IAAI,MAAM,IAAI;AACpC,QAAI,UAAU;AACZ,UAAI,SAAS,IAAI,MAAM,OAAO,GAAG;AAC/B;AAAA,MACF;AACA,eAAS,IAAI,MAAM,OAAO;AAAA,IAC5B,OAAO;AACL,WAAK,IAAI,MAAM,MAAM,oBAAI,IAAI,CAAC,MAAM,OAAO,CAAC,CAAC;AAAA,IAC/C;AACA,iBAAa,KAAK,EAAE,MAAM,MAAM,MAAM,SAAS,MAAM,QAAQ,CAAC;AAAA,EAChE;AAEA,eAAa,KAAK,CAAC,GAAG,MAAM;AAC1B,QAAI,EAAE,SAAS,EAAE,MAAM;AACrB,aAAO,gBAAgB,EAAE,SAAS,EAAE,OAAO;AAAA,IAC7C;AACA,WAAO,EAAE,OAAO,EAAE,OAAO,KAAK;AAAA,EAChC,CAAC;AAED,QAAM,iBAA2B,CAAC;AAClC,aAAW,CAAC,MAAM,QAAQ,KAAK,MAAM;AACnC,QAAI,SAAS,OAAO,GAAG;AACrB,qBAAe,KAAK,IAAI;AAAA,IAC1B;AAAA,EACF;AAEA,SAAO;AAAA,IACL,SAAS,EAAE,WAAW,SAAS,WAAW,UAAU,aAAa;AAAA,IACjE,OAAO;AAAA,MACL,aAAa,KAAK;AAAA,MAClB;AAAA,MACA,cAAc,SAAS,SAAS;AAAA,IAClC;AAAA,EACF;AACF;AAEO,SAAS,gBAAgB,GAAW,GAAmB;AAC5D,MAAI,MAAM,GAAG;AACX,WAAO;AAAA,EACT;AAEA,QAAM,CAAC,OAAO,IAAI,IAAI,gBAAgB,CAAC;AACvC,QAAM,CAAC,OAAO,IAAI,IAAI,gBAAgB,CAAC;AAEvC,QAAM,UAAU,gBAAgB,MAAM,MAAM,GAAG,GAAG,MAAM,MAAM,GAAG,CAAC;AAClE,MAAI,YAAY,GAAG;AACjB,WAAO;AAAA,EACT;AAEA,MAAI,SAAS,QAAQ,SAAS,MAAM;AAClC,WAAO;AAAA,EACT;AACA,MAAI,SAAS,MAAM;AACjB,WAAO;AAAA,EACT;AACA,MAAI,SAAS,MAAM;AACjB,WAAO;AAAA,EACT;AACA,SAAO,gBAAgB,KAAK,MAAM,GAAG,GAAG,KAAK,MAAM,GAAG,CAAC;AACzD;AAEA,SAAS,gBAAgB,SAA0C;AACjE,QAAM,UAAU,QAAQ,QAAQ,UAAU,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAC1D,QAAM,YAAY,QAAQ,QAAQ,GAAG;AACrC,MAAI,cAAc,IAAI;AACpB,WAAO,CAAC,SAAS,IAAI;AAAA,EACvB;AACA,SAAO,CAAC,QAAQ,MAAM,GAAG,SAAS,GAAG,QAAQ,MAAM,YAAY,CAAC,CAAC;AACnE;AAEA,SAAS,gBAAgB,GAAa,GAAqB;AACzD,QAAM,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM;AACvC,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,UAAM,QAAQ,EAAE,CAAC;AACjB,UAAM,QAAQ,EAAE,CAAC;AACjB,QAAI,UAAU,QAAW;AACvB,aAAO;AAAA,IACT;AACA,QAAI,UAAU,QAAW;AACvB,aAAO;AAAA,IACT;AACA,UAAM,OAAO,QAAQ,KAAK,KAAK;AAC/B,UAAM,OAAO,QAAQ,KAAK,KAAK;AAC/B,QAAI,QAAQ,MAAM;AAChB,YAAM,OAAO,OAAO,KAAK,IAAI,OAAO,KAAK;AACzC,UAAI,SAAS,GAAG;AACd,eAAO,OAAO,IAAI,KAAK;AAAA,MACzB;AACA;AAAA,IACF;AACA,QAAI,MAAM;AACR,aAAO;AAAA,IACT;AACA,QAAI,MAAM;AACR,aAAO;AAAA,IACT;AACA,QAAI,QAAQ,OAAO;AACjB,aAAO;AAAA,IACT;AACA,QAAI,QAAQ,OAAO;AACjB,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;;;ACjIO,IAAM,mBAAmB;AACzB,IAAM,qBAAqB;AAE3B,SAAS,iBAAiB,MAAc,UAAkC;AAC/E,QAAM,UAAU,KAAK,QAAQ,OAAO,EAAE;AACtC,SAAO,aAAa,UAAa,aAAa,QAAQ,SAAS,SAAS,IACpE,GAAG,OAAO,IAAI,mBAAmB,QAAQ,CAAC,KAC1C;AACN;AASO,SAAS,cAAc,UAAkB,UAA0B;AACxE,QAAM,SAAS,IAAI,IAAI,QAAQ,EAAE;AACjC,SAAO,GAAG,MAAM,uBAAuB,mBAAmB,QAAQ,CAAC;AACrE;AAEA,eAAsB,aACpB,QACA,SACgC;AAChC,QAAM,MAAM,iBAAiB,OAAO,UAAU,OAAO,QAAQ;AAC7D,QAAM,YAAY,OAAO;AAEzB,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,MAAM,KAAK;AAAA,MAC1B,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,QAAQ;AAAA,QACR,cAAc;AAAA,MAChB;AAAA,MACA,MAAM,KAAK,UAAU,OAAO;AAAA,MAC5B,QAAQ,YAAY,QAAQ,SAAS;AAAA,IACvC,CAAC;AAAA,EACH,SAAS,OAAO;AACd,QAAI,eAAe,KAAK,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,yBAAyB,GAAG,oBAAoB,SAAS;AAAA,QACzD;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,UAAM,IAAI;AAAA,MACR,iCAAiC,GAAG;AAAA,MACpC;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAO,MAAM,SAAS,KAAK;AACjC,MAAI,OAAqC;AACzC,MAAI;AACF,WAAO,KAAK,SAAS,IAAK,KAAK,MAAM,IAAI,IAA8B;AAAA,EACzE,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,MAAI,SAAS,WAAW,KAAK;AAC3B,UAAM,IAAI;AAAA,MACR,MAAM,SAAS;AAAA,MACf;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAS,WAAW,KAAK;AAC3B,UAAM,IAAI;AAAA,MACR,MAAM,WAAW;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAS,SAAS,OAAO,SAAS,UAAU,KAAK;AACnD,UAAM,IAAI;AAAA,MACR,uBAAuB,SAAS,MAAM,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAS,MAAM;AACjB,UAAM,IAAI,gBAAgB,0CAA0C,cAAc;AAAA,EACpF;AAEA,SAAO;AACT;AAEA,SAAS,eAAe,OAAyB;AAC/C,MAAI,iBAAiB,OAAO;AAC1B,WAAO,MAAM,SAAS,kBAAkB,MAAM,SAAS;AAAA,EACzD;AACA,SAAO;AACT;;;ACpGA,SAAS,YAAAC,WAAU,iBAAiB;AACpC,OAAOC,WAAU;AAIjB,IAAM,kBAAkB;AAqBxB,eAAsB,cAAc,SAAgD;AAClF,QAAM,WAAW,MAAM,eAAe,QAAQ,GAAG;AACjD,QAAM,UAAU,QAAQ;AAExB,QAAM,WACJ,QAAQ,eACR,QAAQ,YACR,SAAS,YACT;AAEF,QAAM,WACJ,QAAQ,eACR,QAAQ,YACR,SAAS,YACT;AAEF,QAAM,YAAY,QAAQ,aAAa,SAAS,aAAa;AAE7D,MAAI,aAAa,QAAQ,SAAS,SAAS,KAAK,CAAC,OAAO,QAAQ,GAAG;AACjE,UAAM,IAAI;AAAA,MACR,cAAc,QAAQ;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,QAAQ,oBAAoB,aAAa,QAAQ,SAAS,WAAW,IAAI;AAC3E,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU,aAAa,QAAQ,SAAS,WAAW,IAAI,OAAO;AAAA,IAC9D;AAAA,IACA;AAAA,EACF;AACF;AAEA,eAAsB,gBAAgB,KAAa,QAAqC;AACtF,QAAM,SAASC,MAAK,KAAK,KAAK,eAAe;AAC7C,QAAM,UAAU,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI;AAClD,QAAM,UAAU,QAAQ,SAAS,MAAM;AACvC,SAAO;AACT;AAMA,eAAsB,gBAAgB,KAAa,UAAmC;AACpF,QAAM,WAAW,MAAM,eAAe,GAAG;AACzC,SAAO,gBAAgB,KAAK,EAAE,GAAG,UAAU,SAAS,CAAC;AACvD;AAEA,eAAe,eAAe,KAAkC;AAC9D,QAAM,SAASA,MAAK,KAAK,KAAK,eAAe;AAC7C,MAAI;AACJ,MAAI;AACF,UAAM,MAAMC,UAAS,QAAQ,MAAM;AAAA,EACrC,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,UAAU;AACpD,aAAO,CAAC;AAAA,IACV;AACA,UAAM,IAAI;AAAA,MACR,kBAAkB,MAAM,KAAM,IAAc,OAAO;AAAA,MACnD;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACF,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,eAAe,MAAM;AAAA,MACrB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,UAAsB;AAC7B,QAAM,aAAa,QAAQ,IAAI;AAC/B,MAAI;AACJ,MAAI,eAAe,UAAa,WAAW,SAAS,GAAG;AACrD,UAAM,SAAS,OAAO,UAAU;AAChC,QAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,GAAG;AAC3C,YAAM,IAAI;AAAA,QACR,yDAAyD,UAAU;AAAA,QACnE;AAAA,MACF;AAAA,IACF;AACA,gBAAY;AAAA,EACd;AACA,SAAO;AAAA,IACL,UAAU,QAAQ,IAAI,wBAAwB;AAAA,IAC9C,UAAU,QAAQ,IAAI,uBAAuB;AAAA,IAC7C;AAAA,EACF;AACF;AAEA,SAAS,OAAO,OAAwB;AACtC,SAAO,kEAAkE,KAAK,KAAK;AACrF;;;AClGA,eAAsB,cACpB,UAAgC,CAAC,GACH;AAC9B,QAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;AACvC,QAAM,SAAS,QAAQ,UAAW,MAAM,cAAc,EAAE,IAAI,CAAC;AAC7D,QAAM,WAAW,MAAM,aAAa,GAAG;AACvC,QAAM,EAAE,SAAS,MAAM,IAAI,iBAAiB,QAAQ;AACpD,QAAM,WAAW,MAAM,aAAa,QAAQ,OAAO;AAInD,MAAI,OAAO,aAAa,QAAQ,SAAS,SAAS,UAAa,SAAS,KAAK,SAAS,GAAG;AACvF,UAAM,gBAAgB,KAAK,SAAS,IAAI;AAAA,EAC1C;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,gBAAgB,MAAM;AAAA,IACtB,aAAa,MAAM;AAAA,IACnB,cAAc,MAAM;AAAA,EACtB;AACF;","names":["path","readFile","path","path","readFile","readFile","readFile","path","readFile","path","path","readFile"]} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c249b61..fbd5ad7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@patchstack/connect", - "version": "0.2.0", + "version": "0.2.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@patchstack/connect", - "version": "0.2.0", + "version": "0.2.4", "license": "MIT", "bin": { "patchstack-connect": "dist/cli.js" @@ -1865,6 +1865,7 @@ "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", diff --git a/src/parsers/index.ts b/src/parsers/index.ts index 317a3bd..d19c020 100644 --- a/src/parsers/index.ts +++ b/src/parsers/index.ts @@ -3,6 +3,7 @@ import path from 'node:path'; import { PatchstackError, type Manifest, type PackageEntry } from '../types.js'; import { parseNpmLockfile } from './npm.js'; import { walkNodeModules } from './node_modules.js'; +import { parsePnpmLockfile } from './pnpm.js'; type LockfileFilename = | 'package-lock.json' @@ -11,7 +12,7 @@ type LockfileFilename = | 'yarn.lock' | 'pnpm-lock.yaml'; -type DetectionStrategy = 'npm-lockfile' | 'node-modules-walk'; +type DetectionStrategy = 'npm-lockfile' | 'node-modules-walk' | 'pnpm-lockfile'; interface DetectedLockfile { ecosystem: 'npm'; @@ -51,6 +52,16 @@ export async function detectLockfile(cwd: string): Promise { }; } + const pnpmLock = path.join(cwd, 'pnpm-lock.yaml'); + if (await exists(pnpmLock)) { + return { + ecosystem: 'npm', + filePath: pnpmLock, + filename: 'pnpm-lock.yaml', + strategy: 'pnpm-lockfile', + }; + } + const yarnLock = path.join(cwd, 'yarn.lock'); if (await exists(yarnLock)) { throw new PatchstackError( @@ -59,14 +70,6 @@ export async function detectLockfile(cwd: string): Promise { ); } - const pnpmLock = path.join(cwd, 'pnpm-lock.yaml'); - if (await exists(pnpmLock)) { - throw new PatchstackError( - 'pnpm-lock.yaml detected but not yet supported. Open an issue at github.com/patchstack/connect to request support.', - 'LOCKFILE_UNSUPPORTED', - ); - } - throw new PatchstackError( `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`, 'LOCKFILE_NOT_FOUND', @@ -86,6 +89,8 @@ async function runStrategy( switch (detected.strategy) { case 'npm-lockfile': return parseNpmLockfile(detected.filePath); + case 'pnpm-lockfile': + return parsePnpmLockfile(detected.filePath); case 'node-modules-walk': return walkNodeModules(cwd); } diff --git a/src/parsers/pnpm.ts b/src/parsers/pnpm.ts new file mode 100644 index 0000000..7196a52 --- /dev/null +++ b/src/parsers/pnpm.ts @@ -0,0 +1,357 @@ +import { readFile } from 'node:fs/promises'; +import { PatchstackError, type PackageEntry } from '../types.js'; + +/** + * Parses pnpm-lock.yaml without pulling in a YAML library. We don't need full + * YAML semantics — only the `packages:` block (the canonical list of every + * installed package) and, for direct-dep marking, the dependency sections under + * `importers:` (v9+) or at the top level (v6-v8 single-project). + * + * Supported pnpm-lock key formats: + * v5: /pkg/1.0.0 /@scope/pkg/1.0.0 + * v6-v8: /pkg@1.0.0 /@scope/pkg@1.0.0 (optional `(peer@x)` suffix) + * v9: pkg@1.0.0 @scope/pkg@1.0.0 (optional `(peer@x)` suffix, may be quoted) + */ +export async function parsePnpmLockfile(lockfilePath: string): Promise { + let raw: string; + try { + raw = await readFile(lockfilePath, 'utf8'); + } catch (cause) { + throw new PatchstackError( + `Could not read lockfile at ${lockfilePath}`, + 'LOCKFILE_NOT_FOUND', + cause, + ); + } + + const lines = raw.split(/\r?\n/); + const directNames = collectDirectDepNames(lines); + const packageKeys = collectPackagesBlockKeys(lines); + + if (packageKeys.length === 0) { + throw new PatchstackError( + `Lockfile at ${lockfilePath} has no "packages" entries`, + 'LOCKFILE_PARSE_ERROR', + ); + } + + const entries: PackageEntry[] = []; + for (const key of packageKeys) { + const parsed = parsePackageKey(key); + if (parsed === null) { + continue; + } + entries.push({ + name: parsed.name, + version: parsed.version, + direct: directNames.has(parsed.name), + }); + } + + return entries; +} + +interface ParsedKey { + name: string; + version: string; +} + +export function parsePackageKey(rawKey: string): ParsedKey | null { + let k = rawKey.trim(); + if (k.length === 0) { + return null; + } + + if ( + (k.startsWith("'") && k.endsWith("'")) || + (k.startsWith('"') && k.endsWith('"')) + ) { + k = k.slice(1, -1); + } + + if (k.startsWith('/')) { + k = k.slice(1); + } + + // Strip peer-dependency suffix used by v6+: `pkg@1.0.0(peer@1.0.0)(other@2)`. + const parenIdx = k.indexOf('('); + if (parenIdx >= 0) { + k = k.slice(0, parenIdx); + } + + // Split off the optional `@scope/` prefix so the remaining "body" contains + // exactly one separator between the bare name and the version. npm forbids + // both `@` and `/` inside the bare-name portion, so the first occurrence of + // either character in the body is unambiguously the name/version separator. + let scopePrefix = ''; + let body = k; + if (k.startsWith('@')) { + const firstSlash = k.indexOf('/'); + if (firstSlash <= 0) { + return null; + } + scopePrefix = k.slice(0, firstSlash + 1); + body = k.slice(firstSlash + 1); + } + + const slashIdx = body.indexOf('/'); + const atIdx = body.indexOf('@'); + + let sepIdx: number; + if (slashIdx < 0 && atIdx < 0) { + return null; + } else if (slashIdx < 0) { + sepIdx = atIdx; + } else if (atIdx < 0) { + sepIdx = slashIdx; + } else { + sepIdx = Math.min(slashIdx, atIdx); + } + + const name = scopePrefix + body.slice(0, sepIdx); + let version = body.slice(sepIdx + 1); + + // v5 peer suffix uses `_`: `1.0.0_react@18.2.0`. + const underscoreIdx = version.indexOf('_'); + if (underscoreIdx >= 0) { + version = version.slice(0, underscoreIdx); + } + + if (name.length === 0 || version.length === 0) { + return null; + } + + return { name, version }; +} + +function indentOf(line: string): number { + let i = 0; + while (i < line.length && line[i] === ' ') { + i++; + } + return i; +} + +function isBlankOrComment(line: string): boolean { + const trimmed = line.trim(); + return trimmed.length === 0 || trimmed.startsWith('#'); +} + +/** + * Collects keys directly nested under the top-level `packages:` block. Stops + * when another top-level key (e.g. `snapshots:`) appears. + */ +function collectPackagesBlockKeys(lines: string[]): string[] { + const keys: string[] = []; + let inBlock = false; + let childIndent: number | null = null; + + for (const line of lines) { + if (isBlankOrComment(line)) { + continue; + } + + const indent = indentOf(line); + + if (!inBlock) { + if (indent === 0 && line.trim() === 'packages:') { + inBlock = true; + } + continue; + } + + if (indent === 0) { + break; + } + + if (childIndent === null) { + childIndent = indent; + } + + if (indent !== childIndent) { + continue; + } + + const content = line.slice(indent); + if (!content.endsWith(':')) { + continue; + } + + keys.push(content.slice(0, -1)); + } + + return keys; +} + +/** + * Collects direct dependency names. Looks in two places, since the format + * differs across lockfile versions and workspaces: + * + * 1. `importers:` > `:` > `{dependencies,devDependencies,optionalDependencies}:` + * (v9 always, v6-v8 in workspaces) + * 2. Top-level `{dependencies,devDependencies,optionalDependencies}:` + * (v6-v8 single-project lockfiles) + * + * Direct-dep marking is best-effort: a name absent from both is simply left as + * not-direct, matching the npm parser's behaviour for transitive packages. + */ +function collectDirectDepNames(lines: string[]): Set { + const names = new Set(); + collectFromImporters(lines, names); + for (const section of ['dependencies', 'devDependencies', 'optionalDependencies']) { + collectFromTopLevelSection(lines, section, names); + } + return names; +} + +const DEP_SECTIONS = new Set([ + 'dependencies', + 'devDependencies', + 'optionalDependencies', +]); + +function collectFromImporters(lines: string[], out: Set): void { + let inImporters = false; + let importerIndent: number | null = null; + let inDepSection = false; + let depSectionIndent: number | null = null; + let leafIndent: number | null = null; + + for (const line of lines) { + if (isBlankOrComment(line)) { + continue; + } + const indent = indentOf(line); + const trimmed = line.trim(); + + if (!inImporters) { + if (indent === 0 && trimmed === 'importers:') { + inImporters = true; + } + continue; + } + + if (indent === 0) { + break; + } + + // Track the indent at which importer entries (e.g. `.:`, `packages/foo:`) sit. + if (importerIndent === null) { + importerIndent = indent; + } + + if (indent === importerIndent) { + inDepSection = false; + depSectionIndent = null; + leafIndent = null; + continue; + } + + if (!inDepSection) { + const key = stripTrailingColon(trimmed); + if (key !== null && DEP_SECTIONS.has(key)) { + inDepSection = true; + depSectionIndent = indent; + } + continue; + } + + if (depSectionIndent !== null && indent <= depSectionIndent) { + // Reset — we've exited the dep section. Reconsider this line. + inDepSection = false; + depSectionIndent = null; + leafIndent = null; + const key = stripTrailingColon(trimmed); + if (key !== null && DEP_SECTIONS.has(key)) { + inDepSection = true; + depSectionIndent = indent; + } + continue; + } + + if (leafIndent === null) { + leafIndent = indent; + } + + if (indent !== leafIndent) { + continue; + } + + const name = extractLeafName(trimmed); + if (name !== null) { + out.add(name); + } + } +} + +function collectFromTopLevelSection( + lines: string[], + section: string, + out: Set, +): void { + let inSection = false; + let leafIndent: number | null = null; + + for (const line of lines) { + if (isBlankOrComment(line)) { + continue; + } + const indent = indentOf(line); + const trimmed = line.trim(); + + if (!inSection) { + if (indent === 0 && trimmed === `${section}:`) { + inSection = true; + } + continue; + } + + if (indent === 0) { + break; + } + + if (leafIndent === null) { + leafIndent = indent; + } + + if (indent !== leafIndent) { + continue; + } + + const name = extractLeafName(trimmed); + if (name !== null) { + out.add(name); + } + } +} + +function stripTrailingColon(s: string): string | null { + if (!s.endsWith(':')) { + return null; + } + return s.slice(0, -1).trim(); +} + +/** + * Extracts the package name from a dependency-section leaf, handling both the + * short form (`pkg: 1.0.0`) and the v9 expanded form (`pkg:` followed by + * `specifier:` / `version:` children). Quoted scoped names like `'@scope/pkg':` + * are unquoted. + */ +function extractLeafName(trimmed: string): string | null { + const colonIdx = trimmed.indexOf(':'); + if (colonIdx < 0) { + return null; + } + let name = trimmed.slice(0, colonIdx).trim(); + if (name.length === 0) { + return null; + } + if ( + (name.startsWith("'") && name.endsWith("'")) || + (name.startsWith('"') && name.endsWith('"')) + ) { + name = name.slice(1, -1); + } + return name.length > 0 ? name : null; +} diff --git a/tests/fixtures/pnpm-lock-v6.yaml b/tests/fixtures/pnpm-lock-v6.yaml new file mode 100644 index 0000000..c1072af --- /dev/null +++ b/tests/fixtures/pnpm-lock-v6.yaml @@ -0,0 +1,43 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + axios: + specifier: ^1.6.0 + version: 1.6.0 + '@scope/pkg': + specifier: 2.1.0 + version: 2.1.0 + +devDependencies: + vitest: + specifier: ^3.0.0 + version: 3.0.0 + +packages: + + /@scope/pkg@2.1.0: + resolution: {integrity: sha512-fake==} + + /axios@1.6.0: + resolution: {integrity: sha512-fake==} + dev: false + + /follow-redirects@1.15.3: + resolution: {integrity: sha512-fake==} + dev: false + + /react@18.2.0: + resolution: {integrity: sha512-fake==} + + /react-dom@18.2.0(react@18.2.0): + resolution: {integrity: sha512-fake==} + peerDependencies: + react: ^18.2.0 + + /vitest@3.0.0: + resolution: {integrity: sha512-fake==} + dev: true diff --git a/tests/fixtures/pnpm-lock-v9.yaml b/tests/fixtures/pnpm-lock-v9.yaml new file mode 100644 index 0000000..ba0b585 --- /dev/null +++ b/tests/fixtures/pnpm-lock-v9.yaml @@ -0,0 +1,64 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + axios: + specifier: ^1.6.0 + version: 1.6.0 + '@scope/pkg': + specifier: 2.1.0 + version: 2.1.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + devDependencies: + vitest: + specifier: ^3.0.0 + version: 3.0.0 + +packages: + + '@scope/pkg@2.1.0': + resolution: {integrity: sha512-fake==} + engines: {node: '>=18'} + + axios@1.6.0: + resolution: {integrity: sha512-fake==} + + follow-redirects@1.15.3: + resolution: {integrity: sha512-fake==} + + react@18.2.0: + resolution: {integrity: sha512-fake==} + + 'react-dom@18.2.0(react@18.2.0)': + resolution: {integrity: sha512-fake==} + peerDependencies: + react: ^18.2.0 + + vitest@3.0.0: + resolution: {integrity: sha512-fake==} + +snapshots: + + axios@1.6.0: + dependencies: + follow-redirects: 1.15.3 + + follow-redirects@1.15.3: {} + + react@18.2.0: {} + + 'react-dom@18.2.0(react@18.2.0)': + dependencies: + react: 18.2.0 + + vitest@3.0.0: {} + + '@scope/pkg@2.1.0': {} diff --git a/tests/pnpm.test.ts b/tests/pnpm.test.ts new file mode 100644 index 0000000..7e9eced --- /dev/null +++ b/tests/pnpm.test.ts @@ -0,0 +1,194 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { copyFile, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { parsePackageKey, parsePnpmLockfile } from '../src/parsers/pnpm.js'; +import { detectLockfile, scanLockfile } from '../src/parsers/index.js'; +import { PatchstackError } from '../src/types.js'; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const fixtures = path.join(here, 'fixtures'); +const v9Fixture = path.join(fixtures, 'pnpm-lock-v9.yaml'); +const v6Fixture = path.join(fixtures, 'pnpm-lock-v6.yaml'); + +describe('parsePackageKey', () => { + it('parses plain v9 keys', () => { + expect(parsePackageKey('axios@1.6.0')).toEqual({ name: 'axios', version: '1.6.0' }); + }); + + it('parses scoped v9 keys', () => { + expect(parsePackageKey('@scope/pkg@2.1.0')).toEqual({ + name: '@scope/pkg', + version: '2.1.0', + }); + }); + + it('strips v6 leading slash', () => { + expect(parsePackageKey('/axios@1.6.0')).toEqual({ name: 'axios', version: '1.6.0' }); + expect(parsePackageKey('/@scope/pkg@2.1.0')).toEqual({ + name: '@scope/pkg', + version: '2.1.0', + }); + }); + + it('strips v6+ peer suffix', () => { + expect(parsePackageKey('react-dom@18.2.0(react@18.2.0)')).toEqual({ + name: 'react-dom', + version: '18.2.0', + }); + expect(parsePackageKey("'react-dom@18.2.0(react@18.2.0)(other@2)'")).toEqual({ + name: 'react-dom', + version: '18.2.0', + }); + }); + + it('parses v5 slash-separated keys', () => { + expect(parsePackageKey('/axios/1.6.0')).toEqual({ name: 'axios', version: '1.6.0' }); + expect(parsePackageKey('/@scope/pkg/2.1.0')).toEqual({ + name: '@scope/pkg', + version: '2.1.0', + }); + }); + + it('strips v5 underscore peer suffix', () => { + expect(parsePackageKey('/react-dom/18.2.0_react@18.2.0')).toEqual({ + name: 'react-dom', + version: '18.2.0', + }); + }); + + it('returns null for unparseable input', () => { + expect(parsePackageKey('')).toBeNull(); + expect(parsePackageKey('bare-name')).toBeNull(); + }); +}); + +describe('parsePnpmLockfile (v9)', () => { + it('extracts every package from the packages: block', async () => { + const entries = await parsePnpmLockfile(v9Fixture); + const names = entries.map((e) => `${e.name}@${e.version}`).sort(); + expect(names).toEqual( + [ + '@scope/pkg@2.1.0', + 'axios@1.6.0', + 'follow-redirects@1.15.3', + 'react@18.2.0', + 'react-dom@18.2.0', + 'vitest@3.0.0', + ].sort(), + ); + }); + + it('marks importers.dependencies and devDependencies as direct', async () => { + const entries = await parsePnpmLockfile(v9Fixture); + const byName = new Map(entries.map((e) => [e.name, e])); + + expect(byName.get('axios')?.direct).toBe(true); + expect(byName.get('@scope/pkg')?.direct).toBe(true); + expect(byName.get('react-dom')?.direct).toBe(true); + expect(byName.get('vitest')?.direct).toBe(true); + expect(byName.get('follow-redirects')?.direct).toBe(false); + expect(byName.get('react')?.direct).toBe(false); + }); + + it('deduplicates peer-suffixed keys to a single entry', async () => { + const entries = await parsePnpmLockfile(v9Fixture); + const reactDom = entries.filter((e) => e.name === 'react-dom'); + expect(reactDom).toHaveLength(1); + expect(reactDom[0]?.version).toBe('18.2.0'); + }); +}); + +describe('parsePnpmLockfile (v6)', () => { + it('parses leading-slash keys and top-level direct deps', async () => { + const entries = await parsePnpmLockfile(v6Fixture); + const names = entries.map((e) => `${e.name}@${e.version}`).sort(); + expect(names).toEqual( + [ + '@scope/pkg@2.1.0', + 'axios@1.6.0', + 'follow-redirects@1.15.3', + 'react@18.2.0', + 'react-dom@18.2.0', + 'vitest@3.0.0', + ].sort(), + ); + + const byName = new Map(entries.map((e) => [e.name, e])); + expect(byName.get('axios')?.direct).toBe(true); + expect(byName.get('vitest')?.direct).toBe(true); + expect(byName.get('follow-redirects')?.direct).toBe(false); + }); +}); + +describe('parsePnpmLockfile errors', () => { + it('throws LOCKFILE_NOT_FOUND when the file is missing', async () => { + await expect(parsePnpmLockfile('/nonexistent/pnpm-lock.yaml')).rejects.toBeInstanceOf( + PatchstackError, + ); + }); + + it('throws LOCKFILE_PARSE_ERROR when the packages block is empty', async () => { + const dir = await mkdtemp(path.join(tmpdir(), 'patchstack-connect-pnpm-empty-')); + try { + const file = path.join(dir, 'pnpm-lock.yaml'); + await writeFile(file, "lockfileVersion: '9.0'\n"); + await expect(parsePnpmLockfile(file)).rejects.toMatchObject({ + code: 'LOCKFILE_PARSE_ERROR', + }); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); +}); + +describe('detectLockfile for pnpm', () => { + let cwd: string; + + beforeEach(async () => { + cwd = await mkdtemp(path.join(tmpdir(), 'patchstack-connect-pnpm-detect-')); + }); + + afterEach(async () => { + await rm(cwd, { recursive: true, force: true }); + }); + + it('detects pnpm-lock.yaml and routes to the pnpm parser', async () => { + await copyFile(v9Fixture, path.join(cwd, 'pnpm-lock.yaml')); + const detected = await detectLockfile(cwd); + expect(detected.filename).toBe('pnpm-lock.yaml'); + expect(detected.strategy).toBe('pnpm-lockfile'); + expect(detected.ecosystem).toBe('npm'); + }); + + it('prefers package-lock.json over pnpm-lock.yaml when both exist', async () => { + await copyFile( + path.join(fixtures, 'package-lock-v3.json'), + path.join(cwd, 'package-lock.json'), + ); + await copyFile(v9Fixture, path.join(cwd, 'pnpm-lock.yaml')); + const detected = await detectLockfile(cwd); + expect(detected.filename).toBe('package-lock.json'); + }); +}); + +describe('scanLockfile for pnpm', () => { + let cwd: string; + + beforeEach(async () => { + cwd = await mkdtemp(path.join(tmpdir(), 'patchstack-connect-pnpm-scan-')); + await copyFile(v9Fixture, path.join(cwd, 'pnpm-lock.yaml')); + }); + + afterEach(async () => { + await rm(cwd, { recursive: true, force: true }); + }); + + it('returns an npm-ecosystem manifest', async () => { + const manifest = await scanLockfile(cwd); + expect(manifest.ecosystem).toBe('npm'); + expect(manifest.packages.length).toBeGreaterThan(0); + expect(manifest.packages.find((p) => p.name === 'axios')).toBeDefined(); + }); +});