diff --git a/README.md b/README.md index bb18f63..52ff071 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ Precedence (highest wins): Environment variables: - `PATCHSTACK_SITE_UUID` — the site UUID from your Patchstack dashboard -- `PATCHSTACK_ENDPOINT` — override the API endpoint (default `http://api.patchstack.com/monitor/pulse/manifest`) +- `PATCHSTACK_ENDPOINT` — override the API endpoint (default `https://api.patchstack.com/monitor/pulse/manifest`) - `PATCHSTACK_TIMEOUT_MS` — request timeout in milliseconds (default `30000`) `.patchstackrc.json` example: diff --git a/dist/cli.js b/dist/cli.js index 37c1772..38f5fc0 100755 --- a/dist/cli.js +++ b/dist/cli.js @@ -382,7 +382,7 @@ function compareSegments(a, b) { } // src/client.ts -var DEFAULT_ENDPOINT = "http://api.patchstack.com/monitor/pulse/manifest"; +var DEFAULT_ENDPOINT = "https://api.patchstack.com/monitor/pulse/manifest"; var DEFAULT_TIMEOUT_MS = 3e4; function buildEndpointUrl(base, siteUuid) { const trimmed = base.replace(/\/$/, ""); @@ -390,7 +390,7 @@ function buildEndpointUrl(base, siteUuid) { } function buildClaimUrl(endpoint, siteUuid) { const origin = new URL(endpoint).origin; - return `${origin}/claim?site=${encodeURIComponent(siteUuid)}`; + return `${origin}/monitor/claim?site=${encodeURIComponent(siteUuid)}`; } async function postManifest(config, payload) { const url = buildEndpointUrl(config.endpoint, config.siteUuid); @@ -563,7 +563,7 @@ Options (for scan and status): Environment: PATCHSTACK_SITE_UUID Site UUID - PATCHSTACK_ENDPOINT API endpoint (default: http://api.patchstack.com/monitor/pulse/manifest) + 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. diff --git a/dist/cli.js.map b/dist/cli.js.map index 4d24ea4..34d15b8 100644 --- a/dist/cli.js.map +++ b/dist/cli.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/parsers/index.ts","../src/types.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/normalize.ts","../src/client.ts","../src/config.ts","../src/cli.ts"],"sourcesContent":["import { access } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Manifest, type PackageEntry } from '../types.js';\nimport { parseNpmLockfile } from './npm.js';\nimport { walkNodeModules } from './node_modules.js';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy = 'npm-lockfile' | 'node-modules-walk';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\nexport async function detectLockfile(cwd: string): Promise {\n const npmLock = path.join(cwd, 'package-lock.json');\n if (await exists(npmLock)) {\n return {\n ecosystem: 'npm',\n filePath: npmLock,\n filename: 'package-lock.json',\n strategy: 'npm-lockfile',\n };\n }\n\n const bunLock = path.join(cwd, 'bun.lock');\n if (await exists(bunLock)) {\n return {\n ecosystem: 'npm',\n filePath: bunLock,\n filename: 'bun.lock',\n strategy: 'node-modules-walk',\n };\n }\n\n const bunLockB = path.join(cwd, 'bun.lockb');\n if (await exists(bunLockB)) {\n return {\n ecosystem: 'npm',\n filePath: bunLockB,\n filename: 'bun.lockb',\n strategy: 'node-modules-walk',\n };\n }\n\n const yarnLock = path.join(cwd, 'yarn.lock');\n if (await exists(yarnLock)) {\n throw new PatchstackError(\n 'yarn.lock detected but not yet supported. Run `npm install` to generate a package-lock.json, or open an issue at github.com/patchstack/connect.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n const pnpmLock = path.join(cwd, 'pnpm-lock.yaml');\n if (await exists(pnpmLock)) {\n throw new PatchstackError(\n 'pnpm-lock.yaml detected but not yet supported. Open an issue at github.com/patchstack/connect to request support.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n throw new PatchstackError(\n `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`,\n 'LOCKFILE_NOT_FOUND',\n );\n}\n\nexport async function scanLockfile(cwd: string): Promise {\n const detected = await detectLockfile(cwd);\n const packages = await runStrategy(detected, cwd);\n return { ecosystem: detected.ecosystem, packages };\n}\n\nasync function runStrategy(\n detected: DetectedLockfile,\n cwd: string,\n): Promise {\n switch (detected.strategy) {\n case 'npm-lockfile':\n return parseNpmLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n","export type Ecosystem = 'npm' | 'composer';\n\nexport interface PackageEntry {\n name: string;\n version: string;\n path?: string;\n direct?: boolean;\n}\n\nexport interface Manifest {\n ecosystem: Ecosystem;\n packages: PackageEntry[];\n}\n\nexport interface Config {\n /**\n * The site UUID. `null` means we don't have one yet — `postManifest` will then\n * post to the bare endpoint, the server will provision a fresh site, and the\n * UUID it returns should be persisted via `persistSiteUuid()`.\n */\n siteUuid: string | null;\n endpoint: string;\n timeoutMs: number;\n}\n\nexport interface StoreManifestResponse {\n /** The UUID of the site the manifest was stored against. Always returned. */\n uuid?: string;\n stored: boolean;\n manifest_id?: number;\n checksum?: string;\n reason?: string;\n message?: string;\n error?: string;\n}\n\nexport class PatchstackError extends Error {\n constructor(\n message: string,\n public readonly code:\n | 'CONFIG_MISSING'\n | 'CONFIG_INVALID'\n | 'LOCKFILE_NOT_FOUND'\n | 'LOCKFILE_UNSUPPORTED'\n | 'LOCKFILE_PARSE_ERROR'\n | 'NETWORK_ERROR'\n | 'NETWORK_TIMEOUT'\n | 'SITE_NOT_FOUND'\n | 'VALIDATION_ERROR'\n | 'SERVER_ERROR',\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = 'PatchstackError';\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\ninterface LockfileV2Package {\n name?: string;\n version?: string;\n link?: boolean;\n resolved?: string;\n}\n\ninterface LockfileV2 {\n lockfileVersion: number;\n packages?: Record;\n dependencies?: Record;\n}\n\ninterface LockfileV1Dependency {\n version: string;\n dependencies?: Record;\n}\n\nexport async function parseNpmLockfile(lockfilePath: string): Promise {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, 'LOCKFILE_NOT_FOUND', cause);\n }\n\n let parsed: LockfileV2;\n try {\n parsed = JSON.parse(raw) as LockfileV2;\n } catch (cause) {\n throw new PatchstackError(`Lockfile at ${lockfilePath} is not valid JSON`, 'LOCKFILE_PARSE_ERROR', cause);\n }\n\n if (parsed.packages) {\n return extractFromV2(parsed.packages);\n }\n\n if (parsed.dependencies) {\n return extractFromV1(parsed.dependencies);\n }\n\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} has no \"packages\" or \"dependencies\" key`,\n 'LOCKFILE_PARSE_ERROR',\n );\n}\n\nfunction extractFromV2(packages: Record): PackageEntry[] {\n const entries: PackageEntry[] = [];\n\n for (const [pkgPath, pkg] of Object.entries(packages)) {\n if (pkgPath === '') {\n continue;\n }\n if (pkg.link === true) {\n continue;\n }\n if (typeof pkg.version !== 'string' || pkg.version.length === 0) {\n continue;\n }\n\n const name = pkg.name ?? extractNameFromPath(pkgPath);\n if (name === null) {\n continue;\n }\n\n entries.push({\n name,\n version: pkg.version,\n path: pkgPath,\n direct: isDirectV2(pkgPath),\n });\n }\n\n return entries;\n}\n\nfunction extractFromV1(\n deps: Record,\n acc: PackageEntry[] = [],\n depth = 0,\n): PackageEntry[] {\n for (const [name, dep] of Object.entries(deps)) {\n if (typeof dep.version === 'string' && dep.version.length > 0) {\n acc.push({ name, version: dep.version, direct: depth === 0 });\n }\n if (dep.dependencies) {\n extractFromV1(dep.dependencies, acc, depth + 1);\n }\n }\n return acc;\n}\n\nfunction extractNameFromPath(pkgPath: string): string | null {\n const segments = pkgPath.split('node_modules' + path.sep === pkgPath ? path.sep : '/');\n const parts = pkgPath.split('/');\n const nmIndex = parts.lastIndexOf('node_modules');\n if (nmIndex === -1 || nmIndex >= parts.length - 1) {\n return segments[segments.length - 1] ?? null;\n }\n const tail = parts.slice(nmIndex + 1);\n if (tail.length === 0) {\n return null;\n }\n const first = tail[0];\n if (first !== undefined && first.startsWith('@') && tail.length >= 2) {\n return `${first}/${tail[1]}`;\n }\n return first ?? null;\n}\n\nfunction isDirectV2(pkgPath: string): boolean {\n const parts = pkgPath.split('/');\n const nmCount = parts.filter((p) => p === 'node_modules').length;\n return nmCount === 1;\n}\n","import { lstat, readFile, readdir, stat } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\nexport async function walkNodeModules(cwd: string): Promise {\n const root = path.join(cwd, 'node_modules');\n\n try {\n const info = await stat(root);\n if (!info.isDirectory()) {\n throw new PatchstackError(\n `${root} exists but is not a directory.`,\n 'LOCKFILE_NOT_FOUND',\n );\n }\n } catch (cause) {\n if (cause instanceof PatchstackError) {\n throw cause;\n }\n throw new PatchstackError(\n `node_modules/ not found at ${cwd}. Install dependencies first (e.g. \\`bun install\\` or \\`npm install\\`).`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const entries: PackageEntry[] = [];\n await walk(root, entries, 0);\n return entries;\n}\n\nasync function walk(dir: string, acc: PackageEntry[], depth: number): Promise {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n return;\n }\n\n for (const name of names) {\n if (name.startsWith('.')) {\n continue;\n }\n\n const fullPath = path.join(dir, name);\n if (!(await isPlainDirectory(fullPath))) {\n continue;\n }\n\n if (name.startsWith('@')) {\n let subNames: string[];\n try {\n subNames = await readdir(fullPath);\n } catch {\n continue;\n }\n for (const sub of subNames) {\n if (sub.startsWith('.')) {\n continue;\n }\n const scopedDir = path.join(fullPath, sub);\n if (!(await isPlainDirectory(scopedDir))) {\n continue;\n }\n await readPackage(scopedDir, depth, acc);\n await walkNested(scopedDir, acc, depth);\n }\n continue;\n }\n\n await readPackage(fullPath, depth, acc);\n await walkNested(fullPath, acc, depth);\n }\n}\n\nasync function readPackage(\n pkgDir: string,\n depth: number,\n acc: PackageEntry[],\n): Promise {\n let raw: string;\n try {\n raw = await readFile(path.join(pkgDir, 'package.json'), 'utf8');\n } catch {\n return;\n }\n\n let parsed: { name?: unknown; version?: unknown };\n try {\n parsed = JSON.parse(raw) as { name?: unknown; version?: unknown };\n } catch {\n return;\n }\n\n if (typeof parsed.name !== 'string' || parsed.name.length === 0) {\n return;\n }\n if (typeof parsed.version !== 'string' || parsed.version.length === 0) {\n return;\n }\n\n acc.push({\n name: parsed.name,\n version: parsed.version,\n direct: depth === 0,\n });\n}\n\nasync function walkNested(\n pkgDir: string,\n acc: PackageEntry[],\n depth: number,\n): Promise {\n const nested = path.join(pkgDir, 'node_modules');\n if (!(await isPlainDirectory(nested))) {\n return;\n }\n await walk(nested, acc, depth + 1);\n}\n\nasync function isPlainDirectory(dir: string): Promise {\n try {\n const info = await lstat(dir);\n return info.isDirectory() && !info.isSymbolicLink();\n } catch {\n return false;\n }\n}\n","import type { Manifest, PackageEntry } from './types.js';\n\nexport interface WirePackage {\n name: string;\n version: string;\n}\n\nexport interface WirePayload {\n ecosystem: Manifest['ecosystem'];\n packages: WirePackage[];\n}\n\nexport interface NormalizeStats {\n uniqueNames: number;\n duplicateNames: string[];\n totalEntries: number;\n}\n\nexport interface NormalizeResult {\n payload: WirePayload;\n stats: NormalizeStats;\n}\n\nexport function buildWirePayload(manifest: Manifest): NormalizeResult {\n const seen = new Map>();\n const wirePackages: WirePackage[] = [];\n\n for (const entry of manifest.packages) {\n const versions = seen.get(entry.name);\n if (versions) {\n if (versions.has(entry.version)) {\n continue;\n }\n versions.add(entry.version);\n } else {\n seen.set(entry.name, new Set([entry.version]));\n }\n wirePackages.push({ name: entry.name, version: entry.version });\n }\n\n wirePackages.sort((a, b) => {\n if (a.name === b.name) {\n return compareVersions(a.version, b.version);\n }\n return a.name < b.name ? -1 : 1;\n });\n\n const duplicateNames: string[] = [];\n for (const [name, versions] of seen) {\n if (versions.size > 1) {\n duplicateNames.push(name);\n }\n }\n\n return {\n payload: { ecosystem: manifest.ecosystem, packages: wirePackages },\n stats: {\n uniqueNames: seen.size,\n duplicateNames,\n totalEntries: manifest.packages.length,\n },\n };\n}\n\nexport function compareVersions(a: string, b: string): number {\n if (a === b) {\n return 0;\n }\n\n const [aBase, aPre] = splitPrerelease(a);\n const [bBase, bPre] = splitPrerelease(b);\n\n const baseCmp = compareSegments(aBase.split('.'), bBase.split('.'));\n if (baseCmp !== 0) {\n return baseCmp;\n }\n\n if (aPre === null && bPre === null) {\n return 0;\n }\n if (aPre === null) {\n return 1;\n }\n if (bPre === null) {\n return -1;\n }\n return compareSegments(aPre.split('.'), bPre.split('.'));\n}\n\nfunction splitPrerelease(version: string): [string, string | null] {\n const cleaned = version.replace(/^[v=]+/, '').split('+')[0]!;\n const dashIndex = cleaned.indexOf('-');\n if (dashIndex === -1) {\n return [cleaned, null];\n }\n return [cleaned.slice(0, dashIndex), cleaned.slice(dashIndex + 1)];\n}\n\nfunction compareSegments(a: string[], b: string[]): number {\n const max = Math.max(a.length, b.length);\n for (let i = 0; i < max; i++) {\n const aPart = a[i];\n const bPart = b[i];\n if (aPart === undefined) {\n return -1;\n }\n if (bPart === undefined) {\n return 1;\n }\n const aNum = /^\\d+$/.test(aPart);\n const bNum = /^\\d+$/.test(bPart);\n if (aNum && bNum) {\n const diff = Number(aPart) - Number(bPart);\n if (diff !== 0) {\n return diff < 0 ? -1 : 1;\n }\n continue;\n }\n if (aNum) {\n return -1;\n }\n if (bNum) {\n return 1;\n }\n if (aPart < bPart) {\n return -1;\n }\n if (aPart > bPart) {\n return 1;\n }\n }\n return 0;\n}\n\nexport function findPackageInManifest(\n manifest: Manifest,\n name: string,\n): PackageEntry[] {\n return manifest.packages.filter((p) => p.name === name);\n}\n","import { PatchstackError, type Config, type StoreManifestResponse } from './types.js';\nimport type { WirePayload } from './normalize.js';\n\nexport const DEFAULT_ENDPOINT = 'http://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 `/claim?site=`. Using the API endpoint's origin\n * (rather than a hard-coded https://app.patchstack.com) means staging, ngrok\n * tunnels and local dev environments all produce a claim URL on the same host\n * 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}/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: http://api.patchstack.com/monitor/pulse/manifest)\n PATCHSTACK_TIMEOUT_MS Request timeout in ms (default: 30000)\n\nPrecedence: CLI flag > environment variable > .patchstackrc.json.\n\nExamples:\n npx @patchstack/connect scan\n npx @patchstack/connect scan --dry-run\n npx @patchstack/connect init 550e8400-e29b-41d4-a716-446655440000\n npx @patchstack/connect scan --site-uuid 550e8400-...-446655440000\n`;\n\nconst VALUE_FLAGS = new Set(['site-uuid', 'endpoint']);\n\ninterface ParsedArgs {\n command: string | null;\n positional: string[];\n flags: Map;\n}\n\nfunction parseArgs(argv: string[]): ParsedArgs {\n const args = argv.slice(2);\n const positional: string[] = [];\n const flags = new Map();\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n if (!arg.startsWith('--')) {\n positional.push(arg);\n continue;\n }\n const stripped = arg.slice(2);\n const eqIdx = stripped.indexOf('=');\n if (eqIdx !== -1) {\n flags.set(stripped.slice(0, eqIdx), stripped.slice(eqIdx + 1));\n continue;\n }\n const next = args[i + 1];\n if (VALUE_FLAGS.has(stripped) && next !== undefined && !next.startsWith('--')) {\n flags.set(stripped, next);\n i++;\n } else {\n flags.set(stripped, true);\n }\n }\n\n return {\n command: positional.shift() ?? null,\n positional,\n flags,\n };\n}\n\nfunction getStringFlag(flags: Map, name: string): string | undefined {\n const value = flags.get(name);\n return typeof value === 'string' ? value : undefined;\n}\n\nasync function runInit(args: ParsedArgs): Promise {\n const uuid = args.positional[0];\n if (!uuid) {\n console.error('Error: site UUID is required.\\n');\n console.error('Usage: patchstack-connect init ');\n return 1;\n }\n if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(uuid)) {\n console.error(`Error: \"${uuid}\" does not look like a valid UUID.`);\n return 1;\n }\n\n const target = await writeConfigFile(process.cwd(), { siteUuid: uuid });\n console.log(`Wrote ${target}`);\n console.log('');\n console.log('Next: run `npx @patchstack/connect scan` to send your first manifest.');\n return 0;\n}\n\nasync function runScan(args: ParsedArgs): Promise {\n const dryRun = args.flags.get('dry-run') === true;\n const config = await resolveConfig({\n cwd: process.cwd(),\n cliSiteUuid: getStringFlag(args.flags, 'site-uuid'),\n cliEndpoint: getStringFlag(args.flags, 'endpoint'),\n });\n const manifest = await scanLockfile(process.cwd());\n const { payload, stats } = buildWirePayload(manifest);\n\n console.log(\n `Found ${payload.packages.length} unique package versions across ${stats.uniqueNames} package names in ${manifest.ecosystem} lockfile.`,\n );\n if (stats.duplicateNames.length > 0) {\n console.log(`${stats.duplicateNames.length} package(s) appear at multiple versions:`);\n if (stats.duplicateNames.length <= 10) {\n console.log(` ${stats.duplicateNames.join(', ')}`);\n }\n }\n\n if (dryRun) {\n console.log('');\n if (config.siteUuid === null) {\n console.log('--dry-run: no site UUID configured. A real run would provision one.');\n } else {\n console.log(`--dry-run: not posting to Patchstack (site UUID ${config.siteUuid}).`);\n }\n console.log('Payload preview:');\n const preview = JSON.stringify(payload, null, 2).split('\\n');\n console.log(preview.slice(0, Math.min(preview.length, 30)).join('\\n'));\n if (preview.length > 30) {\n console.log(` ... (${preview.length - 30} more lines)`);\n }\n return 0;\n }\n\n const provisioning = config.siteUuid === null;\n if (provisioning) {\n console.log('No site UUID configured — provisioning a new Patchstack site from this manifest…');\n }\n\n const response = await postManifest(config, payload);\n\n // The server always returns the UUID. If we didn't have one, persist it so\n // every subsequent scan targets the same site.\n if (provisioning && response.uuid !== undefined && response.uuid.length > 0) {\n const target = await persistSiteUuid(process.cwd(), response.uuid);\n console.log(`Provisioned site ${response.uuid}. Saved UUID to ${target}.`);\n }\n\n if (response.stored) {\n console.log(`Stored manifest #${response.manifest_id} (checksum ${response.checksum}).`);\n } else if (response.reason === 'duplicate') {\n console.log('Manifest unchanged since last scan — nothing to store.');\n } else {\n console.log(`Server response: ${response.message ?? JSON.stringify(response)}`);\n }\n\n // On the first scan (provisioning), surface the claim URL so the user can\n // attach this site to their Patchstack account. `npx @patchstack/connect status`\n // re-displays it any time.\n if (provisioning && response.uuid !== undefined && response.uuid.length > 0) {\n console.log('');\n console.log('Claim this site to view vulnerability reports in your Patchstack dashboard:');\n console.log(` ${buildClaimUrl(config.endpoint, response.uuid)}`);\n }\n\n return 0;\n}\n\nasync function runStatus(args: ParsedArgs): Promise {\n const config = await resolveConfig({\n cwd: process.cwd(),\n cliSiteUuid: getStringFlag(args.flags, 'site-uuid'),\n cliEndpoint: getStringFlag(args.flags, 'endpoint'),\n });\n console.log(`Site UUID: ${config.siteUuid ?? '(none yet — the next `scan` will provision one)'}`);\n console.log(`Endpoint: ${config.endpoint}`);\n console.log(`Timeout: ${config.timeoutMs}ms`);\n if (config.siteUuid !== null) {\n console.log(`Claim URL: ${buildClaimUrl(config.endpoint, config.siteUuid)}`);\n }\n return 0;\n}\n\nasync function main(): Promise {\n const args = parseArgs(process.argv);\n\n if (args.flags.has('help') || args.command === 'help' || args.command === null) {\n console.log(HELP);\n return 0;\n }\n\n switch (args.command) {\n case 'init':\n return runInit(args);\n case 'scan':\n return runScan(args);\n case 'status':\n return runStatus(args);\n default:\n console.error(`Unknown command: ${args.command}\\n`);\n console.error(HELP);\n return 1;\n }\n}\n\nmain()\n .then((code) => process.exit(code))\n .catch((err: unknown) => {\n if (err instanceof PatchstackError) {\n console.error(`Error (${err.code}): ${err.message}`);\n process.exit(1);\n }\n console.error('Unexpected error:', err);\n process.exit(2);\n });\n"],"mappings":"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","names":["path","readFile","path","path","readFile","path","readFile","path","path","readFile"]} \ No newline at end of file +{"version":3,"sources":["../src/parsers/index.ts","../src/types.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/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 index 9515108..cf6e14a 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -428,7 +428,7 @@ function compareSegments(a, b) { } // src/client.ts -var DEFAULT_ENDPOINT = "http://api.patchstack.com/monitor/pulse/manifest"; +var DEFAULT_ENDPOINT = "https://api.patchstack.com/monitor/pulse/manifest"; var DEFAULT_TIMEOUT_MS = 3e4; function buildEndpointUrl(base, siteUuid) { const trimmed = base.replace(/\/$/, ""); @@ -436,7 +436,7 @@ function buildEndpointUrl(base, siteUuid) { } function buildClaimUrl(endpoint, siteUuid) { const origin = new URL(endpoint).origin; - return `${origin}/claim?site=${encodeURIComponent(siteUuid)}`; + return `${origin}/monitor/claim?site=${encodeURIComponent(siteUuid)}`; } async function postManifest(config, payload) { const url = buildEndpointUrl(config.endpoint, config.siteUuid); diff --git a/dist/index.cjs.map b/dist/index.cjs.map index 1625a03..7ae8720 100644 --- a/dist/index.cjs.map +++ b/dist/index.cjs.map @@ -1 +1 @@ -{"version":3,"sources":["../src/index.ts","../src/parsers/index.ts","../src/types.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/normalize.ts","../src/client.ts","../src/config.ts"],"sourcesContent":["import { scanLockfile } from './parsers/index.js';\nimport { buildWirePayload } from './normalize.js';\nimport { postManifest } from './client.js';\nimport { persistSiteUuid, resolveConfig } from './config.js';\nimport type { Config, Manifest, StoreManifestResponse } from './types.js';\n\nexport { scanLockfile, detectLockfile } from './parsers/index.js';\nexport { buildWirePayload, compareVersions } from './normalize.js';\nexport { postManifest, buildClaimUrl, buildEndpointUrl, DEFAULT_ENDPOINT } from './client.js';\nexport { persistSiteUuid, resolveConfig, writeConfigFile } from './config.js';\nexport {\n PatchstackError,\n type Config,\n type Ecosystem,\n type Manifest,\n type PackageEntry,\n type StoreManifestResponse,\n} from './types.js';\n\nexport interface ScanAndReportOptions {\n cwd?: string;\n config?: Config;\n}\n\nexport interface ScanAndReportResult {\n manifest: Manifest;\n response: StoreManifestResponse;\n duplicateNames: string[];\n uniqueNames: number;\n totalEntries: number;\n}\n\nexport async function scanAndReport(\n options: ScanAndReportOptions = {},\n): Promise {\n const cwd = options.cwd ?? process.cwd();\n const config = options.config ?? (await resolveConfig({ cwd }));\n const manifest = await scanLockfile(cwd);\n const { payload, stats } = buildWirePayload(manifest);\n const response = await postManifest(config, payload);\n\n // First-run convenience: if we didn't have a UUID and the server provisioned\n // one for us, persist it so subsequent runs target the same site.\n if (config.siteUuid === null && response.uuid !== undefined && response.uuid.length > 0) {\n await persistSiteUuid(cwd, response.uuid);\n }\n\n return {\n manifest,\n response,\n duplicateNames: stats.duplicateNames,\n uniqueNames: stats.uniqueNames,\n totalEntries: stats.totalEntries,\n };\n}\n","import { access } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Manifest, type PackageEntry } from '../types.js';\nimport { parseNpmLockfile } from './npm.js';\nimport { walkNodeModules } from './node_modules.js';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy = 'npm-lockfile' | 'node-modules-walk';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\nexport async function detectLockfile(cwd: string): Promise {\n const npmLock = path.join(cwd, 'package-lock.json');\n if (await exists(npmLock)) {\n return {\n ecosystem: 'npm',\n filePath: npmLock,\n filename: 'package-lock.json',\n strategy: 'npm-lockfile',\n };\n }\n\n const bunLock = path.join(cwd, 'bun.lock');\n if (await exists(bunLock)) {\n return {\n ecosystem: 'npm',\n filePath: bunLock,\n filename: 'bun.lock',\n strategy: 'node-modules-walk',\n };\n }\n\n const bunLockB = path.join(cwd, 'bun.lockb');\n if (await exists(bunLockB)) {\n return {\n ecosystem: 'npm',\n filePath: bunLockB,\n filename: 'bun.lockb',\n strategy: 'node-modules-walk',\n };\n }\n\n const yarnLock = path.join(cwd, 'yarn.lock');\n if (await exists(yarnLock)) {\n throw new PatchstackError(\n 'yarn.lock detected but not yet supported. Run `npm install` to generate a package-lock.json, or open an issue at github.com/patchstack/connect.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n const pnpmLock = path.join(cwd, 'pnpm-lock.yaml');\n if (await exists(pnpmLock)) {\n throw new PatchstackError(\n 'pnpm-lock.yaml detected but not yet supported. Open an issue at github.com/patchstack/connect to request support.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n throw new PatchstackError(\n `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`,\n 'LOCKFILE_NOT_FOUND',\n );\n}\n\nexport async function scanLockfile(cwd: string): Promise {\n const detected = await detectLockfile(cwd);\n const packages = await runStrategy(detected, cwd);\n return { ecosystem: detected.ecosystem, packages };\n}\n\nasync function runStrategy(\n detected: DetectedLockfile,\n cwd: string,\n): Promise {\n switch (detected.strategy) {\n case 'npm-lockfile':\n return parseNpmLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n","export type Ecosystem = 'npm' | 'composer';\n\nexport interface PackageEntry {\n name: string;\n version: string;\n path?: string;\n direct?: boolean;\n}\n\nexport interface Manifest {\n ecosystem: Ecosystem;\n packages: PackageEntry[];\n}\n\nexport interface Config {\n /**\n * The site UUID. `null` means we don't have one yet — `postManifest` will then\n * post to the bare endpoint, the server will provision a fresh site, and the\n * UUID it returns should be persisted via `persistSiteUuid()`.\n */\n siteUuid: string | null;\n endpoint: string;\n timeoutMs: number;\n}\n\nexport interface StoreManifestResponse {\n /** The UUID of the site the manifest was stored against. Always returned. */\n uuid?: string;\n stored: boolean;\n manifest_id?: number;\n checksum?: string;\n reason?: string;\n message?: string;\n error?: string;\n}\n\nexport class PatchstackError extends Error {\n constructor(\n message: string,\n public readonly code:\n | 'CONFIG_MISSING'\n | 'CONFIG_INVALID'\n | 'LOCKFILE_NOT_FOUND'\n | 'LOCKFILE_UNSUPPORTED'\n | 'LOCKFILE_PARSE_ERROR'\n | 'NETWORK_ERROR'\n | 'NETWORK_TIMEOUT'\n | 'SITE_NOT_FOUND'\n | 'VALIDATION_ERROR'\n | 'SERVER_ERROR',\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = 'PatchstackError';\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\ninterface LockfileV2Package {\n name?: string;\n version?: string;\n link?: boolean;\n resolved?: string;\n}\n\ninterface LockfileV2 {\n lockfileVersion: number;\n packages?: Record;\n dependencies?: Record;\n}\n\ninterface LockfileV1Dependency {\n version: string;\n dependencies?: Record;\n}\n\nexport async function parseNpmLockfile(lockfilePath: string): Promise {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, 'LOCKFILE_NOT_FOUND', cause);\n }\n\n let parsed: LockfileV2;\n try {\n parsed = JSON.parse(raw) as LockfileV2;\n } catch (cause) {\n throw new PatchstackError(`Lockfile at ${lockfilePath} is not valid JSON`, 'LOCKFILE_PARSE_ERROR', cause);\n }\n\n if (parsed.packages) {\n return extractFromV2(parsed.packages);\n }\n\n if (parsed.dependencies) {\n return extractFromV1(parsed.dependencies);\n }\n\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} has no \"packages\" or \"dependencies\" key`,\n 'LOCKFILE_PARSE_ERROR',\n );\n}\n\nfunction extractFromV2(packages: Record): PackageEntry[] {\n const entries: PackageEntry[] = [];\n\n for (const [pkgPath, pkg] of Object.entries(packages)) {\n if (pkgPath === '') {\n continue;\n }\n if (pkg.link === true) {\n continue;\n }\n if (typeof pkg.version !== 'string' || pkg.version.length === 0) {\n continue;\n }\n\n const name = pkg.name ?? extractNameFromPath(pkgPath);\n if (name === null) {\n continue;\n }\n\n entries.push({\n name,\n version: pkg.version,\n path: pkgPath,\n direct: isDirectV2(pkgPath),\n });\n }\n\n return entries;\n}\n\nfunction extractFromV1(\n deps: Record,\n acc: PackageEntry[] = [],\n depth = 0,\n): PackageEntry[] {\n for (const [name, dep] of Object.entries(deps)) {\n if (typeof dep.version === 'string' && dep.version.length > 0) {\n acc.push({ name, version: dep.version, direct: depth === 0 });\n }\n if (dep.dependencies) {\n extractFromV1(dep.dependencies, acc, depth + 1);\n }\n }\n return acc;\n}\n\nfunction extractNameFromPath(pkgPath: string): string | null {\n const segments = pkgPath.split('node_modules' + path.sep === pkgPath ? path.sep : '/');\n const parts = pkgPath.split('/');\n const nmIndex = parts.lastIndexOf('node_modules');\n if (nmIndex === -1 || nmIndex >= parts.length - 1) {\n return segments[segments.length - 1] ?? null;\n }\n const tail = parts.slice(nmIndex + 1);\n if (tail.length === 0) {\n return null;\n }\n const first = tail[0];\n if (first !== undefined && first.startsWith('@') && tail.length >= 2) {\n return `${first}/${tail[1]}`;\n }\n return first ?? null;\n}\n\nfunction isDirectV2(pkgPath: string): boolean {\n const parts = pkgPath.split('/');\n const nmCount = parts.filter((p) => p === 'node_modules').length;\n return nmCount === 1;\n}\n","import { lstat, readFile, readdir, stat } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\nexport async function walkNodeModules(cwd: string): Promise {\n const root = path.join(cwd, 'node_modules');\n\n try {\n const info = await stat(root);\n if (!info.isDirectory()) {\n throw new PatchstackError(\n `${root} exists but is not a directory.`,\n 'LOCKFILE_NOT_FOUND',\n );\n }\n } catch (cause) {\n if (cause instanceof PatchstackError) {\n throw cause;\n }\n throw new PatchstackError(\n `node_modules/ not found at ${cwd}. Install dependencies first (e.g. \\`bun install\\` or \\`npm install\\`).`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const entries: PackageEntry[] = [];\n await walk(root, entries, 0);\n return entries;\n}\n\nasync function walk(dir: string, acc: PackageEntry[], depth: number): Promise {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n return;\n }\n\n for (const name of names) {\n if (name.startsWith('.')) {\n continue;\n }\n\n const fullPath = path.join(dir, name);\n if (!(await isPlainDirectory(fullPath))) {\n continue;\n }\n\n if (name.startsWith('@')) {\n let subNames: string[];\n try {\n subNames = await readdir(fullPath);\n } catch {\n continue;\n }\n for (const sub of subNames) {\n if (sub.startsWith('.')) {\n continue;\n }\n const scopedDir = path.join(fullPath, sub);\n if (!(await isPlainDirectory(scopedDir))) {\n continue;\n }\n await readPackage(scopedDir, depth, acc);\n await walkNested(scopedDir, acc, depth);\n }\n continue;\n }\n\n await readPackage(fullPath, depth, acc);\n await walkNested(fullPath, acc, depth);\n }\n}\n\nasync function readPackage(\n pkgDir: string,\n depth: number,\n acc: PackageEntry[],\n): Promise {\n let raw: string;\n try {\n raw = await readFile(path.join(pkgDir, 'package.json'), 'utf8');\n } catch {\n return;\n }\n\n let parsed: { name?: unknown; version?: unknown };\n try {\n parsed = JSON.parse(raw) as { name?: unknown; version?: unknown };\n } catch {\n return;\n }\n\n if (typeof parsed.name !== 'string' || parsed.name.length === 0) {\n return;\n }\n if (typeof parsed.version !== 'string' || parsed.version.length === 0) {\n return;\n }\n\n acc.push({\n name: parsed.name,\n version: parsed.version,\n direct: depth === 0,\n });\n}\n\nasync function walkNested(\n pkgDir: string,\n acc: PackageEntry[],\n depth: number,\n): Promise {\n const nested = path.join(pkgDir, 'node_modules');\n if (!(await isPlainDirectory(nested))) {\n return;\n }\n await walk(nested, acc, depth + 1);\n}\n\nasync function isPlainDirectory(dir: string): Promise {\n try {\n const info = await lstat(dir);\n return info.isDirectory() && !info.isSymbolicLink();\n } catch {\n return false;\n }\n}\n","import type { Manifest, PackageEntry } from './types.js';\n\nexport interface WirePackage {\n name: string;\n version: string;\n}\n\nexport interface WirePayload {\n ecosystem: Manifest['ecosystem'];\n packages: WirePackage[];\n}\n\nexport interface NormalizeStats {\n uniqueNames: number;\n duplicateNames: string[];\n totalEntries: number;\n}\n\nexport interface NormalizeResult {\n payload: WirePayload;\n stats: NormalizeStats;\n}\n\nexport function buildWirePayload(manifest: Manifest): NormalizeResult {\n const seen = new Map>();\n const wirePackages: WirePackage[] = [];\n\n for (const entry of manifest.packages) {\n const versions = seen.get(entry.name);\n if (versions) {\n if (versions.has(entry.version)) {\n continue;\n }\n versions.add(entry.version);\n } else {\n seen.set(entry.name, new Set([entry.version]));\n }\n wirePackages.push({ name: entry.name, version: entry.version });\n }\n\n wirePackages.sort((a, b) => {\n if (a.name === b.name) {\n return compareVersions(a.version, b.version);\n }\n return a.name < b.name ? -1 : 1;\n });\n\n const duplicateNames: string[] = [];\n for (const [name, versions] of seen) {\n if (versions.size > 1) {\n duplicateNames.push(name);\n }\n }\n\n return {\n payload: { ecosystem: manifest.ecosystem, packages: wirePackages },\n stats: {\n uniqueNames: seen.size,\n duplicateNames,\n totalEntries: manifest.packages.length,\n },\n };\n}\n\nexport function compareVersions(a: string, b: string): number {\n if (a === b) {\n return 0;\n }\n\n const [aBase, aPre] = splitPrerelease(a);\n const [bBase, bPre] = splitPrerelease(b);\n\n const baseCmp = compareSegments(aBase.split('.'), bBase.split('.'));\n if (baseCmp !== 0) {\n return baseCmp;\n }\n\n if (aPre === null && bPre === null) {\n return 0;\n }\n if (aPre === null) {\n return 1;\n }\n if (bPre === null) {\n return -1;\n }\n return compareSegments(aPre.split('.'), bPre.split('.'));\n}\n\nfunction splitPrerelease(version: string): [string, string | null] {\n const cleaned = version.replace(/^[v=]+/, '').split('+')[0]!;\n const dashIndex = cleaned.indexOf('-');\n if (dashIndex === -1) {\n return [cleaned, null];\n }\n return [cleaned.slice(0, dashIndex), cleaned.slice(dashIndex + 1)];\n}\n\nfunction compareSegments(a: string[], b: string[]): number {\n const max = Math.max(a.length, b.length);\n for (let i = 0; i < max; i++) {\n const aPart = a[i];\n const bPart = b[i];\n if (aPart === undefined) {\n return -1;\n }\n if (bPart === undefined) {\n return 1;\n }\n const aNum = /^\\d+$/.test(aPart);\n const bNum = /^\\d+$/.test(bPart);\n if (aNum && bNum) {\n const diff = Number(aPart) - Number(bPart);\n if (diff !== 0) {\n return diff < 0 ? -1 : 1;\n }\n continue;\n }\n if (aNum) {\n return -1;\n }\n if (bNum) {\n return 1;\n }\n if (aPart < bPart) {\n return -1;\n }\n if (aPart > bPart) {\n return 1;\n }\n }\n return 0;\n}\n\nexport function findPackageInManifest(\n manifest: Manifest,\n name: string,\n): PackageEntry[] {\n return manifest.packages.filter((p) => p.name === name);\n}\n","import { PatchstackError, type Config, type StoreManifestResponse } from './types.js';\nimport type { WirePayload } from './normalize.js';\n\nexport const DEFAULT_ENDPOINT = 'http://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 `/claim?site=`. Using the API endpoint's origin\n * (rather than a hard-coded https://app.patchstack.com) means staging, ngrok\n * tunnels and local dev environments all produce a claim URL on the same host\n * 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}/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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAAA,mBAAuB;AACvB,IAAAC,oBAAiB;;;ACmCV,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACzC,YACE,SACgB,MAWA,OAChB;AACA,UAAM,OAAO;AAbG;AAWA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EAfkB;AAAA,EAWA;AAKpB;;;ACvDA,sBAAyB;AACzB,uBAAiB;AAqBjB,eAAsB,iBAAiB,cAA+C;AACpF,MAAI;AACJ,MAAI;AACF,UAAM,UAAM,0BAAS,cAAc,MAAM;AAAA,EAC3C,SAAS,OAAO;AACd,UAAM,IAAI,gBAAgB,8BAA8B,YAAY,IAAI,sBAAsB,KAAK;AAAA,EACrG;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,OAAO;AACd,UAAM,IAAI,gBAAgB,eAAe,YAAY,sBAAsB,wBAAwB,KAAK;AAAA,EAC1G;AAEA,MAAI,OAAO,UAAU;AACnB,WAAO,cAAc,OAAO,QAAQ;AAAA,EACtC;AAEA,MAAI,OAAO,cAAc;AACvB,WAAO,cAAc,OAAO,YAAY;AAAA,EAC1C;AAEA,QAAM,IAAI;AAAA,IACR,eAAe,YAAY;AAAA,IAC3B;AAAA,EACF;AACF;AAEA,SAAS,cAAc,UAA6D;AAClF,QAAM,UAA0B,CAAC;AAEjC,aAAW,CAAC,SAAS,GAAG,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACrD,QAAI,YAAY,IAAI;AAClB;AAAA,IACF;AACA,QAAI,IAAI,SAAS,MAAM;AACrB;AAAA,IACF;AACA,QAAI,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,WAAW,GAAG;AAC/D;AAAA,IACF;AAEA,UAAM,OAAO,IAAI,QAAQ,oBAAoB,OAAO;AACpD,QAAI,SAAS,MAAM;AACjB;AAAA,IACF;AAEA,YAAQ,KAAK;AAAA,MACX;AAAA,MACA,SAAS,IAAI;AAAA,MACb,MAAM;AAAA,MACN,QAAQ,WAAW,OAAO;AAAA,IAC5B,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAEA,SAAS,cACP,MACA,MAAsB,CAAC,GACvB,QAAQ,GACQ;AAChB,aAAW,CAAC,MAAM,GAAG,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC9C,QAAI,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,SAAS,GAAG;AAC7D,UAAI,KAAK,EAAE,MAAM,SAAS,IAAI,SAAS,QAAQ,UAAU,EAAE,CAAC;AAAA,IAC9D;AACA,QAAI,IAAI,cAAc;AACpB,oBAAc,IAAI,cAAc,KAAK,QAAQ,CAAC;AAAA,IAChD;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,SAAgC;AAC3D,QAAM,WAAW,QAAQ,MAAM,iBAAiB,iBAAAC,QAAK,QAAQ,UAAU,iBAAAA,QAAK,MAAM,GAAG;AACrF,QAAM,QAAQ,QAAQ,MAAM,GAAG;AAC/B,QAAM,UAAU,MAAM,YAAY,cAAc;AAChD,MAAI,YAAY,MAAM,WAAW,MAAM,SAAS,GAAG;AACjD,WAAO,SAAS,SAAS,SAAS,CAAC,KAAK;AAAA,EAC1C;AACA,QAAM,OAAO,MAAM,MAAM,UAAU,CAAC;AACpC,MAAI,KAAK,WAAW,GAAG;AACrB,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,KAAK,CAAC;AACpB,MAAI,UAAU,UAAa,MAAM,WAAW,GAAG,KAAK,KAAK,UAAU,GAAG;AACpE,WAAO,GAAG,KAAK,IAAI,KAAK,CAAC,CAAC;AAAA,EAC5B;AACA,SAAO,SAAS;AAClB;AAEA,SAAS,WAAW,SAA0B;AAC5C,QAAM,QAAQ,QAAQ,MAAM,GAAG;AAC/B,QAAM,UAAU,MAAM,OAAO,CAAC,MAAM,MAAM,cAAc,EAAE;AAC1D,SAAO,YAAY;AACrB;;;ACvHA,IAAAC,mBAA+C;AAC/C,IAAAC,oBAAiB;AAGjB,eAAsB,gBAAgB,KAAsC;AAC1E,QAAM,OAAO,kBAAAC,QAAK,KAAK,KAAK,cAAc;AAE1C,MAAI;AACF,UAAM,OAAO,UAAM,uBAAK,IAAI;AAC5B,QAAI,CAAC,KAAK,YAAY,GAAG;AACvB,YAAM,IAAI;AAAA,QACR,GAAG,IAAI;AAAA,QACP;AAAA,MACF;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,QAAI,iBAAiB,iBAAiB;AACpC,YAAM;AAAA,IACR;AACA,UAAM,IAAI;AAAA,MACR,8BAA8B,GAAG;AAAA,MACjC;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAA0B,CAAC;AACjC,QAAM,KAAK,MAAM,SAAS,CAAC;AAC3B,SAAO;AACT;AAEA,eAAe,KAAK,KAAa,KAAqB,OAA8B;AAClF,MAAI;AACJ,MAAI;AACF,YAAQ,UAAM,0BAAQ,GAAG;AAAA,EAC3B,QAAQ;AACN;AAAA,EACF;AAEA,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,WAAW,GAAG,GAAG;AACxB;AAAA,IACF;AAEA,UAAM,WAAW,kBAAAA,QAAK,KAAK,KAAK,IAAI;AACpC,QAAI,CAAE,MAAM,iBAAiB,QAAQ,GAAI;AACvC;AAAA,IACF;AAEA,QAAI,KAAK,WAAW,GAAG,GAAG;AACxB,UAAI;AACJ,UAAI;AACF,mBAAW,UAAM,0BAAQ,QAAQ;AAAA,MACnC,QAAQ;AACN;AAAA,MACF;AACA,iBAAW,OAAO,UAAU;AAC1B,YAAI,IAAI,WAAW,GAAG,GAAG;AACvB;AAAA,QACF;AACA,cAAM,YAAY,kBAAAA,QAAK,KAAK,UAAU,GAAG;AACzC,YAAI,CAAE,MAAM,iBAAiB,SAAS,GAAI;AACxC;AAAA,QACF;AACA,cAAM,YAAY,WAAW,OAAO,GAAG;AACvC,cAAM,WAAW,WAAW,KAAK,KAAK;AAAA,MACxC;AACA;AAAA,IACF;AAEA,UAAM,YAAY,UAAU,OAAO,GAAG;AACtC,UAAM,WAAW,UAAU,KAAK,KAAK;AAAA,EACvC;AACF;AAEA,eAAe,YACb,QACA,OACA,KACe;AACf,MAAI;AACJ,MAAI;AACF,UAAM,UAAM,2BAAS,kBAAAA,QAAK,KAAK,QAAQ,cAAc,GAAG,MAAM;AAAA,EAChE,QAAQ;AACN;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,QAAQ;AACN;AAAA,EACF;AAEA,MAAI,OAAO,OAAO,SAAS,YAAY,OAAO,KAAK,WAAW,GAAG;AAC/D;AAAA,EACF;AACA,MAAI,OAAO,OAAO,YAAY,YAAY,OAAO,QAAQ,WAAW,GAAG;AACrE;AAAA,EACF;AAEA,MAAI,KAAK;AAAA,IACP,MAAM,OAAO;AAAA,IACb,SAAS,OAAO;AAAA,IAChB,QAAQ,UAAU;AAAA,EACpB,CAAC;AACH;AAEA,eAAe,WACb,QACA,KACA,OACe;AACf,QAAM,SAAS,kBAAAA,QAAK,KAAK,QAAQ,cAAc;AAC/C,MAAI,CAAE,MAAM,iBAAiB,MAAM,GAAI;AACrC;AAAA,EACF;AACA,QAAM,KAAK,QAAQ,KAAK,QAAQ,CAAC;AACnC;AAEA,eAAe,iBAAiB,KAA+B;AAC7D,MAAI;AACF,UAAM,OAAO,UAAM,wBAAM,GAAG;AAC5B,WAAO,KAAK,YAAY,KAAK,CAAC,KAAK,eAAe;AAAA,EACpD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AHzGA,eAAsB,eAAe,KAAwC;AAC3E,QAAM,UAAU,kBAAAC,QAAK,KAAK,KAAK,mBAAmB;AAClD,MAAI,MAAM,OAAO,OAAO,GAAG;AACzB,WAAO;AAAA,MACL,WAAW;AAAA,MACX,UAAU;AAAA,MACV,UAAU;AAAA,MACV,UAAU;AAAA,IACZ;AAAA,EACF;AAEA,QAAM,UAAU,kBAAAA,QAAK,KAAK,KAAK,UAAU;AACzC,MAAI,MAAM,OAAO,OAAO,GAAG;AACzB,WAAO;AAAA,MACL,WAAW;AAAA,MACX,UAAU;AAAA,MACV,UAAU;AAAA,MACV,UAAU;AAAA,IACZ;AAAA,EACF;AAEA,QAAM,WAAW,kBAAAA,QAAK,KAAK,KAAK,WAAW;AAC3C,MAAI,MAAM,OAAO,QAAQ,GAAG;AAC1B,WAAO;AAAA,MACL,WAAW;AAAA,MACX,UAAU;AAAA,MACV,UAAU;AAAA,MACV,UAAU;AAAA,IACZ;AAAA,EACF;AAEA,QAAM,WAAW,kBAAAA,QAAK,KAAK,KAAK,WAAW;AAC3C,MAAI,MAAM,OAAO,QAAQ,GAAG;AAC1B,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,WAAW,kBAAAA,QAAK,KAAK,KAAK,gBAAgB;AAChD,MAAI,MAAM,OAAO,QAAQ,GAAG;AAC1B,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,IAAI;AAAA,IACR,wBAAwB,GAAG;AAAA,IAC3B;AAAA,EACF;AACF;AAEA,eAAsB,aAAa,KAAgC;AACjE,QAAM,WAAW,MAAM,eAAe,GAAG;AACzC,QAAM,WAAW,MAAM,YAAY,UAAU,GAAG;AAChD,SAAO,EAAE,WAAW,SAAS,WAAW,SAAS;AACnD;AAEA,eAAe,YACb,UACA,KACyB;AACzB,UAAQ,SAAS,UAAU;AAAA,IACzB,KAAK;AACH,aAAO,iBAAiB,SAAS,QAAQ;AAAA,IAC3C,KAAK;AACH,aAAO,gBAAgB,GAAG;AAAA,EAC9B;AACF;AAEA,eAAe,OAAO,UAAoC;AACxD,MAAI;AACF,cAAM,yBAAO,QAAQ;AACrB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AI7EO,SAAS,iBAAiB,UAAqC;AACpE,QAAM,OAAO,oBAAI,IAAyB;AAC1C,QAAM,eAA8B,CAAC;AAErC,aAAW,SAAS,SAAS,UAAU;AACrC,UAAM,WAAW,KAAK,IAAI,MAAM,IAAI;AACpC,QAAI,UAAU;AACZ,UAAI,SAAS,IAAI,MAAM,OAAO,GAAG;AAC/B;AAAA,MACF;AACA,eAAS,IAAI,MAAM,OAAO;AAAA,IAC5B,OAAO;AACL,WAAK,IAAI,MAAM,MAAM,oBAAI,IAAI,CAAC,MAAM,OAAO,CAAC,CAAC;AAAA,IAC/C;AACA,iBAAa,KAAK,EAAE,MAAM,MAAM,MAAM,SAAS,MAAM,QAAQ,CAAC;AAAA,EAChE;AAEA,eAAa,KAAK,CAAC,GAAG,MAAM;AAC1B,QAAI,EAAE,SAAS,EAAE,MAAM;AACrB,aAAO,gBAAgB,EAAE,SAAS,EAAE,OAAO;AAAA,IAC7C;AACA,WAAO,EAAE,OAAO,EAAE,OAAO,KAAK;AAAA,EAChC,CAAC;AAED,QAAM,iBAA2B,CAAC;AAClC,aAAW,CAAC,MAAM,QAAQ,KAAK,MAAM;AACnC,QAAI,SAAS,OAAO,GAAG;AACrB,qBAAe,KAAK,IAAI;AAAA,IAC1B;AAAA,EACF;AAEA,SAAO;AAAA,IACL,SAAS,EAAE,WAAW,SAAS,WAAW,UAAU,aAAa;AAAA,IACjE,OAAO;AAAA,MACL,aAAa,KAAK;AAAA,MAClB;AAAA,MACA,cAAc,SAAS,SAAS;AAAA,IAClC;AAAA,EACF;AACF;AAEO,SAAS,gBAAgB,GAAW,GAAmB;AAC5D,MAAI,MAAM,GAAG;AACX,WAAO;AAAA,EACT;AAEA,QAAM,CAAC,OAAO,IAAI,IAAI,gBAAgB,CAAC;AACvC,QAAM,CAAC,OAAO,IAAI,IAAI,gBAAgB,CAAC;AAEvC,QAAM,UAAU,gBAAgB,MAAM,MAAM,GAAG,GAAG,MAAM,MAAM,GAAG,CAAC;AAClE,MAAI,YAAY,GAAG;AACjB,WAAO;AAAA,EACT;AAEA,MAAI,SAAS,QAAQ,SAAS,MAAM;AAClC,WAAO;AAAA,EACT;AACA,MAAI,SAAS,MAAM;AACjB,WAAO;AAAA,EACT;AACA,MAAI,SAAS,MAAM;AACjB,WAAO;AAAA,EACT;AACA,SAAO,gBAAgB,KAAK,MAAM,GAAG,GAAG,KAAK,MAAM,GAAG,CAAC;AACzD;AAEA,SAAS,gBAAgB,SAA0C;AACjE,QAAM,UAAU,QAAQ,QAAQ,UAAU,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAC1D,QAAM,YAAY,QAAQ,QAAQ,GAAG;AACrC,MAAI,cAAc,IAAI;AACpB,WAAO,CAAC,SAAS,IAAI;AAAA,EACvB;AACA,SAAO,CAAC,QAAQ,MAAM,GAAG,SAAS,GAAG,QAAQ,MAAM,YAAY,CAAC,CAAC;AACnE;AAEA,SAAS,gBAAgB,GAAa,GAAqB;AACzD,QAAM,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM;AACvC,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,UAAM,QAAQ,EAAE,CAAC;AACjB,UAAM,QAAQ,EAAE,CAAC;AACjB,QAAI,UAAU,QAAW;AACvB,aAAO;AAAA,IACT;AACA,QAAI,UAAU,QAAW;AACvB,aAAO;AAAA,IACT;AACA,UAAM,OAAO,QAAQ,KAAK,KAAK;AAC/B,UAAM,OAAO,QAAQ,KAAK,KAAK;AAC/B,QAAI,QAAQ,MAAM;AAChB,YAAM,OAAO,OAAO,KAAK,IAAI,OAAO,KAAK;AACzC,UAAI,SAAS,GAAG;AACd,eAAO,OAAO,IAAI,KAAK;AAAA,MACzB;AACA;AAAA,IACF;AACA,QAAI,MAAM;AACR,aAAO;AAAA,IACT;AACA,QAAI,MAAM;AACR,aAAO;AAAA,IACT;AACA,QAAI,QAAQ,OAAO;AACjB,aAAO;AAAA,IACT;AACA,QAAI,QAAQ,OAAO;AACjB,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;;;ACjIO,IAAM,mBAAmB;AACzB,IAAM,qBAAqB;AAE3B,SAAS,iBAAiB,MAAc,UAAkC;AAC/E,QAAM,UAAU,KAAK,QAAQ,OAAO,EAAE;AACtC,SAAO,aAAa,UAAa,aAAa,QAAQ,SAAS,SAAS,IACpE,GAAG,OAAO,IAAI,mBAAmB,QAAQ,CAAC,KAC1C;AACN;AASO,SAAS,cAAc,UAAkB,UAA0B;AACxE,QAAM,SAAS,IAAI,IAAI,QAAQ,EAAE;AACjC,SAAO,GAAG,MAAM,eAAe,mBAAmB,QAAQ,CAAC;AAC7D;AAEA,eAAsB,aACpB,QACA,SACgC;AAChC,QAAM,MAAM,iBAAiB,OAAO,UAAU,OAAO,QAAQ;AAC7D,QAAM,YAAY,OAAO;AAEzB,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,MAAM,KAAK;AAAA,MAC1B,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,QAAQ;AAAA,QACR,cAAc;AAAA,MAChB;AAAA,MACA,MAAM,KAAK,UAAU,OAAO;AAAA,MAC5B,QAAQ,YAAY,QAAQ,SAAS;AAAA,IACvC,CAAC;AAAA,EACH,SAAS,OAAO;AACd,QAAI,eAAe,KAAK,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,yBAAyB,GAAG,oBAAoB,SAAS;AAAA,QACzD;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,UAAM,IAAI;AAAA,MACR,iCAAiC,GAAG;AAAA,MACpC;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAO,MAAM,SAAS,KAAK;AACjC,MAAI,OAAqC;AACzC,MAAI;AACF,WAAO,KAAK,SAAS,IAAK,KAAK,MAAM,IAAI,IAA8B;AAAA,EACzE,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,MAAI,SAAS,WAAW,KAAK;AAC3B,UAAM,IAAI;AAAA,MACR,MAAM,SAAS;AAAA,MACf;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAS,WAAW,KAAK;AAC3B,UAAM,IAAI;AAAA,MACR,MAAM,WAAW;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAS,SAAS,OAAO,SAAS,UAAU,KAAK;AACnD,UAAM,IAAI;AAAA,MACR,uBAAuB,SAAS,MAAM,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAS,MAAM;AACjB,UAAM,IAAI,gBAAgB,0CAA0C,cAAc;AAAA,EACpF;AAEA,SAAO;AACT;AAEA,SAAS,eAAe,OAAyB;AAC/C,MAAI,iBAAiB,OAAO;AAC1B,WAAO,MAAM,SAAS,kBAAkB,MAAM,SAAS;AAAA,EACzD;AACA,SAAO;AACT;;;ACpGA,IAAAC,mBAAoC;AACpC,IAAAC,oBAAiB;AAIjB,IAAM,kBAAkB;AAqBxB,eAAsB,cAAc,SAAgD;AAClF,QAAM,WAAW,MAAM,eAAe,QAAQ,GAAG;AACjD,QAAM,UAAU,QAAQ;AAExB,QAAM,WACJ,QAAQ,eACR,QAAQ,YACR,SAAS,YACT;AAEF,QAAM,WACJ,QAAQ,eACR,QAAQ,YACR,SAAS,YACT;AAEF,QAAM,YAAY,QAAQ,aAAa,SAAS,aAAa;AAE7D,MAAI,aAAa,QAAQ,SAAS,SAAS,KAAK,CAAC,OAAO,QAAQ,GAAG;AACjE,UAAM,IAAI;AAAA,MACR,cAAc,QAAQ;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,QAAQ,oBAAoB,aAAa,QAAQ,SAAS,WAAW,IAAI;AAC3E,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU,aAAa,QAAQ,SAAS,WAAW,IAAI,OAAO;AAAA,IAC9D;AAAA,IACA;AAAA,EACF;AACF;AAEA,eAAsB,gBAAgB,KAAa,QAAqC;AACtF,QAAM,SAAS,kBAAAC,QAAK,KAAK,KAAK,eAAe;AAC7C,QAAM,UAAU,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI;AAClD,YAAM,4BAAU,QAAQ,SAAS,MAAM;AACvC,SAAO;AACT;AAMA,eAAsB,gBAAgB,KAAa,UAAmC;AACpF,QAAM,WAAW,MAAM,eAAe,GAAG;AACzC,SAAO,gBAAgB,KAAK,EAAE,GAAG,UAAU,SAAS,CAAC;AACvD;AAEA,eAAe,eAAe,KAAkC;AAC9D,QAAM,SAAS,kBAAAA,QAAK,KAAK,KAAK,eAAe;AAC7C,MAAI;AACJ,MAAI;AACF,UAAM,UAAM,2BAAS,QAAQ,MAAM;AAAA,EACrC,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,UAAU;AACpD,aAAO,CAAC;AAAA,IACV;AACA,UAAM,IAAI;AAAA,MACR,kBAAkB,MAAM,KAAM,IAAc,OAAO;AAAA,MACnD;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACF,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,eAAe,MAAM;AAAA,MACrB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,UAAsB;AAC7B,QAAM,aAAa,QAAQ,IAAI;AAC/B,MAAI;AACJ,MAAI,eAAe,UAAa,WAAW,SAAS,GAAG;AACrD,UAAM,SAAS,OAAO,UAAU;AAChC,QAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,GAAG;AAC3C,YAAM,IAAI;AAAA,QACR,yDAAyD,UAAU;AAAA,QACnE;AAAA,MACF;AAAA,IACF;AACA,gBAAY;AAAA,EACd;AACA,SAAO;AAAA,IACL,UAAU,QAAQ,IAAI,wBAAwB;AAAA,IAC9C,UAAU,QAAQ,IAAI,uBAAuB;AAAA,IAC7C;AAAA,EACF;AACF;AAEA,SAAS,OAAO,OAAwB;AACtC,SAAO,kEAAkE,KAAK,KAAK;AACrF;;;APlGA,eAAsB,cACpB,UAAgC,CAAC,GACH;AAC9B,QAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;AACvC,QAAM,SAAS,QAAQ,UAAW,MAAM,cAAc,EAAE,IAAI,CAAC;AAC7D,QAAM,WAAW,MAAM,aAAa,GAAG;AACvC,QAAM,EAAE,SAAS,MAAM,IAAI,iBAAiB,QAAQ;AACpD,QAAM,WAAW,MAAM,aAAa,QAAQ,OAAO;AAInD,MAAI,OAAO,aAAa,QAAQ,SAAS,SAAS,UAAa,SAAS,KAAK,SAAS,GAAG;AACvF,UAAM,gBAAgB,KAAK,SAAS,IAAI;AAAA,EAC1C;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,gBAAgB,MAAM;AAAA,IACtB,aAAa,MAAM;AAAA,IACnB,cAAc,MAAM;AAAA,EACtB;AACF;","names":["import_promises","import_node_path","path","import_promises","import_node_path","path","path","import_promises","import_node_path","path"]} \ No newline at end of file +{"version":3,"sources":["../src/index.ts","../src/parsers/index.ts","../src/types.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/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 index e6b07ce..b5ad5b6 100644 --- a/dist/index.d.cts +++ b/dist/index.d.cts @@ -66,14 +66,14 @@ interface NormalizeResult { declare function buildWirePayload(manifest: Manifest): NormalizeResult; declare function compareVersions(a: string, b: string): number; -declare const DEFAULT_ENDPOINT = "http://api.patchstack.com/monitor/pulse/manifest"; +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 `/claim?site=`. Using the API endpoint's origin - * (rather than a hard-coded https://app.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. + * 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; diff --git a/dist/index.d.ts b/dist/index.d.ts index e6b07ce..b5ad5b6 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -66,14 +66,14 @@ interface NormalizeResult { declare function buildWirePayload(manifest: Manifest): NormalizeResult; declare function compareVersions(a: string, b: string): number; -declare const DEFAULT_ENDPOINT = "http://api.patchstack.com/monitor/pulse/manifest"; +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 `/claim?site=`. Using the API endpoint's origin - * (rather than a hard-coded https://app.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. + * 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; diff --git a/dist/index.js b/dist/index.js index a63fdbe..7253be4 100644 --- a/dist/index.js +++ b/dist/index.js @@ -380,7 +380,7 @@ function compareSegments(a, b) { } // src/client.ts -var DEFAULT_ENDPOINT = "http://api.patchstack.com/monitor/pulse/manifest"; +var DEFAULT_ENDPOINT = "https://api.patchstack.com/monitor/pulse/manifest"; var DEFAULT_TIMEOUT_MS = 3e4; function buildEndpointUrl(base, siteUuid) { const trimmed = base.replace(/\/$/, ""); @@ -388,7 +388,7 @@ function buildEndpointUrl(base, siteUuid) { } function buildClaimUrl(endpoint, siteUuid) { const origin = new URL(endpoint).origin; - return `${origin}/claim?site=${encodeURIComponent(siteUuid)}`; + return `${origin}/monitor/claim?site=${encodeURIComponent(siteUuid)}`; } async function postManifest(config, payload) { const url = buildEndpointUrl(config.endpoint, config.siteUuid); diff --git a/dist/index.js.map b/dist/index.js.map index e459e65..19a8503 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/parsers/index.ts","../src/types.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/normalize.ts","../src/client.ts","../src/config.ts","../src/index.ts"],"sourcesContent":["import { access } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Manifest, type PackageEntry } from '../types.js';\nimport { parseNpmLockfile } from './npm.js';\nimport { walkNodeModules } from './node_modules.js';\n\ntype LockfileFilename =\n | 'package-lock.json'\n | 'bun.lock'\n | 'bun.lockb'\n | 'yarn.lock'\n | 'pnpm-lock.yaml';\n\ntype DetectionStrategy = 'npm-lockfile' | 'node-modules-walk';\n\ninterface DetectedLockfile {\n ecosystem: 'npm';\n filePath: string;\n filename: LockfileFilename;\n strategy: DetectionStrategy;\n}\n\nexport async function detectLockfile(cwd: string): Promise {\n const npmLock = path.join(cwd, 'package-lock.json');\n if (await exists(npmLock)) {\n return {\n ecosystem: 'npm',\n filePath: npmLock,\n filename: 'package-lock.json',\n strategy: 'npm-lockfile',\n };\n }\n\n const bunLock = path.join(cwd, 'bun.lock');\n if (await exists(bunLock)) {\n return {\n ecosystem: 'npm',\n filePath: bunLock,\n filename: 'bun.lock',\n strategy: 'node-modules-walk',\n };\n }\n\n const bunLockB = path.join(cwd, 'bun.lockb');\n if (await exists(bunLockB)) {\n return {\n ecosystem: 'npm',\n filePath: bunLockB,\n filename: 'bun.lockb',\n strategy: 'node-modules-walk',\n };\n }\n\n const yarnLock = path.join(cwd, 'yarn.lock');\n if (await exists(yarnLock)) {\n throw new PatchstackError(\n 'yarn.lock detected but not yet supported. Run `npm install` to generate a package-lock.json, or open an issue at github.com/patchstack/connect.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n const pnpmLock = path.join(cwd, 'pnpm-lock.yaml');\n if (await exists(pnpmLock)) {\n throw new PatchstackError(\n 'pnpm-lock.yaml detected but not yet supported. Open an issue at github.com/patchstack/connect to request support.',\n 'LOCKFILE_UNSUPPORTED',\n );\n }\n\n throw new PatchstackError(\n `No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`,\n 'LOCKFILE_NOT_FOUND',\n );\n}\n\nexport async function scanLockfile(cwd: string): Promise {\n const detected = await detectLockfile(cwd);\n const packages = await runStrategy(detected, cwd);\n return { ecosystem: detected.ecosystem, packages };\n}\n\nasync function runStrategy(\n detected: DetectedLockfile,\n cwd: string,\n): Promise {\n switch (detected.strategy) {\n case 'npm-lockfile':\n return parseNpmLockfile(detected.filePath);\n case 'node-modules-walk':\n return walkNodeModules(cwd);\n }\n}\n\nasync function exists(filePath: string): Promise {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n","export type Ecosystem = 'npm' | 'composer';\n\nexport interface PackageEntry {\n name: string;\n version: string;\n path?: string;\n direct?: boolean;\n}\n\nexport interface Manifest {\n ecosystem: Ecosystem;\n packages: PackageEntry[];\n}\n\nexport interface Config {\n /**\n * The site UUID. `null` means we don't have one yet — `postManifest` will then\n * post to the bare endpoint, the server will provision a fresh site, and the\n * UUID it returns should be persisted via `persistSiteUuid()`.\n */\n siteUuid: string | null;\n endpoint: string;\n timeoutMs: number;\n}\n\nexport interface StoreManifestResponse {\n /** The UUID of the site the manifest was stored against. Always returned. */\n uuid?: string;\n stored: boolean;\n manifest_id?: number;\n checksum?: string;\n reason?: string;\n message?: string;\n error?: string;\n}\n\nexport class PatchstackError extends Error {\n constructor(\n message: string,\n public readonly code:\n | 'CONFIG_MISSING'\n | 'CONFIG_INVALID'\n | 'LOCKFILE_NOT_FOUND'\n | 'LOCKFILE_UNSUPPORTED'\n | 'LOCKFILE_PARSE_ERROR'\n | 'NETWORK_ERROR'\n | 'NETWORK_TIMEOUT'\n | 'SITE_NOT_FOUND'\n | 'VALIDATION_ERROR'\n | 'SERVER_ERROR',\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = 'PatchstackError';\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\ninterface LockfileV2Package {\n name?: string;\n version?: string;\n link?: boolean;\n resolved?: string;\n}\n\ninterface LockfileV2 {\n lockfileVersion: number;\n packages?: Record;\n dependencies?: Record;\n}\n\ninterface LockfileV1Dependency {\n version: string;\n dependencies?: Record;\n}\n\nexport async function parseNpmLockfile(lockfilePath: string): Promise {\n let raw: string;\n try {\n raw = await readFile(lockfilePath, 'utf8');\n } catch (cause) {\n throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, 'LOCKFILE_NOT_FOUND', cause);\n }\n\n let parsed: LockfileV2;\n try {\n parsed = JSON.parse(raw) as LockfileV2;\n } catch (cause) {\n throw new PatchstackError(`Lockfile at ${lockfilePath} is not valid JSON`, 'LOCKFILE_PARSE_ERROR', cause);\n }\n\n if (parsed.packages) {\n return extractFromV2(parsed.packages);\n }\n\n if (parsed.dependencies) {\n return extractFromV1(parsed.dependencies);\n }\n\n throw new PatchstackError(\n `Lockfile at ${lockfilePath} has no \"packages\" or \"dependencies\" key`,\n 'LOCKFILE_PARSE_ERROR',\n );\n}\n\nfunction extractFromV2(packages: Record): PackageEntry[] {\n const entries: PackageEntry[] = [];\n\n for (const [pkgPath, pkg] of Object.entries(packages)) {\n if (pkgPath === '') {\n continue;\n }\n if (pkg.link === true) {\n continue;\n }\n if (typeof pkg.version !== 'string' || pkg.version.length === 0) {\n continue;\n }\n\n const name = pkg.name ?? extractNameFromPath(pkgPath);\n if (name === null) {\n continue;\n }\n\n entries.push({\n name,\n version: pkg.version,\n path: pkgPath,\n direct: isDirectV2(pkgPath),\n });\n }\n\n return entries;\n}\n\nfunction extractFromV1(\n deps: Record,\n acc: PackageEntry[] = [],\n depth = 0,\n): PackageEntry[] {\n for (const [name, dep] of Object.entries(deps)) {\n if (typeof dep.version === 'string' && dep.version.length > 0) {\n acc.push({ name, version: dep.version, direct: depth === 0 });\n }\n if (dep.dependencies) {\n extractFromV1(dep.dependencies, acc, depth + 1);\n }\n }\n return acc;\n}\n\nfunction extractNameFromPath(pkgPath: string): string | null {\n const segments = pkgPath.split('node_modules' + path.sep === pkgPath ? path.sep : '/');\n const parts = pkgPath.split('/');\n const nmIndex = parts.lastIndexOf('node_modules');\n if (nmIndex === -1 || nmIndex >= parts.length - 1) {\n return segments[segments.length - 1] ?? null;\n }\n const tail = parts.slice(nmIndex + 1);\n if (tail.length === 0) {\n return null;\n }\n const first = tail[0];\n if (first !== undefined && first.startsWith('@') && tail.length >= 2) {\n return `${first}/${tail[1]}`;\n }\n return first ?? null;\n}\n\nfunction isDirectV2(pkgPath: string): boolean {\n const parts = pkgPath.split('/');\n const nmCount = parts.filter((p) => p === 'node_modules').length;\n return nmCount === 1;\n}\n","import { lstat, readFile, readdir, stat } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type PackageEntry } from '../types.js';\n\nexport async function walkNodeModules(cwd: string): Promise {\n const root = path.join(cwd, 'node_modules');\n\n try {\n const info = await stat(root);\n if (!info.isDirectory()) {\n throw new PatchstackError(\n `${root} exists but is not a directory.`,\n 'LOCKFILE_NOT_FOUND',\n );\n }\n } catch (cause) {\n if (cause instanceof PatchstackError) {\n throw cause;\n }\n throw new PatchstackError(\n `node_modules/ not found at ${cwd}. Install dependencies first (e.g. \\`bun install\\` or \\`npm install\\`).`,\n 'LOCKFILE_NOT_FOUND',\n cause,\n );\n }\n\n const entries: PackageEntry[] = [];\n await walk(root, entries, 0);\n return entries;\n}\n\nasync function walk(dir: string, acc: PackageEntry[], depth: number): Promise {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n return;\n }\n\n for (const name of names) {\n if (name.startsWith('.')) {\n continue;\n }\n\n const fullPath = path.join(dir, name);\n if (!(await isPlainDirectory(fullPath))) {\n continue;\n }\n\n if (name.startsWith('@')) {\n let subNames: string[];\n try {\n subNames = await readdir(fullPath);\n } catch {\n continue;\n }\n for (const sub of subNames) {\n if (sub.startsWith('.')) {\n continue;\n }\n const scopedDir = path.join(fullPath, sub);\n if (!(await isPlainDirectory(scopedDir))) {\n continue;\n }\n await readPackage(scopedDir, depth, acc);\n await walkNested(scopedDir, acc, depth);\n }\n continue;\n }\n\n await readPackage(fullPath, depth, acc);\n await walkNested(fullPath, acc, depth);\n }\n}\n\nasync function readPackage(\n pkgDir: string,\n depth: number,\n acc: PackageEntry[],\n): Promise {\n let raw: string;\n try {\n raw = await readFile(path.join(pkgDir, 'package.json'), 'utf8');\n } catch {\n return;\n }\n\n let parsed: { name?: unknown; version?: unknown };\n try {\n parsed = JSON.parse(raw) as { name?: unknown; version?: unknown };\n } catch {\n return;\n }\n\n if (typeof parsed.name !== 'string' || parsed.name.length === 0) {\n return;\n }\n if (typeof parsed.version !== 'string' || parsed.version.length === 0) {\n return;\n }\n\n acc.push({\n name: parsed.name,\n version: parsed.version,\n direct: depth === 0,\n });\n}\n\nasync function walkNested(\n pkgDir: string,\n acc: PackageEntry[],\n depth: number,\n): Promise {\n const nested = path.join(pkgDir, 'node_modules');\n if (!(await isPlainDirectory(nested))) {\n return;\n }\n await walk(nested, acc, depth + 1);\n}\n\nasync function isPlainDirectory(dir: string): Promise {\n try {\n const info = await lstat(dir);\n return info.isDirectory() && !info.isSymbolicLink();\n } catch {\n return false;\n }\n}\n","import type { Manifest, PackageEntry } from './types.js';\n\nexport interface WirePackage {\n name: string;\n version: string;\n}\n\nexport interface WirePayload {\n ecosystem: Manifest['ecosystem'];\n packages: WirePackage[];\n}\n\nexport interface NormalizeStats {\n uniqueNames: number;\n duplicateNames: string[];\n totalEntries: number;\n}\n\nexport interface NormalizeResult {\n payload: WirePayload;\n stats: NormalizeStats;\n}\n\nexport function buildWirePayload(manifest: Manifest): NormalizeResult {\n const seen = new Map>();\n const wirePackages: WirePackage[] = [];\n\n for (const entry of manifest.packages) {\n const versions = seen.get(entry.name);\n if (versions) {\n if (versions.has(entry.version)) {\n continue;\n }\n versions.add(entry.version);\n } else {\n seen.set(entry.name, new Set([entry.version]));\n }\n wirePackages.push({ name: entry.name, version: entry.version });\n }\n\n wirePackages.sort((a, b) => {\n if (a.name === b.name) {\n return compareVersions(a.version, b.version);\n }\n return a.name < b.name ? -1 : 1;\n });\n\n const duplicateNames: string[] = [];\n for (const [name, versions] of seen) {\n if (versions.size > 1) {\n duplicateNames.push(name);\n }\n }\n\n return {\n payload: { ecosystem: manifest.ecosystem, packages: wirePackages },\n stats: {\n uniqueNames: seen.size,\n duplicateNames,\n totalEntries: manifest.packages.length,\n },\n };\n}\n\nexport function compareVersions(a: string, b: string): number {\n if (a === b) {\n return 0;\n }\n\n const [aBase, aPre] = splitPrerelease(a);\n const [bBase, bPre] = splitPrerelease(b);\n\n const baseCmp = compareSegments(aBase.split('.'), bBase.split('.'));\n if (baseCmp !== 0) {\n return baseCmp;\n }\n\n if (aPre === null && bPre === null) {\n return 0;\n }\n if (aPre === null) {\n return 1;\n }\n if (bPre === null) {\n return -1;\n }\n return compareSegments(aPre.split('.'), bPre.split('.'));\n}\n\nfunction splitPrerelease(version: string): [string, string | null] {\n const cleaned = version.replace(/^[v=]+/, '').split('+')[0]!;\n const dashIndex = cleaned.indexOf('-');\n if (dashIndex === -1) {\n return [cleaned, null];\n }\n return [cleaned.slice(0, dashIndex), cleaned.slice(dashIndex + 1)];\n}\n\nfunction compareSegments(a: string[], b: string[]): number {\n const max = Math.max(a.length, b.length);\n for (let i = 0; i < max; i++) {\n const aPart = a[i];\n const bPart = b[i];\n if (aPart === undefined) {\n return -1;\n }\n if (bPart === undefined) {\n return 1;\n }\n const aNum = /^\\d+$/.test(aPart);\n const bNum = /^\\d+$/.test(bPart);\n if (aNum && bNum) {\n const diff = Number(aPart) - Number(bPart);\n if (diff !== 0) {\n return diff < 0 ? -1 : 1;\n }\n continue;\n }\n if (aNum) {\n return -1;\n }\n if (bNum) {\n return 1;\n }\n if (aPart < bPart) {\n return -1;\n }\n if (aPart > bPart) {\n return 1;\n }\n }\n return 0;\n}\n\nexport function findPackageInManifest(\n manifest: Manifest,\n name: string,\n): PackageEntry[] {\n return manifest.packages.filter((p) => p.name === name);\n}\n","import { PatchstackError, type Config, type StoreManifestResponse } from './types.js';\nimport type { WirePayload } from './normalize.js';\n\nexport const DEFAULT_ENDPOINT = 'http://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 `/claim?site=`. Using the API endpoint's origin\n * (rather than a hard-coded https://app.patchstack.com) means staging, ngrok\n * tunnels and local dev environments all produce a claim URL on the same host\n * 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}/claim?site=${encodeURIComponent(siteUuid)}`;\n}\n\nexport async function postManifest(\n config: Config,\n payload: WirePayload,\n): Promise {\n const url = buildEndpointUrl(config.endpoint, config.siteUuid);\n const timeoutMs = config.timeoutMs;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'User-Agent': '@patchstack/connect',\n },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (cause) {\n if (isTimeoutError(cause)) {\n throw new PatchstackError(\n `Patchstack request to ${url} timed out after ${timeoutMs}ms. Override with PATCHSTACK_TIMEOUT_MS.`,\n 'NETWORK_TIMEOUT',\n cause,\n );\n }\n throw new PatchstackError(\n `Could not reach Patchstack at ${url}. Check your network connection.`,\n 'NETWORK_ERROR',\n cause,\n );\n }\n\n const text = await response.text();\n let body: StoreManifestResponse | null = null;\n try {\n body = text.length > 0 ? (JSON.parse(text) as StoreManifestResponse) : null;\n } catch {\n body = null;\n }\n\n if (response.status === 404) {\n throw new PatchstackError(\n body?.error ?? 'Site not found. Check that your site UUID is correct and that the app is registered as a Pulse app in your Patchstack dashboard.',\n 'SITE_NOT_FOUND',\n );\n }\n\n if (response.status === 422) {\n throw new PatchstackError(\n body?.message ?? 'Patchstack rejected the manifest payload (validation failed).',\n 'VALIDATION_ERROR',\n );\n }\n\n if (response.status < 200 || response.status >= 300) {\n throw new PatchstackError(\n `Patchstack returned ${response.status}: ${text.slice(0, 200)}`,\n 'SERVER_ERROR',\n );\n }\n\n if (body === null) {\n throw new PatchstackError('Patchstack returned an empty response.', 'SERVER_ERROR');\n }\n\n return body;\n}\n\nfunction isTimeoutError(cause: unknown): boolean {\n if (cause instanceof Error) {\n return cause.name === 'TimeoutError' || cause.name === 'AbortError';\n }\n return false;\n}\n","import { readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { PatchstackError, type Config } from './types.js';\nimport { DEFAULT_ENDPOINT, DEFAULT_TIMEOUT_MS } from './client.js';\n\nconst CONFIG_FILENAME = '.patchstackrc.json';\n\ninterface ConfigFile {\n siteUuid?: string;\n endpoint?: string;\n timeoutMs?: number;\n}\n\nexport interface ResolveConfigOptions {\n cwd: string;\n cliSiteUuid?: string;\n cliEndpoint?: string;\n /**\n * When true, resolveConfig throws CONFIG_MISSING if no site UUID is configured.\n * Defaults to false: callers that can run without a UUID (the first `scan` after\n * `npm install`) just get `siteUuid: null` back and learn the UUID from the\n * server response.\n */\n requireSiteUuid?: boolean;\n}\n\nexport async function resolveConfig(options: ResolveConfigOptions): Promise {\n const fromFile = await readConfigFile(options.cwd);\n const fromEnv = readEnv();\n\n const siteUuid =\n options.cliSiteUuid ??\n fromEnv.siteUuid ??\n fromFile.siteUuid ??\n null;\n\n const endpoint =\n options.cliEndpoint ??\n fromEnv.endpoint ??\n fromFile.endpoint ??\n DEFAULT_ENDPOINT;\n\n const timeoutMs = fromEnv.timeoutMs ?? fromFile.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n if (siteUuid !== null && siteUuid.length > 0 && !isUuid(siteUuid)) {\n throw new PatchstackError(\n `Site UUID \"${siteUuid}\" does not look like a valid UUID.`,\n 'CONFIG_INVALID',\n );\n }\n\n if (options.requireSiteUuid && (siteUuid === null || siteUuid.length === 0)) {\n throw new PatchstackError(\n 'No site UUID configured. Run `patchstack-connect scan` to provision one, or set PATCHSTACK_SITE_UUID.',\n 'CONFIG_MISSING',\n );\n }\n\n return {\n siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid,\n endpoint,\n timeoutMs,\n };\n}\n\nexport async function writeConfigFile(cwd: string, config: ConfigFile): Promise {\n const target = path.join(cwd, CONFIG_FILENAME);\n const content = JSON.stringify(config, null, 2) + '\\n';\n await writeFile(target, content, 'utf8');\n return target;\n}\n\n/**\n * Merge a new siteUuid into the existing `.patchstackrc.json` (or create it).\n * Preserves any `endpoint` / `timeoutMs` the user already wrote.\n */\nexport async function persistSiteUuid(cwd: string, siteUuid: string): Promise {\n const existing = await readConfigFile(cwd);\n return writeConfigFile(cwd, { ...existing, siteUuid });\n}\n\nasync function readConfigFile(cwd: string): Promise {\n const target = path.join(cwd, CONFIG_FILENAME);\n let raw: string;\n try {\n raw = await readFile(target, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return {};\n }\n throw new PatchstackError(\n `Could not read ${target}: ${(err as Error).message}`,\n 'CONFIG_INVALID',\n err,\n );\n }\n\n try {\n return JSON.parse(raw) as ConfigFile;\n } catch (err) {\n throw new PatchstackError(\n `Config file ${target} contains invalid JSON.`,\n 'CONFIG_INVALID',\n err,\n );\n }\n}\n\nfunction readEnv(): ConfigFile {\n const timeoutRaw = process.env.PATCHSTACK_TIMEOUT_MS;\n let timeoutMs: number | undefined;\n if (timeoutRaw !== undefined && timeoutRaw.length > 0) {\n const parsed = Number(timeoutRaw);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n throw new PatchstackError(\n `PATCHSTACK_TIMEOUT_MS must be a positive number; got \"${timeoutRaw}\".`,\n 'CONFIG_INVALID',\n );\n }\n timeoutMs = parsed;\n }\n return {\n siteUuid: process.env.PATCHSTACK_SITE_UUID ?? undefined,\n endpoint: process.env.PATCHSTACK_ENDPOINT ?? undefined,\n timeoutMs,\n };\n}\n\nfunction isUuid(value: string): boolean {\n return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);\n}\n","import { scanLockfile } from './parsers/index.js';\nimport { buildWirePayload } from './normalize.js';\nimport { postManifest } from './client.js';\nimport { persistSiteUuid, resolveConfig } from './config.js';\nimport type { Config, Manifest, StoreManifestResponse } from './types.js';\n\nexport { scanLockfile, detectLockfile } from './parsers/index.js';\nexport { buildWirePayload, compareVersions } from './normalize.js';\nexport { postManifest, buildClaimUrl, buildEndpointUrl, DEFAULT_ENDPOINT } from './client.js';\nexport { persistSiteUuid, resolveConfig, writeConfigFile } from './config.js';\nexport {\n PatchstackError,\n type Config,\n type Ecosystem,\n type Manifest,\n type PackageEntry,\n type StoreManifestResponse,\n} from './types.js';\n\nexport interface ScanAndReportOptions {\n cwd?: string;\n config?: Config;\n}\n\nexport interface ScanAndReportResult {\n manifest: Manifest;\n response: StoreManifestResponse;\n duplicateNames: string[];\n uniqueNames: number;\n totalEntries: number;\n}\n\nexport async function scanAndReport(\n options: ScanAndReportOptions = {},\n): Promise {\n const cwd = options.cwd ?? process.cwd();\n const config = options.config ?? (await resolveConfig({ cwd }));\n const manifest = await scanLockfile(cwd);\n const { payload, stats } = buildWirePayload(manifest);\n const response = await postManifest(config, payload);\n\n // First-run convenience: if we didn't have a UUID and the server provisioned\n // one for us, persist it so subsequent runs target the same site.\n if (config.siteUuid === null && response.uuid !== undefined && response.uuid.length > 0) {\n await persistSiteUuid(cwd, response.uuid);\n }\n\n return {\n manifest,\n response,\n duplicateNames: stats.duplicateNames,\n uniqueNames: stats.uniqueNames,\n totalEntries: stats.totalEntries,\n };\n}\n"],"mappings":"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","names":["path","readFile","path","path","readFile","path","readFile","path","path","readFile"]} \ No newline at end of file +{"version":3,"sources":["../src/parsers/index.ts","../src/types.ts","../src/parsers/npm.ts","../src/parsers/node_modules.ts","../src/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 diff --git a/src/cli.ts b/src/cli.ts index 6519b67..a339e33 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -22,7 +22,7 @@ Options (for scan and status): Environment: PATCHSTACK_SITE_UUID Site UUID - PATCHSTACK_ENDPOINT API endpoint (default: http://api.patchstack.com/monitor/pulse/manifest) + 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. diff --git a/src/client.ts b/src/client.ts index 3c5a816..61a3da8 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,7 +1,7 @@ import { PatchstackError, type Config, type StoreManifestResponse } from './types.js'; import type { WirePayload } from './normalize.js'; -export const DEFAULT_ENDPOINT = 'http://api.patchstack.com/monitor/pulse/manifest'; +export const DEFAULT_ENDPOINT = 'https://api.patchstack.com/monitor/pulse/manifest'; export const DEFAULT_TIMEOUT_MS = 30_000; export function buildEndpointUrl(base: string, siteUuid?: string | null): string { @@ -13,14 +13,14 @@ export 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 `/claim?site=`. Using the API endpoint's origin - * (rather than a hard-coded https://app.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. + * 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. */ export function buildClaimUrl(endpoint: string, siteUuid: string): string { const origin = new URL(endpoint).origin; - return `${origin}/claim?site=${encodeURIComponent(siteUuid)}`; + return `${origin}/monitor/claim?site=${encodeURIComponent(siteUuid)}`; } export async function postManifest( diff --git a/tests/client.test.ts b/tests/client.test.ts index 34529f7..d31e8b2 100644 --- a/tests/client.test.ts +++ b/tests/client.test.ts @@ -25,39 +25,39 @@ describe('buildEndpointUrl', () => { }); describe('buildClaimUrl', () => { - it('uses the API endpoint origin plus /claim?site=', () => { + it('uses the API endpoint origin plus /monitor/claim?site=', () => { expect( buildClaimUrl('https://app.patchstack.com/monitor/pulse/manifest', 'abc-def'), - ).toBe('https://app.patchstack.com/claim?site=abc-def'); + ).toBe('https://app.patchstack.com/monitor/claim?site=abc-def'); }); it('preserves staging origins (ngrok)', () => { expect( buildClaimUrl('https://3ad1-18-170-248-162.ngrok-free.app/monitor/pulse/manifest', 'xyz'), - ).toBe('https://3ad1-18-170-248-162.ngrok-free.app/claim?site=xyz'); + ).toBe('https://3ad1-18-170-248-162.ngrok-free.app/monitor/claim?site=xyz'); }); it('drops the manifest path from the API endpoint', () => { expect( buildClaimUrl('https://example.com/some/deep/path', 'abc'), - ).toBe('https://example.com/claim?site=abc'); + ).toBe('https://example.com/monitor/claim?site=abc'); }); it('handles a trailing slash on the endpoint', () => { expect( buildClaimUrl('https://example.com/monitor/pulse/manifest/', 'abc'), - ).toBe('https://example.com/claim?site=abc'); + ).toBe('https://example.com/monitor/claim?site=abc'); }); it('url-encodes the uuid', () => { expect(buildClaimUrl('https://example.com/x', 'a b')).toBe( - 'https://example.com/claim?site=a%20b', + 'https://example.com/monitor/claim?site=a%20b', ); }); it('preserves the scheme (http vs https) of the API endpoint', () => { expect(buildClaimUrl('http://localhost:8001/monitor/pulse/manifest', 'abc')).toBe( - 'http://localhost:8001/claim?site=abc', + 'http://localhost:8001/monitor/claim?site=abc', ); }); });