diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a2b9aef..53cf425 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,9 +49,6 @@ jobs: - name: Build run: npm run build - - name: Verify committed dist is current - run: git diff --exit-code -- dist - - name: Verify package contents run: npm pack --dry-run diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b4b9bbb..7766931 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -48,9 +48,6 @@ jobs: - name: Build run: npm run build - - name: Verify committed dist is current - run: git diff --exit-code -- dist - - name: Verify package contents run: npm pack --dry-run diff --git a/.gitignore b/.gitignore index 7e1b78c..f4a2a9e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,7 @@ node_modules # used to exercise the node-modules walker. Keep those tracked. !tests/fixtures/**/node_modules !tests/fixtures/**/node_modules/** -# dist/ is committed pre-npm-publish so consumers can `npm install github:patchstack/connect` -# in sandboxed environments (WebContainer/StackBlitz/Bolt/Lovable) where the `prepare` -# script can't run `tsup`. Remove this exception once the package is published to npm. +dist *.log .DS_Store .env diff --git a/dist/cli.js b/dist/cli.js deleted file mode 100755 index 38f5fc0..0000000 --- a/dist/cli.js +++ /dev/null @@ -1,727 +0,0 @@ -#!/usr/bin/env node - -// src/parsers/index.ts -import { access } from "fs/promises"; -import path3 from "path"; - -// src/types.ts -var PatchstackError = class extends Error { - constructor(message, code, cause) { - super(message); - this.code = code; - this.cause = cause; - this.name = "PatchstackError"; - } - code; - cause; -}; - -// src/parsers/npm.ts -import { readFile } from "fs/promises"; -import path from "path"; -async function parseNpmLockfile(lockfilePath) { - let raw; - try { - raw = await readFile(lockfilePath, "utf8"); - } catch (cause) { - throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, "LOCKFILE_NOT_FOUND", cause); - } - let parsed; - try { - parsed = JSON.parse(raw); - } catch (cause) { - throw new PatchstackError(`Lockfile at ${lockfilePath} is not valid JSON`, "LOCKFILE_PARSE_ERROR", cause); - } - if (parsed.packages) { - return extractFromV2(parsed.packages); - } - if (parsed.dependencies) { - return extractFromV1(parsed.dependencies); - } - throw new PatchstackError( - `Lockfile at ${lockfilePath} has no "packages" or "dependencies" key`, - "LOCKFILE_PARSE_ERROR" - ); -} -function extractFromV2(packages) { - const entries = []; - for (const [pkgPath, pkg] of Object.entries(packages)) { - if (pkgPath === "") { - continue; - } - if (pkg.link === true) { - continue; - } - if (typeof pkg.version !== "string" || pkg.version.length === 0) { - continue; - } - const name = pkg.name ?? extractNameFromPath(pkgPath); - if (name === null) { - continue; - } - entries.push({ - name, - version: pkg.version, - path: pkgPath, - direct: isDirectV2(pkgPath) - }); - } - return entries; -} -function extractFromV1(deps, acc = [], depth = 0) { - for (const [name, dep] of Object.entries(deps)) { - if (typeof dep.version === "string" && dep.version.length > 0) { - acc.push({ name, version: dep.version, direct: depth === 0 }); - } - if (dep.dependencies) { - extractFromV1(dep.dependencies, acc, depth + 1); - } - } - return acc; -} -function extractNameFromPath(pkgPath) { - const segments = pkgPath.split("node_modules" + path.sep === pkgPath ? path.sep : "/"); - const parts = pkgPath.split("/"); - const nmIndex = parts.lastIndexOf("node_modules"); - if (nmIndex === -1 || nmIndex >= parts.length - 1) { - return segments[segments.length - 1] ?? null; - } - const tail = parts.slice(nmIndex + 1); - if (tail.length === 0) { - return null; - } - const first = tail[0]; - if (first !== void 0 && first.startsWith("@") && tail.length >= 2) { - return `${first}/${tail[1]}`; - } - return first ?? null; -} -function isDirectV2(pkgPath) { - const parts = pkgPath.split("/"); - const nmCount = parts.filter((p) => p === "node_modules").length; - return nmCount === 1; -} - -// src/parsers/node_modules.ts -import { lstat, readFile as readFile2, readdir, stat } from "fs/promises"; -import path2 from "path"; -async function walkNodeModules(cwd) { - const root = path2.join(cwd, "node_modules"); - try { - const info = await stat(root); - if (!info.isDirectory()) { - throw new PatchstackError( - `${root} exists but is not a directory.`, - "LOCKFILE_NOT_FOUND" - ); - } - } catch (cause) { - if (cause instanceof PatchstackError) { - throw cause; - } - throw new PatchstackError( - `node_modules/ not found at ${cwd}. Install dependencies first (e.g. \`bun install\` or \`npm install\`).`, - "LOCKFILE_NOT_FOUND", - cause - ); - } - const entries = []; - await walk(root, entries, 0); - return entries; -} -async function walk(dir, acc, depth) { - let names; - try { - names = await readdir(dir); - } catch { - return; - } - for (const name of names) { - if (name.startsWith(".")) { - continue; - } - const fullPath = path2.join(dir, name); - if (!await isPlainDirectory(fullPath)) { - continue; - } - if (name.startsWith("@")) { - let subNames; - try { - subNames = await readdir(fullPath); - } catch { - continue; - } - for (const sub of subNames) { - if (sub.startsWith(".")) { - continue; - } - const scopedDir = path2.join(fullPath, sub); - if (!await isPlainDirectory(scopedDir)) { - continue; - } - await readPackage(scopedDir, depth, acc); - await walkNested(scopedDir, acc, depth); - } - continue; - } - await readPackage(fullPath, depth, acc); - await walkNested(fullPath, acc, depth); - } -} -async function readPackage(pkgDir, depth, acc) { - let raw; - try { - raw = await readFile2(path2.join(pkgDir, "package.json"), "utf8"); - } catch { - return; - } - let parsed; - try { - parsed = JSON.parse(raw); - } catch { - return; - } - if (typeof parsed.name !== "string" || parsed.name.length === 0) { - return; - } - if (typeof parsed.version !== "string" || parsed.version.length === 0) { - return; - } - acc.push({ - name: parsed.name, - version: parsed.version, - direct: depth === 0 - }); -} -async function walkNested(pkgDir, acc, depth) { - const nested = path2.join(pkgDir, "node_modules"); - if (!await isPlainDirectory(nested)) { - return; - } - await walk(nested, acc, depth + 1); -} -async function isPlainDirectory(dir) { - try { - const info = await lstat(dir); - return info.isDirectory() && !info.isSymbolicLink(); - } catch { - return false; - } -} - -// src/parsers/index.ts -async function detectLockfile(cwd) { - const npmLock = path3.join(cwd, "package-lock.json"); - if (await exists(npmLock)) { - return { - ecosystem: "npm", - filePath: npmLock, - filename: "package-lock.json", - strategy: "npm-lockfile" - }; - } - const bunLock = path3.join(cwd, "bun.lock"); - if (await exists(bunLock)) { - return { - ecosystem: "npm", - filePath: bunLock, - filename: "bun.lock", - strategy: "node-modules-walk" - }; - } - const bunLockB = path3.join(cwd, "bun.lockb"); - if (await exists(bunLockB)) { - return { - ecosystem: "npm", - filePath: bunLockB, - filename: "bun.lockb", - strategy: "node-modules-walk" - }; - } - const yarnLock = path3.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" - ); - } - const pnpmLock = path3.join(cwd, "pnpm-lock.yaml"); - if (await exists(pnpmLock)) { - throw new PatchstackError( - "pnpm-lock.yaml detected but not yet supported. Open an issue at github.com/patchstack/connect to request support.", - "LOCKFILE_UNSUPPORTED" - ); - } - throw new PatchstackError( - `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`, - "LOCKFILE_NOT_FOUND" - ); -} -async function scanLockfile(cwd) { - const detected = await detectLockfile(cwd); - const packages = await runStrategy(detected, cwd); - return { ecosystem: detected.ecosystem, packages }; -} -async function runStrategy(detected, cwd) { - switch (detected.strategy) { - case "npm-lockfile": - return parseNpmLockfile(detected.filePath); - case "node-modules-walk": - return walkNodeModules(cwd); - } -} -async function exists(filePath) { - try { - await access(filePath); - return true; - } catch { - return false; - } -} - -// src/normalize.ts -function buildWirePayload(manifest) { - const seen = /* @__PURE__ */ new Map(); - const wirePackages = []; - for (const entry of manifest.packages) { - const versions = seen.get(entry.name); - if (versions) { - if (versions.has(entry.version)) { - continue; - } - versions.add(entry.version); - } else { - seen.set(entry.name, /* @__PURE__ */ new Set([entry.version])); - } - wirePackages.push({ name: entry.name, version: entry.version }); - } - wirePackages.sort((a, b) => { - if (a.name === b.name) { - return compareVersions(a.version, b.version); - } - return a.name < b.name ? -1 : 1; - }); - const duplicateNames = []; - for (const [name, versions] of seen) { - if (versions.size > 1) { - duplicateNames.push(name); - } - } - return { - payload: { ecosystem: manifest.ecosystem, packages: wirePackages }, - stats: { - uniqueNames: seen.size, - duplicateNames, - totalEntries: manifest.packages.length - } - }; -} -function compareVersions(a, b) { - if (a === b) { - return 0; - } - const [aBase, aPre] = splitPrerelease(a); - const [bBase, bPre] = splitPrerelease(b); - const baseCmp = compareSegments(aBase.split("."), bBase.split(".")); - if (baseCmp !== 0) { - return baseCmp; - } - if (aPre === null && bPre === null) { - return 0; - } - if (aPre === null) { - return 1; - } - if (bPre === null) { - return -1; - } - return compareSegments(aPre.split("."), bPre.split(".")); -} -function splitPrerelease(version) { - const cleaned = version.replace(/^[v=]+/, "").split("+")[0]; - const dashIndex = cleaned.indexOf("-"); - if (dashIndex === -1) { - return [cleaned, null]; - } - return [cleaned.slice(0, dashIndex), cleaned.slice(dashIndex + 1)]; -} -function compareSegments(a, b) { - const max = Math.max(a.length, b.length); - for (let i = 0; i < max; i++) { - const aPart = a[i]; - const bPart = b[i]; - if (aPart === void 0) { - return -1; - } - if (bPart === void 0) { - return 1; - } - const aNum = /^\d+$/.test(aPart); - const bNum = /^\d+$/.test(bPart); - if (aNum && bNum) { - const diff = Number(aPart) - Number(bPart); - if (diff !== 0) { - return diff < 0 ? -1 : 1; - } - continue; - } - if (aNum) { - return -1; - } - if (bNum) { - return 1; - } - if (aPart < bPart) { - return -1; - } - if (aPart > bPart) { - return 1; - } - } - return 0; -} - -// src/client.ts -var DEFAULT_ENDPOINT = "https://api.patchstack.com/monitor/pulse/manifest"; -var DEFAULT_TIMEOUT_MS = 3e4; -function buildEndpointUrl(base, siteUuid) { - const trimmed = base.replace(/\/$/, ""); - return siteUuid !== void 0 && siteUuid !== null && siteUuid.length > 0 ? `${trimmed}/${encodeURIComponent(siteUuid)}` : trimmed; -} -function buildClaimUrl(endpoint, siteUuid) { - const origin = new URL(endpoint).origin; - return `${origin}/monitor/claim?site=${encodeURIComponent(siteUuid)}`; -} -async function postManifest(config, payload) { - const url = buildEndpointUrl(config.endpoint, config.siteUuid); - const timeoutMs = config.timeoutMs; - let response; - try { - response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - "User-Agent": "@patchstack/connect" - }, - body: JSON.stringify(payload), - signal: AbortSignal.timeout(timeoutMs) - }); - } catch (cause) { - if (isTimeoutError(cause)) { - throw new PatchstackError( - `Patchstack request to ${url} timed out after ${timeoutMs}ms. Override with PATCHSTACK_TIMEOUT_MS.`, - "NETWORK_TIMEOUT", - cause - ); - } - throw new PatchstackError( - `Could not reach Patchstack at ${url}. Check your network connection.`, - "NETWORK_ERROR", - cause - ); - } - const text = await response.text(); - let body = null; - try { - body = text.length > 0 ? JSON.parse(text) : null; - } catch { - body = null; - } - if (response.status === 404) { - throw new PatchstackError( - 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.", - "SITE_NOT_FOUND" - ); - } - if (response.status === 422) { - throw new PatchstackError( - body?.message ?? "Patchstack rejected the manifest payload (validation failed).", - "VALIDATION_ERROR" - ); - } - if (response.status < 200 || response.status >= 300) { - throw new PatchstackError( - `Patchstack returned ${response.status}: ${text.slice(0, 200)}`, - "SERVER_ERROR" - ); - } - if (body === null) { - throw new PatchstackError("Patchstack returned an empty response.", "SERVER_ERROR"); - } - return body; -} -function isTimeoutError(cause) { - if (cause instanceof Error) { - return cause.name === "TimeoutError" || cause.name === "AbortError"; - } - return false; -} - -// src/config.ts -import { readFile as readFile3, writeFile } from "fs/promises"; -import path4 from "path"; -var CONFIG_FILENAME = ".patchstackrc.json"; -async function resolveConfig(options) { - const fromFile = await readConfigFile(options.cwd); - const fromEnv = readEnv(); - const siteUuid = options.cliSiteUuid ?? fromEnv.siteUuid ?? fromFile.siteUuid ?? null; - const endpoint = options.cliEndpoint ?? fromEnv.endpoint ?? fromFile.endpoint ?? DEFAULT_ENDPOINT; - const timeoutMs = fromEnv.timeoutMs ?? fromFile.timeoutMs ?? DEFAULT_TIMEOUT_MS; - if (siteUuid !== null && siteUuid.length > 0 && !isUuid(siteUuid)) { - throw new PatchstackError( - `Site UUID "${siteUuid}" does not look like a valid UUID.`, - "CONFIG_INVALID" - ); - } - if (options.requireSiteUuid && (siteUuid === null || siteUuid.length === 0)) { - throw new PatchstackError( - "No site UUID configured. Run `patchstack-connect scan` to provision one, or set PATCHSTACK_SITE_UUID.", - "CONFIG_MISSING" - ); - } - return { - siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid, - endpoint, - timeoutMs - }; -} -async function writeConfigFile(cwd, config) { - const target = path4.join(cwd, CONFIG_FILENAME); - const content = JSON.stringify(config, null, 2) + "\n"; - await writeFile(target, content, "utf8"); - return target; -} -async function persistSiteUuid(cwd, siteUuid) { - const existing = await readConfigFile(cwd); - return writeConfigFile(cwd, { ...existing, siteUuid }); -} -async function readConfigFile(cwd) { - const target = path4.join(cwd, CONFIG_FILENAME); - let raw; - try { - raw = await readFile3(target, "utf8"); - } catch (err) { - if (err.code === "ENOENT") { - return {}; - } - throw new PatchstackError( - `Could not read ${target}: ${err.message}`, - "CONFIG_INVALID", - err - ); - } - try { - return JSON.parse(raw); - } catch (err) { - throw new PatchstackError( - `Config file ${target} contains invalid JSON.`, - "CONFIG_INVALID", - err - ); - } -} -function readEnv() { - const timeoutRaw = process.env.PATCHSTACK_TIMEOUT_MS; - let timeoutMs; - if (timeoutRaw !== void 0 && timeoutRaw.length > 0) { - const parsed = Number(timeoutRaw); - if (!Number.isFinite(parsed) || parsed <= 0) { - throw new PatchstackError( - `PATCHSTACK_TIMEOUT_MS must be a positive number; got "${timeoutRaw}".`, - "CONFIG_INVALID" - ); - } - timeoutMs = parsed; - } - return { - siteUuid: process.env.PATCHSTACK_SITE_UUID ?? void 0, - endpoint: process.env.PATCHSTACK_ENDPOINT ?? void 0, - timeoutMs - }; -} -function isUuid(value) { - return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value); -} - -// src/cli.ts -var HELP = `@patchstack/connect \u2014 scan your lockfile and report packages to Patchstack. - -Usage: - patchstack-connect scan [options] Scan lockfile and POST to Patchstack. - If no UUID is configured, the server - provisions one and we persist it. - patchstack-connect init Optional: pre-seed .patchstackrc.json - with an existing site UUID - patchstack-connect status [options] Show current configuration - patchstack-connect help Print this message - -Options (for scan and status): - --site-uuid Override the configured site UUID - --endpoint Override the API endpoint - --dry-run (scan only) Show the payload without posting - -Environment: - PATCHSTACK_SITE_UUID Site UUID - PATCHSTACK_ENDPOINT API endpoint (default: https://api.patchstack.com/monitor/pulse/manifest) - PATCHSTACK_TIMEOUT_MS Request timeout in ms (default: 30000) - -Precedence: CLI flag > environment variable > .patchstackrc.json. - -Examples: - npx @patchstack/connect scan - npx @patchstack/connect scan --dry-run - npx @patchstack/connect init 550e8400-e29b-41d4-a716-446655440000 - npx @patchstack/connect scan --site-uuid 550e8400-...-446655440000 -`; -var VALUE_FLAGS = /* @__PURE__ */ new Set(["site-uuid", "endpoint"]); -function parseArgs(argv) { - const args = argv.slice(2); - const positional = []; - const flags = /* @__PURE__ */ new Map(); - for (let i = 0; i < args.length; i++) { - const arg = args[i]; - if (!arg.startsWith("--")) { - positional.push(arg); - continue; - } - const stripped = arg.slice(2); - const eqIdx = stripped.indexOf("="); - if (eqIdx !== -1) { - flags.set(stripped.slice(0, eqIdx), stripped.slice(eqIdx + 1)); - continue; - } - const next = args[i + 1]; - if (VALUE_FLAGS.has(stripped) && next !== void 0 && !next.startsWith("--")) { - flags.set(stripped, next); - i++; - } else { - flags.set(stripped, true); - } - } - return { - command: positional.shift() ?? null, - positional, - flags - }; -} -function getStringFlag(flags, name) { - const value = flags.get(name); - return typeof value === "string" ? value : void 0; -} -async function runInit(args) { - const uuid = args.positional[0]; - if (!uuid) { - console.error("Error: site UUID is required.\n"); - console.error("Usage: patchstack-connect init "); - return 1; - } - if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(uuid)) { - console.error(`Error: "${uuid}" does not look like a valid UUID.`); - return 1; - } - const target = await writeConfigFile(process.cwd(), { siteUuid: uuid }); - console.log(`Wrote ${target}`); - console.log(""); - console.log("Next: run `npx @patchstack/connect scan` to send your first manifest."); - return 0; -} -async function runScan(args) { - const dryRun = args.flags.get("dry-run") === true; - const config = await resolveConfig({ - cwd: process.cwd(), - cliSiteUuid: getStringFlag(args.flags, "site-uuid"), - cliEndpoint: getStringFlag(args.flags, "endpoint") - }); - const manifest = await scanLockfile(process.cwd()); - const { payload, stats } = buildWirePayload(manifest); - console.log( - `Found ${payload.packages.length} unique package versions across ${stats.uniqueNames} package names in ${manifest.ecosystem} lockfile.` - ); - if (stats.duplicateNames.length > 0) { - console.log(`${stats.duplicateNames.length} package(s) appear at multiple versions:`); - if (stats.duplicateNames.length <= 10) { - console.log(` ${stats.duplicateNames.join(", ")}`); - } - } - if (dryRun) { - console.log(""); - if (config.siteUuid === null) { - console.log("--dry-run: no site UUID configured. A real run would provision one."); - } else { - console.log(`--dry-run: not posting to Patchstack (site UUID ${config.siteUuid}).`); - } - console.log("Payload preview:"); - const preview = JSON.stringify(payload, null, 2).split("\n"); - console.log(preview.slice(0, Math.min(preview.length, 30)).join("\n")); - if (preview.length > 30) { - console.log(` ... (${preview.length - 30} more lines)`); - } - return 0; - } - const provisioning = config.siteUuid === null; - if (provisioning) { - console.log("No site UUID configured \u2014 provisioning a new Patchstack site from this manifest\u2026"); - } - const response = await postManifest(config, payload); - if (provisioning && response.uuid !== void 0 && response.uuid.length > 0) { - const target = await persistSiteUuid(process.cwd(), response.uuid); - console.log(`Provisioned site ${response.uuid}. Saved UUID to ${target}.`); - } - if (response.stored) { - console.log(`Stored manifest #${response.manifest_id} (checksum ${response.checksum}).`); - } else if (response.reason === "duplicate") { - console.log("Manifest unchanged since last scan \u2014 nothing to store."); - } else { - console.log(`Server response: ${response.message ?? JSON.stringify(response)}`); - } - if (provisioning && response.uuid !== void 0 && response.uuid.length > 0) { - console.log(""); - console.log("Claim this site to view vulnerability reports in your Patchstack dashboard:"); - console.log(` ${buildClaimUrl(config.endpoint, response.uuid)}`); - } - return 0; -} -async function runStatus(args) { - const config = await resolveConfig({ - cwd: process.cwd(), - cliSiteUuid: getStringFlag(args.flags, "site-uuid"), - cliEndpoint: getStringFlag(args.flags, "endpoint") - }); - console.log(`Site UUID: ${config.siteUuid ?? "(none yet \u2014 the next `scan` will provision one)"}`); - console.log(`Endpoint: ${config.endpoint}`); - console.log(`Timeout: ${config.timeoutMs}ms`); - if (config.siteUuid !== null) { - console.log(`Claim URL: ${buildClaimUrl(config.endpoint, config.siteUuid)}`); - } - return 0; -} -async function main() { - const args = parseArgs(process.argv); - if (args.flags.has("help") || args.command === "help" || args.command === null) { - console.log(HELP); - return 0; - } - switch (args.command) { - case "init": - return runInit(args); - case "scan": - return runScan(args); - case "status": - return runStatus(args); - default: - console.error(`Unknown command: ${args.command} -`); - console.error(HELP); - return 1; - } -} -main().then((code) => process.exit(code)).catch((err) => { - if (err instanceof PatchstackError) { - console.error(`Error (${err.code}): ${err.message}`); - process.exit(1); - } - console.error("Unexpected error:", err); - process.exit(2); -}); -//# sourceMappingURL=cli.js.map \ No newline at end of file diff --git a/dist/cli.js.map b/dist/cli.js.map deleted file mode 100644 index 34d15b8..0000000 --- a/dist/cli.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/parsers/index.ts","../src/types.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/normalize.ts","../src/client.ts","../src/config.ts","../src/cli.ts"],"sourcesContent":["import { access } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Manifest, type PackageEntry } from '../types.js';\nimport { parseNpmLockfile } from './npm.js';\nimport { walkNodeModules } from './node_modules.js';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy = 'npm-lockfile' | 'node-modules-walk';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\nexport async function detectLockfile(cwd: string): Promise {\n const npmLock = path.join(cwd, 'package-lock.json');\n if (await exists(npmLock)) {\n return {\n ecosystem: 'npm',\n filePath: npmLock,\n filename: 'package-lock.json',\n strategy: 'npm-lockfile',\n };\n }\n\n const bunLock = path.join(cwd, 'bun.lock');\n if (await exists(bunLock)) {\n return {\n ecosystem: 'npm',\n filePath: bunLock,\n filename: 'bun.lock',\n strategy: 'node-modules-walk',\n };\n }\n\n const bunLockB = path.join(cwd, 'bun.lockb');\n if (await exists(bunLockB)) {\n return {\n ecosystem: 'npm',\n filePath: bunLockB,\n filename: 'bun.lockb',\n strategy: 'node-modules-walk',\n };\n }\n\n const yarnLock = path.join(cwd, 'yarn.lock');\n if (await exists(yarnLock)) {\n throw new PatchstackError(\n 'yarn.lock detected but not yet supported. Run `npm install` to generate a package-lock.json, or open an issue at github.com/patchstack/connect.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n const pnpmLock = path.join(cwd, 'pnpm-lock.yaml');\n if (await exists(pnpmLock)) {\n throw new PatchstackError(\n 'pnpm-lock.yaml detected but not yet supported. Open an issue at github.com/patchstack/connect to request support.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n throw new PatchstackError(\n `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`,\n 'LOCKFILE_NOT_FOUND',\n );\n}\n\nexport async function scanLockfile(cwd: string): Promise {\n const detected = await detectLockfile(cwd);\n const packages = await runStrategy(detected, cwd);\n return { ecosystem: detected.ecosystem, packages };\n}\n\nasync function runStrategy(\n detected: DetectedLockfile,\n cwd: string,\n): Promise {\n switch (detected.strategy) {\n case 'npm-lockfile':\n return parseNpmLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n","export type Ecosystem = 'npm' | 'composer';\n\nexport interface PackageEntry {\n name: string;\n version: string;\n path?: string;\n direct?: boolean;\n}\n\nexport interface Manifest {\n ecosystem: Ecosystem;\n packages: PackageEntry[];\n}\n\nexport interface Config {\n /**\n * The site UUID. `null` means we don't have one yet — `postManifest` will then\n * post to the bare endpoint, the server will provision a fresh site, and the\n * UUID it returns should be persisted via `persistSiteUuid()`.\n */\n siteUuid: string | null;\n endpoint: string;\n timeoutMs: number;\n}\n\nexport interface StoreManifestResponse {\n /** The UUID of the site the manifest was stored against. Always returned. */\n uuid?: string;\n stored: boolean;\n manifest_id?: number;\n checksum?: string;\n reason?: string;\n message?: string;\n error?: string;\n}\n\nexport class PatchstackError extends Error {\n constructor(\n message: string,\n public readonly code:\n | 'CONFIG_MISSING'\n | 'CONFIG_INVALID'\n | 'LOCKFILE_NOT_FOUND'\n | 'LOCKFILE_UNSUPPORTED'\n | 'LOCKFILE_PARSE_ERROR'\n | 'NETWORK_ERROR'\n | 'NETWORK_TIMEOUT'\n | 'SITE_NOT_FOUND'\n | 'VALIDATION_ERROR'\n | 'SERVER_ERROR',\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = 'PatchstackError';\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\ninterface LockfileV2Package {\n name?: string;\n version?: string;\n link?: boolean;\n resolved?: string;\n}\n\ninterface LockfileV2 {\n lockfileVersion: number;\n packages?: Record;\n dependencies?: Record;\n}\n\ninterface LockfileV1Dependency {\n version: string;\n dependencies?: Record;\n}\n\nexport async function parseNpmLockfile(lockfilePath: string): Promise {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, 'LOCKFILE_NOT_FOUND', cause);\n }\n\n let parsed: LockfileV2;\n try {\n parsed = JSON.parse(raw) as LockfileV2;\n } catch (cause) {\n throw new PatchstackError(`Lockfile at ${lockfilePath} is not valid JSON`, 'LOCKFILE_PARSE_ERROR', cause);\n }\n\n if (parsed.packages) {\n return extractFromV2(parsed.packages);\n }\n\n if (parsed.dependencies) {\n return extractFromV1(parsed.dependencies);\n }\n\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} has no \"packages\" or \"dependencies\" key`,\n 'LOCKFILE_PARSE_ERROR',\n );\n}\n\nfunction extractFromV2(packages: Record): PackageEntry[] {\n const entries: PackageEntry[] = [];\n\n for (const [pkgPath, pkg] of Object.entries(packages)) {\n if (pkgPath === '') {\n continue;\n }\n if (pkg.link === true) {\n continue;\n }\n if (typeof pkg.version !== 'string' || pkg.version.length === 0) {\n continue;\n }\n\n const name = pkg.name ?? extractNameFromPath(pkgPath);\n if (name === null) {\n continue;\n }\n\n entries.push({\n name,\n version: pkg.version,\n path: pkgPath,\n direct: isDirectV2(pkgPath),\n });\n }\n\n return entries;\n}\n\nfunction extractFromV1(\n deps: Record,\n acc: PackageEntry[] = [],\n depth = 0,\n): PackageEntry[] {\n for (const [name, dep] of Object.entries(deps)) {\n if (typeof dep.version === 'string' && dep.version.length > 0) {\n acc.push({ name, version: dep.version, direct: depth === 0 });\n }\n if (dep.dependencies) {\n extractFromV1(dep.dependencies, acc, depth + 1);\n }\n }\n return acc;\n}\n\nfunction extractNameFromPath(pkgPath: string): string | null {\n const segments = pkgPath.split('node_modules' + path.sep === pkgPath ? path.sep : '/');\n const parts = pkgPath.split('/');\n const nmIndex = parts.lastIndexOf('node_modules');\n if (nmIndex === -1 || nmIndex >= parts.length - 1) {\n return segments[segments.length - 1] ?? null;\n }\n const tail = parts.slice(nmIndex + 1);\n if (tail.length === 0) {\n return null;\n }\n const first = tail[0];\n if (first !== undefined && first.startsWith('@') && tail.length >= 2) {\n return `${first}/${tail[1]}`;\n }\n return first ?? null;\n}\n\nfunction isDirectV2(pkgPath: string): boolean {\n const parts = pkgPath.split('/');\n const nmCount = parts.filter((p) => p === 'node_modules').length;\n return nmCount === 1;\n}\n","import { lstat, readFile, readdir, stat } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\nexport async function walkNodeModules(cwd: string): Promise {\n const root = path.join(cwd, 'node_modules');\n\n try {\n const info = await stat(root);\n if (!info.isDirectory()) {\n throw new PatchstackError(\n `${root} exists but is not a directory.`,\n 'LOCKFILE_NOT_FOUND',\n );\n }\n } catch (cause) {\n if (cause instanceof PatchstackError) {\n throw cause;\n }\n throw new PatchstackError(\n `node_modules/ not found at ${cwd}. Install dependencies first (e.g. \\`bun install\\` or \\`npm install\\`).`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const entries: PackageEntry[] = [];\n await walk(root, entries, 0);\n return entries;\n}\n\nasync function walk(dir: string, acc: PackageEntry[], depth: number): Promise {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n return;\n }\n\n for (const name of names) {\n if (name.startsWith('.')) {\n continue;\n }\n\n const fullPath = path.join(dir, name);\n if (!(await isPlainDirectory(fullPath))) {\n continue;\n }\n\n if (name.startsWith('@')) {\n let subNames: string[];\n try {\n subNames = await readdir(fullPath);\n } catch {\n continue;\n }\n for (const sub of subNames) {\n if (sub.startsWith('.')) {\n continue;\n }\n const scopedDir = path.join(fullPath, sub);\n if (!(await isPlainDirectory(scopedDir))) {\n continue;\n }\n await readPackage(scopedDir, depth, acc);\n await walkNested(scopedDir, acc, depth);\n }\n continue;\n }\n\n await readPackage(fullPath, depth, acc);\n await walkNested(fullPath, acc, depth);\n }\n}\n\nasync function readPackage(\n pkgDir: string,\n depth: number,\n acc: PackageEntry[],\n): Promise {\n let raw: string;\n try {\n raw = await readFile(path.join(pkgDir, 'package.json'), 'utf8');\n } catch {\n return;\n }\n\n let parsed: { name?: unknown; version?: unknown };\n try {\n parsed = JSON.parse(raw) as { name?: unknown; version?: unknown };\n } catch {\n return;\n }\n\n if (typeof parsed.name !== 'string' || parsed.name.length === 0) {\n return;\n }\n if (typeof parsed.version !== 'string' || parsed.version.length === 0) {\n return;\n }\n\n acc.push({\n name: parsed.name,\n version: parsed.version,\n direct: depth === 0,\n });\n}\n\nasync function walkNested(\n pkgDir: string,\n acc: PackageEntry[],\n depth: number,\n): Promise {\n const nested = path.join(pkgDir, 'node_modules');\n if (!(await isPlainDirectory(nested))) {\n return;\n }\n await walk(nested, acc, depth + 1);\n}\n\nasync function isPlainDirectory(dir: string): Promise {\n try {\n const info = await lstat(dir);\n return info.isDirectory() && !info.isSymbolicLink();\n } catch {\n return false;\n }\n}\n","import type { Manifest, PackageEntry } from './types.js';\n\nexport interface WirePackage {\n name: string;\n version: string;\n}\n\nexport interface WirePayload {\n ecosystem: Manifest['ecosystem'];\n packages: WirePackage[];\n}\n\nexport interface NormalizeStats {\n uniqueNames: number;\n duplicateNames: string[];\n totalEntries: number;\n}\n\nexport interface NormalizeResult {\n payload: WirePayload;\n stats: NormalizeStats;\n}\n\nexport function buildWirePayload(manifest: Manifest): NormalizeResult {\n const seen = new Map>();\n const wirePackages: WirePackage[] = [];\n\n for (const entry of manifest.packages) {\n const versions = seen.get(entry.name);\n if (versions) {\n if (versions.has(entry.version)) {\n continue;\n }\n versions.add(entry.version);\n } else {\n seen.set(entry.name, new Set([entry.version]));\n }\n wirePackages.push({ name: entry.name, version: entry.version });\n }\n\n wirePackages.sort((a, b) => {\n if (a.name === b.name) {\n return compareVersions(a.version, b.version);\n }\n return a.name < b.name ? -1 : 1;\n });\n\n const duplicateNames: string[] = [];\n for (const [name, versions] of seen) {\n if (versions.size > 1) {\n duplicateNames.push(name);\n }\n }\n\n return {\n payload: { ecosystem: manifest.ecosystem, packages: wirePackages },\n stats: {\n uniqueNames: seen.size,\n duplicateNames,\n totalEntries: manifest.packages.length,\n },\n };\n}\n\nexport function compareVersions(a: string, b: string): number {\n if (a === b) {\n return 0;\n }\n\n const [aBase, aPre] = splitPrerelease(a);\n const [bBase, bPre] = splitPrerelease(b);\n\n const baseCmp = compareSegments(aBase.split('.'), bBase.split('.'));\n if (baseCmp !== 0) {\n return baseCmp;\n }\n\n if (aPre === null && bPre === null) {\n return 0;\n }\n if (aPre === null) {\n return 1;\n }\n if (bPre === null) {\n return -1;\n }\n return compareSegments(aPre.split('.'), bPre.split('.'));\n}\n\nfunction splitPrerelease(version: string): [string, string | null] {\n const cleaned = version.replace(/^[v=]+/, '').split('+')[0]!;\n const dashIndex = cleaned.indexOf('-');\n if (dashIndex === -1) {\n return [cleaned, null];\n }\n return [cleaned.slice(0, dashIndex), cleaned.slice(dashIndex + 1)];\n}\n\nfunction compareSegments(a: string[], b: string[]): number {\n const max = Math.max(a.length, b.length);\n for (let i = 0; i < max; i++) {\n const aPart = a[i];\n const bPart = b[i];\n if (aPart === undefined) {\n return -1;\n }\n if (bPart === undefined) {\n return 1;\n }\n const aNum = /^\\d+$/.test(aPart);\n const bNum = /^\\d+$/.test(bPart);\n if (aNum && bNum) {\n const diff = Number(aPart) - Number(bPart);\n if (diff !== 0) {\n return diff < 0 ? -1 : 1;\n }\n continue;\n }\n if (aNum) {\n return -1;\n }\n if (bNum) {\n return 1;\n }\n if (aPart < bPart) {\n return -1;\n }\n if (aPart > bPart) {\n return 1;\n }\n }\n return 0;\n}\n\nexport function findPackageInManifest(\n manifest: Manifest,\n name: string,\n): PackageEntry[] {\n return manifest.packages.filter((p) => p.name === name);\n}\n","import { PatchstackError, type Config, type StoreManifestResponse } from './types.js';\nimport type { WirePayload } from './normalize.js';\n\nexport const DEFAULT_ENDPOINT = 'https://api.patchstack.com/monitor/pulse/manifest';\nexport const DEFAULT_TIMEOUT_MS = 30_000;\n\nexport function buildEndpointUrl(base: string, siteUuid?: string | null): string {\n const trimmed = base.replace(/\\/$/, '');\n return siteUuid !== undefined && siteUuid !== null && siteUuid.length > 0\n ? `${trimmed}/${encodeURIComponent(siteUuid)}`\n : trimmed;\n}\n\n/**\n * Build the claim URL for a site. The claim page lives on the same origin as\n * the API endpoint, at `/monitor/claim?site=`. Using the API endpoint's\n * origin (rather than a hard-coded https://api.patchstack.com) means staging,\n * ngrok tunnels and local dev environments all produce a claim URL on the same\n * host the connector is already talking to.\n */\nexport function buildClaimUrl(endpoint: string, siteUuid: string): string {\n const origin = new URL(endpoint).origin;\n return `${origin}/monitor/claim?site=${encodeURIComponent(siteUuid)}`;\n}\n\nexport async function postManifest(\n config: Config,\n payload: WirePayload,\n): Promise {\n const url = buildEndpointUrl(config.endpoint, config.siteUuid);\n const timeoutMs = config.timeoutMs;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'User-Agent': '@patchstack/connect',\n },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (cause) {\n if (isTimeoutError(cause)) {\n throw new PatchstackError(\n `Patchstack request to ${url} timed out after ${timeoutMs}ms. Override with PATCHSTACK_TIMEOUT_MS.`,\n 'NETWORK_TIMEOUT',\n cause,\n );\n }\n throw new PatchstackError(\n `Could not reach Patchstack at ${url}. Check your network connection.`,\n 'NETWORK_ERROR',\n cause,\n );\n }\n\n const text = await response.text();\n let body: StoreManifestResponse | null = null;\n try {\n body = text.length > 0 ? (JSON.parse(text) as StoreManifestResponse) : null;\n } catch {\n body = null;\n }\n\n if (response.status === 404) {\n throw new PatchstackError(\n body?.error ?? 'Site not found. Check that your site UUID is correct and that the app is registered as a Pulse app in your Patchstack dashboard.',\n 'SITE_NOT_FOUND',\n );\n }\n\n if (response.status === 422) {\n throw new PatchstackError(\n body?.message ?? 'Patchstack rejected the manifest payload (validation failed).',\n 'VALIDATION_ERROR',\n );\n }\n\n if (response.status < 200 || response.status >= 300) {\n throw new PatchstackError(\n `Patchstack returned ${response.status}: ${text.slice(0, 200)}`,\n 'SERVER_ERROR',\n );\n }\n\n if (body === null) {\n throw new PatchstackError('Patchstack returned an empty response.', 'SERVER_ERROR');\n }\n\n return body;\n}\n\nfunction isTimeoutError(cause: unknown): boolean {\n if (cause instanceof Error) {\n return cause.name === 'TimeoutError' || cause.name === 'AbortError';\n }\n return false;\n}\n","import { readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Config } from './types.js';\nimport { DEFAULT_ENDPOINT, DEFAULT_TIMEOUT_MS } from './client.js';\n\nconst CONFIG_FILENAME = '.patchstackrc.json';\n\ninterface ConfigFile {\n siteUuid?: string;\n endpoint?: string;\n timeoutMs?: number;\n}\n\nexport interface ResolveConfigOptions {\n cwd: string;\n cliSiteUuid?: string;\n cliEndpoint?: string;\n /**\n * When true, resolveConfig throws CONFIG_MISSING if no site UUID is configured.\n * Defaults to false: callers that can run without a UUID (the first `scan` after\n * `npm install`) just get `siteUuid: null` back and learn the UUID from the\n * server response.\n */\n requireSiteUuid?: boolean;\n}\n\nexport async function resolveConfig(options: ResolveConfigOptions): Promise {\n const fromFile = await readConfigFile(options.cwd);\n const fromEnv = readEnv();\n\n const siteUuid =\n options.cliSiteUuid ??\n fromEnv.siteUuid ??\n fromFile.siteUuid ??\n null;\n\n const endpoint =\n options.cliEndpoint ??\n fromEnv.endpoint ??\n fromFile.endpoint ??\n DEFAULT_ENDPOINT;\n\n const timeoutMs = fromEnv.timeoutMs ?? fromFile.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n if (siteUuid !== null && siteUuid.length > 0 && !isUuid(siteUuid)) {\n throw new PatchstackError(\n `Site UUID \"${siteUuid}\" does not look like a valid UUID.`,\n 'CONFIG_INVALID',\n );\n }\n\n if (options.requireSiteUuid && (siteUuid === null || siteUuid.length === 0)) {\n throw new PatchstackError(\n 'No site UUID configured. Run `patchstack-connect scan` to provision one, or set PATCHSTACK_SITE_UUID.',\n 'CONFIG_MISSING',\n );\n }\n\n return {\n siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid,\n endpoint,\n timeoutMs,\n };\n}\n\nexport async function writeConfigFile(cwd: string, config: ConfigFile): Promise {\n const target = path.join(cwd, CONFIG_FILENAME);\n const content = JSON.stringify(config, null, 2) + '\\n';\n await writeFile(target, content, 'utf8');\n return target;\n}\n\n/**\n * Merge a new siteUuid into the existing `.patchstackrc.json` (or create it).\n * Preserves any `endpoint` / `timeoutMs` the user already wrote.\n */\nexport async function persistSiteUuid(cwd: string, siteUuid: string): Promise {\n const existing = await readConfigFile(cwd);\n return writeConfigFile(cwd, { ...existing, siteUuid });\n}\n\nasync function readConfigFile(cwd: string): Promise {\n const target = path.join(cwd, CONFIG_FILENAME);\n let raw: string;\n try {\n raw = await readFile(target, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return {};\n }\n throw new PatchstackError(\n `Could not read ${target}: ${(err as Error).message}`,\n 'CONFIG_INVALID',\n err,\n );\n }\n\n try {\n return JSON.parse(raw) as ConfigFile;\n } catch (err) {\n throw new PatchstackError(\n `Config file ${target} contains invalid JSON.`,\n 'CONFIG_INVALID',\n err,\n );\n }\n}\n\nfunction readEnv(): ConfigFile {\n const timeoutRaw = process.env.PATCHSTACK_TIMEOUT_MS;\n let timeoutMs: number | undefined;\n if (timeoutRaw !== undefined && timeoutRaw.length > 0) {\n const parsed = Number(timeoutRaw);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n throw new PatchstackError(\n `PATCHSTACK_TIMEOUT_MS must be a positive number; got \"${timeoutRaw}\".`,\n 'CONFIG_INVALID',\n );\n }\n timeoutMs = parsed;\n }\n return {\n siteUuid: process.env.PATCHSTACK_SITE_UUID ?? undefined,\n endpoint: process.env.PATCHSTACK_ENDPOINT ?? undefined,\n timeoutMs,\n };\n}\n\nfunction isUuid(value: string): boolean {\n return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);\n}\n","import { scanLockfile } from './parsers/index.js';\nimport { buildWirePayload } from './normalize.js';\nimport { buildClaimUrl, postManifest } from './client.js';\nimport { persistSiteUuid, resolveConfig, writeConfigFile } from './config.js';\nimport { PatchstackError } from './types.js';\n\nconst HELP = `@patchstack/connect — scan your lockfile and report packages to Patchstack.\n\nUsage:\n patchstack-connect scan [options] Scan lockfile and POST to Patchstack.\n If no UUID is configured, the server\n provisions one and we persist it.\n patchstack-connect init Optional: pre-seed .patchstackrc.json\n with an existing site UUID\n patchstack-connect status [options] Show current configuration\n patchstack-connect help Print this message\n\nOptions (for scan and status):\n --site-uuid Override the configured site UUID\n --endpoint Override the API endpoint\n --dry-run (scan only) Show the payload without posting\n\nEnvironment:\n PATCHSTACK_SITE_UUID Site UUID\n PATCHSTACK_ENDPOINT API endpoint (default: https://api.patchstack.com/monitor/pulse/manifest)\n PATCHSTACK_TIMEOUT_MS Request timeout in ms (default: 30000)\n\nPrecedence: CLI flag > environment variable > .patchstackrc.json.\n\nExamples:\n npx @patchstack/connect scan\n npx @patchstack/connect scan --dry-run\n npx @patchstack/connect init 550e8400-e29b-41d4-a716-446655440000\n npx @patchstack/connect scan --site-uuid 550e8400-...-446655440000\n`;\n\nconst VALUE_FLAGS = new Set(['site-uuid', 'endpoint']);\n\ninterface ParsedArgs {\n command: string | null;\n positional: string[];\n flags: Map;\n}\n\nfunction parseArgs(argv: string[]): ParsedArgs {\n const args = argv.slice(2);\n const positional: string[] = [];\n const flags = new Map();\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n if (!arg.startsWith('--')) {\n positional.push(arg);\n continue;\n }\n const stripped = arg.slice(2);\n const eqIdx = stripped.indexOf('=');\n if (eqIdx !== -1) {\n flags.set(stripped.slice(0, eqIdx), stripped.slice(eqIdx + 1));\n continue;\n }\n const next = args[i + 1];\n if (VALUE_FLAGS.has(stripped) && next !== undefined && !next.startsWith('--')) {\n flags.set(stripped, next);\n i++;\n } else {\n flags.set(stripped, true);\n }\n }\n\n return {\n command: positional.shift() ?? null,\n positional,\n flags,\n };\n}\n\nfunction getStringFlag(flags: Map, name: string): string | undefined {\n const value = flags.get(name);\n return typeof value === 'string' ? value : undefined;\n}\n\nasync function runInit(args: ParsedArgs): Promise {\n const uuid = args.positional[0];\n if (!uuid) {\n console.error('Error: site UUID is required.\\n');\n console.error('Usage: patchstack-connect init ');\n return 1;\n }\n if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(uuid)) {\n console.error(`Error: \"${uuid}\" does not look like a valid UUID.`);\n return 1;\n }\n\n const target = await writeConfigFile(process.cwd(), { siteUuid: uuid });\n console.log(`Wrote ${target}`);\n console.log('');\n console.log('Next: run `npx @patchstack/connect scan` to send your first manifest.');\n return 0;\n}\n\nasync function runScan(args: ParsedArgs): Promise {\n const dryRun = args.flags.get('dry-run') === true;\n const config = await resolveConfig({\n cwd: process.cwd(),\n cliSiteUuid: getStringFlag(args.flags, 'site-uuid'),\n cliEndpoint: getStringFlag(args.flags, 'endpoint'),\n });\n const manifest = await scanLockfile(process.cwd());\n const { payload, stats } = buildWirePayload(manifest);\n\n console.log(\n `Found ${payload.packages.length} unique package versions across ${stats.uniqueNames} package names in ${manifest.ecosystem} lockfile.`,\n );\n if (stats.duplicateNames.length > 0) {\n console.log(`${stats.duplicateNames.length} package(s) appear at multiple versions:`);\n if (stats.duplicateNames.length <= 10) {\n console.log(` ${stats.duplicateNames.join(', ')}`);\n }\n }\n\n if (dryRun) {\n console.log('');\n if (config.siteUuid === null) {\n console.log('--dry-run: no site UUID configured. A real run would provision one.');\n } else {\n console.log(`--dry-run: not posting to Patchstack (site UUID ${config.siteUuid}).`);\n }\n console.log('Payload preview:');\n const preview = JSON.stringify(payload, null, 2).split('\\n');\n console.log(preview.slice(0, Math.min(preview.length, 30)).join('\\n'));\n if (preview.length > 30) {\n console.log(` ... (${preview.length - 30} more lines)`);\n }\n return 0;\n }\n\n const provisioning = config.siteUuid === null;\n if (provisioning) {\n console.log('No site UUID configured — provisioning a new Patchstack site from this manifest…');\n }\n\n const response = await postManifest(config, payload);\n\n // The server always returns the UUID. If we didn't have one, persist it so\n // every subsequent scan targets the same site.\n if (provisioning && response.uuid !== undefined && response.uuid.length > 0) {\n const target = await persistSiteUuid(process.cwd(), response.uuid);\n console.log(`Provisioned site ${response.uuid}. Saved UUID to ${target}.`);\n }\n\n if (response.stored) {\n console.log(`Stored manifest #${response.manifest_id} (checksum ${response.checksum}).`);\n } else if (response.reason === 'duplicate') {\n console.log('Manifest unchanged since last scan — nothing to store.');\n } else {\n console.log(`Server response: ${response.message ?? JSON.stringify(response)}`);\n }\n\n // On the first scan (provisioning), surface the claim URL so the user can\n // attach this site to their Patchstack account. `npx @patchstack/connect status`\n // re-displays it any time.\n if (provisioning && response.uuid !== undefined && response.uuid.length > 0) {\n console.log('');\n console.log('Claim this site to view vulnerability reports in your Patchstack dashboard:');\n console.log(` ${buildClaimUrl(config.endpoint, response.uuid)}`);\n }\n\n return 0;\n}\n\nasync function runStatus(args: ParsedArgs): Promise {\n const config = await resolveConfig({\n cwd: process.cwd(),\n cliSiteUuid: getStringFlag(args.flags, 'site-uuid'),\n cliEndpoint: getStringFlag(args.flags, 'endpoint'),\n });\n console.log(`Site UUID: ${config.siteUuid ?? '(none yet — the next `scan` will provision one)'}`);\n console.log(`Endpoint: ${config.endpoint}`);\n console.log(`Timeout: ${config.timeoutMs}ms`);\n if (config.siteUuid !== null) {\n console.log(`Claim URL: ${buildClaimUrl(config.endpoint, config.siteUuid)}`);\n }\n return 0;\n}\n\nasync function main(): Promise {\n const args = parseArgs(process.argv);\n\n if (args.flags.has('help') || args.command === 'help' || args.command === null) {\n console.log(HELP);\n return 0;\n }\n\n switch (args.command) {\n case 'init':\n return runInit(args);\n case 'scan':\n return runScan(args);\n case 'status':\n return runStatus(args);\n default:\n console.error(`Unknown command: ${args.command}\\n`);\n console.error(HELP);\n return 1;\n }\n}\n\nmain()\n .then((code) => process.exit(code))\n .catch((err: unknown) => {\n if (err instanceof PatchstackError) {\n console.error(`Error (${err.code}): ${err.message}`);\n process.exit(1);\n }\n console.error('Unexpected error:', err);\n process.exit(2);\n });\n"],"mappings":"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","names":["path","readFile","path","path","readFile","path","readFile","path","path","readFile"]} \ No newline at end of file diff --git a/dist/index.cjs b/dist/index.cjs deleted file mode 100644 index cf6e14a..0000000 --- a/dist/index.cjs +++ /dev/null @@ -1,627 +0,0 @@ -"use strict"; -var __create = Object.create; -var __defProp = Object.defineProperty; -var __getOwnPropDesc = Object.getOwnPropertyDescriptor; -var __getOwnPropNames = Object.getOwnPropertyNames; -var __getProtoOf = Object.getPrototypeOf; -var __hasOwnProp = Object.prototype.hasOwnProperty; -var __export = (target, all) => { - for (var name in all) - __defProp(target, name, { get: all[name], enumerable: true }); -}; -var __copyProps = (to, from, except, desc) => { - if (from && typeof from === "object" || typeof from === "function") { - for (let key of __getOwnPropNames(from)) - if (!__hasOwnProp.call(to, key) && key !== except) - __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); - } - return to; -}; -var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( - // If the importer is in node compatibility mode or this is not an ESM - // file that has been converted to a CommonJS file using a Babel- - // compatible transform (i.e. "__esModule" has not been set), then set - // "default" to the CommonJS "module.exports" for node compatibility. - isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, - mod -)); -var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); - -// src/index.ts -var src_exports = {}; -__export(src_exports, { - DEFAULT_ENDPOINT: () => DEFAULT_ENDPOINT, - PatchstackError: () => PatchstackError, - buildClaimUrl: () => buildClaimUrl, - buildEndpointUrl: () => buildEndpointUrl, - buildWirePayload: () => buildWirePayload, - compareVersions: () => compareVersions, - detectLockfile: () => detectLockfile, - persistSiteUuid: () => persistSiteUuid, - postManifest: () => postManifest, - resolveConfig: () => resolveConfig, - scanAndReport: () => scanAndReport, - scanLockfile: () => scanLockfile, - writeConfigFile: () => writeConfigFile -}); -module.exports = __toCommonJS(src_exports); - -// src/parsers/index.ts -var import_promises3 = require("fs/promises"); -var import_node_path3 = __toESM(require("path"), 1); - -// src/types.ts -var PatchstackError = class extends Error { - constructor(message, code, cause) { - super(message); - this.code = code; - this.cause = cause; - this.name = "PatchstackError"; - } - code; - cause; -}; - -// src/parsers/npm.ts -var import_promises = require("fs/promises"); -var import_node_path = __toESM(require("path"), 1); -async function parseNpmLockfile(lockfilePath) { - let raw; - try { - raw = await (0, import_promises.readFile)(lockfilePath, "utf8"); - } catch (cause) { - throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, "LOCKFILE_NOT_FOUND", cause); - } - let parsed; - try { - parsed = JSON.parse(raw); - } catch (cause) { - throw new PatchstackError(`Lockfile at ${lockfilePath} is not valid JSON`, "LOCKFILE_PARSE_ERROR", cause); - } - if (parsed.packages) { - return extractFromV2(parsed.packages); - } - if (parsed.dependencies) { - return extractFromV1(parsed.dependencies); - } - throw new PatchstackError( - `Lockfile at ${lockfilePath} has no "packages" or "dependencies" key`, - "LOCKFILE_PARSE_ERROR" - ); -} -function extractFromV2(packages) { - const entries = []; - for (const [pkgPath, pkg] of Object.entries(packages)) { - if (pkgPath === "") { - continue; - } - if (pkg.link === true) { - continue; - } - if (typeof pkg.version !== "string" || pkg.version.length === 0) { - continue; - } - const name = pkg.name ?? extractNameFromPath(pkgPath); - if (name === null) { - continue; - } - entries.push({ - name, - version: pkg.version, - path: pkgPath, - direct: isDirectV2(pkgPath) - }); - } - return entries; -} -function extractFromV1(deps, acc = [], depth = 0) { - for (const [name, dep] of Object.entries(deps)) { - if (typeof dep.version === "string" && dep.version.length > 0) { - acc.push({ name, version: dep.version, direct: depth === 0 }); - } - if (dep.dependencies) { - extractFromV1(dep.dependencies, acc, depth + 1); - } - } - return acc; -} -function extractNameFromPath(pkgPath) { - const segments = pkgPath.split("node_modules" + import_node_path.default.sep === pkgPath ? import_node_path.default.sep : "/"); - const parts = pkgPath.split("/"); - const nmIndex = parts.lastIndexOf("node_modules"); - if (nmIndex === -1 || nmIndex >= parts.length - 1) { - return segments[segments.length - 1] ?? null; - } - const tail = parts.slice(nmIndex + 1); - if (tail.length === 0) { - return null; - } - const first = tail[0]; - if (first !== void 0 && first.startsWith("@") && tail.length >= 2) { - return `${first}/${tail[1]}`; - } - return first ?? null; -} -function isDirectV2(pkgPath) { - const parts = pkgPath.split("/"); - const nmCount = parts.filter((p) => p === "node_modules").length; - return nmCount === 1; -} - -// src/parsers/node_modules.ts -var import_promises2 = require("fs/promises"); -var import_node_path2 = __toESM(require("path"), 1); -async function walkNodeModules(cwd) { - const root = import_node_path2.default.join(cwd, "node_modules"); - try { - const info = await (0, import_promises2.stat)(root); - if (!info.isDirectory()) { - throw new PatchstackError( - `${root} exists but is not a directory.`, - "LOCKFILE_NOT_FOUND" - ); - } - } catch (cause) { - if (cause instanceof PatchstackError) { - throw cause; - } - throw new PatchstackError( - `node_modules/ not found at ${cwd}. Install dependencies first (e.g. \`bun install\` or \`npm install\`).`, - "LOCKFILE_NOT_FOUND", - cause - ); - } - const entries = []; - await walk(root, entries, 0); - return entries; -} -async function walk(dir, acc, depth) { - let names; - try { - names = await (0, import_promises2.readdir)(dir); - } catch { - return; - } - for (const name of names) { - if (name.startsWith(".")) { - continue; - } - const fullPath = import_node_path2.default.join(dir, name); - if (!await isPlainDirectory(fullPath)) { - continue; - } - if (name.startsWith("@")) { - let subNames; - try { - subNames = await (0, import_promises2.readdir)(fullPath); - } catch { - continue; - } - for (const sub of subNames) { - if (sub.startsWith(".")) { - continue; - } - const scopedDir = import_node_path2.default.join(fullPath, sub); - if (!await isPlainDirectory(scopedDir)) { - continue; - } - await readPackage(scopedDir, depth, acc); - await walkNested(scopedDir, acc, depth); - } - continue; - } - await readPackage(fullPath, depth, acc); - await walkNested(fullPath, acc, depth); - } -} -async function readPackage(pkgDir, depth, acc) { - let raw; - try { - raw = await (0, import_promises2.readFile)(import_node_path2.default.join(pkgDir, "package.json"), "utf8"); - } catch { - return; - } - let parsed; - try { - parsed = JSON.parse(raw); - } catch { - return; - } - if (typeof parsed.name !== "string" || parsed.name.length === 0) { - return; - } - if (typeof parsed.version !== "string" || parsed.version.length === 0) { - return; - } - acc.push({ - name: parsed.name, - version: parsed.version, - direct: depth === 0 - }); -} -async function walkNested(pkgDir, acc, depth) { - const nested = import_node_path2.default.join(pkgDir, "node_modules"); - if (!await isPlainDirectory(nested)) { - return; - } - await walk(nested, acc, depth + 1); -} -async function isPlainDirectory(dir) { - try { - const info = await (0, import_promises2.lstat)(dir); - return info.isDirectory() && !info.isSymbolicLink(); - } catch { - return false; - } -} - -// src/parsers/index.ts -async function detectLockfile(cwd) { - const npmLock = import_node_path3.default.join(cwd, "package-lock.json"); - if (await exists(npmLock)) { - return { - ecosystem: "npm", - filePath: npmLock, - filename: "package-lock.json", - strategy: "npm-lockfile" - }; - } - const bunLock = import_node_path3.default.join(cwd, "bun.lock"); - if (await exists(bunLock)) { - return { - ecosystem: "npm", - filePath: bunLock, - filename: "bun.lock", - strategy: "node-modules-walk" - }; - } - const bunLockB = import_node_path3.default.join(cwd, "bun.lockb"); - if (await exists(bunLockB)) { - return { - ecosystem: "npm", - filePath: bunLockB, - filename: "bun.lockb", - strategy: "node-modules-walk" - }; - } - const yarnLock = import_node_path3.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" - ); - } - const pnpmLock = import_node_path3.default.join(cwd, "pnpm-lock.yaml"); - if (await exists(pnpmLock)) { - throw new PatchstackError( - "pnpm-lock.yaml detected but not yet supported. Open an issue at github.com/patchstack/connect to request support.", - "LOCKFILE_UNSUPPORTED" - ); - } - throw new PatchstackError( - `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`, - "LOCKFILE_NOT_FOUND" - ); -} -async function scanLockfile(cwd) { - const detected = await detectLockfile(cwd); - const packages = await runStrategy(detected, cwd); - return { ecosystem: detected.ecosystem, packages }; -} -async function runStrategy(detected, cwd) { - switch (detected.strategy) { - case "npm-lockfile": - return parseNpmLockfile(detected.filePath); - case "node-modules-walk": - return walkNodeModules(cwd); - } -} -async function exists(filePath) { - try { - await (0, import_promises3.access)(filePath); - return true; - } catch { - return false; - } -} - -// src/normalize.ts -function buildWirePayload(manifest) { - const seen = /* @__PURE__ */ new Map(); - const wirePackages = []; - for (const entry of manifest.packages) { - const versions = seen.get(entry.name); - if (versions) { - if (versions.has(entry.version)) { - continue; - } - versions.add(entry.version); - } else { - seen.set(entry.name, /* @__PURE__ */ new Set([entry.version])); - } - wirePackages.push({ name: entry.name, version: entry.version }); - } - wirePackages.sort((a, b) => { - if (a.name === b.name) { - return compareVersions(a.version, b.version); - } - return a.name < b.name ? -1 : 1; - }); - const duplicateNames = []; - for (const [name, versions] of seen) { - if (versions.size > 1) { - duplicateNames.push(name); - } - } - return { - payload: { ecosystem: manifest.ecosystem, packages: wirePackages }, - stats: { - uniqueNames: seen.size, - duplicateNames, - totalEntries: manifest.packages.length - } - }; -} -function compareVersions(a, b) { - if (a === b) { - return 0; - } - const [aBase, aPre] = splitPrerelease(a); - const [bBase, bPre] = splitPrerelease(b); - const baseCmp = compareSegments(aBase.split("."), bBase.split(".")); - if (baseCmp !== 0) { - return baseCmp; - } - if (aPre === null && bPre === null) { - return 0; - } - if (aPre === null) { - return 1; - } - if (bPre === null) { - return -1; - } - return compareSegments(aPre.split("."), bPre.split(".")); -} -function splitPrerelease(version) { - const cleaned = version.replace(/^[v=]+/, "").split("+")[0]; - const dashIndex = cleaned.indexOf("-"); - if (dashIndex === -1) { - return [cleaned, null]; - } - return [cleaned.slice(0, dashIndex), cleaned.slice(dashIndex + 1)]; -} -function compareSegments(a, b) { - const max = Math.max(a.length, b.length); - for (let i = 0; i < max; i++) { - const aPart = a[i]; - const bPart = b[i]; - if (aPart === void 0) { - return -1; - } - if (bPart === void 0) { - return 1; - } - const aNum = /^\d+$/.test(aPart); - const bNum = /^\d+$/.test(bPart); - if (aNum && bNum) { - const diff = Number(aPart) - Number(bPart); - if (diff !== 0) { - return diff < 0 ? -1 : 1; - } - continue; - } - if (aNum) { - return -1; - } - if (bNum) { - return 1; - } - if (aPart < bPart) { - return -1; - } - if (aPart > bPart) { - return 1; - } - } - return 0; -} - -// src/client.ts -var DEFAULT_ENDPOINT = "https://api.patchstack.com/monitor/pulse/manifest"; -var DEFAULT_TIMEOUT_MS = 3e4; -function buildEndpointUrl(base, siteUuid) { - const trimmed = base.replace(/\/$/, ""); - return siteUuid !== void 0 && siteUuid !== null && siteUuid.length > 0 ? `${trimmed}/${encodeURIComponent(siteUuid)}` : trimmed; -} -function buildClaimUrl(endpoint, siteUuid) { - const origin = new URL(endpoint).origin; - return `${origin}/monitor/claim?site=${encodeURIComponent(siteUuid)}`; -} -async function postManifest(config, payload) { - const url = buildEndpointUrl(config.endpoint, config.siteUuid); - const timeoutMs = config.timeoutMs; - let response; - try { - response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - "User-Agent": "@patchstack/connect" - }, - body: JSON.stringify(payload), - signal: AbortSignal.timeout(timeoutMs) - }); - } catch (cause) { - if (isTimeoutError(cause)) { - throw new PatchstackError( - `Patchstack request to ${url} timed out after ${timeoutMs}ms. Override with PATCHSTACK_TIMEOUT_MS.`, - "NETWORK_TIMEOUT", - cause - ); - } - throw new PatchstackError( - `Could not reach Patchstack at ${url}. Check your network connection.`, - "NETWORK_ERROR", - cause - ); - } - const text = await response.text(); - let body = null; - try { - body = text.length > 0 ? JSON.parse(text) : null; - } catch { - body = null; - } - if (response.status === 404) { - throw new PatchstackError( - 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.", - "SITE_NOT_FOUND" - ); - } - if (response.status === 422) { - throw new PatchstackError( - body?.message ?? "Patchstack rejected the manifest payload (validation failed).", - "VALIDATION_ERROR" - ); - } - if (response.status < 200 || response.status >= 300) { - throw new PatchstackError( - `Patchstack returned ${response.status}: ${text.slice(0, 200)}`, - "SERVER_ERROR" - ); - } - if (body === null) { - throw new PatchstackError("Patchstack returned an empty response.", "SERVER_ERROR"); - } - return body; -} -function isTimeoutError(cause) { - if (cause instanceof Error) { - return cause.name === "TimeoutError" || cause.name === "AbortError"; - } - return false; -} - -// src/config.ts -var import_promises4 = require("fs/promises"); -var import_node_path4 = __toESM(require("path"), 1); -var CONFIG_FILENAME = ".patchstackrc.json"; -async function resolveConfig(options) { - const fromFile = await readConfigFile(options.cwd); - const fromEnv = readEnv(); - const siteUuid = options.cliSiteUuid ?? fromEnv.siteUuid ?? fromFile.siteUuid ?? null; - const endpoint = options.cliEndpoint ?? fromEnv.endpoint ?? fromFile.endpoint ?? DEFAULT_ENDPOINT; - const timeoutMs = fromEnv.timeoutMs ?? fromFile.timeoutMs ?? DEFAULT_TIMEOUT_MS; - if (siteUuid !== null && siteUuid.length > 0 && !isUuid(siteUuid)) { - throw new PatchstackError( - `Site UUID "${siteUuid}" does not look like a valid UUID.`, - "CONFIG_INVALID" - ); - } - if (options.requireSiteUuid && (siteUuid === null || siteUuid.length === 0)) { - throw new PatchstackError( - "No site UUID configured. Run `patchstack-connect scan` to provision one, or set PATCHSTACK_SITE_UUID.", - "CONFIG_MISSING" - ); - } - return { - siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid, - endpoint, - timeoutMs - }; -} -async function writeConfigFile(cwd, config) { - const target = import_node_path4.default.join(cwd, CONFIG_FILENAME); - const content = JSON.stringify(config, null, 2) + "\n"; - await (0, import_promises4.writeFile)(target, content, "utf8"); - return target; -} -async function persistSiteUuid(cwd, siteUuid) { - const existing = await readConfigFile(cwd); - return writeConfigFile(cwd, { ...existing, siteUuid }); -} -async function readConfigFile(cwd) { - const target = import_node_path4.default.join(cwd, CONFIG_FILENAME); - let raw; - try { - raw = await (0, import_promises4.readFile)(target, "utf8"); - } catch (err) { - if (err.code === "ENOENT") { - return {}; - } - throw new PatchstackError( - `Could not read ${target}: ${err.message}`, - "CONFIG_INVALID", - err - ); - } - try { - return JSON.parse(raw); - } catch (err) { - throw new PatchstackError( - `Config file ${target} contains invalid JSON.`, - "CONFIG_INVALID", - err - ); - } -} -function readEnv() { - const timeoutRaw = process.env.PATCHSTACK_TIMEOUT_MS; - let timeoutMs; - if (timeoutRaw !== void 0 && timeoutRaw.length > 0) { - const parsed = Number(timeoutRaw); - if (!Number.isFinite(parsed) || parsed <= 0) { - throw new PatchstackError( - `PATCHSTACK_TIMEOUT_MS must be a positive number; got "${timeoutRaw}".`, - "CONFIG_INVALID" - ); - } - timeoutMs = parsed; - } - return { - siteUuid: process.env.PATCHSTACK_SITE_UUID ?? void 0, - endpoint: process.env.PATCHSTACK_ENDPOINT ?? void 0, - timeoutMs - }; -} -function isUuid(value) { - return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value); -} - -// src/index.ts -async function scanAndReport(options = {}) { - const cwd = options.cwd ?? process.cwd(); - const config = options.config ?? await resolveConfig({ cwd }); - const manifest = await scanLockfile(cwd); - const { payload, stats } = buildWirePayload(manifest); - const response = await postManifest(config, payload); - if (config.siteUuid === null && response.uuid !== void 0 && response.uuid.length > 0) { - await persistSiteUuid(cwd, response.uuid); - } - return { - manifest, - response, - duplicateNames: stats.duplicateNames, - uniqueNames: stats.uniqueNames, - totalEntries: stats.totalEntries - }; -} -// Annotate the CommonJS export names for ESM import in node: -0 && (module.exports = { - DEFAULT_ENDPOINT, - PatchstackError, - buildClaimUrl, - buildEndpointUrl, - buildWirePayload, - compareVersions, - detectLockfile, - persistSiteUuid, - postManifest, - resolveConfig, - scanAndReport, - scanLockfile, - writeConfigFile -}); -//# sourceMappingURL=index.cjs.map \ No newline at end of file diff --git a/dist/index.cjs.map b/dist/index.cjs.map deleted file mode 100644 index 7ae8720..0000000 --- a/dist/index.cjs.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/index.ts","../src/parsers/index.ts","../src/types.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/normalize.ts","../src/client.ts","../src/config.ts"],"sourcesContent":["import { scanLockfile } from './parsers/index.js';\nimport { buildWirePayload } from './normalize.js';\nimport { postManifest } from './client.js';\nimport { persistSiteUuid, resolveConfig } from './config.js';\nimport type { Config, Manifest, StoreManifestResponse } from './types.js';\n\nexport { scanLockfile, detectLockfile } from './parsers/index.js';\nexport { buildWirePayload, compareVersions } from './normalize.js';\nexport { postManifest, buildClaimUrl, buildEndpointUrl, DEFAULT_ENDPOINT } from './client.js';\nexport { persistSiteUuid, resolveConfig, writeConfigFile } from './config.js';\nexport {\n PatchstackError,\n type Config,\n type Ecosystem,\n type Manifest,\n type PackageEntry,\n type StoreManifestResponse,\n} from './types.js';\n\nexport interface ScanAndReportOptions {\n cwd?: string;\n config?: Config;\n}\n\nexport interface ScanAndReportResult {\n manifest: Manifest;\n response: StoreManifestResponse;\n duplicateNames: string[];\n uniqueNames: number;\n totalEntries: number;\n}\n\nexport async function scanAndReport(\n options: ScanAndReportOptions = {},\n): Promise {\n const cwd = options.cwd ?? process.cwd();\n const config = options.config ?? (await resolveConfig({ cwd }));\n const manifest = await scanLockfile(cwd);\n const { payload, stats } = buildWirePayload(manifest);\n const response = await postManifest(config, payload);\n\n // First-run convenience: if we didn't have a UUID and the server provisioned\n // one for us, persist it so subsequent runs target the same site.\n if (config.siteUuid === null && response.uuid !== undefined && response.uuid.length > 0) {\n await persistSiteUuid(cwd, response.uuid);\n }\n\n return {\n manifest,\n response,\n duplicateNames: stats.duplicateNames,\n uniqueNames: stats.uniqueNames,\n totalEntries: stats.totalEntries,\n };\n}\n","import { access } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Manifest, type PackageEntry } from '../types.js';\nimport { parseNpmLockfile } from './npm.js';\nimport { walkNodeModules } from './node_modules.js';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy = 'npm-lockfile' | 'node-modules-walk';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\nexport async function detectLockfile(cwd: string): Promise {\n const npmLock = path.join(cwd, 'package-lock.json');\n if (await exists(npmLock)) {\n return {\n ecosystem: 'npm',\n filePath: npmLock,\n filename: 'package-lock.json',\n strategy: 'npm-lockfile',\n };\n }\n\n const bunLock = path.join(cwd, 'bun.lock');\n if (await exists(bunLock)) {\n return {\n ecosystem: 'npm',\n filePath: bunLock,\n filename: 'bun.lock',\n strategy: 'node-modules-walk',\n };\n }\n\n const bunLockB = path.join(cwd, 'bun.lockb');\n if (await exists(bunLockB)) {\n return {\n ecosystem: 'npm',\n filePath: bunLockB,\n filename: 'bun.lockb',\n strategy: 'node-modules-walk',\n };\n }\n\n const yarnLock = path.join(cwd, 'yarn.lock');\n if (await exists(yarnLock)) {\n throw new PatchstackError(\n 'yarn.lock detected but not yet supported. Run `npm install` to generate a package-lock.json, or open an issue at github.com/patchstack/connect.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n const pnpmLock = path.join(cwd, 'pnpm-lock.yaml');\n if (await exists(pnpmLock)) {\n throw new PatchstackError(\n 'pnpm-lock.yaml detected but not yet supported. Open an issue at github.com/patchstack/connect to request support.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n throw new PatchstackError(\n `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`,\n 'LOCKFILE_NOT_FOUND',\n );\n}\n\nexport async function scanLockfile(cwd: string): Promise {\n const detected = await detectLockfile(cwd);\n const packages = await runStrategy(detected, cwd);\n return { ecosystem: detected.ecosystem, packages };\n}\n\nasync function runStrategy(\n detected: DetectedLockfile,\n cwd: string,\n): Promise {\n switch (detected.strategy) {\n case 'npm-lockfile':\n return parseNpmLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n","export type Ecosystem = 'npm' | 'composer';\n\nexport interface PackageEntry {\n name: string;\n version: string;\n path?: string;\n direct?: boolean;\n}\n\nexport interface Manifest {\n ecosystem: Ecosystem;\n packages: PackageEntry[];\n}\n\nexport interface Config {\n /**\n * The site UUID. `null` means we don't have one yet — `postManifest` will then\n * post to the bare endpoint, the server will provision a fresh site, and the\n * UUID it returns should be persisted via `persistSiteUuid()`.\n */\n siteUuid: string | null;\n endpoint: string;\n timeoutMs: number;\n}\n\nexport interface StoreManifestResponse {\n /** The UUID of the site the manifest was stored against. Always returned. */\n uuid?: string;\n stored: boolean;\n manifest_id?: number;\n checksum?: string;\n reason?: string;\n message?: string;\n error?: string;\n}\n\nexport class PatchstackError extends Error {\n constructor(\n message: string,\n public readonly code:\n | 'CONFIG_MISSING'\n | 'CONFIG_INVALID'\n | 'LOCKFILE_NOT_FOUND'\n | 'LOCKFILE_UNSUPPORTED'\n | 'LOCKFILE_PARSE_ERROR'\n | 'NETWORK_ERROR'\n | 'NETWORK_TIMEOUT'\n | 'SITE_NOT_FOUND'\n | 'VALIDATION_ERROR'\n | 'SERVER_ERROR',\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = 'PatchstackError';\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\ninterface LockfileV2Package {\n name?: string;\n version?: string;\n link?: boolean;\n resolved?: string;\n}\n\ninterface LockfileV2 {\n lockfileVersion: number;\n packages?: Record;\n dependencies?: Record;\n}\n\ninterface LockfileV1Dependency {\n version: string;\n dependencies?: Record;\n}\n\nexport async function parseNpmLockfile(lockfilePath: string): Promise {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, 'LOCKFILE_NOT_FOUND', cause);\n }\n\n let parsed: LockfileV2;\n try {\n parsed = JSON.parse(raw) as LockfileV2;\n } catch (cause) {\n throw new PatchstackError(`Lockfile at ${lockfilePath} is not valid JSON`, 'LOCKFILE_PARSE_ERROR', cause);\n }\n\n if (parsed.packages) {\n return extractFromV2(parsed.packages);\n }\n\n if (parsed.dependencies) {\n return extractFromV1(parsed.dependencies);\n }\n\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} has no \"packages\" or \"dependencies\" key`,\n 'LOCKFILE_PARSE_ERROR',\n );\n}\n\nfunction extractFromV2(packages: Record): PackageEntry[] {\n const entries: PackageEntry[] = [];\n\n for (const [pkgPath, pkg] of Object.entries(packages)) {\n if (pkgPath === '') {\n continue;\n }\n if (pkg.link === true) {\n continue;\n }\n if (typeof pkg.version !== 'string' || pkg.version.length === 0) {\n continue;\n }\n\n const name = pkg.name ?? extractNameFromPath(pkgPath);\n if (name === null) {\n continue;\n }\n\n entries.push({\n name,\n version: pkg.version,\n path: pkgPath,\n direct: isDirectV2(pkgPath),\n });\n }\n\n return entries;\n}\n\nfunction extractFromV1(\n deps: Record,\n acc: PackageEntry[] = [],\n depth = 0,\n): PackageEntry[] {\n for (const [name, dep] of Object.entries(deps)) {\n if (typeof dep.version === 'string' && dep.version.length > 0) {\n acc.push({ name, version: dep.version, direct: depth === 0 });\n }\n if (dep.dependencies) {\n extractFromV1(dep.dependencies, acc, depth + 1);\n }\n }\n return acc;\n}\n\nfunction extractNameFromPath(pkgPath: string): string | null {\n const segments = pkgPath.split('node_modules' + path.sep === pkgPath ? path.sep : '/');\n const parts = pkgPath.split('/');\n const nmIndex = parts.lastIndexOf('node_modules');\n if (nmIndex === -1 || nmIndex >= parts.length - 1) {\n return segments[segments.length - 1] ?? null;\n }\n const tail = parts.slice(nmIndex + 1);\n if (tail.length === 0) {\n return null;\n }\n const first = tail[0];\n if (first !== undefined && first.startsWith('@') && tail.length >= 2) {\n return `${first}/${tail[1]}`;\n }\n return first ?? null;\n}\n\nfunction isDirectV2(pkgPath: string): boolean {\n const parts = pkgPath.split('/');\n const nmCount = parts.filter((p) => p === 'node_modules').length;\n return nmCount === 1;\n}\n","import { lstat, readFile, readdir, stat } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\nexport async function walkNodeModules(cwd: string): Promise {\n const root = path.join(cwd, 'node_modules');\n\n try {\n const info = await stat(root);\n if (!info.isDirectory()) {\n throw new PatchstackError(\n `${root} exists but is not a directory.`,\n 'LOCKFILE_NOT_FOUND',\n );\n }\n } catch (cause) {\n if (cause instanceof PatchstackError) {\n throw cause;\n }\n throw new PatchstackError(\n `node_modules/ not found at ${cwd}. Install dependencies first (e.g. \\`bun install\\` or \\`npm install\\`).`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const entries: PackageEntry[] = [];\n await walk(root, entries, 0);\n return entries;\n}\n\nasync function walk(dir: string, acc: PackageEntry[], depth: number): Promise {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n return;\n }\n\n for (const name of names) {\n if (name.startsWith('.')) {\n continue;\n }\n\n const fullPath = path.join(dir, name);\n if (!(await isPlainDirectory(fullPath))) {\n continue;\n }\n\n if (name.startsWith('@')) {\n let subNames: string[];\n try {\n subNames = await readdir(fullPath);\n } catch {\n continue;\n }\n for (const sub of subNames) {\n if (sub.startsWith('.')) {\n continue;\n }\n const scopedDir = path.join(fullPath, sub);\n if (!(await isPlainDirectory(scopedDir))) {\n continue;\n }\n await readPackage(scopedDir, depth, acc);\n await walkNested(scopedDir, acc, depth);\n }\n continue;\n }\n\n await readPackage(fullPath, depth, acc);\n await walkNested(fullPath, acc, depth);\n }\n}\n\nasync function readPackage(\n pkgDir: string,\n depth: number,\n acc: PackageEntry[],\n): Promise {\n let raw: string;\n try {\n raw = await readFile(path.join(pkgDir, 'package.json'), 'utf8');\n } catch {\n return;\n }\n\n let parsed: { name?: unknown; version?: unknown };\n try {\n parsed = JSON.parse(raw) as { name?: unknown; version?: unknown };\n } catch {\n return;\n }\n\n if (typeof parsed.name !== 'string' || parsed.name.length === 0) {\n return;\n }\n if (typeof parsed.version !== 'string' || parsed.version.length === 0) {\n return;\n }\n\n acc.push({\n name: parsed.name,\n version: parsed.version,\n direct: depth === 0,\n });\n}\n\nasync function walkNested(\n pkgDir: string,\n acc: PackageEntry[],\n depth: number,\n): Promise {\n const nested = path.join(pkgDir, 'node_modules');\n if (!(await isPlainDirectory(nested))) {\n return;\n }\n await walk(nested, acc, depth + 1);\n}\n\nasync function isPlainDirectory(dir: string): Promise {\n try {\n const info = await lstat(dir);\n return info.isDirectory() && !info.isSymbolicLink();\n } catch {\n return false;\n }\n}\n","import type { Manifest, PackageEntry } from './types.js';\n\nexport interface WirePackage {\n name: string;\n version: string;\n}\n\nexport interface WirePayload {\n ecosystem: Manifest['ecosystem'];\n packages: WirePackage[];\n}\n\nexport interface NormalizeStats {\n uniqueNames: number;\n duplicateNames: string[];\n totalEntries: number;\n}\n\nexport interface NormalizeResult {\n payload: WirePayload;\n stats: NormalizeStats;\n}\n\nexport function buildWirePayload(manifest: Manifest): NormalizeResult {\n const seen = new Map>();\n const wirePackages: WirePackage[] = [];\n\n for (const entry of manifest.packages) {\n const versions = seen.get(entry.name);\n if (versions) {\n if (versions.has(entry.version)) {\n continue;\n }\n versions.add(entry.version);\n } else {\n seen.set(entry.name, new Set([entry.version]));\n }\n wirePackages.push({ name: entry.name, version: entry.version });\n }\n\n wirePackages.sort((a, b) => {\n if (a.name === b.name) {\n return compareVersions(a.version, b.version);\n }\n return a.name < b.name ? -1 : 1;\n });\n\n const duplicateNames: string[] = [];\n for (const [name, versions] of seen) {\n if (versions.size > 1) {\n duplicateNames.push(name);\n }\n }\n\n return {\n payload: { ecosystem: manifest.ecosystem, packages: wirePackages },\n stats: {\n uniqueNames: seen.size,\n duplicateNames,\n totalEntries: manifest.packages.length,\n },\n };\n}\n\nexport function compareVersions(a: string, b: string): number {\n if (a === b) {\n return 0;\n }\n\n const [aBase, aPre] = splitPrerelease(a);\n const [bBase, bPre] = splitPrerelease(b);\n\n const baseCmp = compareSegments(aBase.split('.'), bBase.split('.'));\n if (baseCmp !== 0) {\n return baseCmp;\n }\n\n if (aPre === null && bPre === null) {\n return 0;\n }\n if (aPre === null) {\n return 1;\n }\n if (bPre === null) {\n return -1;\n }\n return compareSegments(aPre.split('.'), bPre.split('.'));\n}\n\nfunction splitPrerelease(version: string): [string, string | null] {\n const cleaned = version.replace(/^[v=]+/, '').split('+')[0]!;\n const dashIndex = cleaned.indexOf('-');\n if (dashIndex === -1) {\n return [cleaned, null];\n }\n return [cleaned.slice(0, dashIndex), cleaned.slice(dashIndex + 1)];\n}\n\nfunction compareSegments(a: string[], b: string[]): number {\n const max = Math.max(a.length, b.length);\n for (let i = 0; i < max; i++) {\n const aPart = a[i];\n const bPart = b[i];\n if (aPart === undefined) {\n return -1;\n }\n if (bPart === undefined) {\n return 1;\n }\n const aNum = /^\\d+$/.test(aPart);\n const bNum = /^\\d+$/.test(bPart);\n if (aNum && bNum) {\n const diff = Number(aPart) - Number(bPart);\n if (diff !== 0) {\n return diff < 0 ? -1 : 1;\n }\n continue;\n }\n if (aNum) {\n return -1;\n }\n if (bNum) {\n return 1;\n }\n if (aPart < bPart) {\n return -1;\n }\n if (aPart > bPart) {\n return 1;\n }\n }\n return 0;\n}\n\nexport function findPackageInManifest(\n manifest: Manifest,\n name: string,\n): PackageEntry[] {\n return manifest.packages.filter((p) => p.name === name);\n}\n","import { PatchstackError, type Config, type StoreManifestResponse } from './types.js';\nimport type { WirePayload } from './normalize.js';\n\nexport const DEFAULT_ENDPOINT = 'https://api.patchstack.com/monitor/pulse/manifest';\nexport const DEFAULT_TIMEOUT_MS = 30_000;\n\nexport function buildEndpointUrl(base: string, siteUuid?: string | null): string {\n const trimmed = base.replace(/\\/$/, '');\n return siteUuid !== undefined && siteUuid !== null && siteUuid.length > 0\n ? `${trimmed}/${encodeURIComponent(siteUuid)}`\n : trimmed;\n}\n\n/**\n * Build the claim URL for a site. The claim page lives on the same origin as\n * the API endpoint, at `/monitor/claim?site=`. Using the API endpoint's\n * origin (rather than a hard-coded https://api.patchstack.com) means staging,\n * ngrok tunnels and local dev environments all produce a claim URL on the same\n * host the connector is already talking to.\n */\nexport function buildClaimUrl(endpoint: string, siteUuid: string): string {\n const origin = new URL(endpoint).origin;\n return `${origin}/monitor/claim?site=${encodeURIComponent(siteUuid)}`;\n}\n\nexport async function postManifest(\n config: Config,\n payload: WirePayload,\n): Promise {\n const url = buildEndpointUrl(config.endpoint, config.siteUuid);\n const timeoutMs = config.timeoutMs;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'User-Agent': '@patchstack/connect',\n },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (cause) {\n if (isTimeoutError(cause)) {\n throw new PatchstackError(\n `Patchstack request to ${url} timed out after ${timeoutMs}ms. Override with PATCHSTACK_TIMEOUT_MS.`,\n 'NETWORK_TIMEOUT',\n cause,\n );\n }\n throw new PatchstackError(\n `Could not reach Patchstack at ${url}. Check your network connection.`,\n 'NETWORK_ERROR',\n cause,\n );\n }\n\n const text = await response.text();\n let body: StoreManifestResponse | null = null;\n try {\n body = text.length > 0 ? (JSON.parse(text) as StoreManifestResponse) : null;\n } catch {\n body = null;\n }\n\n if (response.status === 404) {\n throw new PatchstackError(\n body?.error ?? 'Site not found. Check that your site UUID is correct and that the app is registered as a Pulse app in your Patchstack dashboard.',\n 'SITE_NOT_FOUND',\n );\n }\n\n if (response.status === 422) {\n throw new PatchstackError(\n body?.message ?? 'Patchstack rejected the manifest payload (validation failed).',\n 'VALIDATION_ERROR',\n );\n }\n\n if (response.status < 200 || response.status >= 300) {\n throw new PatchstackError(\n `Patchstack returned ${response.status}: ${text.slice(0, 200)}`,\n 'SERVER_ERROR',\n );\n }\n\n if (body === null) {\n throw new PatchstackError('Patchstack returned an empty response.', 'SERVER_ERROR');\n }\n\n return body;\n}\n\nfunction isTimeoutError(cause: unknown): boolean {\n if (cause instanceof Error) {\n return cause.name === 'TimeoutError' || cause.name === 'AbortError';\n }\n return false;\n}\n","import { readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Config } from './types.js';\nimport { DEFAULT_ENDPOINT, DEFAULT_TIMEOUT_MS } from './client.js';\n\nconst CONFIG_FILENAME = '.patchstackrc.json';\n\ninterface ConfigFile {\n siteUuid?: string;\n endpoint?: string;\n timeoutMs?: number;\n}\n\nexport interface ResolveConfigOptions {\n cwd: string;\n cliSiteUuid?: string;\n cliEndpoint?: string;\n /**\n * When true, resolveConfig throws CONFIG_MISSING if no site UUID is configured.\n * Defaults to false: callers that can run without a UUID (the first `scan` after\n * `npm install`) just get `siteUuid: null` back and learn the UUID from the\n * server response.\n */\n requireSiteUuid?: boolean;\n}\n\nexport async function resolveConfig(options: ResolveConfigOptions): Promise {\n const fromFile = await readConfigFile(options.cwd);\n const fromEnv = readEnv();\n\n const siteUuid =\n options.cliSiteUuid ??\n fromEnv.siteUuid ??\n fromFile.siteUuid ??\n null;\n\n const endpoint =\n options.cliEndpoint ??\n fromEnv.endpoint ??\n fromFile.endpoint ??\n DEFAULT_ENDPOINT;\n\n const timeoutMs = fromEnv.timeoutMs ?? fromFile.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n if (siteUuid !== null && siteUuid.length > 0 && !isUuid(siteUuid)) {\n throw new PatchstackError(\n `Site UUID \"${siteUuid}\" does not look like a valid UUID.`,\n 'CONFIG_INVALID',\n );\n }\n\n if (options.requireSiteUuid && (siteUuid === null || siteUuid.length === 0)) {\n throw new PatchstackError(\n 'No site UUID configured. Run `patchstack-connect scan` to provision one, or set PATCHSTACK_SITE_UUID.',\n 'CONFIG_MISSING',\n );\n }\n\n return {\n siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid,\n endpoint,\n timeoutMs,\n };\n}\n\nexport async function writeConfigFile(cwd: string, config: ConfigFile): Promise {\n const target = path.join(cwd, CONFIG_FILENAME);\n const content = JSON.stringify(config, null, 2) + '\\n';\n await writeFile(target, content, 'utf8');\n return target;\n}\n\n/**\n * Merge a new siteUuid into the existing `.patchstackrc.json` (or create it).\n * Preserves any `endpoint` / `timeoutMs` the user already wrote.\n */\nexport async function persistSiteUuid(cwd: string, siteUuid: string): Promise {\n const existing = await readConfigFile(cwd);\n return writeConfigFile(cwd, { ...existing, siteUuid });\n}\n\nasync function readConfigFile(cwd: string): Promise {\n const target = path.join(cwd, CONFIG_FILENAME);\n let raw: string;\n try {\n raw = await readFile(target, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return {};\n }\n throw new PatchstackError(\n `Could not read ${target}: ${(err as Error).message}`,\n 'CONFIG_INVALID',\n err,\n );\n }\n\n try {\n return JSON.parse(raw) as ConfigFile;\n } catch (err) {\n throw new PatchstackError(\n `Config file ${target} contains invalid JSON.`,\n 'CONFIG_INVALID',\n err,\n );\n }\n}\n\nfunction readEnv(): ConfigFile {\n const timeoutRaw = process.env.PATCHSTACK_TIMEOUT_MS;\n let timeoutMs: number | undefined;\n if (timeoutRaw !== undefined && timeoutRaw.length > 0) {\n const parsed = Number(timeoutRaw);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n throw new PatchstackError(\n `PATCHSTACK_TIMEOUT_MS must be a positive number; got \"${timeoutRaw}\".`,\n 'CONFIG_INVALID',\n );\n }\n timeoutMs = parsed;\n }\n return {\n siteUuid: process.env.PATCHSTACK_SITE_UUID ?? undefined,\n endpoint: process.env.PATCHSTACK_ENDPOINT ?? undefined,\n timeoutMs,\n };\n}\n\nfunction isUuid(value: string): boolean {\n return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);\n}\n"],"mappings":"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","names":["import_promises","import_node_path","path","import_promises","import_node_path","path","path","import_promises","import_node_path","path"]} \ No newline at end of file diff --git a/dist/index.d.cts b/dist/index.d.cts deleted file mode 100644 index b5ad5b6..0000000 --- a/dist/index.d.cts +++ /dev/null @@ -1,119 +0,0 @@ -type Ecosystem = 'npm' | 'composer'; -interface PackageEntry { - name: string; - version: string; - path?: string; - direct?: boolean; -} -interface Manifest { - ecosystem: Ecosystem; - packages: PackageEntry[]; -} -interface Config { - /** - * The site UUID. `null` means we don't have one yet — `postManifest` will then - * post to the bare endpoint, the server will provision a fresh site, and the - * UUID it returns should be persisted via `persistSiteUuid()`. - */ - siteUuid: string | null; - endpoint: string; - timeoutMs: number; -} -interface StoreManifestResponse { - /** The UUID of the site the manifest was stored against. Always returned. */ - uuid?: string; - stored: boolean; - manifest_id?: number; - checksum?: string; - reason?: string; - message?: string; - error?: string; -} -declare class PatchstackError extends Error { - readonly code: 'CONFIG_MISSING' | 'CONFIG_INVALID' | 'LOCKFILE_NOT_FOUND' | 'LOCKFILE_UNSUPPORTED' | 'LOCKFILE_PARSE_ERROR' | 'NETWORK_ERROR' | 'NETWORK_TIMEOUT' | 'SITE_NOT_FOUND' | 'VALIDATION_ERROR' | 'SERVER_ERROR'; - readonly cause?: unknown | undefined; - constructor(message: string, code: 'CONFIG_MISSING' | 'CONFIG_INVALID' | 'LOCKFILE_NOT_FOUND' | 'LOCKFILE_UNSUPPORTED' | 'LOCKFILE_PARSE_ERROR' | 'NETWORK_ERROR' | 'NETWORK_TIMEOUT' | 'SITE_NOT_FOUND' | 'VALIDATION_ERROR' | 'SERVER_ERROR', cause?: unknown | undefined); -} - -type LockfileFilename = 'package-lock.json' | 'bun.lock' | 'bun.lockb' | 'yarn.lock' | 'pnpm-lock.yaml'; -type DetectionStrategy = 'npm-lockfile' | 'node-modules-walk'; -interface DetectedLockfile { - ecosystem: 'npm'; - filePath: string; - filename: LockfileFilename; - strategy: DetectionStrategy; -} -declare function detectLockfile(cwd: string): Promise; -declare function scanLockfile(cwd: string): Promise; - -interface WirePackage { - name: string; - version: string; -} -interface WirePayload { - ecosystem: Manifest['ecosystem']; - packages: WirePackage[]; -} -interface NormalizeStats { - uniqueNames: number; - duplicateNames: string[]; - totalEntries: number; -} -interface NormalizeResult { - payload: WirePayload; - stats: NormalizeStats; -} -declare function buildWirePayload(manifest: Manifest): NormalizeResult; -declare function compareVersions(a: string, b: string): number; - -declare const DEFAULT_ENDPOINT = "https://api.patchstack.com/monitor/pulse/manifest"; -declare function buildEndpointUrl(base: string, siteUuid?: string | null): string; -/** - * Build the claim URL for a site. The claim page lives on the same origin as - * the API endpoint, at `/monitor/claim?site=`. Using the API endpoint's - * origin (rather than a hard-coded https://api.patchstack.com) means staging, - * ngrok tunnels and local dev environments all produce a claim URL on the same - * host the connector is already talking to. - */ -declare function buildClaimUrl(endpoint: string, siteUuid: string): string; -declare function postManifest(config: Config, payload: WirePayload): Promise; - -interface ConfigFile { - siteUuid?: string; - endpoint?: string; - timeoutMs?: number; -} -interface ResolveConfigOptions { - cwd: string; - cliSiteUuid?: string; - cliEndpoint?: string; - /** - * When true, resolveConfig throws CONFIG_MISSING if no site UUID is configured. - * Defaults to false: callers that can run without a UUID (the first `scan` after - * `npm install`) just get `siteUuid: null` back and learn the UUID from the - * server response. - */ - requireSiteUuid?: boolean; -} -declare function resolveConfig(options: ResolveConfigOptions): Promise; -declare function writeConfigFile(cwd: string, config: ConfigFile): Promise; -/** - * Merge a new siteUuid into the existing `.patchstackrc.json` (or create it). - * Preserves any `endpoint` / `timeoutMs` the user already wrote. - */ -declare function persistSiteUuid(cwd: string, siteUuid: string): Promise; - -interface ScanAndReportOptions { - cwd?: string; - config?: Config; -} -interface ScanAndReportResult { - manifest: Manifest; - response: StoreManifestResponse; - duplicateNames: string[]; - uniqueNames: number; - totalEntries: number; -} -declare function scanAndReport(options?: ScanAndReportOptions): Promise; - -export { type Config, DEFAULT_ENDPOINT, type Ecosystem, type Manifest, type PackageEntry, PatchstackError, type ScanAndReportOptions, type ScanAndReportResult, type StoreManifestResponse, buildClaimUrl, buildEndpointUrl, buildWirePayload, compareVersions, detectLockfile, persistSiteUuid, postManifest, resolveConfig, scanAndReport, scanLockfile, writeConfigFile }; diff --git a/dist/index.d.ts b/dist/index.d.ts deleted file mode 100644 index b5ad5b6..0000000 --- a/dist/index.d.ts +++ /dev/null @@ -1,119 +0,0 @@ -type Ecosystem = 'npm' | 'composer'; -interface PackageEntry { - name: string; - version: string; - path?: string; - direct?: boolean; -} -interface Manifest { - ecosystem: Ecosystem; - packages: PackageEntry[]; -} -interface Config { - /** - * The site UUID. `null` means we don't have one yet — `postManifest` will then - * post to the bare endpoint, the server will provision a fresh site, and the - * UUID it returns should be persisted via `persistSiteUuid()`. - */ - siteUuid: string | null; - endpoint: string; - timeoutMs: number; -} -interface StoreManifestResponse { - /** The UUID of the site the manifest was stored against. Always returned. */ - uuid?: string; - stored: boolean; - manifest_id?: number; - checksum?: string; - reason?: string; - message?: string; - error?: string; -} -declare class PatchstackError extends Error { - readonly code: 'CONFIG_MISSING' | 'CONFIG_INVALID' | 'LOCKFILE_NOT_FOUND' | 'LOCKFILE_UNSUPPORTED' | 'LOCKFILE_PARSE_ERROR' | 'NETWORK_ERROR' | 'NETWORK_TIMEOUT' | 'SITE_NOT_FOUND' | 'VALIDATION_ERROR' | 'SERVER_ERROR'; - readonly cause?: unknown | undefined; - constructor(message: string, code: 'CONFIG_MISSING' | 'CONFIG_INVALID' | 'LOCKFILE_NOT_FOUND' | 'LOCKFILE_UNSUPPORTED' | 'LOCKFILE_PARSE_ERROR' | 'NETWORK_ERROR' | 'NETWORK_TIMEOUT' | 'SITE_NOT_FOUND' | 'VALIDATION_ERROR' | 'SERVER_ERROR', cause?: unknown | undefined); -} - -type LockfileFilename = 'package-lock.json' | 'bun.lock' | 'bun.lockb' | 'yarn.lock' | 'pnpm-lock.yaml'; -type DetectionStrategy = 'npm-lockfile' | 'node-modules-walk'; -interface DetectedLockfile { - ecosystem: 'npm'; - filePath: string; - filename: LockfileFilename; - strategy: DetectionStrategy; -} -declare function detectLockfile(cwd: string): Promise; -declare function scanLockfile(cwd: string): Promise; - -interface WirePackage { - name: string; - version: string; -} -interface WirePayload { - ecosystem: Manifest['ecosystem']; - packages: WirePackage[]; -} -interface NormalizeStats { - uniqueNames: number; - duplicateNames: string[]; - totalEntries: number; -} -interface NormalizeResult { - payload: WirePayload; - stats: NormalizeStats; -} -declare function buildWirePayload(manifest: Manifest): NormalizeResult; -declare function compareVersions(a: string, b: string): number; - -declare const DEFAULT_ENDPOINT = "https://api.patchstack.com/monitor/pulse/manifest"; -declare function buildEndpointUrl(base: string, siteUuid?: string | null): string; -/** - * Build the claim URL for a site. The claim page lives on the same origin as - * the API endpoint, at `/monitor/claim?site=`. Using the API endpoint's - * origin (rather than a hard-coded https://api.patchstack.com) means staging, - * ngrok tunnels and local dev environments all produce a claim URL on the same - * host the connector is already talking to. - */ -declare function buildClaimUrl(endpoint: string, siteUuid: string): string; -declare function postManifest(config: Config, payload: WirePayload): Promise; - -interface ConfigFile { - siteUuid?: string; - endpoint?: string; - timeoutMs?: number; -} -interface ResolveConfigOptions { - cwd: string; - cliSiteUuid?: string; - cliEndpoint?: string; - /** - * When true, resolveConfig throws CONFIG_MISSING if no site UUID is configured. - * Defaults to false: callers that can run without a UUID (the first `scan` after - * `npm install`) just get `siteUuid: null` back and learn the UUID from the - * server response. - */ - requireSiteUuid?: boolean; -} -declare function resolveConfig(options: ResolveConfigOptions): Promise; -declare function writeConfigFile(cwd: string, config: ConfigFile): Promise; -/** - * Merge a new siteUuid into the existing `.patchstackrc.json` (or create it). - * Preserves any `endpoint` / `timeoutMs` the user already wrote. - */ -declare function persistSiteUuid(cwd: string, siteUuid: string): Promise; - -interface ScanAndReportOptions { - cwd?: string; - config?: Config; -} -interface ScanAndReportResult { - manifest: Manifest; - response: StoreManifestResponse; - duplicateNames: string[]; - uniqueNames: number; - totalEntries: number; -} -declare function scanAndReport(options?: ScanAndReportOptions): Promise; - -export { type Config, DEFAULT_ENDPOINT, type Ecosystem, type Manifest, type PackageEntry, PatchstackError, type ScanAndReportOptions, type ScanAndReportResult, type StoreManifestResponse, buildClaimUrl, buildEndpointUrl, buildWirePayload, compareVersions, detectLockfile, persistSiteUuid, postManifest, resolveConfig, scanAndReport, scanLockfile, writeConfigFile }; diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index 7253be4..0000000 --- a/dist/index.js +++ /dev/null @@ -1,578 +0,0 @@ -// src/parsers/index.ts -import { access } from "fs/promises"; -import path3 from "path"; - -// src/types.ts -var PatchstackError = class extends Error { - constructor(message, code, cause) { - super(message); - this.code = code; - this.cause = cause; - this.name = "PatchstackError"; - } - code; - cause; -}; - -// src/parsers/npm.ts -import { readFile } from "fs/promises"; -import path from "path"; -async function parseNpmLockfile(lockfilePath) { - let raw; - try { - raw = await readFile(lockfilePath, "utf8"); - } catch (cause) { - throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, "LOCKFILE_NOT_FOUND", cause); - } - let parsed; - try { - parsed = JSON.parse(raw); - } catch (cause) { - throw new PatchstackError(`Lockfile at ${lockfilePath} is not valid JSON`, "LOCKFILE_PARSE_ERROR", cause); - } - if (parsed.packages) { - return extractFromV2(parsed.packages); - } - if (parsed.dependencies) { - return extractFromV1(parsed.dependencies); - } - throw new PatchstackError( - `Lockfile at ${lockfilePath} has no "packages" or "dependencies" key`, - "LOCKFILE_PARSE_ERROR" - ); -} -function extractFromV2(packages) { - const entries = []; - for (const [pkgPath, pkg] of Object.entries(packages)) { - if (pkgPath === "") { - continue; - } - if (pkg.link === true) { - continue; - } - if (typeof pkg.version !== "string" || pkg.version.length === 0) { - continue; - } - const name = pkg.name ?? extractNameFromPath(pkgPath); - if (name === null) { - continue; - } - entries.push({ - name, - version: pkg.version, - path: pkgPath, - direct: isDirectV2(pkgPath) - }); - } - return entries; -} -function extractFromV1(deps, acc = [], depth = 0) { - for (const [name, dep] of Object.entries(deps)) { - if (typeof dep.version === "string" && dep.version.length > 0) { - acc.push({ name, version: dep.version, direct: depth === 0 }); - } - if (dep.dependencies) { - extractFromV1(dep.dependencies, acc, depth + 1); - } - } - return acc; -} -function extractNameFromPath(pkgPath) { - const segments = pkgPath.split("node_modules" + path.sep === pkgPath ? path.sep : "/"); - const parts = pkgPath.split("/"); - const nmIndex = parts.lastIndexOf("node_modules"); - if (nmIndex === -1 || nmIndex >= parts.length - 1) { - return segments[segments.length - 1] ?? null; - } - const tail = parts.slice(nmIndex + 1); - if (tail.length === 0) { - return null; - } - const first = tail[0]; - if (first !== void 0 && first.startsWith("@") && tail.length >= 2) { - return `${first}/${tail[1]}`; - } - return first ?? null; -} -function isDirectV2(pkgPath) { - const parts = pkgPath.split("/"); - const nmCount = parts.filter((p) => p === "node_modules").length; - return nmCount === 1; -} - -// src/parsers/node_modules.ts -import { lstat, readFile as readFile2, readdir, stat } from "fs/promises"; -import path2 from "path"; -async function walkNodeModules(cwd) { - const root = path2.join(cwd, "node_modules"); - try { - const info = await stat(root); - if (!info.isDirectory()) { - throw new PatchstackError( - `${root} exists but is not a directory.`, - "LOCKFILE_NOT_FOUND" - ); - } - } catch (cause) { - if (cause instanceof PatchstackError) { - throw cause; - } - throw new PatchstackError( - `node_modules/ not found at ${cwd}. Install dependencies first (e.g. \`bun install\` or \`npm install\`).`, - "LOCKFILE_NOT_FOUND", - cause - ); - } - const entries = []; - await walk(root, entries, 0); - return entries; -} -async function walk(dir, acc, depth) { - let names; - try { - names = await readdir(dir); - } catch { - return; - } - for (const name of names) { - if (name.startsWith(".")) { - continue; - } - const fullPath = path2.join(dir, name); - if (!await isPlainDirectory(fullPath)) { - continue; - } - if (name.startsWith("@")) { - let subNames; - try { - subNames = await readdir(fullPath); - } catch { - continue; - } - for (const sub of subNames) { - if (sub.startsWith(".")) { - continue; - } - const scopedDir = path2.join(fullPath, sub); - if (!await isPlainDirectory(scopedDir)) { - continue; - } - await readPackage(scopedDir, depth, acc); - await walkNested(scopedDir, acc, depth); - } - continue; - } - await readPackage(fullPath, depth, acc); - await walkNested(fullPath, acc, depth); - } -} -async function readPackage(pkgDir, depth, acc) { - let raw; - try { - raw = await readFile2(path2.join(pkgDir, "package.json"), "utf8"); - } catch { - return; - } - let parsed; - try { - parsed = JSON.parse(raw); - } catch { - return; - } - if (typeof parsed.name !== "string" || parsed.name.length === 0) { - return; - } - if (typeof parsed.version !== "string" || parsed.version.length === 0) { - return; - } - acc.push({ - name: parsed.name, - version: parsed.version, - direct: depth === 0 - }); -} -async function walkNested(pkgDir, acc, depth) { - const nested = path2.join(pkgDir, "node_modules"); - if (!await isPlainDirectory(nested)) { - return; - } - await walk(nested, acc, depth + 1); -} -async function isPlainDirectory(dir) { - try { - const info = await lstat(dir); - return info.isDirectory() && !info.isSymbolicLink(); - } catch { - return false; - } -} - -// src/parsers/index.ts -async function detectLockfile(cwd) { - const npmLock = path3.join(cwd, "package-lock.json"); - if (await exists(npmLock)) { - return { - ecosystem: "npm", - filePath: npmLock, - filename: "package-lock.json", - strategy: "npm-lockfile" - }; - } - const bunLock = path3.join(cwd, "bun.lock"); - if (await exists(bunLock)) { - return { - ecosystem: "npm", - filePath: bunLock, - filename: "bun.lock", - strategy: "node-modules-walk" - }; - } - const bunLockB = path3.join(cwd, "bun.lockb"); - if (await exists(bunLockB)) { - return { - ecosystem: "npm", - filePath: bunLockB, - filename: "bun.lockb", - strategy: "node-modules-walk" - }; - } - const yarnLock = path3.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" - ); - } - const pnpmLock = path3.join(cwd, "pnpm-lock.yaml"); - if (await exists(pnpmLock)) { - throw new PatchstackError( - "pnpm-lock.yaml detected but not yet supported. Open an issue at github.com/patchstack/connect to request support.", - "LOCKFILE_UNSUPPORTED" - ); - } - throw new PatchstackError( - `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`, - "LOCKFILE_NOT_FOUND" - ); -} -async function scanLockfile(cwd) { - const detected = await detectLockfile(cwd); - const packages = await runStrategy(detected, cwd); - return { ecosystem: detected.ecosystem, packages }; -} -async function runStrategy(detected, cwd) { - switch (detected.strategy) { - case "npm-lockfile": - return parseNpmLockfile(detected.filePath); - case "node-modules-walk": - return walkNodeModules(cwd); - } -} -async function exists(filePath) { - try { - await access(filePath); - return true; - } catch { - return false; - } -} - -// src/normalize.ts -function buildWirePayload(manifest) { - const seen = /* @__PURE__ */ new Map(); - const wirePackages = []; - for (const entry of manifest.packages) { - const versions = seen.get(entry.name); - if (versions) { - if (versions.has(entry.version)) { - continue; - } - versions.add(entry.version); - } else { - seen.set(entry.name, /* @__PURE__ */ new Set([entry.version])); - } - wirePackages.push({ name: entry.name, version: entry.version }); - } - wirePackages.sort((a, b) => { - if (a.name === b.name) { - return compareVersions(a.version, b.version); - } - return a.name < b.name ? -1 : 1; - }); - const duplicateNames = []; - for (const [name, versions] of seen) { - if (versions.size > 1) { - duplicateNames.push(name); - } - } - return { - payload: { ecosystem: manifest.ecosystem, packages: wirePackages }, - stats: { - uniqueNames: seen.size, - duplicateNames, - totalEntries: manifest.packages.length - } - }; -} -function compareVersions(a, b) { - if (a === b) { - return 0; - } - const [aBase, aPre] = splitPrerelease(a); - const [bBase, bPre] = splitPrerelease(b); - const baseCmp = compareSegments(aBase.split("."), bBase.split(".")); - if (baseCmp !== 0) { - return baseCmp; - } - if (aPre === null && bPre === null) { - return 0; - } - if (aPre === null) { - return 1; - } - if (bPre === null) { - return -1; - } - return compareSegments(aPre.split("."), bPre.split(".")); -} -function splitPrerelease(version) { - const cleaned = version.replace(/^[v=]+/, "").split("+")[0]; - const dashIndex = cleaned.indexOf("-"); - if (dashIndex === -1) { - return [cleaned, null]; - } - return [cleaned.slice(0, dashIndex), cleaned.slice(dashIndex + 1)]; -} -function compareSegments(a, b) { - const max = Math.max(a.length, b.length); - for (let i = 0; i < max; i++) { - const aPart = a[i]; - const bPart = b[i]; - if (aPart === void 0) { - return -1; - } - if (bPart === void 0) { - return 1; - } - const aNum = /^\d+$/.test(aPart); - const bNum = /^\d+$/.test(bPart); - if (aNum && bNum) { - const diff = Number(aPart) - Number(bPart); - if (diff !== 0) { - return diff < 0 ? -1 : 1; - } - continue; - } - if (aNum) { - return -1; - } - if (bNum) { - return 1; - } - if (aPart < bPart) { - return -1; - } - if (aPart > bPart) { - return 1; - } - } - return 0; -} - -// src/client.ts -var DEFAULT_ENDPOINT = "https://api.patchstack.com/monitor/pulse/manifest"; -var DEFAULT_TIMEOUT_MS = 3e4; -function buildEndpointUrl(base, siteUuid) { - const trimmed = base.replace(/\/$/, ""); - return siteUuid !== void 0 && siteUuid !== null && siteUuid.length > 0 ? `${trimmed}/${encodeURIComponent(siteUuid)}` : trimmed; -} -function buildClaimUrl(endpoint, siteUuid) { - const origin = new URL(endpoint).origin; - return `${origin}/monitor/claim?site=${encodeURIComponent(siteUuid)}`; -} -async function postManifest(config, payload) { - const url = buildEndpointUrl(config.endpoint, config.siteUuid); - const timeoutMs = config.timeoutMs; - let response; - try { - response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - "User-Agent": "@patchstack/connect" - }, - body: JSON.stringify(payload), - signal: AbortSignal.timeout(timeoutMs) - }); - } catch (cause) { - if (isTimeoutError(cause)) { - throw new PatchstackError( - `Patchstack request to ${url} timed out after ${timeoutMs}ms. Override with PATCHSTACK_TIMEOUT_MS.`, - "NETWORK_TIMEOUT", - cause - ); - } - throw new PatchstackError( - `Could not reach Patchstack at ${url}. Check your network connection.`, - "NETWORK_ERROR", - cause - ); - } - const text = await response.text(); - let body = null; - try { - body = text.length > 0 ? JSON.parse(text) : null; - } catch { - body = null; - } - if (response.status === 404) { - throw new PatchstackError( - 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.", - "SITE_NOT_FOUND" - ); - } - if (response.status === 422) { - throw new PatchstackError( - body?.message ?? "Patchstack rejected the manifest payload (validation failed).", - "VALIDATION_ERROR" - ); - } - if (response.status < 200 || response.status >= 300) { - throw new PatchstackError( - `Patchstack returned ${response.status}: ${text.slice(0, 200)}`, - "SERVER_ERROR" - ); - } - if (body === null) { - throw new PatchstackError("Patchstack returned an empty response.", "SERVER_ERROR"); - } - return body; -} -function isTimeoutError(cause) { - if (cause instanceof Error) { - return cause.name === "TimeoutError" || cause.name === "AbortError"; - } - return false; -} - -// src/config.ts -import { readFile as readFile3, writeFile } from "fs/promises"; -import path4 from "path"; -var CONFIG_FILENAME = ".patchstackrc.json"; -async function resolveConfig(options) { - const fromFile = await readConfigFile(options.cwd); - const fromEnv = readEnv(); - const siteUuid = options.cliSiteUuid ?? fromEnv.siteUuid ?? fromFile.siteUuid ?? null; - const endpoint = options.cliEndpoint ?? fromEnv.endpoint ?? fromFile.endpoint ?? DEFAULT_ENDPOINT; - const timeoutMs = fromEnv.timeoutMs ?? fromFile.timeoutMs ?? DEFAULT_TIMEOUT_MS; - if (siteUuid !== null && siteUuid.length > 0 && !isUuid(siteUuid)) { - throw new PatchstackError( - `Site UUID "${siteUuid}" does not look like a valid UUID.`, - "CONFIG_INVALID" - ); - } - if (options.requireSiteUuid && (siteUuid === null || siteUuid.length === 0)) { - throw new PatchstackError( - "No site UUID configured. Run `patchstack-connect scan` to provision one, or set PATCHSTACK_SITE_UUID.", - "CONFIG_MISSING" - ); - } - return { - siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid, - endpoint, - timeoutMs - }; -} -async function writeConfigFile(cwd, config) { - const target = path4.join(cwd, CONFIG_FILENAME); - const content = JSON.stringify(config, null, 2) + "\n"; - await writeFile(target, content, "utf8"); - return target; -} -async function persistSiteUuid(cwd, siteUuid) { - const existing = await readConfigFile(cwd); - return writeConfigFile(cwd, { ...existing, siteUuid }); -} -async function readConfigFile(cwd) { - const target = path4.join(cwd, CONFIG_FILENAME); - let raw; - try { - raw = await readFile3(target, "utf8"); - } catch (err) { - if (err.code === "ENOENT") { - return {}; - } - throw new PatchstackError( - `Could not read ${target}: ${err.message}`, - "CONFIG_INVALID", - err - ); - } - try { - return JSON.parse(raw); - } catch (err) { - throw new PatchstackError( - `Config file ${target} contains invalid JSON.`, - "CONFIG_INVALID", - err - ); - } -} -function readEnv() { - const timeoutRaw = process.env.PATCHSTACK_TIMEOUT_MS; - let timeoutMs; - if (timeoutRaw !== void 0 && timeoutRaw.length > 0) { - const parsed = Number(timeoutRaw); - if (!Number.isFinite(parsed) || parsed <= 0) { - throw new PatchstackError( - `PATCHSTACK_TIMEOUT_MS must be a positive number; got "${timeoutRaw}".`, - "CONFIG_INVALID" - ); - } - timeoutMs = parsed; - } - return { - siteUuid: process.env.PATCHSTACK_SITE_UUID ?? void 0, - endpoint: process.env.PATCHSTACK_ENDPOINT ?? void 0, - timeoutMs - }; -} -function isUuid(value) { - return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value); -} - -// src/index.ts -async function scanAndReport(options = {}) { - const cwd = options.cwd ?? process.cwd(); - const config = options.config ?? await resolveConfig({ cwd }); - const manifest = await scanLockfile(cwd); - const { payload, stats } = buildWirePayload(manifest); - const response = await postManifest(config, payload); - if (config.siteUuid === null && response.uuid !== void 0 && response.uuid.length > 0) { - await persistSiteUuid(cwd, response.uuid); - } - return { - manifest, - response, - duplicateNames: stats.duplicateNames, - uniqueNames: stats.uniqueNames, - totalEntries: stats.totalEntries - }; -} -export { - DEFAULT_ENDPOINT, - PatchstackError, - buildClaimUrl, - buildEndpointUrl, - buildWirePayload, - compareVersions, - detectLockfile, - persistSiteUuid, - postManifest, - resolveConfig, - scanAndReport, - scanLockfile, - writeConfigFile -}; -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map deleted file mode 100644 index 19a8503..0000000 --- a/dist/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/parsers/index.ts","../src/types.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/normalize.ts","../src/client.ts","../src/config.ts","../src/index.ts"],"sourcesContent":["import { access } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Manifest, type PackageEntry } from '../types.js';\nimport { parseNpmLockfile } from './npm.js';\nimport { walkNodeModules } from './node_modules.js';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy = 'npm-lockfile' | 'node-modules-walk';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\nexport async function detectLockfile(cwd: string): Promise {\n const npmLock = path.join(cwd, 'package-lock.json');\n if (await exists(npmLock)) {\n return {\n ecosystem: 'npm',\n filePath: npmLock,\n filename: 'package-lock.json',\n strategy: 'npm-lockfile',\n };\n }\n\n const bunLock = path.join(cwd, 'bun.lock');\n if (await exists(bunLock)) {\n return {\n ecosystem: 'npm',\n filePath: bunLock,\n filename: 'bun.lock',\n strategy: 'node-modules-walk',\n };\n }\n\n const bunLockB = path.join(cwd, 'bun.lockb');\n if (await exists(bunLockB)) {\n return {\n ecosystem: 'npm',\n filePath: bunLockB,\n filename: 'bun.lockb',\n strategy: 'node-modules-walk',\n };\n }\n\n const yarnLock = path.join(cwd, 'yarn.lock');\n if (await exists(yarnLock)) {\n throw new PatchstackError(\n 'yarn.lock detected but not yet supported. Run `npm install` to generate a package-lock.json, or open an issue at github.com/patchstack/connect.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n const pnpmLock = path.join(cwd, 'pnpm-lock.yaml');\n if (await exists(pnpmLock)) {\n throw new PatchstackError(\n 'pnpm-lock.yaml detected but not yet supported. Open an issue at github.com/patchstack/connect to request support.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n throw new PatchstackError(\n `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`,\n 'LOCKFILE_NOT_FOUND',\n );\n}\n\nexport async function scanLockfile(cwd: string): Promise {\n const detected = await detectLockfile(cwd);\n const packages = await runStrategy(detected, cwd);\n return { ecosystem: detected.ecosystem, packages };\n}\n\nasync function runStrategy(\n detected: DetectedLockfile,\n cwd: string,\n): Promise {\n switch (detected.strategy) {\n case 'npm-lockfile':\n return parseNpmLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n","export type Ecosystem = 'npm' | 'composer';\n\nexport interface PackageEntry {\n name: string;\n version: string;\n path?: string;\n direct?: boolean;\n}\n\nexport interface Manifest {\n ecosystem: Ecosystem;\n packages: PackageEntry[];\n}\n\nexport interface Config {\n /**\n * The site UUID. `null` means we don't have one yet — `postManifest` will then\n * post to the bare endpoint, the server will provision a fresh site, and the\n * UUID it returns should be persisted via `persistSiteUuid()`.\n */\n siteUuid: string | null;\n endpoint: string;\n timeoutMs: number;\n}\n\nexport interface StoreManifestResponse {\n /** The UUID of the site the manifest was stored against. Always returned. */\n uuid?: string;\n stored: boolean;\n manifest_id?: number;\n checksum?: string;\n reason?: string;\n message?: string;\n error?: string;\n}\n\nexport class PatchstackError extends Error {\n constructor(\n message: string,\n public readonly code:\n | 'CONFIG_MISSING'\n | 'CONFIG_INVALID'\n | 'LOCKFILE_NOT_FOUND'\n | 'LOCKFILE_UNSUPPORTED'\n | 'LOCKFILE_PARSE_ERROR'\n | 'NETWORK_ERROR'\n | 'NETWORK_TIMEOUT'\n | 'SITE_NOT_FOUND'\n | 'VALIDATION_ERROR'\n | 'SERVER_ERROR',\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = 'PatchstackError';\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\ninterface LockfileV2Package {\n name?: string;\n version?: string;\n link?: boolean;\n resolved?: string;\n}\n\ninterface LockfileV2 {\n lockfileVersion: number;\n packages?: Record;\n dependencies?: Record;\n}\n\ninterface LockfileV1Dependency {\n version: string;\n dependencies?: Record;\n}\n\nexport async function parseNpmLockfile(lockfilePath: string): Promise {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, 'LOCKFILE_NOT_FOUND', cause);\n }\n\n let parsed: LockfileV2;\n try {\n parsed = JSON.parse(raw) as LockfileV2;\n } catch (cause) {\n throw new PatchstackError(`Lockfile at ${lockfilePath} is not valid JSON`, 'LOCKFILE_PARSE_ERROR', cause);\n }\n\n if (parsed.packages) {\n return extractFromV2(parsed.packages);\n }\n\n if (parsed.dependencies) {\n return extractFromV1(parsed.dependencies);\n }\n\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} has no \"packages\" or \"dependencies\" key`,\n 'LOCKFILE_PARSE_ERROR',\n );\n}\n\nfunction extractFromV2(packages: Record): PackageEntry[] {\n const entries: PackageEntry[] = [];\n\n for (const [pkgPath, pkg] of Object.entries(packages)) {\n if (pkgPath === '') {\n continue;\n }\n if (pkg.link === true) {\n continue;\n }\n if (typeof pkg.version !== 'string' || pkg.version.length === 0) {\n continue;\n }\n\n const name = pkg.name ?? extractNameFromPath(pkgPath);\n if (name === null) {\n continue;\n }\n\n entries.push({\n name,\n version: pkg.version,\n path: pkgPath,\n direct: isDirectV2(pkgPath),\n });\n }\n\n return entries;\n}\n\nfunction extractFromV1(\n deps: Record,\n acc: PackageEntry[] = [],\n depth = 0,\n): PackageEntry[] {\n for (const [name, dep] of Object.entries(deps)) {\n if (typeof dep.version === 'string' && dep.version.length > 0) {\n acc.push({ name, version: dep.version, direct: depth === 0 });\n }\n if (dep.dependencies) {\n extractFromV1(dep.dependencies, acc, depth + 1);\n }\n }\n return acc;\n}\n\nfunction extractNameFromPath(pkgPath: string): string | null {\n const segments = pkgPath.split('node_modules' + path.sep === pkgPath ? path.sep : '/');\n const parts = pkgPath.split('/');\n const nmIndex = parts.lastIndexOf('node_modules');\n if (nmIndex === -1 || nmIndex >= parts.length - 1) {\n return segments[segments.length - 1] ?? null;\n }\n const tail = parts.slice(nmIndex + 1);\n if (tail.length === 0) {\n return null;\n }\n const first = tail[0];\n if (first !== undefined && first.startsWith('@') && tail.length >= 2) {\n return `${first}/${tail[1]}`;\n }\n return first ?? null;\n}\n\nfunction isDirectV2(pkgPath: string): boolean {\n const parts = pkgPath.split('/');\n const nmCount = parts.filter((p) => p === 'node_modules').length;\n return nmCount === 1;\n}\n","import { lstat, readFile, readdir, stat } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\nexport async function walkNodeModules(cwd: string): Promise {\n const root = path.join(cwd, 'node_modules');\n\n try {\n const info = await stat(root);\n if (!info.isDirectory()) {\n throw new PatchstackError(\n `${root} exists but is not a directory.`,\n 'LOCKFILE_NOT_FOUND',\n );\n }\n } catch (cause) {\n if (cause instanceof PatchstackError) {\n throw cause;\n }\n throw new PatchstackError(\n `node_modules/ not found at ${cwd}. Install dependencies first (e.g. \\`bun install\\` or \\`npm install\\`).`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const entries: PackageEntry[] = [];\n await walk(root, entries, 0);\n return entries;\n}\n\nasync function walk(dir: string, acc: PackageEntry[], depth: number): Promise {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n return;\n }\n\n for (const name of names) {\n if (name.startsWith('.')) {\n continue;\n }\n\n const fullPath = path.join(dir, name);\n if (!(await isPlainDirectory(fullPath))) {\n continue;\n }\n\n if (name.startsWith('@')) {\n let subNames: string[];\n try {\n subNames = await readdir(fullPath);\n } catch {\n continue;\n }\n for (const sub of subNames) {\n if (sub.startsWith('.')) {\n continue;\n }\n const scopedDir = path.join(fullPath, sub);\n if (!(await isPlainDirectory(scopedDir))) {\n continue;\n }\n await readPackage(scopedDir, depth, acc);\n await walkNested(scopedDir, acc, depth);\n }\n continue;\n }\n\n await readPackage(fullPath, depth, acc);\n await walkNested(fullPath, acc, depth);\n }\n}\n\nasync function readPackage(\n pkgDir: string,\n depth: number,\n acc: PackageEntry[],\n): Promise {\n let raw: string;\n try {\n raw = await readFile(path.join(pkgDir, 'package.json'), 'utf8');\n } catch {\n return;\n }\n\n let parsed: { name?: unknown; version?: unknown };\n try {\n parsed = JSON.parse(raw) as { name?: unknown; version?: unknown };\n } catch {\n return;\n }\n\n if (typeof parsed.name !== 'string' || parsed.name.length === 0) {\n return;\n }\n if (typeof parsed.version !== 'string' || parsed.version.length === 0) {\n return;\n }\n\n acc.push({\n name: parsed.name,\n version: parsed.version,\n direct: depth === 0,\n });\n}\n\nasync function walkNested(\n pkgDir: string,\n acc: PackageEntry[],\n depth: number,\n): Promise {\n const nested = path.join(pkgDir, 'node_modules');\n if (!(await isPlainDirectory(nested))) {\n return;\n }\n await walk(nested, acc, depth + 1);\n}\n\nasync function isPlainDirectory(dir: string): Promise {\n try {\n const info = await lstat(dir);\n return info.isDirectory() && !info.isSymbolicLink();\n } catch {\n return false;\n }\n}\n","import type { Manifest, PackageEntry } from './types.js';\n\nexport interface WirePackage {\n name: string;\n version: string;\n}\n\nexport interface WirePayload {\n ecosystem: Manifest['ecosystem'];\n packages: WirePackage[];\n}\n\nexport interface NormalizeStats {\n uniqueNames: number;\n duplicateNames: string[];\n totalEntries: number;\n}\n\nexport interface NormalizeResult {\n payload: WirePayload;\n stats: NormalizeStats;\n}\n\nexport function buildWirePayload(manifest: Manifest): NormalizeResult {\n const seen = new Map>();\n const wirePackages: WirePackage[] = [];\n\n for (const entry of manifest.packages) {\n const versions = seen.get(entry.name);\n if (versions) {\n if (versions.has(entry.version)) {\n continue;\n }\n versions.add(entry.version);\n } else {\n seen.set(entry.name, new Set([entry.version]));\n }\n wirePackages.push({ name: entry.name, version: entry.version });\n }\n\n wirePackages.sort((a, b) => {\n if (a.name === b.name) {\n return compareVersions(a.version, b.version);\n }\n return a.name < b.name ? -1 : 1;\n });\n\n const duplicateNames: string[] = [];\n for (const [name, versions] of seen) {\n if (versions.size > 1) {\n duplicateNames.push(name);\n }\n }\n\n return {\n payload: { ecosystem: manifest.ecosystem, packages: wirePackages },\n stats: {\n uniqueNames: seen.size,\n duplicateNames,\n totalEntries: manifest.packages.length,\n },\n };\n}\n\nexport function compareVersions(a: string, b: string): number {\n if (a === b) {\n return 0;\n }\n\n const [aBase, aPre] = splitPrerelease(a);\n const [bBase, bPre] = splitPrerelease(b);\n\n const baseCmp = compareSegments(aBase.split('.'), bBase.split('.'));\n if (baseCmp !== 0) {\n return baseCmp;\n }\n\n if (aPre === null && bPre === null) {\n return 0;\n }\n if (aPre === null) {\n return 1;\n }\n if (bPre === null) {\n return -1;\n }\n return compareSegments(aPre.split('.'), bPre.split('.'));\n}\n\nfunction splitPrerelease(version: string): [string, string | null] {\n const cleaned = version.replace(/^[v=]+/, '').split('+')[0]!;\n const dashIndex = cleaned.indexOf('-');\n if (dashIndex === -1) {\n return [cleaned, null];\n }\n return [cleaned.slice(0, dashIndex), cleaned.slice(dashIndex + 1)];\n}\n\nfunction compareSegments(a: string[], b: string[]): number {\n const max = Math.max(a.length, b.length);\n for (let i = 0; i < max; i++) {\n const aPart = a[i];\n const bPart = b[i];\n if (aPart === undefined) {\n return -1;\n }\n if (bPart === undefined) {\n return 1;\n }\n const aNum = /^\\d+$/.test(aPart);\n const bNum = /^\\d+$/.test(bPart);\n if (aNum && bNum) {\n const diff = Number(aPart) - Number(bPart);\n if (diff !== 0) {\n return diff < 0 ? -1 : 1;\n }\n continue;\n }\n if (aNum) {\n return -1;\n }\n if (bNum) {\n return 1;\n }\n if (aPart < bPart) {\n return -1;\n }\n if (aPart > bPart) {\n return 1;\n }\n }\n return 0;\n}\n\nexport function findPackageInManifest(\n manifest: Manifest,\n name: string,\n): PackageEntry[] {\n return manifest.packages.filter((p) => p.name === name);\n}\n","import { PatchstackError, type Config, type StoreManifestResponse } from './types.js';\nimport type { WirePayload } from './normalize.js';\n\nexport const DEFAULT_ENDPOINT = 'https://api.patchstack.com/monitor/pulse/manifest';\nexport const DEFAULT_TIMEOUT_MS = 30_000;\n\nexport function buildEndpointUrl(base: string, siteUuid?: string | null): string {\n const trimmed = base.replace(/\\/$/, '');\n return siteUuid !== undefined && siteUuid !== null && siteUuid.length > 0\n ? `${trimmed}/${encodeURIComponent(siteUuid)}`\n : trimmed;\n}\n\n/**\n * Build the claim URL for a site. The claim page lives on the same origin as\n * the API endpoint, at `/monitor/claim?site=`. Using the API endpoint's\n * origin (rather than a hard-coded https://api.patchstack.com) means staging,\n * ngrok tunnels and local dev environments all produce a claim URL on the same\n * host the connector is already talking to.\n */\nexport function buildClaimUrl(endpoint: string, siteUuid: string): string {\n const origin = new URL(endpoint).origin;\n return `${origin}/monitor/claim?site=${encodeURIComponent(siteUuid)}`;\n}\n\nexport async function postManifest(\n config: Config,\n payload: WirePayload,\n): Promise {\n const url = buildEndpointUrl(config.endpoint, config.siteUuid);\n const timeoutMs = config.timeoutMs;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'User-Agent': '@patchstack/connect',\n },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (cause) {\n if (isTimeoutError(cause)) {\n throw new PatchstackError(\n `Patchstack request to ${url} timed out after ${timeoutMs}ms. Override with PATCHSTACK_TIMEOUT_MS.`,\n 'NETWORK_TIMEOUT',\n cause,\n );\n }\n throw new PatchstackError(\n `Could not reach Patchstack at ${url}. Check your network connection.`,\n 'NETWORK_ERROR',\n cause,\n );\n }\n\n const text = await response.text();\n let body: StoreManifestResponse | null = null;\n try {\n body = text.length > 0 ? (JSON.parse(text) as StoreManifestResponse) : null;\n } catch {\n body = null;\n }\n\n if (response.status === 404) {\n throw new PatchstackError(\n body?.error ?? 'Site not found. Check that your site UUID is correct and that the app is registered as a Pulse app in your Patchstack dashboard.',\n 'SITE_NOT_FOUND',\n );\n }\n\n if (response.status === 422) {\n throw new PatchstackError(\n body?.message ?? 'Patchstack rejected the manifest payload (validation failed).',\n 'VALIDATION_ERROR',\n );\n }\n\n if (response.status < 200 || response.status >= 300) {\n throw new PatchstackError(\n `Patchstack returned ${response.status}: ${text.slice(0, 200)}`,\n 'SERVER_ERROR',\n );\n }\n\n if (body === null) {\n throw new PatchstackError('Patchstack returned an empty response.', 'SERVER_ERROR');\n }\n\n return body;\n}\n\nfunction isTimeoutError(cause: unknown): boolean {\n if (cause instanceof Error) {\n return cause.name === 'TimeoutError' || cause.name === 'AbortError';\n }\n return false;\n}\n","import { readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Config } from './types.js';\nimport { DEFAULT_ENDPOINT, DEFAULT_TIMEOUT_MS } from './client.js';\n\nconst CONFIG_FILENAME = '.patchstackrc.json';\n\ninterface ConfigFile {\n siteUuid?: string;\n endpoint?: string;\n timeoutMs?: number;\n}\n\nexport interface ResolveConfigOptions {\n cwd: string;\n cliSiteUuid?: string;\n cliEndpoint?: string;\n /**\n * When true, resolveConfig throws CONFIG_MISSING if no site UUID is configured.\n * Defaults to false: callers that can run without a UUID (the first `scan` after\n * `npm install`) just get `siteUuid: null` back and learn the UUID from the\n * server response.\n */\n requireSiteUuid?: boolean;\n}\n\nexport async function resolveConfig(options: ResolveConfigOptions): Promise {\n const fromFile = await readConfigFile(options.cwd);\n const fromEnv = readEnv();\n\n const siteUuid =\n options.cliSiteUuid ??\n fromEnv.siteUuid ??\n fromFile.siteUuid ??\n null;\n\n const endpoint =\n options.cliEndpoint ??\n fromEnv.endpoint ??\n fromFile.endpoint ??\n DEFAULT_ENDPOINT;\n\n const timeoutMs = fromEnv.timeoutMs ?? fromFile.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n if (siteUuid !== null && siteUuid.length > 0 && !isUuid(siteUuid)) {\n throw new PatchstackError(\n `Site UUID \"${siteUuid}\" does not look like a valid UUID.`,\n 'CONFIG_INVALID',\n );\n }\n\n if (options.requireSiteUuid && (siteUuid === null || siteUuid.length === 0)) {\n throw new PatchstackError(\n 'No site UUID configured. Run `patchstack-connect scan` to provision one, or set PATCHSTACK_SITE_UUID.',\n 'CONFIG_MISSING',\n );\n }\n\n return {\n siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid,\n endpoint,\n timeoutMs,\n };\n}\n\nexport async function writeConfigFile(cwd: string, config: ConfigFile): Promise {\n const target = path.join(cwd, CONFIG_FILENAME);\n const content = JSON.stringify(config, null, 2) + '\\n';\n await writeFile(target, content, 'utf8');\n return target;\n}\n\n/**\n * Merge a new siteUuid into the existing `.patchstackrc.json` (or create it).\n * Preserves any `endpoint` / `timeoutMs` the user already wrote.\n */\nexport async function persistSiteUuid(cwd: string, siteUuid: string): Promise {\n const existing = await readConfigFile(cwd);\n return writeConfigFile(cwd, { ...existing, siteUuid });\n}\n\nasync function readConfigFile(cwd: string): Promise {\n const target = path.join(cwd, CONFIG_FILENAME);\n let raw: string;\n try {\n raw = await readFile(target, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return {};\n }\n throw new PatchstackError(\n `Could not read ${target}: ${(err as Error).message}`,\n 'CONFIG_INVALID',\n err,\n );\n }\n\n try {\n return JSON.parse(raw) as ConfigFile;\n } catch (err) {\n throw new PatchstackError(\n `Config file ${target} contains invalid JSON.`,\n 'CONFIG_INVALID',\n err,\n );\n }\n}\n\nfunction readEnv(): ConfigFile {\n const timeoutRaw = process.env.PATCHSTACK_TIMEOUT_MS;\n let timeoutMs: number | undefined;\n if (timeoutRaw !== undefined && timeoutRaw.length > 0) {\n const parsed = Number(timeoutRaw);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n throw new PatchstackError(\n `PATCHSTACK_TIMEOUT_MS must be a positive number; got \"${timeoutRaw}\".`,\n 'CONFIG_INVALID',\n );\n }\n timeoutMs = parsed;\n }\n return {\n siteUuid: process.env.PATCHSTACK_SITE_UUID ?? undefined,\n endpoint: process.env.PATCHSTACK_ENDPOINT ?? undefined,\n timeoutMs,\n };\n}\n\nfunction isUuid(value: string): boolean {\n return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);\n}\n","import { scanLockfile } from './parsers/index.js';\nimport { buildWirePayload } from './normalize.js';\nimport { postManifest } from './client.js';\nimport { persistSiteUuid, resolveConfig } from './config.js';\nimport type { Config, Manifest, StoreManifestResponse } from './types.js';\n\nexport { scanLockfile, detectLockfile } from './parsers/index.js';\nexport { buildWirePayload, compareVersions } from './normalize.js';\nexport { postManifest, buildClaimUrl, buildEndpointUrl, DEFAULT_ENDPOINT } from './client.js';\nexport { persistSiteUuid, resolveConfig, writeConfigFile } from './config.js';\nexport {\n PatchstackError,\n type Config,\n type Ecosystem,\n type Manifest,\n type PackageEntry,\n type StoreManifestResponse,\n} from './types.js';\n\nexport interface ScanAndReportOptions {\n cwd?: string;\n config?: Config;\n}\n\nexport interface ScanAndReportResult {\n manifest: Manifest;\n response: StoreManifestResponse;\n duplicateNames: string[];\n uniqueNames: number;\n totalEntries: number;\n}\n\nexport async function scanAndReport(\n options: ScanAndReportOptions = {},\n): Promise {\n const cwd = options.cwd ?? process.cwd();\n const config = options.config ?? (await resolveConfig({ cwd }));\n const manifest = await scanLockfile(cwd);\n const { payload, stats } = buildWirePayload(manifest);\n const response = await postManifest(config, payload);\n\n // First-run convenience: if we didn't have a UUID and the server provisioned\n // one for us, persist it so subsequent runs target the same site.\n if (config.siteUuid === null && response.uuid !== undefined && response.uuid.length > 0) {\n await persistSiteUuid(cwd, response.uuid);\n }\n\n return {\n manifest,\n response,\n duplicateNames: stats.duplicateNames,\n uniqueNames: stats.uniqueNames,\n totalEntries: stats.totalEntries,\n };\n}\n"],"mappings":"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","names":["path","readFile","path","path","readFile","path","readFile","path","path","readFile"]} \ No newline at end of file