From d042e10f118459b26bab81df0d8473dcc6c9bc6b Mon Sep 17 00:00:00 2001 From: Elliot Taylor Date: Tue, 19 May 2026 20:53:26 +0100 Subject: [PATCH] Add yarn.lock parser (yarn classic v1 and berry v2+) Yarn is the last of the four major Node package managers we didn't yet read. This adds a zero-dep parser that handles both yarn classic (`version "x"`) and yarn berry (`version: x`, `__metadata` header, `npm:` descriptors) by walking the shared block structure and only branching on the value syntax. Yarn's lockfile, unlike pnpm v9's, doesn't record an importer manifest, so direct-vs-transitive marking is cross-referenced against the sibling `package.json`. Absent or unreadable package.json leaves all entries unmarked rather than failing the scan. Detection priority is npm > pnpm > yarn > bun, so a yarn migration in progress with multiple lockfiles still resolves deterministically and matches what the package manager itself would prefer. Co-Authored-By: Claude Opus 4.7 --- README.md | 2 +- dist/cli.js | 225 ++++++++++++++-- dist/cli.js.map | 2 +- dist/index.cjs | 231 ++++++++++++++-- dist/index.cjs.map | 2 +- dist/index.d.cts | 2 +- dist/index.d.ts | 2 +- dist/index.js | 225 ++++++++++++++-- dist/index.js.map | 2 +- src/parsers/index.ts | 19 +- src/parsers/yarn.ts | 252 ++++++++++++++++++ .../fixtures/yarn-berry-project/package.json | 14 + tests/fixtures/yarn-berry-project/yarn.lock | 52 ++++ tests/fixtures/yarn-v1-project/package.json | 14 + tests/fixtures/yarn-v1-project/yarn.lock | 42 +++ tests/yarn.test.ts | 239 +++++++++++++++++ 16 files changed, 1266 insertions(+), 59 deletions(-) create mode 100644 src/parsers/yarn.ts create mode 100644 tests/fixtures/yarn-berry-project/package.json create mode 100644 tests/fixtures/yarn-berry-project/yarn.lock create mode 100644 tests/fixtures/yarn-v1-project/package.json create mode 100644 tests/fixtures/yarn-v1-project/yarn.lock create mode 100644 tests/yarn.test.ts diff --git a/README.md b/README.md index 9c094ce..41d423e 100644 --- a/README.md +++ b/README.md @@ -115,9 +115,9 @@ That's the entire payload. No source code, no environment variables, no file pat - ✅ `package-lock.json` (npm v6 / v2 / v3) — parsed directly - ✅ `pnpm-lock.yaml` (pnpm v5 / v6 / v7 / v8 / v9) — parsed directly +- ✅ `yarn.lock` (yarn classic v1 and yarn berry v2+) — parsed directly - ✅ `bun.lockb` (binary) — package list resolved by walking `node_modules/` - ✅ `bun.lock` (text) — same fallback; direct parsing coming -- ❌ `yarn.lock` — 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 0342fa5..720b51a 100755 --- a/dist/cli.js +++ b/dist/cli.js @@ -2,7 +2,7 @@ // src/parsers/index.ts import { access } from "fs/promises"; -import path3 from "path"; +import path4 from "path"; // src/types.ts var PatchstackError = class extends Error { @@ -461,9 +461,200 @@ function extractLeafName(trimmed) { return name.length > 0 ? name : null; } +// src/parsers/yarn.ts +import { readFile as readFile4 } from "fs/promises"; +import path3 from "path"; +async function parseYarnLockfile(lockfilePath) { + let raw; + try { + raw = await readFile4(lockfilePath, "utf8"); + } catch (cause) { + throw new PatchstackError( + `Could not read lockfile at ${lockfilePath}`, + "LOCKFILE_NOT_FOUND", + cause + ); + } + const blocks = parseBlocks(raw); + if (blocks.length === 0) { + throw new PatchstackError( + `Lockfile at ${lockfilePath} contains no package entries`, + "LOCKFILE_PARSE_ERROR" + ); + } + const directNames = await readDirectDepNames(path3.dirname(lockfilePath)); + const entries = []; + const seen = /* @__PURE__ */ new Set(); + for (const block of blocks) { + if (block.version.length === 0 || block.names.size === 0) { + continue; + } + for (const name of block.names) { + const dedupKey = `${name}@${block.version}`; + if (seen.has(dedupKey)) { + continue; + } + seen.add(dedupKey); + entries.push({ + name, + version: block.version, + direct: directNames.has(name) + }); + } + } + return entries; +} +function parseBlocks(raw) { + const lines = raw.split(/\r?\n/); + const blocks = []; + let current = null; + const finalize = () => { + if (current !== null && current.version.length > 0 && current.names.size > 0) { + blocks.push(current); + } + current = null; + }; + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed.length === 0 || trimmed.startsWith("#")) { + continue; + } + const indent = countLeadingSpaces(line); + if (indent === 0) { + finalize(); + if (!trimmed.endsWith(":")) { + continue; + } + const keyLine = trimmed.slice(0, -1); + const names = /* @__PURE__ */ new Set(); + for (const spec of splitDescriptors(keyLine)) { + const name = extractName(spec); + if (name !== null) { + names.add(name); + } + } + current = { names, version: "" }; + continue; + } + if (current === null) { + continue; + } + const version = parseVersionField(trimmed); + if (version !== null) { + current.version = version; + } + } + finalize(); + return blocks; +} +function countLeadingSpaces(line) { + let i = 0; + while (i < line.length && line[i] === " ") { + i++; + } + return i; +} +function splitDescriptors(keyLine) { + const parts = []; + let current = ""; + let quote = null; + for (let i = 0; i < keyLine.length; i++) { + const c = keyLine[i]; + if (quote !== null) { + current += c; + if (c === quote) { + quote = null; + } + continue; + } + if (c === '"' || c === "'") { + quote = c; + current += c; + continue; + } + if (c === ",") { + const piece = current.trim(); + if (piece.length > 0) { + parts.push(piece); + } + current = ""; + continue; + } + current += c; + } + const tail = current.trim(); + if (tail.length > 0) { + parts.push(tail); + } + return parts; +} +function extractName(rawSpec) { + let s = rawSpec.trim(); + if (s.length === 0) { + return null; + } + if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) { + s = s.slice(1, -1); + } + const atIdx = s.lastIndexOf("@"); + if (atIdx <= 0) { + return null; + } + const name = s.slice(0, atIdx); + return name.length > 0 ? name : null; +} +function parseVersionField(content) { + if (!content.startsWith("version")) { + return null; + } + const after = content.slice("version".length); + const firstChar = after.charAt(0); + if (firstChar !== " " && firstChar !== " " && firstChar !== ":") { + return null; + } + let rest = firstChar === ":" ? after.slice(1) : after; + rest = rest.trim(); + if (rest.length === 0) { + return null; + } + if (rest.startsWith('"') && rest.endsWith('"') || rest.startsWith("'") && rest.endsWith("'")) { + rest = rest.slice(1, -1); + } + return rest.length > 0 ? rest : null; +} +async function readDirectDepNames(cwd) { + const names = /* @__PURE__ */ new Set(); + let raw; + try { + raw = await readFile4(path3.join(cwd, "package.json"), "utf8"); + } catch { + return names; + } + let parsed; + try { + parsed = JSON.parse(raw); + } catch { + return names; + } + if (typeof parsed !== "object" || parsed === null) { + return names; + } + const obj = parsed; + for (const field of ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"]) { + const section = obj[field]; + if (typeof section !== "object" || section === null) { + continue; + } + for (const name of Object.keys(section)) { + names.add(name); + } + } + return names; +} + // src/parsers/index.ts async function detectLockfile(cwd) { - const npmLock = path3.join(cwd, "package-lock.json"); + const npmLock = path4.join(cwd, "package-lock.json"); if (await exists(npmLock)) { return { ecosystem: "npm", @@ -472,7 +663,7 @@ async function detectLockfile(cwd) { strategy: "npm-lockfile" }; } - const bunLock = path3.join(cwd, "bun.lock"); + const bunLock = path4.join(cwd, "bun.lock"); if (await exists(bunLock)) { return { ecosystem: "npm", @@ -481,7 +672,7 @@ async function detectLockfile(cwd) { strategy: "node-modules-walk" }; } - const bunLockB = path3.join(cwd, "bun.lockb"); + const bunLockB = path4.join(cwd, "bun.lockb"); if (await exists(bunLockB)) { return { ecosystem: "npm", @@ -490,7 +681,7 @@ async function detectLockfile(cwd) { strategy: "node-modules-walk" }; } - const pnpmLock = path3.join(cwd, "pnpm-lock.yaml"); + const pnpmLock = path4.join(cwd, "pnpm-lock.yaml"); if (await exists(pnpmLock)) { return { ecosystem: "npm", @@ -499,12 +690,14 @@ async function detectLockfile(cwd) { strategy: "pnpm-lockfile" }; } - const yarnLock = path3.join(cwd, "yarn.lock"); + const yarnLock = path4.join(cwd, "yarn.lock"); if (await exists(yarnLock)) { - throw new PatchstackError( - "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.", - "LOCKFILE_UNSUPPORTED" - ); + return { + ecosystem: "npm", + filePath: yarnLock, + filename: "yarn.lock", + strategy: "yarn-lockfile" + }; } throw new PatchstackError( `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`, @@ -522,6 +715,8 @@ async function runStrategy(detected, cwd) { return parseNpmLockfile(detected.filePath); case "pnpm-lockfile": return parsePnpmLockfile(detected.filePath); + case "yarn-lockfile": + return parseYarnLockfile(detected.filePath); case "node-modules-walk": return walkNodeModules(cwd); } @@ -715,8 +910,8 @@ function isTimeoutError(cause) { } // src/config.ts -import { readFile as readFile4, writeFile } from "fs/promises"; -import path4 from "path"; +import { readFile as readFile5, writeFile } from "fs/promises"; +import path5 from "path"; var CONFIG_FILENAME = ".patchstackrc.json"; async function resolveConfig(options) { const fromFile = await readConfigFile(options.cwd); @@ -743,7 +938,7 @@ async function resolveConfig(options) { }; } async function writeConfigFile(cwd, config) { - const target = path4.join(cwd, CONFIG_FILENAME); + const target = path5.join(cwd, CONFIG_FILENAME); const content = JSON.stringify(config, null, 2) + "\n"; await writeFile(target, content, "utf8"); return target; @@ -753,10 +948,10 @@ async function persistSiteUuid(cwd, siteUuid) { return writeConfigFile(cwd, { ...existing, siteUuid }); } async function readConfigFile(cwd) { - const target = path4.join(cwd, CONFIG_FILENAME); + const target = path5.join(cwd, CONFIG_FILENAME); let raw; try { - raw = await readFile4(target, "utf8"); + raw = await readFile5(target, "utf8"); } catch (err) { if (err.code === "ENOENT") { return {}; diff --git a/dist/cli.js.map b/dist/cli.js.map index c332efb..b5ec628 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/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 +{"version":3,"sources":["../src/parsers/index.ts","../src/types.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/parsers/pnpm.ts","../src/parsers/yarn.ts","../src/normalize.ts","../src/client.ts","../src/config.ts","../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';\nimport { parseYarnLockfile } from './yarn.js';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy =\n | 'npm-lockfile'\n | 'node-modules-walk'\n | 'pnpm-lockfile'\n | 'yarn-lockfile';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\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 return {\n ecosystem: 'npm',\n filePath: yarnLock,\n filename: 'yarn.lock',\n strategy: 'yarn-lockfile',\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 'yarn-lockfile':\n return parseYarnLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise {\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 { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\n/**\n * Parses yarn.lock (yarn classic v1 and yarn berry v2+) without a YAML\n * dependency. Both generations share the same block structure — a top-level\n * mapping of comma-separated descriptor lists to a block containing a\n * `version` field — so we walk them with the same scanner and only branch on\n * the `version` syntax (`version \"x\"` for v1, `version: x` for berry).\n *\n * Direct vs transitive can't be derived from yarn.lock alone (yarn does not\n * record an importer manifest the way pnpm v9 does), so we cross-reference\n * the sibling `package.json` when present.\n */\nexport async function parseYarnLockfile(lockfilePath: string): Promise {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(\n `Could not read lockfile at ${lockfilePath}`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const blocks = parseBlocks(raw);\n if (blocks.length === 0) {\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} contains no package entries`,\n 'LOCKFILE_PARSE_ERROR',\n );\n }\n\n const directNames = await readDirectDepNames(path.dirname(lockfilePath));\n\n const entries: PackageEntry[] = [];\n const seen = new Set();\n for (const block of blocks) {\n if (block.version.length === 0 || block.names.size === 0) {\n continue;\n }\n for (const name of block.names) {\n const dedupKey = `${name}@${block.version}`;\n if (seen.has(dedupKey)) {\n continue;\n }\n seen.add(dedupKey);\n entries.push({\n name,\n version: block.version,\n direct: directNames.has(name),\n });\n }\n }\n\n return entries;\n}\n\ninterface Block {\n names: Set;\n version: string;\n}\n\nfunction parseBlocks(raw: string): Block[] {\n const lines = raw.split(/\\r?\\n/);\n const blocks: Block[] = [];\n let current: Block | null = null;\n\n const finalize = () => {\n if (current !== null && current.version.length > 0 && current.names.size > 0) {\n blocks.push(current);\n }\n current = null;\n };\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed.length === 0 || trimmed.startsWith('#')) {\n continue;\n }\n\n const indent = countLeadingSpaces(line);\n\n if (indent === 0) {\n finalize();\n if (!trimmed.endsWith(':')) {\n continue;\n }\n // `__metadata:` (yarn berry header) has no `@` in any descriptor and\n // produces an empty names set, so it's naturally skipped on finalize.\n const keyLine = trimmed.slice(0, -1);\n const names = new Set();\n for (const spec of splitDescriptors(keyLine)) {\n const name = extractName(spec);\n if (name !== null) {\n names.add(name);\n }\n }\n current = { names, version: '' };\n continue;\n }\n\n if (current === null) {\n continue;\n }\n\n const version = parseVersionField(trimmed);\n if (version !== null) {\n current.version = version;\n }\n }\n\n finalize();\n return blocks;\n}\n\nfunction countLeadingSpaces(line: string): number {\n let i = 0;\n while (i < line.length && line[i] === ' ') {\n i++;\n }\n return i;\n}\n\n/**\n * Splits a yarn descriptor key list on top-level commas. yarn quotes any\n * descriptor that contains characters needing escaping, so we respect quotes\n * while splitting to avoid breaking on commas inside (rare in practice but\n * cheap to handle).\n */\nexport function splitDescriptors(keyLine: string): string[] {\n const parts: string[] = [];\n let current = '';\n let quote: '\"' | \"'\" | null = null;\n\n for (let i = 0; i < keyLine.length; i++) {\n const c = keyLine[i];\n if (quote !== null) {\n current += c;\n if (c === quote) {\n quote = null;\n }\n continue;\n }\n if (c === '\"' || c === \"'\") {\n quote = c;\n current += c;\n continue;\n }\n if (c === ',') {\n const piece = current.trim();\n if (piece.length > 0) {\n parts.push(piece);\n }\n current = '';\n continue;\n }\n current += c;\n }\n const tail = current.trim();\n if (tail.length > 0) {\n parts.push(tail);\n }\n return parts;\n}\n\n/**\n * Extracts the package name from a yarn descriptor like `axios@^1.6.0`,\n * `\"@scope/pkg@^2.1.0\"`, or `\"@scope/pkg@npm:2.1.0\"`. The descriptor's\n * range portion is discarded — we only need the name, since the resolved\n * version comes from the `version` field of the block.\n */\nexport function extractName(rawSpec: string): string | null {\n let s = rawSpec.trim();\n if (s.length === 0) {\n return null;\n }\n if (\n (s.startsWith('\"') && s.endsWith('\"')) ||\n (s.startsWith(\"'\") && s.endsWith(\"'\"))\n ) {\n s = s.slice(1, -1);\n }\n // Position-0 `@` belongs to a scope, so we want the last `@` after it.\n const atIdx = s.lastIndexOf('@');\n if (atIdx <= 0) {\n return null;\n }\n const name = s.slice(0, atIdx);\n return name.length > 0 ? name : null;\n}\n\nfunction parseVersionField(content: string): string | null {\n if (!content.startsWith('version')) {\n return null;\n }\n const after = content.slice('version'.length);\n // yarn v1: `version \"1.2.3\"` (whitespace then quoted)\n // yarn berry: `version: 1.2.3` or `version: \"1.2.3\"`\n const firstChar = after.charAt(0);\n if (firstChar !== ' ' && firstChar !== '\\t' && firstChar !== ':') {\n return null;\n }\n let rest = firstChar === ':' ? after.slice(1) : after;\n rest = rest.trim();\n if (rest.length === 0) {\n return null;\n }\n if (\n (rest.startsWith('\"') && rest.endsWith('\"')) ||\n (rest.startsWith(\"'\") && rest.endsWith(\"'\"))\n ) {\n rest = rest.slice(1, -1);\n }\n return rest.length > 0 ? rest : null;\n}\n\nasync function readDirectDepNames(cwd: string): Promise> {\n const names = new Set();\n let raw: string;\n try {\n raw = await readFile(path.join(cwd, 'package.json'), 'utf8');\n } catch {\n return names;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return names;\n }\n\n if (typeof parsed !== 'object' || parsed === null) {\n return names;\n }\n const obj = parsed as Record;\n\n for (const field of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) {\n const section = obj[field];\n if (typeof section !== 'object' || section === null) {\n continue;\n }\n for (const name of Object.keys(section)) {\n names.add(name);\n }\n }\n\n return names;\n}\n","import type { Manifest, PackageEntry } from './types.js';\n\nexport interface WirePackage {\n name: string;\n version: string;\n}\n\nexport interface WirePayload {\n ecosystem: Manifest['ecosystem'];\n packages: WirePackage[];\n}\n\nexport interface NormalizeStats {\n uniqueNames: number;\n duplicateNames: string[];\n totalEntries: number;\n}\n\nexport interface NormalizeResult {\n payload: WirePayload;\n stats: NormalizeStats;\n}\n\nexport function buildWirePayload(manifest: Manifest): NormalizeResult {\n const seen = new Map>();\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","readFile","path","readFile","path","path","readFile","path","path","readFile"]} \ No newline at end of file diff --git a/dist/index.cjs b/dist/index.cjs index 201ef3c..7af4344 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -47,8 +47,8 @@ __export(src_exports, { module.exports = __toCommonJS(src_exports); // src/parsers/index.ts -var import_promises4 = require("fs/promises"); -var import_node_path3 = __toESM(require("path"), 1); +var import_promises5 = require("fs/promises"); +var import_node_path4 = __toESM(require("path"), 1); // src/types.ts var PatchstackError = class extends Error { @@ -507,9 +507,200 @@ function extractLeafName(trimmed) { return name.length > 0 ? name : null; } +// src/parsers/yarn.ts +var import_promises4 = require("fs/promises"); +var import_node_path3 = __toESM(require("path"), 1); +async function parseYarnLockfile(lockfilePath) { + let raw; + try { + raw = await (0, import_promises4.readFile)(lockfilePath, "utf8"); + } catch (cause) { + throw new PatchstackError( + `Could not read lockfile at ${lockfilePath}`, + "LOCKFILE_NOT_FOUND", + cause + ); + } + const blocks = parseBlocks(raw); + if (blocks.length === 0) { + throw new PatchstackError( + `Lockfile at ${lockfilePath} contains no package entries`, + "LOCKFILE_PARSE_ERROR" + ); + } + const directNames = await readDirectDepNames(import_node_path3.default.dirname(lockfilePath)); + const entries = []; + const seen = /* @__PURE__ */ new Set(); + for (const block of blocks) { + if (block.version.length === 0 || block.names.size === 0) { + continue; + } + for (const name of block.names) { + const dedupKey = `${name}@${block.version}`; + if (seen.has(dedupKey)) { + continue; + } + seen.add(dedupKey); + entries.push({ + name, + version: block.version, + direct: directNames.has(name) + }); + } + } + return entries; +} +function parseBlocks(raw) { + const lines = raw.split(/\r?\n/); + const blocks = []; + let current = null; + const finalize = () => { + if (current !== null && current.version.length > 0 && current.names.size > 0) { + blocks.push(current); + } + current = null; + }; + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed.length === 0 || trimmed.startsWith("#")) { + continue; + } + const indent = countLeadingSpaces(line); + if (indent === 0) { + finalize(); + if (!trimmed.endsWith(":")) { + continue; + } + const keyLine = trimmed.slice(0, -1); + const names = /* @__PURE__ */ new Set(); + for (const spec of splitDescriptors(keyLine)) { + const name = extractName(spec); + if (name !== null) { + names.add(name); + } + } + current = { names, version: "" }; + continue; + } + if (current === null) { + continue; + } + const version = parseVersionField(trimmed); + if (version !== null) { + current.version = version; + } + } + finalize(); + return blocks; +} +function countLeadingSpaces(line) { + let i = 0; + while (i < line.length && line[i] === " ") { + i++; + } + return i; +} +function splitDescriptors(keyLine) { + const parts = []; + let current = ""; + let quote = null; + for (let i = 0; i < keyLine.length; i++) { + const c = keyLine[i]; + if (quote !== null) { + current += c; + if (c === quote) { + quote = null; + } + continue; + } + if (c === '"' || c === "'") { + quote = c; + current += c; + continue; + } + if (c === ",") { + const piece = current.trim(); + if (piece.length > 0) { + parts.push(piece); + } + current = ""; + continue; + } + current += c; + } + const tail = current.trim(); + if (tail.length > 0) { + parts.push(tail); + } + return parts; +} +function extractName(rawSpec) { + let s = rawSpec.trim(); + if (s.length === 0) { + return null; + } + if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) { + s = s.slice(1, -1); + } + const atIdx = s.lastIndexOf("@"); + if (atIdx <= 0) { + return null; + } + const name = s.slice(0, atIdx); + return name.length > 0 ? name : null; +} +function parseVersionField(content) { + if (!content.startsWith("version")) { + return null; + } + const after = content.slice("version".length); + const firstChar = after.charAt(0); + if (firstChar !== " " && firstChar !== " " && firstChar !== ":") { + return null; + } + let rest = firstChar === ":" ? after.slice(1) : after; + rest = rest.trim(); + if (rest.length === 0) { + return null; + } + if (rest.startsWith('"') && rest.endsWith('"') || rest.startsWith("'") && rest.endsWith("'")) { + rest = rest.slice(1, -1); + } + return rest.length > 0 ? rest : null; +} +async function readDirectDepNames(cwd) { + const names = /* @__PURE__ */ new Set(); + let raw; + try { + raw = await (0, import_promises4.readFile)(import_node_path3.default.join(cwd, "package.json"), "utf8"); + } catch { + return names; + } + let parsed; + try { + parsed = JSON.parse(raw); + } catch { + return names; + } + if (typeof parsed !== "object" || parsed === null) { + return names; + } + const obj = parsed; + for (const field of ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"]) { + const section = obj[field]; + if (typeof section !== "object" || section === null) { + continue; + } + for (const name of Object.keys(section)) { + names.add(name); + } + } + return names; +} + // src/parsers/index.ts async function detectLockfile(cwd) { - const npmLock = import_node_path3.default.join(cwd, "package-lock.json"); + const npmLock = import_node_path4.default.join(cwd, "package-lock.json"); if (await exists(npmLock)) { return { ecosystem: "npm", @@ -518,7 +709,7 @@ async function detectLockfile(cwd) { strategy: "npm-lockfile" }; } - const bunLock = import_node_path3.default.join(cwd, "bun.lock"); + const bunLock = import_node_path4.default.join(cwd, "bun.lock"); if (await exists(bunLock)) { return { ecosystem: "npm", @@ -527,7 +718,7 @@ async function detectLockfile(cwd) { strategy: "node-modules-walk" }; } - const bunLockB = import_node_path3.default.join(cwd, "bun.lockb"); + const bunLockB = import_node_path4.default.join(cwd, "bun.lockb"); if (await exists(bunLockB)) { return { ecosystem: "npm", @@ -536,7 +727,7 @@ async function detectLockfile(cwd) { strategy: "node-modules-walk" }; } - const pnpmLock = import_node_path3.default.join(cwd, "pnpm-lock.yaml"); + const pnpmLock = import_node_path4.default.join(cwd, "pnpm-lock.yaml"); if (await exists(pnpmLock)) { return { ecosystem: "npm", @@ -545,12 +736,14 @@ async function detectLockfile(cwd) { strategy: "pnpm-lockfile" }; } - const yarnLock = import_node_path3.default.join(cwd, "yarn.lock"); + const yarnLock = import_node_path4.default.join(cwd, "yarn.lock"); if (await exists(yarnLock)) { - throw new PatchstackError( - "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.", - "LOCKFILE_UNSUPPORTED" - ); + return { + ecosystem: "npm", + filePath: yarnLock, + filename: "yarn.lock", + strategy: "yarn-lockfile" + }; } throw new PatchstackError( `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`, @@ -568,13 +761,15 @@ async function runStrategy(detected, cwd) { return parseNpmLockfile(detected.filePath); case "pnpm-lockfile": return parsePnpmLockfile(detected.filePath); + case "yarn-lockfile": + return parseYarnLockfile(detected.filePath); case "node-modules-walk": return walkNodeModules(cwd); } } async function exists(filePath) { try { - await (0, import_promises4.access)(filePath); + await (0, import_promises5.access)(filePath); return true; } catch { return false; @@ -761,8 +956,8 @@ function isTimeoutError(cause) { } // src/config.ts -var import_promises5 = require("fs/promises"); -var import_node_path4 = __toESM(require("path"), 1); +var import_promises6 = require("fs/promises"); +var import_node_path5 = __toESM(require("path"), 1); var CONFIG_FILENAME = ".patchstackrc.json"; async function resolveConfig(options) { const fromFile = await readConfigFile(options.cwd); @@ -789,9 +984,9 @@ async function resolveConfig(options) { }; } async function writeConfigFile(cwd, config) { - const target = import_node_path4.default.join(cwd, CONFIG_FILENAME); + const target = import_node_path5.default.join(cwd, CONFIG_FILENAME); const content = JSON.stringify(config, null, 2) + "\n"; - await (0, import_promises5.writeFile)(target, content, "utf8"); + await (0, import_promises6.writeFile)(target, content, "utf8"); return target; } async function persistSiteUuid(cwd, siteUuid) { @@ -799,10 +994,10 @@ async function persistSiteUuid(cwd, siteUuid) { return writeConfigFile(cwd, { ...existing, siteUuid }); } async function readConfigFile(cwd) { - const target = import_node_path4.default.join(cwd, CONFIG_FILENAME); + const target = import_node_path5.default.join(cwd, CONFIG_FILENAME); let raw; try { - raw = await (0, import_promises5.readFile)(target, "utf8"); + raw = await (0, import_promises6.readFile)(target, "utf8"); } catch (err) { if (err.code === "ENOENT") { return {}; diff --git a/dist/index.cjs.map b/dist/index.cjs.map index fdfad18..46704f0 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/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 +{"version":3,"sources":["../src/index.ts","../src/parsers/index.ts","../src/types.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/parsers/pnpm.ts","../src/parsers/yarn.ts","../src/normalize.ts","../src/client.ts","../src/config.ts"],"sourcesContent":["import { scanLockfile } from './parsers/index.js';\nimport { buildWirePayload } from './normalize.js';\nimport { postManifest } from './client.js';\nimport { persistSiteUuid, resolveConfig } from './config.js';\nimport type { Config, Manifest, StoreManifestResponse } from './types.js';\n\nexport { scanLockfile, detectLockfile } from './parsers/index.js';\nexport { buildWirePayload, compareVersions } from './normalize.js';\nexport { postManifest, buildClaimUrl, buildEndpointUrl, DEFAULT_ENDPOINT } from './client.js';\nexport { persistSiteUuid, resolveConfig, writeConfigFile } from './config.js';\nexport {\n PatchstackError,\n type Config,\n type Ecosystem,\n type Manifest,\n type PackageEntry,\n type StoreManifestResponse,\n} from './types.js';\n\nexport interface ScanAndReportOptions {\n cwd?: string;\n config?: Config;\n}\n\nexport interface ScanAndReportResult {\n manifest: Manifest;\n response: StoreManifestResponse;\n duplicateNames: string[];\n uniqueNames: number;\n totalEntries: number;\n}\n\nexport async function scanAndReport(\n options: ScanAndReportOptions = {},\n): Promise {\n const cwd = options.cwd ?? process.cwd();\n const config = options.config ?? (await resolveConfig({ cwd }));\n const manifest = await scanLockfile(cwd);\n const { payload, stats } = buildWirePayload(manifest);\n const response = await postManifest(config, payload);\n\n // First-run convenience: if we didn't have a UUID and the server provisioned\n // one for us, persist it so subsequent runs target the same site.\n if (config.siteUuid === null && response.uuid !== undefined && response.uuid.length > 0) {\n await persistSiteUuid(cwd, response.uuid);\n }\n\n return {\n manifest,\n response,\n duplicateNames: stats.duplicateNames,\n uniqueNames: stats.uniqueNames,\n totalEntries: stats.totalEntries,\n };\n}\n","import { access } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Manifest, type PackageEntry } from '../types.js';\nimport { parseNpmLockfile } from './npm.js';\nimport { walkNodeModules } from './node_modules.js';\nimport { parsePnpmLockfile } from './pnpm.js';\nimport { parseYarnLockfile } from './yarn.js';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy =\n | 'npm-lockfile'\n | 'node-modules-walk'\n | 'pnpm-lockfile'\n | 'yarn-lockfile';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\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 return {\n ecosystem: 'npm',\n filePath: yarnLock,\n filename: 'yarn.lock',\n strategy: 'yarn-lockfile',\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 'yarn-lockfile':\n return parseYarnLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise {\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 { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\n/**\n * Parses yarn.lock (yarn classic v1 and yarn berry v2+) without a YAML\n * dependency. Both generations share the same block structure — a top-level\n * mapping of comma-separated descriptor lists to a block containing a\n * `version` field — so we walk them with the same scanner and only branch on\n * the `version` syntax (`version \"x\"` for v1, `version: x` for berry).\n *\n * Direct vs transitive can't be derived from yarn.lock alone (yarn does not\n * record an importer manifest the way pnpm v9 does), so we cross-reference\n * the sibling `package.json` when present.\n */\nexport async function parseYarnLockfile(lockfilePath: string): Promise {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(\n `Could not read lockfile at ${lockfilePath}`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const blocks = parseBlocks(raw);\n if (blocks.length === 0) {\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} contains no package entries`,\n 'LOCKFILE_PARSE_ERROR',\n );\n }\n\n const directNames = await readDirectDepNames(path.dirname(lockfilePath));\n\n const entries: PackageEntry[] = [];\n const seen = new Set();\n for (const block of blocks) {\n if (block.version.length === 0 || block.names.size === 0) {\n continue;\n }\n for (const name of block.names) {\n const dedupKey = `${name}@${block.version}`;\n if (seen.has(dedupKey)) {\n continue;\n }\n seen.add(dedupKey);\n entries.push({\n name,\n version: block.version,\n direct: directNames.has(name),\n });\n }\n }\n\n return entries;\n}\n\ninterface Block {\n names: Set;\n version: string;\n}\n\nfunction parseBlocks(raw: string): Block[] {\n const lines = raw.split(/\\r?\\n/);\n const blocks: Block[] = [];\n let current: Block | null = null;\n\n const finalize = () => {\n if (current !== null && current.version.length > 0 && current.names.size > 0) {\n blocks.push(current);\n }\n current = null;\n };\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed.length === 0 || trimmed.startsWith('#')) {\n continue;\n }\n\n const indent = countLeadingSpaces(line);\n\n if (indent === 0) {\n finalize();\n if (!trimmed.endsWith(':')) {\n continue;\n }\n // `__metadata:` (yarn berry header) has no `@` in any descriptor and\n // produces an empty names set, so it's naturally skipped on finalize.\n const keyLine = trimmed.slice(0, -1);\n const names = new Set();\n for (const spec of splitDescriptors(keyLine)) {\n const name = extractName(spec);\n if (name !== null) {\n names.add(name);\n }\n }\n current = { names, version: '' };\n continue;\n }\n\n if (current === null) {\n continue;\n }\n\n const version = parseVersionField(trimmed);\n if (version !== null) {\n current.version = version;\n }\n }\n\n finalize();\n return blocks;\n}\n\nfunction countLeadingSpaces(line: string): number {\n let i = 0;\n while (i < line.length && line[i] === ' ') {\n i++;\n }\n return i;\n}\n\n/**\n * Splits a yarn descriptor key list on top-level commas. yarn quotes any\n * descriptor that contains characters needing escaping, so we respect quotes\n * while splitting to avoid breaking on commas inside (rare in practice but\n * cheap to handle).\n */\nexport function splitDescriptors(keyLine: string): string[] {\n const parts: string[] = [];\n let current = '';\n let quote: '\"' | \"'\" | null = null;\n\n for (let i = 0; i < keyLine.length; i++) {\n const c = keyLine[i];\n if (quote !== null) {\n current += c;\n if (c === quote) {\n quote = null;\n }\n continue;\n }\n if (c === '\"' || c === \"'\") {\n quote = c;\n current += c;\n continue;\n }\n if (c === ',') {\n const piece = current.trim();\n if (piece.length > 0) {\n parts.push(piece);\n }\n current = '';\n continue;\n }\n current += c;\n }\n const tail = current.trim();\n if (tail.length > 0) {\n parts.push(tail);\n }\n return parts;\n}\n\n/**\n * Extracts the package name from a yarn descriptor like `axios@^1.6.0`,\n * `\"@scope/pkg@^2.1.0\"`, or `\"@scope/pkg@npm:2.1.0\"`. The descriptor's\n * range portion is discarded — we only need the name, since the resolved\n * version comes from the `version` field of the block.\n */\nexport function extractName(rawSpec: string): string | null {\n let s = rawSpec.trim();\n if (s.length === 0) {\n return null;\n }\n if (\n (s.startsWith('\"') && s.endsWith('\"')) ||\n (s.startsWith(\"'\") && s.endsWith(\"'\"))\n ) {\n s = s.slice(1, -1);\n }\n // Position-0 `@` belongs to a scope, so we want the last `@` after it.\n const atIdx = s.lastIndexOf('@');\n if (atIdx <= 0) {\n return null;\n }\n const name = s.slice(0, atIdx);\n return name.length > 0 ? name : null;\n}\n\nfunction parseVersionField(content: string): string | null {\n if (!content.startsWith('version')) {\n return null;\n }\n const after = content.slice('version'.length);\n // yarn v1: `version \"1.2.3\"` (whitespace then quoted)\n // yarn berry: `version: 1.2.3` or `version: \"1.2.3\"`\n const firstChar = after.charAt(0);\n if (firstChar !== ' ' && firstChar !== '\\t' && firstChar !== ':') {\n return null;\n }\n let rest = firstChar === ':' ? after.slice(1) : after;\n rest = rest.trim();\n if (rest.length === 0) {\n return null;\n }\n if (\n (rest.startsWith('\"') && rest.endsWith('\"')) ||\n (rest.startsWith(\"'\") && rest.endsWith(\"'\"))\n ) {\n rest = rest.slice(1, -1);\n }\n return rest.length > 0 ? rest : null;\n}\n\nasync function readDirectDepNames(cwd: string): Promise> {\n const names = new Set();\n let raw: string;\n try {\n raw = await readFile(path.join(cwd, 'package.json'), 'utf8');\n } catch {\n return names;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return names;\n }\n\n if (typeof parsed !== 'object' || parsed === null) {\n return names;\n }\n const obj = parsed as Record;\n\n for (const field of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) {\n const section = obj[field];\n if (typeof section !== 'object' || section === null) {\n continue;\n }\n for (const name of Object.keys(section)) {\n names.add(name);\n }\n }\n\n return names;\n}\n","import type { Manifest, PackageEntry } from './types.js';\n\nexport interface WirePackage {\n name: string;\n version: string;\n}\n\nexport interface WirePayload {\n ecosystem: Manifest['ecosystem'];\n packages: WirePackage[];\n}\n\nexport interface NormalizeStats {\n uniqueNames: number;\n duplicateNames: string[];\n totalEntries: number;\n}\n\nexport interface NormalizeResult {\n payload: WirePayload;\n stats: NormalizeStats;\n}\n\nexport function buildWirePayload(manifest: Manifest): NormalizeResult {\n const seen = new Map>();\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","import_promises","import_node_path","path","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 b57a198..ef724f6 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' | 'pnpm-lockfile'; +type DetectionStrategy = 'npm-lockfile' | 'node-modules-walk' | 'pnpm-lockfile' | 'yarn-lockfile'; interface DetectedLockfile { ecosystem: 'npm'; filePath: string; diff --git a/dist/index.d.ts b/dist/index.d.ts index b57a198..ef724f6 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' | 'pnpm-lockfile'; +type DetectionStrategy = 'npm-lockfile' | 'node-modules-walk' | 'pnpm-lockfile' | 'yarn-lockfile'; interface DetectedLockfile { ecosystem: 'npm'; filePath: string; diff --git a/dist/index.js b/dist/index.js index 3f22477..891a2be 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,6 +1,6 @@ // src/parsers/index.ts import { access } from "fs/promises"; -import path3 from "path"; +import path4 from "path"; // src/types.ts var PatchstackError = class extends Error { @@ -459,9 +459,200 @@ function extractLeafName(trimmed) { return name.length > 0 ? name : null; } +// src/parsers/yarn.ts +import { readFile as readFile4 } from "fs/promises"; +import path3 from "path"; +async function parseYarnLockfile(lockfilePath) { + let raw; + try { + raw = await readFile4(lockfilePath, "utf8"); + } catch (cause) { + throw new PatchstackError( + `Could not read lockfile at ${lockfilePath}`, + "LOCKFILE_NOT_FOUND", + cause + ); + } + const blocks = parseBlocks(raw); + if (blocks.length === 0) { + throw new PatchstackError( + `Lockfile at ${lockfilePath} contains no package entries`, + "LOCKFILE_PARSE_ERROR" + ); + } + const directNames = await readDirectDepNames(path3.dirname(lockfilePath)); + const entries = []; + const seen = /* @__PURE__ */ new Set(); + for (const block of blocks) { + if (block.version.length === 0 || block.names.size === 0) { + continue; + } + for (const name of block.names) { + const dedupKey = `${name}@${block.version}`; + if (seen.has(dedupKey)) { + continue; + } + seen.add(dedupKey); + entries.push({ + name, + version: block.version, + direct: directNames.has(name) + }); + } + } + return entries; +} +function parseBlocks(raw) { + const lines = raw.split(/\r?\n/); + const blocks = []; + let current = null; + const finalize = () => { + if (current !== null && current.version.length > 0 && current.names.size > 0) { + blocks.push(current); + } + current = null; + }; + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed.length === 0 || trimmed.startsWith("#")) { + continue; + } + const indent = countLeadingSpaces(line); + if (indent === 0) { + finalize(); + if (!trimmed.endsWith(":")) { + continue; + } + const keyLine = trimmed.slice(0, -1); + const names = /* @__PURE__ */ new Set(); + for (const spec of splitDescriptors(keyLine)) { + const name = extractName(spec); + if (name !== null) { + names.add(name); + } + } + current = { names, version: "" }; + continue; + } + if (current === null) { + continue; + } + const version = parseVersionField(trimmed); + if (version !== null) { + current.version = version; + } + } + finalize(); + return blocks; +} +function countLeadingSpaces(line) { + let i = 0; + while (i < line.length && line[i] === " ") { + i++; + } + return i; +} +function splitDescriptors(keyLine) { + const parts = []; + let current = ""; + let quote = null; + for (let i = 0; i < keyLine.length; i++) { + const c = keyLine[i]; + if (quote !== null) { + current += c; + if (c === quote) { + quote = null; + } + continue; + } + if (c === '"' || c === "'") { + quote = c; + current += c; + continue; + } + if (c === ",") { + const piece = current.trim(); + if (piece.length > 0) { + parts.push(piece); + } + current = ""; + continue; + } + current += c; + } + const tail = current.trim(); + if (tail.length > 0) { + parts.push(tail); + } + return parts; +} +function extractName(rawSpec) { + let s = rawSpec.trim(); + if (s.length === 0) { + return null; + } + if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) { + s = s.slice(1, -1); + } + const atIdx = s.lastIndexOf("@"); + if (atIdx <= 0) { + return null; + } + const name = s.slice(0, atIdx); + return name.length > 0 ? name : null; +} +function parseVersionField(content) { + if (!content.startsWith("version")) { + return null; + } + const after = content.slice("version".length); + const firstChar = after.charAt(0); + if (firstChar !== " " && firstChar !== " " && firstChar !== ":") { + return null; + } + let rest = firstChar === ":" ? after.slice(1) : after; + rest = rest.trim(); + if (rest.length === 0) { + return null; + } + if (rest.startsWith('"') && rest.endsWith('"') || rest.startsWith("'") && rest.endsWith("'")) { + rest = rest.slice(1, -1); + } + return rest.length > 0 ? rest : null; +} +async function readDirectDepNames(cwd) { + const names = /* @__PURE__ */ new Set(); + let raw; + try { + raw = await readFile4(path3.join(cwd, "package.json"), "utf8"); + } catch { + return names; + } + let parsed; + try { + parsed = JSON.parse(raw); + } catch { + return names; + } + if (typeof parsed !== "object" || parsed === null) { + return names; + } + const obj = parsed; + for (const field of ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"]) { + const section = obj[field]; + if (typeof section !== "object" || section === null) { + continue; + } + for (const name of Object.keys(section)) { + names.add(name); + } + } + return names; +} + // src/parsers/index.ts async function detectLockfile(cwd) { - const npmLock = path3.join(cwd, "package-lock.json"); + const npmLock = path4.join(cwd, "package-lock.json"); if (await exists(npmLock)) { return { ecosystem: "npm", @@ -470,7 +661,7 @@ async function detectLockfile(cwd) { strategy: "npm-lockfile" }; } - const bunLock = path3.join(cwd, "bun.lock"); + const bunLock = path4.join(cwd, "bun.lock"); if (await exists(bunLock)) { return { ecosystem: "npm", @@ -479,7 +670,7 @@ async function detectLockfile(cwd) { strategy: "node-modules-walk" }; } - const bunLockB = path3.join(cwd, "bun.lockb"); + const bunLockB = path4.join(cwd, "bun.lockb"); if (await exists(bunLockB)) { return { ecosystem: "npm", @@ -488,7 +679,7 @@ async function detectLockfile(cwd) { strategy: "node-modules-walk" }; } - const pnpmLock = path3.join(cwd, "pnpm-lock.yaml"); + const pnpmLock = path4.join(cwd, "pnpm-lock.yaml"); if (await exists(pnpmLock)) { return { ecosystem: "npm", @@ -497,12 +688,14 @@ async function detectLockfile(cwd) { strategy: "pnpm-lockfile" }; } - const yarnLock = path3.join(cwd, "yarn.lock"); + const yarnLock = path4.join(cwd, "yarn.lock"); if (await exists(yarnLock)) { - throw new PatchstackError( - "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.", - "LOCKFILE_UNSUPPORTED" - ); + return { + ecosystem: "npm", + filePath: yarnLock, + filename: "yarn.lock", + strategy: "yarn-lockfile" + }; } throw new PatchstackError( `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`, @@ -520,6 +713,8 @@ async function runStrategy(detected, cwd) { return parseNpmLockfile(detected.filePath); case "pnpm-lockfile": return parsePnpmLockfile(detected.filePath); + case "yarn-lockfile": + return parseYarnLockfile(detected.filePath); case "node-modules-walk": return walkNodeModules(cwd); } @@ -713,8 +908,8 @@ function isTimeoutError(cause) { } // src/config.ts -import { readFile as readFile4, writeFile } from "fs/promises"; -import path4 from "path"; +import { readFile as readFile5, writeFile } from "fs/promises"; +import path5 from "path"; var CONFIG_FILENAME = ".patchstackrc.json"; async function resolveConfig(options) { const fromFile = await readConfigFile(options.cwd); @@ -741,7 +936,7 @@ async function resolveConfig(options) { }; } async function writeConfigFile(cwd, config) { - const target = path4.join(cwd, CONFIG_FILENAME); + const target = path5.join(cwd, CONFIG_FILENAME); const content = JSON.stringify(config, null, 2) + "\n"; await writeFile(target, content, "utf8"); return target; @@ -751,10 +946,10 @@ async function persistSiteUuid(cwd, siteUuid) { return writeConfigFile(cwd, { ...existing, siteUuid }); } async function readConfigFile(cwd) { - const target = path4.join(cwd, CONFIG_FILENAME); + const target = path5.join(cwd, CONFIG_FILENAME); let raw; try { - raw = await readFile4(target, "utf8"); + raw = await readFile5(target, "utf8"); } catch (err) { if (err.code === "ENOENT") { return {}; diff --git a/dist/index.js.map b/dist/index.js.map index efd7948..5ea2118 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/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":"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","names":["path","readFile","path","path","readFile","readFile","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/parsers/yarn.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';\nimport { parseYarnLockfile } from './yarn.js';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy =\n | 'npm-lockfile'\n | 'node-modules-walk'\n | 'pnpm-lockfile'\n | 'yarn-lockfile';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\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 return {\n ecosystem: 'npm',\n filePath: yarnLock,\n filename: 'yarn.lock',\n strategy: 'yarn-lockfile',\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 'yarn-lockfile':\n return parseYarnLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise {\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 { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\n/**\n * Parses yarn.lock (yarn classic v1 and yarn berry v2+) without a YAML\n * dependency. Both generations share the same block structure — a top-level\n * mapping of comma-separated descriptor lists to a block containing a\n * `version` field — so we walk them with the same scanner and only branch on\n * the `version` syntax (`version \"x\"` for v1, `version: x` for berry).\n *\n * Direct vs transitive can't be derived from yarn.lock alone (yarn does not\n * record an importer manifest the way pnpm v9 does), so we cross-reference\n * the sibling `package.json` when present.\n */\nexport async function parseYarnLockfile(lockfilePath: string): Promise {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(\n `Could not read lockfile at ${lockfilePath}`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const blocks = parseBlocks(raw);\n if (blocks.length === 0) {\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} contains no package entries`,\n 'LOCKFILE_PARSE_ERROR',\n );\n }\n\n const directNames = await readDirectDepNames(path.dirname(lockfilePath));\n\n const entries: PackageEntry[] = [];\n const seen = new Set();\n for (const block of blocks) {\n if (block.version.length === 0 || block.names.size === 0) {\n continue;\n }\n for (const name of block.names) {\n const dedupKey = `${name}@${block.version}`;\n if (seen.has(dedupKey)) {\n continue;\n }\n seen.add(dedupKey);\n entries.push({\n name,\n version: block.version,\n direct: directNames.has(name),\n });\n }\n }\n\n return entries;\n}\n\ninterface Block {\n names: Set;\n version: string;\n}\n\nfunction parseBlocks(raw: string): Block[] {\n const lines = raw.split(/\\r?\\n/);\n const blocks: Block[] = [];\n let current: Block | null = null;\n\n const finalize = () => {\n if (current !== null && current.version.length > 0 && current.names.size > 0) {\n blocks.push(current);\n }\n current = null;\n };\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed.length === 0 || trimmed.startsWith('#')) {\n continue;\n }\n\n const indent = countLeadingSpaces(line);\n\n if (indent === 0) {\n finalize();\n if (!trimmed.endsWith(':')) {\n continue;\n }\n // `__metadata:` (yarn berry header) has no `@` in any descriptor and\n // produces an empty names set, so it's naturally skipped on finalize.\n const keyLine = trimmed.slice(0, -1);\n const names = new Set();\n for (const spec of splitDescriptors(keyLine)) {\n const name = extractName(spec);\n if (name !== null) {\n names.add(name);\n }\n }\n current = { names, version: '' };\n continue;\n }\n\n if (current === null) {\n continue;\n }\n\n const version = parseVersionField(trimmed);\n if (version !== null) {\n current.version = version;\n }\n }\n\n finalize();\n return blocks;\n}\n\nfunction countLeadingSpaces(line: string): number {\n let i = 0;\n while (i < line.length && line[i] === ' ') {\n i++;\n }\n return i;\n}\n\n/**\n * Splits a yarn descriptor key list on top-level commas. yarn quotes any\n * descriptor that contains characters needing escaping, so we respect quotes\n * while splitting to avoid breaking on commas inside (rare in practice but\n * cheap to handle).\n */\nexport function splitDescriptors(keyLine: string): string[] {\n const parts: string[] = [];\n let current = '';\n let quote: '\"' | \"'\" | null = null;\n\n for (let i = 0; i < keyLine.length; i++) {\n const c = keyLine[i];\n if (quote !== null) {\n current += c;\n if (c === quote) {\n quote = null;\n }\n continue;\n }\n if (c === '\"' || c === \"'\") {\n quote = c;\n current += c;\n continue;\n }\n if (c === ',') {\n const piece = current.trim();\n if (piece.length > 0) {\n parts.push(piece);\n }\n current = '';\n continue;\n }\n current += c;\n }\n const tail = current.trim();\n if (tail.length > 0) {\n parts.push(tail);\n }\n return parts;\n}\n\n/**\n * Extracts the package name from a yarn descriptor like `axios@^1.6.0`,\n * `\"@scope/pkg@^2.1.0\"`, or `\"@scope/pkg@npm:2.1.0\"`. The descriptor's\n * range portion is discarded — we only need the name, since the resolved\n * version comes from the `version` field of the block.\n */\nexport function extractName(rawSpec: string): string | null {\n let s = rawSpec.trim();\n if (s.length === 0) {\n return null;\n }\n if (\n (s.startsWith('\"') && s.endsWith('\"')) ||\n (s.startsWith(\"'\") && s.endsWith(\"'\"))\n ) {\n s = s.slice(1, -1);\n }\n // Position-0 `@` belongs to a scope, so we want the last `@` after it.\n const atIdx = s.lastIndexOf('@');\n if (atIdx <= 0) {\n return null;\n }\n const name = s.slice(0, atIdx);\n return name.length > 0 ? name : null;\n}\n\nfunction parseVersionField(content: string): string | null {\n if (!content.startsWith('version')) {\n return null;\n }\n const after = content.slice('version'.length);\n // yarn v1: `version \"1.2.3\"` (whitespace then quoted)\n // yarn berry: `version: 1.2.3` or `version: \"1.2.3\"`\n const firstChar = after.charAt(0);\n if (firstChar !== ' ' && firstChar !== '\\t' && firstChar !== ':') {\n return null;\n }\n let rest = firstChar === ':' ? after.slice(1) : after;\n rest = rest.trim();\n if (rest.length === 0) {\n return null;\n }\n if (\n (rest.startsWith('\"') && rest.endsWith('\"')) ||\n (rest.startsWith(\"'\") && rest.endsWith(\"'\"))\n ) {\n rest = rest.slice(1, -1);\n }\n return rest.length > 0 ? rest : null;\n}\n\nasync function readDirectDepNames(cwd: string): Promise> {\n const names = new Set();\n let raw: string;\n try {\n raw = await readFile(path.join(cwd, 'package.json'), 'utf8');\n } catch {\n return names;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return names;\n }\n\n if (typeof parsed !== 'object' || parsed === null) {\n return names;\n }\n const obj = parsed as Record;\n\n for (const field of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) {\n const section = obj[field];\n if (typeof section !== 'object' || section === null) {\n continue;\n }\n for (const name of Object.keys(section)) {\n names.add(name);\n }\n }\n\n return names;\n}\n","import type { Manifest, PackageEntry } from './types.js';\n\nexport interface WirePackage {\n name: string;\n version: string;\n}\n\nexport interface WirePayload {\n ecosystem: Manifest['ecosystem'];\n packages: WirePackage[];\n}\n\nexport interface NormalizeStats {\n uniqueNames: number;\n duplicateNames: string[];\n totalEntries: number;\n}\n\nexport interface NormalizeResult {\n payload: WirePayload;\n stats: NormalizeStats;\n}\n\nexport function buildWirePayload(manifest: Manifest): NormalizeResult {\n const seen = new Map>();\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","readFile","readFile","readFile","path","readFile","path","path","readFile","path","path","readFile"]} \ No newline at end of file diff --git a/src/parsers/index.ts b/src/parsers/index.ts index d19c020..86e621d 100644 --- a/src/parsers/index.ts +++ b/src/parsers/index.ts @@ -4,6 +4,7 @@ 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'; +import { parseYarnLockfile } from './yarn.js'; type LockfileFilename = | 'package-lock.json' @@ -12,7 +13,11 @@ type LockfileFilename = | 'yarn.lock' | 'pnpm-lock.yaml'; -type DetectionStrategy = 'npm-lockfile' | 'node-modules-walk' | 'pnpm-lockfile'; +type DetectionStrategy = + | 'npm-lockfile' + | 'node-modules-walk' + | 'pnpm-lockfile' + | 'yarn-lockfile'; interface DetectedLockfile { ecosystem: 'npm'; @@ -64,10 +69,12 @@ export async function detectLockfile(cwd: string): Promise { const yarnLock = path.join(cwd, 'yarn.lock'); if (await exists(yarnLock)) { - throw new PatchstackError( - '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.', - 'LOCKFILE_UNSUPPORTED', - ); + return { + ecosystem: 'npm', + filePath: yarnLock, + filename: 'yarn.lock', + strategy: 'yarn-lockfile', + }; } throw new PatchstackError( @@ -91,6 +98,8 @@ async function runStrategy( return parseNpmLockfile(detected.filePath); case 'pnpm-lockfile': return parsePnpmLockfile(detected.filePath); + case 'yarn-lockfile': + return parseYarnLockfile(detected.filePath); case 'node-modules-walk': return walkNodeModules(cwd); } diff --git a/src/parsers/yarn.ts b/src/parsers/yarn.ts new file mode 100644 index 0000000..d2b1393 --- /dev/null +++ b/src/parsers/yarn.ts @@ -0,0 +1,252 @@ +import { readFile } from 'node:fs/promises'; +import path from 'node:path'; +import { PatchstackError, type PackageEntry } from '../types.js'; + +/** + * Parses yarn.lock (yarn classic v1 and yarn berry v2+) without a YAML + * dependency. Both generations share the same block structure — a top-level + * mapping of comma-separated descriptor lists to a block containing a + * `version` field — so we walk them with the same scanner and only branch on + * the `version` syntax (`version "x"` for v1, `version: x` for berry). + * + * Direct vs transitive can't be derived from yarn.lock alone (yarn does not + * record an importer manifest the way pnpm v9 does), so we cross-reference + * the sibling `package.json` when present. + */ +export async function parseYarnLockfile(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 blocks = parseBlocks(raw); + if (blocks.length === 0) { + throw new PatchstackError( + `Lockfile at ${lockfilePath} contains no package entries`, + 'LOCKFILE_PARSE_ERROR', + ); + } + + const directNames = await readDirectDepNames(path.dirname(lockfilePath)); + + const entries: PackageEntry[] = []; + const seen = new Set(); + for (const block of blocks) { + if (block.version.length === 0 || block.names.size === 0) { + continue; + } + for (const name of block.names) { + const dedupKey = `${name}@${block.version}`; + if (seen.has(dedupKey)) { + continue; + } + seen.add(dedupKey); + entries.push({ + name, + version: block.version, + direct: directNames.has(name), + }); + } + } + + return entries; +} + +interface Block { + names: Set; + version: string; +} + +function parseBlocks(raw: string): Block[] { + const lines = raw.split(/\r?\n/); + const blocks: Block[] = []; + let current: Block | null = null; + + const finalize = () => { + if (current !== null && current.version.length > 0 && current.names.size > 0) { + blocks.push(current); + } + current = null; + }; + + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed.length === 0 || trimmed.startsWith('#')) { + continue; + } + + const indent = countLeadingSpaces(line); + + if (indent === 0) { + finalize(); + if (!trimmed.endsWith(':')) { + continue; + } + // `__metadata:` (yarn berry header) has no `@` in any descriptor and + // produces an empty names set, so it's naturally skipped on finalize. + const keyLine = trimmed.slice(0, -1); + const names = new Set(); + for (const spec of splitDescriptors(keyLine)) { + const name = extractName(spec); + if (name !== null) { + names.add(name); + } + } + current = { names, version: '' }; + continue; + } + + if (current === null) { + continue; + } + + const version = parseVersionField(trimmed); + if (version !== null) { + current.version = version; + } + } + + finalize(); + return blocks; +} + +function countLeadingSpaces(line: string): number { + let i = 0; + while (i < line.length && line[i] === ' ') { + i++; + } + return i; +} + +/** + * Splits a yarn descriptor key list on top-level commas. yarn quotes any + * descriptor that contains characters needing escaping, so we respect quotes + * while splitting to avoid breaking on commas inside (rare in practice but + * cheap to handle). + */ +export function splitDescriptors(keyLine: string): string[] { + const parts: string[] = []; + let current = ''; + let quote: '"' | "'" | null = null; + + for (let i = 0; i < keyLine.length; i++) { + const c = keyLine[i]; + if (quote !== null) { + current += c; + if (c === quote) { + quote = null; + } + continue; + } + if (c === '"' || c === "'") { + quote = c; + current += c; + continue; + } + if (c === ',') { + const piece = current.trim(); + if (piece.length > 0) { + parts.push(piece); + } + current = ''; + continue; + } + current += c; + } + const tail = current.trim(); + if (tail.length > 0) { + parts.push(tail); + } + return parts; +} + +/** + * Extracts the package name from a yarn descriptor like `axios@^1.6.0`, + * `"@scope/pkg@^2.1.0"`, or `"@scope/pkg@npm:2.1.0"`. The descriptor's + * range portion is discarded — we only need the name, since the resolved + * version comes from the `version` field of the block. + */ +export function extractName(rawSpec: string): string | null { + let s = rawSpec.trim(); + if (s.length === 0) { + return null; + } + if ( + (s.startsWith('"') && s.endsWith('"')) || + (s.startsWith("'") && s.endsWith("'")) + ) { + s = s.slice(1, -1); + } + // Position-0 `@` belongs to a scope, so we want the last `@` after it. + const atIdx = s.lastIndexOf('@'); + if (atIdx <= 0) { + return null; + } + const name = s.slice(0, atIdx); + return name.length > 0 ? name : null; +} + +function parseVersionField(content: string): string | null { + if (!content.startsWith('version')) { + return null; + } + const after = content.slice('version'.length); + // yarn v1: `version "1.2.3"` (whitespace then quoted) + // yarn berry: `version: 1.2.3` or `version: "1.2.3"` + const firstChar = after.charAt(0); + if (firstChar !== ' ' && firstChar !== '\t' && firstChar !== ':') { + return null; + } + let rest = firstChar === ':' ? after.slice(1) : after; + rest = rest.trim(); + if (rest.length === 0) { + return null; + } + if ( + (rest.startsWith('"') && rest.endsWith('"')) || + (rest.startsWith("'") && rest.endsWith("'")) + ) { + rest = rest.slice(1, -1); + } + return rest.length > 0 ? rest : null; +} + +async function readDirectDepNames(cwd: string): Promise> { + const names = new Set(); + let raw: string; + try { + raw = await readFile(path.join(cwd, 'package.json'), 'utf8'); + } catch { + return names; + } + + let parsed: unknown; + try { + parsed = JSON.parse(raw); + } catch { + return names; + } + + if (typeof parsed !== 'object' || parsed === null) { + return names; + } + const obj = parsed as Record; + + for (const field of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) { + const section = obj[field]; + if (typeof section !== 'object' || section === null) { + continue; + } + for (const name of Object.keys(section)) { + names.add(name); + } + } + + return names; +} diff --git a/tests/fixtures/yarn-berry-project/package.json b/tests/fixtures/yarn-berry-project/package.json new file mode 100644 index 0000000..ff1a2aa --- /dev/null +++ b/tests/fixtures/yarn-berry-project/package.json @@ -0,0 +1,14 @@ +{ + "name": "yarn-berry-project", + "version": "1.0.0", + "private": true, + "packageManager": "yarn@4.0.0", + "dependencies": { + "@scope/pkg": "^2.1.0", + "axios": "^1.6.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "vitest": "^3.0.0" + } +} diff --git a/tests/fixtures/yarn-berry-project/yarn.lock b/tests/fixtures/yarn-berry-project/yarn.lock new file mode 100644 index 0000000..69d525a --- /dev/null +++ b/tests/fixtures/yarn-berry-project/yarn.lock @@ -0,0 +1,52 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 6 + cacheKey: 8 + +"@scope/pkg@npm:^2.1.0": + version: 2.1.0 + resolution: "@scope/pkg@npm:2.1.0" + checksum: fake + languageName: node + linkType: hard + +"axios@npm:^1.6.0": + version: 1.6.0 + resolution: "axios@npm:1.6.0" + dependencies: + follow-redirects: "npm:^1.15.0" + checksum: fake + languageName: node + linkType: hard + +"follow-redirects@npm:^1.15.0": + version: 1.15.3 + resolution: "follow-redirects@npm:1.15.3" + checksum: fake + languageName: node + linkType: hard + +"react@npm:^18.2.0": + version: 18.2.0 + resolution: "react@npm:18.2.0" + checksum: fake + languageName: node + linkType: hard + +"react-dom@npm:^18.2.0": + version: 18.2.0 + resolution: "react-dom@npm:18.2.0" + dependencies: + react: "npm:^18.2.0" + checksum: fake + languageName: node + linkType: hard + +"vitest@npm:^3.0.0": + version: 3.0.0 + resolution: "vitest@npm:3.0.0" + checksum: fake + languageName: node + linkType: hard diff --git a/tests/fixtures/yarn-v1-project/package.json b/tests/fixtures/yarn-v1-project/package.json new file mode 100644 index 0000000..0471db0 --- /dev/null +++ b/tests/fixtures/yarn-v1-project/package.json @@ -0,0 +1,14 @@ +{ + "name": "yarn-v1-project", + "version": "1.0.0", + "private": true, + "dependencies": { + "@scope/pkg": "^2.1.0", + "axios": "^1.6.0", + "lodash": "4.17.15", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "react": "^18.2.0" + } +} diff --git a/tests/fixtures/yarn-v1-project/yarn.lock b/tests/fixtures/yarn-v1-project/yarn.lock new file mode 100644 index 0000000..c09c4ed --- /dev/null +++ b/tests/fixtures/yarn-v1-project/yarn.lock @@ -0,0 +1,42 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@scope/pkg@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@scope/pkg/-/pkg-2.1.0.tgz#fake" + integrity sha512-fake== + +"axios@^1.6.0", "axios@~1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.0.tgz#fake" + integrity sha512-fake== + dependencies: + follow-redirects "^1.15.0" + +follow-redirects@^1.15.0: + version "1.15.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fake" + integrity sha512-fake== + +lodash@4.17.15: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#fake" + integrity sha512-fake== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#fake" + integrity sha512-fake== + +react@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#fake" + integrity sha512-fake== + +"react-dom@^18.2.0": + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#fake" + integrity sha512-fake== + dependencies: + react "^18.2.0" diff --git a/tests/yarn.test.ts b/tests/yarn.test.ts new file mode 100644 index 0000000..915cdd0 --- /dev/null +++ b/tests/yarn.test.ts @@ -0,0 +1,239 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { copyFile, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { + extractName, + parseYarnLockfile, + splitDescriptors, +} from '../src/parsers/yarn.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 v1Project = path.join(fixtures, 'yarn-v1-project'); +const berryProject = path.join(fixtures, 'yarn-berry-project'); + +describe('extractName', () => { + it('parses plain descriptor', () => { + expect(extractName('axios@^1.6.0')).toBe('axios'); + }); + + it('parses quoted scoped descriptor', () => { + expect(extractName('"@scope/pkg@^2.1.0"')).toBe('@scope/pkg'); + }); + + it('parses yarn berry descriptor with protocol', () => { + expect(extractName('"@scope/pkg@npm:2.1.0"')).toBe('@scope/pkg'); + expect(extractName('"axios@npm:^1.6.0"')).toBe('axios'); + }); + + it('returns null for inputs without a separator', () => { + expect(extractName('')).toBeNull(); + expect(extractName('bare-name')).toBeNull(); + expect(extractName('@scope/no-version')).toBeNull(); + }); +}); + +describe('splitDescriptors', () => { + it('splits unquoted descriptors', () => { + expect(splitDescriptors('axios@^1.6.0, axios@~1.6.0')).toEqual([ + 'axios@^1.6.0', + 'axios@~1.6.0', + ]); + }); + + it('splits quoted descriptors and preserves quotes', () => { + expect(splitDescriptors('"axios@^1.6.0", "axios@~1.6.0"')).toEqual([ + '"axios@^1.6.0"', + '"axios@~1.6.0"', + ]); + }); + + it('treats commas inside quoted strings as literal', () => { + expect(splitDescriptors('"weird@>=1, <2", normal@^3')).toEqual([ + '"weird@>=1, <2"', + 'normal@^3', + ]); + }); +}); + +describe('parseYarnLockfile (v1)', () => { + it('extracts every resolved package', async () => { + const entries = await parseYarnLockfile(path.join(v1Project, 'yarn.lock')); + 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', + 'lodash@4.17.15', + 'lodash@4.17.21', + 'react@18.2.0', + 'react-dom@18.2.0', + ].sort(), + ); + }); + + it('preserves both versions of a duplicated package', async () => { + const entries = await parseYarnLockfile(path.join(v1Project, 'yarn.lock')); + const lodashes = entries.filter((e) => e.name === 'lodash'); + expect(lodashes.map((l) => l.version).sort()).toEqual(['4.17.15', '4.17.21']); + }); + + it('marks names from package.json dependencies/devDependencies as direct', async () => { + const entries = await parseYarnLockfile(path.join(v1Project, 'yarn.lock')); + 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')?.direct).toBe(true); + expect(byName.get('react-dom')?.direct).toBe(true); + expect(byName.get('follow-redirects')?.direct).toBe(false); + }); + + it('deduplicates multi-descriptor blocks to a single entry', async () => { + const entries = await parseYarnLockfile(path.join(v1Project, 'yarn.lock')); + const axios = entries.filter((e) => e.name === 'axios'); + expect(axios).toHaveLength(1); + expect(axios[0]?.version).toBe('1.6.0'); + }); +}); + +describe('parseYarnLockfile (berry)', () => { + it('parses berry-format blocks and skips __metadata', async () => { + const entries = await parseYarnLockfile(path.join(berryProject, 'yarn.lock')); + 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(), + ); + // The `version: 6` inside __metadata must never leak in as a package. + expect(entries.find((e) => e.version === '6')).toBeUndefined(); + }); + + it('marks direct deps from package.json', async () => { + const entries = await parseYarnLockfile(path.join(berryProject, 'yarn.lock')); + 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('react')?.direct).toBe(false); + expect(byName.get('follow-redirects')?.direct).toBe(false); + }); +}); + +describe('parseYarnLockfile errors', () => { + it('throws LOCKFILE_NOT_FOUND when the file is missing', async () => { + await expect(parseYarnLockfile('/nonexistent/yarn.lock')).rejects.toBeInstanceOf( + PatchstackError, + ); + }); + + it('throws LOCKFILE_PARSE_ERROR when no package blocks are present', async () => { + const dir = await mkdtemp(path.join(tmpdir(), 'patchstack-connect-yarn-empty-')); + try { + const file = path.join(dir, 'yarn.lock'); + await writeFile(file, '# yarn lockfile v1\n'); + await expect(parseYarnLockfile(file)).rejects.toMatchObject({ + code: 'LOCKFILE_PARSE_ERROR', + }); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); + + it('leaves direct unset when package.json is absent', async () => { + const dir = await mkdtemp(path.join(tmpdir(), 'patchstack-connect-yarn-no-pkg-')); + try { + await copyFile(path.join(v1Project, 'yarn.lock'), path.join(dir, 'yarn.lock')); + const entries = await parseYarnLockfile(path.join(dir, 'yarn.lock')); + for (const e of entries) { + expect(e.direct).toBe(false); + } + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); +}); + +describe('detectLockfile for yarn', () => { + let cwd: string; + + beforeEach(async () => { + cwd = await mkdtemp(path.join(tmpdir(), 'patchstack-connect-yarn-detect-')); + }); + + afterEach(async () => { + await rm(cwd, { recursive: true, force: true }); + }); + + it('detects yarn.lock and routes to the yarn parser', async () => { + await copyFile(path.join(v1Project, 'yarn.lock'), path.join(cwd, 'yarn.lock')); + const detected = await detectLockfile(cwd); + expect(detected.filename).toBe('yarn.lock'); + expect(detected.strategy).toBe('yarn-lockfile'); + expect(detected.ecosystem).toBe('npm'); + }); + + it('prefers package-lock.json over yarn.lock when both exist', async () => { + await copyFile( + path.join(fixtures, 'package-lock-v3.json'), + path.join(cwd, 'package-lock.json'), + ); + await copyFile(path.join(v1Project, 'yarn.lock'), path.join(cwd, 'yarn.lock')); + const detected = await detectLockfile(cwd); + expect(detected.filename).toBe('package-lock.json'); + }); + + it('prefers pnpm-lock.yaml over yarn.lock when both exist', async () => { + await copyFile( + path.join(fixtures, 'pnpm-lock-v9.yaml'), + path.join(cwd, 'pnpm-lock.yaml'), + ); + await copyFile(path.join(v1Project, 'yarn.lock'), path.join(cwd, 'yarn.lock')); + const detected = await detectLockfile(cwd); + expect(detected.filename).toBe('pnpm-lock.yaml'); + }); +}); + +describe('scanLockfile for yarn', () => { + it('returns an npm-ecosystem manifest from a yarn v1 project', async () => { + const cwd = await mkdtemp(path.join(tmpdir(), 'patchstack-connect-yarn-scan-')); + try { + await copyFile(path.join(v1Project, 'yarn.lock'), path.join(cwd, 'yarn.lock')); + await copyFile( + path.join(v1Project, 'package.json'), + path.join(cwd, 'package.json'), + ); + const manifest = await scanLockfile(cwd); + expect(manifest.ecosystem).toBe('npm'); + expect(manifest.packages.find((p) => p.name === 'axios')).toBeDefined(); + } finally { + await rm(cwd, { recursive: true, force: true }); + } + }); + + it('returns an npm-ecosystem manifest from a yarn berry project', async () => { + const cwd = await mkdtemp(path.join(tmpdir(), 'patchstack-connect-yarn-scan-berry-')); + try { + await mkdir(cwd, { recursive: true }); + await copyFile(path.join(berryProject, 'yarn.lock'), path.join(cwd, 'yarn.lock')); + await copyFile( + path.join(berryProject, 'package.json'), + path.join(cwd, 'package.json'), + ); + const manifest = await scanLockfile(cwd); + expect(manifest.ecosystem).toBe('npm'); + expect(manifest.packages.find((p) => p.name === 'vitest')).toBeDefined(); + } finally { + await rm(cwd, { recursive: true, force: true }); + } + }); +});